mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
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:
@@ -88,6 +88,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
p: () => {
|
||||
this._setEdgelessTool('brush');
|
||||
},
|
||||
'Shift-p': () => {
|
||||
this._setEdgelessTool('highlighter');
|
||||
},
|
||||
e: () => {
|
||||
this._setEdgelessTool('eraser');
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user