mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 18:09:58 +08:00
fix(editor): footnote popup style and position issues (#11771)
Close [BS-3049](https://linear.app/affine-design/issue/BS-3049/chat引用的样式坏了) [BS-3024](https://linear.app/affine-design/issue/BS-3024/footnote-在容器边缘时,hover-抽搐)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const outlineBlockPreview = style({
|
export const outlineBlockPreview = style({
|
||||||
fontFamily: cssVar('fontFamily'),
|
fontFamily: cssVar('fontFamily'),
|
||||||
@@ -108,6 +108,11 @@ export const linkedDocPreviewUnavailable = style({
|
|||||||
color: cssVarV2('text/disable'),
|
color: cssVarV2('text/disable'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const linkedDocPreviewAvailable = style({});
|
||||||
|
globalStyle(`${linkedDocPreviewAvailable} > svg`, {
|
||||||
|
marginBottom: '0.1em',
|
||||||
|
});
|
||||||
|
|
||||||
export const linkedDocTextUnavailable = style({
|
export const linkedDocTextUnavailable = style({
|
||||||
color: cssVarV2('text/disable'),
|
color: cssVarV2('text/disable'),
|
||||||
textDecoration: 'line-through',
|
textDecoration: 'line-through',
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
return html`<span
|
return html`<span
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
[styles.linkedDocPreviewUnavailable]: unavailable,
|
[styles.linkedDocPreviewUnavailable]: unavailable,
|
||||||
|
[styles.linkedDocPreviewAvailable]: !unavailable,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${icon}
|
${icon}
|
||||||
|
|||||||
@@ -29,13 +29,23 @@ export const markdownFootnoteReferenceToDeltaMatcher =
|
|||||||
const footnoteReference = FootNoteReferenceParamsSchema.parse(
|
const footnoteReference = FootNoteReferenceParamsSchema.parse(
|
||||||
footnoteDefinitionJson
|
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 = {
|
const footnote = {
|
||||||
label: ast.identifier,
|
label: ast.identifier,
|
||||||
reference: footnoteReference,
|
reference: footnoteReference,
|
||||||
};
|
};
|
||||||
return [{ insert: ' ', attributes: { footnote } }];
|
return [{ insert: ' ', attributes: { footnote } }];
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.warn('Error parsing footnote reference', error);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
ZERO_WIDTH_FOR_EMPTY_LINE,
|
ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||||
} from '@blocksuite/std/inline';
|
} from '@blocksuite/std/inline';
|
||||||
import type { DeltaInsert } from '@blocksuite/store';
|
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 { baseTheme } from '@toeverything/theme';
|
||||||
import { css, html, nothing, unsafeCSS } from 'lit';
|
import { css, html, nothing, unsafeCSS } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
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.
|
// Virtual padding for the footnote popup overflow detection offsets.
|
||||||
const POPUP_SHIFT_PADDING = 8;
|
const POPUP_SHIFT_PADDING = 8;
|
||||||
|
// The offset between the footnote node and the popup.
|
||||||
|
const POPUP_OFFSET = 4;
|
||||||
|
|
||||||
export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
@@ -203,12 +205,16 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
template: this._FootNotePopup(footnote, abortController),
|
template: this._FootNotePopup(footnote, abortController),
|
||||||
container: this,
|
container: this.std.host,
|
||||||
computePosition: {
|
computePosition: {
|
||||||
referenceElement: this,
|
referenceElement: this,
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
middleware: [shift({ padding: POPUP_SHIFT_PADDING })],
|
middleware: [
|
||||||
|
shift({ padding: POPUP_SHIFT_PADDING }),
|
||||||
|
flip(),
|
||||||
|
offset(POPUP_OFFSET),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,48 +9,42 @@ export class FootNotePopupChip extends LitElement {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
max-width: 173px;
|
max-width: 173px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 2px 4px;
|
padding: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
transition: width 0.3s ease-in-out;
|
transition: width 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefix-icon,
|
.prefix-icon {
|
||||||
.suffix-icon {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
color: ${unsafeCSSVarV2('icon/primary')};
|
color: ${unsafeCSSVarV2('icon/primary')};
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
svg,
|
svg,
|
||||||
object {
|
img {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
fill: ${unsafeCSSVarV2('icon/primary')};
|
fill: ${unsafeCSSVarV2('icon/primary')};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.suffix-icon:hover {
|
|
||||||
background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-chip-label {
|
.popup-chip-label {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
height: 20px;
|
height: 22px;
|
||||||
line-height: 20px;
|
line-height: 22px;
|
||||||
color: ${unsafeCSSVarV2('text/primary')};
|
color: ${unsafeCSSVarV2('text/primary')};
|
||||||
font-size: 12px;
|
font-size: var(--affine-font-sm);
|
||||||
font-weight: 400;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -63,11 +57,6 @@ export class FootNotePopupChip extends LitElement {
|
|||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<div class="popup-chip-label" title=${this.tooltip}>${this.label}</div>
|
<div class="popup-chip-label" title=${this.tooltip}>${this.label}</div>
|
||||||
${this.suffixIcon
|
|
||||||
? html`<div class="suffix-icon" @click=${this.onSuffixClick}>
|
|
||||||
${this.suffixIcon}
|
|
||||||
</div>`
|
|
||||||
: nothing}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -78,9 +67,6 @@ export class FootNotePopupChip extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor label: string = '';
|
accessor label: string = '';
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor suffixIcon: TemplateResult | undefined = undefined;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor tooltip: string = '';
|
accessor tooltip: string = '';
|
||||||
|
|
||||||
@@ -89,7 +75,4 @@ export class FootNotePopupChip extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor onPrefixClick: (() => void) | undefined = undefined;
|
accessor onPrefixClick: (() => void) | undefined = undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor onSuffixClick: (() => void) | undefined = undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ import {
|
|||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
import { DualLinkIcon, LinkIcon } from '@blocksuite/icons/lit';
|
|
||||||
import type { BlockStdScope } from '@blocksuite/std';
|
import type { BlockStdScope } from '@blocksuite/std';
|
||||||
import { computed, signal } from '@preact/signals-core';
|
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 { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
import type { FootNotePopupClickHandler } from './footnote-config';
|
import type { FootNotePopupClickHandler } from './footnote-config';
|
||||||
@@ -56,36 +55,11 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const favicon = this._linkPreview$.value?.favicon;
|
const favicon = this._linkPreview$.value?.favicon;
|
||||||
if (!favicon) {
|
return favicon ? html`<img src=${favicon} alt="favicon" />` : WebIcon16;
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const titleIconType =
|
|
||||||
favicon.split('.').pop() === 'svg'
|
|
||||||
? 'svg+xml'
|
|
||||||
: favicon.split('.').pop();
|
|
||||||
const titleIcon = html`<object
|
|
||||||
type="image/${titleIconType}"
|
|
||||||
data=${favicon}
|
|
||||||
draggable="false"
|
|
||||||
>
|
|
||||||
${WebIcon16}
|
|
||||||
</object>`;
|
|
||||||
return titleIcon;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
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(() => {
|
private readonly _popupLabel$ = computed(() => {
|
||||||
const referenceType = this.footnote.reference.type;
|
const referenceType = this.footnote.reference.type;
|
||||||
let label = '';
|
let label = '';
|
||||||
@@ -157,7 +131,6 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
<footnote-popup-chip
|
<footnote-popup-chip
|
||||||
.prefixIcon=${this._prefixIcon$.value}
|
.prefixIcon=${this._prefixIcon$.value}
|
||||||
.label=${this._popupLabel$.value}
|
.label=${this._popupLabel$.value}
|
||||||
.suffixIcon=${this._suffixIcon()}
|
|
||||||
.onClick=${this._onChipClick}
|
.onClick=${this._onChipClick}
|
||||||
.tooltip=${this._tooltip$.value}
|
.tooltip=${this._tooltip$.value}
|
||||||
></footnote-popup-chip>
|
></footnote-popup-chip>
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: 1px 2px 1px 0;
|
padding: 1px 2px 1px 0;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.affine-reference:hover {
|
.affine-reference:hover {
|
||||||
background: var(--affine-hover-color);
|
background: var(--affine-hover-color);
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ export class DocDisplayMetaService
|
|||||||
function iconBuilder(
|
function iconBuilder(
|
||||||
icon: typeof PageIcon,
|
icon: typeof PageIcon,
|
||||||
size = '1.25em',
|
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 });
|
return icon({ width: size, height: size, style });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user