Improved paste handling (#4474)
* Improved paste handling * Remove file
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { Slice } from "prosemirror-model";
|
||||
import { Selection } from "prosemirror-state";
|
||||
import { __parseFromClipboard } from "prosemirror-view";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import isMarkdown from "@shared/editor/lib/isMarkdown";
|
||||
import normalizePastedMarkdown from "@shared/editor/lib/markdown/normalize";
|
||||
import { light } from "@shared/styles/theme";
|
||||
import {
|
||||
getCurrentDateAsString,
|
||||
@@ -120,9 +124,12 @@ const EditableTitle = React.forwardRef(
|
||||
const handlePaste = React.useCallback(
|
||||
(event: React.ClipboardEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const text = event.clipboardData.getData("text/plain");
|
||||
const html = event.clipboardData.getData("text/html");
|
||||
const [firstLine, ...rest] = text.split(`\n`);
|
||||
const content = rest.join(`\n`).trim();
|
||||
|
||||
window.document.execCommand(
|
||||
"insertText",
|
||||
false,
|
||||
@@ -130,11 +137,37 @@ const EditableTitle = React.forwardRef(
|
||||
);
|
||||
|
||||
if (editor && content) {
|
||||
const { view, parser } = editor;
|
||||
const { view, pasteParser } = editor;
|
||||
let slice;
|
||||
|
||||
if (isMarkdown(text)) {
|
||||
const paste = pasteParser.parse(normalizePastedMarkdown(content));
|
||||
slice = paste.slice(0);
|
||||
} else {
|
||||
const defaultSlice = __parseFromClipboard(
|
||||
view,
|
||||
text,
|
||||
html,
|
||||
false,
|
||||
view.state.selection.$from
|
||||
);
|
||||
|
||||
// remove first node from slice
|
||||
slice = defaultSlice.content.firstChild
|
||||
? new Slice(
|
||||
defaultSlice.content.cut(
|
||||
defaultSlice.content.firstChild.nodeSize
|
||||
),
|
||||
defaultSlice.openStart,
|
||||
defaultSlice.openEnd
|
||||
)
|
||||
: defaultSlice;
|
||||
}
|
||||
|
||||
view.dispatch(
|
||||
view.state.tr
|
||||
.setSelection(Selection.atStart(view.state.doc))
|
||||
.insert(0, parser.parse(content))
|
||||
.replaceSelection(slice)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function isMarkdown(text: string): boolean {
|
||||
}
|
||||
|
||||
// list-ish
|
||||
const listItems = text.match(/^[\d-*].?\s\S+/gm);
|
||||
const listItems = text.match(/^([-*]|\d+.)\s\S+/gm);
|
||||
if (listItems && listItems.length > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
22
shared/editor/lib/markdown/normalize.ts
Normal file
22
shared/editor/lib/markdown/normalize.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Add support for additional syntax that users paste even though it isn't
|
||||
* supported by the markdown parser directly by massaging the text content.
|
||||
*
|
||||
* @param text The incoming pasted plain text
|
||||
*/
|
||||
export default function normalizePastedMarkdown(text: string): string {
|
||||
const CHECKBOX_REGEX = /^\s?(\[(X|\s|_|-)\]\s(.*)?)/gim;
|
||||
|
||||
// find checkboxes not contained in a list and wrap them in list items
|
||||
while (text.match(CHECKBOX_REGEX)) {
|
||||
text = text.replace(CHECKBOX_REGEX, (match) => `- ${match.trim()}`);
|
||||
}
|
||||
|
||||
// find multiple newlines and insert a hard break to ensure they are respected
|
||||
text = text.replace(/\n{3,}/g, "\n\n\\\n");
|
||||
|
||||
// find single newlines and insert an extra to ensure they are treated as paragraphs
|
||||
text = text.replace(/\b\n\b/g, "\n\n");
|
||||
|
||||
return text;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { isInTable } from "prosemirror-tables";
|
||||
import { isUrl } from "../../utils/urls";
|
||||
import Extension from "../lib/Extension";
|
||||
import isMarkdown from "../lib/isMarkdown";
|
||||
import normalizePastedMarkdown from "../lib/markdown/normalize";
|
||||
import isInCode from "../queries/isInCode";
|
||||
import { LANGUAGES } from "./Prism";
|
||||
|
||||
@@ -13,29 +14,6 @@ function isDropboxPaper(html: string): boolean {
|
||||
return html?.includes("usually-unique-id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add support for additional syntax that users paste even though it isn't
|
||||
* supported by the markdown parser directly by massaging the text content.
|
||||
*
|
||||
* @param text The incoming pasted plain text
|
||||
*/
|
||||
function normalizePastedMarkdown(text: string): string {
|
||||
const CHECKBOX_REGEX = /^\s?(\[(X|\s|_|-)\]\s(.*)?)/gim;
|
||||
|
||||
// find checkboxes not contained in a list and wrap them in list items
|
||||
while (text.match(CHECKBOX_REGEX)) {
|
||||
text = text.replace(CHECKBOX_REGEX, (match) => `- ${match.trim()}`);
|
||||
}
|
||||
|
||||
// find multiple newlines and insert a hard break to ensure they are respected
|
||||
text = text.replace(/\n{2,}/g, "\n\n\\\n");
|
||||
|
||||
// find single newlines and insert an extra to ensure they are treated as paragraphs
|
||||
text = text.replace(/\b\n\b/g, "\n\n");
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
export default class PasteHandler extends Extension {
|
||||
get name() {
|
||||
return "markdown-paste";
|
||||
|
||||
Reference in New Issue
Block a user