mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-05 11:35:34 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4afbb7c68 | |||
| 565f61456f |
@@ -6,7 +6,6 @@ yarn install
|
||||
|
||||
# Build Server Dependencies
|
||||
yarn affine @affine/server-native build
|
||||
yarn affine @affine/reader build
|
||||
|
||||
# Create database
|
||||
yarn affine @affine/server prisma migrate reset -f
|
||||
|
||||
@@ -10,7 +10,6 @@ services:
|
||||
environment:
|
||||
DATABASE_URL: postgresql://affine:affine@db:5432/affine
|
||||
REDIS_SERVER_HOST: redis
|
||||
AFFINE_INDEXER_SEARCH_ENDPOINT: http://indexer:9308
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg16
|
||||
@@ -24,19 +23,5 @@ services:
|
||||
redis:
|
||||
image: redis
|
||||
|
||||
indexer:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
volumes:
|
||||
- manticoresearch_data:/var/lib/manticore
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
manticoresearch_data:
|
||||
|
||||
@@ -12,4 +12,4 @@ DB_DATABASE_NAME=affine
|
||||
# ELASTIC_PLATFORM=linux/arm64
|
||||
|
||||
# manticoresearch
|
||||
MANTICORE_VERSION=9.3.2
|
||||
MANTICORE_VERSION=9.2.14
|
||||
|
||||
@@ -26,7 +26,7 @@ services:
|
||||
|
||||
# https://manual.manticoresearch.com/Starting_the_server/Docker
|
||||
manticoresearch:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.2.14}
|
||||
ports:
|
||||
- 9308:9308
|
||||
ulimits:
|
||||
|
||||
@@ -21,3 +21,8 @@ CONFIG_LOCATION=~/.affine/self-host/config
|
||||
DB_USERNAME=affine
|
||||
DB_PASSWORD=
|
||||
DB_DATABASE=affine
|
||||
|
||||
# indexer search provider manticoresearch version
|
||||
MANTICORE_VERSION=9.2.14
|
||||
# position of the manticoresearch data to persist
|
||||
MANTICORE_DATA_LOCATION=~/.affine/self-host/manticore
|
||||
|
||||
@@ -10,6 +10,8 @@ services:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
indexer:
|
||||
condition: service_healthy
|
||||
affine_migration:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
@@ -21,7 +23,7 @@ services:
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_INDEXER_SEARCH_ENDPOINT=http://indexer:9308
|
||||
restart: unless-stopped
|
||||
|
||||
affine_migration:
|
||||
@@ -37,12 +39,14 @@ services:
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_INDEXER_SEARCH_ENDPOINT=http://indexer:9308
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
indexer:
|
||||
condition: service_healthy
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
@@ -74,3 +78,24 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
indexer:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.2.14}
|
||||
container_name: affine_indexer
|
||||
volumes:
|
||||
- ${MANTICORE_DATA_LOCATION}:/var/lib/manticore
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
healthcheck:
|
||||
test:
|
||||
['CMD', 'wget', '-O-', 'http://127.0.0.1:9308']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -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,
|
||||
@@ -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\":\"\"}",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -125,7 +125,6 @@ jobs:
|
||||
- name: Run BS Docs Build
|
||||
run: |
|
||||
yarn affine bs-docs build
|
||||
git checkout packages/frontend/i18n/src/i18n-completenesses.json
|
||||
git status --porcelain | grep . && {
|
||||
echo "Run 'yarn typecheck && yarn affine bs-docs build' and make sure all changes are submitted"
|
||||
exit 1
|
||||
@@ -184,7 +183,6 @@ jobs:
|
||||
yarn affine gql build
|
||||
yarn affine i18n build
|
||||
yarn affine server genconfig
|
||||
git checkout packages/frontend/i18n/src/i18n-completenesses.json
|
||||
git status --porcelain | grep . && {
|
||||
echo "Run 'yarn affine init && yarn affine gql build && yarn affine i18n build && yarn affine server genconfig' and make sure all changes are submitted"
|
||||
exit 1
|
||||
@@ -585,7 +583,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -735,7 +733,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -959,7 +957,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -1001,7 +999,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
|
||||
@@ -1053,7 +1056,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -1100,7 +1103,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 }}
|
||||
@@ -1175,7 +1183,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -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
|
||||
@@ -130,7 +135,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -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 }}
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-foundation": "workspace:*",
|
||||
"@blocksuite/affine-fragment-adapter-panel": "workspace:*",
|
||||
"@blocksuite/affine-fragment-doc-title": "workspace:*",
|
||||
"@blocksuite/affine-fragment-frame-panel": "workspace:*",
|
||||
"@blocksuite/affine-fragment-outline": "workspace:*",
|
||||
@@ -210,8 +209,6 @@
|
||||
"./fragments/frame-panel/view": "./src/fragments/frame-panel/view.ts",
|
||||
"./fragments/outline": "./src/fragments/outline/index.ts",
|
||||
"./fragments/outline/view": "./src/fragments/outline/view.ts",
|
||||
"./fragments/adapter-panel": "./src/fragments/adapter-panel/index.ts",
|
||||
"./fragments/adapter-panel/view": "./src/fragments/adapter-panel/view.ts",
|
||||
"./gfx/text": "./src/gfx/text/index.ts",
|
||||
"./gfx/text/store": "./src/gfx/text/store.ts",
|
||||
"./gfx/text/view": "./src/gfx/text/view.ts",
|
||||
|
||||
@@ -19,7 +19,6 @@ import { SurfaceViewExtension } from '@blocksuite/affine-block-surface/view';
|
||||
import { SurfaceRefViewExtension } from '@blocksuite/affine-block-surface-ref/view';
|
||||
import { TableViewExtension } from '@blocksuite/affine-block-table/view';
|
||||
import { FoundationViewExtension } from '@blocksuite/affine-foundation/view';
|
||||
import { AdapterPanelViewExtension } from '@blocksuite/affine-fragment-adapter-panel/view';
|
||||
import { DocTitleViewExtension } from '@blocksuite/affine-fragment-doc-title/view';
|
||||
import { FramePanelViewExtension } from '@blocksuite/affine-fragment-frame-panel/view';
|
||||
import { OutlineViewExtension } from '@blocksuite/affine-fragment-outline/view';
|
||||
@@ -125,6 +124,5 @@ export function getInternalViewExtensions() {
|
||||
DocTitleViewExtension,
|
||||
FramePanelViewExtension,
|
||||
OutlineViewExtension,
|
||||
AdapterPanelViewExtension,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-fragment-adapter-panel';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-fragment-adapter-panel/view';
|
||||
@@ -30,7 +30,6 @@
|
||||
{ "path": "../components" },
|
||||
{ "path": "../ext-loader" },
|
||||
{ "path": "../foundation" },
|
||||
{ "path": "../fragments/adapter-panel" },
|
||||
{ "path": "../fragments/doc-title" },
|
||||
{ "path": "../fragments/frame-panel" },
|
||||
{ "path": "../fragments/outline" },
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.14",
|
||||
"file-type": "^21.0.0",
|
||||
"file-type": "^20.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
@@ -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 {
|
||||
@@ -303,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(name.split('.').pop() ?? '');
|
||||
|
||||
const resolvedState = this.resourceController.resolveStateWith({
|
||||
loadingIcon: LoadingIcon(),
|
||||
loadingIcon,
|
||||
errorIcon: WarningIcon(),
|
||||
icon: AttachmentIcon(),
|
||||
title: name,
|
||||
|
||||
@@ -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 {
|
||||
@@ -106,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
+8
-4
@@ -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
|
||||
|
||||
+4
-4
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
+6
-2
@@ -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 =
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.14",
|
||||
"file-type": "^21.0.0",
|
||||
"file-type": "^20.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
@@ -46,16 +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;
|
||||
right: 4px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
background: ${unsafeCSSVarV2('loading/backgroundLayer')};
|
||||
|
||||
& > svg {
|
||||
font-size: 25.71px;
|
||||
}
|
||||
}
|
||||
|
||||
affine-page-image .affine-image-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,9 +138,7 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
|
||||
});
|
||||
|
||||
const resovledState = this.resourceController.resolveStateWith({
|
||||
loadingIcon: LoadingIcon({
|
||||
strokeColor: cssVarV2('button/pureWhiteText'),
|
||||
}),
|
||||
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,15 +39,11 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
background: ${unsafeCSSVarV2('loading/backgroundLayer')};
|
||||
|
||||
& > svg {
|
||||
font-size: 25.71px;
|
||||
}
|
||||
}
|
||||
|
||||
affine-edgeless-image .affine-image-status {
|
||||
@@ -111,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;
|
||||
|
||||
@@ -124,9 +124,7 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
|
||||
});
|
||||
|
||||
const resovledState = this.resourceController.resolveStateWith({
|
||||
loadingIcon: LoadingIcon({
|
||||
strokeColor: cssVarV2('button/pureWhiteText'),
|
||||
}),
|
||||
loadingIcon,
|
||||
errorIcon: BrokenImageIcon(),
|
||||
icon: ImageIcon(),
|
||||
title: 'Image',
|
||||
@@ -150,7 +148,7 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
|
||||
</div>
|
||||
${when(
|
||||
resovledState.loading,
|
||||
() => html`<div class="loading">${resovledState.icon}</div>`
|
||||
() => html`<div class="loading">${loadingIcon}</div>`
|
||||
)}
|
||||
${when(
|
||||
resovledState.error && resovledState.description,
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -74,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) {
|
||||
@@ -92,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>
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
@@ -69,6 +66,7 @@ import {
|
||||
DEFAULT_NOTE_TIP,
|
||||
} from './utils/consts.js';
|
||||
import { deleteElements } from './utils/crud.js';
|
||||
import { getNextShapeType } from './utils/hotkey-utils.js';
|
||||
import { isCanvasElement } from './utils/query.js';
|
||||
|
||||
export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
@@ -218,8 +216,10 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { shapeName } = controller.activatedOption;
|
||||
const nextShapeName = getNextShapeType(shapeName);
|
||||
this._setEdgelessTool(ShapeTool, {
|
||||
shapeName: controller.cycleShapeName('prev'),
|
||||
shapeName: nextShapeName,
|
||||
});
|
||||
|
||||
controller.createOverlay();
|
||||
@@ -475,6 +475,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 +515,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 +715,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 +731,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;
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ShapeToolOption } from '@blocksuite/affine-gfx-shape';
|
||||
import { ShapeType } from '@blocksuite/affine-model';
|
||||
|
||||
const shapeMap: Record<ShapeToolOption['shapeName'], number> = {
|
||||
[ShapeType.Rect]: 0,
|
||||
[ShapeType.Ellipse]: 1,
|
||||
[ShapeType.Diamond]: 2,
|
||||
[ShapeType.Triangle]: 3,
|
||||
roundedRect: 4,
|
||||
};
|
||||
const shapes = Object.keys(shapeMap) as ShapeToolOption['shapeName'][];
|
||||
|
||||
export function getNextShapeType(cur: ShapeToolOption['shapeName']) {
|
||||
return shapes[(shapeMap[cur] + 1) % shapes.length];
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -745,7 +745,7 @@ export class TableCell extends SignalWatcher(
|
||||
padding: '8px 12px',
|
||||
})}
|
||||
.yText="${this.text}"
|
||||
.inlineEventSource="${this.topContenteditableElement ?? nothing}"
|
||||
.inlineEventSource="${this.topContenteditableElement}"
|
||||
.attributesSchema="${this.inlineManager?.getSchema()}"
|
||||
.attributeRenderer="${this.inlineManager?.getRenderer()}"
|
||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||
|
||||
@@ -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,23 +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,
|
||||
strokeColor = cssVarV2('loading/foreground'),
|
||||
}: {
|
||||
size?: string;
|
||||
progress?: number;
|
||||
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;
|
||||
@@ -28,24 +19,21 @@ export const LoadingIcon = ({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="8"
|
||||
stroke="${cssVarV2('loading/background')}"
|
||||
stroke-width="4"
|
||||
<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"
|
||||
/>
|
||||
<circle
|
||||
<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',
|
||||
|
||||
@@ -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,39 +0,0 @@
|
||||
{
|
||||
"name": "@blocksuite/affine-fragment-adapter-panel",
|
||||
"description": "Adapter panel fragment for BlockSuite.",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"keywords": [],
|
||||
"author": "toeverything",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.14",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist",
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.21.0"
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
import type { Store, TransformerMiddleware } from '@blocksuite/affine/store';
|
||||
import {
|
||||
type HtmlAdapter,
|
||||
HtmlAdapterFactoryIdentifier,
|
||||
type MarkdownAdapter,
|
||||
MarkdownAdapterFactoryIdentifier,
|
||||
type PlainTextAdapter,
|
||||
PlainTextAdapterFactoryIdentifier,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { provide } from '@lit/context';
|
||||
import { effect, signal } from '@preact/signals-core';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, LitElement, type PropertyValues, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import {
|
||||
type AdapterPanelContext,
|
||||
adapterPanelContext,
|
||||
ADAPTERS,
|
||||
} from './config';
|
||||
|
||||
export const AFFINE_ADAPTER_PANEL = 'affine-adapter-panel';
|
||||
|
||||
export class AdapterPanel extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.adapters-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--affine-background-primary-color);
|
||||
box-sizing: border-box;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
}
|
||||
`;
|
||||
|
||||
get activeAdapter() {
|
||||
return this._context.activeAdapter$.value;
|
||||
}
|
||||
|
||||
private _createJob() {
|
||||
return this.store.getTransformer(this.transformerMiddlewares);
|
||||
}
|
||||
|
||||
private _getDocSnapshot() {
|
||||
const job = this._createJob();
|
||||
const result = job.docToSnapshot(this.store);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async _getHtmlContent() {
|
||||
try {
|
||||
const job = this._createJob();
|
||||
const htmlAdapterFactory = this.store.get(HtmlAdapterFactoryIdentifier);
|
||||
const htmlAdapter = htmlAdapterFactory.get(job) as HtmlAdapter;
|
||||
const result = await htmlAdapter.fromDoc(this.store);
|
||||
return result?.file;
|
||||
} catch (error) {
|
||||
console.error('Failed to get html content', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private async _getMarkdownContent() {
|
||||
try {
|
||||
const job = this._createJob();
|
||||
const markdownAdapterFactory = this.store.get(
|
||||
MarkdownAdapterFactoryIdentifier
|
||||
);
|
||||
const markdownAdapter = markdownAdapterFactory.get(
|
||||
job
|
||||
) as MarkdownAdapter;
|
||||
const result = await markdownAdapter.fromDoc(this.store);
|
||||
return result?.file;
|
||||
} catch (error) {
|
||||
console.error('Failed to get markdown content', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private async _getPlainTextContent() {
|
||||
try {
|
||||
const job = this._createJob();
|
||||
const plainTextAdapterFactory = this.store.get(
|
||||
PlainTextAdapterFactoryIdentifier
|
||||
);
|
||||
const plainTextAdapter = plainTextAdapterFactory.get(
|
||||
job
|
||||
) as PlainTextAdapter;
|
||||
const result = await plainTextAdapter.fromDoc(this.store);
|
||||
return result?.file;
|
||||
} catch (error) {
|
||||
console.error('Failed to get plain text content', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _updateActiveContent = async () => {
|
||||
const activeId = this.activeAdapter.id;
|
||||
switch (activeId) {
|
||||
case 'markdown':
|
||||
this._context.markdownContent$.value =
|
||||
(await this._getMarkdownContent()) || '';
|
||||
break;
|
||||
case 'html':
|
||||
this._context.htmlContent$.value = (await this._getHtmlContent()) || '';
|
||||
break;
|
||||
case 'plaintext':
|
||||
this._context.plainTextContent$.value =
|
||||
(await this._getPlainTextContent()) || '';
|
||||
break;
|
||||
case 'snapshot':
|
||||
this._context.docSnapshot$.value = this._getDocSnapshot() || null;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._context = {
|
||||
activeAdapter$: signal(ADAPTERS[0]),
|
||||
isHtmlPreview$: signal(false),
|
||||
docSnapshot$: signal(null),
|
||||
htmlContent$: signal(''),
|
||||
markdownContent$: signal(''),
|
||||
plainTextContent$: signal(''),
|
||||
};
|
||||
}
|
||||
|
||||
override willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
if (changedProperties.has('store')) {
|
||||
this._updateActiveContent().catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
if (this.activeAdapter) {
|
||||
this._updateActiveContent().catch(console.error);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<div class="adapters-container">
|
||||
<affine-adapter-panel-header
|
||||
.updateActiveContent=${this._updateActiveContent}
|
||||
></affine-adapter-panel-header>
|
||||
<affine-adapter-panel-body></affine-adapter-panel-body>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor store!: Store;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor transformerMiddlewares: TransformerMiddleware[] = [];
|
||||
|
||||
@provide({ context: adapterPanelContext })
|
||||
private accessor _context!: AdapterPanelContext;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_ADAPTER_PANEL]: AdapterPanel;
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
import { scrollbarStyle } from '@blocksuite/affine-shared/styles';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { consume } from '@lit/context';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
import {
|
||||
type AdapterItem,
|
||||
type AdapterPanelContext,
|
||||
adapterPanelContext,
|
||||
ADAPTERS,
|
||||
} from '../config';
|
||||
|
||||
export const AFFINE_ADAPTER_PANEL_BODY = 'affine-adapter-panel-body';
|
||||
|
||||
export class AdapterPanelBody extends SignalWatcher(LitElement) {
|
||||
static override styles = css`
|
||||
.adapter-panel-body {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
${scrollbarStyle('.adapter-panel-body')}
|
||||
|
||||
.adapter-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: pre-wrap;
|
||||
color: var(--affine-text-primary-color);
|
||||
font-size: var(--affine-font-sm);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.html-content {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.html-preview-container,
|
||||
.html-panel-content {
|
||||
width: 100%;
|
||||
flex: 1 0 0;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
color: var(--affine-text-primary-color);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
${scrollbarStyle('.html-panel-content')}
|
||||
|
||||
.html-panel-footer {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.html-toggle-container {
|
||||
display: flex;
|
||||
background: ${unsafeCSSVarV2('segment/background')};
|
||||
justify-content: flex-start;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.html-toggle-item {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 0px 4px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
border-radius: 4px;
|
||||
color: ${unsafeCSSVarV2('text/primary')};
|
||||
}
|
||||
|
||||
.html-toggle-item:hover {
|
||||
background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
}
|
||||
|
||||
.html-toggle-item[active] {
|
||||
background: ${unsafeCSSVarV2('segment/button')};
|
||||
box-shadow:
|
||||
var(--Shadow-buttonShadow-1-x, 0px) var(--Shadow-buttonShadow-1-y, 0px)
|
||||
var(--Shadow-buttonShadow-1-blur, 1px) 0px
|
||||
var(--Shadow-buttonShadow-1-color, rgba(0, 0, 0, 0.12)),
|
||||
var(--Shadow-buttonShadow-2-x, 0px) var(--Shadow-buttonShadow-2-y, 1px)
|
||||
var(--Shadow-buttonShadow-2-blur, 5px) 0px
|
||||
var(--Shadow-buttonShadow-2-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
.adapter-container {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.adapter-container.active {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
get activeAdapter() {
|
||||
return this._context.activeAdapter$.value;
|
||||
}
|
||||
|
||||
get isHtmlPreview() {
|
||||
return this._context.isHtmlPreview$.value;
|
||||
}
|
||||
|
||||
get htmlContent() {
|
||||
return this._context.htmlContent$.value;
|
||||
}
|
||||
|
||||
get markdownContent() {
|
||||
return this._context.markdownContent$.value;
|
||||
}
|
||||
|
||||
get plainTextContent() {
|
||||
return this._context.plainTextContent$.value;
|
||||
}
|
||||
|
||||
get docSnapshot() {
|
||||
return this._context.docSnapshot$.value;
|
||||
}
|
||||
|
||||
private _renderHtmlPanel() {
|
||||
return html`
|
||||
${this.isHtmlPreview
|
||||
? html`<iframe
|
||||
class="html-preview-container"
|
||||
.srcdoc=${this.htmlContent}
|
||||
sandbox="allow-same-origin"
|
||||
></iframe>`
|
||||
: html`<div class="html-panel-content">${this.htmlContent}</div>`}
|
||||
<div class="html-panel-footer">
|
||||
<div class="html-toggle-container">
|
||||
<span
|
||||
class="html-toggle-item"
|
||||
?active=${!this.isHtmlPreview}
|
||||
@click=${() => (this._context.isHtmlPreview$.value = false)}
|
||||
>Source</span
|
||||
>
|
||||
<span
|
||||
class="html-toggle-item"
|
||||
?active=${this.isHtmlPreview}
|
||||
@click=${() => (this._context.isHtmlPreview$.value = true)}
|
||||
>Preview</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private readonly _renderAdapterContent = (adapter: AdapterItem) => {
|
||||
switch (adapter.id) {
|
||||
case 'html':
|
||||
return this._renderHtmlPanel();
|
||||
case 'markdown':
|
||||
return this.markdownContent;
|
||||
case 'plaintext':
|
||||
return this.plainTextContent;
|
||||
case 'snapshot':
|
||||
return this.docSnapshot
|
||||
? JSON.stringify(this.docSnapshot, null, 4)
|
||||
: '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _renderAdapterContainer = (adapter: AdapterItem) => {
|
||||
const containerClasses = classMap({
|
||||
'adapter-container': true,
|
||||
active: this.activeAdapter.id === adapter.id,
|
||||
});
|
||||
|
||||
const contentClasses = classMap({
|
||||
'adapter-content': true,
|
||||
[`${adapter.id}-content`]: true,
|
||||
});
|
||||
|
||||
const content = this._renderAdapterContent(adapter);
|
||||
|
||||
return html`
|
||||
<div class=${containerClasses}>
|
||||
<div class=${contentClasses}>${content}</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<div class="adapter-panel-body">
|
||||
${ADAPTERS.map(adapter => this._renderAdapterContainer(adapter))}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@consume({ context: adapterPanelContext })
|
||||
private accessor _context!: AdapterPanelContext;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_ADAPTER_PANEL_BODY]: AdapterPanelBody;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { DocSnapshot } from '@blocksuite/store';
|
||||
import { createContext } from '@lit/context';
|
||||
import type { Signal } from '@preact/signals-core';
|
||||
|
||||
export type AdapterItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export const ADAPTERS: AdapterItem[] = [
|
||||
{ id: 'markdown', label: 'Markdown' },
|
||||
{ id: 'plaintext', label: 'PlainText' },
|
||||
{ id: 'html', label: 'HTML' },
|
||||
{ id: 'snapshot', label: 'Snapshot' },
|
||||
];
|
||||
|
||||
export type AdapterPanelContext = {
|
||||
activeAdapter$: Signal<AdapterItem>;
|
||||
isHtmlPreview$: Signal<boolean>;
|
||||
docSnapshot$: Signal<DocSnapshot | null>;
|
||||
htmlContent$: Signal<string>;
|
||||
markdownContent$: Signal<string>;
|
||||
plainTextContent$: Signal<string>;
|
||||
};
|
||||
|
||||
export const adapterPanelContext = createContext<AdapterPanelContext>(
|
||||
'adapterPanelContext'
|
||||
);
|
||||
@@ -1,17 +0,0 @@
|
||||
import { AdapterPanel, AFFINE_ADAPTER_PANEL } from './adapter-panel';
|
||||
import {
|
||||
AdapterPanelBody,
|
||||
AFFINE_ADAPTER_PANEL_BODY,
|
||||
} from './body/adapter-panel-body';
|
||||
import { AdapterMenu, AFFINE_ADAPTER_MENU } from './header/adapter-menu';
|
||||
import {
|
||||
AdapterPanelHeader,
|
||||
AFFINE_ADAPTER_PANEL_HEADER,
|
||||
} from './header/adapter-panel-header';
|
||||
|
||||
export function effects() {
|
||||
customElements.define(AFFINE_ADAPTER_PANEL, AdapterPanel);
|
||||
customElements.define(AFFINE_ADAPTER_MENU, AdapterMenu);
|
||||
customElements.define(AFFINE_ADAPTER_PANEL_HEADER, AdapterPanelHeader);
|
||||
customElements.define(AFFINE_ADAPTER_PANEL_BODY, AdapterPanelBody);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { consume } from '@lit/context';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
import {
|
||||
type AdapterItem,
|
||||
type AdapterPanelContext,
|
||||
adapterPanelContext,
|
||||
ADAPTERS,
|
||||
} from '../config';
|
||||
|
||||
export const AFFINE_ADAPTER_MENU = 'affine-adapter-menu';
|
||||
|
||||
export class AdapterMenu extends SignalWatcher(LitElement) {
|
||||
static override styles = css`
|
||||
.adapter-menu {
|
||||
min-width: 120px;
|
||||
padding: 4px;
|
||||
background: var(--affine-background-primary-color);
|
||||
border: 1px solid var(--affine-border-color);
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--affine-shadow-1);
|
||||
}
|
||||
.adapter-menu-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: var(--affine-text-primary-color);
|
||||
font-family: var(--affine-font-family);
|
||||
font-size: var(--affine-font-xs);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.adapter-menu-item:hover {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
.adapter-menu-item.active {
|
||||
color: var(--affine-primary-color);
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
`;
|
||||
|
||||
get activeAdapter() {
|
||||
return this._context.activeAdapter$.value;
|
||||
}
|
||||
|
||||
private readonly _handleAdapterChange = async (adapter: AdapterItem) => {
|
||||
this._context.activeAdapter$.value = adapter;
|
||||
this.abortController?.abort();
|
||||
};
|
||||
|
||||
override render() {
|
||||
return html`<div class="adapter-menu">
|
||||
${ADAPTERS.map(adapter => {
|
||||
const classes = classMap({
|
||||
'adapter-menu-item': true,
|
||||
active: this.activeAdapter.id === adapter.id,
|
||||
});
|
||||
return html`
|
||||
<button
|
||||
class=${classes}
|
||||
@click=${() => this._handleAdapterChange(adapter)}
|
||||
>
|
||||
${adapter.label}
|
||||
</button>
|
||||
`;
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor abortController: AbortController | null = null;
|
||||
|
||||
@consume({ context: adapterPanelContext })
|
||||
private accessor _context!: AdapterPanelContext;
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_ADAPTER_MENU]: AdapterMenu;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { ArrowDownSmallIcon, FlipDirectionIcon } from '@blocksuite/icons/lit';
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { consume } from '@lit/context';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
|
||||
import { type AdapterPanelContext, adapterPanelContext } from '../config';
|
||||
|
||||
export const AFFINE_ADAPTER_PANEL_HEADER = 'affine-adapter-panel-header';
|
||||
|
||||
export class AdapterPanelHeader extends SignalWatcher(LitElement) {
|
||||
static override styles = css`
|
||||
.adapter-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: var(--affine-background-primary-color);
|
||||
}
|
||||
.adapter-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.adapter-selector:hover {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
.adapter-selector-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--affine-text-primary-color);
|
||||
font-size: var(--affine-font-xs);
|
||||
}
|
||||
.update-button {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--affine-icon-color);
|
||||
}
|
||||
.update-button:hover {
|
||||
background-color: var(--affine-hover-color);
|
||||
}
|
||||
`;
|
||||
|
||||
get activeAdapter() {
|
||||
return this._context.activeAdapter$.value;
|
||||
}
|
||||
|
||||
private _adapterMenuAbortController: AbortController | null = null;
|
||||
private readonly _toggleAdapterMenu = () => {
|
||||
if (this._adapterMenuAbortController) {
|
||||
this._adapterMenuAbortController.abort();
|
||||
}
|
||||
this._adapterMenuAbortController = new AbortController();
|
||||
|
||||
createLitPortal({
|
||||
template: html`<affine-adapter-menu
|
||||
.abortController=${this._adapterMenuAbortController}
|
||||
></affine-adapter-menu>`,
|
||||
portalStyles: {
|
||||
zIndex: 'var(--affine-z-index-popover)',
|
||||
},
|
||||
container: this._adapterPanelHeader,
|
||||
computePosition: {
|
||||
referenceElement: this._adapterSelector,
|
||||
placement: 'bottom-start',
|
||||
middleware: [flip(), offset(4)],
|
||||
autoUpdate: { animationFrame: true },
|
||||
},
|
||||
abortController: this._adapterMenuAbortController,
|
||||
closeOnClickAway: true,
|
||||
});
|
||||
};
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<div class="adapter-panel-header">
|
||||
<div class="adapter-selector" @click="${this._toggleAdapterMenu}">
|
||||
<span class="adapter-selector-label">
|
||||
${this.activeAdapter.label}
|
||||
</span>
|
||||
${ArrowDownSmallIcon({ width: '16px', height: '16px' })}
|
||||
</div>
|
||||
<div class="update-button" @click="${this.updateActiveContent}">
|
||||
${FlipDirectionIcon({ width: '16px', height: '16px' })}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@query('.adapter-panel-header')
|
||||
private accessor _adapterPanelHeader!: HTMLDivElement;
|
||||
|
||||
@query('.adapter-selector')
|
||||
private accessor _adapterSelector!: HTMLDivElement;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor updateActiveContent: () => void = () => {};
|
||||
|
||||
@consume({ context: adapterPanelContext })
|
||||
private accessor _context!: AdapterPanelContext;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_ADAPTER_PANEL_HEADER]: AdapterPanelHeader;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './adapter-panel.js';
|
||||
export * from './body/adapter-panel-body.js';
|
||||
export * from './header/adapter-menu.js';
|
||||
export * from './header/adapter-panel-header.js';
|
||||
@@ -1,12 +0,0 @@
|
||||
import { ViewExtensionProvider } from '@blocksuite/affine-ext-loader';
|
||||
|
||||
import { effects } from './effects';
|
||||
|
||||
export class AdapterPanelViewExtension extends ViewExtensionProvider {
|
||||
override name = 'affine-adapter-panel-fragment';
|
||||
|
||||
override effect() {
|
||||
super.effect();
|
||||
effects();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"include": ["./src"],
|
||||
"references": [
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../ext-loader" },
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../../framework/global" },
|
||||
{ "path": "../../../framework/std" },
|
||||
{ "path": "../../../framework/store" }
|
||||
]
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
import { adjustColorAlpha } from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
BRUSH_LINE_WIDTHS,
|
||||
DefaultTheme,
|
||||
HIGHLIGHTER_LINE_WIDTHS,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { DefaultTheme } from '@blocksuite/affine-model';
|
||||
import {
|
||||
FeatureFlagService,
|
||||
ThemeProvider,
|
||||
@@ -101,11 +97,6 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
this.onChange({ color });
|
||||
};
|
||||
|
||||
private readonly _onPickLineWidth = (e: CustomEvent<number>) => {
|
||||
e.stopPropagation();
|
||||
this.onChange({ lineWidth: e.detail });
|
||||
};
|
||||
|
||||
override type = [BrushTool, HighlighterTool];
|
||||
|
||||
override render() {
|
||||
@@ -118,13 +109,10 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
value: { brush: brushIcon, highlighter: highlighterIcon },
|
||||
},
|
||||
penInfo$: {
|
||||
value: { type, color, lineWidth },
|
||||
value: { type, color },
|
||||
},
|
||||
} = this;
|
||||
|
||||
const lineWidths =
|
||||
type === 'brush' ? BRUSH_LINE_WIDTHS : HIGHLIGHTER_LINE_WIDTHS;
|
||||
|
||||
return html`
|
||||
<edgeless-slide-menu>
|
||||
<div class="pens" slot="prefix">
|
||||
@@ -168,13 +156,6 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
<menu-divider .vertical=${true}></menu-divider>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<edgeless-line-width-panel
|
||||
.selectedSize=${lineWidth}
|
||||
.lineWidths=${lineWidths}
|
||||
@select=${this._onPickLineWidth}
|
||||
>
|
||||
</edgeless-line-width-panel>
|
||||
<menu-divider .vertical=${true}></menu-divider>
|
||||
<edgeless-color-panel
|
||||
class="one-way"
|
||||
@select=${this._onPickColor}
|
||||
@@ -208,7 +189,6 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
type: Pen;
|
||||
color: string;
|
||||
icon: TemplateResult<1>;
|
||||
lineWidth: number;
|
||||
tip: string;
|
||||
shortcut: string;
|
||||
}>;
|
||||
|
||||
@@ -73,20 +73,6 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
return this.colors$.value[pen];
|
||||
});
|
||||
|
||||
private readonly lineWidths$ = computed(() => {
|
||||
const brush = this.settings.lastProps$.value.brush.lineWidth;
|
||||
const highlighter = this.settings.lastProps$.value.highlighter.lineWidth;
|
||||
return {
|
||||
brush,
|
||||
highlighter,
|
||||
};
|
||||
});
|
||||
|
||||
private readonly lineWidth$ = computed(() => {
|
||||
const pen = this.pen$.value;
|
||||
return this.lineWidths$.value[pen];
|
||||
});
|
||||
|
||||
private readonly penIconMap$ = computed(() => {
|
||||
const theme = this.themeProvider.app$.value;
|
||||
return penIconMap[theme];
|
||||
@@ -99,12 +85,13 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
|
||||
private readonly penInfo$ = computed(() => {
|
||||
const type = this.pen$.value;
|
||||
const icon = this.penIcon$.value;
|
||||
const color = this.color$.value;
|
||||
return {
|
||||
...penInfoMap[type],
|
||||
type: this.pen$.value,
|
||||
icon: this.penIcon$.value,
|
||||
color: this.color$.value,
|
||||
lineWidth: this.lineWidth$.value,
|
||||
color,
|
||||
icon,
|
||||
type,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -223,15 +223,13 @@ export class ConnectorTool extends BaseTool<ConnectorToolOptions> {
|
||||
}
|
||||
|
||||
getNextMode() {
|
||||
// reorder the enum values
|
||||
const modes = [
|
||||
ConnectorMode.Curve,
|
||||
ConnectorMode.Orthogonal,
|
||||
ConnectorMode.Straight,
|
||||
];
|
||||
|
||||
const currentIndex = modes.indexOf(this.activatedOption.mode);
|
||||
const nextIndex = (currentIndex + 1) % modes.length;
|
||||
return modes[nextIndex];
|
||||
switch (this.activatedOption.mode) {
|
||||
case ConnectorMode.Curve:
|
||||
return ConnectorMode.Orthogonal;
|
||||
case ConnectorMode.Orthogonal:
|
||||
return ConnectorMode.Straight;
|
||||
case ConnectorMode.Straight:
|
||||
return ConnectorMode.Curve;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { GroupElementModel } from '@blocksuite/affine-model';
|
||||
import { InteractivityExtension } from '@blocksuite/std/gfx';
|
||||
|
||||
export class GroupInteractionExtension extends InteractivityExtension {
|
||||
static override key = 'group-selection';
|
||||
|
||||
override mounted(): void {
|
||||
this.action.onElementSelect(context => {
|
||||
const { candidates, suggest } = context;
|
||||
const { activeGroup } = this.gfx.selection;
|
||||
let target = context.target;
|
||||
|
||||
if (activeGroup && activeGroup.hasDescendant(target)) {
|
||||
const groups = target.groups;
|
||||
const activeGroupIdx = groups.indexOf(activeGroup);
|
||||
|
||||
if (activeGroupIdx !== -1) {
|
||||
target =
|
||||
groups
|
||||
.slice(0, activeGroupIdx)
|
||||
.findLast(
|
||||
group =>
|
||||
group instanceof GroupElementModel &&
|
||||
candidates.includes(group)
|
||||
) ?? target;
|
||||
}
|
||||
} else {
|
||||
const groups = target.groups;
|
||||
|
||||
target =
|
||||
groups.findLast(group => {
|
||||
return (
|
||||
group instanceof GroupElementModel && candidates.includes(group)
|
||||
);
|
||||
}) ?? target;
|
||||
}
|
||||
|
||||
if (target !== context.target) {
|
||||
suggest({
|
||||
id: target.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
import { effects } from './effects';
|
||||
import { GroupElementRendererExtension } from './element-renderer';
|
||||
import { GroupElementView, GroupInteraction } from './element-view';
|
||||
import { GroupInteractionExtension } from './interaction-ext';
|
||||
import { groupToolbarExtension } from './toolbar/config';
|
||||
|
||||
export class GroupViewExtension extends ViewExtensionProvider {
|
||||
@@ -24,7 +23,6 @@ export class GroupViewExtension extends ViewExtensionProvider {
|
||||
if (this.isEdgeless(context.scope)) {
|
||||
context.register(groupToolbarExtension);
|
||||
context.register(GroupInteraction);
|
||||
context.register(GroupInteractionExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LoadingIcon } from '@blocksuite/affine-components/icons';
|
||||
import { LightLoadingIcon } from '@blocksuite/affine-components/icons';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { css, html } from 'lit';
|
||||
@@ -46,7 +46,7 @@ export class MindMapPlaceholder extends ShadowlessElement {
|
||||
return html`<div class="placeholder-container">
|
||||
<div class="preview-icon">${importMindMapIcon}</div>
|
||||
<div class="description">
|
||||
${LoadingIcon()}
|
||||
${LightLoadingIcon}
|
||||
<span>Importing mind map...</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type BoxSelectionContext,
|
||||
GfxElementModelView,
|
||||
GfxViewInteractionExtension,
|
||||
type SelectedContext,
|
||||
} from '@blocksuite/std/gfx';
|
||||
|
||||
import { handleLayout } from './utils.js';
|
||||
@@ -334,6 +335,33 @@ export class MindMapView extends GfxElementModelView<MindmapElementModel> {
|
||||
return collapseButton;
|
||||
}
|
||||
|
||||
override onSelected(context: SelectedContext): void | boolean {
|
||||
const { position } = context;
|
||||
const target = this.model.childElements.find(child => {
|
||||
if (child.elementBound.containsPoint([position.x, position.y])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (target) {
|
||||
if (this.model.isLocked()) {
|
||||
return super.onSelected(context);
|
||||
}
|
||||
|
||||
if (context.multiSelect) {
|
||||
this.gfx.selection.toggle(target);
|
||||
} else {
|
||||
this.gfx.selection.set({ elements: [target.id] });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
override onBoxSelected(context: BoxSelectionContext) {
|
||||
const { box } = context;
|
||||
const bound = new Bound(box.x, box.y, box.w, box.h);
|
||||
@@ -355,24 +383,11 @@ export class MindMapView extends GfxElementModelView<MindmapElementModel> {
|
||||
}
|
||||
}
|
||||
|
||||
export const MindMapInteraction = GfxViewInteractionExtension<MindMapView>(
|
||||
export const MindMapInteraction = GfxViewInteractionExtension(
|
||||
MindMapView.type,
|
||||
{
|
||||
resizeConstraint: {
|
||||
allowedHandlers: [],
|
||||
},
|
||||
handleSelection: () => {
|
||||
return {
|
||||
onSelect(context) {
|
||||
const { model } = context;
|
||||
|
||||
if (model.isLocked()) {
|
||||
return context.default(context);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import { on } from '@blocksuite/affine-shared/utils';
|
||||
import type { PointerEventState } from '@blocksuite/std';
|
||||
import { BaseTool, MouseButton, type ToolOptions } from '@blocksuite/std/gfx';
|
||||
@@ -85,14 +84,6 @@ export class PanTool extends BaseTool<PanToolOption> {
|
||||
this.gfx.selection.set(selectionToRestore);
|
||||
};
|
||||
|
||||
// If in presentation mode, disable black background after middle mouse drag
|
||||
if (currentTool.toolType?.toolName === 'frameNavigator') {
|
||||
const slots = this.std.get(EdgelessLegacySlotIdentifier);
|
||||
slots.navigatorSettingUpdated.next({
|
||||
blackBackground: false,
|
||||
});
|
||||
}
|
||||
|
||||
this.controller.setTool(PanTool, {
|
||||
panning: true,
|
||||
});
|
||||
|
||||
@@ -235,18 +235,21 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
|
||||
const locked = this.gfx.viewport.locked;
|
||||
const selection = this.gfx.selection;
|
||||
if (locked || selection.editing) return;
|
||||
if (
|
||||
this.gfx.tool.dragging$.peek() &&
|
||||
this.gfx.tool.currentToolName$.peek() === 'shape'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTool = this.gfx.tool.currentToolName$.peek();
|
||||
const activeIndex = shapes.findIndex(
|
||||
s => s.name === this.draggingShape
|
||||
);
|
||||
const nextIndex = (activeIndex + 1) % shapes.length;
|
||||
const next = shapes[nextIndex];
|
||||
this.draggingShape = next.name;
|
||||
|
||||
if (this.readyToDrop) {
|
||||
if (currentTool === ShapeTool.toolName) {
|
||||
const activeIndex = shapes.findIndex(
|
||||
s => s.name === this.draggingShape
|
||||
);
|
||||
const nextIndex = (activeIndex + 1) % shapes.length;
|
||||
const next = shapes[nextIndex];
|
||||
this.draggingShape = next.name;
|
||||
}
|
||||
this.draggableController.cancelWithoutAnimation();
|
||||
const el = this.shapeContainer.querySelector(
|
||||
`.shape.${this.draggingShape}`
|
||||
@@ -261,14 +264,8 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
|
||||
const clientPos = { x: x + left, y: y + top };
|
||||
this.draggableController.dragAndMoveTo(el, clientPos);
|
||||
} else {
|
||||
if (this.gfx.tool.dragging$.peek()) return;
|
||||
let shapeName =
|
||||
this.gfx.tool.get(ShapeTool).activatedOption.shapeName;
|
||||
if (currentTool === ShapeTool.toolName) {
|
||||
shapeName = this.gfx.tool.get(ShapeTool).cycleShapeName('next');
|
||||
}
|
||||
this.setEdgelessTool(ShapeTool, {
|
||||
shapeName,
|
||||
shapeName: this.draggingShape,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,11 +5,7 @@ import {
|
||||
type SurfaceBlockComponent,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { ShapeElementModel, ShapeName } from '@blocksuite/affine-model';
|
||||
import {
|
||||
DefaultTheme,
|
||||
getShapeType,
|
||||
ShapeType,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { DefaultTheme, getShapeType } from '@blocksuite/affine-model';
|
||||
import {
|
||||
EditPropsStore,
|
||||
TelemetryProvider,
|
||||
@@ -19,7 +15,7 @@ import { hasClassNameInList } from '@blocksuite/affine-shared/utils';
|
||||
import type { IBound } from '@blocksuite/global/gfx';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import type { PointerEventState } from '@blocksuite/std';
|
||||
import { BaseTool, type GfxController } from '@blocksuite/std/gfx';
|
||||
import { BaseTool } from '@blocksuite/std/gfx';
|
||||
import { effect } from '@preact/signals-core';
|
||||
|
||||
import {
|
||||
@@ -163,13 +159,6 @@ export class ShapeTool extends BaseTool<ShapeToolOption> {
|
||||
this._surfaceComponent?.refresh();
|
||||
}
|
||||
|
||||
constructor(gfx: GfxController) {
|
||||
super(gfx);
|
||||
this.activatedOption = {
|
||||
shapeName: ShapeType.Rect,
|
||||
};
|
||||
}
|
||||
|
||||
override activate() {
|
||||
this.createOverlay();
|
||||
}
|
||||
@@ -348,20 +337,4 @@ export class ShapeTool extends BaseTool<ShapeToolOption> {
|
||||
setDisableOverlay(disable: boolean) {
|
||||
this._disableOverlay = disable;
|
||||
}
|
||||
|
||||
cycleShapeName(dir: 'prev' | 'next' = 'next'): ShapeName {
|
||||
const shapeNames: ShapeName[] = [
|
||||
ShapeType.Rect,
|
||||
ShapeType.Ellipse,
|
||||
ShapeType.Diamond,
|
||||
ShapeType.Triangle,
|
||||
'roundedRect',
|
||||
];
|
||||
|
||||
const currentIndex = shapeNames.indexOf(this.activatedOption.shapeName);
|
||||
const nextIndex =
|
||||
(currentIndex + (dir === 'prev' ? -1 : 1) + shapeNames.length) %
|
||||
shapeNames.length;
|
||||
return shapeNames[nextIndex];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
getAttachmentFileIcon,
|
||||
LoadingIcon,
|
||||
getLoadingIconWith,
|
||||
WebIcon16,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import type { FootNote } from '@blocksuite/affine-model';
|
||||
@@ -8,6 +8,7 @@ import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
DocDisplayMetaProvider,
|
||||
LinkPreviewServiceIdentifier,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
@@ -76,7 +77,7 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
return getAttachmentFileIcon(fileType);
|
||||
} else if (referenceType === 'url') {
|
||||
if (this._isLoading$.value) {
|
||||
return LoadingIcon();
|
||||
return this._LoadingIcon();
|
||||
}
|
||||
|
||||
const favicon = this._linkPreview$.value?.favicon;
|
||||
@@ -125,6 +126,11 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
return this._popupLabel$.value;
|
||||
});
|
||||
|
||||
private readonly _LoadingIcon = () => {
|
||||
const theme = this.std.get(ThemeProvider).theme;
|
||||
return getLoadingIconWith(theme);
|
||||
};
|
||||
|
||||
private readonly _onClick = () => {
|
||||
this.onPopupClick(this.footnote, this.abortController);
|
||||
this.abortController.abort();
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
DocModeProvider,
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { Command, TextSelection } from '@blocksuite/std';
|
||||
|
||||
export const insertInlineLatex: Command<{
|
||||
@@ -41,21 +37,6 @@ export const insertInlineLatex: Command<{
|
||||
length: 1,
|
||||
});
|
||||
|
||||
const mode = ctx.std.get(DocModeProvider).getEditorMode() ?? 'page';
|
||||
const ifEdgelessText = blockComponent.closest('affine-edgeless-text');
|
||||
ctx.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: 'inline equation',
|
||||
control: 'create inline equation',
|
||||
});
|
||||
|
||||
inlineEditor
|
||||
.waitForUpdate()
|
||||
.then(async () => {
|
||||
|
||||
@@ -57,9 +57,6 @@ export class LatexEditorMenu extends SignalWatcher(
|
||||
|
||||
font-family: ${unsafeCSSVar('fontCodeFamily')};
|
||||
border: 1px solid transparent;
|
||||
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.latex-editor:focus-within {
|
||||
border: 1px solid ${unsafeCSSVar('blue700')};
|
||||
@@ -100,10 +97,6 @@ export class LatexEditorMenu extends SignalWatcher(
|
||||
return this.querySelector<RichText>('rich-text');
|
||||
}
|
||||
|
||||
private readonly _getVerticalScrollContainer = () => {
|
||||
return this.querySelector('.latex-editor');
|
||||
};
|
||||
|
||||
private _updateHighlightTokens(text: string) {
|
||||
const editorTheme = this.std.get(ThemeProvider).theme;
|
||||
const theme = editorTheme === ColorScheme.Dark ? 'dark-plus' : 'light-plus';
|
||||
@@ -178,14 +171,11 @@ export class LatexEditorMenu extends SignalWatcher(
|
||||
override render() {
|
||||
return html`<div class="latex-editor-container">
|
||||
<div class="latex-editor">
|
||||
<div class="latex-editor-content">
|
||||
<rich-text
|
||||
.yText=${this.yText}
|
||||
.attributesSchema=${this.inlineManager.getSchema()}
|
||||
.attributeRenderer=${this.inlineManager.getRenderer()}
|
||||
.verticalScrollContainerGetter=${this._getVerticalScrollContainer}
|
||||
></rich-text>
|
||||
</div>
|
||||
<rich-text
|
||||
.yText=${this.yText}
|
||||
.attributesSchema=${this.inlineManager.getSchema()}
|
||||
.attributeRenderer=${this.inlineManager.getRenderer()}
|
||||
></rich-text>
|
||||
</div>
|
||||
<div class="latex-editor-confirm">
|
||||
<span @click=${() => this.abortController.abort()}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
DocModeProvider,
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type { BlockComponent } from '@blocksuite/std';
|
||||
import { InlineMarkdownExtension } from '@blocksuite/std/inline';
|
||||
@@ -18,20 +14,6 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
const inlinePrefix = match.groups['inlinePrefix'];
|
||||
const blockPrefix = match.groups['blockPrefix'];
|
||||
|
||||
if (!inlineEditor.rootElement) return;
|
||||
const blockComponent =
|
||||
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
|
||||
if (!blockComponent) return;
|
||||
|
||||
const doc = blockComponent.store;
|
||||
const std = blockComponent.std;
|
||||
const parentComponent = blockComponent.parentComponent;
|
||||
if (!parentComponent) return;
|
||||
const index = parentComponent.model.children.indexOf(blockComponent.model);
|
||||
if (index === -1) return;
|
||||
const mode = std.get(DocModeProvider).getEditorMode() ?? 'page';
|
||||
const ifEdgelessText = blockComponent.closest('affine-edgeless-text');
|
||||
|
||||
if (blockPrefix === '$$$$') {
|
||||
inlineEditor.insertText(
|
||||
{
|
||||
@@ -47,6 +29,20 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
|
||||
undoManager.stopCapturing();
|
||||
|
||||
if (!inlineEditor.rootElement) return;
|
||||
const blockComponent =
|
||||
inlineEditor.rootElement.closest<BlockComponent>('[data-block-id]');
|
||||
if (!blockComponent) return;
|
||||
|
||||
const doc = blockComponent.store;
|
||||
const parentComponent = blockComponent.parentComponent;
|
||||
if (!parentComponent) return;
|
||||
|
||||
const index = parentComponent.model.children.indexOf(
|
||||
blockComponent.model
|
||||
);
|
||||
if (index === -1) return;
|
||||
|
||||
inlineEditor.deleteText({
|
||||
index: inlineRange.index - 4,
|
||||
length: 5,
|
||||
@@ -71,19 +67,6 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -141,19 +124,6 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
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: 'inline equation',
|
||||
control: 'create inline equation',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,18 +169,5 @@ export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
index: startIndex + 1,
|
||||
length: 0,
|
||||
});
|
||||
|
||||
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: 'inline equation',
|
||||
control: 'create inline equation',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,9 +11,9 @@ export enum ShapeTextFontSize {
|
||||
}
|
||||
|
||||
export enum ShapeType {
|
||||
Rect = 'rect',
|
||||
Ellipse = 'ellipse',
|
||||
Diamond = 'diamond',
|
||||
Ellipse = 'ellipse',
|
||||
Rect = 'rect',
|
||||
Triangle = 'triangle',
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.14",
|
||||
"@types/bytes": "^3.1.5",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
@@ -72,6 +71,7 @@
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/bytes": "^3.1.5",
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"version": "0.21.0"
|
||||
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
ElementCreationEvent,
|
||||
ElementLockEvent,
|
||||
ElementUpdatedEvent,
|
||||
LatexEvent,
|
||||
LinkedDocCreatedEvent,
|
||||
LinkEvent,
|
||||
MindMapCollapseEvent,
|
||||
@@ -43,7 +42,6 @@ export type TelemetryEventMap = OutDatabaseAllEvents &
|
||||
BlockCreated: BlockCreationEvent;
|
||||
EdgelessToolPicked: EdgelessToolPickedEvent;
|
||||
CreateEmbedBlock: LinkEvent;
|
||||
Latex: LatexEvent;
|
||||
};
|
||||
|
||||
export interface TelemetryService {
|
||||
|
||||
@@ -117,11 +117,3 @@ export interface ElementUpdatedEvent extends TelemetryEvent {
|
||||
export interface LinkEvent extends TelemetryEvent {
|
||||
result?: 'success' | 'failure';
|
||||
}
|
||||
|
||||
export interface LatexEvent extends TelemetryEvent {
|
||||
from: 'doc' | 'edgeless text' | 'edgeless note';
|
||||
page: 'doc' | 'edgeless';
|
||||
segment: 'doc' | 'whiteboard';
|
||||
module: 'equation' | 'inline equation';
|
||||
control: 'create equation' | 'create inline equation';
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { type CalloutBlockComponent } from '@blocksuite/affine-block-callout';
|
||||
import {
|
||||
AFFINE_EDGELESS_NOTE,
|
||||
EdgelessNoteBackground,
|
||||
EdgelessNoteBlockComponent,
|
||||
type EdgelessNoteBlockComponent,
|
||||
} from '@blocksuite/affine-block-note';
|
||||
import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph';
|
||||
import {
|
||||
@@ -283,21 +282,14 @@ export function getDuplicateBlocks(blocks: BlockModel[]) {
|
||||
* Get hovering note with given a point in edgeless mode.
|
||||
*/
|
||||
function getHoveringNote(point: Point) {
|
||||
const elements = document.elementsFromPoint(point.x, point.y);
|
||||
for (const el of elements) {
|
||||
if (el instanceof EdgelessNoteBlockComponent) {
|
||||
return el;
|
||||
}
|
||||
|
||||
// When in edit mode for edgeless-note, the rect of note-background is larger than
|
||||
// that of edgeless-note. Therefore, when the point is located in the area between
|
||||
// note-background and edgeless-note, using elementsFromPoint alone cannot correctly
|
||||
// retrieve the edgeless-note.
|
||||
if (el instanceof EdgelessNoteBackground) {
|
||||
return el.closest(AFFINE_EDGELESS_NOTE) ?? null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
document
|
||||
.elementsFromPoint(point.x, point.y)
|
||||
.find(
|
||||
(e): e is EdgelessNoteBlockComponent =>
|
||||
e.tagName.toLowerCase() === AFFINE_EDGELESS_NOTE
|
||||
) || null
|
||||
);
|
||||
}
|
||||
|
||||
export function getSnapshotRect(snapshot: SliceSnapshot): Bound | null {
|
||||
|
||||
@@ -1569,11 +1569,6 @@ export class DragEventWatcher {
|
||||
view.hideMask = false;
|
||||
}
|
||||
},
|
||||
onDrop: () => {
|
||||
if (isNote && 'hideMask' in view) {
|
||||
view.hideMask = false;
|
||||
}
|
||||
},
|
||||
setDropData: () => {
|
||||
return {
|
||||
modelId: view.model.id,
|
||||
|
||||
@@ -12,6 +12,10 @@ export class PageWatcher {
|
||||
watch() {
|
||||
const { disposables } = this.widget;
|
||||
|
||||
disposables.add(
|
||||
this.widget.store.slots.blockUpdated.subscribe(() => this.widget.hide())
|
||||
);
|
||||
|
||||
disposables.add(
|
||||
this.pageViewportService.subscribe(() => {
|
||||
this.widget.hide();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { IconButton } from '@blocksuite/affine-components/icon-button';
|
||||
import { LoadingIcon } from '@blocksuite/affine-components/icons';
|
||||
import { getLoadingIconWith } from '@blocksuite/affine-components/icons';
|
||||
import {
|
||||
cleanSpecifiedTail,
|
||||
getTextContentFromInlineRange,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
||||
import {
|
||||
createKeydownObserver,
|
||||
@@ -127,6 +128,10 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
);
|
||||
}
|
||||
|
||||
private get _loadingIcon() {
|
||||
return getLoadingIconWith(this.context.std.get(ThemeProvider).theme$.value);
|
||||
}
|
||||
|
||||
private _getActionItems(group: LinkedMenuGroup) {
|
||||
const isExpanded = !!this._expanded.get(group.name);
|
||||
let items = resolveSignal(group.items);
|
||||
@@ -286,7 +291,7 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
<div class="group-title">
|
||||
<div class="group-title-text">${group.name}</div>
|
||||
${group.isLoading
|
||||
? html`<span class="loading-icon">${LoadingIcon()}</span>`
|
||||
? html`<span class="loading-icon">${this._loadingIcon}</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="group" style=${group.styles ?? ''}>
|
||||
@@ -381,7 +386,7 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
}
|
||||
|
||||
const ele = shadowRoot.querySelector(
|
||||
`icon-button[data-id=${CSS.escape(this._activatedItemKey)}]`
|
||||
`icon-button[data-id="${this._activatedItemKey}"]`
|
||||
);
|
||||
|
||||
// If the element doesn't exist, don't log a warning
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
const agent = globalThis.navigator?.userAgent ?? '';
|
||||
const platform = globalThis.navigator?.platform || globalThis.process?.platform;
|
||||
const platform = globalThis.navigator?.platform;
|
||||
|
||||
export const IS_WEB =
|
||||
typeof window !== 'undefined' && typeof document !== 'undefined';
|
||||
@@ -17,13 +17,13 @@ export const IS_IOS =
|
||||
IS_SAFARI &&
|
||||
(/Mobile\/\w+/.test(agent) || globalThis.navigator?.maxTouchPoints > 2);
|
||||
|
||||
export const IS_MAC = /Mac/i.test(platform) || /darwin/.test(platform);
|
||||
export const IS_MAC = /Mac/i.test(platform);
|
||||
|
||||
export const IS_IPAD =
|
||||
/iPad/i.test(platform) ||
|
||||
/iPad/i.test(agent) ||
|
||||
(/Macintosh/i.test(agent) && globalThis.navigator?.maxTouchPoints > 2);
|
||||
|
||||
export const IS_WINDOWS = /Win/.test(platform) || /win32/.test(platform);
|
||||
export const IS_WINDOWS = /Win/.test(platform);
|
||||
|
||||
export const IS_MOBILE = IS_IOS || IS_IPAD || IS_ANDROID;
|
||||
|
||||
@@ -26,7 +26,10 @@ import { LayerManager } from './layer.js';
|
||||
import type { PointTestOptions } from './model/base.js';
|
||||
import { GfxBlockElementModel } from './model/gfx-block-model.js';
|
||||
import type { GfxModel } from './model/model.js';
|
||||
import { GfxPrimitiveElementModel } from './model/surface/element-model.js';
|
||||
import {
|
||||
GfxGroupLikeElementModel,
|
||||
GfxPrimitiveElementModel,
|
||||
} from './model/surface/element-model.js';
|
||||
import type { SurfaceBlockModel } from './model/surface/surface-model.js';
|
||||
import { FIT_TO_SCREEN_PADDING, Viewport, ZOOM_INITIAL } from './viewport.js';
|
||||
|
||||
@@ -178,6 +181,54 @@ export class GfxController extends LifeCycleWatcher {
|
||||
return last(picked) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the top element in the given point.
|
||||
* If the element is in a group, the group will be returned.
|
||||
* If the group is currently selected, the child element will be returned.
|
||||
* @param x
|
||||
* @param y
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
getElementInGroup(
|
||||
x: number,
|
||||
y: number,
|
||||
options?: PointTestOptions
|
||||
): GfxModel | null {
|
||||
const selectionManager = this.selection;
|
||||
const results = this.getElementByPoint(x, y, {
|
||||
...options,
|
||||
all: true,
|
||||
});
|
||||
let picked = last(results) ?? null;
|
||||
const { activeGroup } = selectionManager;
|
||||
const first = picked;
|
||||
|
||||
if (activeGroup && picked && activeGroup.hasDescendant(picked)) {
|
||||
let index = results.length - 1;
|
||||
|
||||
while (
|
||||
picked === activeGroup ||
|
||||
(picked instanceof GfxGroupLikeElementModel &&
|
||||
picked.hasDescendant(activeGroup))
|
||||
) {
|
||||
picked = results[--index];
|
||||
}
|
||||
} else if (picked) {
|
||||
let index = results.length - 1;
|
||||
|
||||
while (picked.group instanceof GfxGroupLikeElementModel) {
|
||||
if (--index < 0) {
|
||||
picked = null;
|
||||
break;
|
||||
}
|
||||
picked = results[index];
|
||||
}
|
||||
}
|
||||
|
||||
return (picked ?? first) as GfxModel | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all elements in an area.
|
||||
* @param bound
|
||||
|
||||
@@ -36,7 +36,7 @@ export type {
|
||||
RotateEndContext,
|
||||
RotateMoveContext,
|
||||
RotateStartContext,
|
||||
SelectContext,
|
||||
SelectedContext,
|
||||
} from './interactivity/index.js';
|
||||
export {
|
||||
GfxViewEventManager,
|
||||
|
||||
@@ -13,7 +13,6 @@ import type {
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
} from '../types/drag.js';
|
||||
import type { ExtensionElementSelectContext } from '../types/select.js';
|
||||
|
||||
export const InteractivityExtensionIdentifier =
|
||||
createIdentifier<InteractivityExtension>('interactivity-extension');
|
||||
@@ -119,10 +118,6 @@ type ActionContextMap = {
|
||||
| undefined
|
||||
>;
|
||||
};
|
||||
elementSelect: {
|
||||
context: ExtensionElementSelectContext;
|
||||
returnType: void;
|
||||
};
|
||||
};
|
||||
|
||||
export class InteractivityActionAPI {
|
||||
@@ -156,18 +151,6 @@ export class InteractivityActionAPI {
|
||||
};
|
||||
}
|
||||
|
||||
onElementSelect(
|
||||
handler: (
|
||||
ctx: ActionContextMap['elementSelect']['context']
|
||||
) => ActionContextMap['elementSelect']['returnType']
|
||||
) {
|
||||
this._handlers['elementSelect'] = handler;
|
||||
|
||||
return () => {
|
||||
return delete this._handlers['elementSelect'];
|
||||
};
|
||||
}
|
||||
|
||||
emit<K extends keyof ActionContextMap>(
|
||||
event: K,
|
||||
context: ActionContextMap[K]['context']
|
||||
|
||||
@@ -15,21 +15,18 @@ import type {
|
||||
RotateEndContext,
|
||||
RotateMoveContext,
|
||||
RotateStartContext,
|
||||
SelectableContext,
|
||||
SelectContext,
|
||||
} from '../types/view';
|
||||
|
||||
type ExtendedViewContext<
|
||||
T extends GfxBlockComponent | GfxElementModelView,
|
||||
Context,
|
||||
DefaultReturnType = void,
|
||||
> = {
|
||||
/**
|
||||
* The default function of the interaction.
|
||||
* If the interaction is handled by the extension, the default function will not be executed.
|
||||
* But extension can choose to call the default function by `context.default(context)` if needed.
|
||||
*/
|
||||
default: (context: Context) => DefaultReturnType;
|
||||
default: (context: Context) => void;
|
||||
|
||||
model: T['model'];
|
||||
|
||||
@@ -102,19 +99,6 @@ export type GfxViewInteractionConfig<
|
||||
context: RotateEndContext & ExtendedViewContext<T, RotateEndContext>
|
||||
): void;
|
||||
};
|
||||
|
||||
handleSelection?: (
|
||||
context: Omit<ViewInteractionHandleContext<T>, 'add' | 'delete'>
|
||||
) => {
|
||||
selectable?: (
|
||||
context: SelectableContext &
|
||||
ExtendedViewContext<T, SelectableContext, boolean>
|
||||
) => boolean;
|
||||
onSelect?: (
|
||||
context: SelectContext &
|
||||
ExtendedViewContext<T, SelectContext, boolean | void>
|
||||
) => boolean | void;
|
||||
};
|
||||
};
|
||||
|
||||
export const GfxViewInteractionIdentifier =
|
||||
|
||||
@@ -29,5 +29,5 @@ export type {
|
||||
RotateEndContext,
|
||||
RotateMoveContext,
|
||||
RotateStartContext,
|
||||
SelectContext,
|
||||
SelectedContext,
|
||||
} from './types/view.js';
|
||||
|
||||
@@ -2,7 +2,6 @@ import { type ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { Bound, clamp, Point } from '@blocksuite/global/gfx';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import last from 'lodash-es/last.js';
|
||||
|
||||
import type { PointerEventState } from '../../event/state/pointer.js';
|
||||
import { getTopElements } from '../../utils/tree.js';
|
||||
@@ -10,7 +9,6 @@ import type { GfxBlockComponent } from '../../view/index.js';
|
||||
import { GfxExtension, GfxExtensionIdentifier } from '../extension.js';
|
||||
import { GfxBlockElementModel } from '../model/gfx-block-model.js';
|
||||
import type { GfxModel } from '../model/model.js';
|
||||
import { GfxPrimitiveElementModel } from '../model/surface/element-model.js';
|
||||
import type { GfxElementModelView } from '../view/view.js';
|
||||
import { createInteractionContext, type SupportedEvents } from './event.js';
|
||||
import {
|
||||
@@ -42,7 +40,6 @@ import type {
|
||||
BoxSelectionContext,
|
||||
ResizeConstraint,
|
||||
RotateConstraint,
|
||||
SelectContext,
|
||||
} from './types/view.js';
|
||||
|
||||
type ExtensionPointerHandler = Exclude<
|
||||
@@ -123,112 +120,6 @@ export class InteractivityManager extends GfxExtension {
|
||||
};
|
||||
}
|
||||
|
||||
private _getSelectionConfig(models: GfxModel[]) {
|
||||
type SelectionHandlers = Required<
|
||||
ReturnType<Required<GfxViewInteractionConfig>['handleSelection']>
|
||||
>;
|
||||
|
||||
const selectionConfigMap = new Map<
|
||||
string,
|
||||
{
|
||||
view: GfxBlockComponent | GfxElementModelView;
|
||||
handlers: SelectionHandlers;
|
||||
defaultHandlers: SelectionHandlers;
|
||||
}
|
||||
>();
|
||||
|
||||
models.forEach(model => {
|
||||
const typeOrFlavour = 'flavour' in model ? model.flavour : model.type;
|
||||
const view = this.gfx.view.get(model);
|
||||
const config = this.std.getOptional(
|
||||
GfxViewInteractionIdentifier(typeOrFlavour)
|
||||
);
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectionConfig =
|
||||
config?.handleSelection?.({
|
||||
gfx: this.gfx,
|
||||
std: this.std,
|
||||
view,
|
||||
model,
|
||||
}) ?? {};
|
||||
const defaultHandlers = {
|
||||
selectable: () => {
|
||||
return !model.isLockedByAncestor();
|
||||
},
|
||||
onSelect: (context: SelectContext) => {
|
||||
if (context.multiSelect) {
|
||||
this.gfx.selection.toggle(model);
|
||||
} else {
|
||||
this.gfx.selection.set({ elements: [model.id] });
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
selectionConfigMap.set(model.id, {
|
||||
view,
|
||||
defaultHandlers,
|
||||
handlers: {
|
||||
...defaultHandlers,
|
||||
...selectionConfig,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return selectionConfigMap;
|
||||
}
|
||||
|
||||
private _getSuggestedTarget(context: {
|
||||
candidates: GfxModel[];
|
||||
target: GfxModel;
|
||||
}) {
|
||||
const { candidates, target } = context;
|
||||
|
||||
const suggestedElements: {
|
||||
id: string;
|
||||
priority?: number;
|
||||
}[] = [];
|
||||
const suggest = (element: { id: string; priority?: number }) => {
|
||||
suggestedElements.push(element);
|
||||
};
|
||||
|
||||
const extensions = this.interactExtensions;
|
||||
extensions
|
||||
.values()
|
||||
.toArray()
|
||||
.forEach(ext => {
|
||||
return (ext.action as InteractivityActionAPI).emit('elementSelect', {
|
||||
candidates,
|
||||
target,
|
||||
suggest,
|
||||
});
|
||||
});
|
||||
|
||||
if (suggestedElements.length) {
|
||||
suggestedElements.sort((a, b) => {
|
||||
return (a.priority ?? 0) - (b.priority ?? 0);
|
||||
});
|
||||
|
||||
const suggested = last(suggestedElements) as {
|
||||
id: string;
|
||||
priority?: number;
|
||||
};
|
||||
const elm = this.gfx.getElementById(suggested.id);
|
||||
|
||||
return elm instanceof GfxPrimitiveElementModel ||
|
||||
elm instanceof GfxBlockElementModel
|
||||
? elm
|
||||
: target;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle element selection.
|
||||
* @param evt The pointer event that triggered the selection.
|
||||
@@ -238,72 +129,32 @@ export class InteractivityManager extends GfxExtension {
|
||||
const { raw } = evt;
|
||||
const { gfx } = this;
|
||||
const [x, y] = gfx.viewport.toModelCoordFromClientCoord([raw.x, raw.y]);
|
||||
let candidates = this.gfx.getElementByPoint(x, y, {
|
||||
all: true,
|
||||
});
|
||||
const picked = this.gfx.getElementInGroup(x, y);
|
||||
|
||||
const selectionConfigs = this._getSelectionConfig(candidates);
|
||||
const context = {
|
||||
multiSelect: raw.shiftKey,
|
||||
event: raw,
|
||||
position: Point.from([x, y]),
|
||||
const tryGetLockedAncestor = (e: GfxModel) => {
|
||||
if (e?.isLockedByAncestor()) {
|
||||
return e.groups.findLast(group => group.isLocked()) ?? e;
|
||||
}
|
||||
return e;
|
||||
};
|
||||
|
||||
candidates = candidates.filter(model => {
|
||||
if (!selectionConfigs.has(model.id)) {
|
||||
return false;
|
||||
}
|
||||
const config = selectionConfigs.get(model.id)!;
|
||||
|
||||
return (
|
||||
selectionConfigs.has(model.id) &&
|
||||
selectionConfigs.get(model.id)?.handlers.selectable({
|
||||
...context,
|
||||
view: config.view,
|
||||
model,
|
||||
default: config.defaultHandlers.selectable as () => boolean,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
{
|
||||
let target = last(candidates);
|
||||
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
target = this._getSuggestedTarget({
|
||||
candidates,
|
||||
target,
|
||||
});
|
||||
|
||||
const config = selectionConfigs.has(target.id)
|
||||
? selectionConfigs.get(target.id)
|
||||
: this._getSelectionConfig([target]).get(target.id);
|
||||
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (picked) {
|
||||
const lockedElement = tryGetLockedAncestor(picked);
|
||||
const multiSelect = raw.shiftKey;
|
||||
const view = gfx.view.get(lockedElement);
|
||||
const context = {
|
||||
selected: multiSelect ? !gfx.selection.has(target.id) : true,
|
||||
selected: multiSelect ? !gfx.selection.has(picked.id) : true,
|
||||
multiSelect,
|
||||
event: raw,
|
||||
position: Point.from([x, y]),
|
||||
fallback: lockedElement !== picked,
|
||||
};
|
||||
|
||||
const result = config.handlers.onSelect({
|
||||
...context,
|
||||
selected: multiSelect ? !gfx.selection.has(target.id) : true,
|
||||
view: config.view,
|
||||
model: target,
|
||||
default: config.defaultHandlers.onSelect as () => void,
|
||||
});
|
||||
|
||||
return result ?? true;
|
||||
const selected = view?.onSelected(context);
|
||||
return selected ?? true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
handleBoxSelection(context: { box: BoxSelectionContext['box'] }) {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { GfxModel } from '../../model/model';
|
||||
|
||||
export type ExtensionElementSelectContext = {
|
||||
/**
|
||||
* The candidate elements for selection.
|
||||
*/
|
||||
candidates: GfxModel[];
|
||||
|
||||
/**
|
||||
* The element which is ready to be selected.
|
||||
*/
|
||||
target: GfxModel;
|
||||
|
||||
/**
|
||||
* Use to change the target element of selection.
|
||||
* @param element
|
||||
* @returns
|
||||
*/
|
||||
suggest: (element: {
|
||||
/**
|
||||
* The suggested element id
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The priority of the suggestion. If there are multiple suggestions coming from different extensions,
|
||||
* the one with the highest priority will be used.
|
||||
*
|
||||
* Default to 0.
|
||||
*/
|
||||
priority?: number;
|
||||
}) => void;
|
||||
};
|
||||
@@ -126,7 +126,12 @@ export type RotateMoveContext = RotateStartContext & {
|
||||
|
||||
export type RotateEndContext = RotateStartContext;
|
||||
|
||||
export type SelectableContext = {
|
||||
export type SelectedContext = {
|
||||
/**
|
||||
* The selected state of the element
|
||||
*/
|
||||
selected: boolean;
|
||||
|
||||
/**
|
||||
* Whether is multi-select, usually triggered by shift key
|
||||
*/
|
||||
@@ -141,13 +146,14 @@ export type SelectableContext = {
|
||||
* The model position of the event pointer
|
||||
*/
|
||||
position: IPoint;
|
||||
};
|
||||
|
||||
export type SelectContext = SelectableContext & {
|
||||
/**
|
||||
* The selected state of the element
|
||||
* If the current selection is a fallback selection.
|
||||
*
|
||||
* E.g., if selecting a child element inside a group, the `onSelected` method will be executed on group, and
|
||||
* the fallback is true because the it's not the original target(the child element).
|
||||
*/
|
||||
selected: boolean;
|
||||
fallback: boolean;
|
||||
};
|
||||
|
||||
export type BoxSelectionContext = {
|
||||
@@ -166,6 +172,11 @@ export type GfxViewTransformInterface = {
|
||||
onDragMove: (context: DragMoveContext) => void;
|
||||
onDragEnd: (context: DragEndContext) => void;
|
||||
|
||||
/**
|
||||
* When the element is selected by the pointer
|
||||
*/
|
||||
onSelected: (context: SelectedContext) => void;
|
||||
|
||||
/**
|
||||
* When the element is selected by box selection, return false to prevent the default selection behavior.
|
||||
*/
|
||||
|
||||
@@ -626,11 +626,6 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all groups in the group chain. The last group is the top level group.
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
getGroups(id: string): GfxGroupModel[] {
|
||||
const groups: GfxGroupModel[] = [];
|
||||
const visited = new Set<GfxGroupModel>();
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
DragMoveContext,
|
||||
DragStartContext,
|
||||
GfxViewTransformInterface,
|
||||
SelectedContext,
|
||||
} from '../interactivity/index.js';
|
||||
import type { GfxElementGeometry, PointTestOptions } from '../model/base.js';
|
||||
import { GfxPrimitiveElementModel } from '../model/surface/element-model.js';
|
||||
@@ -209,6 +210,18 @@ export class GfxElementModelView<
|
||||
this.model.xywh = currentBound.moveDelta(dx, dy).serialize();
|
||||
}
|
||||
|
||||
onSelected(context: SelectedContext): void | boolean {
|
||||
if (this.model instanceof GfxPrimitiveElementModel) {
|
||||
if (context.multiSelect) {
|
||||
this.gfx.selection.toggle(this.model);
|
||||
} else {
|
||||
this.gfx.selection.set({ elements: [this.model.id] });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
onBoxSelected(_: BoxSelectionContext): boolean | void {}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user