From ce951ec316d3d625fd37f83f84e9124725fc5893 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Thu, 19 Jun 2025 09:12:33 +0800 Subject: [PATCH] feat(editor): by default render code iframe for html preview (#12848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### PR Dependency Tree * **PR #12848** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## 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. --- .../src/services/feature-flag-service.ts | 2 + .../code-block-preview/html-preview.ts | 40 ++++++++++++++----- .../code-block-preview/iframe-container.ts | 7 ++++ .../code-block-preview/index.ts | 18 --------- .../core/src/modules/feature-flag/constant.ts | 17 ++++---- .../e2e/blocksuite/code/crud.spec.ts | 13 ------ 6 files changed, 46 insertions(+), 51 deletions(-) create mode 100644 packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts diff --git a/blocksuite/affine/shared/src/services/feature-flag-service.ts b/blocksuite/affine/shared/src/services/feature-flag-service.ts index bdbf5c55b8..659388d1e3 100644 --- a/blocksuite/affine/shared/src/services/feature-flag-service.ts +++ b/blocksuite/affine/shared/src/services/feature-flag-service.ts @@ -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) { diff --git a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts index 6dd2b27b96..223a44c83d 100644 --- a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts +++ b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts @@ -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() { diff --git a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts new file mode 100644 index 0000000000..c0043d5c41 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts @@ -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'); +} diff --git a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/index.ts b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/index.ts index ac2959df29..13f2e6d502 100644 --- a/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/index.ts +++ b/packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/index.ts @@ -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 ) { 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); } } diff --git a/packages/frontend/core/src/modules/feature-flag/constant.ts b/packages/frontend/core/src/modules/feature-flag/constant.ts index cb557c2dc3..50e2809e2a 100644 --- a/packages/frontend/core/src/modules/feature-flag/constant.ts +++ b/packages/frontend/core/src/modules/feature-flag/constant.ts @@ -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 diff --git a/tests/affine-local/e2e/blocksuite/code/crud.spec.ts b/tests/affine-local/e2e/blocksuite/code/crud.spec.ts index 7590cfb44b..c85210c090 100644 --- a/tests/affine-local/e2e/blocksuite/code/crud.spec.ts +++ b/tests/affine-local/e2e/blocksuite/code/crud.spec.ts @@ -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"]')