addProseMirrorPlugins()

in app/assets/javascripts/content_editor/extensions/copy_paste.js [110:230]


  addProseMirrorPlugins() {
    let pasteRaw = false;

    const handleCutAndCopy = (view, event) => {
      const slice = view.state.selection.content();
      let gfmContent = this.options.serializer.serialize({ doc: slice.content });
      const gfmContentWithoutSingleTableCell = gfmContent.replace(
        /^<table>[\s\n]*<tr>[\s\n]*<t[hd]>|<\/t[hd]>[\s\n]*<\/tr>[\s\n]*<\/table>[\s\n]*$/gim,
        '',
      );
      const containsSingleTableCell = !/<t[hd]>/.test(gfmContentWithoutSingleTableCell);

      if (containsSingleTableCell) {
        gfmContent = gfmContentWithoutSingleTableCell;
      }
      const documentFragment = DOMSerializer.fromSchema(view.state.schema).serializeFragment(
        slice.content,
      );
      const div = document.createElement('div');
      div.appendChild(documentFragment);

      event.clipboardData.setData(TEXT_FORMAT, div.innerText);
      event.clipboardData.setData(HTML_FORMAT, div.innerHTML);
      event.clipboardData.setData(GFM_FORMAT, gfmContent);

      event.preventDefault();
      event.stopPropagation();
    };

    return [
      new Plugin({
        key: new PluginKey('copyPaste'),
        props: {
          handleDOMEvents: {
            copy: handleCutAndCopy,
            cut: (view, event) => {
              handleCutAndCopy(view, event);
              this.editor.commands.deleteSelection();
            },
          },
          handleKeyDown: (_, event) => {
            pasteRaw = event.key === 'v' && (event.metaKey || event.ctrlKey) && event.shiftKey;
          },

          handlePaste: (view, event) => {
            const { clipboardData } = event;

            const gfmContent = clipboardData.getData(GFM_FORMAT);
            const textContent = clipboardData.getData(TEXT_FORMAT);
            const htmlContent = clipboardData.getData(HTML_FORMAT);

            const { from, to } = view.state.selection;
            const isCodeBlockActive = CODE_BLOCK_NODE_TYPES.some((type) =>
              this.editor.isActive(type),
            );

            if (pasteRaw || isCodeBlockActive) {
              const isMarkdownCodeBlockActive = this.editor.isActive(CodeBlockHighlight.name, {
                language: 'markdown',
              });

              let contentToInsert;
              if (isMarkdownCodeBlockActive) {
                contentToInsert = gfmContent || textContent;
              } else if (pasteRaw) {
                contentToInsert = textContent.replace(/^\s+|\s+$/gm, '');
              } else {
                contentToInsert = textContent;
              }

              if (!contentToInsert) return false;

              if (isCodeBlockActive) contentToInsert = { type: 'text', text: contentToInsert };
              else {
                contentToInsert = {
                  type: 'paragraph',
                  content: contentToInsert
                    .split('\n')
                    .map((text) => [{ type: 'text', text }, { type: 'hardBreak' }])
                    .flat(),
                };
              }

              this.editor.commands.insertContentAt({ from, to }, contentToInsert);
              return true;
            }

            if (!textContent) return false;

            const hasHTML = clipboardData.types.some((type) => type === HTML_FORMAT);
            const hasVsCode = clipboardData.types.some((type) => type === VS_CODE_FORMAT);
            const vsCodeMeta = hasVsCode ? JSON.parse(clipboardData.getData(VS_CODE_FORMAT)) : {};
            const language = vsCodeMeta.mode;

            if (hasVsCode) {
              return this.editor.commands.pasteContent(
                language === 'markdown' ? textContent : `\`\`\`${language}\n${textContent}\n\`\`\``,
                true,
              );
            }

            if (gfmContent) {
              return this.editor.commands.pasteContent(gfmContent, true);
            }

            const preStartRegex = /^<pre[^>]*lang="markdown"[^>]*>/;
            const preEndRegex = /<\/pre>$/;
            const htmlContentWithoutMeta = htmlContent?.replace(/^<meta[^>]*>/, '');
            const pastingMarkdownBlock =
              hasHTML &&
              preStartRegex.test(htmlContentWithoutMeta) &&
              preEndRegex.test(htmlContentWithoutMeta);

            if (pastingMarkdownBlock) return this.editor.commands.pasteContent(textContent, true);

            return this.editor.commands.pasteContent(hasHTML ? htmlContent : textContent, !hasHTML);
          },
        },
      }),
    ];
  },