# How to copy text in markdown format from marks

**URL:** <https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054>\
**Category:** Uncategorized\
**Created:** [September 16, 2021, 3:51am UTC](https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054 "2021-09-16T03:51:01Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![nokola](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/nokola/32/2332_2.png) [@nokola](https://discuss.prosemirror.net/u/nokola)\
**Post date:** [September 16, 2021, 3:51am UTC](https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054/1 "2021-09-16T03:51:01Z")

</div>

Looked around the forum, online and in source, but couldn’t find the solution yet. Wondering how to copy-paste a mark from ProseMirror to notepad (plain text), but keep markdown styling.

For example, I’d like to copy this in ProseMirror: test **bold** test

Then paste it in Notepad, and get this: `test **bold** test`

I’m looking for bold as the easiest example, but would like to do similar thing for links, annotations (custom mark) etc.

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)\
**Post date:** [September 16, 2021, 5:53am UTC](https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054/2 "2021-09-16T05:53:44Z")

</div>

The general idea would be to register DOM [event handlers](https://prosemirror.net/docs/ref/#view.EditorProps.handleDOMEvents) for “copy” (and maybe “cut”), and in those, take the selected [content](https://prosemirror.net/docs/ref/#state.Selection.content) and run that through a [`MarkdownSerializer`](https://github.com/ProseMirror/prosemirror-markdown/#class-markdownserializer). That only takes a node, so you may need to wrap the slice’s `fragment` property in a node (I think an arbitrary node will do) for it to work.

---

<div class="post-metadata">

**Author:** ![nokola](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/nokola/32/2332_2.png) [@nokola](https://discuss.prosemirror.net/u/nokola)\
**Post date:** [September 16, 2021, 4:21pm UTC](https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054/3 "2021-09-16T16:21:56Z")

</div>

Thank you! I appreciate the super fast and helpful response, feeling excited to try out the suggestion! While looking at event handlers I noticed [clipboardTextParser](https://prosemirror.net/docs/ref/#view.EditorProps.clipboardTextParser) - would that be better to use instead of DOM copy/cut events?

I’m trying it out now. Found a related issue where I cannot copy a block element that is first in list, will open a separate discuss topic.

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)\
**Post date:** [September 16, 2021, 9:42pm UTC](https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054/4 "2021-09-16T21:42:40Z")

</div>

`clipboardTextParser` goes the other way. But yeah, [`clipboardTextSerializer`](https://prosemirror.net/docs/ref/#view.EditorProps.clipboardTextSerializer) does what you need here in a simpler way then what I was going on about before.

---

<div class="post-metadata">

**Author:** ![bhl](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/bhl/32/4074_2.png) [@bhl](https://discuss.prosemirror.net/u/bhl)\
**Post date:** [September 16, 2021, 10:42pm UTC](https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054/5 "2021-09-16T22:42:48Z")

</div>

There’s a good example of how to use the `clipboardTextSerializer` here [https://github.com/ueberdosis/tiptap/blob/main/packages/core/src/extensions/clipboardTextSerializer.ts](https://github.com/ueberdosis/tiptap/blob/main/packages/core/src/extensions/clipboardTextSerializer.ts), [https://github.com/ueberdosis/tiptap/commit/fe6a3e7491f6a42123d3d8a92ab588f2a40d7799](https://github.com/ueberdosis/tiptap/commit/fe6a3e7491f6a42123d3d8a92ab588f2a40d7799), [Replace Image With Text On Text Copy - #7 by jorisgu](https://discuss.prosemirror.net/t/replace-image-with-text-on-text-copy/3518/7).

---

<div class="post-metadata">

**Author:** ![nokola](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/nokola/32/2332_2.png) [@nokola](https://discuss.prosemirror.net/u/nokola)\
**Post date:** [October 12, 2021, 6:16am UTC](https://discuss.prosemirror.net/t/how-to-copy-text-in-markdown-format-from-marks/4054/6 "2021-10-12T06:16:04Z")

</div>

Just for information in case someone else looks for the same behavior. I ended up using a slightly modified version of [prosemirror-markdown/to\_markdown.js](https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/to_markdown.js).

I made some changes to my custom-flavor markdown (github + extensions)

1. Serialize paragraphs as single line
2. Serialize bold as \* instead of \*\*

I also updated some of the node serializers to use my custom implementation. Here’s some code, using tiptap.dev to access the underlying ProseMirror API directly:

```ts
import { defaultMarkdownSerializer, MarkdownSerializer, MarkdownSerializerState } from "../markdown/to_markdown";

            new Plugin({
                key: new PluginKey('clipboardTextSerializer'),
                props: {
                    clipboardTextSerializer: (slice: Slice<any>): string => {
                        const serializer: MarkdownSerializer = getMarkdownSerializer(this.editor.schema as any);
                        return serializer.serialize(slice.content as any, {
                            tightLists: true,
                            paragraphNewlines: 1, // tight newlines
                        });
                        // old code
                        // const { editor } = this;
                        // const { state, schema } = editor;
                        // const { doc, selection } = state;
                        // const { from, to } = selection;
                        // const textSerializers = getTextSeralizersFromSchema(schema as any);
                        // const range = { from, to };
                        // return getTextBetween(doc, range, { textSerializers });
                    },
                    // handleDOMEvents: {
                    // copy: (view: EditorView<any>, event: ClipboardEvent) => {
                    // console.log("COPY!!!");
                    // return false;
                    // }
                    // },
                },
            }),

function getMarkdownSerializer(schema: Schema) {
    const serializer: MarkdownSerializer = defaultMarkdownSerializer;
    serializer.marks['bold'] = { open: "*", close: "*", mixable: true, expelEnclosingWhitespace: true };
    serializer.marks['highlight'] = { open: "==", close: "==", mixable: true, expelEnclosingWhitespace: true };
    serializer.marks['italic'] = { open: "_", close: "_", mixable: true, expelEnclosingWhitespace: true };
    serializer.marks['strike'] = { open: " ~~", close: "~~", mixable: true, expelEnclosingWhitespace: true };

    Object.entries(schema.nodes).forEach(([name, tiptapNode]) => {
        if (!tiptapNode.spec.toText) {
            return;
        }

        serializer.nodes[name] = (state: MarkdownSerializerState, node: Node, parent?: Node, index?: number) => {
            state.write(tiptapNode.spec.toText({ node, pos: -1, parent, index }));
            if (!tiptapNode.spec.inline) {
                state.closeBlock(node);
            }
        };
    });

    //serializer.nodes['blockquote']
    serializer.nodes['bulletList'] = serializer.nodes.bullet_list;
    serializer.nodes['codeBlock'] = serializer.nodes.code_block;
    serializer.nodes['hardBreak'] = serializer.nodes.hard_break;
    //serializer.nodes['heading']
    serializer.nodes['horizontalRule'] = serializer.nodes.horizontal_rule;
    //serializer.nodes['image']
    serializer.nodes['listItem'] = serializer.nodes.list_item;
    serializer.nodes['orderedList'] = serializer.nodes.ordered_list;
    //serializer.nodes['paragraph']
    //serializer.nodes['scribeTask']
    //serializer.nodes['tableOfContents']
    //serializer.nodes['text']
    return serializer;
}

```

Here’s an example of the `toText()` function for e.g. the tableOfContents node:

```auto
        return "[[toc]]\r\n";

```

Hope this helps someone.
