Skip to content

Commit 4c32e51

Browse files
iapolyamakhnatkin
andauthored
feat: support mobile view (#588)
Co-authored-by: Sergey Makhnatkin <[email protected]>
1 parent a401f7d commit 4c32e51

24 files changed

+237
-44
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type {StoryObj} from '@storybook/react';
2+
3+
import {MobileEditor as component} from './MobileEditor';
4+
5+
export const Story: StoryObj<typeof component> = {};
6+
Story.storyName = 'Mobile editor';
7+
8+
export default {
9+
title: 'Experiments / Mobile editor',
10+
component,
11+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {memo} from 'react';
2+
3+
import {MarkdownEditorView, useMarkdownEditor} from '../../../src';
4+
import {PlaygroundLayout} from '../../components/PlaygroundLayout';
5+
6+
import {toolbarPreset} from './preset';
7+
8+
export const MobileEditor = memo(() => {
9+
const editor = useMarkdownEditor({
10+
mobile: true,
11+
});
12+
13+
return (
14+
<PlaygroundLayout
15+
editor={editor}
16+
view={({className}) => (
17+
<MarkdownEditorView
18+
stickyToolbar
19+
settingsVisible={false}
20+
editor={editor}
21+
className={className}
22+
toolbarsPreset={toolbarPreset}
23+
/>
24+
)}
25+
/>
26+
);
27+
});
28+
29+
MobileEditor.displayName = 'MobileEditor';

demo/stories/mobile-editor/preset.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {ActionName as Action} from 'src/bundle/config/action-names';
2+
import {ToolbarName as Toolbar} from 'src/modules/toolbars/constants';
3+
import {
4+
boldItemMarkup,
5+
boldItemView,
6+
boldItemWysiwyg,
7+
italicItemMarkup,
8+
italicItemView,
9+
italicItemWysiwyg,
10+
strikethroughItemMarkup,
11+
strikethroughItemView,
12+
strikethroughItemWysiwyg,
13+
underlineItemMarkup,
14+
underlineItemView,
15+
underlineItemWysiwyg,
16+
} from 'src/modules/toolbars/items';
17+
import type {ToolbarsPreset} from 'src/modules/toolbars/types';
18+
19+
export const toolbarPreset: ToolbarsPreset = {
20+
items: {
21+
[Action.bold]: {
22+
view: boldItemView,
23+
wysiwyg: boldItemWysiwyg,
24+
markup: boldItemMarkup,
25+
},
26+
[Action.italic]: {
27+
view: italicItemView,
28+
wysiwyg: italicItemWysiwyg,
29+
markup: italicItemMarkup,
30+
},
31+
[Action.underline]: {
32+
view: underlineItemView,
33+
wysiwyg: underlineItemWysiwyg,
34+
markup: underlineItemMarkup,
35+
},
36+
[Action.strike]: {
37+
view: strikethroughItemView,
38+
wysiwyg: strikethroughItemWysiwyg,
39+
markup: strikethroughItemMarkup,
40+
},
41+
},
42+
orders: {
43+
[Toolbar.wysiwygMain]: [[Action.bold, Action.italic, Action.underline, Action.strike]],
44+
[Toolbar.markupMain]: [[Action.bold, Action.italic, Action.underline, Action.strike]],
45+
},
46+
};

src/bundle/Editor.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export interface EditorInt
8888
readonly preset: EditorPreset;
8989
readonly mdOptions: Readonly<MarkdownEditorMdOptions>;
9090
readonly directiveSyntax: DirectiveSyntaxContext;
91+
readonly mobile: boolean;
9192

9293
/** @internal used in demo for dev-tools */
9394
readonly _wysiwygView?: PMEditorView;
@@ -126,7 +127,7 @@ export type ChangeEditorModeOptions = {
126127

127128
export type EditorOptions = Pick<
128129
MarkdownEditorOptions,
129-
'md' | 'initial' | 'handlers' | 'experimental' | 'markupConfig' | 'wysiwygConfig'
130+
'md' | 'initial' | 'handlers' | 'experimental' | 'markupConfig' | 'wysiwygConfig' | 'mobile'
130131
> & {
131132
logger: Logger2.ILogger;
132133
renderStorage: ReactRenderStorage;
@@ -165,6 +166,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
165166
#beforeEditorModeChange?: (
166167
options: Pick<ChangeEditorModeOptions, 'mode' | 'reason'>,
167168
) => boolean | undefined;
169+
#mobile: boolean;
168170

169171
get _wysiwygView(): PMEditorView {
170172
// @ts-expect-error internal typing
@@ -336,6 +338,10 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
336338
return this.#enableNewImageSizeCalculation;
337339
}
338340

341+
get mobile(): boolean {
342+
return this.#mobile;
343+
}
344+
339345
constructor(opts: EditorOptions) {
340346
const {logger} = opts;
341347

@@ -353,6 +359,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
353359
experimental = {},
354360
markupConfig = {},
355361
wysiwygConfig = {},
362+
mobile = false,
356363
} = opts;
357364

358365
this.#logger = logger;
@@ -388,6 +395,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
388395
this.#prepareRawMarkup = experimental.prepareRawMarkup;
389396
this.#escapeConfig = wysiwygConfig.escapeConfig;
390397
this.#beforeEditorModeChange = experimental.beforeEditorModeChange;
398+
this.#mobile = mobile;
391399
}
392400

393401
// ---> implements CodeEditor

src/bundle/MarkdownEditorView.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,15 @@ const EditorWrapper = forwardRef<HTMLDivElement, EditorWrapperProps>(
160160
splitModeEnabled: editor.splitModeEnabled,
161161
stickyToolbar,
162162
toolbarVisibility: editor.toolbarVisible && !showPreview,
163+
disableMark: editor.mobile,
163164
};
164165

165166
const areSettingsVisible =
166167
settingsVisibleProp === true ||
167168
(Array.isArray(settingsVisibleProp) && settingsVisibleProp.length > 0);
168169

170+
const toolbarDisplay = editor.mobile ? 'scroll' : 'shrink';
171+
169172
return (
170173
<div
171174
className={b('editor-wrapper')}
@@ -198,6 +201,7 @@ const EditorWrapper = forwardRef<HTMLDivElement, EditorWrapperProps>(
198201
className={b('editor', {mode: editorMode})}
199202
toolbarClassName={b('toolbar')}
200203
stickyToolbar={stickyToolbar}
204+
toolbarDisplay={toolbarDisplay}
201205
>
202206
<Settings
203207
{...settingsProps}
@@ -218,6 +222,7 @@ const EditorWrapper = forwardRef<HTMLDivElement, EditorWrapperProps>(
218222
className={b('editor', {mode: editorMode})}
219223
toolbarClassName={b('toolbar')}
220224
stickyToolbar={stickyToolbar}
225+
toolbarDisplay={toolbarDisplay}
221226
>
222227
<Settings
223228
{...settingsProps}

src/bundle/MarkupEditorView.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {EditorInt} from './Editor';
1111
import {MarkupEditorComponent} from './MarkupEditorComponent';
1212
import {ToolbarView} from './ToolbarView';
1313
import {MarkupToolbarContextProvider} from './toolbar/markup/context';
14-
import type {MToolbarData, MToolbarItemData} from './toolbar/types';
14+
import type {MToolbarData, MToolbarItemData, ToolbarDisplay} from './toolbar/types';
1515
import type {MarkdownEditorSplitMode} from './types';
1616

1717
import './MarkupEditorView.scss';
@@ -31,6 +31,7 @@ export type MarkupEditorViewProps = ClassNameProps &
3131
splitModeEnabled: boolean;
3232
hiddenActionsConfig?: MToolbarItemData[];
3333
children?: React.ReactNode;
34+
toolbarDisplay?: ToolbarDisplay;
3435
};
3536

3637
export const MarkupEditorView = memo<MarkupEditorViewProps>((props) => {
@@ -46,6 +47,7 @@ export const MarkupEditorView = memo<MarkupEditorViewProps>((props) => {
4647
toolbarClassName,
4748
children,
4849
stickyToolbar = true,
50+
toolbarDisplay,
4951
} = props;
5052
useRenderTime((time) => {
5153
globalLogger.metrics({
@@ -81,6 +83,7 @@ export const MarkupEditorView = memo<MarkupEditorViewProps>((props) => {
8183
toolbarFocus={() => editor.focus()}
8284
settingsVisible={settingsVisible}
8385
className={b('toolbar', [toolbarClassName])}
86+
toolbarDisplay={toolbarDisplay}
8487
>
8588
{children}
8689
</ToolbarView>

src/bundle/ToolbarView.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {useUpdate} from 'react-use';
66
import type {ClassNameProps} from '../classname';
77
import {i18n} from '../i18n/menubar';
88
import {useSticky} from '../react-utils/useSticky';
9-
import {FlexToolbar, type ToolbarData, type ToolbarItemData} from '../toolbar';
9+
import {FlexToolbar, type ToolbarData, type ToolbarDisplay, type ToolbarItemData} from '../toolbar';
1010

1111
import type {EditorInt} from './Editor';
1212
import {stickyCn} from './sticky';
@@ -23,6 +23,7 @@ export type ToolbarViewProps<T> = ClassNameProps &
2323
hiddenActionsConfig?: ToolbarItemData<T>[];
2424
children?: React.ReactNode;
2525
stickyToolbar: boolean;
26+
toolbarDisplay?: ToolbarDisplay;
2627
};
2728

2829
export function ToolbarView<T>({
@@ -31,6 +32,7 @@ export function ToolbarView<T>({
3132
toolbarEditor,
3233
toolbarFocus,
3334
toolbarConfig,
35+
toolbarDisplay,
3436
hiddenActionsConfig,
3537
settingsVisible,
3638
className,
@@ -49,6 +51,8 @@ export function ToolbarView<T>({
4951
const wrapperRef = useRef<HTMLDivElement>(null);
5052
const isStickyActive = useSticky(wrapperRef) && stickyToolbar;
5153

54+
const mobile = editor.mobile;
55+
5256
return (
5357
<div
5458
data-qa={qa}
@@ -69,6 +73,10 @@ export function ToolbarView<T>({
6973
focus={toolbarFocus}
7074
dotsTitle={i18n('more_action')}
7175
onClick={(id, attrs) => editor.emit('toolbar-action', {id, attrs, editorMode})}
76+
display={toolbarDisplay}
77+
disableTooltip={mobile}
78+
disableHotkey={mobile}
79+
disablePreview={mobile}
7280
/>
7381
{children}
7482
</div>

src/bundle/WysiwygEditorView.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {useRenderTime} from '../react-utils/hooks';
1010
import type {EditorInt} from './Editor';
1111
import {ToolbarView} from './ToolbarView';
1212
import {WysiwygEditorComponent} from './WysiwygEditorComponent';
13-
import type {WToolbarData, WToolbarItemData} from './toolbar/types';
13+
import type {ToolbarDisplay, WToolbarData, WToolbarItemData} from './toolbar/types';
1414

1515
import './WysiwygEditorView.scss';
1616

@@ -27,6 +27,7 @@ export type WysiwygEditorViewProps = ClassNameProps &
2727
toolbarClassName?: string;
2828
hiddenActionsConfig?: WToolbarItemData[];
2929
children?: React.ReactNode;
30+
toolbarDisplay?: ToolbarDisplay;
3031
};
3132

3233
export const WysiwygEditorView = memo<WysiwygEditorViewProps>((props) => {
@@ -42,7 +43,9 @@ export const WysiwygEditorView = memo<WysiwygEditorViewProps>((props) => {
4243
toolbarClassName,
4344
children,
4445
stickyToolbar = true,
46+
toolbarDisplay,
4547
} = props;
48+
4649
useRenderTime((time) => {
4750
globalLogger.metrics({
4851
component: 'wysiwyg-editor',
@@ -55,6 +58,7 @@ export const WysiwygEditorView = memo<WysiwygEditorViewProps>((props) => {
5558
duration: time,
5659
});
5760
});
61+
5862
return (
5963
<div className={b({toolbar: toolbarVisible}, [className])} data-qa={qa}>
6064
{toolbarVisible ? (
@@ -69,6 +73,7 @@ export const WysiwygEditorView = memo<WysiwygEditorViewProps>((props) => {
6973
hiddenActionsConfig={hiddenActionsConfig}
7074
settingsVisible={settingsVisible}
7175
className={b('toolbar', [toolbarClassName])}
76+
toolbarDisplay={toolbarDisplay}
7277
>
7378
{children}
7479
</ToolbarView>

src/bundle/settings/index.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ type SettingsContentProps = ClassNameProps &
119119
splitMode?: MarkdownEditorSplitMode;
120120
splitModeEnabled?: boolean;
121121
onSplitModeChange?: (splitModeEnabled: boolean) => void;
122+
disableMark?: boolean;
122123
};
123124

124125
const mdHelpPlacement: PopupPlacement = ['bottom', 'bottom-end', 'right-start', 'right', 'left'];
@@ -136,6 +137,7 @@ const SettingsContent: React.FC<SettingsContentProps> = function SettingsContent
136137
showPreview,
137138
settingsVisible,
138139
qa,
140+
disableMark,
139141
}) {
140142
const isSettingsArray = Array.isArray(settingsVisible);
141143
const showModeSetting = isSettingsArray ? settingsVisible?.includes('mode') : true;
@@ -167,23 +169,25 @@ const SettingsContent: React.FC<SettingsContentProps> = function SettingsContent
167169
iconStart={<Icon data={LogoMarkdown} />}
168170
>
169171
{i18n('settings_markup')}
170-
<HelpMark
171-
popoverProps={{
172-
placement: mdHelpPlacement,
173-
modal: false,
174-
}}
175-
className={bContent('mode-help')}
176-
>
177-
<div
178-
onClick={(e) => {
179-
// stop clicks propagation
180-
// because otherwise click in MarkdownHints handled as click on MenuItem
181-
e.stopPropagation();
172+
{!disableMark && (
173+
<HelpMark
174+
popoverProps={{
175+
placement: mdHelpPlacement,
176+
modal: false,
182177
}}
178+
className={bContent('mode-help')}
183179
>
184-
<MarkdownHints />
185-
</div>
186-
</HelpMark>
180+
<div
181+
onClick={(e) => {
182+
// stop clicks propagation
183+
// because otherwise click in MarkdownHints handled as click on MenuItem
184+
e.stopPropagation();
185+
}}
186+
>
187+
<MarkdownHints />
188+
</div>
189+
</HelpMark>
190+
)}
187191
</Menu.Item>
188192
</Menu>
189193
)}

src/bundle/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,6 @@ export type MarkdownEditorOptions = {
210210
/** Options for wysiwyg mode */
211211
wysiwygConfig?: MarkdownEditorWysiwygConfig;
212212
logger?: Logger2.ILogger;
213+
/** Mobile view */
214+
mobile?: boolean;
213215
};

0 commit comments

Comments
 (0)