mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
fix converter
This commit is contained in:
@@ -62,7 +62,7 @@
|
|||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"cookie": "^1.0.2",
|
"cookie": "^1.0.2",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"happy-dom": "^15.11.6",
|
"happy-dom": "^18.0.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"kysely": "^0.28.2",
|
"kysely": "^0.28.2",
|
||||||
"kysely-migration-cli": "^0.4.2",
|
"kysely-migration-cli": "^0.4.2",
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ import {
|
|||||||
} from '@docmost/editor-ext';
|
} from '@docmost/editor-ext';
|
||||||
import { generateText, getSchema, JSONContent } from '@tiptap/core';
|
import { generateText, getSchema, JSONContent } from '@tiptap/core';
|
||||||
import { generateHTML } from '../common/helpers/prosemirror/html';
|
import { generateHTML } from '../common/helpers/prosemirror/html';
|
||||||
|
import { generateJSON } from '../common/helpers/prosemirror/html';
|
||||||
// @tiptap/html library works best for generating prosemirror json state but not 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/5352
|
||||||
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
||||||
import { generateJSON } from '@tiptap/html';
|
//import { generateJSON } from '@tiptap/html';
|
||||||
import { Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
|
|
||||||
export const tiptapExtensions = [
|
export const tiptapExtensions = [
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
import { Extensions, getSchema, JSONContent } from '@tiptap/core';
|
import { type Extensions, type JSONContent, getSchema } from '@tiptap/core';
|
||||||
import { DOMSerializer, Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { Window } from 'happy-dom';
|
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 {
|
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 schema = getSchema(extensions);
|
||||||
const contentNode = Node.fromJSON(schema, doc);
|
const contentNode = Node.fromJSON(schema, doc);
|
||||||
|
|
||||||
const window = new Window();
|
return getHTMLFromFragment(contentNode, schema);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,55 @@
|
|||||||
import { Extensions, getSchema } from '@tiptap/core';
|
import type { Extensions } from '@tiptap/core';
|
||||||
import { DOMParser, ParseOptions } from '@tiptap/pm/model';
|
import { getSchema } from '@tiptap/core';
|
||||||
|
import { type ParseOptions, DOMParser as PMDOMParser } from '@tiptap/pm/model';
|
||||||
import { Window } from 'happy-dom';
|
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(
|
export function generateJSON(
|
||||||
html: string,
|
html: string,
|
||||||
extensions: Extensions,
|
extensions: Extensions,
|
||||||
options?: ParseOptions,
|
options?: ParseOptions,
|
||||||
): Record<string, any> {
|
): 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 localWindow = new Window();
|
||||||
const document = window.document;
|
const localDOMParser = new localWindow.DOMParser();
|
||||||
document.body.innerHTML = html;
|
let result: Record<string, any>;
|
||||||
|
|
||||||
return DOMParser.fromSchema(schema)
|
try {
|
||||||
.parse(document as never, options)
|
const schema = getSchema(extensions);
|
||||||
.toJSON();
|
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,43 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
Generated
+28
-11
@@ -117,7 +117,7 @@ importers:
|
|||||||
version: 3.6.1(@tiptap/core@3.6.1(@tiptap/pm@3.6.1))
|
version: 3.6.1(@tiptap/core@3.6.1(@tiptap/pm@3.6.1))
|
||||||
'@tiptap/html':
|
'@tiptap/html':
|
||||||
specifier: ^3.6.1
|
specifier: ^3.6.1
|
||||||
version: 3.6.1(@tiptap/core@3.6.1(@tiptap/pm@3.6.1))(@tiptap/pm@3.6.1)(happy-dom@15.11.7)
|
version: 3.6.1(@tiptap/core@3.6.1(@tiptap/pm@3.6.1))(@tiptap/pm@3.6.1)(happy-dom@18.0.1)
|
||||||
'@tiptap/pm':
|
'@tiptap/pm':
|
||||||
specifier: ^3.6.1
|
specifier: ^3.6.1
|
||||||
version: 3.6.1
|
version: 3.6.1
|
||||||
@@ -508,8 +508,8 @@ importers:
|
|||||||
specifier: ^11.3.0
|
specifier: ^11.3.0
|
||||||
version: 11.3.0
|
version: 11.3.0
|
||||||
happy-dom:
|
happy-dom:
|
||||||
specifier: ^15.11.6
|
specifier: ^18.0.1
|
||||||
version: 15.11.7
|
version: 18.0.1
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.2
|
specifier: ^9.0.2
|
||||||
version: 9.0.2
|
version: 9.0.2
|
||||||
@@ -4531,6 +4531,9 @@ packages:
|
|||||||
'@types/ms@2.1.0':
|
'@types/ms@2.1.0':
|
||||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||||
|
|
||||||
|
'@types/node@20.19.17':
|
||||||
|
resolution: {integrity: sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==}
|
||||||
|
|
||||||
'@types/node@22.10.0':
|
'@types/node@22.10.0':
|
||||||
resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==}
|
resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==}
|
||||||
|
|
||||||
@@ -4621,6 +4624,9 @@ packages:
|
|||||||
'@types/validator@13.12.0':
|
'@types/validator@13.12.0':
|
||||||
resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==}
|
resolution: {integrity: sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==}
|
||||||
|
|
||||||
|
'@types/whatwg-mimetype@3.0.2':
|
||||||
|
resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
|
||||||
|
|
||||||
'@types/ws@8.5.14':
|
'@types/ws@8.5.14':
|
||||||
resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==}
|
resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==}
|
||||||
|
|
||||||
@@ -6510,9 +6516,9 @@ packages:
|
|||||||
hachure-fill@0.5.2:
|
hachure-fill@0.5.2:
|
||||||
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
|
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
|
||||||
|
|
||||||
happy-dom@15.11.7:
|
happy-dom@18.0.1:
|
||||||
resolution: {integrity: sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==}
|
resolution: {integrity: sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
has-bigints@1.0.2:
|
has-bigints@1.0.2:
|
||||||
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
|
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
|
||||||
@@ -9441,6 +9447,9 @@ packages:
|
|||||||
undici-types@6.20.0:
|
undici-types@6.20.0:
|
||||||
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
|
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
|
||||||
|
|
||||||
|
undici-types@6.21.0:
|
||||||
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
undici@7.10.0:
|
undici@7.10.0:
|
||||||
resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==}
|
resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==}
|
||||||
engines: {node: '>=20.18.1'}
|
engines: {node: '>=20.18.1'}
|
||||||
@@ -14234,11 +14243,11 @@ snapshots:
|
|||||||
'@tiptap/core': 3.6.1(@tiptap/pm@3.6.1)
|
'@tiptap/core': 3.6.1(@tiptap/pm@3.6.1)
|
||||||
'@tiptap/pm': 3.6.1
|
'@tiptap/pm': 3.6.1
|
||||||
|
|
||||||
'@tiptap/html@3.6.1(@tiptap/core@3.6.1(@tiptap/pm@3.6.1))(@tiptap/pm@3.6.1)(happy-dom@15.11.7)':
|
'@tiptap/html@3.6.1(@tiptap/core@3.6.1(@tiptap/pm@3.6.1))(@tiptap/pm@3.6.1)(happy-dom@18.0.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.6.1(@tiptap/pm@3.6.1)
|
'@tiptap/core': 3.6.1(@tiptap/pm@3.6.1)
|
||||||
'@tiptap/pm': 3.6.1
|
'@tiptap/pm': 3.6.1
|
||||||
happy-dom: 15.11.7
|
happy-dom: 18.0.1
|
||||||
|
|
||||||
'@tiptap/pm@3.6.1':
|
'@tiptap/pm@3.6.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14626,6 +14635,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/ms@2.1.0': {}
|
'@types/ms@2.1.0': {}
|
||||||
|
|
||||||
|
'@types/node@20.19.17':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.21.0
|
||||||
|
|
||||||
'@types/node@22.10.0':
|
'@types/node@22.10.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.20.0
|
undici-types: 6.20.0
|
||||||
@@ -14738,6 +14751,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/validator@13.12.0': {}
|
'@types/validator@13.12.0': {}
|
||||||
|
|
||||||
|
'@types/whatwg-mimetype@3.0.2': {}
|
||||||
|
|
||||||
'@types/ws@8.5.14':
|
'@types/ws@8.5.14':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.4
|
'@types/node': 22.13.4
|
||||||
@@ -17039,10 +17054,10 @@ snapshots:
|
|||||||
|
|
||||||
hachure-fill@0.5.2: {}
|
hachure-fill@0.5.2: {}
|
||||||
|
|
||||||
happy-dom@15.11.7:
|
happy-dom@18.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
entities: 4.5.0
|
'@types/node': 20.19.17
|
||||||
webidl-conversions: 7.0.0
|
'@types/whatwg-mimetype': 3.0.2
|
||||||
whatwg-mimetype: 3.0.0
|
whatwg-mimetype: 3.0.0
|
||||||
|
|
||||||
has-bigints@1.0.2: {}
|
has-bigints@1.0.2: {}
|
||||||
@@ -20458,6 +20473,8 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@6.20.0: {}
|
undici-types@6.20.0: {}
|
||||||
|
|
||||||
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
undici@7.10.0: {}
|
undici@7.10.0: {}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user