mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 04:26:23 +08:00
a828c74f87
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. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
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`<affine-adapter-menu
|
|
.abortController=${this._adapterMenuAbortController}
|
|
></affine-adapter-menu>`,
|
|
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`
|
|
<div class="adapter-panel-header">
|
|
<div class="adapter-selector" @click="${this._toggleAdapterMenu}">
|
|
<span class="adapter-selector-label">
|
|
${this.activeAdapter.label}
|
|
</span>
|
|
${ArrowDownSmallIcon({ width: '16px', height: '16px' })}
|
|
</div>
|
|
<div class="update-button" @click="${this.updateActiveContent}">
|
|
${FlipDirectionIcon({ width: '16px', height: '16px' })}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
@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;
|
|
}
|
|
}
|