Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions demo/components/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import {SplitModePreview} from './SplitModePreview';

const fileUploadHandler: FileUploadHandler = async (file) => {
console.info('[Playground] Uploading file: ' + file.name);

Check warning on line 46 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
await randomDelay(1000, 3000);
return {url: URL.createObjectURL(file)};
};
Expand Down Expand Up @@ -86,6 +86,7 @@
disableMarkdownItAttrs?: boolean;
markupParseHtmlOnPaste?: boolean;
style?: React.CSSProperties;
storyAdditionalControls?: Record<string, any>;

Check warning on line 89 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected any. Specify a different type
} & Pick<UseMarkdownEditorProps, 'experimental' | 'wysiwygConfig'> &
Pick<
MarkdownEditorViewProps,
Expand All @@ -98,12 +99,12 @@
>;

logger.setLogger({
log: (...data) => console.log('[Deprecated logger]', ...data),

Check warning on line 102 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
info: (...data) => console.info('[Deprecated logger]', ...data),

Check warning on line 103 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
warn: (...data) => console.warn('[Deprecated logger]', ...data),

Check warning on line 104 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
error: (...data) => console.error('[Deprecated logger]', ...data),

Check warning on line 105 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
metrics: (...data) => console.info('[Deprecated logger]', ...data),

Check warning on line 106 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
action: (data) => console.info(`[Deprecated logger] Action: ${data.action}`, data),

Check warning on line 107 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
});

export const Playground = memo<PlaygroundProps>((props) => {
Expand Down Expand Up @@ -141,6 +142,7 @@
disableMarkdownItAttrs,
markupParseHtmlOnPaste,
style,
storyAdditionalControls,
} = props;
const [editorMode, setEditorMode] = useState<MarkdownEditorMode>(initialEditor ?? 'wysiwyg');
const [mdRaw, setMdRaw] = useState<MarkupString>(initial || '');
Expand All @@ -150,7 +152,7 @@
}, [mdRaw]);

