diff --git a/.changeset/quiet-months-argue.md b/.changeset/quiet-months-argue.md
new file mode 100644
index 00000000000..9db967f66fa
--- /dev/null
+++ b/.changeset/quiet-months-argue.md
@@ -0,0 +1,5 @@
+---
+'@keystone-6/fields-document': patch
+---
+
+Adds support for pasting a url onto text to create a link
diff --git a/packages/fields-document/src/DocumentEditor/pasting/index.ts b/packages/fields-document/src/DocumentEditor/pasting/index.ts
index e5e22b500c1..3756787133e 100644
--- a/packages/fields-document/src/DocumentEditor/pasting/index.ts
+++ b/packages/fields-document/src/DocumentEditor/pasting/index.ts
@@ -1,8 +1,11 @@
-import { Descendant, Editor, Transforms } from 'slate';
+import { Descendant, Editor, Transforms, Range } from 'slate';
+import { isValidURL } from '../isValidURL';
import { insertNodesButReplaceIfSelectionIsAtEmptyParagraphOrHeading } from '../utils';
import { deserializeHTML } from './html';
import { deserializeMarkdown } from './markdown';
+const urlPattern = /https?:\/\//;
+
function insertFragmentButDifferent(editor: Editor, nodes: Descendant[]) {
if (Editor.isBlock(editor, nodes[0])) {
insertNodesButReplaceIfSelectionIsAtEmptyParagraphOrHeading(editor, nodes);
@@ -63,6 +66,29 @@ export function withPasting(editor: Editor): Editor {
}
}
+ const plain = data.getData('text/plain');
+
+ if (
+ // isValidURL is a bit more permissive than a user might expect
+ // so for pasting, we'll constrain it to starting with https:// or http://
+ urlPattern.test(plain) &&
+ isValidURL(plain) &&
+ editor.selection &&
+ !Range.isCollapsed(editor.selection) &&
+ // we only want to turn the selected text into a link if the selection is within the same block
+ Editor.above(editor, {
+ match: node => Editor.isBlock(editor, node) && !Editor.isBlock(editor, node.children[0]),
+ }) &&
+ // and there is only text(potentially with marks) in the selection
+ // no other links or inline relationships
+ Editor.nodes(editor, {
+ match: node => Editor.isInline(editor, node),
+ }).next().done
+ ) {
+ Transforms.wrapNodes(editor, { type: 'link', href: plain, children: [] }, { split: true });
+ return;
+ }
+
const html = data.getData('text/html');
if (html) {
const fragment = deserializeHTML(html);
@@ -70,7 +96,6 @@ export function withPasting(editor: Editor): Editor {
return;
}
- const plain = data.getData('text/plain');
if (plain) {
const fragment = deserializeMarkdown(plain);
insertFragmentButDifferent(editor, fragment);
diff --git a/packages/fields-document/src/DocumentEditor/pasting/links.test.tsx b/packages/fields-document/src/DocumentEditor/pasting/links.test.tsx
new file mode 100644
index 00000000000..30fd97f57b2
--- /dev/null
+++ b/packages/fields-document/src/DocumentEditor/pasting/links.test.tsx
@@ -0,0 +1,132 @@
+/** @jest-environment jsdom */
+/** @jsxRuntime classic */
+/** @jsx jsx */
+import { Editor } from 'slate';
+import { makeEditor, jsx } from '../tests/utils';
+import { MyDataTransfer } from './data-transfer';
+
+function pasteText(editor: Editor, text: string) {
+ const data = new MyDataTransfer();
+ data.setData('text/plain', text);
+ editor.insertData(data);
+}
+
+test('pasting a url on some text wraps the text with a link', () => {
+ const editor = makeEditor(
+
+
+
+ blah
+ blah
+ blah
+
+
+
+ );
+ pasteText(editor, 'https://keystonejs.com');
+ expect(editor).toMatchInlineSnapshot(`
+
+
+
+ blah
+
+
+
+
+ blah
+
+
+
+
+ blah
+
+
+
+ `);
+});
+
+test('pasting a url on a selection spanning multiple blocks replaces the selection with the url', () => {
+ const editor = makeEditor(
+
+
+
+ start should still exist
+ blah blah
+
+
+
+
+ blah blah
+ end should still exist
+
+
+
+ );
+ pasteText(editor, 'https://keystonejs.com');
+ expect(editor).toMatchInlineSnapshot(`
+
+
+
+ start should still exist
+
+
+
+ https://keystonejs.com
+
+
+
+
+ end should still exist
+
+
+
+ `);
+});
+
+test('pasting a url on a selection with a link inside replaces the selection with the url', () => {
+ const editor = makeEditor(
+
+
+
+ start should still exist
+ should{' '}
+
+
+ be
+
+
+ replaced
+ end should still exist
+
+
+
+ );
+ pasteText(editor, 'https://keystonejs.com');
+ expect(editor).toMatchInlineSnapshot(`
+
+
+
+ start should still exist
+
+
+
+ https://keystonejs.com
+
+
+
+
+ end should still exist
+
+
+
+ `);
+});