From a555df020040dc48bc1decc025a8544bb3f7d2ca Mon Sep 17 00:00:00 2001 From: donteatfriedrice Date: Fri, 18 Apr 2025 02:44:21 +0000 Subject: [PATCH] fix(editor): footnote popup style and position issues (#11771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close [BS-3049](https://linear.app/affine-design/issue/BS-3049/chat引用的样式坏了) [BS-3024](https://linear.app/affine-design/issue/BS-3024/footnote-在容器边缘时,hover-抽搐) --- .../outline/src/card/outline-preview.css.ts | 7 +++- .../outline/src/card/outline-preview.ts | 1 + .../src/adapters/markdown/markdown-inline.ts | 14 ++++++-- .../src/footnote-node/footnote-node.ts | 12 +++++-- .../src/footnote-node/footnote-popup-chip.ts | 35 +++++-------------- .../src/footnote-node/footnote-popup.ts | 31 ++-------------- .../src/reference-node/reference-node.ts | 4 +++ .../src/services/doc-display-meta-service.ts | 2 +- 8 files changed, 44 insertions(+), 62 deletions(-) diff --git a/blocksuite/affine/fragments/outline/src/card/outline-preview.css.ts b/blocksuite/affine/fragments/outline/src/card/outline-preview.css.ts index a1ffcdb44..1fcf10809 100644 --- a/blocksuite/affine/fragments/outline/src/card/outline-preview.css.ts +++ b/blocksuite/affine/fragments/outline/src/card/outline-preview.css.ts @@ -1,6 +1,6 @@ import { cssVar } from '@toeverything/theme'; import { cssVarV2 } from '@toeverything/theme/v2'; -import { style } from '@vanilla-extract/css'; +import { globalStyle, style } from '@vanilla-extract/css'; export const outlineBlockPreview = style({ fontFamily: cssVar('fontFamily'), @@ -108,6 +108,11 @@ export const linkedDocPreviewUnavailable = style({ color: cssVarV2('text/disable'), }); +export const linkedDocPreviewAvailable = style({}); +globalStyle(`${linkedDocPreviewAvailable} > svg`, { + marginBottom: '0.1em', +}); + export const linkedDocTextUnavailable = style({ color: cssVarV2('text/disable'), textDecoration: 'line-through', diff --git a/blocksuite/affine/fragments/outline/src/card/outline-preview.ts b/blocksuite/affine/fragments/outline/src/card/outline-preview.ts index 8c781ec25..51ba71e81 100644 --- a/blocksuite/affine/fragments/outline/src/card/outline-preview.ts +++ b/blocksuite/affine/fragments/outline/src/card/outline-preview.ts @@ -67,6 +67,7 @@ export class OutlineBlockPreview extends SignalWatcher( return html` ${icon} diff --git a/blocksuite/affine/inlines/footnote/src/adapters/markdown/markdown-inline.ts b/blocksuite/affine/inlines/footnote/src/adapters/markdown/markdown-inline.ts index c1d3da472..8dd841029 100644 --- a/blocksuite/affine/inlines/footnote/src/adapters/markdown/markdown-inline.ts +++ b/blocksuite/affine/inlines/footnote/src/adapters/markdown/markdown-inline.ts @@ -29,13 +29,23 @@ export const markdownFootnoteReferenceToDeltaMatcher = const footnoteReference = FootNoteReferenceParamsSchema.parse( footnoteDefinitionJson ); + + // If the footnote reference is an attachment, and the file type is not set, + // Try to infer the file type from the file name. + const { type: referenceType, fileName, fileType } = footnoteReference; + if (referenceType === 'attachment' && fileName && !fileType) { + const ext = fileName.split('.').pop()?.toLowerCase(); + if (ext) { + footnoteReference.fileType = ext; + } + } + const footnote = { label: ast.identifier, reference: footnoteReference, }; return [{ insert: ' ', attributes: { footnote } }]; - } catch (error) { - console.warn('Error parsing footnote reference', error); + } catch { return []; } }, diff --git a/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-node.ts b/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-node.ts index 5e1b02fe3..aa1ae13a0 100644 --- a/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-node.ts +++ b/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-node.ts @@ -17,7 +17,7 @@ import { ZERO_WIDTH_FOR_EMPTY_LINE, } from '@blocksuite/std/inline'; import type { DeltaInsert } from '@blocksuite/store'; -import { shift } from '@floating-ui/dom'; +import { flip, offset, shift } from '@floating-ui/dom'; import { baseTheme } from '@toeverything/theme'; import { css, html, nothing, unsafeCSS } from 'lit'; import { property } from 'lit/decorators.js'; @@ -28,6 +28,8 @@ import type { FootNoteNodeConfigProvider } from './footnote-config'; // Virtual padding for the footnote popup overflow detection offsets. const POPUP_SHIFT_PADDING = 8; +// The offset between the footnote node and the popup. +const POPUP_OFFSET = 4; export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) { static override styles = css` @@ -203,12 +205,16 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) { return { template: this._FootNotePopup(footnote, abortController), - container: this, + container: this.std.host, computePosition: { referenceElement: this, placement: 'top', autoUpdate: true, - middleware: [shift({ padding: POPUP_SHIFT_PADDING })], + middleware: [ + shift({ padding: POPUP_SHIFT_PADDING }), + flip(), + offset(POPUP_OFFSET), + ], }, }; }, diff --git a/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup-chip.ts b/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup-chip.ts index 3f7e5031a..fddb5e195 100644 --- a/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup-chip.ts +++ b/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup-chip.ts @@ -9,48 +9,42 @@ export class FootNotePopupChip extends LitElement { border-radius: 4px; max-width: 173px; height: 24px; - padding: 2px 4px; + padding: 4px; align-items: center; - gap: 4px; + gap: 8px; box-sizing: border-box; cursor: default; transition: width 0.3s ease-in-out; } - .prefix-icon, - .suffix-icon { + .prefix-icon { display: flex; align-items: center; justify-content: center; - width: 16px; height: 16px; + width: 16px; color: ${unsafeCSSVarV2('icon/primary')}; border-radius: 4px; svg, - object { + img { width: 16px; height: 16px; fill: ${unsafeCSSVarV2('icon/primary')}; } } - .suffix-icon:hover { - background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')}; - cursor: pointer; - } - .popup-chip-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; - height: 20px; - line-height: 20px; + height: 22px; + line-height: 22px; color: ${unsafeCSSVarV2('text/primary')}; - font-size: 12px; - font-weight: 400; + font-size: var(--affine-font-sm); + font-weight: 500; } `; @@ -63,11 +57,6 @@ export class FootNotePopupChip extends LitElement { ` : nothing} - ${this.suffixIcon - ? html`
- ${this.suffixIcon} -
` - : nothing} `; } @@ -78,9 +67,6 @@ export class FootNotePopupChip extends LitElement { @property({ attribute: false }) accessor label: string = ''; - @property({ attribute: false }) - accessor suffixIcon: TemplateResult | undefined = undefined; - @property({ attribute: false }) accessor tooltip: string = ''; @@ -89,7 +75,4 @@ export class FootNotePopupChip extends LitElement { @property({ attribute: false }) accessor onPrefixClick: (() => void) | undefined = undefined; - - @property({ attribute: false }) - accessor onSuffixClick: (() => void) | undefined = undefined; } diff --git a/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup.ts b/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup.ts index 072b6e93c..6bd3d4a99 100644 --- a/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup.ts +++ b/blocksuite/affine/inlines/footnote/src/footnote-node/footnote-popup.ts @@ -12,10 +12,9 @@ import { } from '@blocksuite/affine-shared/services'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; -import { DualLinkIcon, LinkIcon } from '@blocksuite/icons/lit'; import type { BlockStdScope } from '@blocksuite/std'; import { computed, signal } from '@preact/signals-core'; -import { css, html, LitElement, type TemplateResult } from 'lit'; +import { css, html, LitElement } from 'lit'; import { property } from 'lit/decorators.js'; import type { FootNotePopupClickHandler } from './footnote-config'; @@ -56,36 +55,11 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) { } const favicon = this._linkPreview$.value?.favicon; - if (!favicon) { - return undefined; - } - - const titleIconType = - favicon.split('.').pop() === 'svg' - ? 'svg+xml' - : favicon.split('.').pop(); - const titleIcon = html` - ${WebIcon16} - `; - return titleIcon; + return favicon ? html`favicon` : WebIcon16; } return undefined; }); - private readonly _suffixIcon = (): TemplateResult | undefined => { - const referenceType = this.footnote.reference.type; - if (referenceType === 'doc') { - return DualLinkIcon({ width: '16px', height: '16px' }); - } else if (referenceType === 'url') { - return LinkIcon({ width: '16px', height: '16px' }); - } - return undefined; - }; - private readonly _popupLabel$ = computed(() => { const referenceType = this.footnote.reference.type; let label = ''; @@ -157,7 +131,6 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) { diff --git a/blocksuite/affine/inlines/reference/src/reference-node/reference-node.ts b/blocksuite/affine/inlines/reference/src/reference-node/reference-node.ts index 619ddd6d1..009d008da 100644 --- a/blocksuite/affine/inlines/reference/src/reference-node/reference-node.ts +++ b/blocksuite/affine/inlines/reference/src/reference-node/reference-node.ts @@ -49,6 +49,10 @@ export class AffineReference extends WithDisposable(ShadowlessElement) { cursor: pointer; user-select: none; padding: 1px 2px 1px 0; + + svg { + margin-bottom: 0.1em; + } } .affine-reference:hover { background: var(--affine-hover-color); diff --git a/blocksuite/affine/shared/src/services/doc-display-meta-service.ts b/blocksuite/affine/shared/src/services/doc-display-meta-service.ts index e57862977..c38ee0fda 100644 --- a/blocksuite/affine/shared/src/services/doc-display-meta-service.ts +++ b/blocksuite/affine/shared/src/services/doc-display-meta-service.ts @@ -217,7 +217,7 @@ export class DocDisplayMetaService function iconBuilder( icon: typeof PageIcon, size = '1.25em', - style = 'user-select:none;flex-shrink:0;vertical-align:middle;font-size:inherit;margin-bottom:0.1em;' + style = 'user-select:none;flex-shrink:0;vertical-align:middle;font-size:inherit;' ) { return icon({ width: size, height: size, style }); }