Parsing single line breaks into hard line breaks, instead of paragraphs

I’m new to ProseMirror, and trying to replace what is currently a form’s textarea with a ProseMirror editor. To get started, I’d like to read the contents of the existing textarea and initialize a ProseMirror editor. So I’d like to parse the plain text textContent of the textarea into a very bare schema (paragraph, inline text, and hard_break). Is there some existing code I should use / be aware of?

I looked into what happens / how it is parsed if simply pasting the plain text from the textarea, and it appears to be this code: prosemirror-view/clipboard.ts at facde1aea2ef79ba197e49ecab64a08a8bb14e12 · ProseMirror/prosemirror-view · GitHub — it turns each line break into a new paragraph, but I’d instead want a single line break to become a br (hard_break) and two line breaks (a blank line) to indicate a new paragraph. So this code would be need to be suitably modified and also used as clipboardTextParser or clipboardParser⁠, correct? Or can this be achieved with some combination of existing code and options (preserveWhitespace⁠: "full" ?)

(Looking at related questions: I guess what I’m doing is along the lines of Submitting textarea with markdown + ProseMirror. And I guess Please change the rules for pasting plain text is related.)

For what it’s worth, I just went ahead and did the ad-hoc parsing, with the following code. Comments welcome, especially if I’m doing something wrong:

// Parse a string (the contents of the textarea) into a HTML element.
// Only recognizes blank lines as <p> separators, and line breaks as <br>.
function domFromText(text: string): HTMLDivElement {
    const dom = document.createElement("div");
    text.split(/(?:\r\n?|\n){2,}/).forEach(block => {
        let p = dom.appendChild(document.createElement("p"));
        if (block) {
            block.split(/(?:\r\n?|\n)/).forEach(line => {
                if (line) {
                    if (p.hasChildNodes()) p.appendChild(document.createElement('br'));
    return dom;

function fromText(text: string): Node {
    const dom = domFromText(text);
    return DOMParser.fromSchema(almostTrivialSchema).parse(dom, { preserveWhitespace: "full" });

// Create a new ProseMirror editor with the contents of the textarea, and hide the textarea.
const $textarea = document.querySelector('textarea')!;
const view = new EditorView(document.querySelector("#editor"), {
    state: EditorState.create({
        schema: almostTrivialSchema,
        doc: fromText($textarea.textContent!),
        plugins: plugins(almostTrivialSchema)

And for the other direction:

// Serialize the EditorState into a plain text string.
function toText(): string {
    return view.state.doc.textBetween(
        0, // from
        view.state.doc.content.size, // to
        "\n\n", // blockSeparator
        "\n", // leafNode HACK: Figure out how to do this only for `hard_break` nodes and no other.

That seems like a reasonable approach. You can pass a function as last argument to textBetween to produce different strings for different nodes.

1 Like