Closes: [BS-2909](https://linear.app/affine-design/issue/BS-2909/新增highlighter)
### What's Changed!
Currently the highlighter tool is very similar to brush, but for the future, it's a standalone module.
* Added `Highlighter` element model
* Added `Highlighter` tool
* Added `Highlighter` entry to the global toolbar
### TL;DR
Added new commands to retrieve the first and last content blocks in a document.
### What changed?
- Created `getFirstContentBlockCommand` to find the first content block in a document
- Created `getLastContentBlockCommand` to find the last content block in a document
- Added `getFirstNoteBlock` utility function to find the first note block in a document
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.
### TL;DR
Refactored `SpecProvider` singleton access pattern and reorganized adapter/extension code structure.
### What changed?
- Changed `SpecProvider.getInstance()` to `SpecProvider._` for cleaner singleton access
- Moved adapter/extension code from `_common` directory to dedicated `adapters` and `extensions` folders
- Consolidated adapter extensions into a single file
- Removed unused dependencies from package.json
- Deleted unnecessary schema files
- Extracted `MobileSpecsPatches` class into the mobile patching code
- Updated all references to use the new `SpecProvider._` accessor
### How to test?
- Verify all specs are properly registered and accessible via `SpecProvider._`
- Test adapter functionality for HTML, Markdown, Notion HTML and plain text
- Check mobile-specific features and patches work correctly
- Ensure preview functionality works in both page and edgeless modes
### Why make this change?
- Improves code organization by properly separating adapters and extensions
- Simplifies singleton access pattern
- Removes unnecessary dependencies and files
- Makes the codebase more maintainable by consolidating related functionality
### TL;DR
Moved image proxy middleware and adapter extensions to their respective packages and introduced a new spec provider for adapter registration.
### What changed?
- Relocated `defaultImageProxyMiddleware` from blocks to `@blocksuite/affine-block-image`
- Moved `PresentTool` from fragment-frame-panel to block-frame
- Created new adapter extension specs for HTML, Markdown, and Notion HTML
- Introduced a spec provider pattern for adapter registration
- Removed direct transformer references from RootService
- Updated imports across affected files to use new locations
### How to test?
1. Verify image proxy functionality works in exports and imports
2. Test HTML, Markdown, and Notion HTML adapters still function correctly
3. Confirm presentation mode works with the relocated PresentTool
4. Check that all file import/export operations continue to work as expected
### Why make this change?
This reorganization improves code modularity by placing features in their logical packages and introduces a more maintainable pattern for adapter registration. The spec provider pattern makes it easier to manage and extend adapter functionality while reducing coupling between components.
Related to https://github.com/toeverything/AFFiNE/pull/9970#discussion_r1944971309
### What changes:
- Add missing zod shcema for edgeless basic props
- Change `applyLastProps` to generic function for better return type inference of
- Fix: add `ZodIntersection` case to `makeDeepOptional`
### Changed
- Refactored BlockSuite drag-and-drop using @atlaskit/pragmatic-drag-and-drop/element/adapter.
- Updated block dragging to use the new drag-and-drop infrastructure.
### BlockSuite DND API
Access the BlockSuite drag-and-drop API via `std.dnd`. This is a lightweight wrapper around pragmatic-drag-and-drop, offering convenient generic types and more intuitive option names.
#### Drag payload structure
There's some constrain about drag payload. The whole drag payload looks like this:
```typescript
type DragPayload = {
entity: {
type: string
},
from: {
at: 'blocksuite',
docId: string
}
}
```
- The `from` field is auto-generated—no need for manual handling.
- The `entity` field is customizable, but it must include a `type`.
All drag-and-drop methods accept a generic type for entity, ensuring more accurate payloads in event handlers.
```typescript
type BlockEntity = {
type: 'blocks',
blockIds: string[]
}
dnd.draggable<BlockEntity>({
element: someElement,
setDragData: () => {
// the return type must satisfy the generic type
// in this case, it's BlockEntity
return {
type: 'blocks',
blockIds: []
}
}
});
dnd.monitor<BlockEntity>({
// the arguments is same for other event handler
onDrag({ source }) {
// the type of this is BlockEntity
source.data.entity
}
})
```
#### Drop payload
When hover on droppable target. You can set drop payload as well. All drag-and-drop methods accept a second generic type for drop payload.
The drop payload is customizable. Additionally, the DND system will add an `edge` field to the final payload object, indicating the nearest edge of the drop target relative to the current drag position.
```typescript
type DropPayload = {
blockId: string;
}
dnd.dropTarget<BlockEntity, DropPayload>({
getData() {
// the type should be DropPayload
return {
blockId: 'someId'
}
}
});
dnd.monitor<BlockEntity, DropPayload>({
// drag over on drop target
onDrag({ location }) {
const target = location.current.dropTargets[0];
// the type is DropPayload
target.data;
// retrieve the nearest edge of the drop target relative to the current drop position.
target.data.edge;
}
})
```