feat(editor): by default render code iframe for html preview (#12848)

#### PR Dependency Tree


* **PR #12848** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a feature flag to enable or disable web container
functionality for code block previews.
- **Improvements**
- Code block HTML previews now support an alternative rendering method
based on the new feature flag, enhancing flexibility.
- **Chores**
- Updated feature flag settings by removing an obsolete flag and adding
the new web container flag.
- **Tests**
- Simplified code block preview tests for faster and more direct
validation of HTML preview content.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Peng Xiao
2025-06-19 09:12:33 +08:00
committed by GitHub
parent e046260deb
commit ce951ec316
6 changed files with 46 additions and 51 deletions
@@ -21,6 +21,7 @@ export interface BlockSuiteFlags {
enable_table_virtual_scroll: boolean;
enable_turbo_renderer: boolean;
enable_dom_renderer: boolean;
enable_web_container: boolean;
}
export class FeatureFlagService extends StoreExtension {
@@ -46,6 +47,7 @@ export class FeatureFlagService extends StoreExtension {
enable_table_virtual_scroll: false,
enable_turbo_renderer: false,
enable_dom_renderer: false,
enable_web_container: false,
});
setFlag(key: keyof BlockSuiteFlags, value: boolean) {
@@ -2,12 +2,14 @@ import track from '@affine/track';
import { CodeBlockPreviewExtension } from '@blocksuite/affine/blocks/code';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import type { CodeBlockModel } from '@blocksuite/affine/model';
import { FeatureFlagService } from '@blocksuite/affine/shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { css, html, LitElement, type PropertyValues } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { choose } from 'lit/directives/choose.js';
import { styleMap } from 'lit/directives/style-map.js';
import { linkIframe } from './iframe-container';
import { linkWebContainer } from './web-container';
export const CodeBlockHtmlPreview = CodeBlockPreviewExtension(
@@ -83,20 +85,36 @@ export class HTMLPreview extends SignalWatcher(WithDisposable(LitElement)) {
private _link() {
this.state = 'loading';
linkWebContainer(this.iframe, this.model)
.then(() => {
this.state = 'finish';
})
.catch(error => {
const errorMessage = `Failed to link WebContainer: ${error}`;
console.error(errorMessage);
track.doc.editor.codeBlock.htmlBlockPreviewFailed({
type: errorMessage,
const featureFlagService = this.model.store.get(FeatureFlagService);
const isWebContainerEnabled = featureFlagService.getFlag(
'enable_web_container'
);
if (isWebContainerEnabled) {
linkWebContainer(this.iframe, this.model)
.then(() => {
this.state = 'finish';
})
.catch(error => {
const errorMessage = `Failed to link WebContainer: ${error}`;
console.error(errorMessage);
track.doc.editor.codeBlock.htmlBlockPreviewFailed({
type: errorMessage,
});
this.state = 'error';
});
} else {
try {
linkIframe(this.iframe, this.model);
this.state = 'finish';
} catch (error) {
console.error('HTML preview iframe failed:', error);
this.state = 'error';
});
}
}
}
override render() {
@@ -0,0 +1,7 @@
import type { CodeBlockModel } from '@blocksuite/affine/model';
export function linkIframe(iframe: HTMLIFrameElement, model: CodeBlockModel) {
const html = model.props.text.toString();
iframe.srcdoc = html;
iframe.sandbox.add('allow-scripts');
}
@@ -1,5 +1,3 @@
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import track from '@affine/track';
import {
type ViewExtensionContext,
ViewExtensionProvider,
@@ -32,22 +30,6 @@ export class CodeBlockPreviewViewExtension extends ViewExtensionProvider {
options?: z.infer<typeof optionsSchema>
) {
super.setup(context, options);
const framework = options?.framework;
if (!framework) return;
const flag =
framework.get(FeatureFlagService).flags.enable_code_block_html_preview.$
.value;
if (!flag) return;
if (!window.crossOriginIsolated) {
track.doc.editor.codeBlock.htmlBlockPreviewFailed({
type: 'cross-origin-isolated not supported',
});
return;
}
context.register(CodeBlockHtmlPreview);
}
}
@@ -273,15 +273,6 @@ export const AFFINE_FLAGS = {
configurable: isBetaBuild || isCanaryBuild,
defaultState: false,
},
enable_code_block_html_preview: {
category: 'affine',
displayName:
'com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.name',
description:
'com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.description',
configurable: isCanaryBuild,
defaultState: isCanaryBuild,
},
enable_adapter_panel: {
category: 'affine',
displayName:
@@ -291,6 +282,14 @@ export const AFFINE_FLAGS = {
configurable: isCanaryBuild,
defaultState: false,
},
enable_web_container: {
category: 'blocksuite',
bsFlag: 'enable_web_container',
displayName: 'Enable Web Container',
description: 'Enable web container for code block preview',
defaultState: false,
configurable: true,
},
} satisfies { [key in string]: FlagInfo };
// oxlint-disable-next-line no-redeclare
@@ -22,16 +22,12 @@ test.describe('Code Block Autocomplete Operations', () => {
test.describe('Code Block Preview', () => {
test('enable html preview', async ({ page }) => {
const code = page.locator('affine-code');
const htmlPreview = page.locator('html-preview');
await openHomePage(page);
await createNewPage(page);
await waitForEditorLoad(page);
await gotoContentFromTitle(page);
await type(page, '```html aaa');
await page.waitForTimeout(3000);
// web container can not load as expected at the first time in playwright, not sure why
await page.reload();
await code.hover({
position: {
x: 155,
@@ -39,15 +35,6 @@ test.describe('Code Block Preview', () => {
},
});
await page.getByText('Preview').click();
await expect(
page
.locator('iframe[title="HTML Preview"]')
.contentFrame()
.getByText('aaa')
).toBeHidden();
await expect(htmlPreview).toHaveText('Rendering the code...');
await page.waitForTimeout(20000);
await expect(
page
.locator('iframe[title="HTML Preview"]')