curvenote / prosemirror-docx

Export a prosemirror document to a Microsoft Word file, using docx.
MIT License
92 stars 11 forks source link

[Question] Could it be used with Tiptap (prosemirror based editor)? #4

Open devgeni opened 2 years ago

devgeni commented 2 years ago

Hey! This plugin looks nice and I would like to use it with Tiptap editor. There is a page on how to use prosemirror plugins with Tiptap. But I can't figure out how should I set up your plugin in a similar way. Could you please help me out with this? Thanks!

rowanc1 commented 2 years ago

It should be possible to use with tiptap, I would love to document this for other people as well with your help!

This shouldn't be an extension to the editor in the tip-tap sense, so much as an export using the editor.state which is the prosemirror EditorState (doc, schema are all in there) when you press a button or something.

You need access to the EditorState

const state = editor.state;

// Need some way to pass the buffer into the word serializer (e.g. base64 conversion)
const opts = {
  getImageBuffer(src: string) {
    return anImageBuffer;
  },
};

const wordDocument = defaultDocxSerializer.serialize(state.doc, opts);

I think that should be it?

You could also console.log the schema.nodes and marks, which you will need if you are doing anything non-standard or named differently than the standard prosemirror world. I think the errors should guide you there.

You can extend that:

import { DocxSerializer, defaultNodes, defaultMarks } from 'prosemirror-docx';

const nodeSerializer = {
  ...defaultNodes,
  my_paragraph(state, node) {
    state.renderInline(node);
    state.closeBlock(node);
  },
};

export const myDocxSerializer = new DocxSerializer(nodeSerializer, defaultMarks);

Where you have your paragraph etc. as custom nodes, these would need to match what ever customizations that you do in tiptap.


Let me know if that gives some hints on approach -- excited to document this in the readme, and would love your help with that!

devgeni commented 2 years ago

OK, thanks! I'll try my best!

devgeni commented 2 years ago

@rowanc1 OK, so I managed that to work. The only problem I have is with:

const opts = {
  getImageBuffer(src: string) {
    return anImageBuffer;
  },
};

Can't render image. I think that getImageBuffer should return Promise<Buffer> instead of just Buffer. I can't think of any synchronous operation that would get buffer data from image source. Would be great if I could do something like that:

const opts = {
  async getImageBuffer(src: string) {
    const imgBuffer = await fetchImageBuffer(src);
    return imgBuffer;
  }
}

Other than that, awesome work! 👍

rowanc1 commented 2 years ago

Do you think you would be willing to write a paragraph in the readme under a heading like Integrating with TipTap showing a quick description and a code snippet? If it is easier to post it in this issue that would be fine to! :)

Agree on the promise -- I went that way because docx doesn't support promises in the doc creation. I think that we can abstract over it though. Opened issue #5.

devgeni commented 2 years ago

I just followed your instructions. 🙂 Something like that:

import { Editor } from "@tiptap/react";
import { writeDocx, DocxSerializer, defaultNodes, defaultMarks } from 'prosemirror-docx';

// tiptap has these camelCased
const nodeSerializer = {
  ...defaultNodes,
  hardBreak: defaultNodes.hard_break,
  codeBlock: defaultNodes.code_block,
  orderedList: defaultNodes.ordered_list,
  listItem: defaultNodes.list_item,
  bulletList: defaultNodes.bullet_list,
  horizontalRule: defaultNodes.horizontal_rule,
  image(state, node) {
    // no image
    state.renderInline(node);
    state.closeBlock(node);
  }
};

const myDocxSerializer = new DocxSerializer(nodeSerializer, defaultMarks);

// somewhere in the app
const editor = new Editor({
  // options
});

export const ButtonExportDOCX = (props) => {
  return (
    <button onClick={async () => {
      const opts = {
        getImageBuffer(src: string) {
          return Buffer.from("real buffer here");
        },
      };

      const wordDocument = myDocxSerializer.serialize(editor.state.doc, opts);
      await writeDocx(wordDocument, (buffer) => {
        // use buffer
      });
    }}>
      export to docx
    </button>
  )
}
xiangshu233 commented 2 months ago

How can I export custom nodes in tiptap? I have encapsulated some of them and inserted them into vueNodeView components. image The DOM structure I get through editor.getHTML() is returned as is in the custom Vue component. I think the <question-item> tag may not be recognized when using the export tool.

My English is very poor so I can only use Google Translate to express my meaning, please forgive me

xiangshu233 commented 2 months ago

@devgeni Hello, I would like to ask how to use the custom node export

Nlarou commented 2 months ago

image @xiangshu233 I'm using TipTap with the mentions extension. In my case for a custom node like mention you can do like this.