Skip to content

Commit 93df072

Browse files
committed
Merge branch 'main' of https://github.com/dpolevodin/markdown-editor into fix/tooltip-hidden-under-toolbar
2 parents 32ab6c3 + 2bfcd5d commit 93df072

File tree

81 files changed

+1970
-913
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1970
-913
lines changed

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
# Changelog
22

3+
## [15.2.0](https://github.com/gravity-ui/markdown-editor/compare/v15.1.0...v15.2.0) (2025-02-21)
4+
5+
6+
### Features
7+
8+
* add more event logs ([#650](https://github.com/gravity-ui/markdown-editor/issues/650)) ([9ab291e](https://github.com/gravity-ui/markdown-editor/commit/9ab291eee3c35c618c7acd45d161d456d11f2e11))
9+
* new logger implementation ([#646](https://github.com/gravity-ui/markdown-editor/issues/646)) ([c0bf2ca](https://github.com/gravity-ui/markdown-editor/commit/c0bf2cafef236dfca80ff9bc81c02c200bbff0db))
10+
11+
## [15.1.0](https://github.com/gravity-ui/markdown-editor/compare/v15.0.2...v15.1.0) (2025-02-19)
12+
13+
14+
### Features
15+
16+
* **ImgSize:** added ability to render custom form in image widget ([#639](https://github.com/gravity-ui/markdown-editor/issues/639)) ([b529453](https://github.com/gravity-ui/markdown-editor/commit/b52945349472656eb0f21c6e6b3f93919126987d))
17+
18+
19+
### Bug Fixes
20+
21+
* **ImgSize:** support enableNewImageSizeCalculation in ImageWidget ([#641](https://github.com/gravity-ui/markdown-editor/issues/641)) ([f6a7899](https://github.com/gravity-ui/markdown-editor/commit/f6a7899332adeced9e7bc87447b0ee5a5c19bf7a))
22+
* **YfmCut:** fixed open state styles for nested cuts ([#643](https://github.com/gravity-ui/markdown-editor/issues/643)) ([25089b3](https://github.com/gravity-ui/markdown-editor/commit/25089b312fddb6486b9e1921bbfa9cdfa3b541ea))
23+
24+
## [15.0.2](https://github.com/gravity-ui/markdown-editor/compare/v15.0.1...v15.0.2) (2025-02-17)
25+
26+
27+
### Bug Fixes
28+
29+
* **bundle:** fixed YfmTable serialization inside quotes at any nesting level ([#635](https://github.com/gravity-ui/markdown-editor/issues/635)) ([52b421c](https://github.com/gravity-ui/markdown-editor/commit/52b421c3eeb847685a2c5d8ce113814a6a0cad6c))
30+
* **ImagePaste:** make image paste priority higher than link to make image link paste working ([#634](https://github.com/gravity-ui/markdown-editor/issues/634)) ([9b98e40](https://github.com/gravity-ui/markdown-editor/commit/9b98e401ccfdf9b9b6617b489e9dbd1e36c3c0c7))
31+
* **view:** updated cut open logic to use attributes instead of classes due to extension update ([#628](https://github.com/gravity-ui/markdown-editor/issues/628)) ([0985395](https://github.com/gravity-ui/markdown-editor/commit/0985395c0c6586ac9687e7a36356a0123c630e2d))
32+
333
## [15.0.1](https://github.com/gravity-ui/markdown-editor/compare/v15.0.0...v15.0.1) (2025-02-12)
434

535

demo/components/Playground.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import {type CSSProperties, memo, useCallback, useEffect, useState} from 'react';
1+
import {type CSSProperties, memo, useCallback, useEffect, useMemo, useState} from 'react';
22

33
import {defaultOptions} from '@diplodoc/transform/lib/sanitize';
44
import {Button, DropdownMenu} from '@gravity-ui/uikit';
55

66
import {
77
type DirectiveSyntaxValue,
88
type FileUploadHandler,
9+
Logger2,
910
type MarkdownEditorMode,
1011
MarkdownEditorView,
1112
type MarkdownEditorViewProps,
@@ -29,6 +30,7 @@ import {getSanitizeYfmHtmlBlock} from '../../src/extensions/additional/YfmHtmlBl
2930
import type {CodeEditor} from '../../src/markup';
3031
import type {ToolbarsPreset} from '../../src/modules/toolbars/types';
3132
import {getPlugins} from '../defaults/md-plugins';
33+
import {useLogs} from '../hooks/useLogs';
3234
import useYfmHtmlBlockStyles from '../hooks/useYfmHtmlBlockStyles';
3335
import {randomDelay} from '../utils/delay';
3436
import {parseInsertedUrlAsImage} from '../utils/imageUrl';
@@ -86,9 +88,12 @@ export type PlaygroundProps = {
8688
>;
8789

8890
logger.setLogger({
89-
metrics: console.info,
90-
action: (data) => console.info(`Action: ${data.action}`, data),
91-
...console,
91+
log: (...data) => console.log('[Deprecated logger]', ...data),
92+
info: (...data) => console.info('[Deprecated logger]', ...data),
93+
warn: (...data) => console.warn('[Deprecated logger]', ...data),
94+
error: (...data) => console.error('[Deprecated logger]', ...data),
95+
metrics: (...data) => console.info('[Deprecated logger]', ...data),
96+
action: (data) => console.info(`[Deprecated logger] Action: ${data.action}`, data),
9297
});
9398

9499
export const Playground = memo<PlaygroundProps>((props) => {
@@ -142,8 +147,12 @@ export const Playground = memo<PlaygroundProps>((props) => {
142147
[sanitizeHtml],
143148
);
144149

150+
const logger = useMemo(() => new Logger2().nested({env: 'playground'}), []);
151+
useLogs(logger);
152+
145153
const mdEditor = useMarkdownEditor(
146154
{
155+
logger,
147156
preset: 'full',
148157
wysiwygConfig: {
149158
placeholderOptions: placeholderOptions,

demo/hooks/useLogs.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {useMemo} from 'react';
2+
3+
import type {Logger2} from '../../src';
4+
5+
export function useLogs(logger: Logger2.LogReceiver) {
6+
useMemo(() => {
7+
logger.on('log', (data) => console.log('Log:', data.msg, data));
8+
logger.on('warn', (data) => console.warn('Warn:', data.msg, data));
9+
logger.on('error', (data) => console.error('Error:', data.error, data));
10+
logger.on('event', (data) => console.info('Event:', data.event, data));
11+
logger.on('action', (data) => console.info('Action:', data.action, data));
12+
logger.on('metrics', (data) => console.info('Metrics:', data.component, data));
13+
}, [logger]);
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type {StoryObj} from '@storybook/react';
2+
3+
import {ImageCustomFormDemo as component} from './ImageCustomForm';
4+
5+
export const Story: StoryObj<typeof component> = {
6+
args: {},
7+
};
8+
Story.storyName = 'Custom Image Widget';
9+
10+
export default {
11+
title: 'Examples / Custom Image Widget',
12+
component,
13+
};
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import {memo} from 'react';
2+
3+
import {FilePlus} from '@gravity-ui/icons';
4+
import {Button, FilePreview, Icon, useFileInput} from '@gravity-ui/uikit';
5+
6+
import {type ImgSizeOptions, MarkdownEditorView, useMarkdownEditor} from '../../../../src';
7+
import {PlaygroundLayout} from '../../../components/PlaygroundLayout';
8+
import {randomDelay} from '../../../utils/delay';
9+
10+
type RenderImageWidgetFormFn = NonNullable<ImgSizeOptions['renderImageWidgetForm']>;
11+
type RenderImageWidgetFormProps = Parameters<RenderImageWidgetFormFn>[0];
12+
13+
const ImageForm = memo<RenderImageWidgetFormProps>(function ImageForm({onSubmit, onAttach}) {
14+
const {controlProps, triggerProps} = useFileInput({onUpdate: onAttach});
15+
16+
return (
17+
<div
18+
style={{
19+
display: 'grid',
20+
padding: '12px 16px',
21+
justifyItems: 'center',
22+
alignItems: 'center',
23+
gridTemplateColumns: 'repeat(3, 1fr)',
24+
gridGap: 8,
25+
}}
26+
>
27+
{/* Rendering previews for images */}
28+
{getImages().map(({id, url}) => {
29+
const name = id;
30+
return (
31+
<FilePreview
32+
key={id}
33+
imageSrc={url}
34+
file={{name, type: 'image/png'} as File}
35+
onClick={() => onSubmit({url, name, alt: '', width: 320, height: 320})}
36+
/>
37+
);
38+
})}
39+
40+
{/* Rendering a button for uploading images from device */}
41+
<Button
42+
size="xl"
43+
view="flat-secondary"
44+
width="max"
45+
style={
46+
{
47+
'--g-button-height': '100%',
48+
'--g-button-border-radius': '4px',
49+
} as React.CSSProperties
50+
}
51+
{...triggerProps}
52+
>
53+
<Icon data={FilePlus} width={36} height={36} />
54+
</Button>
55+
<input type="file" multiple={false} accept="image/*" {...controlProps} />
56+
</div>
57+
);
58+
});
59+
60+
export const ImageCustomFormDemo = memo(() => {
61+
const editor = useMarkdownEditor({
62+
initial: {
63+
mode: 'wysiwyg',
64+
markup: '&nbsp;\n\nClick the `Image` action on the toolbar or select it from the slash `/` menu.',
65+
},
66+
handlers: {
67+
uploadFile: async (file) => {
68+
await randomDelay(1000, 3000);
69+
return {url: URL.createObjectURL(file)};
70+
},
71+
},
72+
wysiwygConfig: {
73+
extensionOptions: {
74+
imgSize: {
75+
// pass a function to render custom form in the image widget
76+
renderImageWidgetForm: (props) => <ImageForm {...props} />,
77+
},
78+
},
79+
},
80+
});
81+
82+
return (
83+
<PlaygroundLayout
84+
editor={editor}
85+
view={({className}) => (
86+
<MarkdownEditorView
87+
autofocus
88+
stickyToolbar
89+
settingsVisible
90+
editor={editor}
91+
className={className}
92+
/>
93+
)}
94+
/>
95+
);
96+
});
97+
98+
ImageCustomFormDemo.displayName = 'ImageCustomFormDemo';
99+
100+
type ImageItem = {
101+
id: string;
102+
url: string;
103+
};
104+
105+
function getImages(): ImageItem[] {
106+
return [
107+
{
108+
id: 'low',
109+
url: 'https://avatars.mds.yandex.net/get-shedevrum/14441318/img_1c3b6b42eee211efad66ea120268400c/orig',
110+
},
111+
{
112+
id: 'unsatisfactory',
113+
url: 'https://avatars.mds.yandex.net/get-shedevrum/15170052/img_7ba17345eee211efa1d9c61932b2752e/orig',
114+
},
115+
{
116+
id: 'good',
117+
url: 'https://avatars.mds.yandex.net/get-shedevrum/16106905/img_26cb5157eee311efb16cf600b5cb441c/orig',
118+
},
119+
{
120+
id: 'outstanding',
121+
url: 'https://avatars.mds.yandex.net/get-shedevrum/14441318/img_834891dceee111ef80908e055cc35a5d/orig',
122+
},
123+
{
124+
id: 'amazing',
125+
url: 'https://avatars.mds.yandex.net/get-shedevrum/15320627/img_d62906eeeee211ef9e61968caa0a2b17/orig',
126+
},
127+
];
128+
}

demo/stories/ghost/Ghost.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import cloneDeep from 'lodash/cloneDeep';
22

3-
import {MarkdownEditorView, logger, markupToolbarConfigs, useMarkdownEditor} from '../../../src';
3+
import {MarkdownEditorView, markupToolbarConfigs, useMarkdownEditor} from '../../../src';
44
import {PlaygroundLayout} from '../../components/PlaygroundLayout';
5+
import {useLogs} from '../../hooks/useLogs';
56

67
import {initialMdContent} from './content';
78
import {ghostPopupExtension, ghostPopupToolbarItem} from './ghostExtension';
89

9-
logger.setLogger({
10-
metrics: console.info,
11-
action: (data) => console.info(`Action: ${data.action}`, data),
12-
...console,
13-
});
14-
1510
const mToolbarConfig = cloneDeep(markupToolbarConfigs.mToolbarConfig);
1611
mToolbarConfig.unshift([ghostPopupToolbarItem]);
1712

@@ -21,6 +16,8 @@ export const Ghost = () => {
2116
markupConfig: {extensions: [ghostPopupExtension]},
2217
});
2318

19+
useLogs(editor.logger);
20+
2421
return (
2522
<PlaygroundLayout
2623
editor={editor}

demo/stories/gpt/GPT.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import cloneDeep from 'lodash/cloneDeep';
55
import {
66
MarkdownEditorView,
77
gptExtension,
8-
logger,
98
mGptExtension,
109
mGptToolbarItem,
1110
markupToolbarConfigs,
@@ -14,16 +13,11 @@ import {
1413
wysiwygToolbarConfigs,
1514
} from '../../../src';
1615
import {PlaygroundLayout} from '../../components/PlaygroundLayout';
16+
import {useLogs} from '../../hooks/useLogs';
1717

1818
import {initialMdContent} from './content';
1919
import {gptWidgetProps} from './gptWidgetOptions';
2020

21-
logger.setLogger({
22-
metrics: console.info,
23-
action: (data) => console.info(`Action: ${data.action}`, data),
24-
...console,
25-
});
26-
2721
const wToolbarConfig = cloneDeep(wysiwygToolbarConfigs.wToolbarConfig);
2822
wToolbarConfig.unshift([wGptItemData]);
2923

@@ -62,6 +56,8 @@ export const GPT = memo(() => {
6256
},
6357
});
6458

59+
useLogs(editor.logger);
60+
6561
return (
6662
<PlaygroundLayout
6763
editor={editor}

demo/stories/presets/Preset.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
MarkdownEditorView,
77
type MarkupString,
88
type RenderPreview,
9-
logger,
109
useMarkdownEditor,
1110
} from '../../../src';
1211
import type {ToolbarsPreset} from '../../../src/modules/toolbars/types';
@@ -17,6 +16,7 @@ import {WysiwygSelection} from '../../components/PMSelection';
1716
import {WysiwygDevTools} from '../../components/ProseMirrorDevTools';
1817
import {SplitModePreview} from '../../components/SplitModePreview';
1918
import {plugins} from '../../defaults/md-plugins';
19+
import {useLogs} from '../../hooks/useLogs';
2020
import {block} from '../../utils/cn';
2121
import {randomDelay} from '../../utils/delay';
2222
import {parseInsertedUrlAsImage} from '../../utils/imageUrl';
@@ -43,12 +43,6 @@ export type PresetDemoProps = {
4343
toolbarsPreset?: ToolbarsPreset;
4444
};
4545

46-
logger.setLogger({
47-
metrics: console.info,
48-
action: (data) => console.info(`Action: ${data.action}`, data),
49-
...console,
50-
});
51-
5246
export const Preset = memo<PresetDemoProps>((props) => {
5347
const {
5448
preset,
@@ -109,6 +103,8 @@ export const Preset = memo<PresetDemoProps>((props) => {
109103
},
110104
});
111105

106+
useLogs(mdEditor.logger);
107+
112108
useEffect(() => {
113109
function onChange() {
114110
setMdRaw(mdEditor.getValue());

demo/utils/imageUrl.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
const knownImageHostsRegexString = '(jing|avatars)';
2-
1+
const knownImageHostsRegexString = '(avatars)';
32
const supportedImageExtensionsRegexString = '\\.(jpe?g|png|svgz?|gif|webp)';
3+
const videoExtensions = '\\.(mp4|ogv|ogm|ogg|webm)';
44

55
export const imageUrlRegex = new RegExp(
6-
`^https?:\\/\\/(\\S*?${supportedImageExtensionsRegexString}|${knownImageHostsRegexString}\\S+)$`,
6+
`^(?!.*${videoExtensions})https?:\\/\\/(\\S*?${supportedImageExtensionsRegexString}|${knownImageHostsRegexString}\\S+)$`,
77
);
8-
98
export const imageNameRegex = new RegExp(`\\/([^/]*?)(${supportedImageExtensionsRegexString})?$`);
10-
119
export const parseInsertedUrlAsImage = (text: string) =>
12-
imageUrlRegex.test(text) ? {imageUrl: text, title: text.match(imageNameRegex)?.[1]} : null;
10+
imageUrlRegex.test(text)
11+
? {
12+
imageUrl: text,
13+
title: text.match(imageNameRegex)?.[1],
14+
}
15+
: null;

0 commit comments

Comments
 (0)