From a828c74f87161915278902dddfe65e3efbf37175 Mon Sep 17 00:00:00 2001 From: donteatfriedrice Date: Fri, 23 May 2025 14:08:12 +0000 Subject: [PATCH] feat(editor): add experimental feature adapter panel to AFFiNE canary (#12489) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: [BS-2539](https://linear.app/affine-design/issue/BS-2539/为-affine-添加-ef,并且支持在-affine-预览对应的功能) > [!warning] > This feature is only available in the canary build and is intended for debugging purposes. ## Summary by CodeRabbit - **New Features** - Introduced an "Adapter Panel" feature with a new sidebar tab for previewing document content in multiple formats (Markdown, PlainText, HTML, Snapshot), controllable via a feature flag. - Added a fully integrated adapter panel component with reactive UI elements for selecting adapters, toggling HTML preview modes, and updating content. - Provided a customizable adapter panel for both main app and playground environments, supporting content transformation pipelines and export previews. - Enabled seamless toggling and live updating of adapter panel content through intuitive menus and controls. - **Localization** - Added English translations and descriptive settings for the Adapter Panel feature. - **Chores** - Added new package and workspace dependencies along with TypeScript project references to support the Adapter Panel modules and components. --- blocksuite/affine/all/package.json | 3 + blocksuite/affine/all/src/extensions/view.ts | 2 + .../all/src/fragments/adapter-panel/index.ts | 1 + .../all/src/fragments/adapter-panel/view.ts | 1 + blocksuite/affine/all/tsconfig.json | 1 + .../fragments/adapter-panel/package.json | 39 +++ .../adapter-panel/src/adapter-panel.ts | 177 +++++++++++ .../src/body/adapter-panel-body.ts | 216 +++++++++++++ .../fragments/adapter-panel/src/config.ts | 28 ++ .../fragments/adapter-panel/src/effects.ts | 17 ++ .../adapter-panel/src/header/adapter-menu.ts | 86 ++++++ .../src/header/adapter-panel-header.ts | 119 ++++++++ .../fragments/adapter-panel/src/index.ts | 4 + .../fragments/adapter-panel/src/view.ts | 12 + .../fragments/adapter-panel/tsconfig.json | 18 ++ .../apps/_common/components/adapters-panel.ts | 285 ------------------ .../components/custom-adapter-panel.ts | 61 ++++ .../_common/components/starter-debug-menu.ts | 33 +- .../playground/apps/starter/utils/app.ts | 20 ++ .../workspace/detail-page/detail-page.tsx | 18 ++ .../workspace/detail-page/tabs/adapter.css.ts | 6 + .../workspace/detail-page/tabs/adapter.tsx | 74 +++++ .../core/src/modules/feature-flag/constant.ts | 9 + packages/frontend/i18n/src/i18n.gen.ts | 8 + packages/frontend/i18n/src/resources/en.json | 2 + tools/utils/src/workspace.gen.ts | 15 + tsconfig.json | 1 + yarn.lock | 22 ++ 28 files changed, 970 insertions(+), 308 deletions(-) create mode 100644 blocksuite/affine/all/src/fragments/adapter-panel/index.ts create mode 100644 blocksuite/affine/all/src/fragments/adapter-panel/view.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/package.json create mode 100644 blocksuite/affine/fragments/adapter-panel/src/adapter-panel.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/src/body/adapter-panel-body.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/src/config.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/src/effects.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/src/header/adapter-menu.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/src/header/adapter-panel-header.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/src/index.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/src/view.ts create mode 100644 blocksuite/affine/fragments/adapter-panel/tsconfig.json delete mode 100644 blocksuite/playground/apps/_common/components/adapters-panel.ts create mode 100644 blocksuite/playground/apps/_common/components/custom-adapter-panel.ts create mode 100644 packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.css.ts create mode 100644 packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.tsx diff --git a/blocksuite/affine/all/package.json b/blocksuite/affine/all/package.json index 7bf117b177..a86e395371 100644 --- a/blocksuite/affine/all/package.json +++ b/blocksuite/affine/all/package.json @@ -33,6 +33,7 @@ "@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-ext-loader": "workspace:*", "@blocksuite/affine-foundation": "workspace:*", + "@blocksuite/affine-fragment-adapter-panel": "workspace:*", "@blocksuite/affine-fragment-doc-title": "workspace:*", "@blocksuite/affine-fragment-frame-panel": "workspace:*", "@blocksuite/affine-fragment-outline": "workspace:*", @@ -209,6 +210,8 @@ "./fragments/frame-panel/view": "./src/fragments/frame-panel/view.ts", "./fragments/outline": "./src/fragments/outline/index.ts", "./fragments/outline/view": "./src/fragments/outline/view.ts", + "./fragments/adapter-panel": "./src/fragments/adapter-panel/index.ts", + "./fragments/adapter-panel/view": "./src/fragments/adapter-panel/view.ts", "./gfx/text": "./src/gfx/text/index.ts", "./gfx/text/store": "./src/gfx/text/store.ts", "./gfx/text/view": "./src/gfx/text/view.ts", diff --git a/blocksuite/affine/all/src/extensions/view.ts b/blocksuite/affine/all/src/extensions/view.ts index e20814f00a..79c8647ffb 100644 --- a/blocksuite/affine/all/src/extensions/view.ts +++ b/blocksuite/affine/all/src/extensions/view.ts @@ -19,6 +19,7 @@ import { SurfaceViewExtension } from '@blocksuite/affine-block-surface/view'; import { SurfaceRefViewExtension } from '@blocksuite/affine-block-surface-ref/view'; import { TableViewExtension } from '@blocksuite/affine-block-table/view'; import { FoundationViewExtension } from '@blocksuite/affine-foundation/view'; +import { AdapterPanelViewExtension } from '@blocksuite/affine-fragment-adapter-panel/view'; import { DocTitleViewExtension } from '@blocksuite/affine-fragment-doc-title/view'; import { FramePanelViewExtension } from '@blocksuite/affine-fragment-frame-panel/view'; import { OutlineViewExtension } from '@blocksuite/affine-fragment-outline/view'; @@ -124,5 +125,6 @@ export function getInternalViewExtensions() { DocTitleViewExtension, FramePanelViewExtension, OutlineViewExtension, + AdapterPanelViewExtension, ]; } diff --git a/blocksuite/affine/all/src/fragments/adapter-panel/index.ts b/blocksuite/affine/all/src/fragments/adapter-panel/index.ts new file mode 100644 index 0000000000..950692acc9 --- /dev/null +++ b/blocksuite/affine/all/src/fragments/adapter-panel/index.ts @@ -0,0 +1 @@ +export * from '@blocksuite/affine-fragment-adapter-panel'; diff --git a/blocksuite/affine/all/src/fragments/adapter-panel/view.ts b/blocksuite/affine/all/src/fragments/adapter-panel/view.ts new file mode 100644 index 0000000000..df401534c4 --- /dev/null +++ b/blocksuite/affine/all/src/fragments/adapter-panel/view.ts @@ -0,0 +1 @@ +export * from '@blocksuite/affine-fragment-adapter-panel/view'; diff --git a/blocksuite/affine/all/tsconfig.json b/blocksuite/affine/all/tsconfig.json index b371b44411..8cb7c4be91 100644 --- a/blocksuite/affine/all/tsconfig.json +++ b/blocksuite/affine/all/tsconfig.json @@ -30,6 +30,7 @@ { "path": "../components" }, { "path": "../ext-loader" }, { "path": "../foundation" }, + { "path": "../fragments/adapter-panel" }, { "path": "../fragments/doc-title" }, { "path": "../fragments/frame-panel" }, { "path": "../fragments/outline" }, diff --git a/blocksuite/affine/fragments/adapter-panel/package.json b/blocksuite/affine/fragments/adapter-panel/package.json new file mode 100644 index 0000000000..99509246f5 --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/package.json @@ -0,0 +1,39 @@ +{ + "name": "@blocksuite/affine-fragment-adapter-panel", + "description": "Adapter panel fragment for BlockSuite.", + "type": "module", + "scripts": { + "build": "tsc" + }, + "sideEffects": false, + "keywords": [], + "author": "toeverything", + "license": "MIT", + "dependencies": { + "@blocksuite/affine-components": "workspace:*", + "@blocksuite/affine-ext-loader": "workspace:*", + "@blocksuite/affine-model": "workspace:*", + "@blocksuite/affine-shared": "workspace:*", + "@blocksuite/global": "workspace:*", + "@blocksuite/icons": "^2.2.12", + "@blocksuite/std": "workspace:*", + "@blocksuite/store": "workspace:*", + "@floating-ui/dom": "^1.6.13", + "@lit/context": "^1.1.2", + "@preact/signals-core": "^1.8.0", + "@toeverything/theme": "^1.1.14", + "lit": "^3.2.0", + "rxjs": "^7.8.1" + }, + "exports": { + ".": "./src/index.ts", + "./view": "./src/view.ts" + }, + "files": [ + "src", + "dist", + "!src/__tests__", + "!dist/__tests__" + ], + "version": "0.21.0" +} diff --git a/blocksuite/affine/fragments/adapter-panel/src/adapter-panel.ts b/blocksuite/affine/fragments/adapter-panel/src/adapter-panel.ts new file mode 100644 index 0000000000..d29236c825 --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/adapter-panel.ts @@ -0,0 +1,177 @@ +import type { Store, TransformerMiddleware } from '@blocksuite/affine/store'; +import { + type HtmlAdapter, + HtmlAdapterFactoryIdentifier, + type MarkdownAdapter, + MarkdownAdapterFactoryIdentifier, + type PlainTextAdapter, + PlainTextAdapterFactoryIdentifier, +} from '@blocksuite/affine-shared/adapters'; +import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; +import { provide } from '@lit/context'; +import { effect, signal } from '@preact/signals-core'; +import { baseTheme } from '@toeverything/theme'; +import { css, html, LitElement, type PropertyValues, unsafeCSS } from 'lit'; +import { property } from 'lit/decorators.js'; + +import { + type AdapterPanelContext, + adapterPanelContext, + ADAPTERS, +} from './config'; + +export const AFFINE_ADAPTER_PANEL = 'affine-adapter-panel'; + +export class AdapterPanel extends SignalWatcher(WithDisposable(LitElement)) { + static override styles = css` + :host { + display: block; + width: 100%; + height: 100%; + box-sizing: border-box; + } + + .adapters-container { + width: 100%; + height: 100%; + background-color: var(--affine-background-primary-color); + box-sizing: border-box; + font-family: ${unsafeCSS(baseTheme.fontSansFamily)}; + } + `; + + get activeAdapter() { + return this._context.activeAdapter$.value; + } + + private _createJob() { + return this.store.getTransformer(this.transformerMiddlewares); + } + + private _getDocSnapshot() { + const job = this._createJob(); + const result = job.docToSnapshot(this.store); + return result; + } + + private async _getHtmlContent() { + try { + const job = this._createJob(); + const htmlAdapterFactory = this.store.get(HtmlAdapterFactoryIdentifier); + const htmlAdapter = htmlAdapterFactory.get(job) as HtmlAdapter; + const result = await htmlAdapter.fromDoc(this.store); + return result?.file; + } catch (error) { + console.error('Failed to get html content', error); + return ''; + } + } + + private async _getMarkdownContent() { + try { + const job = this._createJob(); + const markdownAdapterFactory = this.store.get( + MarkdownAdapterFactoryIdentifier + ); + const markdownAdapter = markdownAdapterFactory.get( + job + ) as MarkdownAdapter; + const result = await markdownAdapter.fromDoc(this.store); + return result?.file; + } catch (error) { + console.error('Failed to get markdown content', error); + return ''; + } + } + + private async _getPlainTextContent() { + try { + const job = this._createJob(); + const plainTextAdapterFactory = this.store.get( + PlainTextAdapterFactoryIdentifier + ); + const plainTextAdapter = plainTextAdapterFactory.get( + job + ) as PlainTextAdapter; + const result = await plainTextAdapter.fromDoc(this.store); + return result?.file; + } catch (error) { + console.error('Failed to get plain text content', error); + return ''; + } + } + + private readonly _updateActiveContent = async () => { + const activeId = this.activeAdapter.id; + switch (activeId) { + case 'markdown': + this._context.markdownContent$.value = + (await this._getMarkdownContent()) || ''; + break; + case 'html': + this._context.htmlContent$.value = (await this._getHtmlContent()) || ''; + break; + case 'plaintext': + this._context.plainTextContent$.value = + (await this._getPlainTextContent()) || ''; + break; + case 'snapshot': + this._context.docSnapshot$.value = this._getDocSnapshot() || null; + break; + } + }; + + override connectedCallback() { + super.connectedCallback(); + this._context = { + activeAdapter$: signal(ADAPTERS[0]), + isHtmlPreview$: signal(false), + docSnapshot$: signal(null), + htmlContent$: signal(''), + markdownContent$: signal(''), + plainTextContent$: signal(''), + }; + } + + override willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has('store')) { + this._updateActiveContent().catch(console.error); + } + } + + override firstUpdated() { + this.disposables.add( + effect(() => { + if (this.activeAdapter) { + this._updateActiveContent().catch(console.error); + } + }) + ); + } + + override render() { + return html` +
+ + +
+ `; + } + + @property({ attribute: false }) + accessor store!: Store; + + @property({ attribute: false }) + accessor transformerMiddlewares: TransformerMiddleware[] = []; + + @provide({ context: adapterPanelContext }) + private accessor _context!: AdapterPanelContext; +} + +declare global { + interface HTMLElementTagNameMap { + [AFFINE_ADAPTER_PANEL]: AdapterPanel; + } +} diff --git a/blocksuite/affine/fragments/adapter-panel/src/body/adapter-panel-body.ts b/blocksuite/affine/fragments/adapter-panel/src/body/adapter-panel-body.ts new file mode 100644 index 0000000000..3d1302d6ff --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/body/adapter-panel-body.ts @@ -0,0 +1,216 @@ +import { scrollbarStyle } from '@blocksuite/affine-shared/styles'; +import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; +import { SignalWatcher } from '@blocksuite/global/lit'; +import { consume } from '@lit/context'; +import { css, html, LitElement } from 'lit'; +import { classMap } from 'lit/directives/class-map.js'; + +import { + type AdapterItem, + type AdapterPanelContext, + adapterPanelContext, + ADAPTERS, +} from '../config'; + +export const AFFINE_ADAPTER_PANEL_BODY = 'affine-adapter-panel-body'; + +export class AdapterPanelBody extends SignalWatcher(LitElement) { + static override styles = css` + .adapter-panel-body { + width: 100%; + height: calc(100% - 50px); + box-sizing: border-box; + overflow: auto; + padding: 8px 16px; + } + + ${scrollbarStyle('.adapter-panel-body')} + + .adapter-content { + width: 100%; + height: 100%; + white-space: pre-wrap; + color: var(--affine-text-primary-color); + font-size: var(--affine-font-sm); + box-sizing: border-box; + } + + .html-content { + display: flex; + gap: 8px; + flex-direction: column; + justify-content: space-between; + } + + .html-preview-container, + .html-panel-content { + width: 100%; + flex: 1 0 0; + border: none; + box-sizing: border-box; + color: var(--affine-text-primary-color); + overflow: auto; + } + + ${scrollbarStyle('.html-panel-content')} + + .html-panel-footer { + width: 100%; + height: 24px; + display: flex; + } + + .html-toggle-container { + display: flex; + background: ${unsafeCSSVarV2('segment/background')}; + justify-content: flex-start; + padding: 2px; + border-radius: 4px; + } + + .html-toggle-item { + cursor: pointer; + display: flex; + padding: 0px 4px; + justify-content: center; + align-items: center; + font-size: 12px; + font-weight: 500; + line-height: 20px; + border-radius: 4px; + color: ${unsafeCSSVarV2('text/primary')}; + } + + .html-toggle-item:hover { + background: ${unsafeCSSVarV2('layer/background/hoverOverlay')}; + } + + .html-toggle-item[active] { + background: ${unsafeCSSVarV2('segment/button')}; + box-shadow: + var(--Shadow-buttonShadow-1-x, 0px) var(--Shadow-buttonShadow-1-y, 0px) + var(--Shadow-buttonShadow-1-blur, 1px) 0px + var(--Shadow-buttonShadow-1-color, rgba(0, 0, 0, 0.12)), + var(--Shadow-buttonShadow-2-x, 0px) var(--Shadow-buttonShadow-2-y, 1px) + var(--Shadow-buttonShadow-2-blur, 5px) 0px + var(--Shadow-buttonShadow-2-color, rgba(0, 0, 0, 0.12)); + } + + .adapter-container { + display: none; + width: 100%; + height: 100%; + box-sizing: border-box; + } + + .adapter-container.active { + display: block; + } + `; + + get activeAdapter() { + return this._context.activeAdapter$.value; + } + + get isHtmlPreview() { + return this._context.isHtmlPreview$.value; + } + + get htmlContent() { + return this._context.htmlContent$.value; + } + + get markdownContent() { + return this._context.markdownContent$.value; + } + + get plainTextContent() { + return this._context.plainTextContent$.value; + } + + get docSnapshot() { + return this._context.docSnapshot$.value; + } + + private _renderHtmlPanel() { + return html` + ${this.isHtmlPreview + ? html`` + : html`
${this.htmlContent}
`} + + `; + } + + private readonly _renderAdapterContent = (adapter: AdapterItem) => { + switch (adapter.id) { + case 'html': + return this._renderHtmlPanel(); + case 'markdown': + return this.markdownContent; + case 'plaintext': + return this.plainTextContent; + case 'snapshot': + return this.docSnapshot + ? JSON.stringify(this.docSnapshot, null, 4) + : ''; + default: + return ''; + } + }; + + private readonly _renderAdapterContainer = (adapter: AdapterItem) => { + const containerClasses = classMap({ + 'adapter-container': true, + active: this.activeAdapter.id === adapter.id, + }); + + const contentClasses = classMap({ + 'adapter-content': true, + [`${adapter.id}-content`]: true, + }); + + const content = this._renderAdapterContent(adapter); + + return html` +
+
${content}
+
+ `; + }; + + override render() { + return html` +
+ ${ADAPTERS.map(adapter => this._renderAdapterContainer(adapter))} +
+ `; + } + + @consume({ context: adapterPanelContext }) + private accessor _context!: AdapterPanelContext; +} + +declare global { + interface HTMLElementTagNameMap { + [AFFINE_ADAPTER_PANEL_BODY]: AdapterPanelBody; + } +} diff --git a/blocksuite/affine/fragments/adapter-panel/src/config.ts b/blocksuite/affine/fragments/adapter-panel/src/config.ts new file mode 100644 index 0000000000..3a9276224a --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/config.ts @@ -0,0 +1,28 @@ +import type { DocSnapshot } from '@blocksuite/store'; +import { createContext } from '@lit/context'; +import type { Signal } from '@preact/signals-core'; + +export type AdapterItem = { + id: string; + label: string; +}; + +export const ADAPTERS: AdapterItem[] = [ + { id: 'markdown', label: 'Markdown' }, + { id: 'plaintext', label: 'PlainText' }, + { id: 'html', label: 'HTML' }, + { id: 'snapshot', label: 'Snapshot' }, +]; + +export type AdapterPanelContext = { + activeAdapter$: Signal; + isHtmlPreview$: Signal; + docSnapshot$: Signal; + htmlContent$: Signal; + markdownContent$: Signal; + plainTextContent$: Signal; +}; + +export const adapterPanelContext = createContext( + 'adapterPanelContext' +); diff --git a/blocksuite/affine/fragments/adapter-panel/src/effects.ts b/blocksuite/affine/fragments/adapter-panel/src/effects.ts new file mode 100644 index 0000000000..f4d1c69d2f --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/effects.ts @@ -0,0 +1,17 @@ +import { AdapterPanel, AFFINE_ADAPTER_PANEL } from './adapter-panel'; +import { + AdapterPanelBody, + AFFINE_ADAPTER_PANEL_BODY, +} from './body/adapter-panel-body'; +import { AdapterMenu, AFFINE_ADAPTER_MENU } from './header/adapter-menu'; +import { + AdapterPanelHeader, + AFFINE_ADAPTER_PANEL_HEADER, +} from './header/adapter-panel-header'; + +export function effects() { + customElements.define(AFFINE_ADAPTER_PANEL, AdapterPanel); + customElements.define(AFFINE_ADAPTER_MENU, AdapterMenu); + customElements.define(AFFINE_ADAPTER_PANEL_HEADER, AdapterPanelHeader); + customElements.define(AFFINE_ADAPTER_PANEL_BODY, AdapterPanelBody); +} diff --git a/blocksuite/affine/fragments/adapter-panel/src/header/adapter-menu.ts b/blocksuite/affine/fragments/adapter-panel/src/header/adapter-menu.ts new file mode 100644 index 0000000000..a5cd392c86 --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/header/adapter-menu.ts @@ -0,0 +1,86 @@ +import { SignalWatcher } from '@blocksuite/global/lit'; +import { consume } from '@lit/context'; +import { css, html, LitElement } from 'lit'; +import { property } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { + type AdapterItem, + type AdapterPanelContext, + adapterPanelContext, + ADAPTERS, +} from '../config'; + +export const AFFINE_ADAPTER_MENU = 'affine-adapter-menu'; + +export class AdapterMenu extends SignalWatcher(LitElement) { + static override styles = css` + .adapter-menu { + min-width: 120px; + padding: 4px; + background: var(--affine-background-primary-color); + border: 1px solid var(--affine-border-color); + border-radius: 4px; + box-shadow: var(--affine-shadow-1); + } + .adapter-menu-item { + display: block; + width: 100%; + padding: 6px 8px; + border: none; + background: none; + text-align: left; + cursor: pointer; + color: var(--affine-text-primary-color); + font-family: var(--affine-font-family); + font-size: var(--affine-font-xs); + border-radius: 4px; + } + .adapter-menu-item:hover { + background: var(--affine-hover-color); + } + .adapter-menu-item.active { + color: var(--affine-primary-color); + background: var(--affine-hover-color); + } + `; + + get activeAdapter() { + return this._context.activeAdapter$.value; + } + + private readonly _handleAdapterChange = async (adapter: AdapterItem) => { + this._context.activeAdapter$.value = adapter; + this.abortController?.abort(); + }; + + override render() { + return html`
+ ${ADAPTERS.map(adapter => { + const classes = classMap({ + 'adapter-menu-item': true, + active: this.activeAdapter.id === adapter.id, + }); + return html` + + `; + })} +
`; + } + + @property({ attribute: false }) + accessor abortController: AbortController | null = null; + + @consume({ context: adapterPanelContext }) + private accessor _context!: AdapterPanelContext; +} +declare global { + interface HTMLElementTagNameMap { + [AFFINE_ADAPTER_MENU]: AdapterMenu; + } +} diff --git a/blocksuite/affine/fragments/adapter-panel/src/header/adapter-panel-header.ts b/blocksuite/affine/fragments/adapter-panel/src/header/adapter-panel-header.ts new file mode 100644 index 0000000000..4af036df0a --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/header/adapter-panel-header.ts @@ -0,0 +1,119 @@ +import { createLitPortal } from '@blocksuite/affine-components/portal'; +import { SignalWatcher } from '@blocksuite/global/lit'; +import { ArrowDownSmallIcon, FlipDirectionIcon } from '@blocksuite/icons/lit'; +import { flip, offset } from '@floating-ui/dom'; +import { consume } from '@lit/context'; +import { css, html, LitElement } from 'lit'; +import { property, query } from 'lit/decorators.js'; + +import { type AdapterPanelContext, adapterPanelContext } from '../config'; + +export const AFFINE_ADAPTER_PANEL_HEADER = 'affine-adapter-panel-header'; + +export class AdapterPanelHeader extends SignalWatcher(LitElement) { + static override styles = css` + .adapter-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: var(--affine-background-primary-color); + } + .adapter-selector { + display: flex; + align-items: center; + justify-content: space-between; + width: 100px; + cursor: pointer; + border-radius: 4px; + border: 1px solid var(--affine-border-color); + padding: 4px 8px; + } + .adapter-selector:hover { + background: var(--affine-hover-color); + } + .adapter-selector-label { + display: flex; + align-items: center; + color: var(--affine-text-primary-color); + font-size: var(--affine-font-xs); + } + .update-button { + height: 20px; + width: 20px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + cursor: pointer; + color: var(--affine-icon-color); + } + .update-button:hover { + background-color: var(--affine-hover-color); + } + `; + + get activeAdapter() { + return this._context.activeAdapter$.value; + } + + private _adapterMenuAbortController: AbortController | null = null; + private readonly _toggleAdapterMenu = () => { + if (this._adapterMenuAbortController) { + this._adapterMenuAbortController.abort(); + } + this._adapterMenuAbortController = new AbortController(); + + createLitPortal({ + template: html``, + portalStyles: { + zIndex: 'var(--affine-z-index-popover)', + }, + container: this._adapterPanelHeader, + computePosition: { + referenceElement: this._adapterSelector, + placement: 'bottom-start', + middleware: [flip(), offset(4)], + autoUpdate: { animationFrame: true }, + }, + abortController: this._adapterMenuAbortController, + closeOnClickAway: true, + }); + }; + + override render() { + return html` +
+
+ + ${this.activeAdapter.label} + + ${ArrowDownSmallIcon({ width: '16px', height: '16px' })} +
+
+ ${FlipDirectionIcon({ width: '16px', height: '16px' })} +
+
+ `; + } + + @query('.adapter-panel-header') + private accessor _adapterPanelHeader!: HTMLDivElement; + + @query('.adapter-selector') + private accessor _adapterSelector!: HTMLDivElement; + + @property({ attribute: false }) + accessor updateActiveContent: () => void = () => {}; + + @consume({ context: adapterPanelContext }) + private accessor _context!: AdapterPanelContext; +} + +declare global { + interface HTMLElementTagNameMap { + [AFFINE_ADAPTER_PANEL_HEADER]: AdapterPanelHeader; + } +} diff --git a/blocksuite/affine/fragments/adapter-panel/src/index.ts b/blocksuite/affine/fragments/adapter-panel/src/index.ts new file mode 100644 index 0000000000..2ed6f2dc7f --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/index.ts @@ -0,0 +1,4 @@ +export * from './adapter-panel.js'; +export * from './body/adapter-panel-body.js'; +export * from './header/adapter-menu.js'; +export * from './header/adapter-panel-header.js'; diff --git a/blocksuite/affine/fragments/adapter-panel/src/view.ts b/blocksuite/affine/fragments/adapter-panel/src/view.ts new file mode 100644 index 0000000000..00339c2780 --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/src/view.ts @@ -0,0 +1,12 @@ +import { ViewExtensionProvider } from '@blocksuite/affine-ext-loader'; + +import { effects } from './effects'; + +export class AdapterPanelViewExtension extends ViewExtensionProvider { + override name = 'affine-adapter-panel-fragment'; + + override effect() { + super.effect(); + effects(); + } +} diff --git a/blocksuite/affine/fragments/adapter-panel/tsconfig.json b/blocksuite/affine/fragments/adapter-panel/tsconfig.json new file mode 100644 index 0000000000..d60ba97d5e --- /dev/null +++ b/blocksuite/affine/fragments/adapter-panel/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" + }, + "include": ["./src"], + "references": [ + { "path": "../../components" }, + { "path": "../../ext-loader" }, + { "path": "../../model" }, + { "path": "../../shared" }, + { "path": "../../../framework/global" }, + { "path": "../../../framework/std" }, + { "path": "../../../framework/store" } + ] +} diff --git a/blocksuite/playground/apps/_common/components/adapters-panel.ts b/blocksuite/playground/apps/_common/components/adapters-panel.ts deleted file mode 100644 index 5990b51bd0..0000000000 --- a/blocksuite/playground/apps/_common/components/adapters-panel.ts +++ /dev/null @@ -1,285 +0,0 @@ -/* eslint-disable @typescript-eslint/no-restricted-imports */ -import '@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js'; - -import { WithDisposable } from '@blocksuite/affine/global/lit'; -import { - defaultImageProxyMiddleware, - docLinkBaseURLMiddlewareBuilder, - embedSyncedDocMiddleware, - type HtmlAdapter, - HtmlAdapterFactoryIdentifier, - type MarkdownAdapter, - MarkdownAdapterFactoryIdentifier, - type PlainTextAdapter, - PlainTextAdapterFactoryIdentifier, - titleMiddleware, -} from '@blocksuite/affine/shared/adapters'; -import { ShadowlessElement } from '@blocksuite/affine/std'; -import type { DocSnapshot } from '@blocksuite/affine/store'; -import type { TestAffineEditorContainer } from '@blocksuite/integration-test'; -import { effect } from '@preact/signals-core'; -import type SlTabPanel from '@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js'; -import { css, html, type PropertyValues } from 'lit'; -import { customElement, property, query, state } from 'lit/decorators.js'; - -@customElement('adapters-panel') -export class AdaptersPanel extends WithDisposable(ShadowlessElement) { - static override styles = css` - adapters-panel { - width: 36vw; - } - .adapters-container { - border: 1px solid var(--affine-border-color, #e3e2e4); - background-color: var(--affine-background-primary-color); - box-sizing: border-box; - position: relative; - } - .adapter-container { - padding: 0px 16px; - width: 100%; - height: calc(100vh - 80px); - white-space: pre-wrap; - color: var(--affine-text-primary-color); - overflow: auto; - } - .update-button { - position: absolute; - top: 8px; - right: 12px; - padding: 8px 12px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - cursor: pointer; - border: 1px solid var(--affine-border-color); - font-family: var(--affine-font-family); - color: var(--affine-text-primary-color); - background-color: var(--affine-background-primary-color); - } - .update-button:hover { - background-color: var(--affine-hover-color); - } - .html-panel { - display: flex; - gap: 8px; - flex-direction: column; - } - .html-preview-container, - .html-panel-content { - width: 100%; - flex: 1; - border: none; - box-sizing: border-box; - color: var(--affine-text-primary-color); - overflow: auto; - } - .html-panel-footer { - width: 100%; - height: 32px; - display: flex; - justify-content: flex-end; - - span { - cursor: pointer; - padding: 4px 8px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--affine-border-color); - font-family: var(--affine-font-family); - color: var(--affine-text-primary-color); - background-color: var(--affine-background-primary-color); - line-height: 20px; - } - span[active] { - background-color: var(--affine-hover-color); - } - } - `; - - get doc() { - return this.editor.doc; - } - - private _createJob() { - return this.doc.getTransformer([ - docLinkBaseURLMiddlewareBuilder( - 'https://example.com', - this.doc.workspace.id - ).get(), - titleMiddleware(this.doc.workspace.meta.docMetas), - embedSyncedDocMiddleware('content'), - defaultImageProxyMiddleware, - ]); - } - - private _getDocSnapshot() { - const job = this._createJob(); - const result = job.docToSnapshot(this.doc); - return result; - } - - private async _getHtmlContent() { - const job = this._createJob(); - const htmlAdapterFactory = this.editor.std.provider.get( - HtmlAdapterFactoryIdentifier - ); - const htmlAdapter = htmlAdapterFactory.get(job) as HtmlAdapter; - const result = await htmlAdapter.fromDoc(this.doc); - return result?.file; - } - - private async _getMarkdownContent() { - const job = this._createJob(); - const markdownAdapterFactory = this.editor.std.provider.get( - MarkdownAdapterFactoryIdentifier - ); - const markdownAdapter = markdownAdapterFactory.get(job) as MarkdownAdapter; - const result = await markdownAdapter.fromDoc(this.doc); - return result?.file; - } - - private async _getPlainTextContent() { - const job = this._createJob(); - const plainTextAdapterFactory = this.editor.std.provider.get( - PlainTextAdapterFactoryIdentifier - ); - const plainTextAdapter = plainTextAdapterFactory.get( - job - ) as PlainTextAdapter; - const result = await plainTextAdapter.fromDoc(this.doc); - return result?.file; - } - - private async _handleTabShow(name: string) { - switch (name) { - case 'markdown': - this._markdownContent = (await this._getMarkdownContent()) || ''; - break; - case 'html': - this._htmlContent = (await this._getHtmlContent()) || ''; - break; - case 'plaintext': - this._plainTextContent = (await this._getPlainTextContent()) || ''; - break; - case 'snapshot': - this._docSnapshot = this._getDocSnapshot() || null; - break; - } - } - - private _renderHtmlPanel() { - return html` - ${this._isHtmlPreview - ? html`` - : html`
${this._htmlContent}
`} - - `; - } - - private async _updateActiveTabContent() { - if (!this._activeTab) return; - const activeTabName = this._activeTab.name; - await this._handleTabShow(activeTabName); - } - - override firstUpdated() { - this.disposables.add( - effect(() => { - const doc = this.doc; - if (doc) { - this._updateActiveTabContent().catch(console.error); - } - }) - ); - } - - override render() { - const snapshotString = this._docSnapshot - ? JSON.stringify(this._docSnapshot, null, 4) - : ''; - return html` -
- this._handleTabShow(e.detail.name)} - > - Markdown - PlainText - HTML - Snapshot - - -
${this._markdownContent}
-
- -
- ${this._renderHtmlPanel()} -
-
- -
${this._plainTextContent}
-
- -
${snapshotString}
-
-
- -
- Update -
-
-
- `; - } - - override willUpdate(_changedProperties: PropertyValues) { - if (_changedProperties.has('editor')) { - requestIdleCallback(() => { - this._updateActiveTabContent().catch(console.error); - }); - } - } - - @query('sl-tab-panel[active]') - private accessor _activeTab!: SlTabPanel; - - @state() - private accessor _docSnapshot: DocSnapshot | null = null; - - @state() - private accessor _htmlContent = ''; - - @state() - private accessor _isHtmlPreview = false; - - @state() - private accessor _markdownContent = ''; - - @state() - private accessor _plainTextContent = ''; - - @property({ attribute: false }) - accessor editor!: TestAffineEditorContainer; -} - -declare global { - interface HTMLElementTagNameMap { - 'adapters-panel': AdaptersPanel; - } -} diff --git a/blocksuite/playground/apps/_common/components/custom-adapter-panel.ts b/blocksuite/playground/apps/_common/components/custom-adapter-panel.ts new file mode 100644 index 0000000000..13d800b325 --- /dev/null +++ b/blocksuite/playground/apps/_common/components/custom-adapter-panel.ts @@ -0,0 +1,61 @@ +import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; +import { ShadowlessElement } from '@blocksuite/affine/std'; +import type { TransformerMiddleware } from '@blocksuite/affine/store'; +import type { TestAffineEditorContainer } from '@blocksuite/integration-test'; +import { css, html, nothing } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; + +@customElement('custom-adapter-panel') +export class CustomAdapterPanel extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { + static override styles = css` + .custom-adapter-container { + position: absolute; + top: 0; + right: 16px; + border: 1px solid var(--affine-border-color, #e3e2e4); + background: var(--affine-background-overlay-panel-color); + height: 100vh; + width: 30vw; + box-sizing: border-box; + z-index: 1; + } + `; + + private _renderPanel() { + return html``; + } + + override render() { + return html` + ${this._show + ? html` +
${this._renderPanel()}
+ ` + : nothing} + `; + } + + toggleDisplay() { + this._show = !this._show; + } + + @state() + private accessor _show = false; + + @property({ attribute: false }) + accessor editor!: TestAffineEditorContainer; + + @property({ attribute: false }) + accessor transformerMiddlewares: TransformerMiddleware[] = []; +} + +declare global { + interface HTMLElementTagNameMap { + 'custom-adapter-panel': CustomAdapterPanel; + } +} diff --git a/blocksuite/playground/apps/_common/components/starter-debug-menu.ts b/blocksuite/playground/apps/_common/components/starter-debug-menu.ts index 7294890324..b3c6e9d245 100644 --- a/blocksuite/playground/apps/_common/components/starter-debug-menu.ts +++ b/blocksuite/playground/apps/_common/components/starter-debug-menu.ts @@ -70,7 +70,7 @@ import type { Pane } from 'tweakpane'; import type { CommentPanel } from '../../comment/index.js'; import { createTestEditor } from '../../starter/utils/extensions.js'; import { mockEdgelessTheme } from '../mock-services.js'; -import { AdaptersPanel } from './adapters-panel.js'; +import type { CustomAdapterPanel } from './custom-adapter-panel.js'; import type { CustomFramePanel } from './custom-frame-panel.js'; import type { CustomOutlinePanel } from './custom-outline-panel.js'; import type { CustomOutlineViewer } from './custom-outline-viewer.js'; @@ -612,26 +612,6 @@ export class StarterDebugMenu extends ShadowlessElement { this._hasOffset = !this._hasOffset; } - private _toggleAdaptersPanel() { - const app = document.querySelector('#app'); - if (!app) return; - - const currentAdaptersPanel = app.querySelector('adapters-panel'); - if (currentAdaptersPanel) { - currentAdaptersPanel.remove(); - (app as HTMLElement).style.display = 'block'; - this.editor.style.width = '100%'; - this.editor.style.flex = ''; - return; - } - - const adaptersPanel = new AdaptersPanel(); - adaptersPanel.editor = this.editor; - app.append(adaptersPanel); - this.editor.style.flex = '1'; - (app as HTMLElement).style.display = 'flex'; - } - private _toggleCommentPanel() { document.body.append(this.commentPanel); } @@ -649,6 +629,10 @@ export class StarterDebugMenu extends ShadowlessElement { this.framePanel.toggleDisplay(); } + private _toggleAdapterPanel() { + this.adapterPanel.toggleDisplay(); + } + private _toggleMultipleEditors() { const app = document.querySelector('#app'); if (app) { @@ -926,8 +910,8 @@ export class StarterDebugMenu extends ShadowlessElement { Toggle Multiple Editors - - Toggle Adapters Panel + + Toggle Adapter Panel @@ -1032,6 +1016,9 @@ export class StarterDebugMenu extends ShadowlessElement { @property({ attribute: false }) accessor framePanel!: CustomFramePanel; + @property({ attribute: false }) + accessor adapterPanel!: CustomAdapterPanel; + @property({ attribute: false }) accessor leftSidePanel!: LeftSidePanel; diff --git a/blocksuite/playground/apps/starter/utils/app.ts b/blocksuite/playground/apps/starter/utils/app.ts index 43b119b9c0..d65ff045c4 100644 --- a/blocksuite/playground/apps/starter/utils/app.ts +++ b/blocksuite/playground/apps/starter/utils/app.ts @@ -1,6 +1,13 @@ import type { Store, Workspace } from '@blocksuite/affine/store'; +import { + defaultImageProxyMiddleware, + docLinkBaseURLMiddlewareBuilder, + embedSyncedDocMiddleware, + titleMiddleware, +} from '@blocksuite/affine-shared/adapters'; import { AttachmentViewerPanel } from '../../_common/components/attachment-viewer-panel'; +import { CustomAdapterPanel } from '../../_common/components/custom-adapter-panel'; import { CustomFramePanel } from '../../_common/components/custom-frame-panel'; import { CustomOutlinePanel } from '../../_common/components/custom-outline-panel'; import { CustomOutlineViewer } from '../../_common/components/custom-outline-viewer'; @@ -24,6 +31,7 @@ export async function createTestApp(doc: Store, collection: Workspace) { const docsPanel = new DocsPanel(); const framePanel = new CustomFramePanel(); const outlinePanel = new CustomOutlinePanel(); + const adapterPanel = new CustomAdapterPanel(); const outlineViewer = new CustomOutlineViewer(); const leftSidePanel = new LeftSidePanel(); const commentPanel = new CommentPanel(); @@ -36,6 +44,16 @@ export async function createTestApp(doc: Store, collection: Workspace) { outlineViewer.toggleOutlinePanel = () => { outlinePanel.toggleDisplay(); }; + adapterPanel.editor = editor; + adapterPanel.transformerMiddlewares = [ + docLinkBaseURLMiddlewareBuilder( + 'https://example.com', + editor.doc.workspace.id + ).get(), + titleMiddleware(editor.doc.workspace.meta.docMetas), + embedSyncedDocMiddleware('content'), + defaultImageProxyMiddleware, + ]; debugMenu.collection = collection; debugMenu.editor = editor; @@ -44,6 +62,7 @@ export async function createTestApp(doc: Store, collection: Workspace) { debugMenu.framePanel = framePanel; debugMenu.leftSidePanel = leftSidePanel; debugMenu.docsPanel = docsPanel; + debugMenu.adapterPanel = adapterPanel; debugMenu.commentPanel = commentPanel; @@ -55,6 +74,7 @@ export async function createTestApp(doc: Store, collection: Workspace) { document.body.append(framePanel); document.body.append(leftSidePanel); document.body.append(debugMenu); + document.body.append(adapterPanel); window.editor = editor; window.doc = doc; diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx index 82fc1cf8a6..c77ed040a8 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx @@ -18,6 +18,7 @@ import { TrashPageFooter } from '@affine/core/components/pure/trash-page-footer' import { TopTip } from '@affine/core/components/top-tip'; import { DocService } from '@affine/core/modules/doc'; import { EditorService } from '@affine/core/modules/editor'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { GlobalContextService } from '@affine/core/modules/global-context'; import { PeekViewService } from '@affine/core/modules/peek-view'; import { RecentDocsService } from '@affine/core/modules/quicksearch'; @@ -36,6 +37,7 @@ import { DisposableGroup } from '@blocksuite/affine/global/disposable'; import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference'; import { AiIcon, + ExportIcon, FrameIcon, PropertyIcon, TocIcon, @@ -57,6 +59,7 @@ import { PageNotFound } from '../../404'; import * as styles from './detail-page.css'; import { DetailPageHeader } from './detail-page-header'; import { DetailPageWrapper } from './detail-page-wrapper'; +import { EditorAdapterPanel } from './tabs/adapter'; import { EditorChatPanel } from './tabs/chat'; import { EditorFramePanel } from './tabs/frame'; import { EditorJournalPanel } from './tabs/journal'; @@ -103,6 +106,11 @@ const DetailPageImpl = memo(function DetailPageImpl() { const enableAI = useEnableAI(); + const featureFlagService = useService(FeatureFlagService); + const enableAdapterPanel = useLiveData( + featureFlagService.flags.enable_adapter_panel.$ + ); + useEffect(() => { if (isActiveView) { setActiveBlockSuiteEditor(editorContainer); @@ -360,6 +368,16 @@ const DetailPageImpl = memo(function DetailPageImpl() { + {enableAdapterPanel && ( + }> + + + + + + + )} + {/* FIXME: wait for better ai, */} diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.css.ts b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.css.ts new file mode 100644 index 0000000000..8dff59d450 --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; +export const root = style({ + display: 'flex', + height: '100%', + width: '100%', +}); diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.tsx new file mode 100644 index 0000000000..f6f91cd5d5 --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/adapter.tsx @@ -0,0 +1,74 @@ +import { ServerService } from '@affine/core/modules/cloud'; +import { AdapterPanel } from '@blocksuite/affine/fragments/adapter-panel'; +import { + customImageProxyMiddleware, + docLinkBaseURLMiddlewareBuilder, + embedSyncedDocMiddleware, + titleMiddleware, +} from '@blocksuite/affine/shared/adapters'; +import type { EditorHost } from '@blocksuite/affine/std'; +import type { TransformerMiddleware } from '@blocksuite/affine/store'; +import { useService } from '@toeverything/infra'; +import { useCallback, useEffect, useRef } from 'react'; + +import * as styles from './adapter.css'; + +const createImageProxyUrl = (baseUrl: string) => { + try { + return new URL(BUILD_CONFIG.imageProxyUrl, baseUrl).toString(); + } catch (error) { + console.error('Failed to create image proxy url', error); + return ''; + } +}; + +const createMiddlewares = ( + host: EditorHost, + baseUrl: string +): TransformerMiddleware[] => { + const imageProxyUrl = createImageProxyUrl(baseUrl); + + return [ + docLinkBaseURLMiddlewareBuilder(baseUrl, host.store.workspace.id).get(), + titleMiddleware(host.store.workspace.meta.docMetas), + embedSyncedDocMiddleware('content'), + customImageProxyMiddleware(imageProxyUrl), + ]; +}; + +const getTransformerMiddlewares = ( + host: EditorHost | null, + baseUrl: string +) => { + if (!host) return []; + return createMiddlewares(host, baseUrl); +}; + +// A wrapper for AdapterPanel +export const EditorAdapterPanel = ({ host }: { host: EditorHost | null }) => { + const server = useService(ServerService).server; + const adapterPanelRef = useRef(null); + + const onRefChange = useCallback( + (container: HTMLDivElement | null) => { + if (container && host && container.children.length === 0) { + adapterPanelRef.current = new AdapterPanel(); + adapterPanelRef.current.store = host.store; + adapterPanelRef.current.transformerMiddlewares = + getTransformerMiddlewares(host, server.baseUrl); + container.append(adapterPanelRef.current); + } + }, + [host, server] + ); + + useEffect(() => { + if (host && adapterPanelRef.current) { + adapterPanelRef.current.store = host.store; + adapterPanelRef.current.transformerMiddlewares = + getTransformerMiddlewares(host, server.baseUrl); + } + }, [host, server]); + + return
; +}; diff --git a/packages/frontend/core/src/modules/feature-flag/constant.ts b/packages/frontend/core/src/modules/feature-flag/constant.ts index 1ad9c2e587..f7ba1c721d 100644 --- a/packages/frontend/core/src/modules/feature-flag/constant.ts +++ b/packages/frontend/core/src/modules/feature-flag/constant.ts @@ -327,6 +327,15 @@ export const AFFINE_FLAGS = { configurable: isCanaryBuild, defaultState: true, }, + enable_adapter_panel: { + category: 'affine', + displayName: + 'com.affine.settings.workspace.experimental-features.enable-adapter-panel.name', + description: + 'com.affine.settings.workspace.experimental-features.enable-adapter-panel.description', + configurable: isCanaryBuild, + defaultState: false, + }, } satisfies { [key in string]: FlagInfo }; // oxlint-disable-next-line no-redeclare diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index 646d79d501..f98c674675 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -5890,6 +5890,14 @@ export function useAFFiNEI18N(): { * `Once enabled, you can preview HTML in code block.` */ ["com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.description"](): string; + /** + * `Adapter Panel` + */ + ["com.affine.settings.workspace.experimental-features.enable-adapter-panel.name"](): string; + /** + * `Once enabled, you can preview adapter export content in the right side bar.` + */ + ["com.affine.settings.workspace.experimental-features.enable-adapter-panel.description"](): string; /** * `Only an owner can edit the workspace avatar and name. Changes will be shown for everyone.` */ diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index e2e83719b5..fb7a1a5d20 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1471,6 +1471,8 @@ "com.affine.settings.workspace.experimental-features.enable-table-virtual-scroll.description": "Once enabled, switch table view to virtual scroll mode in Database Block.", "com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.name": "Code block HTML preview", "com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.description": "Once enabled, you can preview HTML in code block.", + "com.affine.settings.workspace.experimental-features.enable-adapter-panel.name": "Adapter Panel", + "com.affine.settings.workspace.experimental-features.enable-adapter-panel.description": "Once enabled, you can preview adapter export content in the right side bar.", "com.affine.settings.workspace.not-owner": "Only an owner can edit the workspace avatar and name. Changes will be shown for everyone.", "com.affine.settings.workspace.preferences": "Preference", "com.affine.settings.workspace.billing": "Team's Billing", diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index 7d1bb94732..2b6e77de0d 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -28,6 +28,7 @@ export const PackageList = [ 'blocksuite/affine/components', 'blocksuite/affine/ext-loader', 'blocksuite/affine/foundation', + 'blocksuite/affine/fragments/adapter-panel', 'blocksuite/affine/fragments/doc-title', 'blocksuite/affine/fragments/frame-panel', 'blocksuite/affine/fragments/outline', @@ -475,6 +476,19 @@ export const PackageList = [ 'blocksuite/framework/store', ], }, + { + location: 'blocksuite/affine/fragments/adapter-panel', + name: '@blocksuite/affine-fragment-adapter-panel', + workspaceDependencies: [ + 'blocksuite/affine/components', + 'blocksuite/affine/ext-loader', + 'blocksuite/affine/model', + 'blocksuite/affine/shared', + 'blocksuite/framework/global', + 'blocksuite/framework/std', + 'blocksuite/framework/store', + ], + }, { location: 'blocksuite/affine/fragments/doc-title', name: '@blocksuite/affine-fragment-doc-title', @@ -1475,6 +1489,7 @@ export type PackageName = | '@blocksuite/data-view' | '@blocksuite/affine-ext-loader' | '@blocksuite/affine-foundation' + | '@blocksuite/affine-fragment-adapter-panel' | '@blocksuite/affine-fragment-doc-title' | '@blocksuite/affine-fragment-frame-panel' | '@blocksuite/affine-fragment-outline' diff --git a/tsconfig.json b/tsconfig.json index a6d05272e5..084476102b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -75,6 +75,7 @@ { "path": "./blocksuite/affine/data-view" }, { "path": "./blocksuite/affine/ext-loader" }, { "path": "./blocksuite/affine/foundation" }, + { "path": "./blocksuite/affine/fragments/adapter-panel" }, { "path": "./blocksuite/affine/fragments/doc-title" }, { "path": "./blocksuite/affine/fragments/frame-panel" }, { "path": "./blocksuite/affine/fragments/outline" }, diff --git a/yarn.lock b/yarn.lock index 550cc163ea..a0ecd4e387 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3067,6 +3067,27 @@ __metadata: languageName: unknown linkType: soft +"@blocksuite/affine-fragment-adapter-panel@workspace:*, @blocksuite/affine-fragment-adapter-panel@workspace:blocksuite/affine/fragments/adapter-panel": + version: 0.0.0-use.local + resolution: "@blocksuite/affine-fragment-adapter-panel@workspace:blocksuite/affine/fragments/adapter-panel" + dependencies: + "@blocksuite/affine-components": "workspace:*" + "@blocksuite/affine-ext-loader": "workspace:*" + "@blocksuite/affine-model": "workspace:*" + "@blocksuite/affine-shared": "workspace:*" + "@blocksuite/global": "workspace:*" + "@blocksuite/icons": "npm:^2.2.12" + "@blocksuite/std": "workspace:*" + "@blocksuite/store": "workspace:*" + "@floating-ui/dom": "npm:^1.6.13" + "@lit/context": "npm:^1.1.2" + "@preact/signals-core": "npm:^1.8.0" + "@toeverything/theme": "npm:^1.1.14" + lit: "npm:^3.2.0" + rxjs: "npm:^7.8.1" + languageName: unknown + linkType: soft + "@blocksuite/affine-fragment-doc-title@workspace:*, @blocksuite/affine-fragment-doc-title@workspace:blocksuite/affine/fragments/doc-title": version: 0.0.0-use.local resolution: "@blocksuite/affine-fragment-doc-title@workspace:blocksuite/affine/fragments/doc-title" @@ -4151,6 +4172,7 @@ __metadata: "@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-ext-loader": "workspace:*" "@blocksuite/affine-foundation": "workspace:*" + "@blocksuite/affine-fragment-adapter-panel": "workspace:*" "@blocksuite/affine-fragment-doc-title": "workspace:*" "@blocksuite/affine-fragment-frame-panel": "workspace:*" "@blocksuite/affine-fragment-outline": "workspace:*"