mirror of
https://github.com/docmost/docmost.git
synced 2025-11-10 06:52:07 +10:00
fix editor converter (#1647)
This commit is contained in:
@ -62,7 +62,7 @@
|
||||
"class-validator": "^0.14.1",
|
||||
"cookie": "^1.0.2",
|
||||
"fs-extra": "^11.3.0",
|
||||
"happy-dom": "^15.11.6",
|
||||
"happy-dom": "^18.0.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"kysely": "^0.28.2",
|
||||
"kysely-migration-cli": "^0.4.2",
|
||||
|
||||
@ -35,11 +35,10 @@ import {
|
||||
Subpages,
|
||||
} from '@docmost/editor-ext';
|
||||
import { generateText, getSchema, JSONContent } from '@tiptap/core';
|
||||
import { generateHTML } from '../common/helpers/prosemirror/html';
|
||||
import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
|
||||
// @tiptap/html library works best for generating prosemirror json state but not HTML
|
||||
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
||||
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
||||
import { generateJSON } from '@tiptap/html';
|
||||
import { Node } from '@tiptap/pm/model';
|
||||
|
||||
export const tiptapExtensions = [
|
||||
|
||||
@ -1,21 +1,29 @@
|
||||
import { Extensions, getSchema, JSONContent } from '@tiptap/core';
|
||||
import { DOMSerializer, Node } from '@tiptap/pm/model';
|
||||
import { Window } from 'happy-dom';
|
||||
import { type Extensions, type JSONContent, getSchema } from '@tiptap/core';
|
||||
import { Node } from '@tiptap/pm/model';
|
||||
import { getHTMLFromFragment } from './getHTMLFromFragment';
|
||||
|
||||
/**
|
||||
* This function generates HTML from a ProseMirror JSON content object.
|
||||
*
|
||||
* @remarks **Important**: This function requires `happy-dom` to be installed in your project.
|
||||
* @param doc - The ProseMirror JSON content object.
|
||||
* @param extensions - The Tiptap extensions used to build the schema.
|
||||
* @returns The generated HTML string.
|
||||
* @example
|
||||
* ```js
|
||||
* const html = generateHTML(doc, extensions)
|
||||
* console.log(html)
|
||||
* ```
|
||||
*/
|
||||
export function generateHTML(doc: JSONContent, extensions: Extensions): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
throw new Error(
|
||||
'generateHTML can only be used in a Node environment\nIf you want to use this in a browser environment, use the `@tiptap/html` import instead.',
|
||||
);
|
||||
}
|
||||
|
||||
const schema = getSchema(extensions);
|
||||
const contentNode = Node.fromJSON(schema, doc);
|
||||
|
||||
const window = new Window();
|
||||
|
||||
const fragment = DOMSerializer.fromSchema(schema).serializeFragment(
|
||||
contentNode.content,
|
||||
{
|
||||
document: window.document as unknown as Document,
|
||||
},
|
||||
);
|
||||
|
||||
const serializer = new window.XMLSerializer();
|
||||
// @ts-ignore
|
||||
return serializer.serializeToString(fragment as unknown as Node);
|
||||
return getHTMLFromFragment(contentNode, schema);
|
||||
}
|
||||
|
||||
@ -1,21 +1,55 @@
|
||||
import { Extensions, getSchema } from '@tiptap/core';
|
||||
import { DOMParser, ParseOptions } from '@tiptap/pm/model';
|
||||
import type { Extensions } from '@tiptap/core';
|
||||
import { getSchema } from '@tiptap/core';
|
||||
import { type ParseOptions, DOMParser as PMDOMParser } from '@tiptap/pm/model';
|
||||
import { Window } from 'happy-dom';
|
||||
|
||||
// this function does not work as intended
|
||||
// it has issues with closing tags
|
||||
/**
|
||||
* Generates a JSON object from the given HTML string and converts it into a Prosemirror node with content.
|
||||
* @remarks **Important**: This function requires `happy-dom` to be installed in your project.
|
||||
* @param {string} html - The HTML string to be converted into a Prosemirror node.
|
||||
* @param {Extensions} extensions - The extensions to be used for generating the schema.
|
||||
* @param {ParseOptions} options - The options to be supplied to the parser.
|
||||
* @returns {Promise<Record<string, any>>} - A promise with the generated JSON object.
|
||||
* @example
|
||||
* const html = '<p>Hello, world!</p>'
|
||||
* const extensions = [...]
|
||||
* const json = generateJSON(html, extensions)
|
||||
* console.log(json) // { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] }] }
|
||||
*/
|
||||
export function generateJSON(
|
||||
html: string,
|
||||
extensions: Extensions,
|
||||
options?: ParseOptions,
|
||||
): Record<string, any> {
|
||||
const schema = getSchema(extensions);
|
||||
if (typeof window !== 'undefined') {
|
||||
throw new Error(
|
||||
'generateJSON can only be used in a Node environment\nIf you want to use this in a browser environment, use the `@tiptap/html` import instead.',
|
||||
);
|
||||
}
|
||||
|
||||
const window = new Window();
|
||||
const document = window.document;
|
||||
document.body.innerHTML = html;
|
||||
const localWindow = new Window();
|
||||
const localDOMParser = new localWindow.DOMParser();
|
||||
let result: Record<string, any>;
|
||||
|
||||
return DOMParser.fromSchema(schema)
|
||||
.parse(document as never, options)
|
||||
.toJSON();
|
||||
try {
|
||||
const schema = getSchema(extensions);
|
||||
let doc: ReturnType<typeof localDOMParser.parseFromString> | null = null;
|
||||
|
||||
const htmlString = `<!DOCTYPE html><html><body>${html}</body></html>`;
|
||||
doc = localDOMParser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
if (!doc) {
|
||||
throw new Error('Failed to parse HTML string');
|
||||
}
|
||||
|
||||
result = PMDOMParser.fromSchema(schema)
|
||||
.parse(doc.body as unknown as Node, options)
|
||||
.toJSON();
|
||||
} finally {
|
||||
// clean up happy-dom to avoid memory leaks
|
||||
localWindow.happyDOM.abort();
|
||||
localWindow.happyDOM.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
import type { Node, Schema } from '@tiptap/pm/model';
|
||||
import { DOMSerializer } from '@tiptap/pm/model';
|
||||
import { Window } from 'happy-dom';
|
||||
|
||||
/**
|
||||
* Returns the HTML string representation of a given document node.
|
||||
*
|
||||
* @remarks **Important**: This function requires `happy-dom` to be installed in your project.
|
||||
* @param doc - The document node to serialize.
|
||||
* @param schema - The Prosemirror schema to use for serialization.
|
||||
* @returns A promise containing the HTML string representation of the document fragment.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const html = getHTMLFromFragment(doc, schema)
|
||||
* ```
|
||||
*/
|
||||
export function getHTMLFromFragment(
|
||||
doc: Node,
|
||||
schema: Schema,
|
||||
options?: { document?: Document },
|
||||
): string {
|
||||
if (options?.document) {
|
||||
const wrap = options.document.createElement('div');
|
||||
|
||||
DOMSerializer.fromSchema(schema).serializeFragment(
|
||||
doc.content,
|
||||
{ document: options.document },
|
||||
wrap,
|
||||
);
|
||||
return wrap.innerHTML;
|
||||
}
|
||||
|
||||
const localWindow = new Window();
|
||||
let result: string;
|
||||
|
||||
try {
|
||||
const fragment = DOMSerializer.fromSchema(schema).serializeFragment(
|
||||
doc.content,
|
||||
{
|
||||
document: localWindow.document as unknown as Document,
|
||||
},
|
||||
);
|
||||
|
||||
const serializer = new localWindow.XMLSerializer();
|
||||
result = serializer.serializeToString(fragment as any);
|
||||
} finally {
|
||||
// clean up happy-dom to avoid memory leaks
|
||||
localWindow.happyDOM.abort();
|
||||
localWindow.happyDOM.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user