Skip to content

Commit 5ddd9d5

Browse files
authored
feat: add autosave while editing Mermaid and YfmHtmlblock (#852)
1 parent 54bd192 commit 5ddd9d5

File tree

10 files changed

+184
-38
lines changed

10 files changed

+184
-38
lines changed

demo/components/Playground.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export type PlaygroundProps = {
8686
disableMarkdownItAttrs?: boolean;
8787
markupParseHtmlOnPaste?: boolean;
8888
style?: React.CSSProperties;
89+
storyAdditionalControls?: Record<string, any>;
8990
} & Pick<UseMarkdownEditorProps, 'experimental' | 'wysiwygConfig'> &
9091
Pick<
9192
MarkdownEditorViewProps,
@@ -141,6 +142,7 @@ export const Playground = memo<PlaygroundProps>((props) => {
141142
disableMarkdownItAttrs,
142143
markupParseHtmlOnPaste,
143144
style,
145+
storyAdditionalControls,
144146
} = props;
145147
const [editorMode, setEditorMode] = useState<MarkdownEditorMode>(initialEditor ?? 'wysiwyg');
146148
const [mdRaw, setMdRaw] = useState<MarkupString>(initial || '');
@@ -200,11 +202,20 @@ export const Playground = memo<PlaygroundProps>((props) => {
200202
/* webpackChunkName: "mermaid-runtime" */ '@diplodoc/mermaid-extension/runtime'
201203
);
202204
},
205+
autoSave: {
206+
enabled: storyAdditionalControls?.mermaidAutoSaveEnabled ?? true,
207+
delay: storyAdditionalControls?.mermaidAutoSaveDelay ?? 1000,
208+
},
203209
})
204210
.use(FoldingHeading)
205211
.use(YfmHtmlBlock, {
206212
useConfig: useYfmHtmlBlockStyles,
207213
sanitize: getSanitizeYfmHtmlBlock({options: defaultOptions}),
214+
autoSave: {
215+
enabled:
216+
storyAdditionalControls?.yfmHtmlBlockAutoSaveEnabled ?? true,
217+
delay: storyAdditionalControls?.yfmHtmlBlockAutoSaveDelay ?? 1000,
218+
},
208219
head: `
209220
<base target="_blank" />
210221
<style>

demo/components/PlaygroundMini.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type PlaygroundMiniProps = Pick<
2828
| 'directiveSyntax'
2929
| 'disabledHTMLBlockModes'
3030
| 'disableMarkdownItAttrs'
31+
| 'storyAdditionalControls'
3132
> & {withDefaultInitialContent?: boolean};
3233

3334
export const PlaygroundMini = memo<PlaygroundMiniProps>(

demo/stories/yfm/YFM.stories.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ export const YfmTabs: Story = {
5353

5454
export const YfmHtmlBlock: Story = {
5555
name: 'YFM HTML',
56-
args: {initial: markup.yfmHtmlBlock},
56+
args: {
57+
initial: markup.yfmHtmlBlock,
58+
storyAdditionalControls: {
59+
yfmHtmlBlockAutoSaveEnabled: false,
60+
yfmHtmlBlockAutoSaveDelay: 2000,
61+
},
62+
},
5763
};
5864

5965
export const YfmFile: Story = {
@@ -73,5 +79,11 @@ export const LaTeXFormulas: Story = {
7379

7480
export const MermaidDiagram: Story = {
7581
name: 'Mermaid diagram',
76-
args: {initial: markup.mermaidDiagram},
82+
args: {
83+
initial: markup.mermaidDiagram,
84+
storyAdditionalControls: {
85+
mermaidAutoSaveEnabled: false,
86+
mermaidAutoSaveDelay: 2000,
87+
},
88+
},
7789
};

src/extensions/additional/Mermaid/MermaidNodeView/MermaidView.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import {useSharedEditingState} from 'src/react-utils/useSharedEditingState';
1212
import {cn} from '../../../../classname';
1313
import {TextAreaFixed as TextArea} from '../../../../forms/TextInput';
1414
import {i18n} from '../../../../i18n/common';
15-
import {useBooleanState, useElementState} from '../../../../react-utils';
15+
import {useAutoSave, useBooleanState, useElementState} from '../../../../react-utils';
1616
import {removeNode} from '../../../../utils';
1717
import {MermaidConsts} from '../MermaidSpecs/const';
18+
import type {MermaidOptions} from '../index';
1819
import type {MermaidEntitySharedState} from '../types';
1920

2021
export const cnMermaid = cn('Mermaid');
@@ -68,22 +69,26 @@ const DiagramEditMode: React.FC<{
6869
mermaidInstance: Mermaid | null;
6970
onSave: (v: string) => void;
7071
onCancel: () => void;
71-
}> = ({initialText, onSave, onCancel, mermaidInstance}) => {
72-
const [text, setText] = useState(initialText || '');
72+
options: MermaidOptions;
73+
}> = ({initialText, onSave, onCancel, mermaidInstance, options: {autoSave}}) => {
74+
const {value, handleChange, handleManualSave, isSaveDisabled} = useAutoSave({
75+
initialValue: initialText || '',
76+
onSave,
77+
onClose: onCancel,
78+
autoSave,
79+
});
7380

7481
return (
7582
<div className={b()}>
76-
<MermaidPreview mermaidInstance={mermaidInstance} text={text} />
83+
<MermaidPreview mermaidInstance={mermaidInstance} text={value} />
7784
<div className={b('Editor')}>
7885
<div>
7986
<TextArea
8087
controlProps={{
8188
className: STOP_EVENT_CLASSNAME,
8289
}}
83-
value={text}
84-
onUpdate={(v) => {
85-
setText(v);
86-
}}
90+
value={value}
91+
onUpdate={handleChange}
8792
autoFocus
8893
/>
8994
</div>
@@ -92,7 +97,11 @@ const DiagramEditMode: React.FC<{
9297
<Button onClick={onCancel} view={'flat'}>
9398
<span className={STOP_EVENT_CLASSNAME}>{i18n('cancel')}</span>
9499
</Button>
95-
<Button onClick={() => onSave(text)} view={'action'}>
100+
<Button
101+
onClick={handleManualSave}
102+
view={'action'}
103+
disabled={isSaveDisabled}
104+
>
96105
<span className={STOP_EVENT_CLASSNAME}>{i18n('save')}</span>
97106
</Button>
98107
</div>
@@ -108,7 +117,8 @@ export const MermaidView: React.FC<{
108117
getMermaidInstance: () => Mermaid;
109118
node: Node;
110119
getPos: () => number | undefined;
111-
}> = ({onChange, node, getPos, view, getMermaidInstance}) => {
120+
options: MermaidOptions;
121+
}> = ({onChange, node, getPos, view, getMermaidInstance, options}) => {
112122
const enitityId: string = node.attrs[MermaidConsts.NodeAttrs.EntityId];
113123
const entityKey = useMemo(
114124
() => SharedStateKey.define<MermaidEntitySharedState>({name: enitityId}),
@@ -144,8 +154,8 @@ export const MermaidView: React.FC<{
144154
onCancel={unsetEditing}
145155
onSave={(v) => {
146156
onChange({[MermaidConsts.NodeAttrs.content]: v});
147-
unsetEditing();
148157
}}
158+
options={options}
149159
/>
150160
);
151161
}

src/extensions/additional/Mermaid/MermaidNodeView/NodeView.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export class WMermaidNodeView implements NodeView {
2020
private readonly getPos;
2121
private readonly renderItem;
2222
private readonly loadRuntimeScript: () => void;
23+
private readonly options: MermaidOptions;
2324

2425
constructor(
2526
node: Node,
@@ -35,6 +36,7 @@ export class WMermaidNodeView implements NodeView {
3536
this.view = view;
3637
this.getPos = getPos;
3738
this.loadRuntimeScript = loadRuntimeScript;
39+
this.options = opts;
3840
this.initializeMermaid();
3941
this.renderItem = getReactRendererFromState(view.state).createItem(
4042
'mermaid-view',
@@ -121,6 +123,7 @@ export class WMermaidNodeView implements NodeView {
121123
node={this.node}
122124
getMermaidInstance={this.getMermaidInstance}
123125
getPos={this.getPos}
126+
options={this.options}
124127
/>
125128
</Portal>
126129
);

src/extensions/additional/Mermaid/index.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,27 @@ import {MermaidSpecs} from './MermaidSpecs';
55
import {MermaidAction} from './MermaidSpecs/const';
66
import {addMermaid} from './actions';
77

8-
export type MermaidOptions = {loadRuntimeScript: () => void};
8+
export type MermaidOptions = {
9+
loadRuntimeScript: () => void;
10+
autoSave?: {
11+
enabled: boolean;
12+
delay?: number;
13+
};
14+
};
915

10-
export const Mermaid: ExtensionAuto<MermaidOptions> = (builder, {loadRuntimeScript}) => {
16+
export const Mermaid: ExtensionAuto<MermaidOptions> = (builder, options) => {
1117
builder.use(MermaidSpecs, {
12-
nodeView: MermaidNodeViewFactory({loadRuntimeScript}),
18+
nodeView: MermaidNodeViewFactory(options),
1319
});
1420

1521
builder.addAction(MermaidAction, () => addMermaid);
1622
};
1723

1824
const MermaidNodeViewFactory: (
1925
opts: MermaidOptions,
20-
) => (deps: ExtensionDeps) => NodeViewConstructor =
21-
({loadRuntimeScript}) =>
22-
() =>
23-
(node, view, getPos) => {
24-
return new WMermaidNodeView(node, view, getPos, {loadRuntimeScript});
25-
};
26+
) => (deps: ExtensionDeps) => NodeViewConstructor = (options) => () => (node, view, getPos) => {
27+
return new WMermaidNodeView(node, view, getPos, options);
28+
};
2629

2730
declare global {
2831
namespace WysiwygEditor {

src/extensions/additional/YfmHtmlBlock/YfmHtmlBlockNodeView/YfmHtmlBlockView.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {SharedStateKey} from 'src/extensions/behavior/SharedState';
1212
import {TextAreaFixed as TextArea} from 'src/forms/TextInput';
1313
import {i18n} from 'src/i18n/common';
1414
import {debounce} from 'src/lodash';
15-
import {useBooleanState, useElementState} from 'src/react-utils/hooks';
15+
import {useAutoSave, useBooleanState, useElementState} from 'src/react-utils/hooks';
1616
import {useSharedEditingState} from 'src/react-utils/useSharedEditingState';
1717
import {removeNode} from 'src/utils/remove-node';
1818

@@ -194,8 +194,14 @@ const CodeEditMode: React.FC<{
194194
initialText: string;
195195
onSave: (v: string) => void;
196196
onCancel: () => void;
197-
}> = ({initialText, onSave, onCancel}) => {
198-
const [text, setText] = useState(initialText || '\n');
197+
options: YfmHtmlBlockOptions;
198+
}> = ({initialText, onSave, onCancel, options: {autoSave}}) => {
199+
const {value, handleChange, handleManualSave, isSaveDisabled} = useAutoSave({
200+
initialValue: initialText || '\n',
201+
onSave,
202+
onClose: onCancel,
203+
autoSave,
204+
});
199205

200206
return (
201207
<div className={b({editing: true})}>
@@ -204,10 +210,8 @@ const CodeEditMode: React.FC<{
204210
controlProps={{
205211
className: STOP_EVENT_CLASSNAME,
206212
}}
207-
value={text}
208-
onUpdate={(v) => {
209-
setText(v);
210-
}}
213+
value={value}
214+
onUpdate={handleChange}
211215
autoFocus
212216
/>
213217

@@ -216,7 +220,11 @@ const CodeEditMode: React.FC<{
216220
<Button onClick={onCancel} view={'flat'}>
217221
<span className={STOP_EVENT_CLASSNAME}>{i18n('cancel')}</span>
218222
</Button>
219-
<Button onClick={() => onSave(text)} view={'action'}>
223+
<Button
224+
onClick={handleManualSave}
225+
view={'action'}
226+
disabled={isSaveDisabled}
227+
>
220228
<span className={STOP_EVENT_CLASSNAME}>{i18n('save')}</span>
221229
</Button>
222230
</div>
@@ -232,13 +240,8 @@ export const YfmHtmlBlockView: React.FC<{
232240
onChange: (attrs: {[YfmHtmlBlockConsts.NodeAttrs.srcdoc]: string}) => void;
233241
options: YfmHtmlBlockOptions;
234242
view: EditorView;
235-
}> = ({
236-
onChange,
237-
node,
238-
getPos,
239-
view,
240-
options: {useConfig, sanitize, styles, baseTarget = '_parent', head: headContent = ''},
241-
}) => {
243+
}> = ({onChange, node, getPos, view, options}) => {
244+
const {useConfig, sanitize, styles, baseTarget = '_parent', head: headContent = ''} = options;
242245
const entityId: string = node.attrs[YfmHtmlBlockConsts.NodeAttrs.EntityId];
243246
const entityKey = useMemo(
244247
() => SharedStateKey.define<YfmHtmlBlockEntitySharedState>({name: entityId}),
@@ -258,8 +261,8 @@ export const YfmHtmlBlockView: React.FC<{
258261
onCancel={unsetEditing}
259262
onSave={(v) => {
260263
onChange({[YfmHtmlBlockConsts.NodeAttrs.srcdoc]: v});
261-
unsetEditing();
262264
}}
265+
options={options}
263266
/>
264267
);
265268
}

src/extensions/additional/YfmHtmlBlock/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import {addYfmHtmlBlock} from './actions';
1111
export interface YfmHtmlBlockOptions
1212
extends Omit<PluginOptions, 'runtimeJsPath' | 'containerClasses' | 'bundle' | 'embeddingMode'> {
1313
useConfig?: () => IHTMLIFrameElementConfig | undefined;
14+
autoSave?: {
15+
enabled: boolean;
16+
delay?: number; // по умолчанию 1000ms
17+
};
1418
}
1519

1620
export const YfmHtmlBlock: ExtensionAuto<YfmHtmlBlockOptions> = (

src/react-utils/hooks.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,10 @@ export function useDebounce<Fn extends AnyFunction>(cb: Fn, wait: number) {
5959

6060
return debouncedFn;
6161
}
62+
63+
export {
64+
useAutoSave,
65+
type AutoSaveOptions,
66+
type UseAutoSaveProps,
67+
type UseAutoSaveReturn,
68+
} from './hooks/useAutoSave';

0 commit comments

Comments
 (0)