Skip to content

Commit ff707ed

Browse files
committed
refactor(editor): edgeless note toolbar config extension
1 parent 697941c commit ff707ed

File tree

30 files changed

+1482
-53
lines changed

30 files changed

+1482
-53
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@blocksuite/affine-components/edgeless-line-styles-panel';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@blocksuite/affine-components/edgeless-line-width-panel';

blocksuite/affine/all/src/effects.ts

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { effects as componentColorPickerEffects } from '@blocksuite/affine-compo
2525
import { effects as componentContextMenuEffects } from '@blocksuite/affine-components/context-menu';
2626
import { effects as componentDatePickerEffects } from '@blocksuite/affine-components/date-picker';
2727
import { effects as componentDropIndicatorEffects } from '@blocksuite/affine-components/drop-indicator';
28+
import { effects as componentEdgelessLineStylesEffects } from '@blocksuite/affine-components/edgeless-line-styles-panel';
29+
import { effects as componentEdgelessLineWidthEffects } from '@blocksuite/affine-components/edgeless-line-width-panel';
2830
import { effects as componentEmbedCardModalEffects } from '@blocksuite/affine-components/embed-card-modal';
2931
import { FilterableListComponent } from '@blocksuite/affine-components/filterable-list';
3032
import { effects as componentHighlightDropdownMenuEffects } from '@blocksuite/affine-components/highlight-dropdown-menu';
@@ -149,6 +151,8 @@ export function effects() {
149151
componentViewDropdownMenuEffects();
150152
componentTooltipContentWithShortcutEffects();
151153
componentSizeDropdownMenuEffects();
154+
componentEdgelessLineWidthEffects();
155+
componentEdgelessLineStylesEffects();
152156

153157
widgetScrollAnchoringEffects();
154158
widgetFrameTitleEffects();

blocksuite/affine/blocks/block-note/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@
2929
"@lit/context": "^1.1.2",
3030
"@preact/signals-core": "^1.8.0",
3131
"@toeverything/theme": "^1.1.12",
32+
"@types/lodash-es": "^4.17.12",
3233
"@types/mdast": "^4.0.4",
3334
"@vanilla-extract/css": "^1.17.0",
3435
"lit": "^3.2.0",
36+
"lodash-es": "^4.17.21",
3537
"minimatch": "^10.0.1",
3638
"zod": "^3.23.8"
3739
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { LineWidth, type StrokeStyle } from '@blocksuite/affine-model';
2+
import { ShadowlessElement } from '@blocksuite/block-std';
3+
import { ArrowDownSmallIcon, LineStyleIcon } from '@blocksuite/icons/lit';
4+
import { html } from 'lit';
5+
import { property } from 'lit/decorators.js';
6+
7+
export class EdgelessNoteBorderDropdownMenu extends ShadowlessElement {
8+
override render() {
9+
const { lineSize, lineStyle } = this;
10+
11+
return html`
12+
<editor-menu-button
13+
.button=${html`
14+
<editor-icon-button
15+
aria-label="Border style"
16+
.tooltip="${'Border style'}"
17+
>
18+
${LineStyleIcon()} ${ArrowDownSmallIcon()}
19+
</editor-icon-button>
20+
`}
21+
>
22+
<affine-edgeless-line-styles-panel
23+
.lineSize=${lineSize}
24+
.lineStyle=${lineStyle}
25+
></affine-edgeless-line-styles-panel>
26+
</editor-menu-button>
27+
`;
28+
}
29+
30+
@property({ attribute: false })
31+
accessor lineStyle!: StrokeStyle;
32+
33+
@property({ attribute: false })
34+
accessor lineSize: LineWidth = LineWidth.Two;
35+
}
36+
37+
declare global {
38+
interface HTMLElementTagNameMap {
39+
'edgeless-note-border-dropdown-menu': EdgelessNoteBorderDropdownMenu;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { NoteDisplayMode } from '@blocksuite/affine-model';
2+
import { ShadowlessElement } from '@blocksuite/block-std';
3+
import { ArrowDownSmallIcon } from '@blocksuite/icons/lit';
4+
import { html } from 'lit';
5+
import { property } from 'lit/decorators.js';
6+
7+
const DisplayModeMap = {
8+
[NoteDisplayMode.DocAndEdgeless]: 'Both',
9+
[NoteDisplayMode.EdgelessOnly]: 'Edgeless',
10+
[NoteDisplayMode.DocOnly]: 'Page',
11+
} as const satisfies Record<NoteDisplayMode, string>;
12+
13+
export class EdgelessNoteDisplayModeDropdownMenu extends ShadowlessElement {
14+
get mode() {
15+
return DisplayModeMap[this.displayMode];
16+
}
17+
18+
select(detail: NoteDisplayMode) {
19+
this.dispatchEvent(new CustomEvent('select', { detail }));
20+
}
21+
22+
override render() {
23+
const { displayMode, mode } = this;
24+
25+
return html`
26+
<span class="display-mode-button-label">Show in</span>
27+
<editor-menu-button
28+
.contentPadding=${'8px'}
29+
.button=${html`
30+
<editor-icon-button
31+
aria-label="Mode"
32+
.tooltip="${'Display mode'}"
33+
.justify="${'space-between'}"
34+
.labelHeight="${'20px'}"
35+
>
36+
<span class="label">${mode}</span>
37+
${ArrowDownSmallIcon()}
38+
</editor-icon-button>
39+
`}
40+
>
41+
<note-display-mode-panel
42+
.displayMode=${displayMode}
43+
.onSelect=${(newMode: NoteDisplayMode) => this.select(newMode)}
44+
>
45+
</note-display-mode-panel>
46+
</editor-menu-button>
47+
`;
48+
}
49+
50+
@property({ attribute: false })
51+
accessor displayMode!: NoteDisplayMode;
52+
}
53+
54+
declare global {
55+
interface HTMLElementTagNameMap {
56+
'edgeless-note-display-mode-dropdown-menu': EdgelessNoteDisplayModeDropdownMenu;
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { ColorScheme, NoteShadow } from '@blocksuite/affine-model';
2+
import {
3+
ArrowDownSmallIcon,
4+
NoteShadowDuotoneIcon,
5+
} from '@blocksuite/icons/lit';
6+
import { css, html, LitElement } from 'lit';
7+
import { property } from 'lit/decorators.js';
8+
import { repeat } from 'lit/directives/repeat.js';
9+
import { styleMap } from 'lit/directives/style-map.js';
10+
11+
import { NoteNoShadowIcon, NoteShadowSampleIcon } from './icons';
12+
13+
const SHADOWS = [
14+
{
15+
type: NoteShadow.None,
16+
styles: {
17+
light: '',
18+
dark: '',
19+
},
20+
tooltip: 'No shadow',
21+
},
22+
{
23+
type: NoteShadow.Box,
24+
styles: {
25+
light:
26+
'0px 0.2px 4.8px 0px rgba(66, 65, 73, 0.2), 0px 0px 1.6px 0px rgba(66, 65, 73, 0.2)',
27+
dark: '0px 0.2px 6px 0px rgba(0, 0, 0, 0.44), 0px 0px 2px 0px rgba(0, 0, 0, 0.66)',
28+
},
29+
tooltip: 'Box shadow',
30+
},
31+
{
32+
type: NoteShadow.Sticker,
33+
styles: {
34+
light:
35+
'0px 9.6px 10.4px -4px rgba(66, 65, 73, 0.07), 0px 10.4px 7.2px -8px rgba(66, 65, 73, 0.22)',
36+
dark: '0px 9.6px 10.4px -4px rgba(0, 0, 0, 0.66), 0px 10.4px 7.2px -8px rgba(0, 0, 0, 0.44)',
37+
},
38+
tooltip: 'Sticker shadow',
39+
},
40+
{
41+
type: NoteShadow.Paper,
42+
styles: {
43+
light:
44+
'0px 0px 0px 4px rgba(255, 255, 255, 1), 0px 1.2px 2.4px 4.8px rgba(66, 65, 73, 0.16)',
45+
dark: '0px 1.2px 2.4px 4.8px rgba(0, 0, 0, 0.36), 0px 0px 0px 3.4px rgba(75, 75, 75, 1)',
46+
},
47+
tooltip: 'Paper shadow',
48+
},
49+
{
50+
type: NoteShadow.Float,
51+
styles: {
52+
light:
53+
'0px 5.2px 12px 0px rgba(66, 65, 73, 0.13), 0px 0px 0.4px 1px rgba(0, 0, 0, 0.06)',
54+
dark: '0px 5.2px 12px 0px rgba(0, 0, 0, 0.66), 0px 0px 0.4px 1px rgba(0, 0, 0, 0.44)',
55+
},
56+
tooltip: 'Floation shadow',
57+
},
58+
{
59+
type: NoteShadow.Film,
60+
styles: {
61+
light:
62+
'0px 0px 0px 1.4px rgba(0, 0, 0, 1), 2.4px 2.4px 0px 1px rgba(0, 0, 0, 1)',
63+
dark: '0px 0px 0px 1.4px rgba(178, 178, 178, 1), 2.4px 2.4px 0px 1px rgba(178, 178, 178, 1)',
64+
},
65+
tooltip: 'Film shadow',
66+
},
67+
];
68+
69+
export class EdgelessNoteShadowDropdownMenu extends LitElement {
70+
static override styles = css`
71+
:host {
72+
display: flex;
73+
align-items: center;
74+
justify-content: space-between;
75+
gap: 8px;
76+
}
77+
78+
.item {
79+
padding: 8px;
80+
border-radius: 4px;
81+
display: flex;
82+
justify-content: center;
83+
align-items: center;
84+
cursor: pointer;
85+
}
86+
87+
.item-icon {
88+
display: flex;
89+
justify-content: center;
90+
align-items: center;
91+
}
92+
93+
.item-icon svg rect:first-of-type {
94+
fill: var(--background);
95+
}
96+
97+
.item:hover {
98+
background-color: var(--affine-hover-color);
99+
}
100+
101+
.item[data-selected] {
102+
border: 1px solid var(--affine-brand-color);
103+
}
104+
`;
105+
106+
select(value: NoteShadow) {
107+
this.dispatchEvent(new CustomEvent('select', { detail: value }));
108+
}
109+
110+
override render() {
111+
const { value, background, theme } = this;
112+
const isDark = theme === ColorScheme.Dark;
113+
114+
return html`
115+
<editor-menu-button
116+
.contentPadding="${'8px'}"
117+
.button=${html`
118+
<editor-icon-button
119+
aria-label="Shadow style"
120+
.tooltip="${'Shadow style'}"
121+
>
122+
${NoteShadowDuotoneIcon()} ${ArrowDownSmallIcon()}
123+
</editor-icon-button>
124+
`}
125+
>
126+
<div
127+
data-orientation="horizontal"
128+
style=${styleMap({
129+
'--background': background.startsWith('--')
130+
? `var(${background})`
131+
: background,
132+
})}
133+
>
134+
${repeat(
135+
SHADOWS,
136+
shadow => shadow.type,
137+
({ type, tooltip, styles: { dark, light } }, index) =>
138+
html`<div
139+
class="item"
140+
?data-selected="${value === type}"
141+
@click=${() => this.select(type)}
142+
>
143+
<editor-icon-button
144+
class="item-icon"
145+
data-testid="${type.replace('--', '')}"
146+
.tooltip=${tooltip}
147+
.tipPosition="${'bottom'}"
148+
.iconContainerPadding=${0}
149+
.hover=${false}
150+
style=${styleMap({
151+
boxShadow: `${isDark ? dark : light}`,
152+
})}
153+
>
154+
${index === 0 ? NoteNoShadowIcon : NoteShadowSampleIcon}
155+
</editor-icon-button>
156+
</div>`
157+
)}
158+
</div>
159+
</editor-menu-button>
160+
`;
161+
}
162+
163+
@property({ attribute: false })
164+
accessor background!: string;
165+
166+
@property({ attribute: false })
167+
accessor theme!: ColorScheme;
168+
169+
@property({ attribute: false })
170+
accessor value!: NoteShadow;
171+
}
172+
173+
declare global {
174+
interface HTMLElementTagNameMap {
175+
'edgeless-note-shadow-dropdown-menu': EdgelessNoteShadowDropdownMenu;
176+
}
177+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { html } from 'lit';
2+
3+
export const NoteNoShadowIcon = html`
4+
<svg
5+
width="60"
6+
height="72"
7+
viewBox="0 0 60 72"
8+
fill="none"
9+
xmlns="http://www.w3.org/2000/svg"
10+
>
11+
<rect width="60" height="72" />
12+
<rect
13+
x="0.5"
14+
y="0.5"
15+
width="58.0769"
16+
height="71"
17+
stroke="black"
18+
stroke-opacity="0.1"
19+
/>
20+
<path
21+
fill-rule="evenodd"
22+
clip-rule="evenodd"
23+
d="M21.9576 26.8962L38.6423 43.5809C42.5269 38.9268 42.2845 31.993 37.9149 27.6235C33.5454 23.254 26.6117 23.0115 21.9576 26.8962ZM37.1193 45.1038L20.4346 28.4192C16.55 33.0732 16.7924 40.007 21.162 44.3765C25.5315 48.746 32.4652 48.9885 37.1193 45.1038ZM19.639 26.1005C25.1063 20.6332 33.9706 20.6332 39.4379 26.1005C44.9053 31.5678 44.9053 40.4322 39.4379 45.8995C33.9706 51.3668 25.1063 51.3668 19.639 45.8995C14.1716 40.4322 14.1716 31.5678 19.639 26.1005Z"
24+
fill="black"
25+
fill-opacity="0.1"
26+
/>
27+
</svg>
28+
`;
29+
30+
export const NoteShadowSampleIcon = html`
31+
<svg
32+
width="60"
33+
height="72"
34+
viewBox="0 0 60 72"
35+
xmlns="http://www.w3.org/2000/svg"
36+
>
37+
<rect width="60" height="72" />
38+
<rect
39+
x="9.23071"
40+
y="12.0771"
41+
width="32.3077"
42+
height="4.61538"
43+
rx="2"
44+
fill="black"
45+
fill-opacity="0.1"
46+
/>
47+
<rect
48+
x="9.23071"
49+
y="25.8462"
50+
width="40.6154"
51+
height="2.76923"
52+
rx="1.38462"
53+
fill="black"
54+
fill-opacity="0.1"
55+
/>
56+
<rect
57+
x="9.23071"
58+
y="35.6152"
59+
width="40.6154"
60+
height="2.76923"
61+
rx="1.38462"
62+
fill="black"
63+
fill-opacity="0.1"
64+
/>
65+
<rect
66+
x="9.23071"
67+
y="45.3843"
68+
width="40.6154"
69+
height="2.76923"
70+
rx="1.38462"
71+
fill="black"
72+
fill-opacity="0.1"
73+
/>
74+
<rect
75+
x="9.23071"
76+
y="55.1533"
77+
width="13.8462"
78+
height="2.76923"
79+
rx="1.38462"
80+
fill="black"
81+
fill-opacity="0.1"
82+
/>
83+
</svg>
84+
`;

0 commit comments

Comments
 (0)