mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-05 00:54:56 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ec7de7e32 | ||
|
|
e5e5c0a8ba | ||
|
|
c644a46b8d | ||
|
|
7e892b3a7e | ||
|
|
848145150d | ||
|
|
dee6be11fb | ||
|
|
abda70d2c8 | ||
|
|
40104f2f87 | ||
|
|
162b7adc1b | ||
|
|
6289981fd1 | ||
|
|
0e581c915c | ||
|
|
59a791fe1f | ||
|
|
378bb3795d | ||
|
|
60b994f38b | ||
|
|
1b2a4377fd | ||
|
|
8b4175c44d | ||
|
|
da7ab51e2d | ||
|
|
a59e640423 | ||
|
|
9bb74bce6b | ||
|
|
a0a97d0751 | ||
|
|
b9e3fc54fd | ||
|
|
b71fe291d1 | ||
|
|
f02b57d58b | ||
|
|
2e0f0c624a | ||
|
|
9435118ef1 | ||
|
|
67889d9364 | ||
|
|
5fe4b2b3e4 | ||
|
|
2d41c2ff8d |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1 +1,2 @@
|
||||
/blocksuite/ @toeverything/blocksuite-core
|
||||
/packages/frontend/core/src/blocksuite @toeverything/blocksuite-core
|
||||
|
||||
2
.github/helm/affine/Chart.yaml
vendored
2
.github/helm/affine/Chart.yaml
vendored
@@ -3,4 +3,4 @@ name: affine
|
||||
description: AFFiNE cloud chart
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.19.0"
|
||||
appVersion: "0.20.0"
|
||||
|
||||
@@ -3,7 +3,7 @@ name: graphql
|
||||
description: AFFiNE GraphQL server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.19.0"
|
||||
appVersion: "0.20.0"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
2
.github/helm/affine/charts/sync/Chart.yaml
vendored
2
.github/helm/affine/charts/sync/Chart.yaml
vendored
@@ -3,7 +3,7 @@ name: sync
|
||||
description: AFFiNE Sync Server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.19.0"
|
||||
appVersion: "0.20.0"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -98,5 +98,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"file-type": "^20.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"zod": "^3.23.8"
|
||||
@@ -40,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -39,5 +39,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"zod": "^3.23.8"
|
||||
@@ -40,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"yjs": "^13.6.21",
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"file-type": "^20.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/katex": "^0.16.7",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"katex": "^0.16.11",
|
||||
@@ -43,5 +43,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"lit": "^3.2.0",
|
||||
@@ -43,5 +43,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -39,5 +39,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
@@ -69,5 +69,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -6,19 +6,19 @@ import {
|
||||
PageViewportServiceExtension,
|
||||
ThemeService,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { dragHandleWidget } from '@blocksuite/affine-widget-drag-handle';
|
||||
import { docRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection';
|
||||
import { scrollAnchoringWidget } from '@blocksuite/affine-widget-scroll-anchoring';
|
||||
import { FlavourExtension } from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { RootBlockAdapterExtensions } from '../adapters/extension';
|
||||
import {
|
||||
docRemoteSelectionWidget,
|
||||
dragHandleWidget,
|
||||
embedCardToolbarWidget,
|
||||
formatBarWidget,
|
||||
innerModalWidget,
|
||||
linkedDocWidget,
|
||||
modalWidget,
|
||||
scrollAnchoringWidget,
|
||||
slashMenuWidget,
|
||||
viewportOverlayWidget,
|
||||
} from './widgets';
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { AFFINE_DRAG_HANDLE_WIDGET } from '@blocksuite/affine-widget-drag-handle';
|
||||
import { AFFINE_DOC_REMOTE_SELECTION_WIDGET } from '@blocksuite/affine-widget-remote-selection';
|
||||
import { AFFINE_SCROLL_ANCHORING_WIDGET } from '@blocksuite/affine-widget-scroll-anchoring';
|
||||
import { WidgetViewExtension } from '@blocksuite/block-std';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
@@ -32,11 +29,6 @@ export const linkedDocWidget = WidgetViewExtension(
|
||||
AFFINE_LINKED_DOC_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_LINKED_DOC_WIDGET)}`
|
||||
);
|
||||
export const dragHandleWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_DRAG_HANDLE_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_DRAG_HANDLE_WIDGET)}`
|
||||
);
|
||||
export const embedCardToolbarWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_EMBED_CARD_TOOLBAR_WIDGET,
|
||||
@@ -47,18 +39,8 @@ export const formatBarWidget = WidgetViewExtension(
|
||||
AFFINE_FORMAT_BAR_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_FORMAT_BAR_WIDGET)}`
|
||||
);
|
||||
export const docRemoteSelectionWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_DOC_REMOTE_SELECTION_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_DOC_REMOTE_SELECTION_WIDGET)}`
|
||||
);
|
||||
export const viewportOverlayWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_VIEWPORT_OVERLAY_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_VIEWPORT_OVERLAY_WIDGET)}`
|
||||
);
|
||||
export const scrollAnchoringWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_SCROLL_ANCHORING_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_SCROLL_ANCHORING_WIDGET)}`
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AFFINE_EDGELESS_AUTO_CONNECT_WIDGET } from '@blocksuite/affine-widget-edgeless-auto-connect';
|
||||
import { AFFINE_FRAME_TITLE_WIDGET } from '@blocksuite/affine-widget-frame-title';
|
||||
import { AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET } from '@blocksuite/affine-widget-remote-selection';
|
||||
import { autoConnectWidget } from '@blocksuite/affine-widget-edgeless-auto-connect';
|
||||
import { frameTitleWidget } from '@blocksuite/affine-widget-frame-title';
|
||||
import { edgelessRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection';
|
||||
import {
|
||||
BlockServiceWatcher,
|
||||
BlockViewExtension,
|
||||
@@ -20,31 +20,16 @@ import { EDGELESS_SELECTED_RECT_WIDGET } from './components/rects/edgeless-selec
|
||||
import { EDGELESS_TOOLBAR_WIDGET } from './components/toolbar/edgeless-toolbar.js';
|
||||
import { EdgelessRootService } from './edgeless-root-service.js';
|
||||
|
||||
export const edgelessRemoteSelectionWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET)}`
|
||||
);
|
||||
export const edgelessZoomToolbarWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET)}`
|
||||
);
|
||||
export const frameTitleWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_FRAME_TITLE_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_FRAME_TITLE_WIDGET)}`
|
||||
);
|
||||
export const elementToolbarWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
EDGELESS_ELEMENT_TOOLBAR_WIDGET,
|
||||
literal`${unsafeStatic(EDGELESS_ELEMENT_TOOLBAR_WIDGET)}`
|
||||
);
|
||||
export const autoConnectWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_EDGELESS_AUTO_CONNECT_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_EDGELESS_AUTO_CONNECT_WIDGET)}`
|
||||
);
|
||||
export const edgelessDraggingAreaWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
EDGELESS_DRAGGING_AREA_WIDGET,
|
||||
|
||||
@@ -29,7 +29,10 @@ async function exportDocs(collection: Workspace, docs: Store[]) {
|
||||
snapshots
|
||||
.filter((snapshot): snapshot is DocSnapshot => !!snapshot)
|
||||
.map(async snapshot => {
|
||||
const snapshotName = `${snapshot.meta.title || 'untitled'}.snapshot.json`;
|
||||
// Use the title and id as the snapshot file name
|
||||
const title = snapshot.meta.title || 'untitled';
|
||||
const id = snapshot.meta.id;
|
||||
const snapshotName = `${title}-${id}.snapshot.json`;
|
||||
await zip.file(snapshotName, JSON.stringify(snapshot, null, 2));
|
||||
})
|
||||
);
|
||||
@@ -63,6 +66,7 @@ async function exportDocs(collection: Workspace, docs: Store[]) {
|
||||
}
|
||||
|
||||
const downloadBlob = await zip.generate();
|
||||
// Use the collection id as the zip file name
|
||||
return download(downloadBlob, `${collection.id}.bs.zip`);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
"lit": "^3.2.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lit": "^3.2.0",
|
||||
@@ -46,5 +46,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { SurfaceBlockProps } from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
SURFACE_TEXT_UNIQ_IDENTIFIER,
|
||||
SURFACE_YMAP_UNIQ_IDENTIFIER,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type {
|
||||
FromSnapshotPayload,
|
||||
SnapshotNode,
|
||||
@@ -7,10 +11,6 @@ import type {
|
||||
import { BaseBlockTransformer } from '@blocksuite/store';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
const SURFACE_TEXT_UNIQ_IDENTIFIER = 'affine:surface:text';
|
||||
// Used for group children field
|
||||
const SURFACE_YMAP_UNIQ_IDENTIFIER = 'affine:surface:ymap';
|
||||
|
||||
export class SurfaceBlockTransformer extends BaseBlockTransformer<SurfaceBlockProps> {
|
||||
private _elementToJSON(element: Y.Map<unknown>) {
|
||||
const value: Record<string, unknown> = {};
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lottiefiles/dotlottie-wc": "^0.4.0",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"collapse-white-space": "^2.1.0",
|
||||
@@ -74,5 +74,5 @@
|
||||
"@types/katex": "^0.16.7",
|
||||
"@types/lodash.clonedeep": "^4.5.9"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EditorHost } from '@blocksuite/block-std';
|
||||
import { type EditorHost, TextSelection } from '@blocksuite/block-std';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import {
|
||||
@@ -26,6 +26,7 @@ export interface TextFormatConfig {
|
||||
hotkey?: string;
|
||||
activeWhen: (host: EditorHost) => boolean;
|
||||
action: (host: EditorHost) => void;
|
||||
textChecker?: (host: EditorHost) => boolean;
|
||||
}
|
||||
|
||||
export const textFormatConfigs: TextFormatConfig[] = [
|
||||
@@ -124,5 +125,14 @@ export const textFormatConfigs: TextFormatConfig[] = [
|
||||
action: host => {
|
||||
host.std.command.chain().pipe(toggleLink).run();
|
||||
},
|
||||
// should check text length
|
||||
textChecker: host => {
|
||||
const textSelection = host.std.selection.find(TextSelection);
|
||||
if (!textSelection || textSelection.isCollapsed()) return false;
|
||||
|
||||
return Boolean(
|
||||
textSelection.from.length + (textSelection.to?.length ?? 0)
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface FootNoteNodeConfig {
|
||||
customPopupRenderer?: FootNotePopupRenderer;
|
||||
interactive?: boolean;
|
||||
hidePopup?: boolean;
|
||||
disableHoverEffect?: boolean;
|
||||
onPopupClick?: FootNotePopupClickHandler;
|
||||
}
|
||||
|
||||
@@ -33,7 +34,9 @@ export class FootNoteNodeConfigProvider {
|
||||
private _customPopupRenderer?: FootNotePopupRenderer;
|
||||
private _hidePopup: boolean;
|
||||
private _interactive: boolean;
|
||||
private _disableHoverEffect: boolean;
|
||||
private _onPopupClick?: FootNotePopupClickHandler;
|
||||
|
||||
get customNodeRenderer() {
|
||||
return this._customNodeRenderer;
|
||||
}
|
||||
@@ -58,6 +61,10 @@ export class FootNoteNodeConfigProvider {
|
||||
return this._interactive;
|
||||
}
|
||||
|
||||
get disableHoverEffect() {
|
||||
return this._disableHoverEffect;
|
||||
}
|
||||
|
||||
constructor(
|
||||
config: FootNoteNodeConfig,
|
||||
readonly std: BlockStdScope
|
||||
@@ -66,6 +73,7 @@ export class FootNoteNodeConfigProvider {
|
||||
this._customPopupRenderer = config.customPopupRenderer;
|
||||
this._hidePopup = config.hidePopup ?? false;
|
||||
this._interactive = config.interactive ?? true;
|
||||
this._disableHoverEffect = config.disableHoverEffect ?? false;
|
||||
this._onPopupClick = config.onPopupClick;
|
||||
}
|
||||
|
||||
@@ -85,6 +93,10 @@ export class FootNoteNodeConfigProvider {
|
||||
this._interactive = interactive;
|
||||
}
|
||||
|
||||
setDisableHoverEffect(disableHoverEffect: boolean) {
|
||||
this._disableHoverEffect = disableHoverEffect;
|
||||
}
|
||||
|
||||
setPopupClick(onPopupClick: FootNotePopupClickHandler) {
|
||||
this._onPopupClick = onPopupClick;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { shift } from '@floating-ui/dom';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, nothing, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit-html/directives/class-map.js';
|
||||
import { ref } from 'lit-html/directives/ref.js';
|
||||
|
||||
import { HoverController } from '../../../../../hover/controller';
|
||||
@@ -37,7 +38,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
.footnote-content-default {
|
||||
display: inline-block;
|
||||
background: ${unsafeCSSVarV2('button/primary')};
|
||||
background: ${unsafeCSSVarV2('block/footnote/numberBgHover')};
|
||||
color: ${unsafeCSSVarV2('button/pureWhiteText')};
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
@@ -48,6 +49,21 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
transition: background 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.footnote-node.hover-effect {
|
||||
.footnote-content-default {
|
||||
color: var(--affine-text-primary-color);
|
||||
background: ${unsafeCSSVarV2('block/footnote/numberBg')};
|
||||
}
|
||||
}
|
||||
|
||||
.footnote-node.hover-effect:hover {
|
||||
.footnote-content-default {
|
||||
color: ${unsafeCSSVarV2('button/pureWhiteText')};
|
||||
background: ${unsafeCSSVarV2('block/footnote/numberBgHover')};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -67,6 +83,10 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
return this.config?.hidePopup;
|
||||
}
|
||||
|
||||
get disableHoverEffect() {
|
||||
return this.config?.disableHoverEffect;
|
||||
}
|
||||
|
||||
get onPopupClick() {
|
||||
return this.config?.onPopupClick;
|
||||
}
|
||||
@@ -142,7 +162,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
},
|
||||
};
|
||||
},
|
||||
{ enterDelay: 500 }
|
||||
{ enterDelay: 300 }
|
||||
);
|
||||
|
||||
override render() {
|
||||
@@ -156,9 +176,14 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
? this.customNodeRenderer(footnote, this.std)
|
||||
: this._FootNoteDefaultContent(footnote);
|
||||
|
||||
const nodeClasses = classMap({
|
||||
'footnote-node': true,
|
||||
'hover-effect': !this.disableHoverEffect,
|
||||
});
|
||||
|
||||
return html`<span
|
||||
${this.hidePopup ? '' : ref(this._whenHover.setReference)}
|
||||
class="footnote-node"
|
||||
class=${nodeClasses}
|
||||
>${node}<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
|
||||
></span>`;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export class FootNotePopupChip extends LitElement {
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
transition: width 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.prefix-icon,
|
||||
|
||||
@@ -26,7 +26,6 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
.footnote-popup-container {
|
||||
border-radius: 4px;
|
||||
box-shadow: ${unsafeCSSVar('overlayPanelShadow')};
|
||||
border-radius: 4px;
|
||||
background-color: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
}
|
||||
|
||||
@@ -20,8 +20,14 @@ export const textFormatKeymap = (std: BlockStdScope) =>
|
||||
const textSelection = selection.find(TextSelection);
|
||||
if (!textSelection) return;
|
||||
|
||||
const allowed = config.textChecker?.(std.host) ?? true;
|
||||
if (!allowed) return;
|
||||
|
||||
const event = ctx.get('keyboardState').raw;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
config.action(std.host);
|
||||
ctx.get('keyboardState').raw.preventDefault();
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"yjs": "^13.6.21",
|
||||
@@ -43,5 +43,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"zod": "^3.23.8"
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/inline": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
@@ -31,5 +31,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -63,10 +63,6 @@ export class BrushElementModel extends GfxPrimitiveElementModel<BrushProps> {
|
||||
return 'brush';
|
||||
}
|
||||
|
||||
static override propsToY(props: BrushProps) {
|
||||
return props;
|
||||
}
|
||||
|
||||
override containsBound(bounds: Bound) {
|
||||
const points = getPointsFromBoundWithRotation(this);
|
||||
return points.some(point => bounds.containsPoint(point));
|
||||
|
||||
@@ -125,8 +125,8 @@ export class ConnectorElementModel extends GfxPrimitiveElementModel<ConnectorEle
|
||||
return 'connector';
|
||||
}
|
||||
|
||||
static override propsToY(props: ConnectorElementProps) {
|
||||
if (props.text && !(props.text instanceof Y.Text)) {
|
||||
static propsToY(props: ConnectorElementProps) {
|
||||
if (typeof props.text === 'string') {
|
||||
props.text = new Y.Text(props.text);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ export class GroupElementModel extends GfxGroupLikeElementModel<GroupElementProp
|
||||
return 'group';
|
||||
}
|
||||
|
||||
static override propsToY(props: Record<string, unknown>) {
|
||||
if ('title' in props && !(props.title instanceof Y.Text)) {
|
||||
static propsToY(props: Record<string, unknown>) {
|
||||
if (typeof props.title === 'string') {
|
||||
props.title = new Y.Text(props.title as string);
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ export class MindmapElementModel extends GfxGroupLikeElementModel<MindmapElement
|
||||
return 'mindmap';
|
||||
}
|
||||
|
||||
static override propsToY(props: Record<string, unknown>) {
|
||||
static propsToY(props: Record<string, unknown>) {
|
||||
if (
|
||||
props.children &&
|
||||
!isNodeType(props.children as Record<string, unknown>) &&
|
||||
|
||||
@@ -67,8 +67,8 @@ export class ShapeElementModel extends GfxPrimitiveElementModel<ShapeProps> {
|
||||
return 'shape';
|
||||
}
|
||||
|
||||
static override propsToY(props: ShapeProps) {
|
||||
if (props.text && !(props.text instanceof Y.Text)) {
|
||||
static propsToY(props: ShapeProps) {
|
||||
if (typeof props.text === 'string') {
|
||||
props.text = new Y.Text(props.text);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ export class TextElementModel extends GfxPrimitiveElementModel<TextElementProps>
|
||||
return 'text';
|
||||
}
|
||||
|
||||
static override propsToY(props: Record<string, unknown>) {
|
||||
if (props.text && !(props.text instanceof Y.Text)) {
|
||||
props.text = new Y.Text(props.text as string);
|
||||
static propsToY(props: Record<string, unknown>) {
|
||||
if (typeof props.text === 'string') {
|
||||
props.text = new Y.Text(props.text);
|
||||
}
|
||||
|
||||
return props;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"dompurify": "^3.2.4",
|
||||
@@ -77,5 +77,5 @@
|
||||
"@types/lodash.mergewith": "^4",
|
||||
"vitest": "3.0.6"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -13,10 +13,20 @@ export class SpecBuilder {
|
||||
|
||||
extend(extensions: ExtensionType[]) {
|
||||
this._value = [...this._value, ...extensions];
|
||||
return this;
|
||||
}
|
||||
|
||||
omit(target: ExtensionType) {
|
||||
this._value = this._value.filter(extension => extension !== target);
|
||||
return this;
|
||||
}
|
||||
|
||||
hasAll(target: ExtensionType[]) {
|
||||
return target.every(t => this._value.includes(t));
|
||||
}
|
||||
|
||||
hasOneOf(target: ExtensionType[]) {
|
||||
return target.some(t => this._value.includes(t));
|
||||
}
|
||||
|
||||
replace(target: ExtensionType[], newExtension: ExtensionType[]) {
|
||||
@@ -24,5 +34,6 @@ export class SpecBuilder {
|
||||
...this._value.filter(extension => !target.includes(extension)),
|
||||
...newExtension,
|
||||
];
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,13 +66,12 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
);
|
||||
});
|
||||
|
||||
const debounceOptions = { leading: false, trailing: true };
|
||||
const debouncedRefresh = debounce(
|
||||
() => {
|
||||
this.refresh().catch(console.error);
|
||||
},
|
||||
1000, // During this period, fallback to DOM
|
||||
debounceOptions
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
this.disposables.add(
|
||||
this.std.store.slots.blockUpdated.on(() => {
|
||||
@@ -97,11 +96,12 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
return this.std.get(GfxControllerIdentifier).viewport;
|
||||
}
|
||||
|
||||
async refresh(force = false) {
|
||||
if (this.state === 'paused' && !force) return;
|
||||
async refresh() {
|
||||
if (this.state === 'paused') return;
|
||||
|
||||
this.clearCanvas();
|
||||
if (this.viewport.zoom > zoomThreshold) {
|
||||
this.clearCanvas();
|
||||
return;
|
||||
} else if (this.canUseBitmapCache()) {
|
||||
this.drawCachedBitmap(this.layoutCache!);
|
||||
} else {
|
||||
@@ -115,8 +115,9 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
this.clearCache();
|
||||
this.clearCanvas();
|
||||
this.layoutCache = null;
|
||||
this.clearTile();
|
||||
this.clearCanvas(); // Should clear immediately after content updates
|
||||
}
|
||||
|
||||
private updateLayoutCache() {
|
||||
@@ -124,11 +125,6 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
this.layoutCache = layout;
|
||||
}
|
||||
|
||||
private clearCache() {
|
||||
this.layoutCache = null;
|
||||
this.clearTile();
|
||||
}
|
||||
|
||||
private clearTile() {
|
||||
if (this.tile) {
|
||||
this.tile.bitmap.close();
|
||||
@@ -154,17 +150,13 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
|
||||
this.worker.onmessage = (e: MessageEvent) => {
|
||||
if (e.data.type === 'bitmapPainted') {
|
||||
this.handlePaintedBitmap(e.data.bitmap, layout, resolve);
|
||||
this.handlePaintedBitmap(e.data.bitmap, resolve);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private handlePaintedBitmap(
|
||||
bitmap: ImageBitmap,
|
||||
layout: ViewportLayout,
|
||||
resolve: () => void
|
||||
) {
|
||||
private handlePaintedBitmap(bitmap: ImageBitmap, resolve: () => void) {
|
||||
if (this.tile) {
|
||||
this.tile.bitmap.close();
|
||||
}
|
||||
@@ -172,7 +164,6 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
bitmap,
|
||||
zoom: this.viewport.zoom,
|
||||
};
|
||||
this.drawCachedBitmap(layout);
|
||||
resolve();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"zod": "^3.23.8"
|
||||
@@ -43,5 +43,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { WidgetViewExtension } from '@blocksuite/block-std';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { AFFINE_DRAG_HANDLE_WIDGET } from './consts';
|
||||
|
||||
export * from './consts';
|
||||
export * from './drag-handle';
|
||||
export * from './utils';
|
||||
export type { DragBlockPayload } from './watchers/drag-event-watcher';
|
||||
|
||||
export const dragHandleWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_DRAG_HANDLE_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_DRAG_HANDLE_WIDGET)}`
|
||||
);
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import type { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
|
||||
import type { ConnectorElementModel } from '@blocksuite/affine-model';
|
||||
import type { BlockStdScope } from '@blocksuite/block-std';
|
||||
import { isGfxGroupCompatibleModel } from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
GfxController,
|
||||
type GfxModel,
|
||||
isGfxGroupCompatibleModel,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
assertType,
|
||||
type IVec,
|
||||
type SerializedXYWH,
|
||||
} from '@blocksuite/global/utils';
|
||||
import type { TransformerMiddleware } from '@blocksuite/store';
|
||||
|
||||
/**
|
||||
@@ -18,6 +28,7 @@ export const gfxBlocksFilter = (
|
||||
const surface = store.getBlocksByFlavour('affine:surface')[0]
|
||||
.model as SurfaceBlockModel;
|
||||
const idsToCheck = ids.slice();
|
||||
const gfx = std.get(GfxController);
|
||||
|
||||
for (const id of idsToCheck) {
|
||||
const blockOrElem = store.getBlock(id)?.model ?? surface.getElementById(id);
|
||||
@@ -45,5 +56,62 @@ export const gfxBlocksFilter = (
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
slots.afterExport.on(payload => {
|
||||
if (payload.type !== 'block') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.model.flavour === 'affine:surface') {
|
||||
const { snapshot } = payload;
|
||||
const elementsMap = snapshot.props.elements as Record<
|
||||
string,
|
||||
{ type: string }
|
||||
>;
|
||||
|
||||
Object.entries(elementsMap).forEach(([elementId, val]) => {
|
||||
if (val.type === 'connector') {
|
||||
assertType<{
|
||||
type: 'connector';
|
||||
source: { position: IVec; id?: string };
|
||||
target: { position: IVec; id?: string };
|
||||
xywh: SerializedXYWH;
|
||||
}>(val);
|
||||
|
||||
const connectorElem = gfx.getElementById(
|
||||
elementId
|
||||
) as ConnectorElementModel;
|
||||
|
||||
if (!connectorElem) {
|
||||
delete elementsMap[elementId];
|
||||
return;
|
||||
}
|
||||
|
||||
// should be deleted during the import process
|
||||
val.xywh = connectorElem.xywh;
|
||||
|
||||
['source', 'target'].forEach(key => {
|
||||
const endpoint = val[key as 'source' | 'target'];
|
||||
if (endpoint.id && !selectedIds.has(endpoint.id)) {
|
||||
const endElem = gfx.getElementById(endpoint.id);
|
||||
|
||||
if (!endElem) {
|
||||
delete elementsMap[elementId];
|
||||
return;
|
||||
}
|
||||
|
||||
const endElemBound = (endElem as GfxModel).elementBound;
|
||||
|
||||
val[key as 'source' | 'target'] = {
|
||||
position: endElemBound.getRelativePoint(
|
||||
endpoint.position ?? [0.5, 0.5]
|
||||
),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -290,13 +290,28 @@ export function getSnapshotRect(snapshot: SliceSnapshot): Bound | null {
|
||||
if (block.flavour === 'affine:surface') {
|
||||
if (block.props.elements) {
|
||||
Object.values(
|
||||
block.props.elements as Record<string, { xywh: SerializedXYWH }>
|
||||
block.props.elements as Record<
|
||||
string,
|
||||
{ type: string; xywh: SerializedXYWH }
|
||||
>
|
||||
).forEach(elem => {
|
||||
if (elem.xywh) {
|
||||
bound = bound
|
||||
? bound.unite(Bound.deserialize(elem.xywh))
|
||||
: Bound.deserialize(elem.xywh);
|
||||
}
|
||||
|
||||
if (elem.type === 'connector') {
|
||||
let connectorBound: Bound | undefined;
|
||||
|
||||
if (elem.xywh) {
|
||||
connectorBound = Bound.deserialize(elem.xywh);
|
||||
}
|
||||
|
||||
if (connectorBound) {
|
||||
bound = bound ? bound.unite(connectorBound) : connectorBound;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph';
|
||||
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
|
||||
import { DropIndicator } from '@blocksuite/affine-components/drop-indicator';
|
||||
import {
|
||||
AttachmentBlockModel,
|
||||
@@ -45,11 +44,14 @@ import {
|
||||
type GfxModel,
|
||||
GfxPrimitiveElementModel,
|
||||
isGfxGroupCompatibleModel,
|
||||
SURFACE_YMAP_UNIQ_IDENTIFIER,
|
||||
SurfaceBlockModel,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
assertType,
|
||||
Bound,
|
||||
groupBy,
|
||||
type IVec,
|
||||
last,
|
||||
Point,
|
||||
Rect,
|
||||
@@ -752,7 +754,11 @@ export class DragEventWatcher {
|
||||
const idRemap = new Map<string, string>();
|
||||
let elemMap: Record<
|
||||
string,
|
||||
{ type: string; children?: { json: Record<string, unknown> } }
|
||||
{
|
||||
type: string;
|
||||
xywh?: SerializedXYWH;
|
||||
children?: { json: Record<string, unknown> };
|
||||
}
|
||||
> = {};
|
||||
const blockMap: Record<
|
||||
string,
|
||||
@@ -766,7 +772,7 @@ export class DragEventWatcher {
|
||||
const constructor = surface.getConstructor(elem.type);
|
||||
const isGroup = Object.isPrototypeOf.call(
|
||||
GfxGroupLikeElementModel.prototype,
|
||||
constructor
|
||||
constructor.prototype
|
||||
);
|
||||
|
||||
return isGroup;
|
||||
@@ -782,24 +788,40 @@ export class DragEventWatcher {
|
||||
if (block.flavour === 'affine:surface') {
|
||||
elemMap = (block.props.elements as typeof elemMap) ?? {};
|
||||
Object.entries(elemMap).forEach(([elemId, elem]) => {
|
||||
if (isGroupLikeElem(elem)) {
|
||||
// only add the group to the root if it's not a child of any other element
|
||||
if (
|
||||
Object.values(containerTree).every(
|
||||
childSet => !childSet.has(elemId)
|
||||
)
|
||||
) {
|
||||
containerTree['root'].add(elem.type);
|
||||
}
|
||||
if (
|
||||
Object.values(containerTree).every(
|
||||
childSet => !childSet.has(elemId)
|
||||
)
|
||||
) {
|
||||
containerTree['root'].add(elemId);
|
||||
}
|
||||
|
||||
if (isGroupLikeElem(elem)) {
|
||||
Object.keys(elem.children?.json ?? {}).forEach(childId => {
|
||||
containerTree[elemId] = containerTree[elemId] ?? new Set();
|
||||
containerTree[elemId].add(childId);
|
||||
// if the child was already added to the root, remove it
|
||||
containerTree['root'].delete(childId);
|
||||
});
|
||||
} else {
|
||||
containerTree['root'].add(elemId);
|
||||
return;
|
||||
} else if (elem.type === 'connector') {
|
||||
assertType<{
|
||||
type: 'connector';
|
||||
source: { position: IVec; id?: string };
|
||||
target: { position: IVec; id?: string };
|
||||
}>(elem);
|
||||
|
||||
if (elem.source.id) {
|
||||
containerTree[elemId] = containerTree[elemId] ?? new Set();
|
||||
containerTree[elemId].add(elem.source.id);
|
||||
containerTree['root'].delete(elem.source.id);
|
||||
}
|
||||
|
||||
if (elem.target.id) {
|
||||
containerTree[elemId] = containerTree[elemId] ?? new Set();
|
||||
containerTree[elemId].add(elem.target.id);
|
||||
containerTree['root'].delete(elem.target.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -876,19 +898,58 @@ export class DragEventWatcher {
|
||||
idRemap.set(id, slices.content[0].id);
|
||||
}
|
||||
} else if (elemMap[id]) {
|
||||
if (elemMap[id].children) {
|
||||
const childJson = elemMap[id].children.json;
|
||||
Object.keys(childJson).forEach(childId => {
|
||||
if (idRemap.has(childId)) {
|
||||
const remappedId = idRemap.get(childId)!;
|
||||
childJson[remappedId] = childJson[childId];
|
||||
delete childJson[childId];
|
||||
} else {
|
||||
delete childJson[childId];
|
||||
const elem = elemMap[id];
|
||||
|
||||
Object.entries(elem).forEach(([_, val]) => {
|
||||
if (
|
||||
val instanceof Object &&
|
||||
Reflect.has(val, SURFACE_YMAP_UNIQ_IDENTIFIER)
|
||||
) {
|
||||
const childJson = Reflect.get(val, 'json') as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
Object.keys(childJson).forEach(oldChildId => {
|
||||
if (idRemap.has(oldChildId)) {
|
||||
const remappedId = idRemap.get(oldChildId)!;
|
||||
const val = structuredClone(childJson[oldChildId]);
|
||||
|
||||
if (elem.type === 'mindmap') {
|
||||
assertType<{ parent?: string }>(val);
|
||||
if (val.parent) {
|
||||
val.parent = idRemap.get(val.parent);
|
||||
}
|
||||
}
|
||||
childJson[remappedId] = val;
|
||||
delete childJson[oldChildId];
|
||||
} else {
|
||||
delete childJson[oldChildId];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (elem.type === 'connector') {
|
||||
assertType<{
|
||||
type: 'connector';
|
||||
source: { position: IVec; id?: string };
|
||||
target: { position: IVec; id?: string };
|
||||
}>(elem);
|
||||
|
||||
(['source', 'target'] as const).forEach(key => {
|
||||
const endpoint = elem[key];
|
||||
if (endpoint.id) {
|
||||
if (idRemap.get(endpoint.id)) {
|
||||
endpoint.id = idRemap.get(endpoint.id);
|
||||
} else {
|
||||
delete endpoint.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const newId = surface.addElement(elemMap[id]);
|
||||
|
||||
const newId = surface.addElement(elem);
|
||||
idRemap.set(id, newId);
|
||||
}
|
||||
};
|
||||
@@ -918,8 +979,45 @@ export class DragEventWatcher {
|
||||
if (block.flavour === 'affine:surface') {
|
||||
if (block.props.elements) {
|
||||
Object.values(
|
||||
block.props.elements as Record<string, { xywh: SerializedXYWH }>
|
||||
block.props.elements as Record<
|
||||
string,
|
||||
{ type: string; xywh?: SerializedXYWH }
|
||||
>
|
||||
).forEach(elem => {
|
||||
if (elem.type === 'connector') {
|
||||
assertType<{
|
||||
type: 'connector';
|
||||
xywh?: SerializedXYWH;
|
||||
source: { position: IVec; id?: string };
|
||||
target: { position: IVec; id?: string };
|
||||
}>(elem);
|
||||
|
||||
const connectorBound = elem.xywh
|
||||
? Bound.deserialize(elem.xywh)
|
||||
: new Bound(0, 0, 0, 0);
|
||||
|
||||
delete elem.xywh;
|
||||
|
||||
(['source', 'target'] as const).forEach(key => {
|
||||
const endpoint = elem[key];
|
||||
if (!endpoint.id) {
|
||||
const originalPos = endpoint.position;
|
||||
|
||||
elem[key] = {
|
||||
position: ignoreOriginalPos
|
||||
? [
|
||||
originalPos[0] - connectorBound.x + modelX,
|
||||
originalPos[1] - connectorBound.y + modelY,
|
||||
]
|
||||
: [
|
||||
originalPos[0] - rect.x + modelX,
|
||||
originalPos[1] - rect.y + modelY,
|
||||
],
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (elem.xywh) {
|
||||
const elemBound = Bound.deserialize(elem.xywh);
|
||||
|
||||
@@ -956,6 +1054,7 @@ export class DragEventWatcher {
|
||||
|
||||
if (
|
||||
block.flavour === 'affine:attachment' ||
|
||||
block.flavour === 'affine:bookmark' ||
|
||||
block.flavour.startsWith('affine:embed-')
|
||||
) {
|
||||
const style = 'vertical' as EmbedCardStyle;
|
||||
@@ -1037,6 +1136,7 @@ export class DragEventWatcher {
|
||||
block.id === content[idx].id &&
|
||||
(block.flavour === 'affine:image' ||
|
||||
block.flavour === 'affine:attachment' ||
|
||||
block.flavour === 'affine:bookmark' ||
|
||||
block.flavour.startsWith('affine:embed-'))
|
||||
) {
|
||||
store.updateBlock(block as BlockModel, {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.3",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
@@ -35,5 +35,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '@blocksuite/affine-model';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import { matchModels, stopPropagation } from '@blocksuite/affine-shared/utils';
|
||||
import { WidgetComponent } from '@blocksuite/block-std';
|
||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/block-std';
|
||||
import {
|
||||
type GfxController,
|
||||
GfxControllerIdentifier,
|
||||
@@ -28,6 +28,7 @@ import { css, html, nothing, type TemplateResult } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
const PAGE_VISIBLE_INDEX_LABEL_WIDTH = 44;
|
||||
const PAGE_VISIBLE_INDEX_LABEL_HEIGHT = 24;
|
||||
@@ -613,6 +614,12 @@ export class EdgelessAutoConnectWidget extends WidgetComponent<RootBlockModel> {
|
||||
private accessor _show = false;
|
||||
}
|
||||
|
||||
export const autoConnectWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_EDGELESS_AUTO_CONNECT_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_EDGELESS_AUTO_CONNECT_WIDGET)}`
|
||||
);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-edgeless-auto-connect-widget': EdgelessAutoConnectWidget;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
@@ -33,5 +33,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FrameBlockModel, type RootBlockModel } from '@blocksuite/affine-model';
|
||||
import { WidgetComponent } from '@blocksuite/block-std';
|
||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/block-std';
|
||||
import { html } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import type { AffineFrameTitle } from './frame-title.js';
|
||||
|
||||
@@ -36,3 +37,9 @@ export class AffineFrameTitleWidget extends WidgetComponent<RootBlockModel> {
|
||||
}
|
||||
|
||||
export * from './styles.js';
|
||||
|
||||
export const frameTitleWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_FRAME_TITLE_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_FRAME_TITLE_WIDGET)}`
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.3",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
@@ -34,5 +34,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import type * as CommandsType from '@blocksuite/affine-shared/commands';
|
||||
import { WidgetViewExtension } from '@blocksuite/block-std';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
declare type _GLOBAL_ = typeof CommandsType;
|
||||
import { AFFINE_DOC_REMOTE_SELECTION_WIDGET } from './doc';
|
||||
import { AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET } from './edgeless';
|
||||
|
||||
export * from './doc';
|
||||
export * from './edgeless';
|
||||
|
||||
export const docRemoteSelectionWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_DOC_REMOTE_SELECTION_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_DOC_REMOTE_SELECTION_WIDGET)}`
|
||||
);
|
||||
|
||||
export const edgelessRemoteSelectionWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET)}`
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@blocksuite/block-std": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
@@ -31,5 +31,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
import { WidgetViewExtension } from '@blocksuite/block-std';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { AFFINE_SCROLL_ANCHORING_WIDGET } from './scroll-anchoring.js';
|
||||
|
||||
export * from './scroll-anchoring.js';
|
||||
|
||||
export const scrollAnchoringWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_SCROLL_ANCHORING_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_SCROLL_ANCHORING_WIDGET)}`
|
||||
);
|
||||
|
||||
@@ -63,5 +63,6 @@
|
||||
"devDependencies": {
|
||||
"@vanilla-extract/vite-plugin": "^5.0.0",
|
||||
"vitest": "3.0.6"
|
||||
}
|
||||
},
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -48,5 +48,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ export {
|
||||
prop,
|
||||
} from './model/surface/local-element-model.js';
|
||||
export {
|
||||
SURFACE_TEXT_UNIQ_IDENTIFIER,
|
||||
SURFACE_YMAP_UNIQ_IDENTIFIER,
|
||||
SurfaceBlockModel,
|
||||
type SurfaceBlockProps,
|
||||
type SurfaceMiddleware,
|
||||
|
||||
@@ -199,10 +199,6 @@ export abstract class GfxPrimitiveElementModel<
|
||||
this.seed = randomSeed();
|
||||
}
|
||||
|
||||
static propsToY(props: Record<string, unknown>) {
|
||||
return props;
|
||||
}
|
||||
|
||||
containsBound(bounds: Bound): boolean {
|
||||
return getPointsFromBoundWithRotation(this).some(point =>
|
||||
bounds.containsPoint(point)
|
||||
|
||||
@@ -12,11 +12,20 @@ import { createDecoratorState } from './decorators/common.js';
|
||||
import { initializeObservers, initializeWatchers } from './decorators/index.js';
|
||||
import {
|
||||
GfxGroupLikeElementModel,
|
||||
GfxPrimitiveElementModel,
|
||||
type GfxPrimitiveElementModel,
|
||||
syncElementFromY,
|
||||
} from './element-model.js';
|
||||
import type { GfxLocalElementModel } from './local-element-model.js';
|
||||
|
||||
/**
|
||||
* Used for text field
|
||||
*/
|
||||
export const SURFACE_TEXT_UNIQ_IDENTIFIER = 'affine:surface:text';
|
||||
/**
|
||||
* Used for field that use Y.Map. E.g. group children field
|
||||
*/
|
||||
export const SURFACE_YMAP_UNIQ_IDENTIFIER = 'affine:surface:ymap';
|
||||
|
||||
export type SurfaceBlockProps = {
|
||||
elements: Boxed<Y.Map<Y.Map<unknown>>>;
|
||||
};
|
||||
@@ -390,8 +399,28 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
|
||||
throw new Error(`Invalid element type: ${type}`);
|
||||
}
|
||||
|
||||
Object.entries(props).forEach(([key, val]) => {
|
||||
if (val instanceof Object) {
|
||||
if (Reflect.has(val, SURFACE_TEXT_UNIQ_IDENTIFIER)) {
|
||||
const yText = new Y.Text();
|
||||
yText.applyDelta(Reflect.get(val, 'delta'));
|
||||
Reflect.set(props, key, yText);
|
||||
}
|
||||
|
||||
if (Reflect.has(val, SURFACE_YMAP_UNIQ_IDENTIFIER)) {
|
||||
const childJson = Reflect.get(val, 'json') as Record<string, unknown>;
|
||||
const childrenYMap = new Y.Map<unknown>();
|
||||
|
||||
Object.keys(childJson).forEach(childId => {
|
||||
childrenYMap.set(childId, childJson[childId]);
|
||||
});
|
||||
Reflect.set(props, key, childrenYMap);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-expect-error ignore
|
||||
return (ctor.propsToY ?? GfxPrimitiveElementModel.propsToY)(props);
|
||||
return ctor.propsToY ? ctor.propsToY(props) : props;
|
||||
}
|
||||
|
||||
private _watchGroupRelationChange() {
|
||||
|
||||
@@ -51,5 +51,5 @@
|
||||
"devDependencies": {
|
||||
"vitest": "3.0.6"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@
|
||||
"devDependencies": {
|
||||
"vitest": "3.0.6"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -46,5 +46,5 @@
|
||||
"!dist/__tests__",
|
||||
"shim.d.ts"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"@lit/context": "^1.1.3",
|
||||
"@lottiefiles/dotlottie-wc": "^0.4.0",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"lit": "^3.2.0",
|
||||
"yjs": "^13.6.21",
|
||||
@@ -52,5 +52,5 @@
|
||||
"vite-plugin-wasm": "^3.4.1",
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -49,5 +49,5 @@
|
||||
"vite-plugin-wasm": "^3.3.0",
|
||||
"vite-plugin-web-components-hmr": "^0.1.3"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/toeverything/blocksuite.git"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
],
|
||||
"ext": "ts,md,json"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/monorepo",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/server-native",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0 < 11 || >= 11.8.0"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -146,3 +146,26 @@ test('should tell downgrade if client version is higher than allowed', async t =
|
||||
'Unsupported client with version [0.23.0], required version is [>=0.20.0 <=0.22.0].'
|
||||
);
|
||||
});
|
||||
|
||||
test('should test prerelease version', async t => {
|
||||
runtime.fetch
|
||||
.withArgs('client/versionControl.requiredVersion')
|
||||
.resolves('>=0.19.0');
|
||||
|
||||
let res = await app
|
||||
.GET('/guarded/test')
|
||||
.set('x-affine-version', '0.19.0-canary.1');
|
||||
|
||||
// 0.19.0-canary.1 is lower than 0.19.0 obviously
|
||||
t.is(res.status, 403);
|
||||
|
||||
res = await app
|
||||
.GET('/guarded/test')
|
||||
.set('x-affine-version', '0.20.0-canary.1');
|
||||
|
||||
t.is(res.status, 200);
|
||||
|
||||
res = await app.GET('/guarded/test').set('x-affine-version', '0.20.0-beta.2');
|
||||
|
||||
t.is(res.status, 200);
|
||||
});
|
||||
|
||||
@@ -299,6 +299,7 @@ export class AuthController {
|
||||
res.send({ id: user.id });
|
||||
}
|
||||
|
||||
@UseNamedGuard('version')
|
||||
@Throttle('default', { limit: 1200 })
|
||||
@Public()
|
||||
@Get('/session')
|
||||
|
||||
@@ -45,7 +45,9 @@ type EventResponse<Data = any> = Data extends never
|
||||
data: Data;
|
||||
};
|
||||
|
||||
type RoomType = 'sync' | `${string}:awareness`;
|
||||
// 019 only receives space:broadcast-doc-updates and send space:push-doc-updates
|
||||
// 020 only receives space:broadcast-doc-update and send space:push-doc-update
|
||||
type RoomType = 'sync' | `${string}:awareness` | 'sync-019';
|
||||
|
||||
function Room(
|
||||
spaceId: string,
|
||||
@@ -214,7 +216,16 @@ export class SpaceSyncGateway
|
||||
): Promise<EventResponse<{ clientId: string; success: true }>> {
|
||||
await this.assertVersion(client, clientVersion);
|
||||
|
||||
await this.selectAdapter(client, spaceType).join(user.id, spaceId);
|
||||
// TODO(@forehalo): remove this after 0.19 goes out of life
|
||||
// simple match 0.19.x
|
||||
if (/^0.19.[\d]$/.test(clientVersion)) {
|
||||
const room = Room(spaceId, 'sync-019');
|
||||
if (!client.rooms.has(room)) {
|
||||
await client.join(room);
|
||||
}
|
||||
} else {
|
||||
await this.selectAdapter(client, spaceType).join(user.id, spaceId);
|
||||
}
|
||||
|
||||
return { data: { clientId: client.id, success: true } };
|
||||
}
|
||||
@@ -270,6 +281,8 @@ export class SpaceSyncGateway
|
||||
|
||||
/**
|
||||
* @deprecated use [space:push-doc-update] instead, client should always merge updates on their own
|
||||
*
|
||||
* only 0.19.x client will send this event
|
||||
*/
|
||||
@SubscribeMessage('space:push-doc-updates')
|
||||
async onReceiveDocUpdates(
|
||||
@@ -289,23 +302,19 @@ export class SpaceSyncGateway
|
||||
user.id
|
||||
);
|
||||
|
||||
// could be put in [adapter.push]
|
||||
// but the event should be kept away from adapter
|
||||
// so
|
||||
// broadcast to 0.19.x clients
|
||||
client
|
||||
.to(adapter.room(spaceId))
|
||||
.to(Room(spaceId, 'sync-019'))
|
||||
.emit('space:broadcast-doc-updates', { ...message, timestamp });
|
||||
|
||||
// TODO(@forehalo): remove backward compatibility
|
||||
if (spaceType === SpaceType.Workspace) {
|
||||
const id = new DocID(docId, spaceId);
|
||||
client.to(adapter.room(spaceId)).emit('server-updates', {
|
||||
workspaceId: spaceId,
|
||||
guid: id.guid,
|
||||
updates,
|
||||
// broadcast to new clients
|
||||
updates.forEach(update => {
|
||||
client.to(adapter.room(spaceId)).emit('space:broadcast-doc-update', {
|
||||
...message,
|
||||
update,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
data: {
|
||||
@@ -333,9 +342,8 @@ export class SpaceSyncGateway
|
||||
user.id
|
||||
);
|
||||
|
||||
// TODO(@forehalo): separate different version of clients into different rooms,
|
||||
// so the clients won't receive useless updates events
|
||||
client.to(adapter.room(spaceId)).emit('space:broadcast-doc-updates', {
|
||||
// broadcast to 0.19.x clients
|
||||
client.to(Room(spaceId, 'sync-019')).emit('space:broadcast-doc-updates', {
|
||||
spaceType,
|
||||
spaceId,
|
||||
docId,
|
||||
@@ -445,163 +453,8 @@ export class SpaceSyncGateway
|
||||
.to(adapter.room(spaceId, roomType))
|
||||
.emit('space:broadcast-awareness-update', message);
|
||||
|
||||
// TODO(@forehalo): remove backward compatibility
|
||||
if (spaceType === SpaceType.Workspace) {
|
||||
client
|
||||
.to(adapter.room(spaceId, roomType))
|
||||
.emit('server-awareness-broadcast', {
|
||||
workspaceId: spaceId,
|
||||
awarenessUpdate: message.awarenessUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO(@forehalo): remove
|
||||
// deprecated section
|
||||
@SubscribeMessage('client-handshake-sync')
|
||||
async handleClientHandshakeSync(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@MessageBody('workspaceId') workspaceId: string,
|
||||
@MessageBody('version') version: string,
|
||||
@ConnectedSocket() client: Socket
|
||||
): Promise<EventResponse<{ clientId: string }>> {
|
||||
await this.assertVersion(client, version);
|
||||
|
||||
return this.onJoinSpace(user, client, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
clientVersion: version,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('client-leave-sync')
|
||||
async handleLeaveSync(
|
||||
@MessageBody() workspaceId: string,
|
||||
@ConnectedSocket() client: Socket
|
||||
): Promise<EventResponse> {
|
||||
return this.onLeaveSpace(client, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('client-pre-sync')
|
||||
async loadDocStats(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@MessageBody()
|
||||
{ workspaceId, timestamp }: { workspaceId: string; timestamp?: number }
|
||||
): Promise<EventResponse<Record<string, number>>> {
|
||||
return this.onLoadDocTimestamps(client, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('client-update-v2')
|
||||
async handleClientUpdateV2(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@MessageBody()
|
||||
{
|
||||
workspaceId,
|
||||
guid,
|
||||
updates,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
guid: string;
|
||||
updates: string[];
|
||||
},
|
||||
@ConnectedSocket() client: Socket
|
||||
): Promise<EventResponse<{ accepted: true; timestamp?: number }>> {
|
||||
return this.onReceiveDocUpdates(client, user, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
docId: guid,
|
||||
updates,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('doc-load-v2')
|
||||
async loadDocV2(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@MessageBody()
|
||||
{
|
||||
workspaceId,
|
||||
guid,
|
||||
stateVector,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
guid: string;
|
||||
stateVector?: string;
|
||||
}
|
||||
): Promise<
|
||||
EventResponse<{ missing: string; state?: string; timestamp: number }>
|
||||
> {
|
||||
return this.onLoadSpaceDoc(client, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
docId: guid,
|
||||
stateVector,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('client-handshake-awareness')
|
||||
async handleClientHandshakeAwareness(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@MessageBody('workspaceId') workspaceId: string,
|
||||
@MessageBody('version') version: string
|
||||
): Promise<EventResponse<{ clientId: string }>> {
|
||||
return this.onJoinAwareness(client, user, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
docId: workspaceId,
|
||||
clientVersion: version,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('client-leave-awareness')
|
||||
async handleLeaveAwareness(
|
||||
@MessageBody() workspaceId: string,
|
||||
@ConnectedSocket() client: Socket
|
||||
): Promise<EventResponse> {
|
||||
return this.onLeaveAwareness(client, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
docId: workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('awareness-init')
|
||||
async handleInitAwareness(
|
||||
@MessageBody() workspaceId: string,
|
||||
@ConnectedSocket() client: Socket
|
||||
): Promise<EventResponse<{ clientId: string }>> {
|
||||
return this.onLoadAwareness(client, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
docId: workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('awareness-update')
|
||||
async handleHelpGatheringAwareness(
|
||||
@MessageBody()
|
||||
{
|
||||
workspaceId,
|
||||
awarenessUpdate,
|
||||
}: { workspaceId: string; awarenessUpdate: string },
|
||||
@ConnectedSocket() client: Socket
|
||||
): Promise<EventResponse> {
|
||||
return this.onUpdateAwareness(client, {
|
||||
spaceType: SpaceType.Workspace,
|
||||
spaceId: workspaceId,
|
||||
docId: workspaceId,
|
||||
awarenessUpdate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SyncSocketAdapter {
|
||||
@@ -647,7 +500,8 @@ abstract class SyncSocketAdapter {
|
||||
): Promise<void>;
|
||||
|
||||
push(spaceId: string, docId: string, updates: Buffer[], editorId: string) {
|
||||
this.assertIn(spaceId);
|
||||
// TODO(@forehalo): enable this after 0.19 goes out of life
|
||||
// this.assertIn(spaceId);
|
||||
return this.storage.pushDocUpdates(spaceId, docId, updates, editorId);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,12 @@ export class VersionService {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!clientVersion || !semver.satisfies(clientVersion, range)) {
|
||||
if (
|
||||
!clientVersion ||
|
||||
!semver.satisfies(clientVersion, range, {
|
||||
includePrerelease: true,
|
||||
})
|
||||
) {
|
||||
throw new UnsupportedClientVersion({
|
||||
clientVersion: clientVersion ?? 'unset_or_invalid',
|
||||
requiredVersion,
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
"@types/debug": "^4.1.12",
|
||||
"vitest": "3.0.6"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
2
packages/common/env/package.json
vendored
2
packages/common/env/package.json
vendored
@@ -21,5 +21,5 @@
|
||||
"dependencies": {
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
"electron": "*",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"version": "0.19.0"
|
||||
"version": "0.20.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/nbstore",
|
||||
"type": "module",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
|
||||
@@ -61,6 +61,11 @@ export class AwarenessFrontend {
|
||||
handleSyncCollect
|
||||
);
|
||||
|
||||
awareness.once('destroy', () => {
|
||||
awareness.off('update', handleAwarenessUpdate);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
return () => {
|
||||
awareness.off('update', handleAwarenessUpdate);
|
||||
unsubscribe();
|
||||
|
||||
@@ -15,8 +15,12 @@ export class BlobFrontend {
|
||||
return this.sync.uploadBlob(blob);
|
||||
}
|
||||
|
||||
fullSync() {
|
||||
return this.sync.fullSync();
|
||||
fullDownload() {
|
||||
return this.sync.fullDownload();
|
||||
}
|
||||
|
||||
fullUpload() {
|
||||
return this.sync.fullUpload();
|
||||
}
|
||||
|
||||
addPriority(_id: string, _priority: number) {
|
||||
|
||||
@@ -399,31 +399,23 @@ export class DocFrontend {
|
||||
this.statusUpdatedSubject$.next(job.docId);
|
||||
}
|
||||
|
||||
/**
|
||||
* skip listen doc update when apply update
|
||||
*/
|
||||
private skipDocUpdate = false;
|
||||
|
||||
applyUpdate(docId: string, update: Uint8Array) {
|
||||
const doc = this.status.docs.get(docId);
|
||||
if (doc && !isEmptyUpdate(update)) {
|
||||
try {
|
||||
this.skipDocUpdate = true;
|
||||
applyUpdate(doc, update, NBSTORE_ORIGIN);
|
||||
} catch (err) {
|
||||
console.error('failed to apply update yjs doc', err);
|
||||
} finally {
|
||||
this.skipDocUpdate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly handleDocUpdate = (
|
||||
update: Uint8Array,
|
||||
_origin: any,
|
||||
origin: any,
|
||||
doc: YDoc
|
||||
) => {
|
||||
if (this.skipDocUpdate) {
|
||||
if (origin === NBSTORE_ORIGIN) {
|
||||
return;
|
||||
}
|
||||
if (!this.status.docs.has(doc.guid)) {
|
||||
@@ -534,4 +526,8 @@ export class DocFrontend {
|
||||
sub?.unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
async resetSync() {
|
||||
await this.sync.resetSync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import type { PeerStorageOptions } from '../types';
|
||||
|
||||
export interface BlobSyncState {
|
||||
isStorageOverCapacity: boolean;
|
||||
total: number;
|
||||
synced: number;
|
||||
}
|
||||
|
||||
export interface BlobSync {
|
||||
@@ -18,7 +20,8 @@ export interface BlobSync {
|
||||
signal?: AbortSignal
|
||||
): Promise<BlobRecord | null>;
|
||||
uploadBlob(blob: BlobRecord, signal?: AbortSignal): Promise<void>;
|
||||
fullSync(signal?: AbortSignal): Promise<void>;
|
||||
fullDownload(signal?: AbortSignal): Promise<void>;
|
||||
fullUpload(signal?: AbortSignal): Promise<void>;
|
||||
setMaxBlobSize(size: number): void;
|
||||
onReachedMaxBlobSize(cb: (byteSize: number) => void): () => void;
|
||||
}
|
||||
@@ -26,6 +29,8 @@ export interface BlobSync {
|
||||
export class BlobSyncImpl implements BlobSync {
|
||||
readonly state$ = new BehaviorSubject<BlobSyncState>({
|
||||
isStorageOverCapacity: false,
|
||||
total: Object.values(this.storages.remotes).length ? 1 : 0,
|
||||
synced: 0,
|
||||
});
|
||||
private abort: AbortController | null = null;
|
||||
private maxBlobSize: number = 1024 * 1024 * 100; // 100MB
|
||||
@@ -34,19 +39,24 @@ export class BlobSyncImpl implements BlobSync {
|
||||
constructor(readonly storages: PeerStorageOptions<BlobStorage>) {}
|
||||
|
||||
async downloadBlob(blobId: string, signal?: AbortSignal) {
|
||||
const localBlob = await this.storages.local.get(blobId, signal);
|
||||
if (localBlob) {
|
||||
return localBlob;
|
||||
}
|
||||
|
||||
for (const storage of Object.values(this.storages.remotes)) {
|
||||
const data = await storage.get(blobId, signal);
|
||||
if (data) {
|
||||
await this.storages.local.set(data, signal);
|
||||
return data;
|
||||
try {
|
||||
const localBlob = await this.storages.local.get(blobId, signal);
|
||||
if (localBlob) {
|
||||
return localBlob;
|
||||
}
|
||||
|
||||
for (const storage of Object.values(this.storages.remotes)) {
|
||||
const data = await storage.get(blobId, signal);
|
||||
if (data) {
|
||||
await this.storages.local.set(data, signal);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.error('error when download blob', e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async uploadBlob(blob: BlobRecord, signal?: AbortSignal) {
|
||||
@@ -62,7 +72,11 @@ export class BlobSyncImpl implements BlobSync {
|
||||
return await remote.set(blob, signal);
|
||||
} catch (err) {
|
||||
if (err instanceof OverCapacityError) {
|
||||
this.state$.next({ isStorageOverCapacity: true });
|
||||
this.state$.next({
|
||||
isStorageOverCapacity: true,
|
||||
total: this.state$.value.total,
|
||||
synced: this.state$.value.synced,
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
@@ -70,71 +84,95 @@ export class BlobSyncImpl implements BlobSync {
|
||||
);
|
||||
}
|
||||
|
||||
async fullSync(signal?: AbortSignal) {
|
||||
async fullDownload(signal?: AbortSignal) {
|
||||
throwIfAborted(signal);
|
||||
|
||||
await this.storages.local.connection.waitForConnected(signal);
|
||||
const localList = (await this.storages.local.list(signal)).map(b => b.key);
|
||||
this.state$.next({
|
||||
...this.state$.value,
|
||||
synced: localList.length,
|
||||
});
|
||||
|
||||
for (const [remotePeer, remote] of Object.entries(this.storages.remotes)) {
|
||||
let localList: string[] = [];
|
||||
let remoteList: string[] = [];
|
||||
await Promise.allSettled(
|
||||
Object.entries(this.storages.remotes).map(
|
||||
async ([remotePeer, remote]) => {
|
||||
await remote.connection.waitForConnected(signal);
|
||||
|
||||
await remote.connection.waitForConnected(signal);
|
||||
const remoteList = (await remote.list(signal)).map(b => b.key);
|
||||
|
||||
try {
|
||||
localList = (await this.storages.local.list(signal)).map(b => b.key);
|
||||
throwIfAborted(signal);
|
||||
remoteList = (await remote.list(signal)).map(b => b.key);
|
||||
throwIfAborted(signal);
|
||||
} catch (err) {
|
||||
if (err === MANUALLY_STOP) {
|
||||
throw err;
|
||||
}
|
||||
console.error(`error when sync`, err);
|
||||
continue;
|
||||
}
|
||||
this.state$.next({
|
||||
...this.state$.value,
|
||||
total: Math.max(this.state$.value.total, remoteList.length),
|
||||
});
|
||||
|
||||
const needUpload = difference(localList, remoteList);
|
||||
for (const key of needUpload) {
|
||||
try {
|
||||
const data = await this.storages.local.get(key, signal);
|
||||
throwIfAborted(signal);
|
||||
if (data) {
|
||||
await remote.set(data, signal);
|
||||
throwIfAborted(signal);
|
||||
|
||||
const needDownload = difference(remoteList, localList);
|
||||
for (const key of needDownload) {
|
||||
try {
|
||||
const data = await remote.get(key, signal);
|
||||
throwIfAborted(signal);
|
||||
if (data) {
|
||||
await this.storages.local.set(data, signal);
|
||||
this.state$.next({
|
||||
...this.state$.value,
|
||||
synced: this.state$.value.synced + 1,
|
||||
});
|
||||
throwIfAborted(signal);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err === MANUALLY_STOP) {
|
||||
throw err;
|
||||
}
|
||||
console.error(
|
||||
`error when sync ${key} from [${remotePeer}] to [local]`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err === MANUALLY_STOP) {
|
||||
throw err;
|
||||
}
|
||||
console.error(
|
||||
`error when sync ${key} from [local] to [${remotePeer}]`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const needDownload = difference(remoteList, localList);
|
||||
async fullUpload(signal?: AbortSignal) {
|
||||
throwIfAborted(signal);
|
||||
|
||||
await this.storages.local.connection.waitForConnected(signal);
|
||||
const localList = (await this.storages.local.list(signal)).map(b => b.key);
|
||||
|
||||
await Promise.allSettled(
|
||||
Object.entries(this.storages.remotes).map(
|
||||
async ([remotePeer, remote]) => {
|
||||
await remote.connection.waitForConnected(signal);
|
||||
|
||||
const remoteList = (await remote.list(signal)).map(b => b.key);
|
||||
|
||||
for (const key of needDownload) {
|
||||
try {
|
||||
const data = await remote.get(key, signal);
|
||||
throwIfAborted(signal);
|
||||
if (data) {
|
||||
await this.storages.local.set(data, signal);
|
||||
throwIfAborted(signal);
|
||||
|
||||
const needUpload = difference(localList, remoteList);
|
||||
for (const key of needUpload) {
|
||||
try {
|
||||
const data = await this.storages.local.get(key, signal);
|
||||
throwIfAborted(signal);
|
||||
if (data) {
|
||||
await remote.set(data, signal);
|
||||
throwIfAborted(signal);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err === MANUALLY_STOP) {
|
||||
throw err;
|
||||
}
|
||||
console.error(
|
||||
`error when sync ${key} from [local] to [${remotePeer}]`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err === MANUALLY_STOP) {
|
||||
throw err;
|
||||
}
|
||||
console.error(
|
||||
`error when sync ${key} from [${remotePeer}] to [local]`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
start() {
|
||||
@@ -144,16 +182,12 @@ export class BlobSyncImpl implements BlobSync {
|
||||
|
||||
const abort = new AbortController();
|
||||
this.abort = abort;
|
||||
|
||||
// TODO(@eyhn): fix this, large blob may cause iOS to crash?
|
||||
if (!BUILD_CONFIG.isIOS) {
|
||||
this.fullSync(abort.signal).catch(error => {
|
||||
if (error === MANUALLY_STOP) {
|
||||
return;
|
||||
}
|
||||
console.error('sync blob error', error);
|
||||
});
|
||||
}
|
||||
this.fullUpload(abort.signal).catch(error => {
|
||||
if (error === MANUALLY_STOP) {
|
||||
return;
|
||||
}
|
||||
console.error('sync blob error', error);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface DocSync {
|
||||
readonly state$: Observable<DocSyncState>;
|
||||
docState$(docId: string): Observable<DocSyncDocState>;
|
||||
addPriority(id: string, priority: number): () => void;
|
||||
resetSync(): Promise<void>;
|
||||
}
|
||||
|
||||
export class DocSyncImpl implements DocSync {
|
||||
@@ -127,4 +128,13 @@ export class DocSyncImpl implements DocSync {
|
||||
const undo = this.peers.map(peer => peer.addPriority(id, priority));
|
||||
return () => undo.forEach(fn => fn());
|
||||
}
|
||||
|
||||
async resetSync() {
|
||||
const running = this.abort !== null;
|
||||
this.stop();
|
||||
await this.sync.clearClocks();
|
||||
if (running) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,6 +680,7 @@ export class DocSyncPeer {
|
||||
const cachedClocks = await this.syncMetadata.getPeerRemoteClocks(
|
||||
this.peerId
|
||||
);
|
||||
this.status.remoteClocks.clear();
|
||||
throwIfAborted(signal);
|
||||
for (const [id, v] of Object.entries(cachedClocks)) {
|
||||
this.status.remoteClocks.set(id, v);
|
||||
@@ -690,8 +691,9 @@ export class DocSyncPeer {
|
||||
const maxClockValue = this.status.remoteClocks.max;
|
||||
const newClocks = await this.remote.getDocTimestamps(maxClockValue);
|
||||
for (const [id, v] of Object.entries(newClocks)) {
|
||||
this.actions.updateRemoteClock(id, v);
|
||||
this.status.remoteClocks.set(id, v);
|
||||
}
|
||||
this.statusUpdatedSubject$.next(true);
|
||||
|
||||
for (const [id, v] of Object.entries(newClocks)) {
|
||||
await this.syncMetadata.setPeerRemoteClock(this.peerId, {
|
||||
|
||||
@@ -222,6 +222,10 @@ class WorkerDocSync implements DocSync {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}
|
||||
|
||||
resetSync(): Promise<void> {
|
||||
return this.client.call('docSync.resetSync');
|
||||
}
|
||||
}
|
||||
|
||||
class WorkerBlobSync implements BlobSync {
|
||||
@@ -253,26 +257,23 @@ class WorkerBlobSync implements BlobSync {
|
||||
uploadBlob(blob: BlobRecord, _signal?: AbortSignal): Promise<void> {
|
||||
return this.client.call('blobSync.uploadBlob', blob);
|
||||
}
|
||||
fullSync(signal?: AbortSignal): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const abortListener = () => {
|
||||
reject(signal?.reason);
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
fullDownload(signal?: AbortSignal): Promise<void> {
|
||||
const download = this.client.call('blobSync.fullDownload');
|
||||
|
||||
signal?.addEventListener('abort', abortListener);
|
||||
|
||||
const subscription = this.client.ob$('blobSync.fullSync').subscribe({
|
||||
next() {
|
||||
signal?.removeEventListener('abort', abortListener);
|
||||
resolve();
|
||||
},
|
||||
error(err) {
|
||||
signal?.removeEventListener('abort', abortListener);
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
signal?.addEventListener('abort', () => {
|
||||
download.cancel();
|
||||
});
|
||||
|
||||
return download;
|
||||
}
|
||||
fullUpload(signal?: AbortSignal): Promise<void> {
|
||||
const upload = this.client.call('blobSync.fullUpload');
|
||||
|
||||
signal?.addEventListener('abort', () => {
|
||||
upload.cancel();
|
||||
});
|
||||
|
||||
return upload;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -231,22 +231,13 @@ class StoreConsumer {
|
||||
const undo = this.docSync.addPriority(docId, priority);
|
||||
return () => undo();
|
||||
}),
|
||||
'docSync.resetSync': () => this.docSync.resetSync(),
|
||||
'blobSync.downloadBlob': key => this.blobSync.downloadBlob(key),
|
||||
'blobSync.uploadBlob': blob => this.blobSync.uploadBlob(blob),
|
||||
'blobSync.fullSync': () =>
|
||||
new Observable(subscriber => {
|
||||
const abortController = new AbortController();
|
||||
this.blobSync
|
||||
.fullSync(abortController.signal)
|
||||
.then(() => {
|
||||
subscriber.next(true);
|
||||
subscriber.complete();
|
||||
})
|
||||
.catch(error => {
|
||||
subscriber.error(error);
|
||||
});
|
||||
return () => abortController.abort(MANUALLY_STOP);
|
||||
}),
|
||||
'blobSync.fullDownload': (_, { signal }) =>
|
||||
this.blobSync.fullDownload(signal),
|
||||
'blobSync.fullUpload': (_, { signal }) =>
|
||||
this.blobSync.fullUpload(signal),
|
||||
'blobSync.state': () => this.blobSync.state$,
|
||||
'blobSync.setMaxBlobSize': size => this.blobSync.setMaxBlobSize(size),
|
||||
'blobSync.onReachedMaxBlobSize': () =>
|
||||
|
||||
@@ -81,12 +81,14 @@ interface GroupedWorkerOps {
|
||||
state: [void, DocSyncState];
|
||||
docState: [string, DocSyncDocState];
|
||||
addPriority: [{ docId: string; priority: number }, boolean];
|
||||
resetSync: [void, void];
|
||||
};
|
||||
|
||||
blobSync: {
|
||||
downloadBlob: [string, BlobRecord | null];
|
||||
uploadBlob: [BlobRecord, void];
|
||||
fullSync: [void, boolean];
|
||||
fullDownload: [void, void];
|
||||
fullUpload: [void, void];
|
||||
setMaxBlobSize: [number, void];
|
||||
onReachedMaxBlobSize: [void, number];
|
||||
state: [void, BlobSyncState];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/admin",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/android",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"description": "AFFiNE Desktop Web application",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron-renderer",
|
||||
"private": true,
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "affine bundle",
|
||||
@@ -16,7 +16,7 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@sentry/react": "^8.44.0",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@toeverything/theme": "^1.1.11",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"async-call-rpc": "^6.4.2",
|
||||
"next-themes": "^0.4.4",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"main": "./dist/main.js",
|
||||
"author": "toeverything",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/ios",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"description": "AFFiNE Desktop Web application",
|
||||
"private": true,
|
||||
"browser": "src/index.tsx",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/mobile",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"description": "AFFiNE Desktop Web application",
|
||||
"private": true,
|
||||
"browser": "src/index.tsx",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user