Saul-Mirone
ef00a158fc
docs(editor): improve documentation for store class ( #10949 )
2025-03-18 07:57:58 +00:00
L-Sun
83af78d9ee
chore(editor): update icon color of link doc card ( #10943 )
...
Close [BS-2679](https://linear.app/affine-design/issue/BS-2679/card-view-icon-color-incorrect )
2025-03-18 05:00:26 +00:00
Saul-Mirone
9f3cf271e3
feat(editor): support user provided role and role schema ( #10939 )
...
Let me analyze the key changes in this diff:
1. **Role System Changes**:
- Changed from a fixed enum of roles (`root`, `hub`, `content`) to a more flexible string-based system
- Removed strict role hierarchy validation rules (hub/content/root relationships)
- Added support for role-based matching using `@` prefix (e.g., `@root`, `@content`)
2. **Schema Validation Updates**:
- Added new `_matchFlavourOrRole` method to handle both flavour and role-based matching
- Updated `_validateParent` to consider both roles and flavours when validating parent-child relationships
- Simplified `_validateRole` by removing specific role hierarchy constraints
3. **Block Schema Changes**:
- Updated parent/children references in various block schemas to use the new `@` prefix notation
- Changed parent definitions from `['affine:page']` to `['@root']` in several blocks
- Updated children definitions to use role-based references (e.g., `['@content']`)
4. **Test Updates**:
- Added new test cases for role-based schema validation
- Introduced new test block schemas (`TestRoleBlockSchema`, `TestParagraphBlockSchema`) to verify role-based functionality
This appears to be a significant architectural change that makes the block schema system more flexible by:
1. Moving away from hardcoded role hierarchies
2. Introducing a more dynamic role-based relationship system
3. Supporting both flavour-based and role-based parent-child relationships
4. Using the `@` prefix convention to distinguish role references from flavour references
The changes make the system more extensible while maintaining backward compatibility with existing flavour-based relationships.
2025-03-18 01:58:59 +00:00
Saul-Mirone
3de7d85eea
feat(editor): improve api for store, and add docs ( #10941 )
2025-03-17 16:30:59 +00:00
L-Sun
3dbeebd6ba
fix(editor): background of surface-ref disappeared when it was re-rendered ( #10931 )
...
This PR fixed the background of surface-ref dis
2025-03-17 14:00:17 +00:00
L-Sun
cc8ec72f2c
feat(editor): insert a mindmap slash menu action ( #10930 )
...
Close [BS-2516](https://linear.app/affine-design/issue/BS-2516/添加mind-map入口 )
2025-03-17 13:19:56 +00:00
L-Sun
d80f1e8067
feat(editor): insert a blank frame slash menu action ( #10899 )
...
Close [BS-2517](https://linear.app/affine-design/issue/BS-2517/%E6%B7%BB%E5%8A%A0frame%E5%85%A5%E5%8F%A3 )
### What changes:
- add a insert blank frame action to slash menu
- move `EdgelessFrameManager` and `FrameOverlay` extensions to `FrameBlockSpec`
- make `FrameBlockSpec` as a part of `CommonBlockSpecs` such that we can use `EdgelessFrameManager` to create a frame more easily
https://github.com/user-attachments/assets/ddff5866-8933-4ce5-aaf4-873661407ee4
2025-03-17 10:32:55 +00:00
EYHN
b401012d85
feat(core): user service loading state ( #10922 )
2025-03-17 09:28:25 +00:00
Saul-Mirone
d5a5df5e49
test(editor): move blocksuite test to tests folder ( #10917 )
2025-03-17 06:40:25 +00:00
Saul-Mirone
808c053c3c
feat(editor): add block meta for table block ( #10915 )
2025-03-17 05:20:10 +00:00
donteatfriedrice
6eb34d70c1
feat(editor): support bookmark block convert to embed iframe block ( #10907 )
2025-03-17 05:00:55 +00:00
Saul-Mirone
8bd4125c96
feat(editor): enable block meta for code, image, list block ( #10905 )
2025-03-17 03:29:51 +00:00
Saul-Mirone
7e6ff8d9c6
chore(editor): remove unused npm scripts ( #10895 )
2025-03-16 17:48:49 +00:00
Saul-Mirone
2f5e801097
fix(editor): missing export ( #10893 )
2025-03-16 16:09:35 +00:00
hackerESQ
fff15222d9
feat(core): add transparent as default shape fill ( #10801 )
2025-03-16 20:58:50 +09:00
donteatfriedrice
d7d512084e
feat(editor): embed iframe error status card in surface ( #10869 )
...
To close [BS-2806](https://linear.app/affine-design/issue/BS-2806/iframe-embed-block-edgeless-loading-and-error-status )
2025-03-16 09:05:04 +00:00
donteatfriedrice
7ecb1f510d
feat(editor): embed iframe loading status card in surface ( #10868 )
2025-03-16 09:05:04 +00:00
Saul-Mirone
26285f7dcb
feat(editor): unify block props api ( #10888 )
...
Closes: [BS-2707](https://linear.app/affine-design/issue/BS-2707/统一使用props获取和更新block-prop )
2025-03-16 05:48:34 +00:00
Oleg
8f9e5bf0aa
fix(editor): minor ui bugs ( #10841 )
...
Co-authored-by: Mirone <Saul-Mirone@outlook.com >
2025-03-15 19:15:56 +09:00
donteatfriedrice
1d4ee1e383
feat(editor): support embed iframe block in edgeless ( #10830 )
...
To close:
[BS-2665](https://linear.app/affine-design/issue/BS-2665/iframe-embed-block-edgeless-mode-支持 )
[BS-2666](https://linear.app/affine-design/issue/BS-2666/iframe-embed-block-edgeless-toolbar )
[BS-2667](https://linear.app/affine-design/issue/BS-2667/iframe-embed-block-edgeless-mode-拖拽调整支持 )
[BS-2789](https://linear.app/affine-design/issue/BS-2789/iframe-embed-block-edgeless-block-component )
2025-03-15 09:23:02 +00:00
L-Sun
3b4453d2b8
chore(editor): update default width of page block ( #10873 )
...
Close [BS-2498](https://linear.app/affine-design/issue/BS-2498/page-block首次切换时默认宽度为800px )
2025-03-14 12:59:17 +00:00
fundon
99fdfe821a
fix(editor): improve type declaration definition ( #10866 )
...
Closes: [BS-2809](https://linear.app/affine-design/issue/BS-2809/改进继承自抽象类的类型后-createidentifier-类型定义 )
### What's Changed!
* Improved type declaration definition
2025-03-14 10:38:17 +00:00
doodlewind
d8dfea6ccf
fix(editor): type import in vite worker env ( #10856 )
...
The dependencies of `@blocksuite/affine-gfx-turbo-renderer` in work is now all type imports.
2025-03-14 10:22:58 +00:00
doodlewind
d1c10f5401
chore(editor): hide tweakpane for turbo renderer ( #10846 )
2025-03-14 06:47:15 +00:00
Saul-Mirone
b8452f56a8
feat(editor): block painter extension ( #10847 )
2025-03-14 05:26:58 +00:00
doodlewind
be9f44fc4f
fix(editor): worker loading in webpack env ( #10832 )
...
### TL;DR
Created dedicated worker entry points to avoid dynamic imports.
### What changed?
- Painters are provided during worker initialization
- Removed `ParagraphPaintConfigExtension` and the associated configuration system
- Created dedicated worker entry points in both the integration test and frontend packages
- Modified `ViewportLayoutPainter` to accept painters in its constructor
- Updated the `TurboRendererConfig` interface to require a `painterWorkerEntry` function
### Why make this change?
Webpack support. Extension objects in main thread are not available to be passed into workers. Dynamic painter path import is hard to support in webpack environment. With the [webpack-ignore](https://webpack.js.org/api/module-methods/#webpackignore ) rule, there are still build errors in webpack.
2025-03-14 05:26:57 +00:00
akumatus
daccb2c865
feat(core): add ai file context api ( #10842 )
...
Close [BS-2349](https://linear.app/affine-design/issue/BS-2349 ).
### What Changed?
- Add file context graphql apis
- Pass matched file chunks to LLM
[录屏2025-02-19 23.27.47.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/8e8a98ca-6959-4bb6-9759-b51d97cede49.mov " />](https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/8e8a98ca-6959-4bb6-9759-b51d97cede49.mov )
2025-03-14 04:29:54 +00:00
yoyoyohamapi
e086fd2a43
refactor(editor): getFirstContentBlock -> getFirstBlock & getLastContentBlock -> getLastBlock ( #10809 )
2025-03-14 02:35:22 +00:00
yoyoyohamapi
d3aae962bc
test(editor): getFirstContentBlock & getLastContentBlock & isNothingSelected command ( #10757 )
...
### TL;DR
Added unit tests for block and selection commands, along with a new test helper system for creating test documents.
### What changed?
- Added unit tests for several commands:
- `getFirstContentBlockCommand`
- `getLastContentBlockCommand`
- `isNothingSelectedCommand`
- Created a new test helpers make it easier to create structured test documents with a html-like syntax:
```typescript
import { describe, expect, it } from 'vitest';
import { affine } from '../__tests__/utils/affine-template';
describe('My Test', () => {
it('should correctly handle document structure', () => {
const doc = affine`
<affine-page>
<affine-note>
<affine-paragraph>Test content</affine-paragraph>
</affine-note>
</affine-page>
`;
// Get blocks
const pages = doc.getBlocksByFlavour('affine:page');
const notes = doc.getBlocksByFlavour('affine:note');
const paragraphs = doc.getBlocksByFlavour('affine:paragraph');
expect(pages.length).toBe(1);
expect(notes.length).toBe(1);
expect(paragraphs.length).toBe(1);
// Perform more tests here...
});
});
```
2025-03-14 02:35:21 +00:00
yoyoyohamapi
04efca362e
feat(editor): is nothing selected command ( #10721 )
...
### TL;DR
Added a new command to check if nothing is currently selected in the editor.
### What changed?
- Created new `isNothingSelectedCommand` to determine if there are no active selections
2025-03-14 02:35:21 +00:00
yoyoyohamapi
aa15b106d9
feat(editor): content block getter command ( #10720 )
...
### 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
2025-03-14 02:35:20 +00:00
fundon
b238aa3182
fix(editor): clamp method in color picker ( #10840 )
...
Related to https://github.com/toeverything/AFFiNE/pull/10577
2025-03-13 19:37:37 +00:00
Saul-Mirone
05f3069efd
feat(editor): add i18n support for block meta display ( #10831 )
2025-03-13 11:28:56 +00:00
doodlewind
0f062b7157
refactor(editor): make turbo renderer a gfx extension ( #10818 )
...
This allows for easier debugging via `gfx.turboRenderer`
2025-03-13 07:45:05 +00:00
Saul-Mirone
5148e67891
feat(editor): improve block meta updated event handler ( #10815 )
2025-03-13 06:34:03 +00:00
L-Sun
8ac687628c
chore(editor): at menu stays open when left right arrow keys are pressed ( #10806 )
...
Close [BS-2644](https://linear.app/affine-design/issue/BS-2644/menu-support )
2025-03-13 06:14:35 +00:00
doodlewind
c023b724d0
refactor(editor): generic layout type support for turbo renderer ( #10766 )
...
This PR refactored the turbo renderer architecture to support multiple block layout types.
- New base class `BlockLayoutPainter` and `BlockLayoutProvider` are introduced for writing extendable per-block layout querying and painting logic.
- Paragraph-specific lines are all moved into dedicated classes (`ParagraphLayoutProvider` and `ParagraphLayoutPainter`) under the `/variants/paragraph` dir.
- The `renderer-utils.ts` doesn't contain paragraph-specific logic now.
- The `text-utils.ts` is also now scoped for paragraph only.
- Worker messages are now strongly typed.
Upcoming PR should further implement the block registration system using extension API. The `variants` dir could still exist, since there will be similar rendering logic that can be reused among block types (i.e., between paragraph block and list block).
2025-03-13 05:18:12 +00:00
donteatfriedrice
d2c62602a4
feat(editor): support embed iframe block ( #10740 )
...
To close:
[BS-2660](https://linear.app/affine-design/issue/BS-2660/slash-menu-支持-iframe-embed )
[BS-2661](https://linear.app/affine-design/issue/BS-2661/iframe-embed-block-model-and-block-component )
[BS-2662](https://linear.app/affine-design/issue/BS-2662/iframe-embed-block-toolbar )
[BS-2768](https://linear.app/affine-design/issue/BS-2768/iframe-embed-block-loading-和-error-态 )
[BS-2670](https://linear.app/affine-design/issue/BS-2670/iframe-embed-block-导出 )
# PR Description
# Add Embed Iframe Block Support
## Overview
This PR introduces a new `EmbedIframeBlock` to enhance content embedding capabilities within our editor. This block allows users to seamlessly embed external content from various providers (Google Drive, Spotify, etc.) directly into their docs.
## New Blocks
### EmbedIframeBlock
The core block that renders embedded iframe content. This block:
* Displays external content within a secure iframe
* Handles loading states with visual feedback
* Provides error handling with edit and retry options
* Supports customization of width, height, and other iframe attributes
### Supporting Components
* **EmbedIframeCreateModal**: Modal interface for creating new iframe embeds
* **EmbedIframeLinkEditPopup**: UI for editing existing embed links
* **EmbedIframeLoadingCard**: Visual feedback during content loading
* **EmbedIframeErrorCard**: Error handling with retry functionality
## New Store Extensions
### EmbedIframeConfigExtension
This extension provides configuration for different embed providers:
```typescript
/**
* The options for the iframe
* @example
* {
* defaultWidth: '100%',
* defaultHeight: '152px',
* style: 'border-radius: 8px;',
* allow: 'autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture',
* }
* =>
* <iframe
* width="100%"
* height="152px"
* style="border-radius: 8px;"
* allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
* ></iframe>
*/
export type IframeOptions = {
defaultWidth?: string;
defaultHeight?: string;
style?: string;
referrerpolicy?: string;
scrolling?: boolean;
allow?: string;
allowFullscreen?: boolean;
};
/**
* Define the config of an embed iframe block provider
*/
export type EmbedIframeConfig = {
/**
* The name of the embed iframe block provider
*/
name: string;
/**
* The function to match the url
*/
match: (url: string) => boolean;
/**
* The function to build the oEmbed URL for fetching embed data
*/
buildOEmbedUrl: (url: string) => string | undefined;
/**
* Use oEmbed URL directly as iframe src without fetching oEmbed data
*/
useOEmbedUrlDirectly: boolean;
/**
* The options for the iframe
*/
options?: IframeOptions;
};
export const EmbedIframeConfigIdentifier =
createIdentifier<EmbedIframeConfig>('EmbedIframeConfig');
export function EmbedIframeConfigExtension(
config: EmbedIframeConfig
): ExtensionType & {
identifier: ServiceIdentifier<EmbedIframeConfig>;
} {
const identifier = EmbedIframeConfigIdentifier(config.name);
return {
setup: di => {
di.addImpl(identifier, () => config);
},
identifier,
};
}
```
**example:**
```typescript
// blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/spotify.ts
const SPOTIFY_DEFAULT_WIDTH = '100%';
const SPOTIFY_DEFAULT_HEIGHT = '152px';
// https://developer.spotify.com/documentation/embeds/reference/oembed
const spotifyEndpoint = 'https://open.spotify.com/oembed ';
const spotifyUrlValidationOptions: EmbedIframeUrlValidationOptions = {
protocols: ['https:'],
hostnames: ['open.spotify.com', 'spotify.link'],
};
const spotifyConfig = {
name: 'spotify',
match: (url: string) =>
validateEmbedIframeUrl(url, spotifyUrlValidationOptions),
buildOEmbedUrl: (url: string) => {
const match = validateEmbedIframeUrl(url, spotifyUrlValidationOptions);
if (!match) {
return undefined;
}
const encodedUrl = encodeURIComponent(url);
const oEmbedUrl = `${spotifyEndpoint}?url=${encodedUrl}`;
return oEmbedUrl;
},
useOEmbedUrlDirectly: false,
options: {
defaultWidth: SPOTIFY_DEFAULT_WIDTH,
defaultHeight: SPOTIFY_DEFAULT_HEIGHT,
allow:
'autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture',
style: 'border-radius: 12px;',
allowFullscreen: true,
},
};
// add the config extension to store
export const SpotifyEmbedConfig = EmbedIframeConfigExtension(spotifyConfig);
```
**Key features:**
* Provider registration and discovery
* URL pattern matching
* Provider-specific embed options (width, height, features)
### EmbedIframeService
This service provides abilities to handle URL validation, data fetching, and block creation
**Type:**
```typescript
/**
* Service for handling embeddable URLs
*/
export interface EmbedIframeProvider {
/**
* Check if a URL can be embedded
* @param url URL to check
* @returns true if the URL can be embedded, false otherwise
*/
canEmbed: (url: string) => boolean;
/**
* Build a API URL for fetching embed data
* @param url URL to build API URL
* @returns API URL if the URL can be embedded, undefined otherwise
*/
buildOEmbedUrl: (url: string) => string | undefined;
/**
* Get the embed iframe config
* @param url URL to get embed iframe config
* @returns Embed iframe config if the URL can be embedded, undefined otherwise
*/
getConfig: (url: string) => EmbedIframeConfig | undefined;
/**
* Get embed iframe data
* @param url URL to get embed iframe data
* @returns Embed iframe data if the URL can be embedded, undefined otherwise
*/
getEmbedIframeData: (url: string) => Promise<EmbedIframeData | null>;
/**
* Parse an embeddable URL and add an EmbedIframeBlock to doc
* @param url Original url to embed
* @param parentId Parent block ID
* @param index Optional index to insert at
* @returns Created block id if successful, undefined if the URL cannot be embedded
*/
addEmbedIframeBlock: (
props: Partial<EmbedIframeBlockProps>,
parentId: string,
index?: number
) => string | undefined;
}
```
**Implemetation:**
```typescript
export class EmbedIframeService
extends StoreExtension
implements EmbedIframeProvider
{
static override key = 'embed-iframe-service';
private readonly _configs: EmbedIframeConfig[];
constructor(store: Store) {
super(store);
this._configs = Array.from(
store.provider.getAll(EmbedIframeConfigIdentifier).values()
);
}
canEmbed = (url: string): boolean => {
return this._configs.some(config => config.match(url));
};
buildOEmbedUrl = (url: string): string | undefined => {
return this._configs.find(config => config.match(url))?.buildOEmbedUrl(url);
};
getConfig = (url: string): EmbedIframeConfig | undefined => {
return this._configs.find(config => config.match(url));
};
getEmbedIframeData = async (
url: string,
signal?: AbortSignal
): Promise<EmbedIframeData | null> => {
try {
const config = this._configs.find(config => config.match(url));
if (!config) {
return null;
}
const oEmbedUrl = config.buildOEmbedUrl(url);
if (!oEmbedUrl) {
return null;
}
// if the config useOEmbedUrlDirectly is true, return the url directly as iframe_url
if (config.useOEmbedUrlDirectly) {
return {
iframe_url: oEmbedUrl,
};
}
// otherwise, fetch the oEmbed data
const response = await fetch(oEmbedUrl, { signal });
if (!response.ok) {
console.warn(
`Failed to fetch oEmbed data: ${response.status} ${response.statusText}`
);
return null;
}
const data = await response.json();
return data as EmbedIframeData;
} catch (error) {
if (error instanceof Error && error.name !== 'AbortError') {
console.error('Error fetching embed iframe data:', error);
}
return null;
}
};
addEmbedIframeBlock = (
props: Partial<EmbedIframeBlockProps>,
parentId: string,
index?: number
): string | undefined => {
const blockId = this.store.addBlock(
'affine:embed-iframe',
props,
parentId,
index
);
return blockId;
};
}
```
**Usage:**
```typescript
// Usage example
const embedIframeService = this.std.get(EmbedIframeService);
// Check if a URL can be embedded
const canEmbed = embedIframeService.canEmbed(url);
// Get embed data for a URL
const embedData = await embedIframeService.getEmbedIframeData(url);
// Add an embed iframe block to the document
const block = embedIframeService.addEmbedIframeBlock({
url,
iframeUrl: embedData.iframe_url,
title: embedData.title,
description: embedData.description
}, parentId, index);
```
**Key features:**
* URL validation and transformation
* Provider-specific data fetching
* Block creation and management
## Adaptations
### Toolbar Integration
Added toolbar actions for embedded content:
* Copy link
* Edit embed title and description
* Toggle between inline/card views
* Add caption
* And more
### Slash Menu Integration
Added a new slash menu option for embedding content:
* Embed item for inserting embed iframe block
* Conditional rendering based on feature flags
### Adapters
Implemented adapters for various formats:
* **HTML Adapter**: Exports embed original urls as html links
* **Markdown Adapter**: Exports embed original urls as markdown links
* **Plain Text Adapter**: Exports embed original urls as link text
## To Be Continued:
- [ ] **UI Optimization**
- [ ] **Edgeless Mode Support**
- [ ] **Mobile Support**
2025-03-13 04:11:46 +00:00
akumatus
98a3cf8516
feat(core): update blocksuite icons ( #10805 )
2025-03-13 03:55:55 +00:00
zzj3720
f6a62fa737
fix(editor): clicking the sorting button results in an error ( #10800 )
2025-03-13 03:17:48 +00:00
fundon
5ed8541cb1
fix(editor): should directly return the sub-action content if it exists ( #10778 )
2025-03-12 16:54:02 +00:00
zzj3720
01151ec18f
refactor(editor): add runtime type checks to database cell values ( #10770 )
2025-03-12 09:22:41 +00:00
fundon
d823792f85
refactor(editor): simplify color picker ( #10776 )
...
### What's Changed!
* Added `enableCustomColor` property into `EdgelessColorPickerButton` component
* Removed redundant code
2025-03-12 05:17:04 +00:00
EYHN
4b5d1de206
feat(core): add blocksuite writer info service ( #10754 )
2025-03-12 05:02:04 +00:00
Mirone
cd63e0ed8b
feat(editor): replace slot with rxjs subject ( #10768 )
2025-03-12 11:29:24 +09:00
L-Sun
c378a8a3ad
fix(editor): horizontal scroll bar missing in code block ( #10742 )
2025-03-12 01:14:45 +00:00
fundon
006bdd29b8
fix(editor): clip content within menu ( #10764 )
...
Closes: [BS-2796](https://linear.app/affine-design/issue/BS-2796/menu-中内容被剪切的问题 )
2025-03-11 12:39:59 +00:00
fundon
aa690e6c91
refactor(editor): move color panel into color picker ( #10758 )
2025-03-11 09:37:09 +00:00
Yifeng Wang
06889295e0
Merge pull request #10745 from toeverything/doodl/gfx-turbo-renderer
...
refactor(editor): add gfx turbo renderer package
2025-03-11 12:48:31 +08:00
Saul-Mirone
9cfd1c321e
fix(editor): missing re-subscription for slots on store ( #10750 )
2025-03-11 04:07:06 +00:00