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)
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
canEmbedAsIframe,
|
||||||
|
EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE,
|
||||||
|
EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE,
|
||||||
|
} from '@blocksuite/affine-block-embed';
|
||||||
import { reassociateConnectorsCommand } from '@blocksuite/affine-block-surface';
|
import { reassociateConnectorsCommand } from '@blocksuite/affine-block-surface';
|
||||||
import { toast } from '@blocksuite/affine-components/toast';
|
import { toast } from '@blocksuite/affine-components/toast';
|
||||||
import {
|
import {
|
||||||
@@ -13,7 +18,6 @@ import {
|
|||||||
ActionPlacement,
|
ActionPlacement,
|
||||||
EmbedIframeService,
|
EmbedIframeService,
|
||||||
EmbedOptionProvider,
|
EmbedOptionProvider,
|
||||||
FeatureFlagService,
|
|
||||||
type LinkEventType,
|
type LinkEventType,
|
||||||
type ToolbarAction,
|
type ToolbarAction,
|
||||||
type ToolbarActionGroup,
|
type ToolbarActionGroup,
|
||||||
@@ -145,26 +149,20 @@ const builtinToolbarConfig = {
|
|||||||
if (!model) return true;
|
if (!model) return true;
|
||||||
|
|
||||||
const url = model.props.url;
|
const url = model.props.url;
|
||||||
// check if the url can be embedded as iframe block
|
// check if the url can be embedded as iframe block or other embed blocks
|
||||||
const featureFlag = ctx.std.get(FeatureFlagService);
|
|
||||||
const embedIframeService = ctx.std.get(EmbedIframeService);
|
|
||||||
const isEmbedIframeEnabled = featureFlag.getFlag(
|
|
||||||
'enable_embed_iframe_block'
|
|
||||||
);
|
|
||||||
const canEmbedAsIframe =
|
|
||||||
isEmbedIframeEnabled && embedIframeService.canEmbed(url);
|
|
||||||
|
|
||||||
const options = ctx.std
|
const options = ctx.std
|
||||||
.get(EmbedOptionProvider)
|
.get(EmbedOptionProvider)
|
||||||
.getEmbedBlockOptions(url);
|
.getEmbedBlockOptions(url);
|
||||||
|
|
||||||
return !canEmbedAsIframe && options?.viewType !== 'embed';
|
return (
|
||||||
|
!canEmbedAsIframe(ctx.std, url) && options?.viewType !== 'embed'
|
||||||
|
);
|
||||||
},
|
},
|
||||||
run(ctx) {
|
run(ctx) {
|
||||||
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
|
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
const { caption, url, style } = model.props;
|
const { caption, url, title, description, style } = model.props;
|
||||||
const { parent } = model;
|
const { parent } = model;
|
||||||
const index = parent?.children.indexOf(model);
|
const index = parent?.children.indexOf(model);
|
||||||
if (!parent) return;
|
if (!parent) return;
|
||||||
@@ -172,14 +170,10 @@ const builtinToolbarConfig = {
|
|||||||
let blockId: string | undefined;
|
let blockId: string | undefined;
|
||||||
|
|
||||||
// first try to embed as iframe block
|
// first try to embed as iframe block
|
||||||
const featureFlag = ctx.std.get(FeatureFlagService);
|
if (canEmbedAsIframe(ctx.std, url)) {
|
||||||
const isEmbedIframeEnabled = featureFlag.getFlag(
|
const embedIframeService = ctx.std.get(EmbedIframeService);
|
||||||
'enable_embed_iframe_block'
|
|
||||||
);
|
|
||||||
const embedIframeService = ctx.std.get(EmbedIframeService);
|
|
||||||
if (isEmbedIframeEnabled && embedIframeService.canEmbed(url)) {
|
|
||||||
blockId = embedIframeService.addEmbedIframeBlock(
|
blockId = embedIframeService.addEmbedIframeBlock(
|
||||||
{ url, caption },
|
{ url, caption, title, description },
|
||||||
parent.id,
|
parent.id,
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
@@ -201,6 +195,8 @@ const builtinToolbarConfig = {
|
|||||||
{
|
{
|
||||||
url,
|
url,
|
||||||
caption,
|
caption,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
style: newStyle,
|
style: newStyle,
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
@@ -379,30 +375,62 @@ const builtinSurfaceToolbarConfig = {
|
|||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
const { id: oldId, xywh, parent } = model;
|
const { id: oldId, xywh, parent } = model;
|
||||||
const { url, caption } = model.props;
|
const { url, caption, title, description } = model.props;
|
||||||
const options = ctx.std
|
|
||||||
.get(EmbedOptionProvider)
|
|
||||||
.getEmbedBlockOptions(url);
|
|
||||||
|
|
||||||
if (options?.viewType !== 'embed') return;
|
let newId: string | undefined;
|
||||||
|
|
||||||
const { flavour, styles } = options;
|
// first try to embed as iframe block
|
||||||
let { style } = model.props;
|
if (canEmbedAsIframe(ctx.std, url)) {
|
||||||
|
const embedIframeService = ctx.std.get(EmbedIframeService);
|
||||||
|
const config = embedIframeService.getConfig(url);
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!styles.includes(style)) {
|
const bound = Bound.deserialize(xywh);
|
||||||
style = styles[0];
|
const options = config.options;
|
||||||
|
const { widthInSurface, heightInSurface } = options ?? {};
|
||||||
|
bound.w = widthInSurface ?? EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE;
|
||||||
|
bound.h =
|
||||||
|
heightInSurface ?? EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE;
|
||||||
|
|
||||||
|
newId = ctx.store.addBlock(
|
||||||
|
'affine:embed-iframe',
|
||||||
|
{ url, caption, title, description, xywh: bound.serialize() },
|
||||||
|
parent
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const options = ctx.std
|
||||||
|
.get(EmbedOptionProvider)
|
||||||
|
.getEmbedBlockOptions(url);
|
||||||
|
|
||||||
|
if (options?.viewType !== 'embed') return;
|
||||||
|
|
||||||
|
const { flavour, styles } = options;
|
||||||
|
let { style } = model.props;
|
||||||
|
|
||||||
|
if (!styles.includes(style)) {
|
||||||
|
style = styles[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const bound = Bound.deserialize(xywh);
|
||||||
|
bound.w = EMBED_CARD_WIDTH[style];
|
||||||
|
bound.h = EMBED_CARD_HEIGHT[style];
|
||||||
|
|
||||||
|
newId = ctx.store.addBlock(
|
||||||
|
flavour,
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
caption,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
style,
|
||||||
|
xywh: bound.serialize(),
|
||||||
|
},
|
||||||
|
parent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = Bound.deserialize(xywh);
|
|
||||||
bounds.w = EMBED_CARD_WIDTH[style];
|
|
||||||
bounds.h = EMBED_CARD_HEIGHT[style];
|
|
||||||
|
|
||||||
const newId = ctx.store.addBlock(
|
|
||||||
flavour,
|
|
||||||
{ url, caption, style, xywh: bounds.serialize() },
|
|
||||||
parent
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.command.exec(reassociateConnectorsCommand, { oldId, newId });
|
ctx.command.exec(reassociateConnectorsCommand, { oldId, newId });
|
||||||
|
|
||||||
ctx.store.deleteBlock(model);
|
ctx.store.deleteBlock(model);
|
||||||
@@ -427,7 +455,7 @@ const builtinSurfaceToolbarConfig = {
|
|||||||
.get(EmbedOptionProvider)
|
.get(EmbedOptionProvider)
|
||||||
.getEmbedBlockOptions(url);
|
.getEmbedBlockOptions(url);
|
||||||
|
|
||||||
return options?.viewType === 'embed';
|
return canEmbedAsIframe(ctx.std, url) || options?.viewType === 'embed';
|
||||||
},
|
},
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
|
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
export * from './adapters';
|
export * from './adapters';
|
||||||
export * from './commands';
|
export * from './commands';
|
||||||
export * from './configs';
|
export * from './configs';
|
||||||
|
export {
|
||||||
|
EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE,
|
||||||
|
EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE,
|
||||||
|
} from './consts';
|
||||||
export * from './embed-iframe-block';
|
export * from './embed-iframe-block';
|
||||||
export * from './embed-iframe-spec';
|
export * from './embed-iframe-spec';
|
||||||
|
export { canEmbedAsIframe } from './utils';
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import {
|
||||||
|
EmbedIframeService,
|
||||||
|
FeatureFlagService,
|
||||||
|
} from '@blocksuite/affine-shared/services';
|
||||||
|
import type { BlockStdScope } from '@blocksuite/block-std';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options for the embed iframe url validation
|
* The options for the embed iframe url validation
|
||||||
*/
|
*/
|
||||||
@@ -59,3 +65,16 @@ export function safeGetIframeSrc(htmlString: string): string | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the url can be embedded as an iframe
|
||||||
|
* @param std The block std scope
|
||||||
|
* @param url The url to check
|
||||||
|
* @returns Whether the url can be embedded as an iframe
|
||||||
|
*/
|
||||||
|
export function canEmbedAsIframe(std: BlockStdScope, url: string) {
|
||||||
|
const featureFlag = std.get(FeatureFlagService);
|
||||||
|
const isEmbedIframeEnabled = featureFlag.getFlag('enable_embed_iframe_block');
|
||||||
|
const embedIframeService = std.get(EmbedIframeService);
|
||||||
|
return isEmbedIframeEnabled && embedIframeService.canEmbed(url);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user