donteatfriedrice
83670ab335
feat(editor): add experimental feature citation ( #11984 )
...
Closes: [BS-3122](https://linear.app/affine-design/issue/BS-3122/footnote-definition-adapter-适配 )
Closes: [BS-3123](https://linear.app/affine-design/issue/BS-3123/几个-block-card-view-适配-footnote-态 )
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
- **New Features**
- Introduced a new citation card component and web element for displaying citations.
- Added support for citation-style rendering in attachment, bookmark, and linked document blocks.
- Enabled citation parsing from footnote definitions in markdown for attachments, bookmarks, and linked docs.
- Added a feature flag to enable or disable citation features.
- Provided new toolbar logic to disable downloads for citation-style attachments.
- **Improvements**
- Updated block models and properties to support citation identifiers.
- Added localization and settings for the citation experimental feature.
- Enhanced markdown adapters to recognize and process citation footnotes.
- Included new constants and styles for citation card display.
- **Bug Fixes**
- Ensured readonly state is respected in block interactions and rendering for citation blocks.
- **Documentation**
- Added exports and effects for new citation components and features.
- **Tests**
- Updated snapshots to include citation-related properties in block data.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-29 06:59:27 +00:00
doodlewind
be28038e94
perf(editor): fallback to placeholder for canvas text ( #12033 )
...
### TL;DR
For canvas elements, this PR adds placeholders during zooming operations to improve performance.

### What changed?
- Implemented placeholder rendering during zooming operations in the canvas renderer, but not only DOM.
- Added a `forceFullRender` property to the `GfxCompatibleInterface` to allow elements to opt out of placeholder rendering
- Set `forceFullRender = true` for connectors to ensure they always render properly, even during zooming
- Connected the turbo renderer to the viewport's zooming state to automatically switch between full and placeholder rendering
### Why make this change?
Rendering complex elements during zooming operations can cause performance issues and make the UI feel sluggish. Rendering connector label also leads to high cost DOM `set font` delays.

The turbo renderer improves performance by displaying simple placeholders for elements during zooming, while still rendering critical elements like connectors fully. This creates a smoother user experience while maintaining essential visual information.
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
- **New Features**
- Introduced a feature-flag-controlled "turbo" rendering mode that displays placeholder graphics during zooming for improved performance.
- Added the ability to override placeholder rendering for specific elements, ensuring full rendering when required.
- **Bug Fixes**
- Enhanced rendering logic to ensure connectors always render fully, even during zoom operations.
- **Documentation**
- Updated API documentation to reflect new properties related to rendering behavior.
- **Tests**
- Improved tests to verify correct rendering behavior for connectors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-29 03:05:17 +00:00
Saul-Mirone
81b439c4e1
feat(editor): add gfx pointer extension ( #12006 )
...
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
- **New Features**
- Introduced a new pointer graphics module with tools and quick tool integration for edgeless surfaces.
- Added a quick tool button for pointer interactions in edgeless mode.
- Exposed new extension points for pointer graphics and effects.
- **Improvements**
- Integrated pointer graphics as a dependency into related packages.
- Enhanced toolbar context to support additional surface alignment modes.
- Added conditional clipboard configuration registrations for edgeless contexts across multiple block types.
- **Removals**
- Removed legacy tool and effect definitions and related quick tool exports from edgeless components.
- Streamlined extension arrays and removed unused exports for a cleaner codebase.
- Deleted obsolete utility functions and component registrations.
- **Chores**
- Updated workspace and TypeScript project references to include the new pointer graphics module.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-27 04:46:44 +00:00
L-Sun
9dbdd4b7ba
refactor(editor): add helper function for undo notification ( #11903 )
...
### What Changes
- Refactors the `notify` function call with undo button. It is called `notifyWithUndo`, which can associate the visibility of the `NotificationCard` with the top undo item in history stack, such as undo by shortcut `Cmd+Z`.
- change icon of the "Insert into page" button. Close [BS-3267](https://linear.app/affine-design/issue/BS-3267/frame和group的insert-into-page图标也更换一下 )
2025-04-23 05:56:23 +00:00
L-Sun
27ff9ab9f4
refactor(core): move actions to footer of notification card ( #11894 )
...
This PR move all actions button to the footer of `NotificationCard`. There are some example as following:
### No Changes



### Changes
### Before

#### After

2025-04-23 05:41:14 +00:00
Saul-Mirone
6d6504e2af
feat(editor): replace spec provider with extension manager ( #11861 )
...
Closes: BS-3273
2025-04-22 07:40:41 +00:00
L-Sun
3264e65980
chore(editor): add feature flag to embed doc with alias ( #11797 )
2025-04-18 07:39:16 +00:00
donteatfriedrice
a555df0200
fix(editor): footnote popup style and position issues ( #11771 )
...
Close [BS-3049](https://linear.app/affine-design/issue/BS-3049/chat引用的样式坏了 ) [BS-3024](https://linear.app/affine-design/issue/BS-3024/footnote-在容器边缘时,hover-抽搐 )
2025-04-18 02:44:21 +00:00
L-Sun
98899b4eea
feat(editor): affine to blocksuite doc dnd with prefered card view ( #11748 )
...
Close [BS-3070](https://linear.app/affine-design/issue/BS-3070/文档拖动进入edgeless,形成引用时,默认形成embeded的引用,但是记录上次选择 )
2025-04-17 04:06:50 +00:00
donteatfriedrice
022e5f2c93
fix(editor): update embed iframe block event tracker ( #11736 )
...
Close [BS-3151](https://linear.app/affine-design/issue/BS-3151/埋一下-reload-link-成功失败 )
2025-04-16 09:53:38 +00:00
L-Sun
e1cb8198f1
fix(editor): android bs keyboard provider error ( #11647 )
...
### What Changes
- fixed keyboard service can not be initialized since a anonymous `BSKeyboardWithActionService` class was provider to di
- fixed tool panel was not closed when focus on other pragraph by clicking
- optimized code structure of fallback `show` and `hide` of keyboard
2025-04-13 09:59:24 +00:00
fundon
bb79781dd8
fix(editor): should clear selection when switching doc mode ( #11545 )
...
Closes: [BS-3050](https://linear.app/affine-design/issue/BS-3050/切换模式时,清除选区 )
2025-04-10 03:10:50 +00:00
Saul-Mirone
8c473cf685
fix(editor): adjust block meta info ( #11488 )
...
Closes: BS-2901
2025-04-07 05:12:04 +00:00
Saul-Mirone
854569af2d
refactor(editor): remove lasso tool ( #11481 )
2025-04-07 01:12:28 +00:00
L-Sun
90a3bd53cd
chore(editor): telemetry for slash menu ( #11438 )
...
Close [BS-2959](https://linear.app/affine-design/issue/BS-2959/slash-menu埋点 )
2025-04-03 13:31:09 +00:00
fundon
565d7b2b1e
fix(editor): should not show inner toolbar of surface-ref in edgeless ( #11453 )
...
Closes: [BS-3015](https://linear.app/affine-design/issue/BS-3015/toolbar-有时会出现空状态且被显示 ) [BS-3023](https://linear.app/affine-design/issue/BS-3023/surface-ref-inner-toolbar-的标题圆角 )
2025-04-03 11:13:09 +00:00
donteatfriedrice
9206145a48
feat(editor): remove embed iframe feature flag ( #11432 )
...
Close [BS-3019](https://linear.app/affine-design/issue/BS-3019/remove-embed-iframe-feature-flag )
2025-04-03 04:59:52 +00:00
L-Sun
5109ceccec
fix(ios): can not open keyboard in editor ( #11401 )
...
Close [BS-2917](https://linear.app/affine-design/issue/BS-2917/【移动端】ios-唤起键盘的edge-case )
This PR fixes an issue where the keyboard cannot be re-triggered on iOS devices after the keyboard toolbar is hidden or executing some actions in keyboard toolbar.
Key changes:
- Preserve and restore the initial input mode when keyboard toolbar shows/hides
- Improve virtual keyboard service interface to better handle keyboard state
- Add proper cleanup of input mode state in component lifecycle
2025-04-03 01:51:56 +00:00
fundon
d9fade8045
fix(core): should display date as original title of journal ( #11375 )
...
Closes: [BS-2991](https://linear.app/affine-design/issue/BS-2991/linked-journal添加alias后,toolbar上获取不到标题 )
2025-04-02 01:24:43 +00:00
fundon
7af5e53838
refactor(editor): image toolbar config extension ( #11329 )
...
Closes: [BS-2378](https://linear.app/affine-design/issue/BS-2378/image-toolbar-迁移 )
2025-04-01 10:04:28 +00:00
donteatfriedrice
1dbd34177e
feat(editor): embed iframe block event tracking ( #11313 )
...
Close [BS-2958](https://linear.app/affine-design/issue/BS-2958/埋点相关 )
2025-04-01 02:50:23 +00:00
Brooooooklyn
2eecfef393
chore: replace woff to woff2 ( #11328 )
2025-03-31 15:23:21 +00:00
Saul-Mirone
205cd7a86d
refactor(editor): rename block-std to std ( #11250 )
...
Closes: BS-2946
2025-03-28 07:20:34 +00:00
L-Sun
af91a0217f
refactor(editor): inner toolbar surface-ref block with extension ( #11246 )
...
This PR refactor `surface-ref` toolbar with `ToolbarExtension`
2025-03-28 05:48:24 +00:00
L-Sun
e2c752d56f
feat(editor): inner toolbar layout for block ( #11243 )
...
Close [BS-2808](https://linear.app/affine-design/issue/BS-2808/组件内-toolbar-重构 )
2025-03-28 03:47:37 +00:00
fundon
c0c0e5ea8f
chore(editor): add event tracking to highlighter tool ( #11232 )
...
Closes: [BS-2947](https://linear.app/affine-design/issue/BS-2947/埋点相关 )
2025-03-28 01:34:44 +00:00
fundon
6ade2b7084
chore(editor): add event tracking to drawing tool ( #11230 )
...
Closes: [BS-2833](https://linear.app/affine-design/issue/BS-2833/考虑屏蔽或者降低brush-行为的采集密度 )
2025-03-27 08:17:44 +00:00
Saul-Mirone
0a8d8e0a6b
feat: seperate createDoc and createStore ( #11182 )
2025-03-26 11:03:47 +00:00
donteatfriedrice
39fa8e87cf
feat(editor): add idle status for embed iframe block ( #11142 )
...
To close:
[BS-2843](https://linear.app/affine-design/issue/BS-2843/iframe-embed-block-占位态 )
[BS-2844](https://linear.app/affine-design/issue/BS-2844/iframe-embed-block-create-modal-ui-调整 )
[BS-2880](https://linear.app/affine-design/issue/BS-2880/spotify-选中时圆角有问题 )
[BS-2881](https://linear.app/affine-design/issue/BS-2881/miro-圆角有问题-点击-see-the-board-加载之后就好了 )
2025-03-26 08:38:06 +00:00
fundon
d7eccd10ee
chore(editor): add edgeless scribbled style feature flag ( #11127 )
...
Closes: [BS-2805](https://linear.app/affine-design/issue/BS-2805/下掉线条样式切换功能,需添加-feature-flag )
2025-03-24 12:55:00 +00:00
donteatfriedrice
f4202a7976
refactor(editor): move embed iframe config and service extension to shared ( #11029 )
2025-03-20 08:56:25 +00:00
fundon
8b995ea420
chore(editor): remove edgeless element toolbar ( #10900 )
2025-03-20 02:08:21 +00:00
fundon
a7acd5c5b1
refactor(editor): fix edgeless toolbar theme ( #10897 )
2025-03-20 02:08:20 +00:00
fundon
07a64eb004
refactor(editor): edgeless toolbar lock and unlock actions ( #10878 )
2025-03-20 02:08:16 +00:00
fundon
1acc7e5a9e
refactor(editor): edgeless text toolbar config extension ( #10811 )
2025-03-20 02:08:15 +00:00
fundon
c98f0900cc
refactor(editor): edgeless mindmap toolbar config extension ( #10803 )
2025-03-19 14:50:55 +00:00
fundon
e320552594
refactor(editor): edgeless note toolbar config extension ( #10719 )
2025-03-19 12:34:18 +00:00
fundon
7f4b56e05c
refactor(editor): edgeless external embed card toolbar config extension ( #10712 )
2025-03-19 04:05:36 +00:00
fundon
251d1d8782
refactor(editor): edgeless attacment toolbar config extension ( #10710 )
2025-03-19 00:52:22 +00:00
fundon
3cce147c60
refactor(editor): improve query methods in toolbar context ( #10714 )
2025-03-19 00:52:22 +00:00
fundon
cb37d25d7b
refactor(editor): edgeless element toolbar with new pattern ( #10511 )
2025-03-18 15:36:25 +00:00
zzj3720
3939cc1c52
feat(editor): support file column and member column for database block ( #10932 )
...
close: BS-2630, BS-2631, BS-2629, BS-2632, BS-2635
2025-03-18 14:51:45 +00:00
EYHN
b401012d85
feat(core): user service loading state ( #10922 )
2025-03-17 09:28:25 +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
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
Saul-Mirone
5148e67891
feat(editor): improve block meta updated event handler ( #10815 )
2025-03-13 06:34:03 +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
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
Saul-Mirone
9cfd1c321e
fix(editor): missing re-subscription for slots on store ( #10750 )
2025-03-11 04:07:06 +00:00