Skip to content

Commit 0bf2af1

Browse files
vladKrkwhiteformedvladkrkmakhnatkind3m1d0v
authored
feat(actions): add heading previews (#582)
Co-authored-by: whiteformed <[email protected]> Co-authored-by: vladkrk <[email protected]> Co-authored-by: makhnatkin <[email protected]> Co-authored-by: d3m1d0v <[email protected]>
1 parent e197af6 commit 0bf2af1

File tree

17 files changed

+258
-41
lines changed

17 files changed

+258
-41
lines changed

src/bundle/config/markup.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
import React from 'react';
55

6+
import {HeadingPreview} from '../../bundle/config/previews/HeadingPreview';
67
import {i18n} from '../../i18n/menubar';
78
import {
89
insertHRule,
@@ -363,6 +364,7 @@ export const mHeading1ItemData: ToolbarListButtonItemData<CodeEditor> = {
363364
exec: (e) => toH1(e.cm),
364365
isActive: inactive,
365366
isEnable: enable,
367+
preview: <HeadingPreview level={1} />,
366368
};
367369
export const mHeading2ItemData: ToolbarListButtonItemData<CodeEditor> = {
368370
id: ActionName.heading2,
@@ -372,6 +374,7 @@ export const mHeading2ItemData: ToolbarListButtonItemData<CodeEditor> = {
372374
exec: (e) => toH2(e.cm),
373375
isActive: inactive,
374376
isEnable: enable,
377+
preview: <HeadingPreview level={2} />,
375378
};
376379
export const mHeading3ItemData: ToolbarListButtonItemData<CodeEditor> = {
377380
id: ActionName.heading3,
@@ -381,6 +384,7 @@ export const mHeading3ItemData: ToolbarListButtonItemData<CodeEditor> = {
381384
exec: (e) => toH3(e.cm),
382385
isActive: inactive,
383386
isEnable: enable,
387+
preview: <HeadingPreview level={3} />,
384388
};
385389
export const mHeading4ItemData: ToolbarListButtonItemData<CodeEditor> = {
386390
id: ActionName.heading4,
@@ -390,6 +394,7 @@ export const mHeading4ItemData: ToolbarListButtonItemData<CodeEditor> = {
390394
exec: (e) => toH4(e.cm),
391395
isActive: inactive,
392396
isEnable: enable,
397+
preview: <HeadingPreview level={4} />,
393398
};
394399
export const mHeading5ItemData: ToolbarListButtonItemData<CodeEditor> = {
395400
id: ActionName.heading5,
@@ -399,6 +404,7 @@ export const mHeading5ItemData: ToolbarListButtonItemData<CodeEditor> = {
399404
exec: (e) => toH5(e.cm),
400405
isActive: inactive,
401406
isEnable: enable,
407+
preview: <HeadingPreview level={5} />,
402408
};
403409
export const mHeading6ItemData: ToolbarListButtonItemData<CodeEditor> = {
404410
id: ActionName.heading6,
@@ -408,6 +414,7 @@ export const mHeading6ItemData: ToolbarListButtonItemData<CodeEditor> = {
408414
exec: (e) => toH6(e.cm),
409415
isActive: inactive,
410416
isEnable: enable,
417+
preview: <HeadingPreview level={6} />,
411418
};
412419

413420
export const mBulletListItemData: ToolbarListButtonItemData<CodeEditor> = {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
:root {
2+
--toolbar-item-preview-width: 144px;
3+
--toolbar-item-preview-height: 104px;
4+
5+
--toolbar-item-preview-h1-margin: 10px 0 2px;
6+
--toolbar-item-preview-h2-margin: 20px 0 4px;
7+
--toolbar-item-preview-h3-margin: 24px 0 4px;
8+
--toolbar-item-preview-h4-margin: 24px 0 4px;
9+
--toolbar-item-preview-h5-margin: 26px 0 6px;
10+
--toolbar-item-preview-h6-margin: 28px 0 6px;
11+
}
12+
13+
.g-md-action-preview {
14+
overflow: hidden;
15+
16+
width: var(--toolbar-item-preview-width);
17+
height: var(--toolbar-item-preview-height);
18+
19+
white-space: pre;
20+
21+
// Redefine yfm styles
22+
&.yfm > *:not(h2):not(h3):not(h4):not(h5):not(h6):first-child {
23+
/* stylelint-disable-next-line declaration-no-important */
24+
margin: var(--toolbar-item-preview-h1-margin) !important;
25+
}
26+
27+
&.yfm > h2 {
28+
/* stylelint-disable-next-line declaration-no-important */
29+
margin: var(--toolbar-item-preview-h2-margin) !important;
30+
}
31+
32+
&.yfm > h3 {
33+
/* stylelint-disable-next-line declaration-no-important */
34+
margin: var(--toolbar-item-preview-h3-margin) !important;
35+
}
36+
37+
&.yfm > h4 {
38+
/* stylelint-disable-next-line declaration-no-important */
39+
margin: var(--toolbar-item-preview-h4-margin) !important;
40+
}
41+
42+
&.yfm > h5 {
43+
/* stylelint-disable-next-line declaration-no-important */
44+
margin: var(--toolbar-item-preview-h5-margin) !important;
45+
}
46+
47+
&.yfm > h6 {
48+
/* stylelint-disable-next-line declaration-no-important */
49+
margin: var(--toolbar-item-preview-h6-margin) !important;
50+
}
51+
52+
&__text-with-head {
53+
margin-top: 0;
54+
55+
color: var(--g-color-text-hint);
56+
}
57+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
import {cn} from '../../../classname';
4+
5+
import './ActionPreview.scss';
6+
7+
const b = cn('action-preview');
8+
9+
export const ActionPreview = ({children}: {children: React.ReactNode}) => {
10+
return <div className={b(null, ['yfm'])}>{children}</div>;
11+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
3+
import {cn} from '../../../classname';
4+
import {i18n} from '../../../i18n/action-previews';
5+
6+
import {ActionPreview} from './ActionPreview';
7+
8+
import './ActionPreview.scss';
9+
10+
const b = cn('action-preview');
11+
12+
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
13+
14+
export const HeadingPreview = ({level}: {level: HeadingLevel}) => {
15+
const Heading = `h${level}` as const;
16+
17+
return (
18+
<ActionPreview>
19+
<Heading>{i18n('heading')}</Heading>
20+
<p className={b('text-with-head')}>{i18n('text-with-head')}</p>
21+
</ActionPreview>
22+
);
23+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
3+
import {cn} from '../../../classname';
4+
import {i18n} from '../../../i18n/action-previews';
5+
6+
import {ActionPreview} from './ActionPreview';
7+
8+
import './ActionPreview.scss';
9+
10+
const b = cn('action-preview');
11+
12+
export const TextPreview = () => {
13+
return (
14+
<ActionPreview>
15+
<p className={b('text')}>{i18n('text')}</p>
16+
</ActionPreview>
17+
);
18+
};

src/bundle/config/w-heading-config.ts renamed to src/bundle/config/w-heading-config.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import React from 'react';
2+
13
import {i18n} from '../../i18n/menubar';
24
import {Action as A, formatter as f} from '../../shortcuts';
35
import type {WToolbarListButtonData, WToolbarListButtonItemData} from '../toolbar/types';
46

57
import {ActionName} from './action-names';
68
import {icons} from './icons';
9+
import {HeadingPreview} from './previews/HeadingPreview';
10+
import {TextPreview} from './previews/TextPreview';
711

812
export const wTextItemData: WToolbarListButtonItemData = {
913
id: ActionName.paragraph,
@@ -14,6 +18,7 @@ export const wTextItemData: WToolbarListButtonItemData = {
1418
isActive: (e) => e.actions.toParagraph.isActive(),
1519
isEnable: (e) => e.actions.toParagraph.isEnable(),
1620
doNotActivateList: true,
21+
preview: <TextPreview />,
1722
};
1823
export const wHeading1ItemData: WToolbarListButtonItemData = {
1924
id: ActionName.heading1,
@@ -23,6 +28,7 @@ export const wHeading1ItemData: WToolbarListButtonItemData = {
2328
exec: (e) => e.actions.toH1.run(),
2429
isActive: (e) => e.actions.toH1.isActive(),
2530
isEnable: (e) => e.actions.toH1.isEnable(),
31+
preview: <HeadingPreview level={1} />,
2632
};
2733
export const wHeading2ItemData: WToolbarListButtonItemData = {
2834
id: ActionName.heading2,
@@ -32,6 +38,7 @@ export const wHeading2ItemData: WToolbarListButtonItemData = {
3238
exec: (e) => e.actions.toH2.run(),
3339
isActive: (e) => e.actions.toH2.isActive(),
3440
isEnable: (e) => e.actions.toH2.isEnable(),
41+
preview: <HeadingPreview level={2} />,
3542
};
3643
export const wHeading3ItemData: WToolbarListButtonItemData = {
3744
id: ActionName.heading3,
@@ -41,6 +48,7 @@ export const wHeading3ItemData: WToolbarListButtonItemData = {
4148
exec: (e) => e.actions.toH3.run(),
4249
isActive: (e) => e.actions.toH3.isActive(),
4350
isEnable: (e) => e.actions.toH3.isEnable(),
51+
preview: <HeadingPreview level={3} />,
4452
};
4553
export const wHeading4ItemData: WToolbarListButtonItemData = {
4654
id: ActionName.heading4,
@@ -50,6 +58,7 @@ export const wHeading4ItemData: WToolbarListButtonItemData = {
5058
exec: (e) => e.actions.toH4.run(),
5159
isActive: (e) => e.actions.toH4.isActive(),
5260
isEnable: (e) => e.actions.toH4.isEnable(),
61+
preview: <HeadingPreview level={4} />,
5362
};
5463
export const wHeading5ItemData: WToolbarListButtonItemData = {
5564
id: ActionName.heading5,
@@ -59,6 +68,7 @@ export const wHeading5ItemData: WToolbarListButtonItemData = {
5968
exec: (e) => e.actions.toH5.run(),
6069
isActive: (e) => e.actions.toH5.isActive(),
6170
isEnable: (e) => e.actions.toH5.isEnable(),
71+
preview: <HeadingPreview level={5} />,
6272
};
6373
export const wHeading6ItemData: WToolbarListButtonItemData = {
6474
id: ActionName.heading6,
@@ -68,6 +78,7 @@ export const wHeading6ItemData: WToolbarListButtonItemData = {
6878
exec: (e) => e.actions.toH6.run(),
6979
isActive: (e) => e.actions.toH6.isActive(),
7080
isEnable: (e) => e.actions.toH6.isEnable(),
81+
preview: <HeadingPreview level={6} />,
7182
};
7283

7384
export const wHeadingListConfig: WToolbarListButtonData = {

src/bundle/toolbar/utils/toolbarsConfigs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface TransformedItem {
2929
icon?: ToolbarIconData;
3030
hotkey?: string;
3131
withArrow?: boolean;
32+
preview?: React.ReactNode;
3233
wysiwyg?: ToolbarItemWysiwyg<ToolbarDataType>;
3334
markup?: ToolbarItemMarkup<ToolbarDataType>;
3435
}
@@ -46,6 +47,7 @@ const transformItem = (
4647
}
4748

4849
const isListButton = item.view.type === ToolbarDataType.ListButton;
50+
const isSingleButton = item.view.type === ToolbarDataType.SingleButton;
4951

5052
return {
5153
type: item.view.type ?? ToolbarDataType.SingleButton,
@@ -54,6 +56,7 @@ const transformItem = (
5456
hint: item.view.hint,
5557
icon: item.view.icon,
5658
hotkey: item.view.hotkey,
59+
...(isSingleButton && {preview: (item.view as any).preview}),
5760
...(isListButton && {withArrow: (item.view as any).withArrow}),
5861
...(type === 'wysiwyg' && item.wysiwyg && {...item.wysiwyg}),
5962
...(type === 'markup' && item.markup && {...item.markup}),

src/extensions/behavior/CommandMenu/component.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {cn} from '../../../classname';
77
import {i18n} from '../../../i18n/suggest';
88
import {isFunction} from '../../../lodash';
99
import {ErrorLoggerBoundary} from '../../../react-utils/ErrorBoundary';
10+
import {PreviewTooltip} from '../../../toolbar/PreviewTooltip';
1011
import {AutocompletePopupProps} from '../../../utils/autocomplete-popup';
1112

1213
import type {CommandAction} from './types';
@@ -24,7 +25,10 @@ function calcListHeight(itemsCount: number): number | undefined {
2425
return Math.min(MAX_LIST_HEIGHT, itemsCount * ITEM_HEIGHT);
2526
}
2627

27-
export type CommandMenuItem = Pick<CommandAction, 'id' | 'title' | 'icon' | 'hotkey' | 'hint'>;
28+
export type CommandMenuItem = Pick<
29+
CommandAction,
30+
'id' | 'title' | 'icon' | 'hotkey' | 'hint' | 'preview'
31+
>;
2832

2933
export type CommandMenuComponentProps = AutocompletePopupProps & {
3034
currentIndex?: number;
@@ -73,20 +77,23 @@ export const CommandMenuComponent: React.FC<CommandMenuComponentProps> = ({
7377
);
7478
};
7579

76-
function renderItem({id, title, icon, hotkey, hint}: CommandMenuItem): React.ReactNode {
80+
function renderItem({id, title, icon, hotkey, hint, preview}: CommandMenuItem): React.ReactNode {
7781
const titleText = isFunction(title) ? title() : title;
7882
const hintText = isFunction(hint) ? hint() : hint;
83+
7984
return (
80-
<div key={id} className={b('item', {id})}>
81-
<Icon data={icon.data} size={20} className={b('item-icon')} />
82-
<div className={b('item-body')}>
83-
<span className={b('item-title')}>{titleText}</span>
84-
<div className={b('item-extra')}>
85-
{hotkey && <Hotkey value={hotkey} className={b('item-hotkey')} />}
86-
{hintText && <HelpPopover className={b('item- hint')} content={hintText} />}
85+
<PreviewTooltip preview={preview}>
86+
<div key={id} className={b('item', {id})}>
87+
<Icon data={icon.data} size={20} className={b('item-icon')} />
88+
<div className={b('item-body')}>
89+
<span className={b('item-title')}>{titleText}</span>
90+
<div className={b('item-extra')}>
91+
{hotkey && <Hotkey value={hotkey} className={b('item-hotkey')} />}
92+
{hintText && <HelpPopover className={b('item-hint')} content={hintText} />}
93+
</div>
8794
</div>
8895
</div>
89-
</div>
96+
</PreviewTooltip>
9097
);
9198
}
9299

src/i18n/action-previews/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"text": "This is a text without a title. \nBoth the title and the text \nbelow it can be highlighted in bold, italics, color, \nstrikethrough, and underline. You can also add lists, \ntables, links, formulas, anchors, \nand code blocks.",
3+
"text-with-head": "This is the text with the title. \nBoth the title and the text \nbelow it can be highlighted in bold, italics, color, \nstrikethrough, and underline. You can also add lists, \ntables, links, formulas, anchors, \nand code blocks.",
4+
"heading": "Text"
5+
}

src/i18n/action-previews/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {registerKeyset} from '../i18n';
2+
3+
import en from './en.json';
4+
import ru from './ru.json';
5+
6+
const KEYSET = 'action-previews';
7+
8+
export const i18n = registerKeyset(KEYSET, {en, ru});

0 commit comments

Comments
 (0)