Compare commits

..

35 Commits

Author SHA1 Message Date
zzj3720 9bee2cb0fa fix(editor): improve string conversion logic for checkbox property
- Add a FALSE_VALUES set containing various falsy string representations

- Support Chinese negation terms like "否", "不", "错", etc.

- Optimize the implementation of cellFromString method
2025-02-26 00:11:36 +08:00
zzj3720 1addd17d64 fix(editor): table block supports parsing rich text 2025-02-25 18:52:13 +08:00
darkskygit 842c39c3be feat(native): doc loader for common native (#9941) 2025-02-25 07:50:56 +00:00
EYHN 26674b0cb8 fix(core): fallback when loading share page (#10428) 2025-02-25 07:35:58 +00:00
EYHN cafff4e0eb fix(nbstore): reduce unnecessary sync (#10426) 2025-02-25 07:21:46 +00:00
fundon abc3f9f23f chore(editor): bump @floating-ui/dom to 1.6.13 (#10425) 2025-02-25 07:06:27 +00:00
Brooooooklyn 5dbffba08d feat(native): media capture (#9992) 2025-02-25 06:51:56 +00:00
EYHN 2ec7de7e32 fix(core): add linked doc button (#10417) 2025-02-25 13:03:56 +08:00
liuyi e5e5c0a8ba perf(core): only full sync before exporting (#10408) 2025-02-25 04:41:56 +00:00
EYHN c644a46b8d fix(nbstore): local doc update lost (#10422) 2025-02-25 04:26:49 +00:00
Peng Xiao 7e892b3a7e fix(core): unused blobs query (#10399) 2025-02-25 10:58:43 +08:00
JimmFly 848145150d fix(core): close popover after successful invite in member editor (#10388) 2025-02-25 09:51:22 +08:00
JimmFly dee6be11fb fix(core): reorder plan card action button conditions (#10387) 2025-02-25 09:51:10 +08:00
JimmFly abda70d2c8 fix(core): fix permission checks for export workspace (#10401) 2025-02-25 09:50:43 +08:00
Saul-Mirone 40104f2f87 refactor(editor): remove unused any convension (#10410) 2025-02-24 15:57:49 +00:00
fundon 162b7adc1b fix(editor): should check text length and stop event propagation when adding a link (#10391) 2025-02-24 11:10:05 +00:00
Saul-Mirone 6289981fd1 refactor(editor): optimize extension register and effects (#10406)
Key Changes:

1. **Code Reorganization and Consolidation**
   - Created new centralized extension management through new files:
     - `enableEditorExtension` in `extensions/entry/enable-editor.ts`
     - `enablePreviewExtension` in `extensions/entry/enable-preview.ts`
   - Removed several spec-related files that are now consolidated:
     - Removed `specs/edgeless.ts`
     - Removed `specs/page.ts`
     - Removed `specs/preview.ts`

2. **Template Management**
   - Added new `register-templates.ts` file to handle template registration
   - Moved template registration logic from `specs/edgeless.ts` to this new file
   - Templates now include both edgeless and sticker templates

3. **Extension Management Changes**
   - Simplified extension enabling process through new centralized functions
   - `enableEditorExtension` now handles both page and edgeless modes
   - `enablePreviewExtension` consolidates preview-related extensions
   - Removed duplicate code for extension management

4. **Preview Functionality Updates**
   - Streamlined preview spec management
   - Consolidated footnote configuration
   - Improved theme and preview extension handling

5. **Dependencies and Effects**
   - Updated how effects are registered and managed
   - Simplified initialization process in `index.ts`
   - More organized approach to handling framework providers

The main theme of this PR appears to be code consolidation and simplification, moving from multiple specialized files to more centralized, reusable extension management. This should make the codebase more maintainable and reduce duplication while keeping the same functionality.

The changes primarily affect the editor's extension system, preview functionality, and template management, making these systems more modular and easier to maintain.
2025-02-24 10:37:59 +00:00
EYHN 0e581c915c feat(core): add resetSync button (#10404) 2025-02-24 10:22:34 +00:00
EYHN 59a791fe1f fix(nbstore): fix doc sync logic (#10400) 2025-02-24 10:22:34 +00:00
donteatfriedrice 378bb3795d refactor(editor): use doc title and id as snapshot file name (#10397)
[BS-2549](https://linear.app/affine-design/issue/BS-2549/snap-shot-导出建议使用文档名称作为文件名,而不是一个-id)
2025-02-24 09:32:32 +00:00
Saul-Mirone 60b994f38b refactor(editor): modular custom specs (#10398)
Key Changes:

1. **Removal of Scroll Anchoring Widget**
- Removed the scroll anchoring widget import and its related implementation from `blocksuite/affine/block-root/src/common-specs/widgets.ts`

2. **Enhanced React-Lit Integration**
- Added `ReactWebComponent` type export in `packages/frontend/component/src/lit-react/index.ts`
- Refactored text renderer component to use React integration:
  - Added React import and created `LitTextRenderer` component using `createReactComponentFromLit`
  - Moved the component declaration to a more appropriate location

3. **AI Feature Flag Integration**
- Added feature flag check for AI functionality in `enableAIExtension`
- Only enables AI extensions if the `enable_ai` flag is true

4. **Component Restructuring**
- Moved several components and utilities to dedicated extension files
- Consolidated Lit adapter implementations
- Removed direct widget imports in favor of extension-based approach
- Reorganized editor component structure for better maintainability

5. **File Reorganization**
- Removed `specs/custom/spec-patchers.ts` and distributed its functionality across multiple extension files
- Created new extension files for various features like:
  - Attachment embed views
  - Doc mode service
  - Doc URL handling
  - Edgeless clipboard
  - Mobile support
  - Note configuration
  - Various service patches (notification, peek view, quick search, etc.)

6. **Mobile Support Improvements**
- Refactored mobile extension enablement to be more modular
- Moved mobile-specific widget omissions into a dedicated extension

7. **Type System Improvements**
- Added more specific type imports for editors and components
- Enhanced type safety across the codebase

This PR appears to be a significant refactoring effort focused on:
1. Improving code organization through better separation of concerns
2. Enhancing the integration between React and Lit components
3. Adding feature flag support for AI capabilities
4. Making the codebase more maintainable and modular
5. Improving mobile support
6. Strengthening type safety

The changes suggest a move towards a more extension-based architecture, where functionality is more clearly separated into distinct modules rather than being centralized in larger files.
2025-02-24 08:30:01 +00:00
donteatfriedrice 1b2a4377fd feat(editor): update footnote node style and config (#10392)
[BS-2581](https://linear.app/affine-design/issue/BS-2581/优化-footnote-node-正文样式)
2025-02-24 08:15:04 +00:00
CatsJuice 8b4175c44d chore(core): update free pricing plan description (#10393) 2025-02-24 07:37:30 +00:00
EYHN da7ab51e2d fix(core): remove unnecessary doc loading (#10395) 2025-02-24 07:22:05 +00:00
EYHN a59e640423 fix(nbstore): leave awareness when destroy (#10394) 2025-02-24 07:22:04 +00:00
doouding 9bb74bce6b fix: drag bookmark from note to edgeless (#10389) 2025-02-24 06:13:05 +00:00
doouding a0a97d0751 fix: drag connector and group element (#10385) 2025-02-24 06:13:05 +00:00
forehalo b9e3fc54fd fix(server): include check of prerelease versions (#10386) 2025-02-24 04:44:44 +00:00
forehalo b71fe291d1 fix(core): version control session (#10384) 2025-02-24 04:44:43 +00:00
forehalo f02b57d58b fix(server): too much redundant updates events (#10383) 2025-02-24 04:44:43 +00:00
forehalo 2e0f0c624a chore: set base version to 0.20 (#10382) 2025-02-24 04:44:42 +00:00
Saul-Mirone 9435118ef1 refactor(editor): optimize ai code structure (#10381)
Let me analyze this diff and provide a clear description of the changes.

This PR introduces several significant changes focused on AI integration and code organization in the AFFiNE codebase:

1. **Enhanced SpecBuilder Functionality** (`blocksuite/affine/shared/src/utils/spec/spec-builder.ts`):
   - Added method chaining by returning `this` from `extend`, `omit`, and `replace` methods
   - Added new utility methods:
     - `hasAll(target: ExtensionType[])`: Checks if all specified extensions exist
     - `hasOneOf(target: ExtensionType[])`: Checks if at least one specified extension exists

2. **AI Extensions Modularization**:
   - Split the large AI-related code into separate modular files under `packages/frontend/core/src/blocksuite/ai/extensions/`:
     - `ai-code.ts`: Code block AI integration
     - `ai-edgeless-root.ts`: Edgeless mode AI features
     - `ai-image.ts`: Image block AI capabilities
     - `ai-page-root.ts`: Page root AI integration
     - `ai-paragraph.ts`: Paragraph block AI features
     - `enable-ai.ts`: Central AI extension enablement logic

3. **Widget Improvements**:
   - Enhanced `AffineAIPanelWidget` and `EdgelessCopilotWidget` with proper widget extensions
   - Moved widget-specific extensions into their respective files
   - Added proper type definitions and component registrations

4. **Code Organization**:
   - Simplified exports in `index.ts`
   - Better separation of concerns between different AI-related components
   - More modular approach to AI feature integration

5. **AI Integration Architecture**:
   - Introduced a new `enableAIExtension` function that handles:
     - Replacing standard blocks with AI-enhanced versions
     - Conditional enabling of AI features based on the current spec configuration
     - Extension of AI chat capabilities

The changes primarily focus on improving code organization, maintainability, and the architecture of AI feature integration in the AFFiNE editor. The modularization will make it easier to maintain and extend AI capabilities across different block types and editor modes.
2025-02-24 04:30:08 +00:00
doodlewind 67889d9364 fix(editor): turbo renderer stale frame lag on zooming (#10376)
Before:

https://github.com/user-attachments/assets/593e91a3-042e-4619-93a0-dca21fa942aa

After:

https://github.com/user-attachments/assets/779d7582-f7b2-4135-a97a-d1f65c7cb467

This is only a bug fix that ensures correct baseline rendering result. There'll be more optimizations specific for zooming TBD.
2025-02-24 03:49:04 +00:00
pengx17 5fe4b2b3e4 fix(core): remove tag page semicolon (#10379) 2025-02-24 03:14:06 +00:00
donteatfriedrice 2d41c2ff8d chore: bump theme (#10358) 2025-02-24 10:08:47 +08:00
286 changed files with 11369 additions and 2502 deletions
+1
View File
@@ -1 +1,2 @@
/blocksuite/ @toeverything/blocksuite-core
/packages/frontend/core/src/blocksuite @toeverything/blocksuite-core
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
Generated
+1285 -42
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -15,8 +15,12 @@ affine_common = { path = "./packages/common/native" }
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
anyhow = "1"
base64-simd = "0.8"
block2 = "0.6"
chrono = "0.4"
core-foundation = "0.10"
coreaudio-rs = "0.12"
criterion2 = { version = "2", default-features = false }
dispatch2 = "0.2"
dotenvy = "0.15"
file-format = { version = "0.26", features = ["reader"] }
homedir = "0.3"
@@ -31,6 +35,8 @@ once_cell = "1"
parking_lot = "0.12"
rand = "0.9"
rayon = "1.10"
rubato = "0.16"
screencapturekit = "0.3"
serde = "1"
serde_json = "1"
sha3 = "0.10"
+1 -1
View File
@@ -98,5 +98,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.19.0"
"version": "0.20.0"
}
@@ -23,10 +23,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
@@ -22,10 +22,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -21,10 +21,10 @@
"@blocksuite/icons": "^2.2.3",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
@@ -23,10 +23,10 @@
"@blocksuite/icons": "^2.2.3",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
@@ -23,10 +23,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -20,10 +20,10 @@
"@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
@@ -22,10 +22,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -22,10 +22,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -21,10 +21,10 @@
"@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -23,10 +23,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -21,10 +21,10 @@
"@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -20,10 +20,10 @@
"@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -23,10 +23,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
@@ -20,10 +20,10 @@
"@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+3 -3
View File
@@ -42,10 +42,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+2 -2
View File
@@ -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> = {};
+2 -2
View File
@@ -22,7 +22,7 @@
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.1",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@preact/signals-core": "^1.8.0",
"@vanilla-extract/css": "^1.17.0",
"lit": "^3.2.0",
@@ -39,5 +39,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.19.0"
"version": "0.20.0"
}
@@ -30,7 +30,10 @@ export const tableBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
}
const { walkerContext } = context;
if (o.node.tagName === 'table') {
const tableProps = parseTableFromHtml(o.node);
const astToDelta = context.deltaConverter.astToDelta.bind(
context.deltaConverter
);
const tableProps = parseTableFromHtml(o.node, astToDelta);
walkerContext.openNode(
{
type: 'block',
@@ -25,12 +25,15 @@ export const tableBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
enter: (o, context) => {
const { walkerContext } = context;
if (o.node.type === 'table') {
const astToDelta = context.deltaConverter.astToDelta.bind(
context.deltaConverter
);
walkerContext.openNode(
{
type: 'block',
id: nanoid(),
flavour: TableModelFlavour,
props: parseTableFromMarkdown(o.node),
props: parseTableFromMarkdown(o.node, astToDelta),
children: [],
},
'children'
@@ -7,6 +7,7 @@ import {
BlockPlainTextAdapterExtension,
type BlockPlainTextAdapterMatcher,
} from '@blocksuite/affine-shared/adapters';
import type { DeltaInsert } from '@blocksuite/inline';
import { nanoid } from '@blocksuite/store';
import { createTableProps, formatTable, processTable } from './utils.js';
@@ -21,10 +22,14 @@ export const tableBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher = {
const text = o.node.content;
const rowTexts = text.split('\n');
if (rowTexts.length <= 1) return;
const rowTextLists: string[][] = [];
const rowTextLists: DeltaInsert[][][] = [];
let columnCount: number | null = null;
for (const row of rowTexts) {
const cells = row.split('\t');
const cells = row.split('\t').map<DeltaInsert[]>(text => [
{
insert: text,
},
]);
if (cells.length <= 1) return;
if (columnCount == null) {
columnCount = cells.length;
@@ -5,14 +5,23 @@ import type {
TableRow,
} from '@blocksuite/affine-model';
import {
AdapterTextUtils,
HastUtils,
type HtmlAST,
type MarkdownAST,
} from '@blocksuite/affine-shared/adapters';
import { HastUtils } from '@blocksuite/affine-shared/adapters';
import { generateFractionalIndexingKeyBetween } from '@blocksuite/affine-shared/utils';
import type { DeltaInsert } from '@blocksuite/inline';
import { nanoid } from '@blocksuite/store';
import type { Element, ElementContent } from 'hast';
import type { PhrasingContent, Table as MarkdownTable, TableCell } from 'mdast';
import type { Element } from 'hast';
import type { Table as MarkdownTable } from 'mdast';
type RichTextType = DeltaInsert[];
const createRichText = (text: RichTextType) => {
return {
'$blocksuite:internal:text$': true,
delta: text,
};
};
function calculateColumnWidths(rows: string[][]): number[] {
return (
rows[0]?.map((_, colIndex) =>
@@ -92,15 +101,6 @@ export const processTable = (
});
return table;
};
const getTextFromElement = (element: ElementContent): string => {
if (element.type === 'text') {
return element.value.trim();
}
if (element.type === 'element') {
return element.children.map(child => getTextFromElement(child)).join('');
}
return '';
};
const getAllTag = (node: Element | undefined, tagName: string): Element[] => {
if (!node) {
@@ -120,7 +120,7 @@ const getAllTag = (node: Element | undefined, tagName: string): Element[] => {
return [];
};
export const createTableProps = (rowTextLists: string[][]) => {
export const createTableProps = (deltasLists: RichTextType[][]) => {
const createIdAndOrder = (count: number) => {
const result: { id: string; order: string }[] = Array.from({
length: count,
@@ -135,8 +135,8 @@ export const createTableProps = (rowTextLists: string[][]) => {
}
return result;
};
const columnCount = Math.max(...rowTextLists.map(row => row.length));
const rowCount = rowTextLists.length;
const columnCount = Math.max(...deltasLists.map(row => row.length));
const rowCount = deltasLists.length;
const columns: TableColumn[] = createIdAndOrder(columnCount).map(v => ({
columnId: v.id,
@@ -156,9 +156,9 @@ export const createTableProps = (rowTextLists: string[][]) => {
continue;
}
const cellId = `${row.rowId}:${column.columnId}`;
const text = rowTextLists[i]?.[j];
const text = deltasLists[i]?.[j];
cells[cellId] = {
text: AdapterTextUtils.createText(text ?? ''),
text: createRichText(text ?? []),
};
}
}
@@ -172,7 +172,8 @@ export const createTableProps = (rowTextLists: string[][]) => {
};
export const parseTableFromHtml = (
element: Element
element: Element,
astToDelta: (ast: HtmlAST) => RichTextType
): TableBlockPropsSerialized => {
const headerRows = getAllTag(element, 'thead').flatMap(node =>
getAllTag(node, 'tr').map(tr => getAllTag(tr, 'th'))
@@ -184,33 +185,26 @@ export const parseTableFromHtml = (
getAllTag(node, 'tr').map(tr => getAllTag(tr, 'td'))
);
const allRows = [...headerRows, ...bodyRows, ...footerRows];
const rowTextLists: string[][] = [];
const rowTextLists: RichTextType[][] = [];
allRows.forEach(cells => {
const row: string[] = [];
const row: RichTextType[] = [];
cells.forEach(cell => {
row.push(getTextFromElement(cell));
row.push(astToDelta(cell));
});
rowTextLists.push(row);
});
return createTableProps(rowTextLists);
};
const getTextFromTableCell = (node: TableCell) => {
const getTextFromPhrasingContent = (node: PhrasingContent) => {
if (node.type === 'text') {
return node.value;
}
return '';
};
return node.children.map(child => getTextFromPhrasingContent(child)).join('');
};
export const parseTableFromMarkdown = (node: MarkdownTable) => {
const rowTextLists: string[][] = [];
export const parseTableFromMarkdown = (
node: MarkdownTable,
astToDelta: (ast: MarkdownAST) => RichTextType
) => {
const rowTextLists: RichTextType[][] = [];
node.children.forEach(row => {
const rowText: string[] = [];
const rowText: RichTextType[] = [];
row.children.forEach(cell => {
rowText.push(getTextFromTableCell(cell));
rowText.push(astToDelta(cell));
});
rowTextLists.push(rowText);
});
+3 -3
View File
@@ -20,11 +20,11 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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;
},
};
+3 -3
View File
@@ -20,10 +20,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/store": "workspace:*",
"@emotion/hash": "^0.9.2",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
@@ -3,17 +3,30 @@ import { propertyType } from '../../core/property/property-config.js';
export const checkboxPropertyType = propertyType('checkbox');
const FALSE_VALUES = new Set([
'false',
'no',
'0',
'',
'undefined',
'null',
'否',
'不',
'错',
'错误',
'取消',
'关闭',
]);
export const checkboxPropertyModelConfig =
checkboxPropertyType.modelConfig<boolean>({
name: 'Checkbox',
type: () => t.boolean.instance(),
defaultData: () => ({}),
cellToString: ({ value }) => (value ? 'True' : 'False'),
cellFromString: ({ value }) => {
return {
value: value !== 'False',
};
},
cellFromString: ({ value }) => ({
value: !FALSE_VALUES.has((value?.trim() ?? '').toLowerCase()),
}),
cellToJson: ({ value }) => value ?? null,
cellFromJson: ({ value }) =>
typeof value !== 'boolean' ? undefined : value,
@@ -23,10 +23,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
@@ -22,10 +22,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+2 -2
View File
@@ -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;
+3 -3
View File
@@ -19,10 +19,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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();
}
@@ -25,10 +25,10 @@
"@blocksuite/icons": "^2.2.1",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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)}`
);
+2 -1
View File
@@ -63,5 +63,6 @@
"devDependencies": {
"@vanilla-extract/vite-plugin": "^5.0.0",
"vitest": "3.0.6"
}
},
"version": "0.20.0"
}
+1 -1
View File
@@ -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() {
+1 -1
View File
@@ -51,5 +51,5 @@
"devDependencies": {
"vitest": "3.0.6"
},
"version": "0.19.0"
"version": "0.20.0"
}
+1 -1
View File
@@ -34,5 +34,5 @@
"devDependencies": {
"vitest": "3.0.6"
},
"version": "0.19.0"
"version": "0.20.0"
}
+1 -1
View File
@@ -46,5 +46,5 @@
"!dist/__tests__",
"shim.d.ts"
],
"version": "0.19.0"
"version": "0.20.0"
}
+1 -1
View File
@@ -32,5 +32,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.19.0"
"version": "0.20.0"
}
+3 -3
View File
@@ -24,11 +24,11 @@
"@blocksuite/icons": "^2.2.2",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@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"
}
+1 -1
View File
@@ -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"
}
+1 -1
View File
@@ -29,5 +29,5 @@
"type": "git",
"url": "https://github.com/toeverything/blocksuite.git"
},
"version": "0.19.0"
"version": "0.20.0"
}
+1 -1
View File
@@ -19,5 +19,5 @@
],
"ext": "ts,md,json"
},
"version": "0.19.0"
"version": "0.20.0"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@affine/monorepo",
"version": "0.19.0",
"version": "0.20.0",
"private": true,
"author": "toeverything",
"license": "MIT",
+1 -1
View File
@@ -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 -1
View File
@@ -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')
+27 -173
View File
@@ -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,
+1 -1
View File
@@ -11,5 +11,5 @@
"@types/debug": "^4.1.12",
"vitest": "3.0.6"
},
"version": "0.19.0"
"version": "0.20.0"
}
+1 -1
View File
@@ -21,5 +21,5 @@
"dependencies": {
"zod": "^3.24.1"
},
"version": "0.19.0"
"version": "0.20.0"
}
+1 -1
View File
@@ -44,5 +44,5 @@
"electron": "*",
"react-dom": "^19.0.0"
},
"version": "0.19.0"
"version": "0.20.0"
}
+48
View File
@@ -3,15 +3,63 @@ edition = "2021"
name = "affine_common"
version = "0.1.0"
[features]
default = []
doc-loader = ["docx-parser", "infer", "path-ext", "pdf-extract", "readability", "serde_json", "strum_macros", "text-splitter", "thiserror", "tree-sitter", "url"]
tree-sitter = [
"cc",
"dep:tree-sitter",
"dep:tree-sitter-c",
"dep:tree-sitter-c-sharp",
"dep:tree-sitter-cpp",
"dep:tree-sitter-go",
"dep:tree-sitter-java",
"dep:tree-sitter-javascript",
"dep:tree-sitter-kotlin-ng",
"dep:tree-sitter-python",
"dep:tree-sitter-rust",
"dep:tree-sitter-scala",
"dep:tree-sitter-typescript",
]
[dependencies]
chrono = { workspace = true }
rand = { workspace = true }
sha3 = { workspace = true }
docx-parser = { git = "https://github.com/toeverything/docx-parser", optional = true }
infer = { version = "0.19.0", optional = true }
path-ext = { version = "0.1.1", optional = true }
pdf-extract = { version = "0.8.2", optional = true }
readability = { version = "0.3.0", optional = true, default-features = false }
serde_json = { version = "1.0", optional = true }
strum_macros = { version = "0.26.2", optional = true }
text-splitter = { version = "0.22", features = ["markdown", "tiktoken-rs"], optional = true }
thiserror = { version = "1", optional = true }
tree-sitter = { version = "0.25", optional = true }
tree-sitter-c = { version = "0.23", optional = true }
tree-sitter-c-sharp = { version = "0.23", optional = true }
tree-sitter-cpp = { version = "0.23", optional = true }
tree-sitter-go = { version = "0.23", optional = true }
tree-sitter-java = { version = "0.23", optional = true }
tree-sitter-javascript = { version = "0.23", optional = true }
tree-sitter-kotlin-ng = { version = "1.1", optional = true }
tree-sitter-python = { version = "0.23", optional = true }
tree-sitter-rust = { version = "0.23", optional = true }
tree-sitter-scala = { version = "0.23", optional = true }
tree-sitter-typescript = { version = "0.23", optional = true }
url = { version = "2.5", optional = true }
tiktoken-rs = { workspace = true }
[dev-dependencies]
criterion2 = { workspace = true }
rayon = { workspace = true }
[build-dependencies]
cc = { version = "1", optional = true }
[[bench]]
harness = false
name = "hashcash"
Binary file not shown.
@@ -0,0 +1,28 @@
# DOCX Demo
# <a name="OLE_LINK1"></a><a name="OLE_LINK2"></a><a name="_Toc359077851"></a>Demonstration of DOCX support in calibre
This document demonstrates the ability of the calibre DOCX Input plugin to convert the various typographic features in a Microsoft Word (2007 and newer) document. Convert this document to a modern ebook format, such as AZW3 for Kindles or EPUB for other ebook readers, to see it in action.
There is support for images, tables, lists, footnotes, endnotes, links, dropcaps and various types of text and paragraph level formatting.
To see the DOCX conversion in action, simply add this file to calibre using the **“Add Books” **button and then click “**Convert”. ** Set the output format in the top right corner of the conversion dialog to EPUB or AZW3 and click **“OK”**.
# <a name="_Toc359077852"></a>Text Formatting
## <a name="_Toc359077853"></a>Inline formatting
Here, we demonstrate various types of inline text formatting and the use of embedded fonts.
Here is some **bold, ***italic, ****bold-italic, ***__underlined __and ~~struck out ~~ text. Then, we have a superscript and a subscript. Now we see some red, green and blue text. Some text with a yellow highlight. Some text in a box. Some text in inverse video.
A paragraph with styled text: subtle emphasis followed by strong text and intense emphasis. This paragraph uses document wide styles for styling rather than inline text properties as demonstrated in the previous paragraph — calibre can handle both with equal ease.
## <a name="_Toc359077854"></a>Fun with fonts
This document has embedded the Ubuntu font family. The body text is in the Ubuntu typeface, here is some text in the Ubuntu Mono typeface, notice how every letter has the same width, even i and m. Every embedded font will automatically be embedded in the output ebook during conversion.
## ***<a name="_Paragraph_level_formatting"></a>******<a name="_Toc359077855"></a>******Paragraph level formatting***
You can do crazy things with paragraphs, if the urge strikes you. For instance this paragraph is right aligned and has a right border. It has also been given a light gray background.
@@ -0,0 +1,28 @@
For the lovers of poetry amongst you, paragraphs with hanging indents, like this often come in handy. You can use hanging indents to ensure that a line of poetry retains its individual identity as a line even when the screen is too narrow to display it as a single line. Not only does this paragraph have a hanging indent, it is also has an extra top margin, setting it apart from the preceding paragraph.
# <a name="_Toc359077856"></a>Tables
| | |
| ----------- | -------- |
| ITEM | NEEDED |
| Books | 1 |
| Pens | 3 |
| Pencils | 2 |
| Highlighter | 2 colors |
| Scissors | 1 pair |
Tables in Word can vary from the extremely simple to the extremely complex. calibre tries to do its best when converting tables. While you may run into trouble with the occasional table, the vast majority of common cases should be converted very well, as demonstrated in this section. Note that for optimum results, when creating tables in Word, you should set their widths using percentages, rather than absolute units. To the left of this paragraph is a floating two column table with a nice green border and header row.
Now lets look at a fancier table—one with alternating row colors and partial borders. This table is stretched out to take 100% of the available width.
| | | | | | |
| ------------ | ------- | ------- | ------- | ------- | ------- |
| City or Town | Point A | Point B | Point C | Point D | Point E |
| Point A | — | | | | |
| Point B | 87 | — | | | |
| Point C | 64 | 56 | — | | |
| Point D | 37 | 32 | 91 | — | |
| Point E | 93 | 35 | 54 | 43 | — |
Next, we see a table with special formatting in various locations. Notice how the formatting for the header row and sub header rows is preserved.
@@ -0,0 +1,21 @@
| | | | |
| ---------------- | ------------- | ------------------- | ------ |
| College | New students | Graduating students | Change |
| | Undergraduate | | |
| Cedar University | 110 | 103 | +7 |
| Oak Institute | 202 | 210 | -8 |
| | Graduate | | |
| Cedar University | 24 | 20 | +4 |
| Elm College | 43 | 53 | -10 |
| Total | 998 | 908 | 90 |
Source: Fictitious data, for illustration purposes only
Next, we have something a little more complex, a nested table, i.e. a table inside another table. Additionally, the inner table has some of its cells merged. The table is displayed horizontally centered.
| | |
| --- | -------------------------------------------------------------- |
| | To the left is a table inside a table, with some cells merged. |
We end with a fancy calendar, note how much of the original formatting is preserved. Note that this table will only display correctly on relatively wide screens. In general, very wide tables or tables whose cells have fixed width requirements dont fare well in ebooks.
@@ -0,0 +1,18 @@
| | | | | | | | | | | | | |
| ------------- | | --- | | --- | | --- | | --- | | --- | | --- |
| December 2007 | | | | | | | | | | | | |
| Sun | | Mon | | Tue | | Wed | | Thu | | Fri | | Sat |
| | | | | | | | | | | | | 1 |
| | | | | | | | | | | | | |
| 2 | | 3 | | 4 | | 5 | | 6 | | 7 | | 8 |
| | | | | | | | | | | | | |
| 9 | | 10 | | 11 | | 12 | | 13 | | 14 | | 15 |
| | | | | | | | | | | | | |
| 16 | | 17 | | 18 | | 19 | | 20 | | 21 | | 22 |
| | | | | | | | | | | | | |
| 23 | | 24 | | 25 | | 26 | | 27 | | 28 | | 29 |
| | | | | | | | | | | | | |
| 30 | | 31 | | | | | | | | | | |
# <a name="_Toc359077857"></a>Structural Elements
@@ -0,0 +1,20 @@
Miscellaneous structural elements you can add to your document, like footnotes, endnotes, dropcaps and the like.
## <a name="_Toc359077858"></a>Footnotes & Endnotes
Footnotes and endnotes are automatically recognized and both are converted to endnotes, with backlinks for maximum ease of use in ebook devices.
## <a name="_Toc359077859"></a>Dropcaps
D
rop caps are used to emphasize the leading paragraph at the start of a section. In Word it is possible to specify how many lines of text a drop-cap should use. Because of limitations in ebook technology, this is not possible when converting. Instead, the converted drop cap will use font size and line height to simulate the effect as well as possible. While not as good as the original, the result is usually tolerable. This paragraph has a “D” dropcap set to occupy three lines of text with a font size of 58.5 pts. Depending on the screen width and capabilities of the device you view the book on, this dropcap can look anything from perfect to ugly.
## <a name="_Toc359077860"></a>Links
Two kinds of links are possible, those that refer to an external website and those that refer to locations inside the document itself. Both are supported by calibre. For example, here is a link pointing to the [calibre download page](http://calibre-ebook.com/download). Then we have a link that points back to the section on [paragraph level formatting](#_Paragraph_level_formatting) in this document.
## <a name="_Toc359077861"></a>Table of Contents
There are two approaches that calibre takes when generating a Table of Contents. The first is if the Word document has a Table of Contents itself. Provided that the Table of Contents uses hyperlinks, calibre will automatically use it. The levels of the Table of Contents are identified by their left indent, so if you want the ebook to have a multi-level Table of Contents, make sure you create a properly indented Table of Contents in Word.
@@ -0,0 +1,30 @@
If no Table of Contents is found in the document, then a table of contents is automatically generated from the headings in the document. A heading is identified as something that has the Heading 1 or Heading 2, etc. style applied to it. These headings are turned into a Table of Contents with Heading 1 being the topmost level, Heading 2 the second level and so on.
You can see the Table of Contents created by calibre by clicking the Table of Contents button in whatever viewer you are using to view the converted ebook.
# <a name="_Toc359077862"></a>Images
Images can be of three main types. Inline images are images that are part of the normal text flow, like this image of a green dot ![dot_green.png](./media/image2.png). Inline images do not cause breaks in the text and are usually small in size. The next category of image is a floating image, one that “floats “ on the page and is surrounded by text. Word supports more types of floating images than are possible with current ebook technology, so the conversion maps floating images to simple left and right floats, as you can see with the left and right arrow images on the sides of this paragraph.
The final type of image is a “block” image, one that becomes a paragraph on its own and has no text on either side. Below is a centered green dot.
Centered images like this are useful for large pictures that should be a focus of attention.
Generally, it is not possible to translate the exact positioning of images from a Word document to an ebook. That is because in Word, image positioning is specified in absolute units from the page boundaries. There is no analogous technology in ebooks, so the conversion will usually end up placing the image either centered or floating close to the point in the text where it was inserted, not necessarily where it appears on the page in Word.
# <a name="_Toc359077863"></a>Lists
All types of lists are supported by the conversion, with the exception of lists that use fancy bullets, these get converted to regular bullets.
## <a name="_Toc359077864"></a>Bulleted List
- One
- Two
## <a name="_Toc359077865"></a>Numbered List
1. One, with a very long line to demonstrate that the hanging indent for the list is working correctly
2. Two
@@ -0,0 +1,37 @@
## <a name="_Toc359077866"></a>Multi-level Lists
1. One
2. Two
3. Three
4. Four with a very long line to demonstrate that the hanging indent for the list is working correctly.
5. Five
6. Six
A Multi-level list with bullets:
- One
- Two
- This bullet uses an image as the bullet item
- Four
- Five
## <a name="_Toc359077867"></a>Continued Lists
i. One
j. Two
An interruption in our regularly scheduled listing, for this essential and very relevant public service announcement.
k. We now resume our normal programming
l. Four

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