From d5a468aa097e6cf5c62c18ffb66cfd60ff19a32f Mon Sep 17 00:00:00 2001 From: Nick the Sick Date: Fri, 20 Dec 2024 14:36:14 +0100 Subject: [PATCH] feat(import): add DOCX import and image upload docs --- .../collaboration/documents/conversion.mdx | 156 +++++++++++++++++- .../extensions/functionality/import.mdx | 17 +- 2 files changed, 162 insertions(+), 11 deletions(-) diff --git a/src/content/collaboration/documents/conversion.mdx b/src/content/collaboration/documents/conversion.mdx index b80b641..65172a9 100644 --- a/src/content/collaboration/documents/conversion.mdx +++ b/src/content/collaboration/documents/conversion.mdx @@ -5,11 +5,12 @@ meta: description: Use Tiptap to convert documents from docx, odt or markdown to Tiptap category: Collaboration --- + import { CodeDemo } from '@/components/CodeDemo' import { Callout } from '@/components/ui/Callout' - This feature is currently in beta and available to all users with a Tiptap account. + This feature is currently in beta and available to all users with a Tiptap account. The document conversion API supports DOCX, ODT, and Markdown conversion from and to Tiptap’s JSON format. @@ -18,7 +19,6 @@ The following demo uses the Document Conversion API by integrating both the [imp - You can also experiment with the Document Conversion API by heading over to our [Postman Collection](https://www.postman.com/docking-module-explorer-14290287/workspace/tiptap-collaboration-public/collection/33042171-cc186a66-df41-4df8-9c6e-e91b20deffe5?action=share&creator=32651125). @@ -34,10 +34,61 @@ Instead of using the Document Conversion API directly, you can use Tiptap Editor ## /import endpoint -The /import endpoint enables the conversion of `docx`, `odt`, or `markdown` files into Tiptap’s JSON format. Users can POST documents to this endpoint and use various parameters to customize how different document elements are handled during the conversion process. +The `/import` endpoint enables the conversion of `docx`, `odt`, or `markdown` files into Tiptap’s JSON format. Users can POST documents to this endpoint and use various parameters to customize how different document elements are handled during the conversion process. + +- **Method**: `POST` + +### Required headers + +| Name | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `Authorization` | The JWT token to authenticate the request. Example: `Bearer your-jwt-token` | +| `X-App-Id` | The Convert App-ID from the Collaboration settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) | + +### Body + +| Name | Type | Description | +| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `file` | `File` | The file to convert | +| `imageUploadCallbackUrl` | `string` | The callback endpoint to upload images that were encountered within the uploaded document, [see more info](#image-uploading) | + +### Query parameters + +Specify how source document elements are mapped to ProseMirror nodes or marks, and adjust the conversion to meet your specific styling and structural preferences. + +| Name | Default | Description | +| ---------------- | ---------------- | -------------------------------------------------------------------- | +| `paragraph` | `paragraph` | Defines which prosemirror type is used for paragraph conversion | +| `heading` | `heading` | Defines which prosemirror type is used for heading conversion | +| `blockquote` | `blockquote` | Defines which prosemirror type is used for blockquote conversion | +| `codeblock` | `codeblock` | Defines which prosemirror type is used for codeblock conversion | +| `bulletlist` | `bulletlist` | Defines which prosemirror type is used for bulletList conversion | +| `orderedlist` | `orderedlist` | Defines which prosemirror type is used for orderedList conversion | +| `listitem` | `listitem` | Defines which prosemirror type is used for listItem conversion | +| `hardbreak` | `hardbreak` | Defines which prosemirror type is used for hardbreak conversion | +| `horizontalrule` | `horizontalrule` | Defines which prosemirror type is used for horizontalRule conversion | +| `table` | `table` | Defines which prosemirror type is used for table conversion | +| `tablecell` | `tablecell` | Defines which prosemirror type is used for tableCell conversion | +| `tableheader` | `tableheader` | Defines which prosemirror type is used for tableHeader conversion | +| `tablerow` | `tablerow` | Defines which prosemirror type is used for tableRow conversion | +| `bold` | `bold` | Defines which prosemirror mark is used for bold conversion | +| `italic` | `italic` | Defines which prosemirror mark is used for italic conversion | +| `underline` | `underline` | Defines which prosemirror mark is used for underline conversion | +| `strikethrough` | `strike` | Defines which prosemirror mark is used for strikethrough conversion | +| `link` | `link` | Defines which prosemirror mark is used for link conversion | +| `code` | `code` | Defines which prosemirror mark is used for code conversion | +| `image` | `image` | Defines which prosemirror mark is used for image conversion | + +## /import-docx endpoint (experimental) + +The `/import-docx` endpoint enables the conversion of `docx` files into Tiptap’s JSON format. Users can POST documents to this endpoint and use various parameters to customize how different document elements are handled during the conversion process. - **Method**: `POST` + + This feature is currently in alpha and available to all users with a Tiptap account. + + ### Required headers | Name | Description | @@ -47,9 +98,10 @@ The /import endpoint enables the conversion of `docx`, `odt`, or `markdown` file ### Body -| Name | Type | Description | -| ------ | ------ | ------------------- | -| `file` | `File` | The file to convert | +| Name | Type | Description | +| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `file` | `File` | The file to convert | +| `imageUploadCallbackUrl` | `string` | The callback endpoint to upload images that were encountered within the uploaded document, [see more info](#image-uploading) | ### Query parameters @@ -76,10 +128,11 @@ Specify how source document elements are mapped to ProseMirror nodes or marks, a | `strikethrough` | `strike` | Defines which prosemirror mark is used for strikethrough conversion | | `link` | `link` | Defines which prosemirror mark is used for link conversion | | `code` | `code` | Defines which prosemirror mark is used for code conversion | +| `image` | `image` | Defines which prosemirror mark is used for image conversion | ## /export endpoint -The /export endpoint converts Tiptap documents back into formats like `docx`, `odt`, or `markdown`. +The `/export` endpoint converts Tiptap documents back into formats like `docx`, `odt`, or `markdown`. - **Method**: `POST` @@ -123,3 +176,92 @@ Convert a Tiptap document to a different format. | `strikethrough` | `strike` | Defines which prosemirror mark is used for strikethrough conversion | | `link` | `link` | Defines which prosemirror mark is used for link conversion | | `code` | `code` | Defines which prosemirror mark is used for code conversion | + +## Image Uploading + + + Tiptap does not provide an image upload service. You will need to implement your own server to + handle image uploads. + + +When converting documents, some formats may include images that you may want to preserve in the converted document. To do this, you can provide the optional `imageUploadCallbackUrl` parameter in the request body. This parameter should be a URL that the Convert service can use to upload images found in the document. + +The Convert service will make a `POST` request to the provided URL with the image file as the request body. The response should be a JSON object with a `url` key containing the URL where the image was uploaded. + +### Tips + +- The image upload callback URL should be a publicly accessible endpoint. +- The image upload callback URL should return a JSON object with a `url` key containing the URL where the image was uploaded. +- You can use any callback URL, so if you want to limit uploads, you can provide a URL that includes an API key or other authentication method. +- The URL you provide, will be written into the document as the image source, so make sure it is accessible to anyone who will view the document. + +### Server Implementation Example + +This example shows a simple server implementation that accepts image uploads & uploads them to an S3 bucket configured by environment variables. + +```ts +import { serve } from '@hono/node-server' +import { Hono } from 'hono' +import { Upload } from '@aws-sdk/lib-storage' +import { S3Client } from '@aws-sdk/client-s3' + +const { + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + AWS_REGION, + AWS_S3_BUCKET, + PORT = '3011', + AWS_ENDPOINT, + AWS_FORCE_STYLE, +} = process.env + +if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY || !AWS_S3_BUCKET) { + console.error('Please provide AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_S3_BUCKET') + process.exit(1) +} + +const s3 = new S3Client({ + credentials: { + accessKeyId: AWS_ACCESS_KEY_ID, + secretAccessKey: AWS_SECRET_ACCESS_KEY, + }, + + region: AWS_REGION, + endpoint: AWS_ENDPOINT, + forcePathStyle: AWS_FORCE_STYLE === 'true', +}) + +const app = new Hono() as Hono + +app.post('/upload', async (c) => { + const body = await c.req.parseBody() + const file = body['file'] + + if (!file || typeof file === 'string') { + return c.json({ error: 'No file uploaded' }, 400) + } + + try { + const data = await new Upload({ + client: s3, + params: { + Bucket: AWS_S3_BUCKET, + // file.name is just current timestamp & file extension + Key: file.name, + Body: file, + ContentType: file.type, + }, + }).done() + + return c.json({ url: data.Location }) + } catch (error) { + console.error(error) + return c.json({ error: 'Failed to upload file' }, 500) + } +}) + +serve({ + fetch: app.fetch, + port: Number(PORT) || 3000, +}) +``` diff --git a/src/content/editor/extensions/functionality/import.mdx b/src/content/editor/extensions/functionality/import.mdx index 88a7a11..0215271 100644 --- a/src/content/editor/extensions/functionality/import.mdx +++ b/src/content/editor/extensions/functionality/import.mdx @@ -109,6 +109,12 @@ const editor = new Editor({ // The JWT token you generated in the previous step token: 'your-jwt', + + // The URL to upload images to, if not provided, images will be stripped from the document + imageUploadCallbackUrl: 'https://your-image-upload-url.com', + + // Enables the experimental DOCX import which should better preserve content styling + experimentalDocxImport: true, }), ], }) @@ -158,10 +164,12 @@ editor ## Options -| Name | Type | Default | Description | -| ------- | -------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `appId` | `string` | `undefined` | The convert app ID from the Convert settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) | -| `token` | `string` | `undefined` | The JWT token generated from your server via secret | +| Name | Type | Default | Description | +| ------------------------ | ---------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `appId` | `string` | `undefined` | The convert app ID from the Convert settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) | +| `token` | `string` | `undefined` | The JWT token generated from your server via secret | +| `imageUploadCallbackUrl` | `string` | `undefined` | The URL to upload images to, if not provided, images will be stripped from the document, [see more info](/collaboration/documents/conversion#image-uploading) | +| `experimentalDocxImport` | `boolean ` | `false` | Enables the experimental DOCX import which should better preserve content styling (experimental, and this API may not be completely stable while in alpha), only applies to DOCX files uploaded | ## Commands @@ -189,6 +197,7 @@ editor ## Caveats and limitations +- **Image upload** - Images are assumed to be inline within the document so, your editor should be setup with `Image.configure({ inline: true })` to display them correctly, otherwise they will be stripped from the document - **Unsupported docx elements on import** - Importing docx files currently does not support page breaks, page headers and footers, horizontal rules or text styles - **Content added via suggestion mode** - Content added via suggestion mode is not included in the imported prosemirror document - **PDF import & export** - Importing and PDF files is not yet supported