diff --git a/packages/editor/src/extensions/markdown/Link/paste-plugin.test.ts b/packages/editor/src/extensions/markdown/Link/paste-plugin.test.ts index 7f27f911..de7e8a64 100644 --- a/packages/editor/src/extensions/markdown/Link/paste-plugin.test.ts +++ b/packages/editor/src/extensions/markdown/Link/paste-plugin.test.ts @@ -27,10 +27,11 @@ const { }).build(); plugins.unshift(LoggerFacet.of(logger)); -const {doc, p, lnk} = builders<'doc' | 'p' | 'lnk'>(schema, { +const {doc, p, lnk, lnkYa} = builders<'doc' | 'p' | 'lnk' | 'lnkYa'>(schema, { doc: {nodeType: BaseNode.Doc}, p: {nodeType: BaseNode.Paragraph}, lnk: {markType: linkMarkName, [LinkAttr.Href]: 'http://example.com?'}, + lnkYa: {markType: linkMarkName, [LinkAttr.Href]: 'http://ya.ru'}, }); const {same} = createMarkupChecker({parser, serializer}); @@ -41,6 +42,20 @@ describe('link paste plugin', () => { expect(match?.[0]?.raw).toBe('http://example.com'); }); + it('pastes bare hostname: text without scheme, href with http', () => { + const startDoc = doc(p('')); + const state = EditorState.create({ + schema, + doc: startDoc, + selection: TextSelection.create(startDoc, startDoc.tag.a), + plugins, + }); + const view = new EditorView(null, {state}); + dispatchPasteEvent(view, {'text/plain': 'ya.ru'}); + expect(view.state.doc).toMatchNode(doc(p(lnkYa('ya.ru')))); + same('[ya.ru](http://ya.ru)', view.state.doc); + }); + it('pastes url ending with question mark as link for selected text', () => { const startDoc = doc(p('test text')); const state = EditorState.create({ diff --git a/packages/editor/src/extensions/markdown/Link/paste-plugin.ts b/packages/editor/src/extensions/markdown/Link/paste-plugin.ts index 41a12848..3b12bad2 100644 --- a/packages/editor/src/extensions/markdown/Link/paste-plugin.ts +++ b/packages/editor/src/extensions/markdown/Link/paste-plugin.ts @@ -7,6 +7,8 @@ import {imageType} from '../Image'; import {LinkAttr, linkType} from './LinkSpecs'; +type PastedLink = {href: string; label: string}; + export function linkPasteEnhance({markupParser: parser}: ExtensionDeps) { return new Plugin({ props: { @@ -27,15 +29,15 @@ export function linkPasteEnhance({markupParser: parser}: ExtensionDeps) { ) { const {$from, $to} = sel; if ($from.pos === $to.pos) { - const url = getUrl(e.clipboardData, parser); - if (url) { + const pasted = getPastedLink(e.clipboardData, parser); + if (pasted) { const linkMarkType = linkType(state.schema); tr = state.tr.replaceSelectionWith( - state.schema.text(url, [ + state.schema.text(pasted.label, [ ...$from .marks() .filter((mark) => mark.type !== linkMarkType), - linkMarkType.create({[LinkAttr.Href]: url}), + linkMarkType.create({[LinkAttr.Href]: pasted.href}), ]), false, ); @@ -44,13 +46,13 @@ export function linkPasteEnhance({markupParser: parser}: ExtensionDeps) { }); } } else if ($from.sameParent($to)) { - const url = getUrl(e.clipboardData, parser); - if (url) { + const pasted = getPastedLink(e.clipboardData, parser); + if (pasted) { tr = state.tr.addMark( $from.pos, $to.pos, linkType(state.schema).create({ - [LinkAttr.Href]: url, + [LinkAttr.Href]: pasted.href, }), ); tr.setSelection(TextSelection.create(tr.doc, $to.pos)); @@ -74,17 +76,35 @@ export function linkPasteEnhance({markupParser: parser}: ExtensionDeps) { }); } -function getUrl(data: DataTransfer | null, parser: Parser): string | null { +function getPastedLink(data: DataTransfer | null, parser: Parser): PastedLink | null { if (!data || data.types.includes(DataTransferType.Yfm)) return null; - if (isIosSafariShare(data)) return data.getData(DataTransferType.UriList); + if (isIosSafariShare(data)) { + const href = data.getData(DataTransferType.UriList); + if (!href) { + return null; + } + + const trimmed = href.trim(); + return {href: trimmed, label: trimmed}; + } // TODO: should we process HTML here? const text = data.getData(DataTransferType.Text); const match = parser.matchLinks(text); if (match?.[0]) { - const {raw, url} = match[0]; - if (raw === text) return url; + const m = match[0]; + const {raw} = m; + if (raw === text) { + const href = parser.normalizeLink(m.schema ? text : m.url); + if (!parser.validateLink(href)) { + return null; + } + + const label = m.schema ? text : m.raw; + return {href, label}; + } if (text.endsWith('?') && raw + '?' === text && parser.validateLink(text)) { - return parser.normalizeLink(text); + const href = parser.normalizeLink(text); + return {href, label: text}; } } return null;