feat(editor): add shortcut to highlighter tool (#11604)

Closes: [BS-3092](https://linear.app/affine-design/issue/BS-3092/highlighter-快捷键)

### What's Changed!

* Added shortcut `⇧ P` to highlighter tool

[Screen Recording 2025-04-10 at 16.33.30.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/8ypiIKZXudF5a0tIgIzf/38aadc08-ed18-4b48-9d91-b4876d14a2d3.mov" />](https://app.graphite.dev/media/video/8ypiIKZXudF5a0tIgIzf/38aadc08-ed18-4b48-9d91-b4876d14a2d3.mov)
This commit is contained in:
fundon
2025-04-11 13:08:59 +00:00
parent aabb09b31f
commit afdc40b510
8 changed files with 177 additions and 38 deletions

View File

@@ -88,6 +88,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
p: () => {
this._setEdgelessTool('brush');
},
'Shift-p': () => {
this._setEdgelessTool('highlighter');
},
e: () => {
this._setEdgelessTool('eraser');
},

View File

@@ -1,5 +1,6 @@
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { repeat } from 'lit-html/directives/repeat.js';
export class TooltipContentWithShortcut extends LitElement {
static override styles = css`
@@ -9,6 +10,10 @@ export class TooltipContentWithShortcut extends LitElement {
align-items: center;
gap: 10px;
}
.tooltip__shortcuts {
display: flex;
gap: 2px;
}
.tooltip__shortcut {
font-size: 12px;
position: relative;
@@ -28,19 +33,30 @@ export class TooltipContentWithShortcut extends LitElement {
opacity: 0.2;
}
.tooltip__label {
display: flex;
flex: 1;
white-space: pre;
}
`;
get shortcuts() {
let shortcut = this.shortcut;
if (!shortcut) return [];
return shortcut.split(' ');
}
override render() {
const { tip, shortcut, postfix } = this;
const { tip, shortcuts, postfix } = this;
return html`
<div class="tooltip-with-shortcut">
<span class="tooltip__label">${tip}</span>
${shortcut
? html`<span class="tooltip__shortcut">${shortcut}</span>`
: ''}
<div class="tooltip__shortcuts">
${repeat(
shortcuts,
shortcut => html`<span class="tooltip__shortcut">${shortcut}</span>`
)}
</div>
${postfix ? html`<span class="tooltip__postfix">${postfix}</span>` : ''}
</div>
`;

View File

@@ -0,0 +1,29 @@
import {
EdgelessBrushDarkIcon,
EdgelessBrushLightIcon,
EdgelessHighlighterDarkIcon,
EdgelessHighlighterLightIcon,
} from './icons';
import type { Pen } from './types';
export const penIconMap = {
dark: {
brush: EdgelessBrushDarkIcon,
highlighter: EdgelessHighlighterDarkIcon,
},
light: {
brush: EdgelessBrushLightIcon,
highlighter: EdgelessHighlighterLightIcon,
},
};
export const penInfoMap: { [k in Pen]: { tip: string; shortcut: string } } = {
brush: {
tip: 'Pen',
shortcut: 'P',
},
highlighter: {
tip: 'Highlighter',
shortcut: '⇧ P',
},
};

View File

@@ -7,11 +7,16 @@ import {
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import { SignalWatcher } from '@blocksuite/global/lit';
import { computed, type Signal } from '@preact/signals-core';
import {
computed,
type ReadonlySignal,
type Signal,
} from '@preact/signals-core';
import { css, html, LitElement, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { penInfoMap } from './consts';
import type { Pen, PenMap } from './types';
export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
@@ -26,8 +31,14 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
.pens {
display: flex;
height: 100%;
padding: 0 4px;
align-items: center;
align-items: flex-end;
edgeless-tool-icon-button {
display: flex;
align-self: flex-start;
}
.pen-wrapper {
display: flex;
@@ -36,7 +47,7 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
align-items: flex-end;
justify-content: center;
position: relative;
transform: translateY(10px);
transform: translateY(-2px);
transition-property: color, transform;
transition-duration: 300ms;
transition-timing-function: ease-in-out;
@@ -46,7 +57,7 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
.pen-wrapper:hover,
.pen-wrapper:active,
.pen-wrapper[data-active] {
transform: translateY(-10px);
transform: translateY(-22px);
}
}
@@ -56,6 +67,8 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
}
menu-divider {
display: flex;
align-self: center;
height: 24px;
margin: 0 9px;
}
@@ -83,42 +96,64 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
override render() {
const {
_theme$: { value: theme },
color$: { value: currentColor },
colors$: {
value: { brush: brushColor, highlighter: highlighterColor },
},
pen$: { value: pen },
penIconMap$: {
value: { brush: brushIcon, highlighter: highlighterIcon },
},
penInfo$: {
value: { type, color },
},
} = this;
return html`
<edgeless-slide-menu>
<div class="pens" slot="prefix">
<div
class="pen-wrapper edgeless-brush-button"
?data-active="${pen === 'brush'}"
style=${styleMap({ color: brushColor })}
<edgeless-tool-icon-button
class="edgeless-brush-button"
.tooltip=${html`<affine-tooltip-content-with-shortcut
data-tip="${penInfoMap.brush.tip}"
data-shortcut="${penInfoMap.brush.shortcut}"
></affine-tooltip-content-with-shortcut>`}
.tooltipOffset=${20}
.hover=${false}
@click=${() => this._onPickPen('brush')}
>
${brushIcon}
</div>
<div
class="pen-wrapper edgeless-highlighter-button"
?data-active="${pen === 'highlighter'}"
style=${styleMap({ color: highlighterColor })}
<div
class="pen-wrapper"
style=${styleMap({ color: brushColor })}
?data-active="${type === 'brush'}"
>
${brushIcon}
</div>
</edgeless-tool-icon-button>
<edgeless-tool-icon-button
class="edgeless-highlighter-button"
.tooltip=${html`<affine-tooltip-content-with-shortcut
data-tip="${penInfoMap.highlighter.tip}"
data-shortcut="${penInfoMap.highlighter.shortcut}"
></affine-tooltip-content-with-shortcut>`}
.tooltipOffset=${20}
.hover=${false}
@click=${() => this._onPickPen('highlighter')}
>
${highlighterIcon}
</div>
<div
class="pen-wrapper"
style=${styleMap({ color: highlighterColor })}
?data-active="${type === 'highlighter'}"
>
${highlighterIcon}
</div>
</edgeless-tool-icon-button>
<menu-divider .vertical=${true}></menu-divider>
</div>
<div class="menu-content">
<edgeless-color-panel
class="one-way"
@select=${this._onPickColor}
.value=${currentColor}
.value=${color}
.theme=${theme}
.palettes=${DefaultTheme.StrokeColorShortPalettes}
.shouldKeepColor=${true}
@@ -135,14 +170,20 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
accessor onChange!: (props: Record<string, unknown>) => void;
@property({ attribute: false })
accessor colors$!: Signal<PenMap<string>>;
accessor colors$!: ReadonlySignal<PenMap<string>>;
@property({ attribute: false })
accessor color$!: Signal<string>;
accessor penIconMap$!: ReadonlySignal<PenMap<TemplateResult>>;
@property({ attribute: false })
accessor pen$!: Signal<Pen>;
@property({ attribute: false })
accessor penIconMap$!: Signal<PenMap<TemplateResult>>;
accessor penInfo$!: ReadonlySignal<{
type: Pen;
color: string;
icon: TemplateResult<1>;
tip: string;
shortcut: string;
}>;
}

View File

@@ -10,7 +10,7 @@ import { css, html, LitElement, nothing } from 'lit';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
import { penIconMap } from './icons';
import { penIconMap, penInfoMap } from './consts';
import type { Pen } from './types';
export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
@@ -81,6 +81,18 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
return this.penIconMap$.value[pen];
});
private readonly penInfo$ = computed(() => {
const type = this.pen$.value;
const icon = this.penIcon$.value;
const color = this.color$.value;
return {
...penInfoMap[type],
color,
icon,
type,
};
});
private readonly pen$ = signal<Pen>('brush');
override enableActiveBackground = true;
@@ -89,9 +101,20 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
override firstUpdated() {
this.disposables.add(
this.gfx.tool.currentToolName$.subscribe(tool => {
if (this.type.map(String).includes(tool)) return;
this.tryDisposePopper();
this.gfx.tool.currentToolName$.subscribe(name => {
const tool = this.type.find(t => t === name);
if (!tool) {
this.tryDisposePopper();
return;
}
if (tool !== this.pen$.peek()) {
this.pen$.value = tool;
}
if (this.active) return;
this._togglePenMenu();
})
);
}
@@ -101,10 +124,10 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
!this.active && this.setEdgelessTool(this.pen$.peek());
const menu = this.createPopper('edgeless-pen-menu', this);
Object.assign(menu.element, {
color$: this.color$,
colors$: this.colors$,
pen$: this.pen$,
penIconMap$: this.penIconMap$,
pen$: this.pen$,
penInfo$: this.penInfo$,
edgeless: this.edgeless,
onChange: (props: Record<string, unknown>) => {
const pen = this.pen$.peek();
@@ -117,20 +140,22 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
override render() {
const {
active,
penIcon$: { value: icon },
color$: { value: color },
penInfo$: {
value: { type, color, icon, tip, shortcut },
},
} = this;
return html`
<edgeless-toolbar-button
class="edgeless-pen-button"
data-drawing-tool="${type}"
.tooltip=${when(
this.popper,
() => nothing,
() =>
html`<affine-tooltip-content-with-shortcut
data-tip="${'Pen'}"
data-shortcut="${'P'}"
data-tip="${tip}"
data-shortcut="${shortcut}"
></affine-tooltip-content-with-shortcut>`
)}
.tooltipOffset=${4}