Compare commits

...

59 Commits

Author SHA1 Message Date
zzj3720 0ef01002b4 fix(editor): slice to snapshot supports flat data structures 2025-02-27 15:29:17 +08:00
Saul-Mirone b7598ff177 chore: optimize code 2025-02-27 12:57:09 +08:00
zzj3720 b07eb5678d fix(editor): toDraftModal supports flat data structures 2025-02-27 12:39:20 +08:00
pengx17 be94f3fc17 fix(native): potential sharablecontent icon/name crash (#10464) 2025-02-27 03:19:52 +00:00
Yifeng Wang e9484e8e15 refactor(editor): remove non null asserts in turbo renderer (#10454) 2025-02-27 10:47:26 +08:00
pengx17 f25266ec88 fix(editor): ai chat panel textarea selection issue (#10461)
fix AF-2244
2025-02-27 02:26:57 +00:00
doodlewind 3252dd7a31 feat(editor): automatically hide canvas optimized blocks (#10451)
Qualified DOM blocks can now be optimized away automatically.

<img alt="image.png" width="500" src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/102bf813-154a-4816-9eb0-2c9c0ce01fe7.png">

Since this is under development, verifying state correctness is more important than rendering details.

This won't affect current production version since the passive APIs added to `GfxViewportElement` are disabled by the `enableOptimization = false` config.
2025-02-27 02:10:49 +00:00
akumatus 903d260880 fix(core): ai chat panel scrolling dizziness problem (#10458)
Fix issue [AF-2281](https://linear.app/affine-design/issue/AF-2281).

### What Changed?
- During the re-rendering process of the rich-text editor, the container height is always expanded.
- If the user manually scrolls the chat panel, immediately stop automatically scrolling

[录屏2025-02-27 07.30.08.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/624ea4fa-b8dd-4cf2-a9be-6997bdabc97b.mov" />](https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/624ea4fa-b8dd-4cf2-a9be-6997bdabc97b.mov)
2025-02-26 23:59:12 +00:00
Saul-Mirone 2c79d7229f refactor(editor): remove legacy service watcher (#10455)
The main changes in this PR involve replacing the deprecated `BlockServiceWatcher` with the new `LifeCycleWatcher` across multiple files. Here's a detailed breakdown:

1. **Core Architectural Change:**
   - Removed `BlockServiceWatcher` class completely (deleted file)
   - Migrated to `LifeCycleWatcher` as the new standard for watching component lifecycle events

2. **Key Changes in Implementation:**
   - Changed from using `blockService.specSlots` events to using `view.viewUpdated` events
   - Replaced `flavour` static property with `key` static property
   - Updated event handling to use more specific payload type checking

3. **Major File Changes:**
   - Modified multiple block components:
     - Embed synced doc block
     - Frame preview
     - Edgeless root spec
     - AI-related components (code, image, paragraph, etc.)
     - Quick search service
     - Edgeless clipboard

4. **Pattern of Changes:**
   The migration follows a consistent pattern:
   ```typescript
   // Old pattern
   class SomeWatcher extends BlockServiceWatcher {
     static override readonly flavour = 'some:flavour';
     mounted() {
       this.blockService.specSlots.viewConnected.on(...)
     }
   }

   // New pattern
   class SomeWatcher extends LifeCycleWatcher {
     static override key = 'some-watcher';
     mounted() {
       const { view } = this.std;
       view.viewUpdated.on(payload => {
         if (payload.type !== 'block' || payload.method !== 'add') return;
         // Handle event
       });
     }
   }
   ```

5. **Benefits:**
   - More explicit and type-safe event handling
   - Cleaner architecture by removing deprecated code
   - More consistent approach to lifecycle management
   - Better separation of concerns

This appears to be a significant architectural improvement that modernizes the codebase by removing deprecated patterns and standardizing on a more robust lifecycle management system.
2025-02-26 15:15:45 +00:00
Saul-Mirone fd6d96a38e refactor(editor): use transformer from store when possible (#10453) 2025-02-26 14:15:04 +00:00
Saul-Mirone 1c5e360d7e feat(editor): add widget in viewUpdated slot (#10452) 2025-02-26 13:24:32 +00:00
doodlewind 589622043c fix(editor): list toggle position offset (#10448)
Before:

![image](https://github.com/user-attachments/assets/fbddc396-1078-4c1e-8e2c-c26c6e4a6d61)

After:

<img width="579" alt="image" src="https://github.com/user-attachments/assets/7b049b53-269b-484e-ba76-fa6f46a2004c" />

This centering approach won't affect heading blocks:

<img width="267" alt="image" src="https://github.com/user-attachments/assets/3f3217e3-7e23-43fc-a7e5-33b6eadccc88" />
2025-02-26 12:09:00 +00:00
Saul-Mirone ce87dcf58e feat(editor): schema extension (#10447)
1. **Major Architectural Change: Schema Management**
   - Moved from `workspace.schema` to `store.schema` throughout the codebase
   - Removed schema property from Workspace and Doc interfaces
   - Added `BlockSchemaExtension` pattern across multiple block types

2. **Block Schema Extensions Added**
   - Added new `BlockSchemaExtension` to numerous block types including:
     - DataView, Surface, Attachment, Bookmark, Code
     - Database, Divider, EdgelessText, Embed blocks (Figma, Github, HTML, etc.)
     - Frame, Image, Latex, List, Note, Paragraph
     - Root, Surface Reference, Table blocks

3. **Import/Export System Updates**
   - Updated import functions to accept `schema` parameter:
     - `importHTMLToDoc`
     - `importHTMLZip`
     - `importMarkdownToDoc`
     - `importMarkdownZip`
     - `importNotionZip`
   - Modified export functions to use new schema pattern

4. **Test Infrastructure Updates**
   - Updated test files to use new schema extensions
   - Modified test document creation to include schema extensions
   - Removed direct schema registration in favor of extensions

5. **Service Layer Changes**
   - Updated various services to use `getAFFiNEWorkspaceSchema()`
   - Modified transformer initialization to use document schema
   - Updated collection initialization patterns

6. **Version Management**
   - Removed version-related properties and methods from:
     - `WorkspaceMetaImpl`
     - `TestMeta`
     - `DocImpl`
   - Removed `blockVersions` and `workspaceVersion/pageVersion`

7. **Store and Extension Updates**
   - Added new store extensions and adapters
   - Updated store initialization patterns
   - Added new schema-related functionality in store extension

This PR represents a significant architectural shift in how schemas are managed, moving from a workspace-centric to a store-centric approach, while introducing a more extensible block schema system through `BlockSchemaExtension`. The changes touch multiple layers of the application including core functionality, services, testing infrastructure, and import/export capabilities.
2025-02-26 11:31:29 +00:00
L-Sun 2732b96d00 fix(editor): overflow of embed github card in edgeless note (#10442)
This PR fixes the overflow of the `embed-github-card` inside edgeless notes.

https://github.com/user-attachments/assets/21775d0f-e4c8-4fcc-86d8-aafb27033358
2025-02-26 09:33:57 +00:00
doodlewind 0f8c837fbe refactor(editor): simplify renderer state (#10441)
The redundant `monitoring` state has been removed. The new `ready` state is used for querying if DOM elements can be safely optimized away.
2025-02-26 08:39:58 +00:00
fundon c058f94e15 chore(editor): improve color formatting tests (#10429) 2025-02-26 08:19:54 +00:00
darkskygit d25b216311 feat(server): adapt doc loader for server native (#9942) 2025-02-26 08:05:20 +00:00
CatsJuice e1fd8f5d80 fix(core): correctly toggle visibility of starter-bar based on doc.isEmpty (#10439) 2025-02-26 07:49:51 +00:00
fundon 866b096304 fix(core): fix doc url parsing with custom domain names (#10444)
Closes: [AF-2279](https://affine-pro.slack.com/archives/C06CTBH5L4R/p1740552397245649?thread_ts=1740547457.278239&cid=C06CTBH5L4R)
2025-02-26 07:35:25 +00:00
doodlewind e38e59d4e5 refactor(editor): request refresh after finding stale bitmap (#10438)
This ensures a bitmap will be eventually generated after tile got invalidated.
2025-02-26 06:49:09 +00:00
EYHN 7dbc1e300d fix(ios): fix magic link sign in (#10436) 2025-02-26 06:32:16 +00:00
zzj3720 1a9bfeaa2c fix(editor): table block supports parsing rich text (#10430)
fix: BS-2685
2025-02-26 04:56:55 +00:00
doodlewind 97cc814a22 fix(editor): remote cursor color inconsistency (#10437)
Fixed:

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/c2db021e-6ee9-4022-913c-84b84953d36e.png)
2025-02-26 04:42:05 +00:00
donteatfriedrice d63f16da5e fix(editor): affine preview root style (#10420)
Fix [BS-2677](https://linear.app/affine-design/issue/BS-2677/linked-doc-embed-view样式错误)

1. Only show the border of embed synced doc block (in note) when hover.
2. Fix affine preview root padding style, set padding only when affine preview root in embed synced doc block (in surface).
3. Only add the footnote config extension to the chat panel and chat block center peek. For footnotes in other page preview scenarios, such as footnote nodes within embed synced doc blocks or embed linked doc blocks, the hover effect should be maintained.
2025-02-26 04:25:24 +00:00
zzj3720 0e4a79959f fix(editor): improve string conversion logic for checkbox property (#10433)
fix: BS-2465

- Add a FALSE_VALUES set containing various falsy string representations

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

- Optimize the implementation of cellFromString method
2025-02-25 23:23:00 +00:00
doodlewind f3911b1b5e fix(editor): discard stale layout bitmap in turbo renderer (#10427)
Fixes this bug caused by stale bitmap:

[Screen Recording 2025-02-24 at 6.10.19 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/lEGcysB4lFTEbCwZ8jMv/3e24f4b7-6f95-4c7c-a79a-b8e4ffdb3b10.mov" />](https://app.graphite.dev/media/video/lEGcysB4lFTEbCwZ8jMv/3e24f4b7-6f95-4c7c-a79a-b8e4ffdb3b10.mov)
2025-02-25 10:51:55 +00: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
420 changed files with 12863 additions and 3452 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
+1284 -42
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -15,11 +15,16 @@ 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"
libc = "0.2"
mimalloc = "0.1"
napi = { version = "3.0.0-alpha.12", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi-build = { version = "2" }
@@ -31,6 +36,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"
}
@@ -5,7 +5,11 @@ import {
type InsertToPosition,
} from '@blocksuite/affine-shared/utils';
import type { DataViewDataType } from '@blocksuite/data-view';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
type Props = {
title: string;
@@ -93,3 +97,6 @@ export const DataViewBlockSchema = defineBlockSchema({
return new DataViewBlockModel();
},
});
export const DataViewBlockSchemaExtension =
BlockSchemaExtension(DataViewBlockSchema);
@@ -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"
}
@@ -9,6 +9,7 @@ import {
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import { findAncestorModel } from '@blocksuite/affine-shared/utils';
import type { BlockService } from '@blocksuite/block-std';
import type { GfxCompatibleProps } from '@blocksuite/block-std/gfx';
import type { BlockModel } from '@blocksuite/store';
@@ -57,7 +58,15 @@ export class EmbedBlockComponent<
) {
this.style.display = 'block';
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
const insideNote = findAncestorModel(
this.model,
m => m.flavour === 'affine:note'
);
if (
!insideNote &&
this.std.get(DocModeProvider).getEditorMode() === 'edgeless'
) {
this.style.minWidth = `${EMBED_CARD_MIN_WIDTH}px`;
}
}
@@ -2,6 +2,7 @@ import { css, html } from 'lit';
export const styles = css`
.affine-embed-github-block {
container: affine-embed-github-block / inline-size;
box-sizing: border-box;
display: flex;
width: 100%;
@@ -24,6 +25,7 @@ export const styles = css`
padding: 12px;
border-radius: var(--1, 0px);
opacity: var(--add, 1);
overflow: hidden;
}
.affine-embed-github-content-title {
@@ -376,6 +378,15 @@ export const styles = css`
display: none;
}
}
@container affine-embed-github-block (width < 375px) {
.affine-embed-github-content {
width: 100%;
}
.affine-embed-github-banner {
display: none;
}
}
`;
export const GithubIcon = html`<svg
@@ -26,9 +26,9 @@ import {
} from '@blocksuite/affine-shared/utils';
import {
BlockSelection,
BlockServiceWatcher,
BlockStdScope,
type EditorHost,
LifeCycleWatcher,
} from '@blocksuite/block-std';
import {
GfxControllerIdentifier,
@@ -124,27 +124,31 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
this.std.getOptional(EditorSettingProvider) ??
signal(GeneralSettingSchema.parse({}));
class EmbedSyncedDocWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:embed-synced-doc';
class EmbedSyncedDocWatcher extends LifeCycleWatcher {
static override key = 'embed-synced-doc-watcher';
override mounted() {
const disposableGroup = this.blockService.disposables;
const slots = this.blockService.specSlots;
disposableGroup.add(
slots.viewConnected.on(({ component }) => {
const nextComponent = component as EmbedSyncedDocBlockComponent;
override mounted(): void {
const { view } = this.std;
view.viewUpdated.on(payload => {
if (
payload.type !== 'block' ||
payload.view.model.flavour !== 'affine:embed-synced-doc'
) {
return;
}
const nextComponent = payload.view as EmbedSyncedDocBlockComponent;
if (payload.method === 'add') {
nextComponent.depth = nextDepth;
currentDisposables.add(() => {
nextComponent.depth = 0;
});
})
);
disposableGroup.add(
slots.viewDisconnected.on(({ component }) => {
const nextComponent = component as EmbedSyncedDocBlockComponent;
return;
}
if (payload.method === 'delete') {
nextComponent.depth = 0;
})
);
return;
}
});
}
}
@@ -231,6 +235,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
[theme]: true,
surface: false,
selected: this.selected$.value,
'show-hover-border': true,
})}
@click=${this._handleClick}
style=${containerStyleMap}
@@ -57,10 +57,13 @@ export const blockStyles = css`
}
.affine-embed-synced-doc-container {
border: 1px solid var(--affine-border-color);
border: 1px solid transparent;
border-radius: 8px;
overflow: hidden;
}
.affine-embed-synced-doc-container.show-hover-border:hover {
border-color: var(--affine-border-color);
}
.affine-embed-synced-doc-container.page {
display: block;
width: 100%;
@@ -151,7 +154,12 @@ export const blockStyles = css`
}
.affine-embed-synced-doc-container.surface {
border-color: var(--affine-border-color);
background: var(--affine-background-primary-color);
affine-preview-root {
padding: 0 24px;
}
}
.affine-embed-synced-doc-container
+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)}`
);
@@ -64,7 +64,6 @@ import {
BlockSnapshotSchema,
fromJSON,
type SliceSnapshot,
Transformer,
} from '@blocksuite/store';
import DOMPurify from 'dompurify';
import * as Y from 'yjs';
@@ -373,15 +372,7 @@ export class EdgelessClipboardController extends PageClipboard {
if (mayBeSurfaceDataJson !== undefined) {
const elementsRawData = JSON.parse(mayBeSurfaceDataJson);
const { snapshot, blobs } = elementsRawData;
const job = new Transformer({
schema: this.std.workspace.schema,
blobCRUD: this.std.workspace.blobSync,
docCRUD: {
create: (id: string) => this.std.workspace.createDoc({ id }),
get: (id: string) => this.std.workspace.getDoc(id),
delete: (id: string) => this.std.workspace.removeDoc(id),
},
});
const job = this.std.store.getTransformer();
const map = job.assetsManager.getAssets();
decodeClipboardBlobs(blobs, map);
for (const blobId of map.keys()) {
@@ -1377,15 +1368,7 @@ export async function prepareClipboardData(
selectedAll: GfxModel[],
std: BlockStdScope
) {
const job = new Transformer({
schema: std.workspace.schema,
blobCRUD: std.workspace.blobSync,
docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }),
get: (id: string) => std.workspace.getDoc(id),
delete: (id: string) => std.workspace.removeDoc(id),
},
});
const job = std.store.getTransformer();
const selected = await Promise.all(
selectedAll.map(async selected => {
const data = serializeElement(selected, selectedAll, job);
@@ -1,9 +1,9 @@
import type { FrameBlockModel } from '@blocksuite/affine-model';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import {
BlockServiceWatcher,
BlockStdScope,
type EditorHost,
LifeCycleWatcher,
ShadowlessElement,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
@@ -117,22 +117,26 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
private _initSpec() {
const refreshViewport = this._refreshViewport.bind(this);
class FramePreviewWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
class FramePreviewWatcher extends LifeCycleWatcher {
static override key = 'frame-preview-watcher';
override mounted() {
const blockService = this.blockService;
blockService.disposables.add(
blockService.specSlots.viewConnected.on(({ component }) => {
const edgelessBlock =
component as EdgelessRootPreviewBlockComponent;
edgelessBlock.editorViewportSelector = 'frame-preview-viewport';
edgelessBlock.service.viewport.sizeUpdated.once(() => {
refreshViewport();
});
})
);
const { view } = this.std;
view.viewUpdated.on(payload => {
if (
payload.type !== 'block' ||
payload.method !== 'add' ||
payload.view.model.flavour !== 'affine:page'
) {
return;
}
const edgelessBlock =
payload.view as EdgelessRootPreviewBlockComponent;
edgelessBlock.editorViewportSelector = 'frame-preview-viewport';
edgelessBlock.service.viewport.sizeUpdated.once(() => {
refreshViewport();
});
});
}
}
this._previewSpec.extend([FramePreviewWatcher]);
@@ -1,12 +1,15 @@
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,
LifeCycleWatcher,
WidgetViewExtension,
} from '@blocksuite/block-std';
import { ToolController } from '@blocksuite/block-std/gfx';
import {
GfxControllerIdentifier,
ToolController,
} from '@blocksuite/block-std/gfx';
import type { ExtensionType } from '@blocksuite/store';
import { literal, unsafeStatic } from 'lit/static-html.js';
@@ -20,31 +23,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,
@@ -71,17 +59,12 @@ export const edgelessToolbarWidget = WidgetViewExtension(
literal`${unsafeStatic(EDGELESS_TOOLBAR_WIDGET)}`
);
class EdgelessLocker extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
class EdgelessLocker extends LifeCycleWatcher {
static override key = 'edgeless-locker';
override mounted() {
const service = this.blockService;
service.disposables.add(
service.specSlots.viewConnected.on(({ service }) => {
// Does not allow the user to move and zoom.
(service as EdgelessRootService).locked = true;
})
);
const { viewport } = this.std.get(GfxControllerIdentifier);
viewport.locked = true;
}
}
@@ -16,7 +16,7 @@ import {
type DocSnapshot,
DocSnapshotSchema,
type SnapshotNode,
Transformer,
type Transformer,
} from '@blocksuite/store';
import type * as Y from 'yjs';
/**
@@ -90,16 +90,7 @@ export class TemplateJob {
type: TemplateType;
constructor({ model, type, middlewares }: TemplateJobConfig) {
this.job = new Transformer({
schema: model.doc.workspace.schema,
blobCRUD: model.doc.workspace.blobSync,
docCRUD: {
create: (id: string) => model.doc.workspace.createDoc({ id }),
get: (id: string) => model.doc.workspace.getDoc(id),
delete: (id: string) => model.doc.workspace.removeDoc(id),
},
middlewares: [],
});
this.job = model.doc.getTransformer();
this.model = model;
this.type = TEMPLATE_TYPES.includes(type as TemplateType)
? (type as TemplateType)
@@ -320,8 +311,7 @@ export class TemplateJob {
from: Record<string, Record<string, unknown>>,
to: Y.Map<Y.Map<unknown>>
) {
const schema =
this.model.doc.workspace.schema.flavourSchemaMap.get('affine:surface');
const schema = this.model.doc.schema.get('affine:surface');
const surfaceTransformer = schema?.transformer?.(
new Map()
) as SurfaceBlockTransformer;
@@ -19,7 +19,7 @@ import {
isGfxGroupCompatibleModel,
type SerializedElement,
} from '@blocksuite/block-std/gfx';
import { type BlockSnapshot, Transformer } from '@blocksuite/store';
import type { BlockSnapshot, Transformer } from '@blocksuite/store';
/**
* return all elements in the tree of the elements
@@ -40,15 +40,7 @@ export function getSortedCloneElements(elements: GfxModel[]) {
export function prepareCloneData(elements: GfxModel[], std: BlockStdScope) {
elements = sortEdgelessElements(elements);
const job = new Transformer({
schema: std.workspace.schema,
blobCRUD: std.workspace.blobSync,
docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }),
get: (id: string) => std.workspace.getDoc(id),
delete: (id: string) => std.workspace.removeDoc(id),
},
});
const job = std.store.getTransformer();
const res = elements.map(element => {
const data = serializeElement(element, elements, job);
return data;
@@ -10,7 +10,6 @@ export class PreviewRootBlockComponent extends BlockComponent {
static override styles = css`
affine-preview-root {
display: block;
padding: 0 24px;
}
`;
@@ -8,19 +8,21 @@ import {
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store';
import type { Schema, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js';
type ImportHTMLToDocOptions = {
collection: Workspace;
schema: Schema;
html: string;
fileName?: string;
};
type ImportHTMLZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -41,19 +43,10 @@ function getProvider() {
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
],
});
const job = doc.getTransformer([
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
]);
const snapshot = job.docToSnapshot(doc);
const adapter = new HtmlAdapter(job, provider);
if (!snapshot) {
@@ -87,18 +80,20 @@ async function exportDoc(doc: Store) {
*
* @param options - The import options.
* @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.html - The HTML content to import.
* @param options.fileName - Optional filename for the imported doc.
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails.
*/
async function importHTMLToDoc({
collection,
schema,
html,
fileName,
}: ImportHTMLToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -127,10 +122,15 @@ async function importHTMLToDoc({
*
* @param options - The import options.
* @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.imported - The zip file as a Blob.
* @returns A Promise that resolves to an array of IDs of the newly created docs.
*/
async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
async function importHTMLZip({
collection,
schema,
imported,
}: ImportHTMLZipOptions) {
const provider = getProvider();
const unzip = new Unzip();
await unzip.load(imported);
@@ -161,7 +161,7 @@ async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
htmlBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -9,7 +9,7 @@ import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { assertExists, sha } from '@blocksuite/global/utils';
import type { Store, Workspace } from '@blocksuite/store';
import type { Schema, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js';
@@ -31,12 +31,14 @@ type ImportMarkdownToBlockOptions = {
type ImportMarkdownToDocOptions = {
collection: Workspace;
schema: Schema;
markdown: string;
fileName?: string;
};
type ImportMarkdownZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -47,19 +49,10 @@ type ImportMarkdownZipOptions = {
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
],
});
const job = doc.getTransformer([
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
]);
const snapshot = job.docToSnapshot(doc);
const adapter = new MarkdownAdapter(job, provider);
@@ -107,19 +100,10 @@ async function importMarkdownToBlock({
blockId,
}: ImportMarkdownToBlockOptions) {
const provider = getProvider();
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware(doc.workspace.id),
],
});
const job = doc.getTransformer([
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware(doc.workspace.id),
]);
const adapter = new MarkdownAdapter(job, provider);
const snapshot = await adapter.toSliceSnapshot({
file: markdown,
@@ -143,18 +127,20 @@ async function importMarkdownToBlock({
* Imports Markdown content into a new doc within a collection.
* @param options Object containing import options
* @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.markdown The Markdown content to import
* @param options.fileName Optional filename for the imported doc
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails
*/
async function importMarkdownToDoc({
collection,
schema,
markdown,
fileName,
}: ImportMarkdownToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -182,11 +168,13 @@ async function importMarkdownToDoc({
* Imports a zip file containing Markdown files and assets into a collection.
* @param options Object containing import options
* @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.imported The zip file as a Blob
* @returns A Promise that resolves to an array of IDs of the newly created docs
*/
async function importMarkdownZip({
collection,
schema,
imported,
}: ImportMarkdownZipOptions) {
const provider = getProvider();
@@ -219,7 +207,7 @@ async function importMarkdownZip({
markdownBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -3,12 +3,18 @@ import { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import { extMimeMap, Transformer, type Workspace } from '@blocksuite/store';
import {
extMimeMap,
type Schema,
Transformer,
type Workspace,
} from '@blocksuite/store';
import { Unzip } from './utils.js';
type ImportNotionZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -26,6 +32,7 @@ function getProvider() {
*
* @param options - The options for importing.
* @param options.collection - The BlockSuite document collection.
* @param options.schema - The schema of the BlockSuite document collection.
* @param options.imported - The imported zip file as a Blob.
*
* @returns A promise that resolves to an object containing:
@@ -36,6 +43,7 @@ function getProvider() {
*/
async function importNotionZip({
collection,
schema,
imported,
}: ImportNotionZipOptions) {
const provider = getProvider();
@@ -117,7 +125,7 @@ async function importNotionZip({
}
const pagePromises = Array.from(pagePaths).map(async path => {
const job = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -3,15 +3,19 @@ import {
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { sha } from '@blocksuite/global/utils';
import type { DocSnapshot, Store, Workspace } from '@blocksuite/store';
import type { DocSnapshot, Schema, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
import { download, Unzip, Zip } from '../transformers/utils.js';
async function exportDocs(collection: Workspace, docs: Store[]) {
async function exportDocs(
collection: Workspace,
schema: Schema,
docs: Store[]
) {
const zip = new Zip();
const job = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -29,7 +33,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,10 +70,15 @@ 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`);
}
async function importDocs(collection: Workspace, imported: Blob) {
async function importDocs(
collection: Workspace,
schema: Schema,
imported: Blob
) {
const unzip = new Unzip();
await unzip.load(imported);
@@ -94,7 +106,7 @@ async function importDocs(collection: Workspace, imported: Blob) {
}
const job = new Transformer({
schema: collection.schema,
schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -233,6 +233,7 @@ export function createNewDocMenuGroup(
};
showImportModal({
collection: doc.workspace,
schema: doc.schema,
onSuccess,
onFail,
});
@@ -8,7 +8,7 @@ import {
} from '@blocksuite/affine-components/icons';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import type { Workspace } from '@blocksuite/store';
import type { Schema, Workspace } from '@blocksuite/store';
import { html, LitElement, type PropertyValues } from 'lit';
import { query, state } from 'lit/decorators.js';
@@ -31,6 +31,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
constructor(
private readonly collection: Workspace,
private readonly schema: Schema,
private readonly onSuccess?: OnSuccessHandler,
private readonly onFail?: OnFailHandler,
private readonly abortController = new AbortController()
@@ -63,6 +64,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
}
const pageId = await HtmlTransformer.importHTMLToDoc({
collection: this.collection,
schema: this.schema,
html: text,
fileName,
});
@@ -93,6 +95,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
}
const pageId = await MarkdownTransformer.importMarkdownToDoc({
collection: this.collection,
schema: this.schema,
markdown: text,
fileName,
});
@@ -117,6 +120,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
const { entryId, pageIds, isWorkspaceFile, hasMarkdown } =
await NotionHtmlTransformer.importNotionZip({
collection: this.collection,
schema: this.schema,
imported: file,
});
needLoading && this.abortController.abort();
@@ -1,4 +1,4 @@
import type { Workspace } from '@blocksuite/store';
import type { Schema, Workspace } from '@blocksuite/store';
import {
ImportDoc,
@@ -7,12 +7,14 @@ import {
} from './import-doc.js';
export function showImportModal({
schema,
collection,
onSuccess,
onFail,
container = document.body,
abortController = new AbortController(),
}: {
schema: Schema;
collection: Workspace;
onSuccess?: OnSuccessHandler;
onFail?: OnFailHandler;
@@ -22,6 +24,7 @@ export function showImportModal({
}) {
const importDoc = new ImportDoc(
collection,
schema,
onSuccess,
onFail,
abortController
@@ -20,10 +20,8 @@ import { choose } from 'lit/directives/choose.js';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import {
type PageRootBlockComponent,
RootBlockConfigExtension,
} from '../../index.js';
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
import { RootBlockConfigExtension } from '../../root-config.js';
import {
type AFFINE_LINKED_DOC_WIDGET,
getMenus,
@@ -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"
}
@@ -423,8 +423,9 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
override mounted() {
const disposable = this.std.view.viewUpdated.on(payload => {
if (payload.type !== 'block') return;
if (
payload.type === 'add' &&
payload.method === 'add' &&
matchModels(payload.view.model, [RootBlockModel])
) {
disposable.dispose();
+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"
}
+5 -1
View File
@@ -50,7 +50,11 @@ export {
} from './adapters/index.js';
export type { SurfaceContext } from './surface-block.js';
export { SurfaceBlockComponent } from './surface-block.js';
export { SurfaceBlockModel, SurfaceBlockSchema } from './surface-model.js';
export {
SurfaceBlockModel,
SurfaceBlockSchema,
SurfaceBlockSchemaExtension,
} from './surface-model.js';
export type { SurfaceBlockService } from './surface-service.js';
export {
EdgelessSurfaceBlockSpec,
@@ -5,7 +5,7 @@ import type {
import type { SurfaceBlockProps } from '@blocksuite/block-std/gfx';
import { SurfaceBlockModel as BaseSurfaceModel } from '@blocksuite/block-std/gfx';
import { DisposableGroup } from '@blocksuite/global/utils';
import { defineBlockSchema } from '@blocksuite/store';
import { BlockSchemaExtension, defineBlockSchema } from '@blocksuite/store';
import * as Y from 'yjs';
import { elementsCtorMap } from './element-model/index.js';
@@ -36,6 +36,9 @@ export const SurfaceBlockSchema = defineBlockSchema({
toModel: () => new SurfaceBlockModel(),
});
export const SurfaceBlockSchemaExtension =
BlockSchemaExtension(SurfaceBlockSchema);
export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void;
export class SurfaceBlockModel extends BaseSurfaceModel {
@@ -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';
@@ -35,19 +36,36 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
cursor: pointer;
}
.footnote-content-default {
display: inline-block;
background: ${unsafeCSSVarV2('button/primary')};
color: ${unsafeCSSVarV2('button/pureWhiteText')};
width: 14px;
height: 14px;
line-height: 14px;
font-size: 10px;
font-weight: 400;
border-radius: 50%;
text-align: center;
text-overflow: ellipsis;
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
.footnote-node {
.footnote-content-default {
display: inline-block;
background: ${unsafeCSSVarV2('block/footnote/numberBgHover')};
color: ${unsafeCSSVarV2('button/pureWhiteText')};
width: 14px;
height: 14px;
line-height: 14px;
font-size: 10px;
font-weight: 400;
border-radius: 50%;
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 +85,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 +164,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
},
};
},
{ enterDelay: 500 }
{ enterDelay: 300 }
);
override render() {
@@ -156,9 +178,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;
},
};
@@ -13,8 +13,11 @@ export class ToggleButton extends WithDisposable(ShadowlessElement) {
.toggle-icon {
display: flex;
align-items: start;
margin-top: 0.45em;
justify-content: start;
position: absolute;
width: 16px;
height: 16px;
top: calc((1em - 16px) / 2 + 5px);
left: 0;
transform: translateX(-100%);
border-radius: 4px;
@@ -22,6 +25,7 @@ export class ToggleButton extends WithDisposable(ShadowlessElement) {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.toggle-icon:hover {
background: var(--affine-hover-color);
}
+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"
}
@@ -3,7 +3,11 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import type { EmbedCardStyle } from '../../utils/index.js';
import { AttachmentBlockTransformer } from './attachment-transformer.js';
@@ -86,6 +90,10 @@ export const AttachmentBlockSchema = defineBlockSchema({
toModel: () => new AttachmentBlockModel(),
});
export const AttachmentBlockSchemaExtension = BlockSchemaExtension(
AttachmentBlockSchema
);
export class AttachmentBlockModel
extends GfxCompatible<AttachmentBlockProps>(BlockModel)
implements GfxElementGeometry {}
@@ -3,7 +3,11 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import type { EmbedCardStyle, LinkPreviewData } from '../../utils/index.js';
@@ -54,6 +58,9 @@ export const BookmarkBlockSchema = defineBlockSchema({
toModel: () => new BookmarkBlockModel(),
});
export const BookmarkBlockSchemaExtension =
BlockSchemaExtension(BookmarkBlockSchema);
export class BookmarkBlockModel
extends GfxCompatible<BookmarkBlockProps>(BlockModel)
implements GfxElementGeometry {}
@@ -1,4 +1,9 @@
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
interface CodeBlockProps {
text: Text;
@@ -30,6 +35,8 @@ export const CodeBlockSchema = defineBlockSchema({
toModel: () => new CodeBlockModel(),
});
export const CodeBlockSchemaExtension = BlockSchemaExtension(CodeBlockSchema);
export class CodeBlockModel extends BlockModel<CodeBlockProps> {
override text!: Text;
}
@@ -1,5 +1,9 @@
import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import type { Column, SerializedCells, ViewBasicDataType } from './types.js';
@@ -28,3 +32,6 @@ export const DatabaseBlockSchema = defineBlockSchema({
},
toModel: () => new DatabaseBlockModel(),
});
export const DatabaseBlockSchemaExtension =
BlockSchemaExtension(DatabaseBlockSchema);
@@ -1,4 +1,8 @@
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export const DividerBlockSchema = defineBlockSchema({
flavour: 'affine:divider',
@@ -15,3 +19,6 @@ type Props = {
};
export class DividerBlockModel extends BlockModel<Props> {}
export const DividerBlockSchemaExtension =
BlockSchemaExtension(DividerBlockSchema);
@@ -3,7 +3,11 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { z } from 'zod';
import {
@@ -76,6 +80,10 @@ export const EdgelessTextBlockSchema = defineBlockSchema({
},
});
export const EdgelessTextBlockSchemaExtension = BlockSchemaExtension(
EdgelessTextBlockSchema
);
export class EdgelessTextBlockModel
extends GfxCompatible<EdgelessTextProps>(BlockModel)
implements GfxElementGeometry {}
@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedFigmaBlockProps,
@@ -20,3 +22,7 @@ export const EmbedFigmaBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedFigmaModel(),
props: (): EmbedFigmaBlockProps => defaultEmbedFigmaProps,
});
export const EmbedFigmaBlockSchemaExtension = BlockSchemaExtension(
EmbedFigmaBlockSchema
);
@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedGithubBlockProps,
@@ -29,3 +31,7 @@ export const EmbedGithubBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedGithubModel(),
props: (): EmbedGithubBlockProps => defaultEmbedGithubProps,
});
export const EmbedGithubBlockSchemaExtension = BlockSchemaExtension(
EmbedGithubBlockSchema
);
@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedHtmlBlockProps,
@@ -18,3 +20,6 @@ export const EmbedHtmlBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedHtmlModel(),
props: (): EmbedHtmlBlockProps => defaultEmbedHtmlProps,
});
export const EmbedHtmlBlockSchemaExtension =
BlockSchemaExtension(EmbedHtmlBlockSchema);
@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedLinkedDocBlockProps,
@@ -20,3 +22,7 @@ export const EmbedLinkedDocBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedLinkedDocModel(),
props: (): EmbedLinkedDocBlockProps => defaultEmbedLinkedDocBlockProps,
});
export const EmbedLinkedDocBlockSchemaExtension = BlockSchemaExtension(
EmbedLinkedDocBlockSchema
);
@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedLoomBlockProps,
@@ -22,3 +24,6 @@ export const EmbedLoomBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedLoomModel(),
props: (): EmbedLoomBlockProps => defaultEmbedLoomProps,
});
export const EmbedLoomBlockSchemaExtension =
BlockSchemaExtension(EmbedLoomBlockSchema);
@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedSyncedDocBlockProps,
@@ -21,3 +23,7 @@ export const EmbedSyncedDocBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedSyncedDocModel(),
props: (): EmbedSyncedDocBlockProps => defaultEmbedSyncedDocBlockProps,
});
export const EmbedSyncedDocBlockSchemaExtension = BlockSchemaExtension(
EmbedSyncedDocBlockSchema
);
@@ -1,3 +1,5 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedYoutubeBlockProps,
@@ -25,3 +27,7 @@ export const EmbedYoutubeBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedYoutubeModel(),
props: (): EmbedYoutubeBlockProps => defaultEmbedYoutubeProps,
});
export const EmbedYoutubeBlockSchemaExtension = BlockSchemaExtension(
EmbedYoutubeBlockSchema
);
@@ -15,7 +15,12 @@ import {
hasDescendantElementImpl,
} from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
import { z } from 'zod';
import { type Color, ColorSchema, DefaultTheme } from '../../themes/index.js';
@@ -57,6 +62,8 @@ export const FrameBlockSchema = defineBlockSchema({
},
});
export const FrameBlockSchemaExtension = BlockSchemaExtension(FrameBlockSchema);
export class FrameBlockModel
extends GfxCompatible<FrameBlockProps>(BlockModel)
implements GfxElementGeometry, GfxGroupCompatibleInterface
@@ -3,7 +3,11 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { ImageBlockTransformer } from './image-transformer.js';
@@ -40,6 +44,8 @@ export const ImageBlockSchema = defineBlockSchema({
toModel: () => new ImageBlockModel(),
});
export const ImageBlockSchemaExtension = BlockSchemaExtension(ImageBlockSchema);
export class ImageBlockModel
extends GfxCompatible<ImageBlockProps>(BlockModel)
implements GfxElementGeometry {}
@@ -3,7 +3,11 @@ import {
GfxCompatible,
type GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type LatexProps = {
latex: string;
@@ -34,6 +38,8 @@ export const LatexBlockSchema = defineBlockSchema({
},
});
export const LatexBlockSchemaExtension = BlockSchemaExtension(LatexBlockSchema);
export class LatexBlockModel
extends GfxCompatible<LatexProps>(BlockModel)
implements GfxElementGeometry {}
@@ -1,5 +1,9 @@
import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
// `toggle` type has been deprecated, do not use it
export type ListType = 'bulleted' | 'numbered' | 'todo' | 'toggle';
@@ -38,6 +42,8 @@ export const ListBlockSchema = defineBlockSchema({
toModel: () => new ListBlockModel(),
});
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
export class ListBlockModel extends BlockModel<ListProps> {
override text!: Text;
}
@@ -4,7 +4,11 @@ import type {
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { z } from 'zod';
import {
@@ -21,6 +25,7 @@ import {
StrokeStyleSchema,
} from '../../consts/note';
import { type Color, ColorSchema, DefaultTheme } from '../../themes';
import { TableModelFlavour } from '../table';
export const NoteZodSchema = z
.object({
@@ -47,7 +52,6 @@ export const NoteZodSchema = z
},
},
});
import { TableModelFlavour } from '../table';
export const NoteBlockSchema = defineBlockSchema({
flavour: 'affine:note',
@@ -92,6 +96,7 @@ export const NoteBlockSchema = defineBlockSchema({
},
});
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
export type NoteProps = {
background: Color;
displayMode: NoteDisplayMode;
@@ -1,4 +1,9 @@
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
export type ParagraphType =
| 'text'
@@ -37,6 +42,9 @@ export const ParagraphBlockSchema = defineBlockSchema({
toModel: () => new ParagraphBlockModel(),
});
export const ParagraphBlockSchemaExtension =
BlockSchemaExtension(ParagraphBlockSchema);
export class ParagraphBlockModel extends BlockModel<ParagraphProps> {
override text!: Text;
@@ -1,5 +1,9 @@
import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type RootBlockProps = {
title: Text;
@@ -51,3 +55,5 @@ export const RootBlockSchema = defineBlockSchema({
},
toModel: () => new RootBlockModel(),
});
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
@@ -1,4 +1,8 @@
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type SurfaceRefProps = {
reference: string;
@@ -21,4 +25,8 @@ export const SurfaceRefBlockSchema = defineBlockSchema({
toModel: () => new SurfaceRefBlockModel(),
});
export const SurfaceRefBlockSchemaExtension = BlockSchemaExtension(
SurfaceRefBlockSchema
);
export class SurfaceRefBlockModel extends BlockModel<SurfaceRefProps> {}
@@ -1,6 +1,10 @@
import type { DeltaInsert } from '@blocksuite/inline';
import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
export type TableCell = {
text: Text;
@@ -56,3 +60,5 @@ export const TableBlockSchema = defineBlockSchema({
},
toModel: () => new TableBlockModel(),
});
export const TableBlockSchemaExtension = BlockSchemaExtension(TableBlockSchema);
@@ -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"
}
@@ -37,7 +37,7 @@ export class DNDAPIExtension extends Extension {
const { docId, flavour = 'affine:embed-linked-doc', blockId } = options;
const slice = Slice.fromModels(this.std.store, []);
const job = this.std.getTransformer();
const job = this.std.store.getTransformer();
const snapshot = job.sliceToSnapshot(slice);
if (!snapshot) {
console.error('Failed to convert slice to snapshot');
@@ -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;
}
}
@@ -1,8 +1,13 @@
import type { EditorHost } from '@blocksuite/block-std';
import { type Viewport } from '@blocksuite/block-std/gfx';
import { Pane } from 'tweakpane';
import { getSentenceRects, segmentSentences } from './text-utils.js';
import type { ParagraphLayout, ViewportLayout } from './types.js';
import type {
ParagraphLayout,
RenderingState,
ViewportLayout,
} from './types.js';
import type { ViewportTurboRendererExtension } from './viewport-renderer.js';
export function syncCanvasSize(canvas: HTMLCanvasElement, host: HTMLElement) {
@@ -19,7 +24,7 @@ export function syncCanvasSize(canvas: HTMLCanvasElement, host: HTMLElement) {
}
export function getViewportLayout(
host: HTMLElement,
host: EditorHost,
viewport: Viewport
): ViewportLayout {
const paragraphBlocks = host.querySelectorAll(
@@ -98,15 +103,15 @@ export function initTweakpane(
paneElement.style.right = '10px';
paneElement.style.width = '250px';
debugPane.title = 'Viewport Turbo Renderer';
debugPane
.addBinding({ paused: false }, 'paused', {
label: 'Paused',
})
.on('change', ({ value }) => {
renderer.state = value ? 'paused' : 'monitoring';
});
debugPane.addButton({ title: 'Invalidate' }).on('click', () => {
renderer.invalidate();
});
}
export function debugLog(message: string, state: RenderingState) {
console.log(
`%c[ViewportTurboRenderer]%c ${message} | state=${state}`,
'color: #4285f4; font-weight: bold;',
'color: inherit;'
);
}
@@ -8,6 +8,7 @@ type WorkerMessagePaint = {
height: number;
dpr: number;
zoom: number;
version: number;
};
};
@@ -63,7 +64,7 @@ class LayoutPainter {
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
paint(layout: ViewportLayout) {
paint(layout: ViewportLayout, version: number) {
const { canvas, ctx } = this;
if (!canvas || !ctx) return;
if (layout.rect.w === 0 || layout.rect.h === 0) {
@@ -103,7 +104,10 @@ class LayoutPainter {
});
const bitmap = canvas.transferToImageBitmap();
self.postMessage({ type: 'bitmapPainted', bitmap }, { transfer: [bitmap] });
self.postMessage(
{ type: 'bitmapPainted', bitmap, version },
{ transfer: [bitmap] }
);
}
}
@@ -127,9 +131,9 @@ self.onmessage = async (e: MessageEvent<WorkerMessage>) => {
switch (type) {
case 'paintLayout': {
const { layout, width, height, dpr, zoom } = data;
const { layout, width, height, dpr, zoom, version } = data;
painter.setSize(width, height, dpr, zoom);
painter.paint(layout);
painter.paint(layout, version);
break;
}
}

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