const renderPreview = useCallback<RenderPreview>(
({getValue, md, directiveSyntax}) => (

Check warning on line 155 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

'directiveSyntax' is already declared in the upper scope on line 140 column 9
<SplitModePreview
getValue={getValue}
allowHTML={md.html}
Expand All @@ -170,7 +172,7 @@
[sanitizeHtml, disabledHTMLBlockModes, disableMarkdownItAttrs],
);

const logger = useMemo(() => new Logger2().nested({env: 'playground'}), []);

Check warning on line 175 in demo/components/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

'logger' is already declared in the upper scope on line 28 column 5
useLogs(logger);

const mdEditor = useMarkdownEditor(
Expand Down Expand Up @@ -200,11 +202,20 @@
/* webpackChunkName: "mermaid-runtime" */ '@diplodoc/mermaid-extension/runtime'
);
},
autoSave: {
enabled: storyAdditionalControls?.mermaidAutoSaveEnabled ?? true,
delay: storyAdditionalControls?.mermaidAutoSaveDelay ?? 1000,
},
})
.use(FoldingHeading)
.use(YfmHtmlBlock, {
useConfig: useYfmHtmlBlockStyles,
sanitize: getSanitizeYfmHtmlBlock({options: defaultOptions}),
autoSave: {
enabled:
storyAdditionalControls?.yfmHtmlBlockAutoSaveEnabled ?? true,
delay: storyAdditionalControls?.yfmHtmlBlockAutoSaveDelay ?? 1000,
},
head: `
<base target="_blank" />
<style>
Expand Down
1 change: 1 addition & 0 deletions demo/components/PlaygroundMini.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type PlaygroundMiniProps = Pick<
| 'directiveSyntax'
| 'disabledHTMLBlockModes'
| 'disableMarkdownItAttrs'
| 'storyAdditionalControls'
> & {withDefaultInitialContent?: boolean};

export const PlaygroundMini = memo<PlaygroundMiniProps>(
Expand Down
16 changes: 14 additions & 2 deletions demo/stories/yfm/YFM.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ export const YfmTabs: Story = {

export const YfmHtmlBlock: Story = {
name: 'YFM HTML',
args: {initial: markup.yfmHtmlBlock},
args: {
initial: markup.yfmHtmlBlock,
storyAdditionalControls: {
yfmHtmlBlockAutoSaveEnabled: false,
yfmHtmlBlockAutoSaveDelay: 2000,
},
},
};

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

export const MermaidDiagram: Story = {
name: 'Mermaid diagram',
args: {initial: markup.mermaidDiagram},
args: {
initial: markup.mermaidDiagram,
storyAdditionalControls: {
mermaidAutoSaveEnabled: false,
mermaidAutoSaveDelay: 2000,
},
},
};
32 changes: 21 additions & 11 deletions src/extensions/additional/Mermaid/MermaidNodeView/MermaidView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import {useSharedEditingState} from 'src/react-utils/useSharedEditingState';
import {cn} from '../../../../classname';
import {TextAreaFixed as TextArea} from '../../../../forms/TextInput';
import {i18n} from '../../../../i18n/common';
import {useBooleanState, useElementState} from '../../../../react-utils';
import {useAutoSave, useBooleanState, useElementState} from '../../../../react-utils';
import {removeNode} from '../../../../utils';
import {MermaidConsts} from '../MermaidSpecs/const';
import type {MermaidOptions} from '../index';
import type {MermaidEntitySharedState} from '../types';

export const cnMermaid = cn('Mermaid');
Expand Down Expand Up @@ -68,22 +69,26 @@ const DiagramEditMode: React.FC<{
mermaidInstance: Mermaid | null;
onSave: (v: string) => void;
onCancel: () => void;
}> = ({initialText, onSave, onCancel, mermaidInstance}) => {
const [text, setText] = useState(initialText || '');
options: MermaidOptions;
}> = ({initialText, onSave, onCancel, mermaidInstance, options: {autoSave}}) => {
const {value, handleChange, handleManualSave, isSaveDisabled} = useAutoSave({
initialValue: initialText || '',
onSave,
onClose: onCancel,
autoSave,
});

return (
<div className={b()}>
<MermaidPreview mermaidInstance={mermaidInstance} text={text} />
<MermaidPreview mermaidInstance={mermaidInstance} text={value} />
<div className={b('Editor')}>
<div>
<TextArea
controlProps={{
className: STOP_EVENT_CLASSNAME,
}}
value={text}
onUpdate={(v) => {
setText(v);
}}
value={value}
onUpdate={handleChange}
autoFocus
/>
</div>
Expand All @@ -92,7 +97,11 @@ const DiagramEditMode: React.FC<{
<Button onClick={onCancel} view={'flat'}>
<span className={STOP_EVENT_CLASSNAME}>{i18n('cancel')}</span>
</Button>
<Button onClick={() => onSave(text)} view={'action'}>
<Button
onClick={handleManualSave}
view={'action'}
disabled={isSaveDisabled}
>
<span className={STOP_EVENT_CLASSNAME}>{i18n('save')}</span>
</Button>
</div>
Expand All @@ -108,7 +117,8 @@ export const MermaidView: React.FC<{
getMermaidInstance: () => Mermaid;
node: Node;
getPos: () => number | undefined;
}> = ({onChange, node, getPos, view, getMermaidInstance}) => {
options: MermaidOptions;
}> = ({onChange, node, getPos, view, getMermaidInstance, options}) => {
const enitityId: string = node.attrs[MermaidConsts.NodeAttrs.EntityId];
const entityKey = useMemo(
() => SharedStateKey.define<MermaidEntitySharedState>({name: enitityId}),
Expand Down Expand Up @@ -144,8 +154,8 @@ export const MermaidView: React.FC<{
onCancel={unsetEditing}
onSave={(v) => {
onChange({[MermaidConsts.NodeAttrs.content]: v});
unsetEditing();
}}
options={options}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class WMermaidNodeView implements NodeView {
private readonly getPos;
private readonly renderItem;
private readonly loadRuntimeScript: () => void;
private readonly options: MermaidOptions;

constructor(
node: Node,
Expand All @@ -35,6 +36,7 @@ export class WMermaidNodeView implements NodeView {
this.view = view;
this.getPos = getPos;
this.loadRuntimeScript = loadRuntimeScript;
this.options = opts;
this.initializeMermaid();
this.renderItem = getReactRendererFromState(view.state).createItem(
'mermaid-view',
Expand Down Expand Up @@ -121,6 +123,7 @@ export class WMermaidNodeView implements NodeView {
node={this.node}
getMermaidInstance={this.getMermaidInstance}
getPos={this.getPos}
options={this.options}
/>
</Portal>
);
Expand Down
21 changes: 12 additions & 9 deletions src/extensions/additional/Mermaid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,27 @@ import {MermaidSpecs} from './MermaidSpecs';
import {MermaidAction} from './MermaidSpecs/const';
import {addMermaid} from './actions';

export type MermaidOptions = {loadRuntimeScript: () => void};
export type MermaidOptions = {
loadRuntimeScript: () => void;
autoSave?: {
enabled: boolean;
delay?: number;
};
};

export const Mermaid: ExtensionAuto<MermaidOptions> = (builder, {loadRuntimeScript}) => {
export const Mermaid: ExtensionAuto<MermaidOptions> = (builder, options) => {
builder.use(MermaidSpecs, {
nodeView: MermaidNodeViewFactory({loadRuntimeScript}),
nodeView: MermaidNodeViewFactory(options),
});

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

const MermaidNodeViewFactory: (
opts: MermaidOptions,
) => (deps: ExtensionDeps) => NodeViewConstructor =
({loadRuntimeScript}) =>
() =>
(node, view, getPos) => {
return new WMermaidNodeView(node, view, getPos, {loadRuntimeScript});
};
) => (deps: ExtensionDeps) => NodeViewConstructor = (options) => () => (node, view, getPos) => {
return new WMermaidNodeView(node, view, getPos, options);
};

declare global {
namespace WysiwygEditor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {SharedStateKey} from 'src/extensions/behavior/SharedState';
import {TextAreaFixed as TextArea} from 'src/forms/TextInput';
import {i18n} from 'src/i18n/common';
import {debounce} from 'src/lodash';
import {useBooleanState, useElementState} from 'src/react-utils/hooks';
import {useAutoSave, useBooleanState, useElementState} from 'src/react-utils/hooks';
import {useSharedEditingState} from 'src/react-utils/useSharedEditingState';
import {removeNode} from 'src/utils/remove-node';

Expand Down Expand Up @@ -194,8 +194,14 @@ const CodeEditMode: React.FC<{
initialText: string;
onSave: (v: string) => void;
onCancel: () => void;
}> = ({initialText, onSave, onCancel}) => {
const [text, setText] = useState(initialText || '\n');
options: YfmHtmlBlockOptions;
}> = ({initialText, onSave, onCancel, options: {autoSave}}) => {
const {value, handleChange, handleManualSave, isSaveDisabled} = useAutoSave({
initialValue: initialText || '\n',
onSave,
onClose: onCancel,
autoSave,
});

return (
<div className={b({editing: true})}>
Expand All @@ -204,10 +210,8 @@ const CodeEditMode: React.FC<{
controlProps={{
className: STOP_EVENT_CLASSNAME,
}}
value={text}
onUpdate={(v) => {
setText(v);
}}
value={value}
onUpdate={handleChange}
autoFocus
/>

Expand All @@ -216,7 +220,11 @@ const CodeEditMode: React.FC<{
<Button onClick={onCancel} view={'flat'}>
<span className={STOP_EVENT_CLASSNAME}>{i18n('cancel')}</span>
</Button>
<Button onClick={() => onSave(text)} view={'action'}>
<Button
onClick={handleManualSave}
view={'action'}
disabled={isSaveDisabled}
>
<span className={STOP_EVENT_CLASSNAME}>{i18n('save')}</span>
</Button>
</div>
Expand All @@ -232,13 +240,8 @@ export const YfmHtmlBlockView: React.FC<{
onChange: (attrs: {[YfmHtmlBlockConsts.NodeAttrs.srcdoc]: string}) => void;
options: YfmHtmlBlockOptions;
view: EditorView;
}> = ({
onChange,
node,
getPos,
view,
options: {useConfig, sanitize, styles, baseTarget = '_parent', head: headContent = ''},
}) => {
}> = ({onChange, node, getPos, view, options}) => {
const {useConfig, sanitize, styles, baseTarget = '_parent', head: headContent = ''} = options;
const entityId: string = node.attrs[YfmHtmlBlockConsts.NodeAttrs.EntityId];
const entityKey = useMemo(
() => SharedStateKey.define<YfmHtmlBlockEntitySharedState>({name: entityId}),
Expand All @@ -258,8 +261,8 @@ export const YfmHtmlBlockView: React.FC<{
onCancel={unsetEditing}
onSave={(v) => {
onChange({[YfmHtmlBlockConsts.NodeAttrs.srcdoc]: v});
unsetEditing();
}}
options={options}
/>
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/extensions/additional/YfmHtmlBlock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {addYfmHtmlBlock} from './actions';
export interface YfmHtmlBlockOptions
extends Omit<PluginOptions, 'runtimeJsPath' | 'containerClasses' | 'bundle' | 'embeddingMode'> {
useConfig?: () => IHTMLIFrameElementConfig | undefined;
autoSave?: {
enabled: boolean;
delay?: number; // по умолчанию 1000ms
};
}

export const YfmHtmlBlock: ExtensionAuto<YfmHtmlBlockOptions> = (
Expand Down
7 changes: 7 additions & 0 deletions src/react-utils/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,10 @@ export function useDebounce<Fn extends AnyFunction>(cb: Fn, wait: number) {

return debouncedFn;
}

export {
useAutoSave,
type AutoSaveOptions,
type UseAutoSaveProps,
type UseAutoSaveReturn,
} from './hooks/useAutoSave';
Loading