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:
@@ -21,6 +21,7 @@ export interface BlockSuiteFlags {
|
|||||||
enable_table_virtual_scroll: boolean;
|
enable_table_virtual_scroll: boolean;
|
||||||
enable_turbo_renderer: boolean;
|
enable_turbo_renderer: boolean;
|
||||||
enable_dom_renderer: boolean;
|
enable_dom_renderer: boolean;
|
||||||
|
enable_web_container: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FeatureFlagService extends StoreExtension {
|
export class FeatureFlagService extends StoreExtension {
|
||||||
@@ -46,6 +47,7 @@ export class FeatureFlagService extends StoreExtension {
|
|||||||
enable_table_virtual_scroll: false,
|
enable_table_virtual_scroll: false,
|
||||||
enable_turbo_renderer: false,
|
enable_turbo_renderer: false,
|
||||||
enable_dom_renderer: false,
|
enable_dom_renderer: false,
|
||||||
|
enable_web_container: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
setFlag(key: keyof BlockSuiteFlags, value: boolean) {
|
setFlag(key: keyof BlockSuiteFlags, value: boolean) {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import track from '@affine/track';
|
|||||||
import { CodeBlockPreviewExtension } from '@blocksuite/affine/blocks/code';
|
import { CodeBlockPreviewExtension } from '@blocksuite/affine/blocks/code';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import type { CodeBlockModel } from '@blocksuite/affine/model';
|
import type { CodeBlockModel } from '@blocksuite/affine/model';
|
||||||
|
import { FeatureFlagService } from '@blocksuite/affine/shared/services';
|
||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||||
import { css, html, LitElement, type PropertyValues } from 'lit';
|
import { css, html, LitElement, type PropertyValues } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
import { choose } from 'lit/directives/choose.js';
|
import { choose } from 'lit/directives/choose.js';
|
||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
|
|
||||||
|
import { linkIframe } from './iframe-container';
|
||||||
import { linkWebContainer } from './web-container';
|
import { linkWebContainer } from './web-container';
|
||||||
|
|
||||||
export const CodeBlockHtmlPreview = CodeBlockPreviewExtension(
|
export const CodeBlockHtmlPreview = CodeBlockPreviewExtension(
|
||||||
@@ -83,20 +85,36 @@ export class HTMLPreview extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
|
|
||||||
private _link() {
|
private _link() {
|
||||||
this.state = 'loading';
|
this.state = 'loading';
|
||||||
linkWebContainer(this.iframe, this.model)
|
|
||||||
.then(() => {
|
|
||||||
this.state = 'finish';
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
const errorMessage = `Failed to link WebContainer: ${error}`;
|
|
||||||
|
|
||||||
console.error(errorMessage);
|
const featureFlagService = this.model.store.get(FeatureFlagService);
|
||||||
track.doc.editor.codeBlock.htmlBlockPreviewFailed({
|
const isWebContainerEnabled = featureFlagService.getFlag(
|
||||||
type: errorMessage,
|
'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';
|
this.state = 'error';
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
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 {
|
import {
|
||||||
type ViewExtensionContext,
|
type ViewExtensionContext,
|
||||||
ViewExtensionProvider,
|
ViewExtensionProvider,
|
||||||
@@ -32,22 +30,6 @@ export class CodeBlockPreviewViewExtension extends ViewExtensionProvider {
|
|||||||
options?: z.infer<typeof optionsSchema>
|
options?: z.infer<typeof optionsSchema>
|
||||||
) {
|
) {
|
||||||
super.setup(context, options);
|
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);
|
context.register(CodeBlockHtmlPreview);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,15 +273,6 @@ export const AFFINE_FLAGS = {
|
|||||||
configurable: isBetaBuild || isCanaryBuild,
|
configurable: isBetaBuild || isCanaryBuild,
|
||||||
defaultState: false,
|
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: {
|
enable_adapter_panel: {
|
||||||
category: 'affine',
|
category: 'affine',
|
||||||
displayName:
|
displayName:
|
||||||
@@ -291,6 +282,14 @@ export const AFFINE_FLAGS = {
|
|||||||
configurable: isCanaryBuild,
|
configurable: isCanaryBuild,
|
||||||
defaultState: false,
|
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 };
|
} satisfies { [key in string]: FlagInfo };
|
||||||
|
|
||||||
// oxlint-disable-next-line no-redeclare
|
// oxlint-disable-next-line no-redeclare
|
||||||
|
|||||||
@@ -22,16 +22,12 @@ test.describe('Code Block Autocomplete Operations', () => {
|
|||||||
test.describe('Code Block Preview', () => {
|
test.describe('Code Block Preview', () => {
|
||||||
test('enable html preview', async ({ page }) => {
|
test('enable html preview', async ({ page }) => {
|
||||||
const code = page.locator('affine-code');
|
const code = page.locator('affine-code');
|
||||||
const htmlPreview = page.locator('html-preview');
|
|
||||||
|
|
||||||
await openHomePage(page);
|
await openHomePage(page);
|
||||||
await createNewPage(page);
|
await createNewPage(page);
|
||||||
await waitForEditorLoad(page);
|
await waitForEditorLoad(page);
|
||||||
await gotoContentFromTitle(page);
|
await gotoContentFromTitle(page);
|
||||||
await type(page, '```html aaa');
|
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({
|
await code.hover({
|
||||||
position: {
|
position: {
|
||||||
x: 155,
|
x: 155,
|
||||||
@@ -39,15 +35,6 @@ test.describe('Code Block Preview', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
await page.getByText('Preview').click();
|
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(
|
await expect(
|
||||||
page
|
page
|
||||||
.locator('iframe[title="HTML Preview"]')
|
.locator('iframe[title="HTML Preview"]')
|
||||||
|
|||||||
Reference in New Issue
Block a user