refactor(editor): edgeless bookmark toolbar config extension (#10711)

This commit is contained in:
fundon
2025-03-19 02:24:26 +00:00
parent 70fe521300
commit d8567e669a
17 changed files with 537 additions and 101 deletions

View File

@@ -3,11 +3,7 @@ import {
type ToolbarAction,
ToolbarContext,
} from '@blocksuite/affine-shared/services';
import {
PropTypes,
requiredProperties,
ShadowlessElement,
} from '@blocksuite/block-std';
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
import { SignalWatcher } from '@blocksuite/global/lit';
import { PaletteIcon } from '@blocksuite/icons/lit';
import {
@@ -15,6 +11,7 @@ import {
type ReadonlySignal,
type Signal,
} from '@preact/signals-core';
import { LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { html, type TemplateResult } from 'lit-html';
import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -24,23 +21,29 @@ import {
EmbedCardDarkCubeIcon,
EmbedCardDarkHorizontalIcon,
EmbedCardDarkListIcon,
EmbedCardDarkVerticalIcon,
EmbedCardLightCubeIcon,
EmbedCardLightHorizontalIcon,
EmbedCardLightListIcon,
EmbedCardLightVerticalIcon,
} from '../icons';
const cardStyleMap: Record<ColorScheme, Record<string, TemplateResult>> = {
light: {
cube: EmbedCardLightCubeIcon,
cubeThick: EmbedCardLightCubeIcon,
horizontal: EmbedCardLightHorizontalIcon,
horizontalThin: EmbedCardLightListIcon,
list: EmbedCardLightListIcon,
cubeThick: EmbedCardLightCubeIcon,
vertical: EmbedCardLightVerticalIcon,
},
dark: {
cube: EmbedCardDarkCubeIcon,
cubeThick: EmbedCardDarkCubeIcon,
horizontal: EmbedCardDarkHorizontalIcon,
horizontalThin: EmbedCardDarkListIcon,
list: EmbedCardDarkListIcon,
cubeThick: EmbedCardDarkCubeIcon,
vertical: EmbedCardDarkVerticalIcon,
},
};
@@ -49,7 +52,7 @@ const cardStyleMap: Record<ColorScheme, Record<string, TemplateResult>> = {
context: PropTypes.instanceOf(ToolbarContext),
style$: PropTypes.object,
})
export class CardStyleDropdownMenu extends SignalWatcher(ShadowlessElement) {
export class CardStyleDropdownMenu extends SignalWatcher(LitElement) {
@property({ attribute: false })
accessor actions!: ToolbarAction[];

View File

@@ -0,0 +1,183 @@
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
import { SignalWatcher } from '@blocksuite/global/lit';
import { ArrowDownSmallIcon, DoneIcon } from '@blocksuite/icons/lit';
import type { ReadonlySignal, Signal } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { property, query } from 'lit/decorators.js';
import { repeat } from 'lit-html/directives/repeat.js';
import { when } from 'lit-html/directives/when.js';
import clamp from 'lodash-es/clamp';
import type { EditorMenuButton } from '../toolbar';
type SizeItem = { key?: string | number; value: number };
const MIN_SIZE = 0;
const MAX_SIZE = 400;
const SIZE_LIST: SizeItem[] = [
{ value: 50 },
{ value: 100 },
{ value: 200 },
] as const;
@requiredProperties({
size$: PropTypes.object,
})
export class SizeDropdownMenu extends SignalWatcher(LitElement) {
static override styles = css`
div[data-orientation] {
width: 68px;
gap: 4px;
min-width: unset;
overflow: unset;
}
editor-menu-action {
justify-content: space-between;
}
:host([data-type='check']) editor-menu-action[data-selected] {
color: var(--affine-primary-color);
background-color: none;
}
input {
display: flex;
align-self: stretch;
border: 0.5px solid var(--affine-border-color);
border-radius: 8px;
padding: 4px 8px;
box-sizing: border-box;
}
input:focus {
outline-color: var(--affine-primary-color);
outline-width: 0.5px;
}
input::placeholder {
color: var(--affine-placeholder-color);
}
`;
@property({ attribute: false })
accessor sizes: readonly SizeItem[] = SIZE_LIST;
@property({ attribute: false })
accessor size$!: Signal<number> | ReadonlySignal<number>;
@property({ attribute: false })
accessor maxSize: number = MAX_SIZE;
@property({ attribute: false })
accessor minSize: number = MIN_SIZE;
@property({ attribute: false })
accessor format: ((e: number) => string) | undefined;
@property({ attribute: 'data-type' })
accessor type: 'normal' | 'check' = 'normal';
clamp(value: number, min = this.minSize, max = this.maxSize) {
return clamp(value, min, max);
}
select(value: number) {
const detail = this.clamp(value);
this.dispatchEvent(new CustomEvent('select', { detail }));
}
private readonly _onKeydown = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.isComposing) return;
if (e.key !== 'Enter') return;
e.preventDefault();
const input = e.target as HTMLInputElement;
const value = parseInt(input.value.trim());
// Handle edge case where user enters a non-number
if (isNaN(value)) {
input.value = '';
return;
}
// Handle edge case when user enters a number that is out of range
this.select(value);
input.value = '';
this.menuButton.hide();
};
@query('editor-menu-button')
accessor menuButton!: EditorMenuButton;
override render() {
const {
sizes,
format,
type,
size$: { value: size },
} = this;
const isCheckType = type === 'check';
const placeholder = format?.(Math.trunc(size)) ?? Math.trunc(size);
return html`
<editor-menu-button
.contentPadding="${'8px'}"
.button=${html`
<editor-icon-button
aria-label="Scale"
.tooltip="${'Scale'}"
.justify="${'space-between'}"
.labelHeight="${'20px'}"
.iconContainerWidth="${'65px'}"
>
<span class="label">${format?.(size) ?? size}</span>
${ArrowDownSmallIcon()}
</editor-icon-button>
`}
>
<div data-orientation="vertical">
${repeat(
sizes,
({ key, value }) => key ?? value,
({ key, value }) => html`
<editor-menu-action
aria-label="${key}"
?data-selected="${size === value}"
@click=${() => this.select(value)}
>
${key ?? format?.(value) ?? value}
${when(isCheckType && size === value, () => DoneIcon())}
</editor-menu-action>
`
)}
<input
type="text"
inputmode="numeric"
pattern="[0-9]*"
min="${this.minSize}"
max="${this.maxSize}"
placeholder="${placeholder}"
@keydown=${this._onKeydown}
@input=${stopPropagation}
@click=${stopPropagation}
@pointerdown=${stopPropagation}
@cut=${stopPropagation}
@copy=${stopPropagation}
@paste=${stopPropagation}
/>
</div>
</editor-menu-button>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'affine-size-dropdown-menu': SizeDropdownMenu;
}
}

View File

@@ -0,0 +1,7 @@
import { SizeDropdownMenu } from './dropdown-menu';
export * from './dropdown-menu';
export function effects() {
customElements.define('affine-size-dropdown-menu', SizeDropdownMenu);
}

View File

@@ -2,14 +2,11 @@ import {
type ToolbarAction,
ToolbarContext,
} from '@blocksuite/affine-shared/services';
import {
PropTypes,
requiredProperties,
ShadowlessElement,
} from '@blocksuite/block-std';
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
import { SignalWatcher } from '@blocksuite/global/lit';
import { ArrowDownSmallIcon } from '@blocksuite/icons/lit';
import type { ReadonlySignal, Signal } from '@preact/signals-core';
import { LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { html } from 'lit-html';
import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -20,7 +17,7 @@ import { repeat } from 'lit-html/directives/repeat.js';
context: PropTypes.instanceOf(ToolbarContext),
viewType$: PropTypes.object,
})
export class ViewDropdownMenu extends SignalWatcher(ShadowlessElement) {
export class ViewDropdownMenu extends SignalWatcher(LitElement) {
@property({ attribute: false })
accessor actions!: ToolbarAction[];
@@ -43,6 +40,7 @@ export class ViewDropdownMenu extends SignalWatcher(ShadowlessElement) {
.button=${html`
<editor-icon-button
aria-label="Switch view"
.tooltip="${'Switch view'}"
.justify="${'space-between'}"
.labelHeight="${'20px'}"
.iconContainerWidth="${'110px'}"