Saul-Mirone
0fbca31c27
refactor(editor): improve edgeless clipboard config ( #11472 )
2025-04-05 03:48:26 +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
donteatfriedrice
b8fd6370f8
fix(editor): remove embed iframe toolbar edit item ( #11396 )
2025-04-02 08:40: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
L-Sun
8d9f5327a1
chore(editor): update slash menu tooltips of latex and loom ( #11350 )
...
Close [BS-2983](https://linear.app/affine-design/issue/BS-2983/slash-menu-loom没有tooltip )
Close [BS-2953](https://linear.app/affine-design/issue/BS-2982/slash-menu中latex没有tooltip )
2025-04-02 01:11:18 +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
donteatfriedrice
b2aa3084ec
feat(editor): support to drag embed iframe from note to surface ( #11267 )
...
Close [BS-2807](https://linear.app/affine-design/issue/BS-2807/note-中与-surface-中-embed-iframe-block-互相拖动时的优化 )
2025-03-31 06:23:11 +00:00
doouding
c4032e1bc0
fix: youtube block style ( #11235 )
...
Fixes [BS-2687](https://linear.app/affine-design/issue/BS-2687/[bug]-video-block-缩放选区坏掉 )
2025-03-28 07:46:07 +00:00
Saul-Mirone
205cd7a86d
refactor(editor): rename block-std to std ( #11250 )
...
Closes: BS-2946
2025-03-28 07:20:34 +00:00
donteatfriedrice
7193393a06
fix(editor): embed iframe block idle status selected style ( #11239 )
...
Close [BS-2936](https://linear.app/affine-design/issue/BS-2936/占位去掉描边-grabber选中时颜色加深即可 )
2025-03-28 02:25:00 +00:00
donteatfriedrice
a459a00b21
feat(editor): support convert edgeless bookmark to embed iframe block ( #11237 )
...
Close [BS-2941](https://linear.app/affine-design/issue/BS-2941/白板-surface-中-card-view-无法转为-embed-view )
2025-03-28 02:25:00 +00:00
donteatfriedrice
676a8d653f
fix(editor): reorder slash menu content & media group ( #11233 )
...
Close [BS-2935](https://linear.app/affine-design/issue/BS-2935/embed-在-slash-menu-中位置调整 )
2025-03-27 08:36:40 +00:00
donteatfriedrice
0c73fde44a
feat(editor): update embed iframe toolbar config ( #11221 )
...
part of [BS-2843](https://linear.app/affine-design/issue/BS-2843/iframe-embed-block-占位态 )
2025-03-27 04:17:30 +00:00
donteatfriedrice
e763061bd6
fix(editor): dragging area gets stuck when hovering some embed blocks ( #11223 )
...
Close [BS-2778](https://linear.app/affine-design/issue/BS-2778/[bug]-选区框选-html-block-卡顿 )
2025-03-27 03:59:26 +00:00
Saul-Mirone
0a8d8e0a6b
feat: seperate createDoc and createStore ( #11182 )
2025-03-26 11:03:47 +00:00
donteatfriedrice
c5624bfd13
refactor(editor): embed iframe block surface toolbar extension ( #11193 )
2025-03-26 08:38:07 +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
doouding
ace5d44a61
refactor: rewrite dblclick and selection logic of default-tool ( #11036 )
...
continue #10824
### Changed
- Moved double-click-to-edit behavior from the default tool to individual model views
- Introduced `onSelected` callback interface in gfx view components to allows developers to override default selection logic
2025-03-26 07:32:43 +00:00
fundon
ce7e3330f4
fix(editor): description of linked doc should be displayed on multiple lines ( #11162 )
...
Closes: [BS-2902](https://linear.app/affine-design/issue/BS-2902/有个bug,这个编辑之后换行会没有-fundon )
2025-03-26 03:00:24 +00:00
Saul-Mirone
e5e429e7b2
feat(editor): add inline packages ( #11048 )
2025-03-20 13:47:35 +00:00
Saul-Mirone
66ea3038af
refactor(editor): align rich text util apis ( #11039 )
2025-03-20 10:40:11 +00:00
donteatfriedrice
f4202a7976
refactor(editor): move embed iframe config and service extension to shared ( #11029 )
2025-03-20 08:56:25 +00:00
donteatfriedrice
e83f7eba00
feat(editor): add embed iframe provider google docs ( #10998 )
...
Related [BS-2835](https://linear.app/affine-design/issue/BS-2835/支持更多-embed-iframe-providers )
2025-03-20 04:42:26 +00:00
donteatfriedrice
46ed76ecb0
feat(editor): add embed iframe provider excalidraw ( #10997 )
2025-03-20 03:14:42 +00:00
fundon
831f290f84
refactor(editor): edgeless toolbar chevron down icon ( #10898 )
2025-03-20 02:08:20 +00:00
Saul-Mirone
2701be8d08
refactor(editor): remove empty block services ( #11014 )
2025-03-20 01:04:20 +09:00
donteatfriedrice
f6e32d8894
feat(editor): add embed iframe provider miro ( #10996 )
2025-03-19 13:57:30 +00:00
fundon
e686a6aecc
refactor(editor): edgeless internal embed card toolbar config extension ( #10717 )
2025-03-19 12:34:17 +00:00
fundon
0442430971
refactor(editor): edgeless html embed card toolbar config extension ( #10716 )
2025-03-19 05:03:00 +00:00
fundon
7f4b56e05c
refactor(editor): edgeless external embed card toolbar config extension ( #10712 )
2025-03-19 04:05:36 +00:00
donteatfriedrice
3a2d386275
feat(editor): support add embed iframe block in mobile ( #10955 )
...
To close [BS-2664](https://linear.app/affine-design/issue/BS-2664/iframe-embed-block-移动端支持 )
2025-03-19 02:58:51 +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
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
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
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
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
Mirone
cd63e0ed8b
feat(editor): replace slot with rxjs subject ( #10768 )
2025-03-12 11:29:24 +09:00
L-Sun
c13d4c575f
chore(editor): update slash menu tooltips ( #10746 )
...
Close [BS-2676](https://linear.app/affine-design/issue/BS-2676/loom入口增加简介 ) [BS-2767](https://linear.app/affine-design/issue/BS-2767/table的tooltip需要更新,现在用的是database的 )
2025-03-10 12:38:59 +00:00
Saul-Mirone
8aedef0a36
chore(editor): reorg packages ( #10702 )
2025-03-08 12:00:34 +08:00