mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 22:37:04 +08:00
refactor(editor): edgeless bookmark toolbar config extension (#10711)
This commit is contained in:
@@ -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[];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SizeDropdownMenu } from './dropdown-menu';
|
||||
|
||||
export * from './dropdown-menu';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('affine-size-dropdown-menu', SizeDropdownMenu);
|
||||
}
|
||||
@@ -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'}"
|
||||
|
||||
Reference in New Issue
Block a user