@@ -15,10 +15,10 @@ import type {ParseInsertedUrlAsImage} from '../../bundle';
1515import type { EventMap } from '../../bundle/Editor' ;
1616import { ActionName } from '../../bundle/config/action-names' ;
1717import type { ReactRenderStorage } from '../../extensions' ;
18- import { DataTransferType } from '../../extensions/behavior/Clipboard/utils' ;
1918import { logger } from '../../logger' ;
2019import { Action as A , formatter as f } from '../../shortcuts' ;
2120import type { Receiver } from '../../utils' ;
21+ import { DataTransferType , shouldSkipHtmlConversion } from '../../utils/clipboard' ;
2222import type { DirectiveSyntaxContext } from '../../utils/directive' ;
2323import {
2424 insertImages ,
@@ -42,6 +42,7 @@ import {
4242import { DirectiveSyntaxFacet } from './directive-facet' ;
4343import { type FileUploadHandler , FileUploadHandlerFacet } from './files-upload-facet' ;
4444import { gravityHighlightStyle , gravityTheme } from './gravity' ;
45+ import { MarkdownConverter } from './html-to-markdown/converters' ;
4546import { PairingCharactersExtension } from './pairing-chars' ;
4647import { ReactRendererFacet } from './react-facet' ;
4748import { SearchPanelPlugin } from './search-plugin/plugin' ;
@@ -61,6 +62,7 @@ export type CreateCodemirrorParams = {
6162 onScroll : ( event : Event ) => void ;
6263 reactRenderer : ReactRenderStorage ;
6364 uploadHandler ?: FileUploadHandler ;
65+ parseHtmlOnPaste ?: boolean ;
6466 parseInsertedUrlAsImage ?: ParseInsertedUrlAsImage ;
6567 needImageDimensions ?: boolean ;
6668 enableNewImageSizeCalculation ?: boolean ;
@@ -91,6 +93,7 @@ export function createCodemirror(params: CreateCodemirrorParams) {
9193 extensions : extraExtensions ,
9294 placeholder : placeholderContent ,
9395 autocompletion : autocompletionConfig ,
96+ parseHtmlOnPaste,
9497 parseInsertedUrlAsImage,
9598 directiveSyntax,
9699 } = params ;
@@ -157,7 +160,47 @@ export function createCodemirror(params: CreateCodemirrorParams) {
157160 onScroll ( event ) ;
158161 } ,
159162 paste ( event , editor ) {
160- if ( event . clipboardData && parseInsertedUrlAsImage ) {
163+ if ( ! event . clipboardData ) return ;
164+
165+ // if clipboard contains YFM content - avoid any meddling with pasted content
166+ // since text/yfm will contain valid markdown
167+ const yfmContent = event . clipboardData . getData ( DataTransferType . Yfm ) ;
168+ if ( yfmContent ) {
169+ event . preventDefault ( ) ;
170+ editor . dispatch ( editor . state . replaceSelection ( yfmContent ) ) ;
171+ return ;
172+ }
173+
174+ // checking if a copy buffer content is suitable for convertion
175+ const shouldSkipHtml = shouldSkipHtmlConversion ( event . clipboardData ) ;
176+
177+ // if we have text/html inside copy/paste buffer
178+ const htmlContent = event . clipboardData . getData ( DataTransferType . Html ) ;
179+ // if we pasting markdown from VsCode we need skip html transformation
180+ if ( htmlContent && parseHtmlOnPaste && ! shouldSkipHtml ) {
181+ let parsedMarkdownMarkup : string | undefined ;
182+ try {
183+ const parser = new DOMParser ( ) ;
184+ const htmlDoc = parser . parseFromString ( htmlContent , 'text/html' ) ;
185+
186+ const converter = new MarkdownConverter ( ) ;
187+ parsedMarkdownMarkup = converter . processNode ( htmlDoc . body ) . trim ( ) ;
188+ } catch ( e ) {
189+ // The code is pretty new and there might be random issues we haven't caught yet,
190+ // especially with invalid HTML or weird DOM parsing errors.
191+ // If something goes wrong, I just want to fall back to the "default pasting"
192+ // rather than break the entire experience for the user.
193+ logger . error ( e ) ;
194+ }
195+
196+ if ( parsedMarkdownMarkup !== undefined ) {
197+ event . preventDefault ( ) ;
198+ editor . dispatch ( editor . state . replaceSelection ( parsedMarkdownMarkup ) ) ;
199+ return ;
200+ }
201+ }
202+
203+ if ( parseInsertedUrlAsImage ) {
161204 const { imageUrl, title} =
162205 parseInsertedUrlAsImage (
163206 event . clipboardData . getData ( DataTransferType . Text ) ?? '' ,
0 commit comments