Compare commits

..

5 Commits

Author SHA1 Message Date
Yifeng Wang f723d41bd8 chore: test result 2025-05-24 17:41:47 +08:00
Yifeng Wang cd753dcd83 chore: test 2025-05-24 17:32:17 +08:00
Yifeng Wang f1608d4298 fix: review 2025-05-24 13:54:00 +08:00
Yifeng Wang a4dd931b71 fix: test 2025-05-24 13:46:00 +08:00
Yifeng Wang ddc9cb7a3d feat(editor): support triangle and diamond shape in shape dom renderer 2025-05-24 13:46:00 +08:00
626 changed files with 9563 additions and 17727 deletions
+3 -77
View File
@@ -31,13 +31,9 @@
"properties": {
"queue": {
"type": "object",
"description": "The config for job queues\n@default {\"attempts\":5,\"backoff\":{\"type\":\"exponential\",\"delay\":1000},\"removeOnComplete\":true,\"removeOnFail\":{\"age\":86400,\"count\":500}}\n@link https://api.docs.bullmq.io/interfaces/v5.QueueOptions.html",
"description": "The config for job queues\n@default {\"attempts\":5,\"removeOnComplete\":true,\"removeOnFail\":{\"age\":86400,\"count\":500}}\n@link https://api.docs.bullmq.io/interfaces/v5.QueueOptions.html",
"default": {
"attempts": 5,
"backoff": {
"type": "exponential",
"delay": 1000
},
"removeOnComplete": true,
"removeOnFail": {
"age": 86400,
@@ -52,14 +48,14 @@
},
"queues.copilot": {
"type": "object",
"description": "The config for copilot job queue\n@default {\"concurrency\":10}",
"description": "The config for copilot job queue\n@default {\"concurrency\":5}",
"properties": {
"concurrency": {
"type": "number"
}
},
"default": {
"concurrency": 10
"concurrency": 5
}
},
"queues.doc": {
@@ -643,41 +639,6 @@
"apiKey": ""
}
},
"providers.geminiVertex": {
"type": "object",
"description": "The config for the google vertex provider.\n@default {}",
"properties": {
"location": {
"type": "string",
"description": "The location of the google vertex provider."
},
"project": {
"type": "string",
"description": "The project name of the google vertex provider."
},
"googleAuthOptions": {
"type": "object",
"description": "The google auth options for the google vertex provider.",
"properties": {
"credentials": {
"type": "object",
"description": "The credentials for the google vertex provider.",
"properties": {
"client_email": {
"type": "string",
"description": "The client email for the google vertex provider."
},
"private_key": {
"type": "string",
"description": "The private key for the google vertex provider."
}
}
}
}
}
},
"default": {}
},
"providers.perplexity": {
"type": "object",
"description": "The config for the perplexity provider.\n@default {\"apiKey\":\"\"}",
@@ -692,41 +653,6 @@
"apiKey": ""
}
},
"providers.anthropicVertex": {
"type": "object",
"description": "The config for the google vertex provider.\n@default {}",
"properties": {
"location": {
"type": "string",
"description": "The location of the google vertex provider."
},
"project": {
"type": "string",
"description": "The project name of the google vertex provider."
},
"googleAuthOptions": {
"type": "object",
"description": "The google auth options for the google vertex provider.",
"properties": {
"credentials": {
"type": "object",
"description": "The credentials for the google vertex provider.",
"properties": {
"client_email": {
"type": "string",
"description": "The client email for the google vertex provider."
},
"private_key": {
"type": "string",
"description": "The private key for the google vertex provider."
}
}
}
}
}
},
"default": {}
},
"unsplash": {
"type": "object",
"description": "The config for the unsplash key.\n@default {\"key\":\"\"}",
+7 -3
View File
@@ -29,7 +29,11 @@ runs:
- name: Import config
shell: bash
env:
DEFAULT_CONFIG: '{}'
run: |
printf '%s\n' "${SERVER_CONFIG:-$DEFAULT_CONFIG}" > ./packages/backend/server/config.json
printf '{"copilot":{"enabled":true,"providers.fal":{"apiKey":"%s"},"providers.gemini":{"apiKey":"%s"},"providers.openai":{"apiKey":"%s"},"providers.perplexity":{"apiKey":"%s"},"providers.anthropic":{"apiKey":"%s"},"exa":{"key":"%s"}}}' \
"$COPILOT_FAL_API_KEY" \
"$COPILOT_GOOGLE_API_KEY" \
"$COPILOT_OPENAI_API_KEY" \
"$COPILOT_PERPLEXITY_API_KEY" \
"$COPILOT_ANTHROPIC_API_KEY" \
"$COPILOT_EXA_API_KEY" > ./packages/backend/server/config.json
@@ -1,12 +1,11 @@
{{- if eq .Values.global.deployment.platform "gcp" -}}
apiVersion: monitoring.googleapis.com/v1
kind: PodMonitoring
kind: ClusterPodMonitoring
metadata:
name: "{{ .Release.Name }}-monitoring"
name: "{{ include "doc.fullname" . }}"
spec:
selector:
matchLabels:
app.kubernetes.io/instance: {{ .Release.Name }}
{{- include "doc.selectorLabels" . | nindent 4 }}
endpoints:
- port: 9464
interval: 30s
@@ -0,0 +1,12 @@
{{- if eq .Values.global.deployment.platform "gcp" -}}
apiVersion: monitoring.googleapis.com/v1
kind: ClusterPodMonitoring
metadata:
name: "{{ include "graphql.fullname" . }}"
spec:
selector:
{{- include "graphql.selectorLabels" . | nindent 4 }}
endpoints:
- port: 9464
interval: 30s
{{- end }}
@@ -0,0 +1,12 @@
{{- if eq .Values.global.deployment.platform "gcp" -}}
apiVersion: monitoring.googleapis.com/v1
kind: ClusterPodMonitoring
metadata:
name: "{{ include "renderer.fullname" . }}"
spec:
selector:
{{- include "renderer.selectorLabels" . | nindent 4 }}
endpoints:
- port: 9464
interval: 30s
{{- end }}
@@ -0,0 +1,12 @@
{{- if eq .Values.global.deployment.platform "gcp" -}}
apiVersion: monitoring.googleapis.com/v1
kind: ClusterPodMonitoring
metadata:
name: "{{ include "sync.fullname" . }}"
spec:
selector:
{{- include "sync.selectorLabels" . | nindent 4 }}
endpoints:
- port: 9464
interval: 30s
{{- end }}
-1
View File
@@ -138,7 +138,6 @@ jobs:
uses: ./.github/actions/build-rust
env:
AFFINE_PRO_PUBLIC_KEY: ${{ secrets.AFFINE_PRO_PUBLIC_KEY }}
AFFINE_PRO_LICENSE_AES_KEY: ${{ secrets.AFFINE_PRO_LICENSE_AES_KEY }}
with:
target: ${{ matrix.targets.name }}
package: '@affine/server-native'
+12 -2
View File
@@ -1001,7 +1001,12 @@ jobs:
- name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_GOOGLE_API_KEY: ${{ secrets.COPILOT_GOOGLE_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
COPILOT_ANTHROPIC_API_KEY: ${{ secrets.COPILOT_ANTHROPIC_API_KEY }}
COPILOT_EXA_API_KEY: ${{ secrets.COPILOT_EXA_API_KEY }}
uses: ./.github/actions/server-test-env
- name: Run server tests
@@ -1100,7 +1105,12 @@ jobs:
- name: Prepare Server Test Environment
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.e2efilter.outputs.changed == 'true' }}
env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_GOOGLE_API_KEY: ${{ secrets.COPILOT_GOOGLE_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
COPILOT_ANTHROPIC_API_KEY: ${{ secrets.COPILOT_ANTHROPIC_API_KEY }}
COPILOT_EXA_API_KEY: ${{ secrets.COPILOT_EXA_API_KEY }}
uses: ./.github/actions/server-test-env
- name: Run Copilot E2E Test ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
+12 -2
View File
@@ -81,7 +81,12 @@ jobs:
- name: Prepare Server Test Environment
env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_GOOGLE_API_KEY: ${{ secrets.COPILOT_GOOGLE_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
COPILOT_ANTHROPIC_API_KEY: ${{ secrets.COPILOT_ANTHROPIC_API_KEY }}
COPILOT_EXA_API_KEY: ${{ secrets.COPILOT_EXA_API_KEY }}
uses: ./.github/actions/server-test-env
- name: Run server tests
@@ -151,7 +156,12 @@ jobs:
- name: Prepare Server Test Environment
env:
SERVER_CONFIG: ${{ secrets.TEST_SERVER_CONFIG }}
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_GOOGLE_API_KEY: ${{ secrets.COPILOT_GOOGLE_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
COPILOT_ANTHROPIC_API_KEY: ${{ secrets.COPILOT_ANTHROPIC_API_KEY }}
COPILOT_EXA_API_KEY: ${{ secrets.COPILOT_EXA_API_KEY }}
uses: ./.github/actions/server-test-env
- name: Run Copilot E2E Test ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
Generated
+7 -50
View File
@@ -20,7 +20,8 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "adobe-cmap-parser"
version = "0.4.1"
source = "git+https://github.com/darkskygit/adobe-cmap-parser#610513ae6035c63eab69f33299b86c43693cabb4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3"
dependencies = [
"pom",
]
@@ -2736,9 +2737,9 @@ dependencies = [
[[package]]
name = "path-ext"
version = "0.1.2"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7603010004b5cdecf8006605bf7b6f07b0e59d3003010f52b767e91bf2582a45"
checksum = "0de7a86239a8b87b5094977b64893fcf0ed768072744dd4ee0df237686b2d815"
dependencies = [
"path-slash",
"walkdir",
@@ -2753,7 +2754,7 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
[[package]]
name = "pdf-extract"
version = "0.8.2"
source = "git+https://github.com/toeverything/pdf-extract?branch=darksky%2Fimprove-font-decoding#040751a61aba51e7a28217b758c18db4415c3ee4"
source = "git+https://github.com/toeverything/pdf-extract?branch=darksky%2Fimprove-font-decoding#e74beed894e1b8dc228c2bf078ed92814b27759f"
dependencies = [
"adobe-cmap-parser",
"cff-parser",
@@ -2762,7 +2763,6 @@ dependencies = [
"log",
"lopdf",
"postscript",
"rust-embed",
"type1-encoding-parser",
"unicode-normalization",
]
@@ -2943,12 +2943,9 @@ checksum = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6"
[[package]]
name = "postscript"
version = "0.19.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2238e788cf2c9b6edc23b83cf8ccdd4a6380cc9bf0598cc220fac42a55def6"
dependencies = [
"typeface",
]
checksum = "78451badbdaebaf17f053fd9152b3ffb33b516104eacb45e7864aaa9c712f306"
[[package]]
name = "potential_utf"
@@ -3336,40 +3333,6 @@ dependencies = [
"realfft",
]
[[package]]
name = "rust-embed"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.101",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -4707,12 +4670,6 @@ dependencies = [
"pom",
]
[[package]]
name = "typeface"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f6b49e025f4dc953a29b83e4f5a905089117d09fa53491015d7678951b8be1"
[[package]]
name = "typenum"
version = "1.18.0"
+1 -1
View File
@@ -57,7 +57,7 @@ objc2-foundation = "0.3"
once_cell = "1"
ordered-float = "5"
parking_lot = "0.12"
path-ext = "0.1.2"
path-ext = "0.1.1"
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
phf = { version = "0.11", features = ["macros"] }
proptest = "1.3"
@@ -4393,61 +4393,6 @@ hhh
},
children: [],
},
{
type: 'block',
id: 'matchesReplaceMap[2]',
flavour: 'affine:paragraph',
props: {
type: 'h6',
text: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'Sources',
},
],
},
collapsed: true,
},
children: [],
},
{
type: 'block',
id: 'matchesReplaceMap[3]',
flavour: 'affine:bookmark',
props: {
style: 'citation',
url,
title,
description,
icon: favicon,
footnoteIdentifier: '1',
},
children: [],
},
{
type: 'block',
id: 'matchesReplaceMap[4]',
flavour: 'affine:embed-linked-doc',
props: {
style: 'citation',
pageId: 'deadbeef',
footnoteIdentifier: '2',
},
children: [],
},
{
type: 'block',
id: 'matchesReplaceMap[5]',
flavour: 'affine:attachment',
props: {
name: 'test.txt',
sourceId: 'abcdefg',
footnoteIdentifier: '3',
style: 'citation',
},
children: [],
},
],
};
@@ -4524,38 +4469,6 @@ hhh
},
children: [],
},
{
type: 'block',
id: 'matchesReplaceMap[2]',
flavour: 'affine:paragraph',
props: {
type: 'h6',
text: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'Sources',
},
],
},
collapsed: true,
},
children: [],
},
{
type: 'block',
id: 'matchesReplaceMap[3]',
flavour: 'affine:bookmark',
props: {
style: 'citation',
url,
title,
description,
icon: favicon,
footnoteIdentifier: '1',
},
children: [],
},
],
};
@@ -23,7 +23,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"file-type": "^21.0.0",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -10,6 +10,7 @@ import {
isFootnoteDefinitionNode,
type MarkdownAST,
} from '@blocksuite/affine-shared/adapters';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { nanoid } from '@blocksuite/store';
const isAttachmentFootnoteDefinitionNode = (node: MarkdownAST) => {
@@ -35,7 +36,15 @@ export const attachmentBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher
fromMatch: o => o.node.flavour === AttachmentBlockSchema.model.flavour,
toBlockSnapshot: {
enter: (o, context) => {
if (!isFootnoteDefinitionNode(o.node)) {
const { provider } = context;
let enableCitation = false;
try {
const featureFlagService = provider?.get(FeatureFlagService);
enableCitation = !!featureFlagService?.getFlag('enable_citation');
} catch {
enableCitation = false;
}
if (!isFootnoteDefinitionNode(o.node) || !enableCitation) {
return;
}
@@ -64,7 +73,6 @@ export const attachmentBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher
name: fileName,
sourceId: blobId,
footnoteIdentifier,
style: 'citation',
},
children: [],
},
@@ -4,7 +4,7 @@ import {
} from '@blocksuite/affine-components/caption';
import {
getAttachmentFileIcon,
LoadingIcon,
getLoadingIconWith,
} from '@blocksuite/affine-components/icons';
import { Peekable } from '@blocksuite/affine-components/peek';
import {
@@ -20,6 +20,7 @@ import {
DocModeProvider,
FileSizeLimitProvider,
TelemetryProvider,
ThemeProvider,
} from '@blocksuite/affine-shared/services';
import { formatSize } from '@blocksuite/affine-shared/utils';
import {
@@ -64,11 +65,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
return this.resourceController.blobUrl$.value;
}
get filetype() {
const name = this.model.props.name$.value;
return name.split('.').pop() ?? '';
}
protected containerStyleMap = styleMap({
position: 'relative',
width: '100%',
@@ -217,23 +213,13 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
);
};
protected renderNormalButton = (needUpload: boolean) => {
const label = needUpload ? 'retry' : 'reload';
const run = async () => {
if (needUpload) {
await this.resourceController.upload();
return;
}
this.refreshData();
};
protected renderReloadButton = () => {
return html`
<button
class="affine-attachment-content-button"
@click=${(event: MouseEvent) => {
event.stopPropagation();
run().catch(console.error);
this.refreshData();
{
const mode =
@@ -245,28 +231,21 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
segment,
page: `${segment} editor`,
module: 'attachment',
control: label,
control: 'reload',
category: 'card',
type: this.filetype,
type: this.model.props.name.split('.').pop() ?? '',
});
}
}}
>
${ResetIcon()} ${label}
${ResetIcon()} Reload
</button>
`;
};
protected renderWithHorizontal(
classInfo: ClassInfo,
{
icon,
title,
description,
kind,
state,
needUpload,
}: AttachmentResolvedStateInfo
{ icon, title, description, kind, state }: AttachmentResolvedStateInfo
) {
return html`
<div class=${classMap(classInfo)}>
@@ -283,7 +262,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
${description}
</div>
${choose(state, [
['error', () => this.renderNormalButton(needUpload)],
['error', this.renderReloadButton],
['error:oversize', this.renderUpgradeButton],
])}
</div>
@@ -296,14 +275,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
protected renderWithVertical(
classInfo: ClassInfo,
{
icon,
title,
description,
kind,
state,
needUpload,
}: AttachmentResolvedStateInfo
{ icon, title, description, kind, state }: AttachmentResolvedStateInfo
) {
return html`
<div class=${classMap(classInfo)}>
@@ -323,7 +295,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
<div class="affine-attachment-banner">
${kind}
${choose(state, [
['error', () => this.renderNormalButton(needUpload)],
['error', this.renderReloadButton],
['error:oversize', this.renderUpgradeButton],
])}
</div>
@@ -332,12 +304,15 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
}
protected resolvedState$ = computed<AttachmentResolvedStateInfo>(() => {
const theme = this.std.get(ThemeProvider).theme$.value;
const loadingIcon = getLoadingIconWith(theme);
const size = this.model.props.size;
const name = this.model.props.name$.value;
const kind = getAttachmentFileIcon(this.filetype);
const kind = getAttachmentFileIcon(name.split('.').pop() ?? '');
const resolvedState = this.resourceController.resolveStateWith({
loadingIcon: LoadingIcon(),
loadingIcon,
errorIcon: WarningIcon(),
icon: AttachmentIcon(),
title: name,
@@ -388,16 +363,11 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
const message = resolvedState.description;
if (!message) return null;
const needUpload = resolvedState.needUpload;
const action = () =>
needUpload ? this.resourceController.upload() : this.reload();
return html`
<affine-resource-status
class="affine-attachment-embed-status"
.message=${message}
.needUpload=${needUpload}
.action=${action}
.reload=${() => this.reload()}
></affine-resource-status>
`;
})}
@@ -406,10 +376,10 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
private readonly _renderCitation = () => {
const { name, footnoteIdentifier } = this.model.props;
const icon = getAttachmentFileIcon(this.filetype);
const fileType = name.split('.').pop() ?? '';
const fileTypeIcon = getAttachmentFileIcon(fileType);
return html`<affine-citation-card
.icon=${icon}
.icon=${fileTypeIcon}
.citationTitle=${name}
.citationIdentifier=${footnoteIdentifier}
.active=${this.selected$.value}
@@ -1,4 +1,4 @@
import { openSingleFileWith } from '@blocksuite/affine-shared/utils';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils';
import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu';
import { ExportToPdfIcon, FileIcon } from '@blocksuite/icons/lit';
@@ -21,7 +21,7 @@ export const attachmentSlashMenuConfig: SlashMenuConfig = {
model.store.schema.flavourSchemaMap.has('affine:attachment'),
action: ({ std, model }) => {
(async () => {
const file = await openSingleFileWith();
const file = await openFileOrFiles();
if (!file) return;
await addSiblingAttachmentBlocks(std, [file], model);
@@ -44,7 +44,7 @@ export const attachmentSlashMenuConfig: SlashMenuConfig = {
model.store.schema.flavourSchemaMap.has('affine:attachment'),
action: ({ std, model }) => {
(async () => {
const file = await openSingleFileWith();
const file = await openFileOrFiles();
if (!file) return;
await addSiblingAttachmentBlocks(std, [file], model);
@@ -47,10 +47,11 @@ export const styles = css`
.affine-attachment-content-title-icon {
display: flex;
width: 16px;
height: 16px;
align-items: center;
justify-content: center;
color: var(--affine-text-primary-color);
font-size: 16px;
}
.affine-attachment-content-title-text {
@@ -91,7 +92,6 @@ export const styles = css`
font-size: var(--affine-font-xs);
font-style: normal;
font-weight: 500;
text-transform: capitalize;
line-height: 20px;
svg {
@@ -107,7 +107,7 @@ export const styles = css`
.affine-attachment-card.loading {
.affine-attachment-content-title-text {
color: ${unsafeCSSVarV2('text/placeholder')};
color: var(--affine-placeholder-color);
}
}
@@ -24,7 +24,7 @@
"@blocksuite/store": "workspace:*",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
@@ -10,6 +10,7 @@ import {
isFootnoteDefinitionNode,
type MarkdownAST,
} from '@blocksuite/affine-shared/adapters';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { nanoid } from '@blocksuite/store';
const isUrlFootnoteDefinitionNode = (node: MarkdownAST) => {
@@ -32,7 +33,15 @@ export const bookmarkBlockMarkdownAdapterMatcher =
toMatch: o => isUrlFootnoteDefinitionNode(o.node),
toBlockSnapshot: {
enter: (o, context) => {
if (!isFootnoteDefinitionNode(o.node)) {
const { provider } = context;
let enableCitation = false;
try {
const featureFlagService = provider?.get(FeatureFlagService);
enableCitation = !!featureFlagService?.getFlag('enable_citation');
} catch {
enableCitation = false;
}
if (!isFootnoteDefinitionNode(o.node) || !enableCitation) {
return;
}
@@ -29,15 +29,6 @@ export class BookmarkEdgelessBlockComponent extends toGfxBlockComponent(
};
}
override connectedCallback(): void {
super.connectedCallback();
this.disposables.add(
this.gfx.selection.slots.updated.subscribe(() => {
this.requestUpdate();
})
);
}
override renderGfxBlock() {
const style = this.model.props.style$.value;
const width = EMBED_CARD_WIDTH[style];
@@ -45,14 +36,12 @@ export class BookmarkEdgelessBlockComponent extends toGfxBlockComponent(
const bound = this.model.elementBound;
const scaleX = bound.w / width;
const scaleY = bound.h / height;
const isSelected = this.gfx.selection.has(this.model.id);
this.containerStyleMap = styleMap({
width: `100%`,
height: `100%`,
transform: `scale(${scaleX}, ${scaleY})`,
transformOrigin: '0 0',
pointerEvents: isSelected ? 'auto' : 'none',
});
return this.renderPageContent();
@@ -1,5 +1,5 @@
import { getEmbedCardIcons } from '@blocksuite/affine-block-embed';
import { LoadingIcon, WebIcon16 } from '@blocksuite/affine-components/icons';
import { WebIcon16 } from '@blocksuite/affine-components/icons';
import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { getHostName } from '@blocksuite/affine-shared/utils';
@@ -60,11 +60,11 @@ export class BookmarkCard extends SignalWatcher(
: title;
const theme = this.bookmark.std.get(ThemeProvider).theme;
const { EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const imageProxyService = this.bookmark.store.get(ImageProxyService);
const titleIcon = this.loading
? LoadingIcon()
? LoadingIcon
: icon
? html`<img src=${imageProxyService.buildUrl(icon)} alt="icon" />`
: WebIcon16;
@@ -25,7 +25,7 @@
"@floating-ui/dom": "^1.6.10",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"emoji-mart": "^5.6.0",
"lit": "^3.2.0",
@@ -12,7 +12,6 @@ import type { BlockComponent } from '@blocksuite/std';
import { flip, offset } from '@floating-ui/dom';
import { css, html } from 'lit';
import { query } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockModel> {
static override styles = css`
:host {
@@ -110,18 +109,14 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
}
override renderBlock() {
const emoji = this.model.props.emoji$.value;
return html`
<div class="affine-callout-block-container">
<div
@click=${this._toggleEmojiMenu}
contenteditable="false"
class="affine-callout-emoji-container"
style=${styleMap({
display: emoji.length === 0 ? 'none' : undefined,
})}
>
<span class="affine-callout-emoji">${emoji}</span>
<span class="affine-callout-emoji">${this.model.props.emoji$}</span>
</div>
<div class="affine-callout-children">
${this.renderChildren(this.model)}
+1 -1
View File
@@ -27,7 +27,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -35,10 +35,14 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
.code-toolbar-button {
color: ${unsafeCSSVarV2('icon/primary')};
background-color: ${unsafeCSSVarV2('button/secondary')};
background-color: ${unsafeCSSVarV2('segment/background')};
box-shadow: var(--affine-shadow-1);
border-radius: 4px;
}
.copy-code {
margin-left: auto;
}
`;
private _currentOpenMenu: AbortController | null = null;
@@ -4,10 +4,6 @@ import {
showPopFilterableList,
} from '@blocksuite/affine-components/filterable-list';
import { ArrowDownIcon } from '@blocksuite/affine-components/icons';
import {
DocModeProvider,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { noop } from '@blocksuite/global/utils';
@@ -77,18 +73,6 @@ export class LanguageListButton extends WithDisposable(
this.blockComponent.store.transact(() => {
this.blockComponent.model.props.language$.value = item.name;
});
const std = this.blockComponent.std;
const mode =
std.getOptional(DocModeProvider)?.getEditorMode() ?? 'page';
const telemetryService = std.getOptional(TelemetryProvider);
if (!telemetryService) return;
telemetryService.track('codeBlockLanguageSelect', {
page: mode,
segment: 'code block',
module: 'language selector',
control: item.name,
});
},
active: item => item.name === this.blockComponent.model.props.language,
items: this._sortedBundledLanguages,
@@ -1,7 +1,3 @@
import {
DocModeProvider,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { css, html, LitElement, nothing } from 'lit';
@@ -13,10 +9,6 @@ import { CodeBlockPreviewIdentifier } from '../../code-preview-extension';
export class PreviewButton extends WithDisposable(SignalWatcher(LitElement)) {
static override styles = css`
:host {
margin-right: auto;
}
.preview-toggle-container {
display: flex;
padding: 2px;
@@ -63,17 +55,6 @@ export class PreviewButton extends WithDisposable(SignalWatcher(LitElement)) {
this.blockComponent.store.updateBlock(this.blockComponent.model, {
preview: value,
});
const std = this.blockComponent.std;
const mode = std.getOptional(DocModeProvider)?.getEditorMode() ?? 'page';
const telemetryService = std.getOptional(TelemetryProvider);
if (!telemetryService) return;
telemetryService.track('htmlBlockTogglePreview', {
page: mode,
segment: 'code block',
module: 'code toolbar container',
control: 'preview toggle button',
});
};
get preview() {
@@ -117,12 +117,13 @@ export const PRIMARY_GROUPS: MenuItemGroup<CodeBlockToolbarContext>[] = [
},
];
export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
type: 'toggle',
// Clipboard Group
export const clipboardGroup: MenuItemGroup<CodeBlockToolbarContext> = {
type: 'clipboard',
items: [
{
type: 'wrap',
generate: ({ blockComponent }) => {
generate: ({ blockComponent, close }) => {
return {
action: () => {},
render: () => {
@@ -133,6 +134,7 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
<editor-menu-action
@click=${() => {
blockComponent.setWrap(!wrapped);
close();
}}
aria-label=${label}
>
@@ -153,7 +155,7 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
when: ({ std }) =>
std.getOptional(CodeBlockConfigExtension.identifier)?.showLineNumbers ??
true,
generate: ({ blockComponent }) => {
generate: ({ blockComponent, close }) => {
return {
action: () => {},
render: () => {
@@ -165,6 +167,8 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
blockComponent.store.updateBlock(blockComponent.model, {
lineNumber: !lineNumber,
});
close();
}}
aria-label=${label}
>
@@ -180,13 +184,6 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
};
},
},
],
};
// Clipboard Group
export const clipboardGroup: MenuItemGroup<CodeBlockToolbarContext> = {
type: 'clipboard',
items: [
{
type: 'duplicate',
label: 'Duplicate',
@@ -236,7 +233,6 @@ export const deleteGroup: MenuItemGroup<CodeBlockToolbarContext> = {
};
export const MORE_GROUPS: MenuItemGroup<CodeBlockToolbarContext>[] = [
toggleGroup,
clipboardGroup,
deleteGroup,
];
@@ -24,7 +24,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -23,9 +23,9 @@ import {
createRecordDetail,
createUniComponentFromWebComponent,
type DataSource,
DataView,
dataViewCommonStyle,
type DataViewProps,
DataViewRootUILogic,
type DataViewSelection,
type DataViewWidget,
type DataViewWidgetProps,
@@ -133,6 +133,8 @@ export class DataViewBlockComponent extends CaptionedBlockComponent<DataViewBloc
private _dataSource?: DataSource;
private readonly dataView = new DataView();
_bindHotkey: DataViewProps['bindHotkey'] = hotkeys => {
return {
dispose: this.host.event.bindHotkey(hotkeys, {
@@ -230,6 +232,10 @@ export class DataViewBlockComponent extends CaptionedBlockComponent<DataViewBloc
return this.rootComponent;
}
get view() {
return this.dataView.expose;
}
private renderDatabaseOps() {
if (this.store.readonly) {
return nothing;
@@ -244,68 +250,68 @@ export class DataViewBlockComponent extends CaptionedBlockComponent<DataViewBloc
this.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true');
}
private readonly dataViewRootLogic = new DataViewRootUILogic({
virtualPadding$: signal(0),
bindHotkey: this._bindHotkey,
handleEvent: this._handleEvent,
selection$: this.selection$,
setSelection: this.setSelection,
dataSource: this.dataSource,
headerWidget: this.headerWidget,
clipboard: this.std.clipboard,
notification: {
toast: message => {
const notification = this.std.getOptional(NotificationProvider);
if (notification) {
notification.toast(message);
} else {
toast(this.host, message);
}
},
},
eventTrace: (key, params) => {
const telemetryService = this.std.getOptional(TelemetryProvider);
telemetryService?.track(key, {
...(params as TelemetryEventMap[typeof key]),
blockId: this.blockId,
});
},
detailPanelConfig: {
openDetailPanel: (target, data) => {
const peekViewService = this.std.getOptional(PeekViewProvider);
if (peekViewService) {
const template = createRecordDetail({
...data,
openDoc: () => {},
detail: {
header: uniMap(
createUniComponentFromWebComponent(BlockRenderer),
props => ({
...props,
host: this.host,
})
),
note: uniMap(
createUniComponentFromWebComponent(NoteRenderer),
props => ({
...props,
model: this.model,
host: this.host,
})
),
},
});
return peekViewService.peek({ target, template });
} else {
return Promise.resolve();
}
},
},
});
override renderBlock() {
const peekViewService = this.std.getOptional(PeekViewProvider);
const telemetryService = this.std.getOptional(TelemetryProvider);
return html`
<div contenteditable="false" style="position: relative">
${this.dataViewRootLogic.render()}
${this.dataView.render({
virtualPadding$: signal(0),
bindHotkey: this._bindHotkey,
handleEvent: this._handleEvent,
selection$: this.selection$,
setSelection: this.setSelection,
dataSource: this.dataSource,
headerWidget: this.headerWidget,
clipboard: this.std.clipboard,
notification: {
toast: message => {
const notification = this.std.getOptional(NotificationProvider);
if (notification) {
notification.toast(message);
} else {
toast(this.host, message);
}
},
},
eventTrace: (key, params) => {
telemetryService?.track(key, {
...(params as TelemetryEventMap[typeof key]),
blockId: this.blockId,
});
},
detailPanelConfig: {
openDetailPanel: (target, data) => {
if (peekViewService) {
const template = createRecordDetail({
...data,
openDoc: () => {},
detail: {
header: uniMap(
createUniComponentFromWebComponent(BlockRenderer),
props => ({
...props,
host: this.host,
})
),
note: uniMap(
createUniComponentFromWebComponent(NoteRenderer),
props => ({
...props,
model: this.model,
host: this.host,
})
),
},
});
return peekViewService.peek({ target, template });
} else {
return Promise.resolve();
}
},
},
})}
</div>
`;
}
@@ -28,7 +28,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"date-fns": "^4.0.0",
"lit": "^3.2.0",
@@ -1,19 +1,15 @@
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import type { DataViewUILogicBase } from '@blocksuite/data-view';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { WithDisposable } from '@blocksuite/global/lit';
import { ShadowlessElement } from '@blocksuite/std';
import type { Text } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import { css, html } from 'lit';
import { property, query } from 'lit/decorators.js';
import { property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import type { DatabaseBlockComponent } from '../../database-block.js';
export class DatabaseTitle extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
export class DatabaseTitle extends WithDisposable(ShadowlessElement) {
static override styles = css`
.affine-database-title {
position: relative;
@@ -75,23 +71,22 @@ export class DatabaseTitle extends SignalWatcher(
`;
private readonly compositionEnd = () => {
this.isComposing$.value = false;
this.titleText.replace(0, this.titleText.length, this.input.value);
};
private readonly onBlur = () => {
this.isFocus$.value = false;
this.isFocus = false;
};
private readonly onFocus = () => {
this.isFocus$.value = true;
if (this.dataViewLogic.selection$.value) {
this.dataViewLogic.setSelection(undefined);
this.isFocus = true;
if (this.database?.viewSelection$?.value) {
this.database?.setSelection(undefined);
}
};
private readonly onInput = (e: InputEvent) => {
this.text$.value = this.input.value;
this.text = this.input.value;
if (!e.isComposing) {
this.titleText.replace(0, this.titleText.length, this.input.value);
}
@@ -107,9 +102,9 @@ export class DatabaseTitle extends SignalWatcher(
};
updateText = () => {
if (!this.isFocus$.value) {
if (!this.isFocus) {
this.input.value = this.titleText.toString();
this.text$.value = this.input.value;
this.text = this.input.value;
}
};
@@ -129,25 +124,25 @@ export class DatabaseTitle extends SignalWatcher(
}
override render() {
const isEmpty = !this.text$.value;
const isEmpty = !this.text;
const classList = classMap({
'affine-database-title': true,
ellipsis: !this.isFocus$.value,
ellipsis: !this.isFocus,
});
const untitledStyle = styleMap({
height: isEmpty ? 'auto' : 0,
opacity: isEmpty && !this.isFocus$.value ? 1 : 0,
opacity: isEmpty && !this.isFocus ? 1 : 0,
});
return html` <div
class="${classList}"
data-title-empty="${isEmpty}"
data-title-focus="${this.isFocus$.value}"
data-title-focus="${this.isFocus}"
>
<div class="text" style="${untitledStyle}">Untitled</div>
<div class="text">${this.text$.value}</div>
<div class="text">${this.text}</div>
<textarea
.disabled="${this.readonly$.value}"
.disabled="${this.readonly}"
@input="${this.onInput}"
@keydown="${this.onKeyDown}"
@copy="${stopPropagation}"
@@ -164,24 +159,23 @@ export class DatabaseTitle extends SignalWatcher(
@query('textarea')
private accessor input!: HTMLTextAreaElement;
private readonly isComposing$ = signal(false);
private readonly isFocus$ = signal(false);
@state()
accessor isComposing = false;
private onPressEnterKey() {
this.dataViewLogic.addRow?.('start');
}
@state()
private accessor isFocus = false;
get readonly$() {
return this.dataViewLogic.view.readonly$;
}
@property({ attribute: false })
accessor onPressEnterKey: (() => void) | undefined = undefined;
private readonly text$ = signal('');
@property({ attribute: false })
accessor readonly!: boolean;
@state()
private accessor text = '';
@property({ attribute: false })
accessor titleText!: Text;
@property({ attribute: false })
accessor dataViewLogic!: DataViewUILogicBase;
}
declare global {
@@ -1,73 +0,0 @@
import { css } from '@emotion/css';
import { cssVarV2 } from '@toeverything/theme/v2';
export const databaseBlockStyles = css({
display: 'block',
borderRadius: '8px',
backgroundColor: 'var(--affine-background-primary-color)',
padding: '8px',
margin: '8px -8px -8px',
});
export const databaseBlockSelectedStyles = css({
backgroundColor: 'var(--affine-hover-color)',
borderRadius: '4px',
});
export const databaseOpsStyles = css({
padding: '2px',
borderRadius: '4px',
display: 'flex',
cursor: 'pointer',
alignItems: 'center',
height: 'max-content',
fontSize: '16px',
color: cssVarV2.icon.primary,
':hover': {
backgroundColor: 'var(--affine-hover-color)',
},
'@media print': {
display: 'none',
},
});
export const databaseHeaderBarStyles = css({
'@media print': {
display: 'none !important',
},
});
export const databaseTitleStyles = css({
overflow: 'hidden',
});
export const databaseHeaderContainerStyles = css({
marginBottom: '16px',
display: 'flex',
flexDirection: 'column',
});
export const databaseTitleRowStyles = css({
display: 'flex',
gap: '12px',
marginBottom: '8px',
alignItems: 'center',
});
export const databaseToolbarRowStyles = css({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px',
});
export const databaseViewBarContainerStyles = css({
flex: 1,
});
export const databaseContentStyles = css({
position: 'relative',
backgroundColor: 'var(--affine-background-primary-color)',
borderRadius: '4px',
});
@@ -19,14 +19,15 @@ import { getDropResult } from '@blocksuite/affine-widget-drag-handle';
import {
createRecordDetail,
createUniComponentFromWebComponent,
DataViewRootUILogic,
DataView,
dataViewCommonStyle,
type DataViewInstance,
type DataViewProps,
type DataViewSelection,
type DataViewUILogicBase,
type DataViewWidget,
type DataViewWidgetProps,
defineUniComponent,
ExternalGroupByConfigProvider,
lazy,
renderUniLit,
type SingleView,
uniMap,
@@ -43,23 +44,12 @@ import { RANGE_SYNC_EXCLUDE_ATTR } from '@blocksuite/std/inline';
import { Slice } from '@blocksuite/store';
import { autoUpdate } from '@floating-ui/dom';
import { computed, signal } from '@preact/signals-core';
import { html, nothing } from 'lit';
import { css, html, nothing, unsafeCSS } from 'lit';
import { popSideDetail } from './components/layout.js';
import { DatabaseConfigExtension } from './config.js';
import { EditorHostKey } from './context/host-context.js';
import { DatabaseBlockDataSource } from './data-source.js';
import {
databaseBlockStyles,
databaseContentStyles,
databaseHeaderBarStyles,
databaseHeaderContainerStyles,
databaseOpsStyles,
databaseTitleRowStyles,
databaseTitleStyles,
databaseToolbarRowStyles,
databaseViewBarContainerStyles,
} from './database-block-styles.js';
import { BlockRenderer } from './detail-panel/block-renderer.js';
import { NoteRenderer } from './detail-panel/note-renderer.js';
import { DatabaseSelection } from './selection.js';
@@ -68,7 +58,52 @@ import { getSingleDocIdFromText } from './utils/title-doc.js';
import type { DatabaseViewExtensionOptions } from './view';
export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBlockModel> {
private readonly clickDatabaseOps = (e: MouseEvent) => {
static override styles = css`
${unsafeCSS(dataViewCommonStyle('affine-database'))}
affine-database {
display: block;
border-radius: 8px;
background-color: var(--affine-background-primary-color);
padding: 8px;
margin: 8px -8px -8px;
}
.database-block-selected {
background-color: var(--affine-hover-color);
border-radius: 4px;
}
.database-ops {
padding: 2px;
border-radius: 4px;
display: flex;
cursor: pointer;
align-items: center;
height: max-content;
}
.database-ops svg {
width: 16px;
height: 16px;
color: var(--affine-icon-color);
}
.database-ops:hover {
background-color: var(--affine-hover-color);
}
@media print {
.database-ops {
display: none;
}
.database-header-bar {
display: none !important;
}
}
`;
private readonly _clickDatabaseOps = (e: MouseEvent) => {
const options = this.optionsConfig.configure(this.model, {
items: [
menu.input({
@@ -120,33 +155,36 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
});
};
private readonly dataSource = lazy(() => {
const dataSource = new DatabaseBlockDataSource(this.model, dataSource => {
dataSource.serviceSet(EditorHostKey, this.host);
this.std.provider
.getAll(ExternalGroupByConfigProvider)
.forEach(config => {
dataSource.serviceSet(
ExternalGroupByConfigProvider(config.name),
config
);
});
});
const id = currentViewStorage.getCurrentView(this.model.id);
if (id && dataSource.viewManager.viewGet(id)) {
dataSource.viewManager.setCurrentView(id);
}
return dataSource;
});
private _dataSource?: DatabaseBlockDataSource;
private readonly renderTitle = (dataViewLogic: DataViewUILogicBase) => {
private readonly dataView = new DataView();
private readonly renderTitle = (dataViewMethod: DataViewInstance) => {
const addRow = () => dataViewMethod.addRow?.('start');
return html` <affine-database-title
class="${databaseTitleStyles}"
style="overflow: hidden"
.titleText="${this.model.props.title}"
.dataViewLogic="${dataViewLogic}"
.readonly="${this.dataSource.readonly$.value}"
.onPressEnterKey="${addRow}"
></affine-database-title>`;
};
_bindHotkey: DataViewProps['bindHotkey'] = hotkeys => {
return {
dispose: this.host.event.bindHotkey(hotkeys, {
blockId: this.topContenteditableElement?.blockId ?? this.blockId,
}),
};
};
_handleEvent: DataViewProps['handleEvent'] = (name, handler) => {
return {
dispose: this.host.event.add(name, handler, {
blockId: this.blockId,
}),
};
};
createTemplate = (
data: {
view: SingleView;
@@ -180,12 +218,18 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
headerWidget: DataViewWidget = defineUniComponent(
(props: DataViewWidgetProps) => {
return html`
<div class="${databaseHeaderContainerStyles}">
<div class="${databaseTitleRowStyles}">
${this.renderTitle(props.dataViewLogic)} ${this.renderDatabaseOps()}
<div style="margin-bottom: 16px;display:flex;flex-direction: column">
<div
style="display:flex;gap:12px;margin-bottom: 8px;align-items: center"
>
${this.renderTitle(props.dataViewInstance)}
${this.renderDatabaseOps()}
</div>
<div class="${databaseToolbarRowStyles} ${databaseHeaderBarStyles}">
<div class="${databaseViewBarContainerStyles}">
<div
style="display:flex;align-items:center;justify-content: space-between;gap: 12px"
class="database-header-bar"
>
<div style="flex:1">
${renderUniLit(widgetPresets.viewBar, {
...props,
onChangeView: id => {
@@ -240,9 +284,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
return () => {};
};
private readonly setSelection = (
selection: DataViewSelection | undefined
) => {
setSelection = (selection: DataViewSelection | undefined) => {
if (selection) {
getSelection()?.removeAllRanges();
}
@@ -259,7 +301,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
);
};
private readonly toolsWidget: DataViewWidget = widgetPresets.createTools({
toolsWidget: DataViewWidget = widgetPresets.createTools({
table: [
widgetPresets.tools.filter,
widgetPresets.tools.sort,
@@ -276,7 +318,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
],
});
private readonly viewSelection$ = computed(() => {
viewSelection$ = computed(() => {
const databaseSelection = this.selection.value.find(
(selection): selection is DatabaseSelection => {
if (selection.blockId !== this.blockId) {
@@ -288,7 +330,28 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
return databaseSelection?.viewSelection;
});
private readonly virtualPadding$ = signal(0);
virtualPadding$ = signal(0);
get dataSource(): DatabaseBlockDataSource {
if (!this._dataSource) {
this._dataSource = new DatabaseBlockDataSource(this.model, dataSource => {
dataSource.serviceSet(EditorHostKey, this.host);
this.std.provider
.getAll(ExternalGroupByConfigProvider)
.forEach(config => {
dataSource.serviceSet(
ExternalGroupByConfigProvider(config.name),
config
);
});
});
const id = currentViewStorage.getCurrentView(this.model.id);
if (id && this.dataSource.viewManager.viewGet(id)) {
this.dataSource.viewManager.setCurrentView(id);
}
}
return this._dataSource;
}
get optionsConfig(): DatabaseViewExtensionOptions {
return {
@@ -306,15 +369,15 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
return this.rootComponent;
}
get view() {
return this.dataView.expose;
}
private renderDatabaseOps() {
if (this.dataSource.value.readonly$.value) {
if (this.dataSource.readonly$.value) {
return nothing;
}
return html` <div
data-testid="database-ops"
class="${databaseOpsStyles}"
@click="${this.clickDatabaseOps}"
>
return html` <div class="database-ops" @click="${this._clickDatabaseOps}">
${MoreHorizontalIcon()}
</div>`;
}
@@ -323,7 +386,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
super.connectedCallback();
this.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true');
this.classList.add(databaseBlockStyles);
this.listenFullWidthChange();
}
@@ -340,97 +402,85 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
})
);
}
private readonly dataViewRootLogic = lazy(
() =>
new DataViewRootUILogic({
virtualPadding$: this.virtualPadding$,
bindHotkey: hotkeys => {
return {
dispose: this.host.event.bindHotkey(hotkeys, {
blockId: this.topContenteditableElement?.blockId ?? this.blockId,
}),
};
},
handleEvent: (name, handler) => {
return {
dispose: this.host.event.add(name, handler, {
blockId: this.blockId,
}),
};
},
selection$: this.viewSelection$,
setSelection: this.setSelection,
dataSource: this.dataSource.value,
headerWidget: this.headerWidget,
onDrag: this.onDrag,
clipboard: this.std.clipboard,
notification: {
toast: message => {
const notification = this.std.getOptional(NotificationProvider);
if (notification) {
notification.toast(message);
} else {
toast(this.host, message);
}
},
},
eventTrace: (key, params) => {
const telemetryService = this.std.getOptional(TelemetryProvider);
telemetryService?.track(key, {
...(params as TelemetryEventMap[typeof key]),
blockId: this.blockId,
});
},
detailPanelConfig: {
openDetailPanel: (target, data) => {
const peekViewService = this.std.getOptional(PeekViewProvider);
if (peekViewService) {
const openDoc = (docId: string) => {
return peekViewService.peek({
docId,
databaseId: this.blockId,
databaseDocId: this.model.store.id,
databaseRowId: data.rowId,
target: this,
});
};
const doc = getSingleDocIdFromText(
this.model.store.getBlock(data.rowId)?.model?.text
);
if (doc) {
return openDoc(doc);
}
const abort = new AbortController();
return new Promise<void>(focusBack => {
peekViewService
.peek(
{
target,
template: this.createTemplate(data, docId => {
// abort.abort();
openDoc(docId).then(focusBack).catch(focusBack);
}),
},
{ abortSignal: abort.signal }
)
.then(focusBack)
.catch(focusBack);
});
} else {
return popSideDetail(
this.createTemplate(data, () => {
//
})
);
}
},
},
})
);
override renderBlock() {
const peekViewService = this.std.getOptional(PeekViewProvider);
const telemetryService = this.std.getOptional(TelemetryProvider);
return html`
<div contenteditable="false" class="${databaseContentStyles}">
${this.dataViewRootLogic.value.render()}
<div
contenteditable="false"
style="position: relative;background-color: var(--affine-background-primary-color);border-radius: 4px"
>
${this.dataView.render({
virtualPadding$: this.virtualPadding$,
bindHotkey: this._bindHotkey,
handleEvent: this._handleEvent,
selection$: this.viewSelection$,
setSelection: this.setSelection,
dataSource: this.dataSource,
headerWidget: this.headerWidget,
onDrag: this.onDrag,
clipboard: this.std.clipboard,
notification: {
toast: message => {
const notification = this.std.getOptional(NotificationProvider);
if (notification) {
notification.toast(message);
} else {
toast(this.host, message);
}
},
},
eventTrace: (key, params) => {
telemetryService?.track(key, {
...(params as TelemetryEventMap[typeof key]),
blockId: this.blockId,
});
},
detailPanelConfig: {
openDetailPanel: (target, data) => {
if (peekViewService) {
const openDoc = (docId: string) => {
return peekViewService.peek({
docId,
databaseId: this.blockId,
databaseDocId: this.model.store.id,
databaseRowId: data.rowId,
target: this,
});
};
const doc = getSingleDocIdFromText(
this.model.store.getBlock(data.rowId)?.model?.text
);
if (doc) {
return openDoc(doc);
}
const abort = new AbortController();
return new Promise<void>(focusBack => {
peekViewService
.peek(
{
target,
template: this.createTemplate(data, docId => {
// abort.abort();
openDoc(docId).then(focusBack).catch(focusBack);
}),
},
{ abortSignal: abort.signal }
)
.then(focusBack)
.catch(focusBack);
});
} else {
return popSideDetail(
this.createTemplate(data, () => {
//
})
);
}
},
},
})}
</div>
`;
}
@@ -20,7 +20,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -26,7 +26,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"rxjs": "^7.8.1",
@@ -22,7 +22,10 @@ import {
GfxBlockComponent,
TextSelection,
} from '@blocksuite/std';
import { GfxViewInteractionExtension } from '@blocksuite/std/gfx';
import {
GfxViewInteractionExtension,
type SelectedContext,
} from '@blocksuite/std/gfx';
import { computed } from '@preact/signals-core';
import { css, html } from 'lit';
import { query, state } from 'lit/decorators.js';
@@ -279,6 +282,69 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
};
}
override onSelected(context: SelectedContext): void | boolean {
const { selected, multiSelect, event: e } = context;
const { editing } = this.gfx.selection;
const alreadySelected = this.gfx.selection.has(this.model.id);
if (!multiSelect && selected && (alreadySelected || editing)) {
if (this.model.isLocked()) return;
if (alreadySelected && editing) {
return;
}
this.gfx.selection.set({
elements: [this.model.id],
editing: true,
});
this.updateComplete
.then(() => {
if (!this.isConnected) {
return;
}
if (this.model.children.length === 0) {
const blockId = this.store.addBlock(
'affine:paragraph',
{ type: 'text' },
this.model.id
);
if (blockId) {
focusTextModel(this.std, blockId);
}
} else {
const rect = this.querySelector(
'.affine-block-children-container'
)?.getBoundingClientRect();
if (rect) {
const offsetY = 8 * this.gfx.viewport.zoom;
const offsetX = 2 * this.gfx.viewport.zoom;
const x = clamp(
e.clientX,
rect.left + offsetX,
rect.right - offsetX
);
const y = clamp(
e.clientY,
rect.top + offsetY,
rect.bottom - offsetY
);
handleNativeRangeAtPoint(x, y);
} else {
handleNativeRangeAtPoint(e.clientX, e.clientY);
}
}
})
.catch(console.error);
} else {
return super.onSelected(context);
}
}
override renderGfxBlock() {
const { model } = this;
const { rotate, hasMaxWidth } = model.props;
@@ -440,73 +506,5 @@ export const EdgelessTextInteraction =
},
};
},
handleSelection: context => {
const { gfx, std, view, model } = context;
return {
onSelect(context) {
const { selected, multiSelect, event: e } = context;
const { editing } = gfx.selection;
const alreadySelected = gfx.selection.has(model.id);
if (!multiSelect && selected && (alreadySelected || editing)) {
if (model.isLocked()) return;
if (alreadySelected && editing) {
return;
}
gfx.selection.set({
elements: [model.id],
editing: true,
});
view.updateComplete
.then(() => {
if (!view.isConnected) {
return;
}
if (model.children.length === 0) {
const blockId = std.store.addBlock(
'affine:paragraph',
{ type: 'text' },
model.id
);
if (blockId) {
focusTextModel(std, blockId);
}
} else {
const rect = view
.querySelector('.affine-block-children-container')
?.getBoundingClientRect();
if (rect) {
const offsetY = 8 * gfx.viewport.zoom;
const offsetX = 2 * gfx.viewport.zoom;
const x = clamp(
e.clientX,
rect.left + offsetX,
rect.right - offsetX
);
const y = clamp(
e.clientY,
rect.top + offsetY,
rect.bottom - offsetY
);
handleNativeRangeAtPoint(x, y);
} else {
handleNativeRangeAtPoint(e.clientX, e.clientY);
}
}
})
.catch(console.error);
} else {
return context.default(context);
}
},
};
},
}
);
@@ -26,7 +26,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
@@ -11,6 +11,7 @@ import {
isFootnoteDefinitionNode,
type MarkdownAST,
} from '@blocksuite/affine-shared/adapters';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { nanoid } from '@blocksuite/store';
const isLinkedDocFootnoteDefinitionNode = (node: MarkdownAST) => {
@@ -35,7 +36,15 @@ export const embedLinkedDocBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatc
fromMatch: o => o.node.flavour === EmbedLinkedDocBlockSchema.model.flavour,
toBlockSnapshot: {
enter: (o, context) => {
if (!isFootnoteDefinitionNode(o.node)) {
const { provider } = context;
let enableCitation = false;
try {
const featureFlagService = provider?.get(FeatureFlagService);
enableCitation = !!featureFlagService?.getFlag('enable_citation');
} catch {
enableCitation = false;
}
if (!isFootnoteDefinitionNode(o.node) || !enableCitation) {
return;
}
@@ -3,7 +3,6 @@ import {
RENDER_CARD_THROTTLE_MS,
} from '@blocksuite/affine-block-embed';
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { LoadingIcon } from '@blocksuite/affine-components/icons';
import { isPeekable, Peekable } from '@blocksuite/affine-components/peek';
import { RefNodeSlotsProvider } from '@blocksuite/affine-inline-reference';
import type {
@@ -32,7 +31,6 @@ import {
referenceToNode,
} from '@blocksuite/affine-shared/utils';
import { Bound } from '@blocksuite/global/gfx';
import { ResetIcon } from '@blocksuite/icons/lit';
import { BlockSelection } from '@blocksuite/std';
import { Text } from '@blocksuite/store';
import { computed } from '@preact/signals-core';
@@ -339,6 +337,8 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
const theme = this.std.get(ThemeProvider).theme;
const {
LoadingIcon,
ReloadIcon,
LinkedDocDeletedBanner,
LinkedDocEmptyBanner,
SyncedDocErrorBanner,
@@ -347,7 +347,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
const icon = isError
? SyncedDocErrorIcon
: isLoading
? LoadingIcon()
? LoadingIcon
: this.icon$.value;
const title = isLoading ? 'Loading...' : this.title$;
const description = this.model.props.description$;
@@ -384,6 +384,10 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
() => html`
<div
class="affine-embed-linked-doc-block ${cardClassMap}"
style=${styleMap({
transform: `scale(${this._scale})`,
transformOrigin: '0 0',
})}
@click=${this._handleClick}
@dblclick=${this._handleDoubleClick}
>
@@ -429,7 +433,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
class="affine-embed-linked-doc-card-content-reload-button"
@click=${this.refreshData}
>
${ResetIcon()} <span>Reload</span>
${ReloadIcon} <span>Reload</span>
</div>
</div>
`
@@ -124,11 +124,11 @@ export const styles = css`
align-items: center;
gap: 4px;
cursor: pointer;
color: ${unsafeCSSVarV2('button/primary')};
}
.affine-embed-linked-doc-card-content-reload-button svg {
width: 12px;
height: 12px;
fill: var(--affine-background-primary-color);
}
.affine-embed-linked-doc-card-content-reload-button > span {
display: -webkit-box;
@@ -138,6 +138,7 @@ export const styles = css`
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
color: var(--affine-brand-color);
font-family: var(--affine-font-family);
font-size: var(--affine-font-xs);
font-style: normal;
@@ -304,6 +305,7 @@ export const styles = css`
.affine-embed-linked-doc-content-note {
-webkit-line-clamp: 16;
max-height: 320px;
}
.affine-embed-linked-doc-content-date {
@@ -1,6 +1,8 @@
import {
EmbedEdgelessIcon,
EmbedPageIcon,
getLoadingIconWith,
ReloadIcon,
} from '@blocksuite/affine-components/icons';
import {
ColorScheme,
@@ -33,6 +35,8 @@ import {
} from './styles.js';
type EmbedCardImages = {
LoadingIcon: TemplateResult<1>;
ReloadIcon: TemplateResult<1>;
LinkedDocIcon: TemplateResult<1>;
LinkedDocDeletedIcon: TemplateResult<1>;
LinkedDocEmptyBanner: TemplateResult<1>;
@@ -46,9 +50,12 @@ export function getEmbedLinkedDocIcons(
style: (typeof EmbedLinkedDocStyles)[number]
): EmbedCardImages {
const small = style !== 'vertical';
const LoadingIcon = getLoadingIconWith(theme);
if (editorMode === 'page') {
if (theme === ColorScheme.Light) {
return {
LoadingIcon,
ReloadIcon,
LinkedDocIcon: EmbedPageIcon,
LinkedDocDeletedIcon,
LinkedDocEmptyBanner: small
@@ -61,6 +68,8 @@ export function getEmbedLinkedDocIcons(
};
} else {
return {
ReloadIcon,
LoadingIcon,
LinkedDocIcon: EmbedPageIcon,
LinkedDocDeletedIcon,
LinkedDocEmptyBanner: small
@@ -75,6 +84,8 @@ export function getEmbedLinkedDocIcons(
} else {
if (theme === ColorScheme.Light) {
return {
ReloadIcon,
LoadingIcon,
LinkedDocIcon: EmbedEdgelessIcon,
LinkedDocDeletedIcon,
LinkedDocEmptyBanner: small
@@ -87,6 +98,8 @@ export function getEmbedLinkedDocIcons(
};
} else {
return {
ReloadIcon,
LoadingIcon,
LinkedDocIcon: EmbedEdgelessIcon,
LinkedDocDeletedIcon,
LinkedDocEmptyBanner: small
@@ -1,8 +1,6 @@
import { RENDER_CARD_THROTTLE_MS } from '@blocksuite/affine-block-embed';
import { LoadingIcon } from '@blocksuite/affine-components/icons';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { WithDisposable } from '@blocksuite/global/lit';
import { ResetIcon } from '@blocksuite/icons/lit';
import {
BlockSelection,
isGfxBlockComponent,
@@ -150,7 +148,9 @@ export class EmbedSyncedDocCard extends WithDisposable(ShadowlessElement) {
const theme = this.std.get(ThemeProvider).theme;
const {
LoadingIcon,
SyncedDocErrorIcon,
ReloadIcon,
SyncedDocEmptyBanner,
SyncedDocErrorBanner,
SyncedDocDeletedBanner,
@@ -159,7 +159,7 @@ export class EmbedSyncedDocCard extends WithDisposable(ShadowlessElement) {
const icon = error
? SyncedDocErrorIcon
: isLoading
? LoadingIcon()
? LoadingIcon
: this.block.icon$.value;
const title = isLoading ? 'Loading...' : this.block.title$;
@@ -216,7 +216,7 @@ export class EmbedSyncedDocCard extends WithDisposable(ShadowlessElement) {
class="affine-embed-synced-doc-card-content-reload-button"
@click=${() => this.block.refreshData()}
>
${ResetIcon()} <span>Reload</span>
${ReloadIcon} <span>Reload</span>
</div>
</div>
`
@@ -303,11 +303,11 @@ export const cardStyles = css`
align-items: center;
gap: 4px;
cursor: pointer;
color: ${unsafeCSSVarV2('button/primary')};
}
.affine-embed-synced-doc-card-content-reload-button svg {
width: 12px;
height: 12px;
fill: var(--affine-background-primary-color);
}
.affine-embed-synced-doc-card-content-reload-button > span {
display: -webkit-box;
@@ -317,6 +317,7 @@ export const cardStyles = css`
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
color: var(--affine-brand-color);
font-family: var(--affine-font-family);
font-size: var(--affine-font-xs);
font-style: normal;
@@ -1,6 +1,8 @@
import {
EmbedEdgelessIcon,
EmbedPageIcon,
getLoadingIconWith,
ReloadIcon,
} from '@blocksuite/affine-components/icons';
import { ColorScheme } from '@blocksuite/affine-model';
import type { BlockComponent } from '@blocksuite/std';
@@ -19,9 +21,11 @@ import {
} from './styles.js';
type SyncedCardImages = {
LoadingIcon: TemplateResult<1>;
SyncedDocIcon: TemplateResult<1>;
SyncedDocErrorIcon: TemplateResult<1>;
SyncedDocDeletedIcon: TemplateResult<1>;
ReloadIcon: TemplateResult<1>;
SyncedDocEmptyBanner: TemplateResult<1>;
SyncedDocErrorBanner: TemplateResult<1>;
SyncedDocDeletedBanner: TemplateResult<1>;
@@ -31,20 +35,25 @@ export function getSyncedDocIcons(
theme: ColorScheme,
editorMode: 'page' | 'edgeless'
): SyncedCardImages {
const LoadingIcon = getLoadingIconWith(theme);
if (theme === ColorScheme.Light) {
return {
LoadingIcon,
SyncedDocIcon: editorMode === 'page' ? EmbedPageIcon : EmbedEdgelessIcon,
SyncedDocErrorIcon,
SyncedDocDeletedIcon,
ReloadIcon,
SyncedDocEmptyBanner: LightSyncedDocEmptyBanner,
SyncedDocErrorBanner: LightSyncedDocErrorBanner,
SyncedDocDeletedBanner: LightSyncedDocDeletedBanner,
};
} else {
return {
LoadingIcon,
SyncedDocIcon: editorMode === 'page' ? EmbedPageIcon : EmbedEdgelessIcon,
SyncedDocErrorIcon,
SyncedDocDeletedIcon,
ReloadIcon,
SyncedDocEmptyBanner: DarkSyncedDocEmptyBanner,
SyncedDocErrorBanner: DarkSyncedDocErrorBanner,
SyncedDocDeletedBanner: DarkSyncedDocDeletedBanner,
+1 -1
View File
@@ -26,7 +26,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lodash-es": "^4.17.21",
@@ -50,6 +50,12 @@ export class EmbedBlockComponent<
_cardStyle: EmbedCardStyle = 'horizontal';
/**
* The actual rendered scale of the embed card.
* By default, it is set to 1.
*/
protected _scale = 1;
blockDraggable = true;
/**
@@ -68,6 +68,7 @@ export function toEdgelessEmbedBlock<
this.blockContainerStyles = {
width: `${bound.w}px`,
};
this._scale = bound.w / this._cardWidth;
return this.renderPageContent();
}
@@ -9,11 +9,13 @@ import {
EmbedCardLightHorizontalIcon,
EmbedCardLightListIcon,
EmbedCardLightVerticalIcon,
getLoadingIconWith,
} from '@blocksuite/affine-components/icons';
import { ColorScheme } from '@blocksuite/affine-model';
import type { TemplateResult } from 'lit';
type EmbedCardIcons = {
LoadingIcon: TemplateResult<1>;
EmbedCardBannerIcon: TemplateResult<1>;
EmbedCardHorizontalIcon: TemplateResult<1>;
EmbedCardListIcon: TemplateResult<1>;
@@ -22,8 +24,11 @@ type EmbedCardIcons = {
};
export function getEmbedCardIcons(theme: ColorScheme): EmbedCardIcons {
const LoadingIcon = getLoadingIconWith(theme);
if (theme === ColorScheme.Light) {
return {
LoadingIcon,
EmbedCardBannerIcon: EmbedCardLightBannerIcon,
EmbedCardHorizontalIcon: EmbedCardLightHorizontalIcon,
EmbedCardListIcon: EmbedCardLightListIcon,
@@ -32,6 +37,7 @@ export function getEmbedCardIcons(theme: ColorScheme): EmbedCardIcons {
};
} else {
return {
LoadingIcon,
EmbedCardBannerIcon: EmbedCardDarkBannerIcon,
EmbedCardHorizontalIcon: EmbedCardDarkHorizontalIcon,
EmbedCardListIcon: EmbedCardDarkListIcon,
@@ -6,6 +6,7 @@ import type {
import { BlockSelection } from '@blocksuite/std';
import { html, nothing } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { EmbedBlockComponent } from '../common/embed-block-element.js';
import { FigmaIcon, styles } from './styles.js';
@@ -75,6 +76,10 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
'affine-embed-figma-block': true,
selected: this.selected$.value,
})}
style=${styleMap({
transform: `scale(${this._scale})`,
transformOrigin: '0 0',
})}
@click=${this._handleClick}
@dblclick=${this._handleDoubleClick}
>
@@ -1,4 +1,4 @@
import { LoadingIcon, OpenIcon } from '@blocksuite/affine-components/icons';
import { OpenIcon } from '@blocksuite/affine-components/icons';
import type {
EmbedGithubModel,
EmbedGithubStyles,
@@ -133,8 +133,8 @@ export class EmbedGithubBlockComponent extends EmbedBlockComponent<
const loading = this.loading;
const theme = this.std.get(ThemeProvider).theme;
const imageProxyService = this.store.get(ImageProxyService);
const { EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIcon = loading ? LoadingIcon() : GithubIcon;
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIcon = loading ? LoadingIcon : GithubIcon;
const statusIcon = status
? getGithubStatusIcon(githubType, status, statusReason)
: nothing;
@@ -1,4 +1,4 @@
import { LoadingIcon } from '@blocksuite/affine-components/icons';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { EmbedIcon } from '@blocksuite/icons/lit';
import { type BlockStdScope } from '@blocksuite/std';
@@ -7,6 +7,7 @@ import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { getEmbedCardIcons } from '../../common/utils';
import { LOADING_CARD_DEFAULT_HEIGHT } from '../consts';
import type { EmbedIframeStatusCardOptions } from '../types';
@@ -155,6 +156,9 @@ export class EmbedIframeLoadingCard extends LitElement {
`;
override render() {
const theme = this.std.get(ThemeProvider).theme;
const { LoadingIcon } = getEmbedCardIcons(theme);
const { layout, width, height } = this.options;
const cardClasses = classMap({
'affine-embed-iframe-loading-card': true,
@@ -172,7 +176,7 @@ export class EmbedIframeLoadingCard extends LitElement {
return html`
<div class=${cardClasses} style=${cardStyle}>
<div class="loading-content">
<div class="loading-spinner">${LoadingIcon()}</div>
<div class="loading-spinner">${LoadingIcon}</div>
<div class="loading-text">Loading...</div>
</div>
<div class="loading-banner">
@@ -1,4 +1,4 @@
import { LoadingIcon, OpenIcon } from '@blocksuite/affine-components/icons';
import { OpenIcon } from '@blocksuite/affine-components/icons';
import type { EmbedLoomModel, EmbedLoomStyles } from '@blocksuite/affine-model';
import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
@@ -94,8 +94,8 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
const loading = this.loading;
const theme = this.std.get(ThemeProvider).theme;
const imageProxyService = this.store.get(ImageProxyService);
const { EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIcon = loading ? LoadingIcon() : LoomIcon;
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIcon = loading ? LoadingIcon : LoomIcon;
const titleText = loading ? 'Loading...' : title;
const descriptionText = loading ? '' : description;
const bannerImage =
@@ -112,6 +112,7 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
selected: this.selected$.value,
})}
style=${styleMap({
transform: `scale(${this._scale})`,
transformOrigin: '0 0',
})}
@click=${this._handleClick}
@@ -1,4 +1,4 @@
import { LoadingIcon, OpenIcon } from '@blocksuite/affine-components/icons';
import { OpenIcon } from '@blocksuite/affine-components/icons';
import type {
EmbedYoutubeModel,
EmbedYoutubeStyles,
@@ -108,8 +108,8 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
const loading = this.loading;
const theme = this.std.get(ThemeProvider).theme;
const imageProxyService = this.store.get(ImageProxyService);
const { EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIcon = loading ? LoadingIcon() : YoutubeIcon;
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIcon = loading ? LoadingIcon : YoutubeIcon;
const titleText = loading ? 'Loading...' : title;
const descriptionText = loading ? null : description;
const bannerImage =
+1 -1
View File
@@ -25,7 +25,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -205,11 +205,10 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
!forceMove
) {
// Clear the flag so future navigations behave normally
// Here we modify the tool's activated option to avoid triggering setTool update
const currentTool = this.gfx.tool.currentTool$.peek();
if (currentTool?.activatedOption) {
currentTool.activatedOption.restoredAfterPan = false;
}
this.gfx.tool.setTool(PresentTool, {
...toolOptions,
restoredAfterPan: false,
});
return;
}
@@ -3,7 +3,6 @@ import {
DefaultTheme,
type FrameBlockModel,
FrameBlockSchema,
isTransparent,
} from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { Bound } from '@blocksuite/global/gfx';
@@ -12,6 +11,7 @@ import {
type BoxSelectionContext,
getTopElements,
GfxViewInteractionExtension,
type SelectedContext,
} from '@blocksuite/std/gfx';
import { cssVarV2 } from '@toeverything/theme/v2';
import { html } from 'lit';
@@ -68,6 +68,22 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
};
}
override onSelected(context: SelectedContext): boolean | void {
const { x, y } = context.position;
if (
!context.fallback &&
// if the frame is selected by title, then ignore it because the title selection is handled by the title widget
(this.model.externalBound?.containsPoint([x, y]) ||
// otherwise if the frame has title, then ignore it because in this case the frame cannot be selected by frame body
this.model.props.title.length)
) {
return false;
}
return super.onSelected(context);
}
override onBoxSelected(context: BoxSelectionContext) {
const { box } = context;
const bound = new Bound(box.x, box.y, box.w, box.h);
@@ -173,17 +189,5 @@ export const FrameBlockInteraction =
},
};
},
handleSelection: () => {
return {
selectable(context) {
const { model } = context;
return (
context.default(context) &&
(model.isLocked() || !isTransparent(model.props.background))
);
},
};
},
}
);
+1 -1
View File
@@ -25,7 +25,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"file-type": "^21.0.0",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -46,19 +46,12 @@ export class ImageBlockPageComponent extends SignalWatcher(
justify-content: center;
position: absolute;
top: 4px;
left: 4px;
width: 36px;
height: 36px;
padding: 5px;
border-radius: 8px;
background: ${unsafeCSSVarV2(
'loading/imageLoadingBackground',
'#92929238'
)};
& > svg {
font-size: 25.71px;
}
right: 4px;
width: 20px;
height: 20px;
padding: 4px;
border-radius: 4px;
background: ${unsafeCSSVarV2('loading/backgroundLayer')};
}
affine-page-image .affine-image-status {
@@ -359,9 +352,7 @@ export class ImageBlockPageComponent extends SignalWatcher(
? ImageSelectedRect(this._doc.readonly)
: null;
const blobUrl = this.block.blobUrl;
const caption = this.block.model.props.caption$.value ?? 'Image';
const { loading, error, icon, description, needUpload } = this.state;
const { loading, error, icon, description } = this.state;
return html`
<div class="resizable-img" style=${styleMap(imageSize)}>
@@ -369,8 +360,8 @@ export class ImageBlockPageComponent extends SignalWatcher(
class="drag-target"
draggable="false"
loading="lazy"
src=${blobUrl}
alt=${caption}
src=${this.block.blobUrl}
alt=${this.block.model.props.caption$.value ?? 'Image'}
@error=${this._handleError}
/>
@@ -379,16 +370,12 @@ export class ImageBlockPageComponent extends SignalWatcher(
${when(loading, () => html`<div class="loading">${icon}</div>`)}
${when(
Boolean(error && description),
error && description,
() =>
html`<affine-resource-status
class="affine-image-status"
.message=${description}
.needUpload=${needUpload}
.action=${() =>
needUpload
? this.block.resourceController.upload()
: this.block.refreshData()}
.reload=${() => this.block.refreshData()}
></affine-resource-status>`
)}
`;
@@ -1,17 +1,19 @@
import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
import { whenHover } from '@blocksuite/affine-components/hover';
import { LoadingIcon } from '@blocksuite/affine-components/icons';
import { getLoadingIconWith } from '@blocksuite/affine-components/icons';
import { Peekable } from '@blocksuite/affine-components/peek';
import { ResourceController } from '@blocksuite/affine-components/resource';
import type { ImageBlockModel } from '@blocksuite/affine-model';
import { ImageSelection } from '@blocksuite/affine-shared/selection';
import { ToolbarRegistryIdentifier } from '@blocksuite/affine-shared/services';
import {
ThemeProvider,
ToolbarRegistryIdentifier,
} from '@blocksuite/affine-shared/services';
import { formatSize } from '@blocksuite/affine-shared/utils';
import { IS_MOBILE } from '@blocksuite/global/env';
import { BrokenImageIcon, ImageIcon } from '@blocksuite/icons/lit';
import { BlockSelection } from '@blocksuite/std';
import { computed } from '@preact/signals-core';
import { cssVarV2 } from '@toeverything/theme/v2';
import { html } from 'lit';
import { query } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
@@ -124,6 +126,9 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
}
override renderBlock() {
const theme = this.std.get(ThemeProvider).theme$.value;
const loadingIcon = getLoadingIconWith(theme);
const blobUrl = this.blobUrl;
const { size = 0 } = this.model.props;
@@ -133,10 +138,7 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
});
const resovledState = this.resourceController.resolveStateWith({
loadingIcon: LoadingIcon({
strokeColor: cssVarV2('button/pureWhiteText'),
ringColor: cssVarV2('loading/imageLoadingLayer', '#ffffff8f'),
}),
loadingIcon,
errorIcon: BrokenImageIcon(),
icon: ImageIcon(),
title: 'Image',
@@ -1,12 +1,13 @@
import type { BlockCaptionEditor } from '@blocksuite/affine-components/caption';
import { LoadingIcon } from '@blocksuite/affine-components/icons';
import { getLoadingIconWith } from '@blocksuite/affine-components/icons';
import { Peekable } from '@blocksuite/affine-components/peek';
import { ResourceController } from '@blocksuite/affine-components/resource';
import {
type ImageBlockModel,
ImageBlockSchema,
} from '@blocksuite/affine-model';
import { cssVarV2, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { formatSize } from '@blocksuite/affine-shared/utils';
import { BrokenImageIcon, ImageIcon } from '@blocksuite/icons/lit';
import { GfxBlockComponent } from '@blocksuite/std';
@@ -38,18 +39,11 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
position: absolute;
top: 4px;
right: 4px;
width: 36px;
height: 36px;
padding: 5px;
border-radius: 8px;
background: ${unsafeCSSVarV2(
'loading/imageLoadingBackground',
'#92929238'
)};
& > svg {
font-size: 25.71px;
}
width: 20px;
height: 20px;
padding: 4px;
border-radius: 4px;
background: ${unsafeCSSVarV2('loading/backgroundLayer')};
}
affine-edgeless-image .affine-image-status {
@@ -114,6 +108,9 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
}
override renderGfxBlock() {
const theme = this.std.get(ThemeProvider).theme$.value;
const loadingIcon = getLoadingIconWith(theme);
const blobUrl = this.blobUrl;
const { rotate = 0, size = 0, caption = 'Image' } = this.model.props;
@@ -127,18 +124,13 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
});
const resovledState = this.resourceController.resolveStateWith({
loadingIcon: LoadingIcon({
strokeColor: cssVarV2('button/pureWhiteText'),
ringColor: cssVarV2('loading/imageLoadingLayer', '#ffffff8f'),
}),
loadingIcon,
errorIcon: BrokenImageIcon(),
icon: ImageIcon(),
title: 'Image',
description: formatSize(size),
});
const { loading, icon, description, error, needUpload } = resovledState;
return html`
<div class="affine-image-container" style=${containerStyleMap}>
${when(
@@ -154,18 +146,17 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
@error=${this._handleError}
/>
</div>
${when(loading, () => html`<div class="loading">${icon}</div>`)}
${when(
Boolean(error && description),
resovledState.loading,
() => html`<div class="loading">${loadingIcon}</div>`
)}
${when(
resovledState.error && resovledState.description,
() =>
html`<affine-resource-status
class="affine-image-status"
.message=${description}
.needUpload=${needUpload}
.action=${() =>
needUpload
? this.resourceController.upload()
: this.refreshData()}
.message=${resovledState.description}
.reload=${() => this.refreshData()}
></affine-resource-status>`
)}
`,
+1 -1
View File
@@ -25,7 +25,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/katex": "^0.16.7",
"@types/mdast": "^4.0.4",
"katex": "^0.16.11",
@@ -1,8 +1,4 @@
import type { LatexProps } from '@blocksuite/affine-model';
import {
DocModeProvider,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import type { Command } from '@blocksuite/std';
import type { BlockModel } from '@blocksuite/store';
@@ -52,21 +48,6 @@ export const insertLatexBlockCommand: Command<
if (blockComponent instanceof LatexBlockComponent) {
await blockComponent.updateComplete;
blockComponent.toggleEditor();
const mode = std.get(DocModeProvider).getEditorMode() ?? 'page';
const ifEdgelessText = blockComponent.closest('affine-edgeless-text');
std.getOptional(TelemetryProvider)?.track('Latex', {
from:
mode === 'page'
? 'doc'
: ifEdgelessText
? 'edgeless text'
: 'edgeless note',
page: mode === 'page' ? 'doc' : 'edgeless',
segment: mode === 'page' ? 'doc' : 'whiteboard',
module: 'equation',
control: 'create equation',
});
}
}
return result[0];
@@ -58,6 +58,7 @@ export class LatexBlockComponent extends CaptionedBlockComponent<LatexBlockModel
try {
katex.render(latex, katexContainer, {
displayMode: true,
output: 'mathml',
});
} catch {
katexContainer.replaceChildren();
@@ -73,16 +74,19 @@ export class LatexBlockComponent extends CaptionedBlockComponent<LatexBlockModel
}
})
);
}
private _handleClick() {
if (this.store.readonly) return;
this.disposables.addFromEvent(this, 'click', () => {
// should not open editor or select block in readonly mode
if (this.store.readonly) {
return;
}
if (this.isBlockSelected) {
this.toggleEditor();
} else {
this.selectBlock();
}
if (this.isBlockSelected) {
this.toggleEditor();
} else {
this.selectBlock();
}
});
}
removeEditor(portal: HTMLDivElement) {
@@ -91,11 +95,7 @@ export class LatexBlockComponent extends CaptionedBlockComponent<LatexBlockModel
override renderBlock() {
return html`
<div
contenteditable="false"
class="latex-block-container"
@click=${this._handleClick}
>
<div contenteditable="false" class="latex-block-container">
<div class="katex"></div>
</div>
`;
+1 -1
View File
@@ -24,7 +24,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -40,11 +40,6 @@ export const listBlockStyles = css`
font-size: var(--affine-font-base);
}
affine-list code {
font-size: calc(var(--affine-font-base) - 3px);
padding: 0px 4px 2px;
}
.affine-list-block-container {
box-sizing: border-box;
border-radius: 4px;
+1 -1
View File
@@ -27,7 +27,7 @@
"@blocksuite/store": "workspace:*",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/lodash-es": "^4.17.12",
"@types/mdast": "^4.0.4",
"@vanilla-extract/css": "^1.17.0",
@@ -6,6 +6,7 @@ import {
isFootnoteDefinitionNode,
type MarkdownAST,
} from '@blocksuite/affine-shared/adapters';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import type { Root } from 'mdast';
const isRootNode = (node: MarkdownAST): node is Root => node.type === 'root';
@@ -65,19 +66,34 @@ const createNoteBlockMarkdownAdapterMatcher = (
}
});
// if there are footnoteDefinition nodes, add a heading node to the noteAst before the first footnoteDefinition node
const footnoteDefinitionIndex = noteAst.children.findIndex(child =>
isFootnoteDefinitionNode(child)
);
if (footnoteDefinitionIndex !== -1) {
noteAst.children.splice(footnoteDefinitionIndex, 0, {
type: 'heading',
depth: 6,
data: {
collapsed: true,
},
children: [{ type: 'text', value: 'Sources' }],
});
const { provider } = context;
let enableCitation = false;
try {
const featureFlagService = provider?.get(FeatureFlagService);
enableCitation = !!featureFlagService?.getFlag('enable_citation');
} catch {
enableCitation = false;
}
if (enableCitation) {
// if there are footnoteDefinition nodes, add a heading node to the noteAst before the first footnoteDefinition node
const footnoteDefinitionIndex = noteAst.children.findIndex(child =>
isFootnoteDefinitionNode(child)
);
if (footnoteDefinitionIndex !== -1) {
noteAst.children.splice(footnoteDefinitionIndex, 0, {
type: 'heading',
depth: 6,
data: {
collapsed: true,
},
children: [{ type: 'text', value: 'Sources' }],
});
}
} else {
// Remove the footnoteDefinition node from the noteAst
noteAst.children = noteAst.children.filter(
child => !isFootnoteDefinitionNode(child)
);
}
},
},
@@ -26,9 +26,10 @@ import {
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import type { BlockModel } from '@blocksuite/store';
import { consume } from '@lit/context';
import { computed, effect } from '@preact/signals-core';
import { nothing } from 'lit';
import { computed } from '@preact/signals-core';
import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { NoteConfigExtension } from '../config';
import * as styles from './edgeless-note-background.css';
@@ -149,20 +150,15 @@ export class EdgelessNoteBackground extends SignalWatcher(
return header;
}
override connectedCallback() {
super.connectedCallback();
this.classList.add(styles.background);
this.disposables.add(
effect(() => {
Object.assign(this.style, this.backgroundStyle$.value);
})
);
this.disposables.addFromEvent(this, 'pointerdown', stopPropagation);
this.disposables.addFromEvent(this, 'click', this._handleClickAtBackground);
}
override render() {
return this.note.isPageBlock() ? this._renderHeader() : nothing;
return html`<div
class=${styles.background}
style=${styleMap(this.backgroundStyle$.value)}
@pointerdown=${stopPropagation}
@click=${this._handleClickAtBackground}
>
${this.note.isPageBlock() ? this._renderHeader() : nothing}
</div>`;
}
@consume({ context: stdContext })
@@ -13,6 +13,7 @@ import { toGfxBlockComponent } from '@blocksuite/std';
import {
type BoxSelectionContext,
GfxViewInteractionExtension,
type SelectedContext,
} from '@blocksuite/std/gfx';
import { html, nothing, type PropertyValues } from 'lit';
import { query, state } from 'lit/decorators.js';
@@ -341,6 +342,69 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent(
`;
}
override onSelected(context: SelectedContext) {
const { selected, multiSelect, event: e } = context;
const { editing } = this.gfx.selection;
const alreadySelected = this.gfx.selection.has(this.model.id);
if (!multiSelect && selected && (alreadySelected || editing)) {
if (this.model.isLocked()) return;
if (alreadySelected && editing) {
return;
}
this.gfx.selection.set({
elements: [this.model.id],
editing: true,
});
this.updateComplete
.then(() => {
if (!this.isConnected) {
return;
}
if (this.model.children.length === 0) {
const blockId = this.store.addBlock(
'affine:paragraph',
{ type: 'text' },
this.model.id
);
if (blockId) {
focusTextModel(this.std, blockId);
}
} else {
const rect = this.querySelector(
'.affine-block-children-container'
)?.getBoundingClientRect();
if (rect) {
const offsetY = 8 * this.gfx.viewport.zoom;
const offsetX = 2 * this.gfx.viewport.zoom;
const x = clamp(
e.clientX,
rect.left + offsetX,
rect.right - offsetX
);
const y = clamp(
e.clientY,
rect.top + offsetY,
rect.bottom - offsetY
);
handleNativeRangeAtPoint(x, y);
} else {
handleNativeRangeAtPoint(e.clientX, e.clientY);
}
}
})
.catch(console.error);
} else {
super.onSelected(context);
}
}
override onBoxSelected(_: BoxSelectionContext) {
return this.model.props.displayMode !== NoteDisplayMode.DocOnly;
}
@@ -400,7 +464,7 @@ export const EdgelessNoteInteraction =
onResizeMove(context): void {
const { originalBound, newBound, lockRatio, constraint } = context;
const { minWidth, minHeight, maxHeight, maxWidth } = constraint;
const { minWidth, minHeight } = constraint;
let scale = initialScale;
let edgelessProp = { ...model.props.edgeless };
@@ -411,8 +475,8 @@ export const EdgelessNoteInteraction =
edgelessProp.scale = scale;
}
newBound.w = clamp(newBound.w, minWidth * scale, maxWidth);
newBound.h = clamp(newBound.h, minHeight * scale, maxHeight);
newBound.w = clamp(newBound.w, minWidth, Number.MAX_SAFE_INTEGER);
newBound.h = clamp(newBound.h, minHeight, Number.MAX_SAFE_INTEGER);
if (newBound.h > minHeight * scale) {
edgelessProp.collapse = true;
@@ -429,71 +493,5 @@ export const EdgelessNoteInteraction =
},
};
},
handleSelection: ({ std, gfx, view, model }) => {
return {
onSelect(context) {
const { selected, multiSelect, event: e } = context;
const { editing } = gfx.selection;
const alreadySelected = gfx.selection.has(model.id);
if (!multiSelect && selected && (alreadySelected || editing)) {
if (model.isLocked()) return;
if (alreadySelected && editing) {
return;
}
gfx.selection.set({
elements: [model.id],
editing: true,
});
view.updateComplete
.then(() => {
if (!view.isConnected) {
return;
}
if (model.children.length === 0) {
const blockId = std.store.addBlock(
'affine:paragraph',
{ type: 'text' },
model.id
);
if (blockId) {
focusTextModel(std, blockId);
}
} else {
const rect = view
.querySelector('.affine-block-children-container')
?.getBoundingClientRect();
if (rect) {
const offsetY = 8 * gfx.viewport.zoom;
const offsetX = 2 * gfx.viewport.zoom;
const x = clamp(
e.clientX,
rect.left + offsetX,
rect.right - offsetX
);
const y = clamp(
e.clientY,
rect.top + offsetY,
rect.bottom - offsetY
);
handleNativeRangeAtPoint(x, y);
} else {
handleNativeRangeAtPoint(e.clientX, e.clientY);
}
}
})
.catch(console.error);
} else {
context.default(context);
}
},
};
},
}
);
@@ -23,7 +23,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
+1 -1
View File
@@ -44,7 +44,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/lodash-es": "^4.17.12",
"dompurify": "^3.2.4",
"html2canvas": "^1.4.1",
@@ -1,9 +1,6 @@
import { insertLinkByQuickSearchCommand } from '@blocksuite/affine-block-bookmark';
import { EdgelessTextBlockComponent } from '@blocksuite/affine-block-edgeless-text';
import {
FrameTool,
type PresentToolOption,
} from '@blocksuite/affine-block-frame';
import { FrameTool } from '@blocksuite/affine-block-frame';
import {
DefaultTool,
EdgelessLegacySlotIdentifier,
@@ -475,6 +472,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
const selection = gfx.selection;
if (event.code === 'Space' && !event.repeat) {
const currentToolName =
this.rootComponent.gfx.tool.currentToolName$.peek();
if (currentToolName === 'frameNavigator') return false;
this._space(event);
} else if (
!selection.editing &&
@@ -512,6 +512,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
ctx => {
const event = ctx.get('keyboardState').raw;
if (event.code === 'Space' && !event.repeat) {
const currentToolName =
this.rootComponent.gfx.tool.currentToolName$.peek();
if (currentToolName === 'frameNavigator') return false;
this._space(event);
}
return false;
@@ -709,18 +712,10 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
const revertToPrevTool = (ev: KeyboardEvent) => {
if (ev.code === 'Space') {
const toolConstructor = currentTool.constructor as typeof DefaultTool;
let finalOptions = currentTool?.activatedOption;
// Handle frameNavigator (PresentTool) restoration after space pan
if (currentTool.toolName === 'frameNavigator') {
finalOptions = {
...currentTool?.activatedOption,
restoredAfterPan: true,
} as PresentToolOption;
}
this._setEdgelessTool(toolConstructor, finalOptions);
this._setEdgelessTool(
(currentTool as DefaultTool).constructor as typeof DefaultTool,
currentTool?.activatedOption
);
selection.set(currentSel);
document.removeEventListener('keyup', revertToPrevTool, false);
}
@@ -733,14 +728,6 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
) {
return;
}
// If in presentation mode, disable black background during space drag
if (currentTool.toolName === 'frameNavigator') {
this.slots.navigatorSettingUpdated.next({
blackBackground: false,
});
}
this._setEdgelessTool(PanTool, { panning: false });
this.std.event.disposables.addFromEvent(
@@ -16,6 +16,7 @@ import type {
GfxController,
GfxModel,
LayerManager,
PointTestOptions,
ReorderingDirection,
} from '@blocksuite/std/gfx';
import {
@@ -167,6 +168,19 @@ export class EdgelessRootService
this._initReadonlyListener();
}
/**
* This method is used to pick element in group, if the picked element is in a
* group, we will pick the group instead. If that picked group is currently selected, then
* we will pick the element itself.
*/
pickElementInGroup(
x: number,
y: number,
options?: PointTestOptions
): GfxModel | null {
return this.gfx.getElementInGroup(x, y, options);
}
removeElement(id: string | GfxModel) {
id = typeof id === 'string' ? id : id.id;
@@ -25,7 +25,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/lodash-es": "^4.17.12",
"fractional-indexing": "^3.2.0",
"lit": "^3.2.0",
@@ -20,7 +20,7 @@
"@blocksuite/store": "workspace:*",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/lodash-es": "^4.17.12",
"fractional-indexing": "^3.2.0",
"html2canvas": "^1.4.1",
@@ -0,0 +1,3 @@
import { ConnectorDomRendererExtension } from '../renderer/dom-elements/index.js';
export { ConnectorDomRendererExtension };
@@ -1,4 +1,5 @@
export * from './clipboard-config';
export * from './connector-dom-renderer';
export * from './crud-extension';
export * from './dom-element-renderer';
export * from './edit-props-middleware-builder';
@@ -0,0 +1,398 @@
import type { ConnectorElementModel } from '@blocksuite/affine-model';
import { ConnectorMode, DefaultTheme } from '@blocksuite/affine-model';
import {
getBezierParameters,
type PointLocation,
} from '@blocksuite/global/gfx';
import { DomElementRendererExtension } from '../../extensions/dom-element-renderer.js';
import type { DomRenderer } from '../dom-renderer.js';
import type { DomElementRenderer } from './index.js';
/**
* DOM renderer for connector elements.
* Uses SVG to render connector paths, endpoints, and labels.
*/
export const connectorDomRenderer: DomElementRenderer<ConnectorElementModel> = (
elementModel,
domElement,
renderer
) => {
const {
mode,
path: points,
strokeStyle,
frontEndpointStyle,
rearEndpointStyle,
strokeWidth,
stroke,
w,
h,
} = elementModel;
// Clear previous content
domElement.innerHTML = '';
// Points might not be built yet in some scenarios (undo/redo, copy/paste)
if (!points.length || points.length < 2) {
return;
}
// Create SVG element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.width = `${w * renderer.viewport.zoom}px`;
svg.style.height = `${h * renderer.viewport.zoom}px`;
svg.style.position = 'absolute';
svg.style.top = '0';
svg.style.left = '0';
svg.style.pointerEvents = 'none';
svg.style.overflow = 'visible';
const strokeColor = renderer.getColorValue(
stroke,
DefaultTheme.connectorColor,
true
);
// Render connector path
renderConnectorPath(
svg,
points,
mode,
strokeStyle,
strokeWidth,
strokeColor,
renderer.viewport.zoom
);
// Render endpoints
if (frontEndpointStyle && frontEndpointStyle !== 'None') {
renderEndpoint(
svg,
points,
frontEndpointStyle,
'front',
strokeWidth,
strokeColor,
mode,
renderer.viewport.zoom
);
}
if (rearEndpointStyle && rearEndpointStyle !== 'None') {
renderEndpoint(
svg,
points,
rearEndpointStyle,
'rear',
strokeWidth,
strokeColor,
mode,
renderer.viewport.zoom
);
}
// Render label if exists
if (elementModel.hasLabel()) {
renderConnectorLabel(elementModel, domElement, renderer);
}
domElement.appendChild(svg);
};
function renderConnectorPath(
svg: SVGSVGElement,
points: PointLocation[],
mode: ConnectorMode,
strokeStyle: string,
strokeWidth: number,
strokeColor: string,
zoom: number
) {
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let pathData = '';
if (mode === ConnectorMode.Curve) {
// Bezier curve
const bezierParams = getBezierParameters(points);
const [p0, p1, p2, p3] = bezierParams;
pathData = `M ${p0[0]} ${p0[1]} C ${p1[0]} ${p1[1]} ${p2[0]} ${p2[1]} ${p3[0]} ${p3[1]}`;
} else {
// Straight or orthogonal lines
pathData = `M ${points[0][0]} ${points[0][1]}`;
for (let i = 1; i < points.length; i++) {
pathData += ` L ${points[i][0]} ${points[i][1]}`;
}
}
path.setAttribute('d', pathData);
path.setAttribute('stroke', strokeColor);
path.setAttribute('stroke-width', (strokeWidth * zoom).toString());
path.setAttribute('fill', 'none');
path.setAttribute('stroke-linecap', 'round');
path.setAttribute('stroke-linejoin', 'round');
if (strokeStyle === 'dash') {
const dashArray = `${12 * zoom},${12 * zoom}`;
path.setAttribute('stroke-dasharray', dashArray);
}
svg.appendChild(path);
}
function renderEndpoint(
svg: SVGSVGElement,
points: PointLocation[],
endpointStyle: string,
position: 'front' | 'rear',
strokeWidth: number,
strokeColor: string,
mode: ConnectorMode,
zoom: number
) {
const pointIndex = position === 'rear' ? points.length - 1 : 0;
const point = points[pointIndex];
const size = 15 * (strokeWidth / 2) * zoom;
// Calculate tangent direction for endpoint orientation
let tangent: [number, number];
if (mode === ConnectorMode.Curve) {
const bezierParams = getBezierParameters(points);
// For curve mode, use bezier tangent
if (position === 'rear') {
const lastIdx = points.length - 1;
const prevPoint = points[lastIdx - 1];
tangent = [point[0] - prevPoint[0], point[1] - prevPoint[1]];
} else {
const nextPoint = points[1];
tangent = [nextPoint[0] - point[0], nextPoint[1] - point[1]];
}
} else {
// For straight/orthogonal mode
if (position === 'rear') {
const prevPoint = points[points.length - 2];
tangent = [point[0] - prevPoint[0], point[1] - prevPoint[1]];
} else {
const nextPoint = points[1];
tangent = [nextPoint[0] - point[0], nextPoint[1] - point[1]];
}
}
// Normalize tangent
const length = Math.sqrt(tangent[0] * tangent[0] + tangent[1] * tangent[1]);
if (length > 0) {
tangent[0] /= length;
tangent[1] /= length;
}
// Adjust tangent direction for front endpoint
if (position === 'front') {
tangent[0] = -tangent[0];
tangent[1] = -tangent[1];
}
switch (endpointStyle) {
case 'Arrow':
renderArrowEndpoint(svg, point, tangent, size, strokeColor, zoom);
break;
case 'Triangle':
renderTriangleEndpoint(svg, point, tangent, size, strokeColor, zoom);
break;
case 'Circle':
renderCircleEndpoint(svg, point, tangent, size, strokeColor, zoom);
break;
case 'Diamond':
renderDiamondEndpoint(svg, point, tangent, size, strokeColor, zoom);
break;
}
}
function renderArrowEndpoint(
svg: SVGSVGElement,
point: PointLocation,
tangent: [number, number],
size: number,
color: string,
zoom: number
) {
const angle = Math.PI / 4; // 45 degrees
const arrowPath = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
// Calculate arrow points
const cos1 = Math.cos(angle);
const sin1 = Math.sin(angle);
const cos2 = Math.cos(-angle);
const sin2 = Math.sin(-angle);
const x1 = point[0] + size * (tangent[0] * cos1 - tangent[1] * sin1);
const y1 = point[1] + size * (tangent[0] * sin1 + tangent[1] * cos1);
const x2 = point[0] + size * (tangent[0] * cos2 - tangent[1] * sin2);
const y2 = point[1] + size * (tangent[0] * sin2 + tangent[1] * cos2);
const pathData = `M ${x1} ${y1} L ${point[0]} ${point[1]} L ${x2} ${y2}`;
arrowPath.setAttribute('d', pathData);
arrowPath.setAttribute('stroke', color);
arrowPath.setAttribute('stroke-width', (2 * zoom).toString());
arrowPath.setAttribute('fill', 'none');
arrowPath.setAttribute('stroke-linecap', 'round');
arrowPath.setAttribute('stroke-linejoin', 'round');
svg.appendChild(arrowPath);
}
function renderTriangleEndpoint(
svg: SVGSVGElement,
point: PointLocation,
tangent: [number, number],
size: number,
color: string,
zoom: number
) {
const triangle = document.createElementNS(
'http://www.w3.org/2000/svg',
'polygon'
);
const angle = Math.PI / 3; // 60 degrees
const cos1 = Math.cos(angle);
const sin1 = Math.sin(angle);
const cos2 = Math.cos(-angle);
const sin2 = Math.sin(-angle);
const x1 = point[0] + size * (tangent[0] * cos1 - tangent[1] * sin1);
const y1 = point[1] + size * (tangent[0] * sin1 + tangent[1] * cos1);
const x2 = point[0] + size * (tangent[0] * cos2 - tangent[1] * sin2);
const y2 = point[1] + size * (tangent[0] * sin2 + tangent[1] * cos2);
const points = `${point[0]},${point[1]} ${x1},${y1} ${x2},${y2}`;
triangle.setAttribute('points', points);
triangle.setAttribute('fill', color);
triangle.setAttribute('stroke', color);
triangle.setAttribute('stroke-width', (1 * zoom).toString());
svg.appendChild(triangle);
}
function renderCircleEndpoint(
svg: SVGSVGElement,
point: PointLocation,
tangent: [number, number],
size: number,
color: string,
zoom: number
) {
const circle = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
const radius = size * 0.5;
const centerX = point[0] + radius * tangent[0];
const centerY = point[1] + radius * tangent[1];
circle.setAttribute('cx', centerX.toString());
circle.setAttribute('cy', centerY.toString());
circle.setAttribute('r', radius.toString());
circle.setAttribute('fill', color);
circle.setAttribute('stroke', color);
circle.setAttribute('stroke-width', (1 * zoom).toString());
svg.appendChild(circle);
}
function renderDiamondEndpoint(
svg: SVGSVGElement,
point: PointLocation,
tangent: [number, number],
size: number,
color: string,
zoom: number
) {
const diamond = document.createElementNS(
'http://www.w3.org/2000/svg',
'polygon'
);
// Calculate diamond points
const perpX = -tangent[1]; // Perpendicular to tangent
const perpY = tangent[0];
const halfSize = size * 0.5;
const x1 = point[0] + halfSize * tangent[0]; // Front point
const y1 = point[1] + halfSize * tangent[1];
const x2 = point[0] + halfSize * perpX; // Right point
const y2 = point[1] + halfSize * perpY;
const x3 = point[0] - halfSize * tangent[0]; // Back point
const y3 = point[1] - halfSize * tangent[1];
const x4 = point[0] - halfSize * perpX; // Left point
const y4 = point[1] - halfSize * perpY;
const points = `${x1},${y1} ${x2},${y2} ${x3},${y3} ${x4},${y4}`;
diamond.setAttribute('points', points);
diamond.setAttribute('fill', color);
diamond.setAttribute('stroke', color);
diamond.setAttribute('stroke-width', (1 * zoom).toString());
svg.appendChild(diamond);
}
function renderConnectorLabel(
elementModel: ConnectorElementModel,
domElement: HTMLElement,
renderer: DomRenderer
) {
if (!elementModel.text || !elementModel.labelXYWH) {
return;
}
const labelElement = document.createElement('div');
const [lx, ly, lw, lh] = elementModel.labelXYWH;
const { x, y } = elementModel;
// Position label relative to the connector
const relativeX = (lx - x) * renderer.viewport.zoom;
const relativeY = (ly - y) * renderer.viewport.zoom;
labelElement.style.position = 'absolute';
labelElement.style.left = `${relativeX}px`;
labelElement.style.top = `${relativeY}px`;
labelElement.style.width = `${lw * renderer.viewport.zoom}px`;
labelElement.style.height = `${lh * renderer.viewport.zoom}px`;
labelElement.style.pointerEvents = 'auto';
labelElement.style.display = 'flex';
labelElement.style.alignItems = 'center';
labelElement.style.justifyContent = 'center';
labelElement.style.backgroundColor = 'white';
labelElement.style.border = '1px solid #e0e0e0';
labelElement.style.borderRadius = '4px';
labelElement.style.padding = '2px 4px';
labelElement.style.fontSize = `${(elementModel.labelStyle?.fontSize || 16) * renderer.viewport.zoom}px`;
labelElement.style.fontFamily =
elementModel.labelStyle?.fontFamily || 'Inter';
labelElement.style.color = renderer.getColorValue(
elementModel.labelStyle?.color || DefaultTheme.black,
DefaultTheme.black,
true
);
labelElement.style.textAlign = elementModel.labelStyle?.textAlign || 'center';
labelElement.style.overflow = 'hidden';
labelElement.style.whiteSpace = 'nowrap';
labelElement.style.textOverflow = 'ellipsis';
// Set label text content
labelElement.textContent = elementModel.text.toString();
domElement.appendChild(labelElement);
}
// Export the extension
import { DomElementRendererExtension } from '../../extensions/dom-element-renderer.js';
export const ConnectorDomRendererExtension = DomElementRendererExtension(
'connector',
connectorDomRenderer
);
@@ -29,3 +29,9 @@ export const DomElementRendererIdentifier = (type: string) =>
export type DomElementRenderer<
T extends SurfaceElementModel = SurfaceElementModel,
> = (elementModel: T, domElement: HTMLElement, renderer: DomRenderer) => void;
// Export the connector DOM renderer
export {
connectorDomRenderer,
ConnectorDomRendererExtension,
} from './connector.js';
@@ -81,53 +81,6 @@ function getOpacity(elementModel: SurfaceElementModel) {
return { opacity: `${elementModel.opacity ?? 1}` };
}
/**
* @class DomRenderer
* Renders surface elements directly to the DOM using HTML elements and CSS.
*
* This renderer supports an extension mechanism to handle different types of surface elements.
* To add rendering support for a new element type (e.g., 'my-custom-element'), follow these steps:
*
* 1. **Define the Renderer Function**:
* Create a function that implements the rendering logic for your element.
* This function will receive the element's model, the target HTMLElement, and the DomRenderer instance.
* Signature: `(model: MyCustomElementModel, domElement: HTMLElement, renderer: DomRenderer) => void;`
* Example: `shapeDomRenderer` in `blocksuite/affine/gfx/shape/src/element-renderer/shape-dom/index.ts`.
* In this function, you'll apply styles and attributes to the `domElement` based on the `model`.
*
* 2. **Create the Renderer Extension**:
* Create a new file (e.g., `my-custom-element-dom-renderer.extension.ts`).
* Import `DomElementRendererExtension` (e.g., from `@blocksuite/affine-block-surface` or its source location
* `blocksuite/affine/blocks/surface/src/extensions/dom-element-renderer.ts`).
* Import your renderer function (from step 1).
* Use the factory to create your extension:
* `export const MyCustomElementDomRendererExtension = DomElementRendererExtension('my-custom-element', myCustomElementRendererFn);`
* Example: `ShapeDomRendererExtension` in `blocksuite/affine/gfx/shape/src/element-renderer/shape-dom.ts`.
*
* 3. **Register the Extension**:
* In your application setup where BlockSuite services and view extensions are registered (e.g., a `ViewExtensionProvider`
* or a central DI configuration place), import your new extension (from step 2) and register it with the
* dependency injection container.
* Example: `context.register(MyCustomElementDomRendererExtension);`
* As seen with `ShapeDomRendererExtension` being registered in `blocksuite/affine/gfx/shape/src/view.ts`.
*
* 4. **Core Infrastructure (Provided by DomRenderer System)**:
* - `DomElementRenderer` (type): The function signature for renderers, defined in
* `blocksuite/affine/blocks/surface/src/renderer/dom-elements/index.ts`.
* - `DomElementRendererIdentifier` (function): Creates unique service identifiers for DI,
* used by `DomRenderer` to look up specific renderers. Defined in the same file.
* - `DomElementRendererExtension` (factory): A helper to create extension objects for easy registration.
* (e.g., from `@blocksuite/affine-block-surface` or its source).
* - `DomRenderer._renderElement()`: This method automatically looks up the registered renderer using
* `DomElementRendererIdentifier(elementType)` and calls it if found.
*
* 5. **Ensure Exports**:
* - The `DomRenderer` class itself should be accessible (e.g., exported from `@blocksuite/affine/blocks/surface`).
* - The `DomElementRendererExtension` factory should be accessible.
*
* By following these steps, `DomRenderer` will automatically pick up and use your custom rendering logic
* when it encounters elements of 'my-custom-element' type.
*/
export class DomRenderer {
private _container!: HTMLElement;
@@ -35,9 +35,7 @@ export abstract class Overlay extends Extension {
]);
}
clear() {
this.refresh();
}
clear() {}
dispose() {}
@@ -154,6 +154,32 @@ export class DefaultTool extends BaseTool {
private _determineDragType(evt: PointerEventState): DefaultModeDragType {
const { x, y } = this.controller.lastMousePos$.peek();
if (this.selection.isInSelectedRect(x, y)) {
if (this.selection.selectedElements.length === 1) {
const currentHoveredElem = this._getElementInGroup(x, y);
let curSelected = this.selection.selectedElements[0];
// If one of the following condition is true, keep the selection:
// 1. if group is currently selected
// 2. if the selected element is descendant of the hovered element
// 3. not hovering any element or hovering the same element
//
// Otherwise, we update the selection to the current hovered element
const shouldKeepSelection =
isGfxGroupCompatibleModel(curSelected) ||
(isGfxGroupCompatibleModel(currentHoveredElem) &&
currentHoveredElem.hasDescendant(curSelected)) ||
!currentHoveredElem ||
currentHoveredElem === curSelected;
if (!shouldKeepSelection) {
curSelected = currentHoveredElem;
this.selection.set({
elements: [curSelected.id],
editing: false,
});
}
}
return this.selection.editing
? DefaultModeDragType.NativeEditing
: DefaultModeDragType.ContentMoving;
@@ -168,6 +194,17 @@ export class DefaultTool extends BaseTool {
}
}
private _getElementInGroup(modelX: number, modelY: number) {
const tryGetLockedAncestor = (e: GfxModel | null) => {
if (e?.isLockedByAncestor()) {
return e.groups.findLast(group => group.isLocked());
}
return e;
};
return tryGetLockedAncestor(this.gfx.getElementInGroup(modelX, modelY));
}
private initializeDragState(
dragType: DefaultModeDragType,
event: PointerEventState
@@ -7,6 +7,7 @@ import { literal } from 'lit/static-html.js';
import { effects } from './effects';
import {
ConnectorDomRendererExtension,
EdgelessCRUDExtension,
EdgelessLegacySlotExtension,
EditPropsMiddlewareBuilder,
@@ -26,6 +27,7 @@ export class SurfaceViewExtension extends ViewExtensionProvider {
super.setup(context);
context.register([
FlavourExtension('affine:surface'),
ConnectorDomRendererExtension,
EdgelessCRUDExtension,
EdgelessLegacySlotExtension,
ExportManagerExtension,
@@ -67,8 +67,6 @@ export class TableSelection extends BaseSelection {
static override type = 'table';
static override recoverable = true;
readonly data: TableSelectionData;
constructor({
+1 -1
View File
@@ -21,7 +21,7 @@
"@lit/context": "^1.1.2",
"@lottiefiles/dotlottie-wc": "^0.5.0",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/hast": "^3.0.4",
"@types/katex": "^0.16.7",
"@types/lodash-es": "^4.17.12",
@@ -111,7 +111,6 @@ export class MenuInput extends MenuFocusable {
}}"
@input="${this.onInput}"
placeholder="${this.data.placeholder ?? ''}"
@keypress="${this.stopPropagation}"
@keydown="${this.onKeydown}"
@copy="${this.stopPropagation}"
@paste="${this.stopPropagation}"
@@ -92,7 +92,6 @@ export class FilterableListComponent<Props = unknown> extends WithDisposable(
const isFlip = !!this.placement?.startsWith('top');
const _handleInputKeydown = (ev: KeyboardEvent) => {
ev.stopPropagation();
switch (ev.key) {
case 'ArrowUp': {
ev.preventDefault();
@@ -1,25 +1,14 @@
import { cssVarV2 } from '@toeverything/theme/v2';
import { ColorScheme } from '@blocksuite/affine-model';
import { html } from 'lit';
export const LoadingIcon = ({
size = '1em',
progress = 0.2,
ringColor = cssVarV2('loading/background'),
strokeColor = cssVarV2('loading/foreground'),
}: {
size?: string;
progress?: number;
ringColor?: string;
strokeColor?: string;
} = {}) =>
const LoadingIcon = (color: string) =>
html`<svg
width="${size}"
height="${size}"
viewBox="0 0 24 24"
width="16"
height="16"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="none"
>
<style>
<style xmlns="http://www.w3.org/2000/svg">
.spinner {
transform-origin: center;
animation: spinner_animate 0.75s infinite linear;
@@ -30,18 +19,21 @@ export const LoadingIcon = ({
}
}
</style>
<circle cx="12" cy="12" r="8" stroke="${ringColor}" stroke-width="4" />
<circle
<path
d="M14.6666 8.00004C14.6666 11.6819 11.6818 14.6667 7.99992 14.6667C4.31802 14.6667 1.33325 11.6819 1.33325 8.00004C1.33325 4.31814 4.31802 1.33337 7.99992 1.33337C11.6818 1.33337 14.6666 4.31814 14.6666 8.00004ZM3.30003 8.00004C3.30003 10.5957 5.40424 12.6999 7.99992 12.6999C10.5956 12.6999 12.6998 10.5957 12.6998 8.00004C12.6998 5.40436 10.5956 3.30015 7.99992 3.30015C5.40424 3.30015 3.30003 5.40436 3.30003 8.00004Z"
fill="${color}"
fill-opacity="0.1"
/>
<path
d="M13.6833 8.00004C14.2263 8.00004 14.674 7.55745 14.5942 7.02026C14.5142 6.48183 14.3684 5.954 14.1591 5.44882C13.8241 4.63998 13.333 3.90505 12.714 3.286C12.0949 2.66694 11.36 2.17588 10.5511 1.84084C10.046 1.63159 9.51812 1.48576 8.9797 1.40576C8.44251 1.32595 7.99992 1.77363 7.99992 2.31671C7.99992 2.85979 8.44486 3.28974 8.9761 3.40253C9.25681 3.46214 9.53214 3.54746 9.79853 3.65781C10.3688 3.894 10.8869 4.2402 11.3233 4.67664C11.7598 5.11307 12.106 5.6312 12.3422 6.20143C12.4525 6.46782 12.5378 6.74315 12.5974 7.02386C12.7102 7.5551 13.1402 8.00004 13.6833 8.00004Z"
fill="#1C9EE4"
class="spinner"
cx="12"
cy="12"
r="8"
stroke="${strokeColor}"
stroke-width="4"
stroke-linecap="round"
stroke-dasharray="${2 * Math.PI * 8 * progress} ${2 *
Math.PI *
8 *
(1 - progress)}"
/>
</svg>`;
export const LightLoadingIcon = LoadingIcon('black');
export const DarkLoadingIcon = LoadingIcon('white');
export const getLoadingIconWith = (theme: ColorScheme = ColorScheme.Light) =>
theme === ColorScheme.Light ? LightLoadingIcon : DarkLoadingIcon;
@@ -840,6 +840,28 @@ export const EmbedCardDarkCubeIcon = html`
</svg>
`;
export const ReloadIcon = html`<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_6505_24239)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.625 6C1.625 3.58375 3.58375 1.625 6 1.625C7.12028 1.625 8.14299 2.04656 8.91676 2.7391L8.91796 2.74017L9.625 3.37847V2C9.625 1.79289 9.79289 1.625 10 1.625C10.2071 1.625 10.375 1.79289 10.375 2V4.22222C10.375 4.42933 10.2071 4.59722 10 4.59722H7.77778C7.57067 4.59722 7.40278 4.42933 7.40278 4.22222C7.40278 4.01512 7.57067 3.84722 7.77778 3.84722H9.025L8.41657 3.29795C8.41637 3.29777 8.41617 3.29759 8.41597 3.29741C7.77447 2.7235 6.92838 2.375 6 2.375C3.99797 2.375 2.375 3.99797 2.375 6C2.375 8.00203 3.99797 9.625 6 9.625C7.72469 9.625 9.16888 8.42017 9.53518 6.80591C9.58101 6.60393 9.78189 6.47736 9.98386 6.52319C10.1858 6.56902 10.3124 6.7699 10.2666 6.97187C9.82447 8.92025 8.08257 10.375 6 10.375C3.58375 10.375 1.625 8.41625 1.625 6Z"
fill="#1E96EB"
/>
</g>
<defs>
<clipPath id="clip0_6505_24239">
<rect width="12" height="12" fill="white" />
</clipPath>
</defs>
</svg>`;
export const EmbedPageIcon = icons.LinkedPageIcon({
width: '16',
height: '16',
@@ -28,7 +28,6 @@ export type ResolvedStateInfoPart = {
error: boolean;
state: StateKind;
url: string | null;
needUpload: boolean;
};
export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
@@ -42,7 +41,6 @@ export class ResourceController implements Disposable {
readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
const url = this.blobUrl$.value;
const {
needUpload = false,
uploading = false,
downloading = false,
overSize = false,
@@ -59,13 +57,7 @@ export class ResourceController implements Disposable {
const loading = state === 'uploading' || state === 'loading';
return {
error: hasError,
needUpload,
loading,
state,
url,
};
return { error: hasError, loading, state, url };
});
private engine?: BlobEngine;
@@ -100,8 +92,7 @@ export class ResourceController implements Disposable {
errorIcon?: TemplateResult;
} & StateInfo
): ResolvedStateInfo {
const { error, loading, state, url, needUpload } =
this.resolvedState$.value;
const { error, loading, state, url } = this.resolvedState$.value;
const { icon, title, description, loadingIcon, errorIcon } = info;
@@ -113,11 +104,11 @@ export class ResourceController implements Disposable {
title,
description,
url,
needUpload,
};
if (loading) {
result.icon = loadingIcon ?? icon;
result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
} else if (error) {
result.icon = errorIcon ?? icon;
result.description = this.state$.value.errorMessage ?? description;
@@ -139,15 +130,13 @@ export class ResourceController implements Disposable {
if (!blobState$) return;
const subscription = blobState$.subscribe(state => {
let { uploading, downloading, errorMessage } = state;
if (state.overSize) {
let { uploading, downloading } = state;
if (state.overSize || state.errorMessage) {
uploading = false;
downloading = false;
} else if ((uploading || downloading) && errorMessage) {
errorMessage = null;
}
this.updateState({ ...state, uploading, downloading, errorMessage });
this.updateState({ ...state, uploading, downloading });
});
return () => subscription.unsubscribe();
@@ -189,9 +178,6 @@ export class ResourceController implements Disposable {
}
async refreshUrlWith(type?: string) {
// Resets the state.
this.state$.value = {};
const url = await this.createUrlWith(type);
if (!url) return;
@@ -205,21 +191,6 @@ export class ResourceController implements Disposable {
URL.revokeObjectURL(prevUrl);
}
// Re-upload to the server.
async upload() {
const blobId = this.blobId$.peek();
if (!blobId) return;
const state = this.state$.peek();
if (!state.needUpload) return;
if (state.uploading) return;
// Resets the state.
this.state$.value = {};
return await this.engine?.upload(blobId);
}
dispose() {
const url = this.blobUrl$.peek();
if (!url) return;
@@ -2,7 +2,7 @@ import {
fontBaseStyle,
panelBaseColorsStyle,
} from '@blocksuite/affine-shared/styles';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import {
createButtonPopper,
stopPropagation,
@@ -15,8 +15,7 @@ import { property, query } from 'lit/decorators.js';
@requiredProperties({
message: PropTypes.string,
needUpload: PropTypes.boolean,
action: PropTypes.instanceOf(Function),
reload: PropTypes.instanceOf(Function),
})
export class ResourceStatus extends WithDisposable(LitElement) {
static override styles = css`
@@ -33,7 +32,7 @@ export class ResourceStatus extends WithDisposable(LitElement) {
cursor: pointer;
color: ${unsafeCSSVarV2('button/pureWhiteText')};
background: ${unsafeCSSVarV2('status/error')};
box-shadow: ${unsafeCSSVar('overlayShadow')};
box-shadow: var(--affine-overlay-shadow);
}
${panelBaseColorsStyle('.popper')}
@@ -44,36 +43,28 @@ export class ResourceStatus extends WithDisposable(LitElement) {
padding: 8px;
border-radius: 8px;
width: 260px;
font-size: var(--affine-font-sm);
font-style: normal;
font-weight: 400;
line-height: 22px;
font-size: ${unsafeCSSVar('fontSm')};
&[data-show] {
display: flex;
flex-direction: column;
gap: 4px;
gap: 8px;
}
}
.header {
font-weight: 500;
}
.content {
font-feature-settings:
'liga' off,
'clig' off;
color: ${unsafeCSSVarV2('text/primary')};
}
.footer {
display: flex;
justify-content: flex-end;
margin-top: 4px;
}
button.action {
button.reload {
display: flex;
align-items: center;
padding: 2px 12px;
@@ -111,35 +102,23 @@ export class ResourceStatus extends WithDisposable(LitElement) {
this._popper?.toggle();
});
this.disposables.addFromEvent(
this._actionButton,
this._reloadButton,
'click',
(_: MouseEvent) => {
this._popper?.hide();
this.action();
this.reload();
}
);
this.disposables.add(() => this._popper?.dispose());
}
override render() {
const { message, needUpload } = this;
const { type, label } = needUpload
? {
type: 'Upload',
label: 'Retry',
}
: {
type: 'Download',
label: 'Reload',
};
return html`
<button class="status">${InformationIcon()}</button>
<div class="popper">
<div class="header">${type} failed</div>
<div class="content">${message}</div>
<div class="content">${this.message}</div>
<div class="footer">
<button class="action">${label}</button>
<button class="reload">Reload</button>
</div>
</div>
`;
@@ -151,15 +130,12 @@ export class ResourceStatus extends WithDisposable(LitElement) {
@query('button.status')
private accessor _trigger!: HTMLButtonElement;
@query('button.action')
private accessor _actionButton!: HTMLButtonElement;
@query('button.reload')
private accessor _reloadButton!: HTMLButtonElement;
@property({ attribute: false })
accessor message!: string;
@property({ attribute: false })
accessor needUpload!: boolean;
@property({ attribute: false })
accessor action!: () => void;
accessor reload!: () => void;
}
@@ -92,7 +92,7 @@ export class Slider extends WithDisposable(LitElement) {
const dispose = on(this, 'pointermove', this._onPointerMove);
this._disposables.add(once(this, 'pointerup', dispose));
this._disposables.add(once(this, 'pointerleave', dispose));
this._disposables.add(once(this, 'pointerout', dispose));
};
private readonly _onPointerMove = (e: PointerEvent) => {
@@ -2,11 +2,6 @@ import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { css } from 'lit';
export const styles = css`
:host {
display: block;
touch-action: none;
}
:host([disabled]) {
opacity: 0.5;
pointer-events: none;
+1 -1
View File
@@ -21,7 +21,7 @@
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@toeverything/theme": "^1.1.14",
"@types/lodash-es": "^4.17.12",
"clsx": "^2.1.1",
"date-fns": "^4.0.0",
@@ -70,19 +70,3 @@ export const dividerV = css({
backgroundColor: 'var(--affine-divider-color)',
margin: '0 8px',
});
export const dv = {
p2,
p4,
p8,
hover,
icon16,
icon20,
border,
round4,
round8,
color2,
shadow2,
dividerH,
dividerV,
};
+161 -138
View File
@@ -2,43 +2,42 @@ import type {
DatabaseAllEvents,
EventTraceFn,
} from '@blocksuite/affine-shared/services';
import type { DisposableMember } from '@blocksuite/global/disposable';
import { IS_MOBILE } from '@blocksuite/global/env';
import { BlockSuiteError } from '@blocksuite/global/exceptions';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import {
type Clipboard,
type EventName,
ShadowlessElement,
type UIEventHandler,
} from '@blocksuite/std';
import { ShadowlessElement } from '@blocksuite/std';
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
import { css, unsafeCSS } from 'lit';
import { property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ref } from 'lit/directives/ref.js';
import { keyed } from 'lit/directives/keyed.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { html } from 'lit/static-html.js';
import { dataViewCommonStyle } from './common/css-variable.js';
import type { DataSource } from './data-source/index.js';
import type { DataViewSelection } from './types.js';
import { cacheComputed } from './utils/cache.js';
import type { DataViewSelection, DataViewSelectionState } from './types.js';
import { renderUniLit } from './utils/uni-component/index.js';
import type { DataViewUILogicBase } from './view/data-view-base.js';
import type { DataViewInstance, DataViewProps } from './view/types.js';
import type { SingleView } from './view-manager/single-view.js';
import type { DataViewWidget } from './widget/index.js';
export type DataViewRendererConfig = {
clipboard: Clipboard;
onDrag?: (evt: MouseEvent, id: string) => () => void;
notification: {
toast: (message: string) => void;
};
virtualPadding$: ReadonlySignal<number>;
headerWidget: DataViewWidget | undefined;
handleEvent: (name: EventName, handler: UIEventHandler) => DisposableMember;
bindHotkey: (hotkeys: Record<string, UIEventHandler>) => DisposableMember;
dataSource: DataSource;
type ViewProps = {
view: SingleView;
selection$: ReadonlySignal<DataViewSelectionState>;
setSelection: (selection?: DataViewSelectionState) => void;
bindHotkey: DataViewProps['bindHotkey'];
handleEvent: DataViewProps['handleEvent'];
};
export type DataViewRendererConfig = Pick<
DataViewProps,
| 'bindHotkey'
| 'handleEvent'
| 'virtualPadding$'
| 'clipboard'
| 'dataSource'
| 'headerWidget'
| 'onDrag'
| 'notification'
> & {
selection$: ReadonlySignal<DataViewSelection | undefined>;
setSelection: (selection: DataViewSelection | undefined) => void;
eventTrace: EventTraceFn<DatabaseAllEvents>;
@@ -53,104 +52,7 @@ export type DataViewRendererConfig = {
};
};
export class DataViewRootUILogic {
private get dataSource() {
return this.config.dataSource;
}
private get viewManager() {
return this.dataSource.viewManager;
}
private createDataViewUILogic(viewId: string): DataViewUILogicBase {
const view = this.viewManager.viewGet(viewId);
if (!view) {
throw new BlockSuiteError(
BlockSuiteError.ErrorCode.DatabaseBlockError,
`View ${viewId} not found`
);
}
const pcLogic = view.meta.renderer.pcLogic;
const mobileLogic = view.meta.renderer.mobileLogic;
const logic = (IS_MOBILE ? mobileLogic : pcLogic) ?? pcLogic;
return new (logic(view))(this, view);
}
private readonly views$ = cacheComputed(this.viewManager.views$, viewId =>
this.createDataViewUILogic(viewId)
);
private readonly viewsMap$ = computed(() => {
return Object.fromEntries(
this.views$.list.value.map(logic => [logic.view.id, logic])
);
});
private readonly _uiRef = signal<DataViewRootUI>();
get selection$() {
return this.config.selection$;
}
setSelection(selection?: DataViewSelection) {
this.config.setSelection(selection);
}
constructor(public readonly config: DataViewRendererConfig) {}
get dataViewRenderer() {
return this._uiRef.value;
}
readonly currentViewId$ = computed(() => {
return this.dataSource.viewManager.currentViewId$.value;
});
readonly currentView$ = computed(() => {
const currentViewId = this.currentViewId$.value;
if (!currentViewId) {
return;
}
return this.viewsMap$.value[currentViewId];
});
focusFirstCell = () => {
this.currentView$.value?.focusFirstCell();
};
openDetailPanel = (ops: {
view: SingleView;
rowId: string;
onClose?: () => void;
}) => {
const openDetailPanel = this.config.detailPanelConfig.openDetailPanel;
const target = this.dataViewRenderer;
if (openDetailPanel && target) {
openDetailPanel(target, {
view: ops.view,
rowId: ops.rowId,
})
.catch(console.error)
.finally(ops.onClose);
}
};
setupViewChangeListener() {
let preId: string | undefined = undefined;
return this.currentViewId$.subscribe(current => {
if (current !== preId) {
this.config.setSelection(undefined);
}
preId = current;
});
}
render() {
return html` <affine-data-view-renderer
${ref(this._uiRef)}
.logic="${this}"
></affine-data-view-renderer>`;
}
}
export class DataViewRootUI extends SignalWatcher(
export class DataViewRenderer extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
@@ -161,14 +63,63 @@ export class DataViewRootUI extends SignalWatcher(
}
`;
@property({ attribute: false })
accessor logic!: DataViewRootUILogic;
private readonly _view = signal<DataViewInstance>();
@state()
accessor currentView: string | undefined = undefined;
@property({ attribute: false })
accessor config!: DataViewRendererConfig;
private readonly currentViewId$ = computed(() => {
return this.config.dataSource.viewManager.currentViewId$.value;
});
viewMap$ = computed(() => {
const manager = this.config.dataSource.viewManager;
return Object.fromEntries(
manager.views$.value.map(view => [view, manager.viewGet(view)])
);
});
currentViewConfig$ = computed<ViewProps | undefined>(() => {
const currentViewId = this.currentViewId$.value;
if (!currentViewId) {
return;
}
const view = this.viewMap$.value[currentViewId];
if (!view) {
return;
}
return {
view: view,
selection$: computed(() => {
const selection$ = this.config.selection$;
if (selection$.value?.viewId === currentViewId) {
return selection$.value;
}
return;
}),
setSelection: selection => {
this.config.setSelection(selection);
},
handleEvent: (name, handler) =>
this.config.handleEvent(name, context => {
return handler(context);
}),
bindHotkey: hotkeys =>
this.config.bindHotkey(
Object.fromEntries(
Object.entries(hotkeys).map(([key, fn]) => [
key,
ctx => {
return fn(ctx);
},
])
)
),
};
});
focusFirstCell = () => {
this.logic.focusFirstCell();
this.view?.focusFirstCell();
};
openDetailPanel = (ops: {
@@ -176,12 +127,72 @@ export class DataViewRootUI extends SignalWatcher(
rowId: string;
onClose?: () => void;
}) => {
this.logic.openDetailPanel(ops);
const openDetailPanel = this.config.detailPanelConfig.openDetailPanel;
if (openDetailPanel) {
openDetailPanel(this, {
view: ops.view,
rowId: ops.rowId,
})
.catch(console.error)
.finally(ops.onClose);
}
};
get view() {
return this._view.value;
}
private renderView(viewData?: ViewProps) {
if (!viewData) {
return;
}
const props: DataViewProps = {
dataViewEle: this,
headerWidget: this.config.headerWidget,
onDrag: this.config.onDrag,
dataSource: this.config.dataSource,
virtualPadding$: this.config.virtualPadding$,
clipboard: this.config.clipboard,
notification: this.config.notification,
view: viewData.view,
selection$: viewData.selection$,
setSelection: viewData.setSelection,
bindHotkey: viewData.bindHotkey,
handleEvent: viewData.handleEvent,
eventTrace: (key, params) => {
this.config.eventTrace(key, {
...(params as DatabaseAllEvents[typeof key]),
viewId: viewData.view.id,
viewType: viewData.view.type,
});
},
};
const renderer = viewData.view.meta.renderer;
const view =
(IS_MOBILE ? renderer.mobileView : renderer.view) ?? renderer.view;
return keyed(
viewData.view.id,
renderUniLit(
view,
{ props },
{
ref: this._view,
}
)
);
}
override connectedCallback() {
super.connectedCallback();
this.disposables.add(this.logic.setupViewChangeListener());
let preId: string | undefined = undefined;
this.disposables.add(
this.currentViewId$.subscribe(current => {
if (current !== preId) {
this.config.setSelection(undefined);
}
preId = current;
})
);
}
override render() {
@@ -190,22 +201,34 @@ export class DataViewRootUI extends SignalWatcher(
'data-view-root': true,
'prevent-reference-popup': true,
});
const currentView = this.logic.currentView$.value;
if (!currentView) {
return;
}
return html`
<div style="display: contents" class="${containerClass}">
${renderUniLit(currentView.renderer, {
logic: currentView,
})}
${this.renderView(this.currentViewConfig$.value)}
</div>
`;
}
@state()
accessor currentView: string | undefined = undefined;
}
declare global {
interface HTMLElementTagNameMap {
'affine-data-view-renderer': DataViewRootUI;
'affine-data-view-renderer': DataViewRenderer;
}
}
export class DataView {
private readonly _ref = createRef<DataViewRenderer>();
get expose() {
return this._ref.value?.view;
}
render(props: DataViewRendererConfig) {
return html` <affine-data-view-renderer
${ref(this._ref)}
.config="${props}"
></affine-data-view-renderer>`;
}
}
@@ -2,7 +2,7 @@ import { DataViewPropertiesSettingView } from './common/properties.js';
import { Button } from './component/button/button.js';
import { Overflow } from './component/overflow/overflow.js';
import { MultiTagSelect, MultiTagView } from './component/tags/index.js';
import { DataViewRootUI } from './data-view.js';
import { DataViewRenderer } from './data-view.js';
import { RecordDetail } from './detail/detail.js';
import { RecordField } from './detail/field.js';
import { VariableRefView } from './expression/ref/ref-view.js';
@@ -15,7 +15,7 @@ import { AffineLitIcon, UniAnyRender, UniLit } from './index.js';
import { AnyRender } from './utils/uni-component/render-template.js';
export function coreEffects() {
customElements.define('affine-data-view-renderer', DataViewRootUI);
customElements.define('affine-data-view-renderer', DataViewRenderer);
customElements.define('any-render', AnyRender);
customElements.define(
'data-view-properties-setting',
@@ -1,7 +1,7 @@
export * from './common/index.js';
export * from './component/index.js';
export { DataSourceBase } from './data-source/base.js';
export { DataViewRootUILogic } from './data-view.js';
export { DataView } from './data-view.js';
export * from './filter/index.js';
export * from './group-by';
export * from './logical/index.js';
@@ -183,6 +183,7 @@ export class TypeSystem {
// eslint-disable-next-line sonarjs/no-collapsible-if
if (realArg != null) {
if (!this._unify(newCtx, realArg, arg)) {
console.log('arg', realArg, arg);
return;
}
}

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