fix(core): use patched preview spec builder in ai chat (#10090)

[BS-2526](https://linear.app/affine-design/issue/BS-2526/chat-panel-里的-footnote-popup-需要支持交互)
This commit is contained in:
donteatfriedrice
2025-02-11 15:11:54 +00:00
parent 54d194afe7
commit 19f0eb1931
11 changed files with 227 additions and 27 deletions
@@ -15,11 +15,17 @@ type FootNotePopupRenderer = (
abortController: AbortController
) => TemplateResult<1>;
export type FootNotePopupClickHandler = (
footnote: FootNote,
abortController: AbortController
) => void;
export interface FootNoteNodeConfig {
customNodeRenderer?: FootNoteNodeRenderer;
customPopupRenderer?: FootNotePopupRenderer;
interactive?: boolean;
hidePopup?: boolean;
onPopupClick?: FootNotePopupClickHandler;
}
export class FootNoteNodeConfigProvider {
@@ -27,7 +33,7 @@ export class FootNoteNodeConfigProvider {
private _customPopupRenderer?: FootNotePopupRenderer;
private _hidePopup: boolean;
private _interactive: boolean;
private _onPopupClick?: FootNotePopupClickHandler;
get customNodeRenderer() {
return this._customNodeRenderer;
}
@@ -36,6 +42,10 @@ export class FootNoteNodeConfigProvider {
return this._customPopupRenderer;
}
get onPopupClick() {
return this._onPopupClick;
}
get doc() {
return this.std.store;
}
@@ -56,6 +66,7 @@ export class FootNoteNodeConfigProvider {
this._customPopupRenderer = config.customPopupRenderer;
this._hidePopup = config.hidePopup ?? false;
this._interactive = config.interactive ?? true;
this._onPopupClick = config.onPopupClick;
}
setCustomNodeRenderer(renderer: FootNoteNodeRenderer) {
@@ -73,6 +84,10 @@ export class FootNoteNodeConfigProvider {
setInteractive(interactive: boolean) {
this._interactive = interactive;
}
setPopupClick(onPopupClick: FootNotePopupClickHandler) {
this._onPopupClick = onPopupClick;
}
}
export const FootNoteNodeConfigIdentifier =
@@ -24,6 +24,9 @@ import { ref } from 'lit-html/directives/ref.js';
import { HoverController } from '../../../../../hover/controller';
import type { FootNoteNodeConfigProvider } from './footnote-config';
// Virtual padding for the footnote popup overflow detection offsets.
const POPUP_SHIFT_PADDING = 8;
export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
static override styles = css`
.footnote-node {
@@ -64,6 +67,10 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
return this.config?.hidePopup;
}
get onPopupClick() {
return this.config?.onPopupClick;
}
get inlineEditor() {
const inlineRoot = this.closest<InlineRootElement<AffineTextAttributes>>(
`[${INLINE_ROOT_ATTR}]`
@@ -92,6 +99,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
.footnote=${footnote}
.std=${this.std}
.abortController=${abortController}
.onPopupClick=${this.onPopupClick}
></footnote-popup>`;
};
@@ -130,7 +138,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
referenceElement: this,
placement: 'top',
autoUpdate: true,
middleware: [shift()],
middleware: [shift({ padding: POPUP_SHIFT_PADDING })],
},
};
},
@@ -18,7 +18,8 @@ import {
LightLoadingIcon,
WebIcon16,
} from '../../../../../icons';
import { RefNodeSlotsProvider } from '../../../../extension/ref-node-slots';
import { PeekViewProvider } from '../../../../../peek/service';
import type { FootNotePopupClickHandler } from './footnote-config';
export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
static override styles = css`
@@ -130,29 +131,49 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
/**
* When clicking the chip, we will navigate to the reference doc or open the url
*/
private readonly _onChipClick = () => {
const referenceType = this.footnote.reference.type;
const { docId, url } = this.footnote.reference;
switch (referenceType) {
case 'doc': {
if (!docId) {
break;
private readonly _handleDocReference = (docId: string) => {
this.std
.getOptional(PeekViewProvider)
?.peek({
docId,
})
.catch(console.error);
};
private readonly _handleUrlReference = (url: string) => {
window.open(url, '_blank');
};
private readonly _handleReference = () => {
const { type, docId, url } = this.footnote.reference;
switch (type) {
case 'doc':
if (docId) {
this._handleDocReference(docId);
}
this.std
.getOptional(RefNodeSlotsProvider)
?.docLinkClicked.emit({ pageId: docId, host: this.std.host });
break;
}
case 'url':
if (!url) {
break;
if (url) {
this._handleUrlReference(url);
}
window.open(url, '_blank');
break;
}
this.abortController.abort();
};
private readonly _onChipClick = () => {
// If the onPopupClick is defined, use it
if (this.onPopupClick) {
this.onPopupClick(this.footnote, this.abortController);
return;
}
// Otherwise, handle the reference by default
this._handleReference();
};
override connectedCallback() {
super.connectedCallback();
if (this.footnote.reference.type === 'url' && this.footnote.reference.url) {
@@ -195,4 +216,7 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
@property({ attribute: false })
accessor abortController!: AbortController;
@property({ attribute: false })
accessor onPopupClick: FootNotePopupClickHandler | undefined = undefined;
}