mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
Compare commits
31 Commits
graphite-b
...
flrande/fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e363ba5f4f | ||
|
|
1e7774929c | ||
|
|
85bb728ca8 | ||
|
|
8b669b725b | ||
|
|
4ecdfb1258 | ||
|
|
820c3fda63 | ||
|
|
a028f027be | ||
|
|
8726b0e462 | ||
|
|
f3693a91c3 | ||
|
|
f215b680ef | ||
|
|
f0c9453459 | ||
|
|
5eca722edf | ||
|
|
8ed4f14380 | ||
|
|
101062aa25 | ||
|
|
b6e9c41ee3 | ||
|
|
147fa9a6b1 | ||
|
|
3a2fe0bf91 | ||
|
|
e98ec93af1 | ||
|
|
6c9f28e08b | ||
|
|
6224344a4f | ||
|
|
393458871d | ||
|
|
d00315e372 | ||
|
|
9fee8147cb | ||
|
|
6a13d69dea | ||
|
|
fabcdd3b2c | ||
|
|
fcc9b31da9 | ||
|
|
6052743671 | ||
|
|
43948f205e | ||
|
|
6fabc0eb1f | ||
|
|
74b2d4dc2e | ||
|
|
ffb72a4491 |
1
.docker/selfhost/.gitignore
vendored
Normal file
1
.docker/selfhost/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.env
|
||||
@@ -23,6 +23,7 @@ services:
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
|
||||
- AFFINE_INDEXER_SEARCH_ENDPOINT=http://indexer:9308
|
||||
restart: unless-stopped
|
||||
|
||||
affine_migration:
|
||||
@@ -38,6 +39,7 @@ services:
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
|
||||
- AFFINE_INDEXER_SEARCH_ENDPOINT=http://indexer:9308
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
@@ -57,7 +59,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:16
|
||||
image: pgvector/pgvector:pg16
|
||||
container_name: affine_postgres
|
||||
volumes:
|
||||
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
|
||||
|
||||
85
.github/workflows/build-test.yml
vendored
85
.github/workflows/build-test.yml
vendored
@@ -582,10 +582,80 @@ jobs:
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
manticoresearch:
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
|
||||
- name: Download server-native.node
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: server-native.node
|
||||
path: ./packages/backend/native
|
||||
|
||||
- name: Prepare Server Test Environment
|
||||
uses: ./.github/actions/server-test-env
|
||||
|
||||
- name: Run server tests
|
||||
run: yarn affine @affine/server test:coverage --forbid-only
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
CI_NODE_INDEX: ${{ matrix.node_index }}
|
||||
CI_NODE_TOTAL: ${{ matrix.total_nodes }}
|
||||
|
||||
- name: Upload server test coverage results
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/backend/server/.coverage/lcov.info
|
||||
flags: server-test
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
server-test-elasticsearch:
|
||||
name: Server Test with Elasticsearch
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-server-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
env:
|
||||
NODE_ENV: test
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
REDIS_SERVER_HOST: localhost
|
||||
AFFINE_INDEXER_SEARCH_PROVIDER: elasticsearch
|
||||
AFFINE_INDEXER_SEARCH_ENDPOINT: http://localhost:9200
|
||||
services:
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg16
|
||||
env:
|
||||
POSTGRES_PASSWORD: affine
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
mailer:
|
||||
image: mailhog/mailhog
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
steps:
|
||||
# https://github.com/elastic/elastic-github-actions/blob/master/elasticsearch/README.md
|
||||
- name: Configure sysctl limits for Elasticsearch
|
||||
@@ -618,8 +688,8 @@ jobs:
|
||||
- name: Prepare Server Test Environment
|
||||
uses: ./.github/actions/server-test-env
|
||||
|
||||
- name: Run server tests
|
||||
run: yarn affine @affine/server test:coverage --forbid-only
|
||||
- name: Run server tests with elasticsearch only
|
||||
run: yarn affine @affine/server test:coverage "**/*/*elasticsearch.spec.ts" --forbid-only
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
CI_NODE_INDEX: ${{ matrix.node_index }}
|
||||
@@ -886,6 +956,10 @@ jobs:
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -981,6 +1055,10 @@ jobs:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -1003,6 +1081,7 @@ jobs:
|
||||
- 'packages/backend/server/src/plugins/copilot/**'
|
||||
- 'packages/backend/server/tests/copilot.*'
|
||||
- 'packages/frontend/core/src/blocksuite/ai/**'
|
||||
- 'packages/frontend/core/src/modules/workspace-indexer-embedding/**'
|
||||
- 'tests/affine-cloud-copilot/**'
|
||||
|
||||
- name: Setup Node.js
|
||||
|
||||
8
.github/workflows/copilot-test.yml
vendored
8
.github/workflows/copilot-test.yml
vendored
@@ -59,6 +59,10 @@ jobs:
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -130,6 +134,10 @@ jobs:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
1
.github/workflows/release-mobile.yml
vendored
1
.github/workflows/release-mobile.yml
vendored
@@ -180,6 +180,7 @@ jobs:
|
||||
- name: Testflight
|
||||
if: ${{ env.BUILD_TYPE != 'stable' }}
|
||||
working-directory: packages/frontend/apps/ios/App
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo -n "${{ env.BUILD_PROVISION_PROFILE }}" | base64 --decode -o $PP_PATH
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
@@ -58,11 +58,14 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-drag-handle": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-dragging-area": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-zoom-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-frame-title": "workspace:*",
|
||||
"@blocksuite/affine-widget-keyboard-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-linked-doc": "workspace:*",
|
||||
"@blocksuite/affine-widget-note-slicer": "workspace:*",
|
||||
"@blocksuite/affine-widget-page-dragging-area": "workspace:*",
|
||||
"@blocksuite/affine-widget-remote-selection": "workspace:*",
|
||||
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*",
|
||||
@@ -178,6 +181,8 @@
|
||||
"./widgets/drag-handle/view": "./src/widgets/drag-handle/view.ts",
|
||||
"./widgets/edgeless-auto-connect": "./src/widgets/edgeless-auto-connect/index.ts",
|
||||
"./widgets/edgeless-auto-connect/view": "./src/widgets/edgeless-auto-connect/view.ts",
|
||||
"./widgets/edgeless-dragging-area": "./src/widgets/edgeless-dragging-area/index.ts",
|
||||
"./widgets/edgeless-dragging-area/view": "./src/widgets/edgeless-dragging-area/view.ts",
|
||||
"./widgets/edgeless-toolbar": "./src/widgets/edgeless-toolbar/index.ts",
|
||||
"./widgets/edgeless-toolbar/view": "./src/widgets/edgeless-toolbar/view.ts",
|
||||
"./widgets/frame-title": "./src/widgets/frame-title/index.ts",
|
||||
|
||||
@@ -40,11 +40,14 @@ import { InlinePresetViewExtension } from '@blocksuite/affine-inline-preset/view
|
||||
import { ReferenceViewExtension } from '@blocksuite/affine-inline-reference/view';
|
||||
import { DragHandleViewExtension } from '@blocksuite/affine-widget-drag-handle/view';
|
||||
import { EdgelessAutoConnectViewExtension } from '@blocksuite/affine-widget-edgeless-auto-connect/view';
|
||||
import { EdgelessDraggingAreaViewExtension } from '@blocksuite/affine-widget-edgeless-dragging-area/view';
|
||||
import { EdgelessSelectedRectViewExtension } from '@blocksuite/affine-widget-edgeless-selected-rect/view';
|
||||
import { EdgelessToolbarViewExtension } from '@blocksuite/affine-widget-edgeless-toolbar/view';
|
||||
import { EdgelessZoomToolbarViewExtension } from '@blocksuite/affine-widget-edgeless-zoom-toolbar/view';
|
||||
import { FrameTitleViewExtension } from '@blocksuite/affine-widget-frame-title/view';
|
||||
import { KeyboardToolbarViewExtension } from '@blocksuite/affine-widget-keyboard-toolbar/view';
|
||||
import { LinkedDocViewExtension } from '@blocksuite/affine-widget-linked-doc/view';
|
||||
import { NoteSlicerViewExtension } from '@blocksuite/affine-widget-note-slicer/view';
|
||||
import { PageDraggingAreaViewExtension } from '@blocksuite/affine-widget-page-dragging-area/view';
|
||||
import { RemoteSelectionViewExtension } from '@blocksuite/affine-widget-remote-selection/view';
|
||||
import { ScrollAnchoringViewExtension } from '@blocksuite/affine-widget-scroll-anchoring/view';
|
||||
@@ -112,6 +115,9 @@ export function getInternalViewExtensions() {
|
||||
ViewportOverlayViewExtension,
|
||||
EdgelessZoomToolbarViewExtension,
|
||||
PageDraggingAreaViewExtension,
|
||||
EdgelessSelectedRectViewExtension,
|
||||
EdgelessDraggingAreaViewExtension,
|
||||
NoteSlicerViewExtension,
|
||||
|
||||
// Fragment
|
||||
DocTitleViewExtension,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-dragging-area';
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-dragging-area/view';
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-selected-rect';
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-selected-rect/view';
|
||||
1
blocksuite/affine/all/src/widgets/note-slicer/index.ts
Normal file
1
blocksuite/affine/all/src/widgets/note-slicer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-widget-note-slicer';
|
||||
1
blocksuite/affine/all/src/widgets/note-slicer/view.ts
Normal file
1
blocksuite/affine/all/src/widgets/note-slicer/view.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-widget-note-slicer/view';
|
||||
@@ -55,11 +55,14 @@
|
||||
{ "path": "../shared" },
|
||||
{ "path": "../widgets/drag-handle" },
|
||||
{ "path": "../widgets/edgeless-auto-connect" },
|
||||
{ "path": "../widgets/edgeless-dragging-area" },
|
||||
{ "path": "../widgets/edgeless-selected-rect" },
|
||||
{ "path": "../widgets/edgeless-toolbar" },
|
||||
{ "path": "../widgets/edgeless-zoom-toolbar" },
|
||||
{ "path": "../widgets/frame-title" },
|
||||
{ "path": "../widgets/keyboard-toolbar" },
|
||||
{ "path": "../widgets/linked-doc" },
|
||||
{ "path": "../widgets/note-slicer" },
|
||||
{ "path": "../widgets/page-dragging-area" },
|
||||
{ "path": "../widgets/remote-selection" },
|
||||
{ "path": "../widgets/scroll-anchoring" },
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
AttachmentBlockStyles,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
DocModeProvider,
|
||||
FileSizeLimitProvider,
|
||||
TelemetryProvider,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { humanFileSize } from '@blocksuite/affine-shared/utils';
|
||||
@@ -143,7 +145,11 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
this.disposables.add(this.resourceController.subscribe());
|
||||
this.disposables.add(this.resourceController);
|
||||
|
||||
this.refreshData();
|
||||
this.disposables.add(
|
||||
this.model.props.sourceId$.subscribe(() => {
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
|
||||
if (!this.model.props.style && !this.store.readonly) {
|
||||
this.store.withoutTransact(() => {
|
||||
@@ -183,6 +189,22 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
@click=${(event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
onOverFileSize?.();
|
||||
|
||||
{
|
||||
const mode =
|
||||
this.std.get(DocModeProvider).getEditorMode() ?? 'page';
|
||||
const segment = mode === 'page' ? 'doc' : 'whiteboard';
|
||||
this.std
|
||||
.getOptional(TelemetryProvider)
|
||||
?.track('AttachmentUpgradedEvent', {
|
||||
segment,
|
||||
page: `${segment} editor`,
|
||||
module: 'attachment',
|
||||
control: 'upgrade',
|
||||
category: 'card',
|
||||
type: this.model.props.name.split('.').pop() ?? '',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
${UpgradeIcon()} Upgrade
|
||||
@@ -198,6 +220,22 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
@click=${(event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
this.refreshData();
|
||||
|
||||
{
|
||||
const mode =
|
||||
this.std.get(DocModeProvider).getEditorMode() ?? 'page';
|
||||
const segment = mode === 'page' ? 'doc' : 'whiteboard';
|
||||
this.std
|
||||
.getOptional(TelemetryProvider)
|
||||
?.track('AttachmentReloadedEvent', {
|
||||
segment,
|
||||
page: `${segment} editor`,
|
||||
module: 'attachment',
|
||||
control: 'reload',
|
||||
category: 'card',
|
||||
type: this.model.props.name.split('.').pop() ?? '',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
${ResetIcon()} Reload
|
||||
|
||||
@@ -264,6 +264,12 @@ const builtinToolbarConfig = {
|
||||
run(ctx) {
|
||||
const block = ctx.getCurrentBlockByType(AttachmentBlockComponent);
|
||||
block?.reload();
|
||||
|
||||
ctx.track('AttachmentReloadedEvent', {
|
||||
...trackBaseProps,
|
||||
control: 'reload',
|
||||
type: block?.model.props.name.split('.').pop() ?? '',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -130,7 +130,7 @@ async function buildPropsWith(
|
||||
std.getOptional(TelemetryProvider)?.track('AttachmentUploadedEvent', {
|
||||
page: `${mode} editor`,
|
||||
module: 'attachment',
|
||||
segment: 'attachment',
|
||||
segment: mode,
|
||||
control: 'uploader',
|
||||
type,
|
||||
category,
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import '@blocksuite/affine-block-embed/effects';
|
||||
|
||||
import { insertEmbedCard } from '@blocksuite/affine-block-embed';
|
||||
import type { EmbedCardStyle } from '@blocksuite/affine-model';
|
||||
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/code-painter.worker.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
|
||||
@@ -388,8 +388,10 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
|
||||
override renderBlock(): TemplateResult<1> {
|
||||
const showLineNumbers =
|
||||
this.std.getOptional(CodeBlockConfigExtension.identifier)
|
||||
?.showLineNumbers ?? true;
|
||||
(this.std.getOptional(CodeBlockConfigExtension.identifier)
|
||||
?.showLineNumbers ??
|
||||
true) &&
|
||||
(this.model.props.lineNumber ?? true);
|
||||
|
||||
const preview = !!this.model.props.preview;
|
||||
const previewContext = this.std.getOptional(
|
||||
@@ -403,6 +405,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
'affine-code-block-container': true,
|
||||
mobile: IS_MOBILE,
|
||||
wrap: this.model.props.wrap,
|
||||
'disable-line-numbers': !showLineNumbers,
|
||||
})}
|
||||
>
|
||||
<rich-text
|
||||
@@ -420,16 +423,14 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
.enableUndoRedo=${false}
|
||||
.wrapText=${this.model.props.wrap}
|
||||
.verticalScrollContainerGetter=${() => getViewportElement(this.host)}
|
||||
.vLineRenderer=${showLineNumbers
|
||||
? (vLine: VLine) => {
|
||||
return html`
|
||||
<span contenteditable="false" class="line-number"
|
||||
>${vLine.index + 1}</span
|
||||
>
|
||||
${vLine.renderVElements()}
|
||||
`;
|
||||
}
|
||||
: undefined}
|
||||
.vLineRenderer=${(vLine: VLine) => {
|
||||
return html`
|
||||
<span contenteditable="false" class="line-number"
|
||||
>${vLine.index + 1}</span
|
||||
>
|
||||
${vLine.renderVElements()}
|
||||
`;
|
||||
}}
|
||||
>
|
||||
</rich-text>
|
||||
<div
|
||||
|
||||
13
blocksuite/affine/blocks/code/src/code-edgeless-block.ts
Normal file
13
blocksuite/affine/blocks/code/src/code-edgeless-block.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { toGfxBlockComponent } from '@blocksuite/std';
|
||||
|
||||
import { CodeBlockComponent } from './code-block.js';
|
||||
|
||||
export class CodeEdgelessBlockComponent extends toGfxBlockComponent(
|
||||
CodeBlockComponent
|
||||
) {}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-edgeless-code': CodeEdgelessBlockComponent;
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
|
||||
import { isInsidePageEditor } from '@blocksuite/affine-shared/utils';
|
||||
import { noop, sleep } from '@blocksuite/global/utils';
|
||||
import { NumberedListIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
|
||||
import { CodeBlockConfigExtension } from '../code-block-config.js';
|
||||
import type { CodeBlockToolbarContext } from './context.js';
|
||||
import { duplicateCodeBlock } from './utils.js';
|
||||
|
||||
@@ -148,6 +150,40 @@ export const clipboardGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'line-number',
|
||||
when: ({ std }) =>
|
||||
std.getOptional(CodeBlockConfigExtension.identifier)?.showLineNumbers ??
|
||||
true,
|
||||
generate: ({ blockComponent, close }) => {
|
||||
return {
|
||||
action: () => {},
|
||||
render: () => {
|
||||
const lineNumber = blockComponent.model.props.lineNumber ?? true;
|
||||
const label = lineNumber ? 'Cancel line number' : 'Line number';
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
blockComponent.store.updateBlock(blockComponent.model, {
|
||||
lineNumber: !lineNumber,
|
||||
});
|
||||
|
||||
close();
|
||||
}}
|
||||
aria-label=${label}
|
||||
>
|
||||
${NumberedListIcon()}
|
||||
<span class="label">${label}</span>
|
||||
<toggle-switch
|
||||
style="margin-left: auto;"
|
||||
.on="${lineNumber}"
|
||||
></toggle-switch>
|
||||
</editor-menu-action>
|
||||
`;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'duplicate',
|
||||
label: 'Duplicate',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CodeBlockComponent } from './code-block';
|
||||
import { CodeEdgelessBlockComponent } from './code-edgeless-block';
|
||||
import {
|
||||
AFFINE_CODE_TOOLBAR_WIDGET,
|
||||
AffineCodeToolbarWidget,
|
||||
@@ -14,6 +15,7 @@ export function effects() {
|
||||
customElements.define(AFFINE_CODE_TOOLBAR_WIDGET, AffineCodeToolbarWidget);
|
||||
customElements.define('affine-code-unit', AffineCodeUnit);
|
||||
customElements.define('affine-code', CodeBlockComponent);
|
||||
customElements.define('affine-edgeless-code', CodeEdgelessBlockComponent);
|
||||
customElements.define('preview-button', PreviewButton);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ export const codeBlockStyles = css`
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.affine-code-block-container.disable-line-numbers .line-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
affine-code .affine-code-block-preview {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,11 @@ export class CodeBlockViewExtension extends ViewExtensionProvider {
|
||||
context.register([
|
||||
FlavourExtension('affine:code'),
|
||||
CodeBlockHighlighter,
|
||||
BlockViewExtension('affine:code', literal`affine-code`),
|
||||
BlockViewExtension('affine:code', model => {
|
||||
return model.parent?.flavour === 'affine:surface'
|
||||
? literal`affine-edgeless-code`
|
||||
: literal`affine-code`;
|
||||
}),
|
||||
SlashMenuConfigExtension('affine:code', codeSlashMenuConfig),
|
||||
CodeKeymapExtension,
|
||||
...getCodeClipboardExtensions(),
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -52,23 +52,25 @@ export const EmbedSyncedDocInteraction =
|
||||
scale = newBound.w / realWidth;
|
||||
}
|
||||
|
||||
const newWidth = newBound.w / scale;
|
||||
|
||||
newBound.w =
|
||||
clamp(newWidth, constraint.minWidth, constraint.maxWidth) * scale;
|
||||
clamp(
|
||||
newBound.w / scale,
|
||||
constraint.minWidth,
|
||||
constraint.maxWidth
|
||||
) * scale;
|
||||
newBound.h =
|
||||
clamp(newBound.h, constraint.minHeight, constraint.maxHeight) *
|
||||
scale;
|
||||
clamp(
|
||||
newBound.h / scale,
|
||||
constraint.minHeight,
|
||||
constraint.maxHeight
|
||||
) * scale;
|
||||
|
||||
const newHeight = newBound.h / scale;
|
||||
|
||||
// only adjust height check the fold state
|
||||
if (originalBound.w === newBound.w) {
|
||||
let preFoldHeight = 0;
|
||||
if (newHeight === constraint.minHeight) {
|
||||
preFoldHeight = initHeight;
|
||||
}
|
||||
model.props.preFoldHeight = preFoldHeight;
|
||||
if (model.isFolded && newHeight > constraint.minHeight) {
|
||||
model.props.preFoldHeight = 0;
|
||||
} else if (!model.isFolded && newHeight <= constraint.minHeight) {
|
||||
model.props.preFoldHeight = initHeight;
|
||||
}
|
||||
|
||||
model.props.scale = scale;
|
||||
|
||||
@@ -76,6 +76,8 @@ export function calcSyncedDocFullHeight(block: BlockComponent) {
|
||||
const bottomPadding = 8;
|
||||
|
||||
return (
|
||||
(headerHeight + contentHeight + bottomPadding) / block.gfx.viewport.zoom
|
||||
(headerHeight + contentHeight + bottomPadding) /
|
||||
block.gfx.viewport.zoom /
|
||||
(block.model.props.scale ?? 1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -185,9 +185,33 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen().catch(console.error);
|
||||
}
|
||||
|
||||
// Reset the flag when fully exiting presentation mode
|
||||
this.edgeless.std
|
||||
.get(EditPropsStore)
|
||||
.setStorage('presentNoFrameToastShown', false);
|
||||
}
|
||||
|
||||
private _moveToCurrentFrame() {
|
||||
private _moveToCurrentFrame(forceMove = false) {
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
const toolOptions = currentToolOption?.options;
|
||||
|
||||
// If PresentTool is being activated after a temporary pan (indicated by restoredAfterPan)
|
||||
// and a forced move isn't explicitly requested, skip moving to the current frame.
|
||||
// This preserves the user's panned position instead of resetting to the frame's default view.
|
||||
if (
|
||||
currentToolOption?.toolType === PresentTool &&
|
||||
toolOptions?.restoredAfterPan &&
|
||||
!forceMove
|
||||
) {
|
||||
// Clear the flag so future navigations behave normally
|
||||
this.gfx.tool.setTool(PresentTool, {
|
||||
...toolOptions,
|
||||
restoredAfterPan: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const current = this._currentFrameIndex;
|
||||
const viewport = this.gfx.viewport;
|
||||
const frame = this._frames[current];
|
||||
@@ -263,28 +287,56 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
|
||||
_disposables.add(
|
||||
effect(() => {
|
||||
const currentTool = this.gfx.tool.currentToolOption$.value;
|
||||
const selection = this.gfx.selection;
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
|
||||
if (currentTool?.toolType === PresentTool) {
|
||||
this._cachedIndex = this._currentFrameIndex;
|
||||
this._navigatorMode =
|
||||
(currentTool.options as ToolOptions<PresentTool>)?.mode ??
|
||||
this._navigatorMode;
|
||||
if (isFrameBlock(selection.selectedElements[0])) {
|
||||
this._cachedIndex = this._frames.findIndex(
|
||||
frame => frame.id === selection.selectedElements[0].id
|
||||
);
|
||||
if (currentToolOption?.toolType === PresentTool) {
|
||||
const opts = currentToolOption.options as
|
||||
| ToolOptions<PresentTool>
|
||||
| undefined;
|
||||
|
||||
const isAlreadyFullscreen = !!document.fullscreenElement;
|
||||
|
||||
if (!isAlreadyFullscreen) {
|
||||
this._toggleFullScreen();
|
||||
} else {
|
||||
this._fullScreenMode = true;
|
||||
}
|
||||
if (this._frames.length === 0)
|
||||
toast(
|
||||
this.host,
|
||||
'The presentation requires at least 1 frame. You can firstly create a frame.',
|
||||
5000
|
||||
);
|
||||
this._toggleFullScreen();
|
||||
}
|
||||
|
||||
this._cachedIndex = this._currentFrameIndex;
|
||||
this._navigatorMode = opts?.mode ?? this._navigatorMode;
|
||||
|
||||
const selection = this.gfx.selection;
|
||||
if (
|
||||
selection.selectedElements.length > 0 &&
|
||||
isFrameBlock(selection.selectedElements[0])
|
||||
) {
|
||||
const selectedFrameId = selection.selectedElements[0].id;
|
||||
const indexOfSelectedFrame = this._frames.findIndex(
|
||||
frame => frame.id === selectedFrameId
|
||||
);
|
||||
if (indexOfSelectedFrame !== -1) {
|
||||
this._cachedIndex = indexOfSelectedFrame;
|
||||
}
|
||||
}
|
||||
|
||||
const store = this.edgeless.std.get(EditPropsStore);
|
||||
if (this._frames.length === 0) {
|
||||
if (!store.getStorage('presentNoFrameToastShown')) {
|
||||
toast(
|
||||
this.host,
|
||||
'The presentation requires at least 1 frame. You can firstly create a frame.',
|
||||
5000
|
||||
);
|
||||
store.setStorage('presentNoFrameToastShown', true);
|
||||
}
|
||||
} else {
|
||||
// If frames exist, and the flag was set, reset it.
|
||||
// This allows the toast to show again if all frames are subsequently deleted.
|
||||
if (store.getStorage('presentNoFrameToastShown')) {
|
||||
store.setStorage('presentNoFrameToastShown', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
@@ -305,12 +357,10 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
|
||||
_disposables.addFromEvent(document, 'fullscreenchange', () => {
|
||||
if (document.fullscreenElement) {
|
||||
// When enter fullscreen, we need to set current frame to the cached index
|
||||
this._timer = setTimeout(() => {
|
||||
this._currentFrameIndex = this._cachedIndex;
|
||||
}, 400);
|
||||
} else {
|
||||
// When exit fullscreen, we need to clear the timer
|
||||
clearTimeout(this._timer);
|
||||
if (
|
||||
this.edgelessTool.toolType === PresentTool &&
|
||||
@@ -324,7 +374,7 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => this._moveToCurrentFrame(), 400);
|
||||
setTimeout(() => this._moveToCurrentFrame(true), 400);
|
||||
this.slots.fullScreenToggled.next();
|
||||
});
|
||||
|
||||
@@ -430,11 +480,29 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
}
|
||||
|
||||
protected override updated(changedProperties: PropertyValues) {
|
||||
if (
|
||||
changedProperties.has('_currentFrameIndex') &&
|
||||
this.edgelessTool.toolType === PresentTool
|
||||
) {
|
||||
this._moveToCurrentFrame();
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
const isPresentToolActive = currentToolOption?.toolType === PresentTool;
|
||||
const toolOptions = currentToolOption?.options;
|
||||
const isRestoredAfterPan = !!(
|
||||
isPresentToolActive && toolOptions?.restoredAfterPan
|
||||
);
|
||||
|
||||
if (changedProperties.has('_currentFrameIndex') && isPresentToolActive) {
|
||||
// When the current frame index changes (e.g., user navigates), a viewport update is needed.
|
||||
// However, if PresentTool is merely being restored after a pan (isRestoredAfterPan = true)
|
||||
// without an explicit index change in this update cycle, we avoid forcing a move to preserve the panned position.
|
||||
// Thus, `forceMove` is true unless it's a pan restoration.
|
||||
const shouldForceMove = !isRestoredAfterPan;
|
||||
this._moveToCurrentFrame(shouldForceMove);
|
||||
} else if (isPresentToolActive && changedProperties.has('edgelessTool')) {
|
||||
// Handles cases where the tool is set/switched to PresentTool (e.g., initial activation or returning from another tool).
|
||||
// Similar to frame index changes, avoid forcing a viewport move if restoring after a pan.
|
||||
const currentToolIsPresentTool =
|
||||
this.edgelessTool.toolType === PresentTool;
|
||||
if (currentToolIsPresentTool) {
|
||||
const shouldForceMoveOnToolChange = !isRestoredAfterPan;
|
||||
this._moveToCurrentFrame(shouldForceMoveOnToolChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { NavigatorMode } from './frame-manager';
|
||||
|
||||
export type PresentToolOption = {
|
||||
mode?: NavigatorMode;
|
||||
restoredAfterPan?: boolean;
|
||||
};
|
||||
|
||||
export class PresentTool extends BaseTool<PresentToolOption> {
|
||||
|
||||
@@ -77,18 +77,16 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
slots.navigatorSettingUpdated.next({
|
||||
blackBackground: this.blackBackground,
|
||||
});
|
||||
this.edgeless.std
|
||||
.get(EditPropsStore)
|
||||
.setStorage('presentBlackBackground', checked);
|
||||
};
|
||||
|
||||
private _tryRestoreSettings() {
|
||||
const blackBackground = this.edgeless.std
|
||||
.get(EditPropsStore)
|
||||
.getStorage('presentBlackBackground');
|
||||
this.blackBackground = blackBackground ?? true;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._tryRestoreSettings();
|
||||
this.blackBackground = blackBackground ?? false;
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
@@ -97,6 +95,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
if (this.edgeless) this._tryRestoreSettings();
|
||||
this._navigatorSettingPopper = createButtonPopper({
|
||||
reference: this._navigatorSettingButton,
|
||||
popperElement: this._navigatorSettingMenu,
|
||||
@@ -133,7 +132,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
<div class="text">Black background</div>
|
||||
|
||||
<toggle-switch
|
||||
.subscribe=${this.blackBackground}
|
||||
.on=${this.blackBackground}
|
||||
.onChange=${this._onBlackBackgroundChange}
|
||||
>
|
||||
</toggle-switch>
|
||||
@@ -143,7 +142,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
<div class="text">Hide toolbar</div>
|
||||
|
||||
<toggle-switch
|
||||
.subscribe=${this.hideToolbar}
|
||||
.on=${this.hideToolbar}
|
||||
.onChange=${(checked: boolean) => {
|
||||
this.onHideToolbarChange && this.onHideToolbarChange(checked);
|
||||
}}
|
||||
@@ -173,7 +172,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
private accessor _navigatorSettingMenu!: HTMLElement;
|
||||
|
||||
@state()
|
||||
accessor blackBackground = true;
|
||||
accessor blackBackground = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor edgeless!: BlockComponent;
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/image-painter.worker.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import { ImageSelection } from '@blocksuite/affine-shared/selection';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import type { BlockComponent, UIEventStateContext } from '@blocksuite/std';
|
||||
import {
|
||||
BlockSelection,
|
||||
@@ -15,8 +15,9 @@ import {
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
import type { BaseSelection } from '@blocksuite/store';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html, type PropertyValues } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
@@ -25,7 +26,9 @@ import { ImageResizeManager } from '../image-resize-manager';
|
||||
import { shouldResizeImage } from '../utils';
|
||||
import { ImageSelectedRect } from './image-selected-rect';
|
||||
|
||||
export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
export class ImageBlockPageComponent extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
affine-page-image {
|
||||
position: relative;
|
||||
@@ -68,6 +71,8 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
`;
|
||||
|
||||
resizeable$ = computed(() => this.block.resizeable$.value);
|
||||
|
||||
private _isDragging = false;
|
||||
|
||||
private get _doc() {
|
||||
@@ -134,21 +139,21 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
return true;
|
||||
},
|
||||
Delete: ctx => {
|
||||
if (this._host.store.readonly || !this._isSelected) return;
|
||||
if (this._host.store.readonly || !this.resizeable$.peek()) return;
|
||||
|
||||
addParagraph(ctx);
|
||||
this._doc.deleteBlock(this._model);
|
||||
return true;
|
||||
},
|
||||
Backspace: ctx => {
|
||||
if (this._host.store.readonly || !this._isSelected) return;
|
||||
if (this._host.store.readonly || !this.resizeable$.peek()) return;
|
||||
|
||||
addParagraph(ctx);
|
||||
this._doc.deleteBlock(this._model);
|
||||
return true;
|
||||
},
|
||||
Enter: ctx => {
|
||||
if (this._host.store.readonly || !this._isSelected) return;
|
||||
if (this._host.store.readonly || !this.resizeable$.peek()) return;
|
||||
|
||||
addParagraph(ctx);
|
||||
return true;
|
||||
@@ -213,19 +218,6 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
private _handleSelection() {
|
||||
const selection = this._host.selection;
|
||||
this._disposables.add(
|
||||
selection.slots.changed.subscribe(selList => {
|
||||
this._isSelected = selList.some(
|
||||
sel => sel.blockId === this.block.blockId && sel.is(ImageSelection)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
this._disposables.add(
|
||||
this._model.propsUpdated.subscribe(() => {
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
|
||||
this._disposables.addFromEvent(
|
||||
this.resizeImg,
|
||||
@@ -249,7 +241,7 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
this.block.handleEvent(
|
||||
'click',
|
||||
() => {
|
||||
if (!this._isSelected) return;
|
||||
if (!this.resizeable$.peek()) return;
|
||||
|
||||
selection.update(selList =>
|
||||
selList.filter(
|
||||
@@ -356,7 +348,7 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
override render() {
|
||||
const imageSize = this._normalizeImageSize();
|
||||
|
||||
const imageSelectedRect = this._isSelected
|
||||
const imageSelectedRect = this.resizeable$.value
|
||||
? ImageSelectedRect(this._doc.readonly)
|
||||
: null;
|
||||
|
||||
@@ -389,9 +381,6 @@ export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
accessor _isSelected = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor block!: ImageBlockComponent;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getLoadingIconWith } from '@blocksuite/affine-components/icons';
|
||||
import { Peekable } from '@blocksuite/affine-components/peek';
|
||||
import { ResourceController } from '@blocksuite/affine-components/resource';
|
||||
import type { ImageBlockModel } from '@blocksuite/affine-model';
|
||||
import { ImageSelection } from '@blocksuite/affine-shared/selection';
|
||||
import {
|
||||
ThemeProvider,
|
||||
ToolbarRegistryIdentifier,
|
||||
@@ -30,6 +31,13 @@ import {
|
||||
enableOn: () => !IS_MOBILE,
|
||||
})
|
||||
export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel> {
|
||||
resizeable$ = computed(() =>
|
||||
this.std.selection.value.some(
|
||||
selection =>
|
||||
selection.is(ImageSelection) && selection.blockId === this.blockId
|
||||
)
|
||||
);
|
||||
|
||||
resourceController = new ResourceController(
|
||||
computed(() => this.model.props.sourceId$.value),
|
||||
'Image'
|
||||
@@ -104,7 +112,11 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
|
||||
this.disposables.add(this.resourceController.subscribe());
|
||||
this.disposables.add(this.resourceController);
|
||||
|
||||
this.refreshData();
|
||||
this.disposables.add(
|
||||
this.model.props.sourceId$.subscribe(() => {
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
|
||||
@@ -100,7 +100,11 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
|
||||
this.disposables.add(this.resourceController.subscribe());
|
||||
this.disposables.add(this.resourceController);
|
||||
|
||||
this.refreshData();
|
||||
this.disposables.add(
|
||||
this.model.props.sourceId$.subscribe(() => {
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override renderGfxBlock() {
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/list-painter.worker.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/note-painter.worker.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/paragraph-painter.worker.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"@blocksuite/affine-block-database": "workspace:*",
|
||||
"@blocksuite/affine-block-edgeless-text": "workspace:*",
|
||||
"@blocksuite/affine-block-embed": "workspace:*",
|
||||
"@blocksuite/affine-block-embed-doc": "workspace:*",
|
||||
"@blocksuite/affine-block-frame": "workspace:*",
|
||||
"@blocksuite/affine-block-image": "workspace:*",
|
||||
"@blocksuite/affine-block-note": "workspace:*",
|
||||
@@ -35,6 +34,7 @@
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
pasteMiddleware,
|
||||
replaceIdMiddleware,
|
||||
surfaceRefToEmbed,
|
||||
uploadMiddleware,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
clearAndSelectFirstModelCommand,
|
||||
@@ -34,14 +35,17 @@ export class PageClipboard extends ReadOnlyClipboard {
|
||||
// When pastina a surface-ref block to another doc
|
||||
const surfaceRefToEmbedMiddleware = surfaceRefToEmbed(this.std);
|
||||
const replaceId = replaceIdMiddleware(this.std.store.workspace.idGenerator);
|
||||
const upload = uploadMiddleware(this.std);
|
||||
this.std.clipboard.use(paste);
|
||||
this.std.clipboard.use(surfaceRefToEmbedMiddleware);
|
||||
this.std.clipboard.use(replaceId);
|
||||
this.std.clipboard.use(upload);
|
||||
this._disposables.add({
|
||||
dispose: () => {
|
||||
this.std.clipboard.unuse(paste);
|
||||
this.std.clipboard.unuse(surfaceRefToEmbedMiddleware);
|
||||
this.std.clipboard.unuse(replaceId);
|
||||
this.std.clipboard.unuse(upload);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import type {
|
||||
CursorType,
|
||||
ResizeHandle,
|
||||
StandardCursor,
|
||||
} from '@blocksuite/std/gfx';
|
||||
|
||||
const rotateCursorMap: {
|
||||
[key in ResizeHandle]: number;
|
||||
} = {
|
||||
'top-right': 0,
|
||||
'bottom-right': 90,
|
||||
'bottom-left': 180,
|
||||
'top-left': 270,
|
||||
|
||||
// not used
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
export function generateCursorUrl(
|
||||
angle = 0,
|
||||
handle: ResizeHandle,
|
||||
fallback: StandardCursor = 'default'
|
||||
): CursorType {
|
||||
angle = ((angle % 360) + 360) % 360;
|
||||
return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg transform='rotate(${rotateCursorMap[handle] + angle} 16 16)'%3E%3Cpath fill='white' d='M13.7,18.5h3.9l0-1.5c0-1.4-1.2-2.6-2.6-2.6h-1.5v3.9l-5.8-5.8l5.8-5.8v3.9h2.3c3.1,0,5.6,2.5,5.6,5.6v2.3h3.9l-5.8,5.8L13.7,18.5z'/%3E%3Cpath d='M20.4,19.4v-3.2c0-2.6-2.1-4.7-4.7-4.7h-3.2l0,0V9L9,12.6l3.6,3.6v-2.6l0,0H15c1.9,0,3.5,1.6,3.5,3.5v2.4l0,0h-2.6l3.6,3.6l3.6-3.6L20.4,19.4L20.4,19.4z'/%3E%3C/g%3E%3C/svg%3E") 16 16, ${fallback}`;
|
||||
}
|
||||
|
||||
const handleToRotateMap: {
|
||||
[key in ResizeHandle]: number;
|
||||
} = {
|
||||
'top-left': 45,
|
||||
'top-right': 135,
|
||||
'bottom-right': 45,
|
||||
'bottom-left': 135,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 90,
|
||||
bottom: 90,
|
||||
};
|
||||
|
||||
const rotateToHandleMap: {
|
||||
[key: number]: StandardCursor;
|
||||
} = {
|
||||
0: 'ew-resize',
|
||||
45: 'nwse-resize',
|
||||
90: 'ns-resize',
|
||||
135: 'nesw-resize',
|
||||
};
|
||||
|
||||
export function getRotatedResizeCursor(option: {
|
||||
handle: ResizeHandle;
|
||||
angle: number;
|
||||
}) {
|
||||
const angle =
|
||||
(Math.round(
|
||||
(handleToRotateMap[option.handle] + ((option.angle + 360) % 360)) / 45
|
||||
) %
|
||||
4) *
|
||||
45;
|
||||
|
||||
return rotateToHandleMap[angle] || 'default';
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import { insertLinkByQuickSearchCommand } from '@blocksuite/affine-block-bookmark';
|
||||
import { EdgelessTextBlockComponent } from '@blocksuite/affine-block-edgeless-text';
|
||||
import { FrameTool } from '@blocksuite/affine-block-frame';
|
||||
import { DefaultTool, isNoteBlock } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
DefaultTool,
|
||||
EdgelessLegacySlotIdentifier,
|
||||
isNoteBlock,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { toast } from '@blocksuite/affine-components/toast';
|
||||
import {
|
||||
BrushTool,
|
||||
@@ -47,6 +51,7 @@ import { SurfaceSelection, TextSelection } from '@blocksuite/std';
|
||||
import {
|
||||
type BaseTool,
|
||||
GfxBlockElementModel,
|
||||
GfxControllerIdentifier,
|
||||
type GfxPrimitiveElementModel,
|
||||
isGfxGroupCompatibleModel,
|
||||
type ToolOptions,
|
||||
@@ -66,7 +71,15 @@ import { isCanvasElement } from './utils/query.js';
|
||||
|
||||
export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
get gfx() {
|
||||
return this.rootComponent.gfx;
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
|
||||
get slots() {
|
||||
return this.std.get(EdgelessLegacySlotIdentifier);
|
||||
}
|
||||
|
||||
get std() {
|
||||
return this.rootComponent.std;
|
||||
}
|
||||
|
||||
constructor(override rootComponent: EdgelessRootBlockComponent) {
|
||||
@@ -118,7 +131,7 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
NoteBlockModel,
|
||||
])
|
||||
) {
|
||||
rootComponent.slots.toggleNoteSlicer.next();
|
||||
this.slots.toggleNoteSlicer.next();
|
||||
}
|
||||
},
|
||||
f: () => {
|
||||
@@ -153,7 +166,7 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
elements.length === 1 &&
|
||||
isNoteBlock(elements[0])
|
||||
) {
|
||||
rootComponent.slots.toggleNoteSlicer.next();
|
||||
this.slots.toggleNoteSlicer.next();
|
||||
}
|
||||
},
|
||||
'@': () => {
|
||||
@@ -454,6 +467,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 &&
|
||||
@@ -491,8 +507,12 @@ 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;
|
||||
},
|
||||
{ global: true }
|
||||
);
|
||||
@@ -705,7 +725,7 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
}
|
||||
this._setEdgelessTool(PanTool, { panning: false });
|
||||
|
||||
edgeless.dispatcher.disposables.addFromEvent(
|
||||
this.std.event.disposables.addFromEvent(
|
||||
document,
|
||||
'keyup',
|
||||
revertToPrevTool
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { NoteConfigExtension } from '@blocksuite/affine-block-note';
|
||||
import {
|
||||
DefaultTool,
|
||||
EdgelessLegacySlotIdentifier,
|
||||
getBgGridGap,
|
||||
normalizeWheelDeltaY,
|
||||
type SurfaceBlockComponent,
|
||||
@@ -45,7 +44,6 @@ import { css, html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { EdgelessSelectedRectWidget } from './components/rects/edgeless-selected-rect.js';
|
||||
import { EdgelessPageKeyboardManager } from './edgeless-keyboard.js';
|
||||
import type { EdgelessRootService } from './edgeless-root-service.js';
|
||||
import { isCanvasElement } from './utils/query.js';
|
||||
@@ -121,43 +119,27 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
|
||||
keyboardManager: EdgelessPageKeyboardManager | null = null;
|
||||
|
||||
get dispatcher() {
|
||||
return this.std.event;
|
||||
}
|
||||
|
||||
get fontLoader() {
|
||||
return this.std.get(FontLoaderService);
|
||||
}
|
||||
|
||||
get gfx() {
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
|
||||
get selectedRectWidget() {
|
||||
return this.host.view.getWidget(
|
||||
'edgeless-selected-rect',
|
||||
this.host.id
|
||||
) as EdgelessSelectedRectWidget;
|
||||
}
|
||||
|
||||
get slots() {
|
||||
return this.std.get(EdgelessLegacySlotIdentifier);
|
||||
}
|
||||
|
||||
get surfaceBlockModel() {
|
||||
return this.model.children.find(
|
||||
child => child.flavour === 'affine:surface'
|
||||
) as SurfaceBlockModel;
|
||||
}
|
||||
|
||||
get viewportElement(): HTMLElement {
|
||||
private get _viewportElement(): HTMLElement {
|
||||
return this.std.get(ViewportElementProvider).viewportElement;
|
||||
}
|
||||
|
||||
get fontLoader() {
|
||||
return this.std.get(FontLoaderService);
|
||||
}
|
||||
|
||||
private _initFontLoader() {
|
||||
this.std
|
||||
.get(FontLoaderService)
|
||||
.ready.then(() => {
|
||||
this.fontLoader.ready
|
||||
.then(() => {
|
||||
this.surface.refresh();
|
||||
})
|
||||
.catch(console.error);
|
||||
@@ -181,7 +163,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
|
||||
private _initPanEvent() {
|
||||
this.disposables.add(
|
||||
this.dispatcher.add('pan', ctx => {
|
||||
this.std.event.add('pan', ctx => {
|
||||
const { viewport } = this.gfx;
|
||||
if (viewport.locked) return;
|
||||
|
||||
@@ -205,7 +187,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
|
||||
private _initPinchEvent() {
|
||||
this.disposables.add(
|
||||
this.dispatcher.add('pinch', ctx => {
|
||||
this.std.event.add('pinch', ctx => {
|
||||
const { viewport } = this.gfx;
|
||||
if (viewport.locked) return;
|
||||
|
||||
@@ -285,7 +267,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
this.gfx.viewport.onResize();
|
||||
});
|
||||
|
||||
resizeObserver.observe(this.viewportElement);
|
||||
resizeObserver.observe(this._viewportElement);
|
||||
this._resizeObserver = resizeObserver;
|
||||
}
|
||||
|
||||
@@ -348,7 +330,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
|
||||
private _initWheelEvent() {
|
||||
this._disposables.add(
|
||||
this.dispatcher.add('wheel', ctx => {
|
||||
this.std.event.add('wheel', ctx => {
|
||||
const config = this.std.getOptional(EditorSettingProvider)?.setting$;
|
||||
const state = ctx.get('defaultState');
|
||||
const e = state.event as WheelEvent;
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
import { LifeCycleWatcher, WidgetViewExtension } from '@blocksuite/std';
|
||||
import { LifeCycleWatcher } from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { NOTE_SLICER_WIDGET } from './components/note-slicer/index.js';
|
||||
import { EDGELESS_DRAGGING_AREA_WIDGET } from './components/rects/edgeless-dragging-area-rect.js';
|
||||
import { EDGELESS_SELECTED_RECT_WIDGET } from './components/rects/edgeless-selected-rect.js';
|
||||
|
||||
export const edgelessDraggingAreaWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
EDGELESS_DRAGGING_AREA_WIDGET,
|
||||
literal`${unsafeStatic(EDGELESS_DRAGGING_AREA_WIDGET)}`
|
||||
);
|
||||
export const noteSlicerWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
NOTE_SLICER_WIDGET,
|
||||
literal`${unsafeStatic(NOTE_SLICER_WIDGET)}`
|
||||
);
|
||||
export const edgelessSelectedRectWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
EDGELESS_SELECTED_RECT_WIDGET,
|
||||
literal`${unsafeStatic(EDGELESS_SELECTED_RECT_WIDGET)}`
|
||||
);
|
||||
|
||||
export class EdgelessLocker extends LifeCycleWatcher {
|
||||
static override key = 'edgeless-locker';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export { EdgelessRootPreviewBlockComponent } from '../preview/edgeless-root-preview-block';
|
||||
export * from './clipboard/clipboard';
|
||||
export * from './clipboard/command';
|
||||
export * from './edgeless-root-block.js';
|
||||
export { EdgelessRootPreviewBlockComponent } from './edgeless-root-preview-block.js';
|
||||
export { EdgelessRootService } from './edgeless-root-service.js';
|
||||
export * from './utils/clipboard-utils.js';
|
||||
export { sortEdgelessElements } from './utils/clone-utils.js';
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
import { EdgelessAutoCompletePanel } from './edgeless/components/auto-complete/auto-complete-panel.js';
|
||||
import { EdgelessAutoComplete } from './edgeless/components/auto-complete/edgeless-auto-complete.js';
|
||||
import {
|
||||
NOTE_SLICER_WIDGET,
|
||||
NoteSlicer,
|
||||
} from './edgeless/components/note-slicer/index.js';
|
||||
import {
|
||||
EDGELESS_DRAGGING_AREA_WIDGET,
|
||||
EdgelessDraggingAreaRectWidget,
|
||||
} from './edgeless/components/rects/edgeless-dragging-area-rect.js';
|
||||
import {
|
||||
EDGELESS_SELECTED_RECT_WIDGET,
|
||||
EdgelessSelectedRectWidget,
|
||||
} from './edgeless/components/rects/edgeless-selected-rect.js';
|
||||
import {
|
||||
EdgelessRootBlockComponent,
|
||||
EdgelessRootPreviewBlockComponent,
|
||||
@@ -22,7 +8,6 @@ import {
|
||||
export function effects() {
|
||||
// Register components by category
|
||||
registerRootComponents();
|
||||
registerMiscComponents();
|
||||
}
|
||||
|
||||
function registerRootComponents() {
|
||||
@@ -35,37 +20,9 @@ function registerRootComponents() {
|
||||
);
|
||||
}
|
||||
|
||||
function registerMiscComponents() {
|
||||
// Auto-complete components
|
||||
customElements.define(
|
||||
'edgeless-auto-complete-panel',
|
||||
EdgelessAutoCompletePanel
|
||||
);
|
||||
customElements.define('edgeless-auto-complete', EdgelessAutoComplete);
|
||||
|
||||
// Note and template components
|
||||
customElements.define(NOTE_SLICER_WIDGET, NoteSlicer);
|
||||
|
||||
// Dragging area components
|
||||
customElements.define(
|
||||
EDGELESS_DRAGGING_AREA_WIDGET,
|
||||
EdgelessDraggingAreaRectWidget
|
||||
);
|
||||
customElements.define(
|
||||
EDGELESS_SELECTED_RECT_WIDGET,
|
||||
EdgelessSelectedRectWidget
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-edgeless-root': EdgelessRootBlockComponent;
|
||||
'affine-edgeless-root-preview': EdgelessRootPreviewBlockComponent;
|
||||
'edgeless-auto-complete-panel': EdgelessAutoCompletePanel;
|
||||
'edgeless-auto-complete': EdgelessAutoComplete;
|
||||
'note-slicer': NoteSlicer;
|
||||
'edgeless-dragging-area-rect': EdgelessDraggingAreaRectWidget;
|
||||
'edgeless-selected-rect': EdgelessSelectedRectWidget;
|
||||
'affine-page-root': PageRootBlockComponent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,13 +25,9 @@ import { css, html } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { EdgelessRootService } from './edgeless-root-service.js';
|
||||
import { isCanvasElement } from './utils/query.js';
|
||||
import { isCanvasElement } from '../edgeless/utils/query';
|
||||
|
||||
export class EdgelessRootPreviewBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootService
|
||||
> {
|
||||
export class EdgelessRootPreviewBlockComponent extends BlockComponent<RootBlockModel> {
|
||||
static override styles = css`
|
||||
affine-edgeless-root-preview {
|
||||
pointer-events: none;
|
||||
@@ -66,9 +62,13 @@ export class EdgelessRootPreviewBlockComponent extends BlockComponent<
|
||||
}
|
||||
`;
|
||||
|
||||
private get _viewport() {
|
||||
return this._gfx.viewport;
|
||||
}
|
||||
|
||||
private readonly _refreshLayerViewport = requestThrottledConnectedFrame(
|
||||
() => {
|
||||
const { zoom, translateX, translateY } = this.service.viewport;
|
||||
const { zoom, translateX, translateY } = this._viewport;
|
||||
const gap = getBgGridGap(zoom);
|
||||
|
||||
this.backgroundStyle = {
|
||||
@@ -81,10 +81,6 @@ export class EdgelessRootPreviewBlockComponent extends BlockComponent<
|
||||
|
||||
private _resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
get dispatcher() {
|
||||
return this.service?.uiEventDispatcher;
|
||||
}
|
||||
|
||||
private get _gfx() {
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
@@ -18,12 +18,7 @@ import { PageClipboard, ReadOnlyClipboard } from './clipboard';
|
||||
import { builtinToolbarConfig } from './configs/toolbar';
|
||||
import { EdgelessClipboardController, EdgelessRootService } from './edgeless';
|
||||
import { EdgelessElementToolbarExtension } from './edgeless/configs/toolbar';
|
||||
import {
|
||||
edgelessDraggingAreaWidget,
|
||||
EdgelessLocker,
|
||||
edgelessSelectedRectWidget,
|
||||
noteSlicerWidget,
|
||||
} from './edgeless/edgeless-root-spec';
|
||||
import { EdgelessLocker } from './edgeless/edgeless-root-spec';
|
||||
import { AltCloneExtension } from './edgeless/interact-extensions/clone-ext';
|
||||
import { effects } from './effects';
|
||||
import { fallbackKeymap } from './keyboard/keymap';
|
||||
@@ -90,9 +85,6 @@ export class RootViewExtension extends ViewExtensionProvider {
|
||||
}
|
||||
context.register([
|
||||
BlockViewExtension('affine:page', literal`affine-edgeless-root`),
|
||||
edgelessDraggingAreaWidget,
|
||||
noteSlicerWidget,
|
||||
edgelessSelectedRectWidget,
|
||||
EdgelessClipboardController,
|
||||
AltCloneExtension,
|
||||
]);
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
{ "path": "../database" },
|
||||
{ "path": "../edgeless-text" },
|
||||
{ "path": "../embed" },
|
||||
{ "path": "../embed-doc" },
|
||||
{ "path": "../frame" },
|
||||
{ "path": "../image" },
|
||||
{ "path": "../note" },
|
||||
@@ -32,6 +31,7 @@
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../widgets/edgeless-selected-rect" },
|
||||
{ "path": "../../widgets/edgeless-toolbar" },
|
||||
{ "path": "../../data-view" },
|
||||
{ "path": "../../../framework/global" },
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@ export const SurfaceBlockSchema = defineBlockSchema({
|
||||
'affine:attachment',
|
||||
'affine:embed-*',
|
||||
'affine:edgeless-text',
|
||||
'affine:code',
|
||||
],
|
||||
},
|
||||
transformer: transformerConfigs =>
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
173
blocksuite/affine/components/src/icons/file-icons-rc.ts
Normal file
173
blocksuite/affine/components/src/icons/file-icons-rc.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
FileIconAepIcon,
|
||||
FileIconAiIcon,
|
||||
FileIconAviIcon,
|
||||
FileIconCssIcon,
|
||||
FileIconCsvIcon,
|
||||
FileIconDmgIcon,
|
||||
FileIconDocIcon,
|
||||
FileIconDocxIcon,
|
||||
FileIconEpsIcon,
|
||||
FileIconExeIcon,
|
||||
FileIconFigIcon,
|
||||
FileIconGifIcon,
|
||||
FileIconHtmlIcon,
|
||||
FileIconInddIcon,
|
||||
FileIconJavaIcon,
|
||||
FileIconJpegIcon,
|
||||
FileIconJsIcon,
|
||||
FileIconJsonIcon,
|
||||
FileIconMkvIcon,
|
||||
FileIconMp3Icon,
|
||||
FileIconMp4Icon,
|
||||
FileIconMpegIcon,
|
||||
FileIconNoneIcon,
|
||||
FileIconPdfIcon,
|
||||
FileIconPngIcon,
|
||||
FileIconPptIcon,
|
||||
FileIconPptxIcon,
|
||||
FileIconPsdIcon,
|
||||
FileIconRarIcon,
|
||||
FileIconRssIcon,
|
||||
FileIconSqlIcon,
|
||||
FileIconSvgIcon,
|
||||
FileIconTiffIcon,
|
||||
FileIconTxtIcon,
|
||||
FileIconWavIcon,
|
||||
FileIconWebpIcon,
|
||||
FileIconXlsIcon,
|
||||
FileIconXlsxIcon,
|
||||
FileIconXmlIcon,
|
||||
FileIconZipIcon,
|
||||
ImageIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
|
||||
export function getAttachmentFileIconRC(filetype: string) {
|
||||
switch (filetype) {
|
||||
case 'img':
|
||||
return ImageIcon;
|
||||
case 'image/jpeg':
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
return FileIconJpegIcon;
|
||||
case 'image/png':
|
||||
case 'png':
|
||||
return FileIconPngIcon;
|
||||
case 'image/webp':
|
||||
case 'webp':
|
||||
return FileIconWebpIcon;
|
||||
case 'image/tiff':
|
||||
case 'tiff':
|
||||
return FileIconTiffIcon;
|
||||
case 'image/gif':
|
||||
case 'gif':
|
||||
return FileIconGifIcon;
|
||||
case 'image/svg':
|
||||
case 'svg':
|
||||
return FileIconSvgIcon;
|
||||
case 'image/eps':
|
||||
case 'eps':
|
||||
return FileIconEpsIcon;
|
||||
case 'application/pdf':
|
||||
case 'pdf':
|
||||
return FileIconPdfIcon;
|
||||
case 'application/msword':
|
||||
case 'application/x-iwork-pages-sffpages':
|
||||
case 'doc':
|
||||
return FileIconDocIcon;
|
||||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||
case 'docx':
|
||||
return FileIconDocxIcon;
|
||||
case 'text/plain':
|
||||
case 'txt':
|
||||
return FileIconTxtIcon;
|
||||
case 'csv':
|
||||
return FileIconCsvIcon;
|
||||
case 'application/vnd.ms-excel':
|
||||
case 'xls':
|
||||
return FileIconXlsIcon;
|
||||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||
case 'application/x-iwork-numbers-sffnumbers':
|
||||
case 'xlsx':
|
||||
return FileIconXlsxIcon;
|
||||
case 'application/vnd.ms-powerpoint':
|
||||
case 'application/x-iwork-keynote-sffkeynote':
|
||||
case 'ppt':
|
||||
return FileIconPptIcon;
|
||||
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
||||
case 'pptx':
|
||||
return FileIconPptxIcon;
|
||||
case 'application/illustrator':
|
||||
case 'fig':
|
||||
return FileIconFigIcon;
|
||||
case 'application/postscript':
|
||||
case 'ai':
|
||||
return FileIconAiIcon;
|
||||
case 'application/vnd.adobe.photoshop':
|
||||
case 'psd':
|
||||
return FileIconPsdIcon;
|
||||
case 'application/vnd.adobe.indesign':
|
||||
case 'indd':
|
||||
return FileIconInddIcon;
|
||||
case 'application/vnd.adobe.afterfx':
|
||||
case 'aep':
|
||||
return FileIconAepIcon;
|
||||
case 'audio/mpeg':
|
||||
case 'audio/mp3':
|
||||
case 'mp3':
|
||||
return FileIconMp3Icon;
|
||||
case 'audio/wav':
|
||||
case 'wav':
|
||||
return FileIconWavIcon;
|
||||
case 'video/mpeg':
|
||||
case 'mpeg':
|
||||
return FileIconMpegIcon;
|
||||
case 'video/mp4':
|
||||
case 'mp4':
|
||||
return FileIconMp4Icon;
|
||||
case 'video/avi':
|
||||
case 'avi':
|
||||
return FileIconAviIcon;
|
||||
case 'video/mkv':
|
||||
case 'mkv':
|
||||
return FileIconMkvIcon;
|
||||
case 'text/html':
|
||||
case 'html':
|
||||
return FileIconHtmlIcon;
|
||||
case 'text/css':
|
||||
case 'css':
|
||||
return FileIconCssIcon;
|
||||
case 'application/rss+xml':
|
||||
case 'rss':
|
||||
return FileIconRssIcon;
|
||||
case 'application/sql':
|
||||
case 'sql':
|
||||
return FileIconSqlIcon;
|
||||
case 'application/javascript':
|
||||
case 'js':
|
||||
return FileIconJsIcon;
|
||||
case 'application/json':
|
||||
case 'json':
|
||||
return FileIconJsonIcon;
|
||||
case 'application/java':
|
||||
case 'java':
|
||||
return FileIconJavaIcon;
|
||||
case 'application/xml':
|
||||
case 'xml':
|
||||
return FileIconXmlIcon;
|
||||
case 'application/x-msdos-program':
|
||||
case 'exe':
|
||||
return FileIconExeIcon;
|
||||
case 'application/x-apple-diskimage':
|
||||
case 'dmg':
|
||||
return FileIconDmgIcon;
|
||||
case 'application/zip':
|
||||
case 'zip':
|
||||
return FileIconZipIcon;
|
||||
case 'application/x-rar-compressed':
|
||||
case 'rar':
|
||||
return FileIconRarIcon;
|
||||
default:
|
||||
return FileIconNoneIcon;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './ai.js';
|
||||
export * from './file-icons.js';
|
||||
export * from './file-icons-rc';
|
||||
export * from './import-export.js';
|
||||
export * from './list.js';
|
||||
export * from './loading.js';
|
||||
|
||||
@@ -35,6 +35,7 @@ export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
|
||||
export class ResourceController implements Disposable {
|
||||
readonly blobUrl$ = signal<string | null>(null);
|
||||
|
||||
// TODO(@fundon): default `loading` status.
|
||||
readonly state$ = signal<Partial<BlobState>>({});
|
||||
|
||||
readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -36,6 +36,8 @@ export class ConnectorFilter extends InteractivityExtension {
|
||||
elements.sort((a, _) => (a instanceof ConnectorElementModel ? -1 : 1));
|
||||
}
|
||||
|
||||
context.elements = elements;
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,6 +188,8 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
|
||||
});
|
||||
this._resizeObserver.observe(this.richText);
|
||||
|
||||
this.connector.stash('labelXYWH');
|
||||
|
||||
this.updateComplete
|
||||
.then(() => {
|
||||
if (!this.inlineEditor) return;
|
||||
@@ -257,7 +259,8 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
|
||||
}
|
||||
}
|
||||
|
||||
connector.lableEditing = false;
|
||||
connector.labelEditing = false;
|
||||
connector.pop('labelXYWH');
|
||||
|
||||
selection.set({
|
||||
elements: [],
|
||||
@@ -293,7 +296,7 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
|
||||
}
|
||||
);
|
||||
|
||||
connector.lableEditing = true;
|
||||
connector.labelEditing = true;
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
@@ -142,6 +142,8 @@ export class ConnectorElementView extends GfxElementModelView<ConnectorElementMo
|
||||
if (!curLabelElement) {
|
||||
curLabelElement = labelElement;
|
||||
|
||||
labelElement.id = `#${this.model.id}-label`;
|
||||
labelElement.creator = this.model;
|
||||
labelElement.fillColor = 'transparent';
|
||||
labelElement.strokeColor = 'transparent';
|
||||
labelElement.strokeWidth = 0;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { on } from '@blocksuite/affine-shared/utils';
|
||||
import type { PointerEventState } from '@blocksuite/std';
|
||||
import { BaseTool, MouseButton } from '@blocksuite/std/gfx';
|
||||
import { BaseTool, MouseButton, type ToolOptions } from '@blocksuite/std/gfx';
|
||||
import { Signal } from '@preact/signals-core';
|
||||
|
||||
interface RestorablePresentToolOptions {
|
||||
mode?: string; // 'fit' | 'fill', simplified to string for local use
|
||||
restoredAfterPan?: boolean;
|
||||
}
|
||||
|
||||
export type PanToolOption = {
|
||||
panning: boolean;
|
||||
};
|
||||
@@ -53,14 +58,30 @@ export class PanTool extends BaseTool<PanToolOption> {
|
||||
|
||||
evt.raw.preventDefault();
|
||||
|
||||
const selection = this.gfx.selection.surfaceSelections;
|
||||
const currentTool = this.controller.currentToolOption$.peek();
|
||||
const restoreToPrevious = () => {
|
||||
const { toolType, options } = currentTool;
|
||||
if (toolType && options) {
|
||||
this.controller.setTool(toolType, options);
|
||||
this.gfx.selection.set(selection);
|
||||
const { toolType, options: originalToolOptions } = currentTool;
|
||||
const selectionToRestore = this.gfx.selection.surfaceSelections;
|
||||
if (!toolType) return;
|
||||
|
||||
let finalOptions: ToolOptions<BaseTool<any>> | undefined =
|
||||
originalToolOptions;
|
||||
const PRESENT_TOOL_NAME = 'frameNavigator';
|
||||
|
||||
if (toolType.toolName === PRESENT_TOOL_NAME) {
|
||||
// When restoring PresentTool (frameNavigator) after a temporary pan (e.g., via middle mouse button),
|
||||
// set 'restoredAfterPan' to true. This allows PresentTool to avoid an unwanted viewport reset
|
||||
// and maintain the panned position.
|
||||
const currentPresentOptions = originalToolOptions as
|
||||
| RestorablePresentToolOptions
|
||||
| undefined;
|
||||
finalOptions = {
|
||||
...currentPresentOptions,
|
||||
restoredAfterPan: true,
|
||||
} as RestorablePresentToolOptions;
|
||||
}
|
||||
this.controller.setTool(toolType, finalOptions);
|
||||
this.gfx.selection.set(selectionToRestore);
|
||||
};
|
||||
|
||||
this.controller.setTool(PanTool, {
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -6,10 +6,16 @@ import { manageClassNames, setStyles } from './utils';
|
||||
|
||||
function applyShapeSpecificStyles(
|
||||
model: ShapeElementModel,
|
||||
element: HTMLElement
|
||||
element: HTMLElement,
|
||||
zoom: number
|
||||
) {
|
||||
if (model.shapeType === 'rect') {
|
||||
element.style.borderRadius = `${model.radius ?? 0}px`;
|
||||
const w = model.w * zoom;
|
||||
const h = model.h * zoom;
|
||||
const r = model.radius ?? 0;
|
||||
const borderRadius =
|
||||
r < 1 ? `${Math.min(w * r, h * r)}px` : `${r * zoom}px`;
|
||||
element.style.borderRadius = borderRadius;
|
||||
} else if (model.shapeType === 'ellipse') {
|
||||
element.style.borderRadius = '50%';
|
||||
} else {
|
||||
@@ -20,11 +26,12 @@ function applyShapeSpecificStyles(
|
||||
function applyBorderStyles(
|
||||
model: ShapeElementModel,
|
||||
element: HTMLElement,
|
||||
strokeColor: string
|
||||
strokeColor: string,
|
||||
zoom: number
|
||||
) {
|
||||
element.style.border =
|
||||
model.strokeStyle !== 'none'
|
||||
? `${model.strokeWidth}px ${model.strokeStyle === 'dash' ? 'dashed' : 'solid'} ${strokeColor}`
|
||||
? `${model.strokeWidth * zoom}px ${model.strokeStyle === 'dash' ? 'dashed' : 'solid'} ${strokeColor}`
|
||||
: 'none';
|
||||
}
|
||||
|
||||
@@ -85,11 +92,11 @@ export const shapeDomRenderer = (
|
||||
element.style.width = `${model.w * zoom}px`;
|
||||
element.style.height = `${model.h * zoom}px`;
|
||||
|
||||
applyShapeSpecificStyles(model, element);
|
||||
applyShapeSpecificStyles(model, element, zoom);
|
||||
|
||||
element.style.backgroundColor = model.filled ? fillColor : 'transparent';
|
||||
|
||||
applyBorderStyles(model, element, strokeColor);
|
||||
applyBorderStyles(model, element, strokeColor, zoom);
|
||||
applyTransformStyles(model, element);
|
||||
|
||||
element.style.boxSizing = 'border-box';
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
type GfxCommonBlockProps,
|
||||
GfxCompatible,
|
||||
type GfxElementGeometry,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
@@ -13,7 +18,9 @@ type CodeBlockProps = {
|
||||
wrap: boolean;
|
||||
caption: string;
|
||||
preview?: boolean;
|
||||
} & BlockMeta;
|
||||
lineNumber?: boolean;
|
||||
} & BlockMeta &
|
||||
GfxCommonBlockProps;
|
||||
|
||||
export const CodeBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:code',
|
||||
@@ -24,10 +31,15 @@ export const CodeBlockSchema = defineBlockSchema({
|
||||
wrap: false,
|
||||
caption: '',
|
||||
preview: undefined,
|
||||
lineNumber: undefined,
|
||||
'meta:createdAt': undefined,
|
||||
'meta:createdBy': undefined,
|
||||
'meta:updatedAt': undefined,
|
||||
'meta:updatedBy': undefined,
|
||||
xywh: '[0,0,16,16]',
|
||||
index: 'a0',
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
}) as CodeBlockProps,
|
||||
metadata: {
|
||||
version: 1,
|
||||
@@ -37,6 +49,7 @@ export const CodeBlockSchema = defineBlockSchema({
|
||||
'affine:paragraph',
|
||||
'affine:list',
|
||||
'affine:edgeless-text',
|
||||
'affine:surface',
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
@@ -45,4 +58,6 @@ export const CodeBlockSchema = defineBlockSchema({
|
||||
|
||||
export const CodeBlockSchemaExtension = BlockSchemaExtension(CodeBlockSchema);
|
||||
|
||||
export class CodeBlockModel extends BlockModel<CodeBlockProps> {}
|
||||
export class CodeBlockModel
|
||||
extends GfxCompatible<CodeBlockProps>(BlockModel)
|
||||
implements GfxElementGeometry {}
|
||||
|
||||
@@ -11,6 +11,10 @@ export type EmbedSyncedDocBlockProps = {
|
||||
style: EmbedCardStyle;
|
||||
caption?: string | null;
|
||||
scale?: number;
|
||||
/**
|
||||
* Record the scaled height of the synced doc block when it is folded,
|
||||
* a.k.a the fourth number of the `xywh`
|
||||
*/
|
||||
preFoldHeight?: number;
|
||||
} & ReferenceInfo &
|
||||
GfxCompatibleProps;
|
||||
|
||||
@@ -278,7 +278,13 @@ export class ConnectorElementModel extends GfxPrimitiveElementModel<ConnectorEle
|
||||
}
|
||||
|
||||
hasLabel() {
|
||||
return Boolean(!this.lableEditing && this.labelDisplay && this.labelXYWH);
|
||||
return Boolean(
|
||||
!this.labelEditing &&
|
||||
this.labelDisplay &&
|
||||
this.labelXYWH &&
|
||||
this.text &&
|
||||
this.text.length
|
||||
);
|
||||
}
|
||||
|
||||
override includesPoint(
|
||||
@@ -450,7 +456,7 @@ export class ConnectorElementModel extends GfxPrimitiveElementModel<ConnectorEle
|
||||
* Local control display and hide, mainly used in editing scenarios.
|
||||
*/
|
||||
@local()
|
||||
accessor lableEditing: boolean = false;
|
||||
accessor labelEditing: boolean = false;
|
||||
|
||||
@field()
|
||||
accessor mode: ConnectorMode = DEFAULT_CONNECTOR_MODE;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AttachmentBlockSchema } from '@blocksuite/affine-model';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { sha } from '@blocksuite/global/utils';
|
||||
import {
|
||||
type AssetsManager,
|
||||
BaseAdapter,
|
||||
@@ -88,40 +88,49 @@ export class AttachmentAdapter extends BaseAdapter<Attachment> {
|
||||
);
|
||||
}
|
||||
|
||||
override async toSliceSnapshot(
|
||||
payload: AttachmentToSliceSnapshotPayload
|
||||
): Promise<SliceSnapshot | null> {
|
||||
override async toSliceSnapshot({
|
||||
assets,
|
||||
file: files,
|
||||
pageId,
|
||||
workspaceId,
|
||||
}: AttachmentToSliceSnapshotPayload): Promise<SliceSnapshot | null> {
|
||||
if (files.length === 0) return null;
|
||||
|
||||
const content: SliceSnapshot['content'] = [];
|
||||
for (const item of payload.file) {
|
||||
const blobId = await sha(await item.arrayBuffer());
|
||||
payload.assets?.getAssets().set(blobId, item);
|
||||
await payload.assets?.writeToBlob(blobId);
|
||||
const flavour = AttachmentBlockSchema.model.flavour;
|
||||
|
||||
for (const blob of files) {
|
||||
const id = nanoid();
|
||||
const { name, size, type } = blob;
|
||||
|
||||
assets?.uploadingAssetsMap.set(id, {
|
||||
blob,
|
||||
mapInto: sourceId => ({ sourceId }),
|
||||
});
|
||||
|
||||
content.push({
|
||||
type: 'block',
|
||||
flavour: 'affine:attachment',
|
||||
id: nanoid(),
|
||||
flavour,
|
||||
id,
|
||||
props: {
|
||||
name: item.name,
|
||||
size: item.size,
|
||||
type: item.type,
|
||||
name,
|
||||
size,
|
||||
type,
|
||||
embed: false,
|
||||
style: 'horizontalThin',
|
||||
index: 'a0',
|
||||
xywh: '[0,0,0,0]',
|
||||
rotate: 0,
|
||||
sourceId: blobId,
|
||||
},
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
if (content.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'slice',
|
||||
content,
|
||||
workspaceId: payload.workspaceId,
|
||||
pageId: payload.pageId,
|
||||
pageId,
|
||||
workspaceId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ImageBlockSchema } from '@blocksuite/affine-model';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { sha } from '@blocksuite/global/utils';
|
||||
import {
|
||||
type AssetsManager,
|
||||
BaseAdapter,
|
||||
@@ -88,32 +88,40 @@ export class ImageAdapter extends BaseAdapter<Image> {
|
||||
);
|
||||
}
|
||||
|
||||
override async toSliceSnapshot(
|
||||
payload: ImageToSliceSnapshotPayload
|
||||
): Promise<SliceSnapshot | null> {
|
||||
override async toSliceSnapshot({
|
||||
assets,
|
||||
file: files,
|
||||
pageId,
|
||||
workspaceId,
|
||||
}: ImageToSliceSnapshotPayload): Promise<SliceSnapshot | null> {
|
||||
if (files.length === 0) return null;
|
||||
|
||||
const content: SliceSnapshot['content'] = [];
|
||||
for (const item of payload.file) {
|
||||
const blobId = await sha(await item.arrayBuffer());
|
||||
payload.assets?.getAssets().set(blobId, item);
|
||||
await payload.assets?.writeToBlob(blobId);
|
||||
const flavour = ImageBlockSchema.model.flavour;
|
||||
|
||||
for (const blob of files) {
|
||||
const id = nanoid();
|
||||
const { size } = blob;
|
||||
|
||||
assets?.uploadingAssetsMap.set(id, {
|
||||
blob,
|
||||
mapInto: sourceId => ({ sourceId }),
|
||||
});
|
||||
|
||||
content.push({
|
||||
type: 'block',
|
||||
flavour: 'affine:image',
|
||||
id: nanoid(),
|
||||
props: {
|
||||
sourceId: blobId,
|
||||
},
|
||||
flavour,
|
||||
id,
|
||||
props: { size },
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
if (content.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'slice',
|
||||
content,
|
||||
workspaceId: payload.workspaceId,
|
||||
pageId: payload.pageId,
|
||||
pageId,
|
||||
workspaceId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from './proxy';
|
||||
export * from './replace-id';
|
||||
export * from './surface-ref-to-embed';
|
||||
export * from './title';
|
||||
export * from './upload';
|
||||
|
||||
@@ -19,7 +19,7 @@ import { matchModels } from '../../utils';
|
||||
|
||||
export const replaceIdMiddleware =
|
||||
(idGenerator: () => string): TransformerMiddleware =>
|
||||
({ slots, docCRUD }) => {
|
||||
({ slots, docCRUD, assetsManager }) => {
|
||||
const idMap = new Map<string, string>();
|
||||
|
||||
// After Import
|
||||
@@ -169,6 +169,16 @@ export const replaceIdMiddleware =
|
||||
}
|
||||
snapshot.id = newId;
|
||||
|
||||
// Should be re-paired.
|
||||
if (['affine:attachment', 'affine:image'].includes(snapshot.flavour)) {
|
||||
if (!assetsManager.uploadingAssetsMap.has(original)) return;
|
||||
|
||||
const data = assetsManager.uploadingAssetsMap.get(original)!;
|
||||
assetsManager.uploadingAssetsMap.set(newId, data);
|
||||
assetsManager.uploadingAssetsMap.delete(original);
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapshot.flavour === 'affine:surface') {
|
||||
// Generate new IDs for images and frames in advance.
|
||||
snapshot.children.forEach(child => {
|
||||
|
||||
114
blocksuite/affine/shared/src/adapters/middlewares/upload.ts
Normal file
114
blocksuite/affine/shared/src/adapters/middlewares/upload.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { sha } from '@blocksuite/global/utils';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import type {
|
||||
BlockModel,
|
||||
BlockProps,
|
||||
TransformerMiddleware,
|
||||
} from '@blocksuite/store';
|
||||
import { filter, from, map, mergeMap } from 'rxjs';
|
||||
|
||||
const ALLOWED_FLAVOURS = new Set(['affine:attachment', 'affine:image']);
|
||||
|
||||
export const uploadMiddleware = (
|
||||
std: BlockStdScope,
|
||||
concurrent = 5
|
||||
): TransformerMiddleware => {
|
||||
const blockView$ = std.view.viewUpdated.pipe(
|
||||
filter(payload => payload.type === 'block'),
|
||||
filter(payload => ALLOWED_FLAVOURS.has(payload.view.model.flavour))
|
||||
);
|
||||
|
||||
return ({ assetsManager }) => {
|
||||
async function upload(
|
||||
model: BlockModel,
|
||||
{
|
||||
blob,
|
||||
mapInto,
|
||||
abortController,
|
||||
}: {
|
||||
blob: Blob;
|
||||
mapInto: (blobId: string) => Partial<BlockProps>;
|
||||
abortController?: AbortController;
|
||||
}
|
||||
) {
|
||||
if (!abortController) return null;
|
||||
|
||||
const signal = abortController.signal;
|
||||
if (signal.aborted) return null;
|
||||
|
||||
// Double check
|
||||
if (!model.store.hasBlock(model.id)) return null;
|
||||
|
||||
try {
|
||||
signal.throwIfAborted();
|
||||
|
||||
const blobId = await Promise.race([
|
||||
(async function processUpload() {
|
||||
const blobId = await sha(await blob.arrayBuffer());
|
||||
|
||||
assetsManager.getAssets().set(blobId, blob);
|
||||
|
||||
await assetsManager.writeToBlob(blobId);
|
||||
|
||||
return await new Promise<string | null>(resolve => {
|
||||
model.store.withoutTransact(() => {
|
||||
if (signal.aborted) return resolve(null);
|
||||
|
||||
model.store.updateBlock(model, mapInto(blobId));
|
||||
|
||||
resolve(blobId);
|
||||
});
|
||||
});
|
||||
})(),
|
||||
// If the signal is not aborted, it will be in the pending state.
|
||||
new Promise<null>(resolve => {
|
||||
signal.addEventListener('abort', () => resolve(null), {
|
||||
once: true,
|
||||
});
|
||||
if (signal.aborted) {
|
||||
resolve(null);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
return blobId;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
blockView$
|
||||
.pipe(
|
||||
map(payload => {
|
||||
if (assetsManager.uploadingAssetsMap.size === 0) return null;
|
||||
|
||||
const model = payload.view.model;
|
||||
if (!assetsManager.uploadingAssetsMap.has(model.id)) return null;
|
||||
|
||||
const state = assetsManager.uploadingAssetsMap.get(model.id)!;
|
||||
|
||||
if (payload.method === 'add') {
|
||||
state.abortController = new AbortController();
|
||||
return { model, state };
|
||||
} else {
|
||||
state.abortController?.abort();
|
||||
assetsManager.uploadingAssetsMap.delete(model.id);
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
filter(Boolean),
|
||||
mergeMap(
|
||||
({ model, state }) =>
|
||||
from(
|
||||
upload(model, state).then(() => {
|
||||
assetsManager.uploadingAssetsMap.delete(model.id);
|
||||
})
|
||||
),
|
||||
concurrent
|
||||
)
|
||||
)
|
||||
.subscribe();
|
||||
};
|
||||
};
|
||||
@@ -41,6 +41,7 @@ const LocalPropsSchema = z.object({
|
||||
presentBlackBackground: z.boolean(),
|
||||
presentFillScreen: z.boolean(),
|
||||
presentHideToolbar: z.boolean(),
|
||||
presentNoFrameToastShown: z.boolean(),
|
||||
|
||||
autoHideEmbedHTMLFullScreenToolbar: z.boolean(),
|
||||
});
|
||||
@@ -126,6 +127,8 @@ export class EditPropsStore extends LifeCycleWatcher {
|
||||
return 'blocksuite:presentation:fillScreen';
|
||||
case 'presentHideToolbar':
|
||||
return 'blocksuite:presentation:hideToolbar';
|
||||
case 'presentNoFrameToastShown':
|
||||
return 'blocksuite:presentation:noFrameToastShown';
|
||||
case 'templateCache':
|
||||
return 'blocksuite:' + id + ':templateTool';
|
||||
case 'remoteColor':
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user