diff --git a/docs/src/demos/Guide/Content/GenerateJSON/index.spec.js b/docs/src/demos/Guide/Content/GenerateJSON/index.spec.js new file mode 100644 index 00000000..78d6813c --- /dev/null +++ b/docs/src/demos/Guide/Content/GenerateJSON/index.spec.js @@ -0,0 +1,7 @@ +context('/demos/Guide/Content/GenerateJSON', () => { + before(() => { + cy.visit('/demos/Guide/Content/GenerateJSON') + }) + + // TODO: Write tests +}) diff --git a/docs/src/demos/Guide/Content/GenerateJSON/index.vue b/docs/src/demos/Guide/Content/GenerateJSON/index.vue new file mode 100644 index 00000000..aff6599d --- /dev/null +++ b/docs/src/demos/Guide/Content/GenerateJSON/index.vue @@ -0,0 +1,30 @@ + + + diff --git a/docs/src/docPages/api/utilities/html.md b/docs/src/docPages/api/utilities/html.md index de242ae8..1b1d76fd 100644 --- a/docs/src/docPages/api/utilities/html.md +++ b/docs/src/docPages/api/utilities/html.md @@ -2,10 +2,15 @@ [![Version](https://img.shields.io/npm/v/@tiptap/html.svg?label=version)](https://www.npmjs.com/package/@tiptap/html) [![Downloads](https://img.shields.io/npm/dm/@tiptap/html.svg)](https://npmcharts.com/compare/@tiptap/html?minimal=true) -The utility helps rendering JSON content as HTML without an editor instance, for example on the server side. All it needs is a JSON and an array of extensions. +The utility helps rendering JSON content as HTML, and generating JSON from HTML, without an editor instance, for example on the server side. + +All it needs is JSON or a HTML string, and a list of extensions. ## Source code [packages/html/](https://github.com/ueberdosis/tiptap/blob/main/packages/html/) -## Usage +## Generate HTML from JSON + +## Generate JSON from HTML + diff --git a/docs/src/docPages/guide/output.md b/docs/src/docPages/guide/output.md index 7c587ecd..e2adc190 100644 --- a/docs/src/docPages/guide/output.md +++ b/docs/src/docPages/guide/output.md @@ -117,7 +117,11 @@ If you need to render the content on the server side, for example to generate th That’s what the `generateHTML()` is for. It’s a helper function which renders HTML without an actual editor instance. - + + +By the way, the other way is possible, too. The below examples shows how to generate JSON from HTML. + + ## Migration If you’re migrating existing content to tiptap we would recommend to get your existing output to HTML. That’s probably the best format to get your initial content into tiptap, because ProseMirror ensures there is nothing wrong with it. Even if there are some tags or attributes that aren’t allowed (based on your configuration), tiptap just throws them away quietly. diff --git a/packages/core/src/helpers/generateJSON.ts b/packages/core/src/helpers/generateJSON.ts new file mode 100644 index 00000000..ec025f49 --- /dev/null +++ b/packages/core/src/helpers/generateJSON.ts @@ -0,0 +1,13 @@ +import { DOMParser } from 'prosemirror-model' +import getSchema from './getSchema' +import elementFromString from '../utilities/elementFromString' +import { Extensions } from '../types' + +export default function generateJSON(html: string, extensions: Extensions): Record { + const schema = getSchema(extensions) + const dom = elementFromString(html) + + return DOMParser.fromSchema(schema) + .parse(dom) + .toJSON() +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9a2124fc..0bec85ec 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -18,6 +18,7 @@ export { default as findChildren } from './helpers/findChildren' export { default as findParentNode } from './helpers/findParentNode' export { default as findParentNodeClosestToPos } from './helpers/findParentNodeClosestToPos' export { default as generateHTML } from './helpers/generateHTML' +export { default as generateJSON } from './helpers/generateJSON' export { default as getSchema } from './helpers/getSchema' export { default as getHTMLFromFragment } from './helpers/getHTMLFromFragment' export { default as getMarkAttributes } from './helpers/getMarkAttributes' diff --git a/packages/html/src/example.js b/packages/html/src/example.js deleted file mode 100644 index 80ac7078..00000000 --- a/packages/html/src/example.js +++ /dev/null @@ -1,24 +0,0 @@ -import { generateHTML } from '@tiptap/html' - -import Document from '@tiptap/extension-document' -import Paragraph from '@tiptap/extension-paragraph' -import Text from '@tiptap/extension-text' - -// eslint-disable-next-line -const html = generateHTML({ - type: 'doc', - content: [{ - type: 'paragraph', - attrs: { - align: 'left', - }, - content: [{ - type: 'text', - text: 'Example Text', - }], - }], -}, [ - new Document(), - new Paragraph(), - new Text(), -]) diff --git a/packages/html/src/generateHTML.ts b/packages/html/src/generateHTML.ts new file mode 100644 index 00000000..d47e9c06 --- /dev/null +++ b/packages/html/src/generateHTML.ts @@ -0,0 +1,10 @@ +import { Extensions, getSchema } from '@tiptap/core' +import { Node } from 'prosemirror-model' +import getHTMLFromFragment from './getHTMLFromFragment' + +export default function generateHTML(doc: object, extensions: Extensions): string { + const schema = getSchema(extensions) + const contentNode = Node.fromJSON(schema, doc) + + return getHTMLFromFragment(contentNode, schema) +} diff --git a/packages/html/src/generateJSON.ts b/packages/html/src/generateJSON.ts new file mode 100644 index 00000000..d55af414 --- /dev/null +++ b/packages/html/src/generateJSON.ts @@ -0,0 +1,13 @@ +import { DOMParser } from 'prosemirror-model' +import { getSchema, Extensions } from '@tiptap/core' +// @ts-ignore +import { parseHTML } from 'hostic-dom' + +export default function generateJSON(html: string, extensions: Extensions): Record { + const schema = getSchema(extensions) + const dom = parseHTML(html) + + return DOMParser.fromSchema(schema) + .parse(dom) + .toJSON() +} diff --git a/packages/html/src/index.ts b/packages/html/src/index.ts index a9ec036a..754f4da9 100644 --- a/packages/html/src/index.ts +++ b/packages/html/src/index.ts @@ -1,10 +1,2 @@ -import { Extensions, getSchema } from '@tiptap/core' -import { Node } from 'prosemirror-model' -import getHTMLFromFragment from './getHTMLFromFragment' - -export function generateHTML(doc: object, extensions: Extensions): string { - const schema = getSchema(extensions) - const contentNode = Node.fromJSON(schema, doc) - - return getHTMLFromFragment(contentNode, schema) -} +export { default as generateHTML } from './generateHTML' +export { default as generateJSON } from './generateJSON' diff --git a/tests/cypress/integration/core/generateHTML.spec.ts b/tests/cypress/integration/core/generateHTML.spec.ts index 1cf1a467..5a60255b 100644 --- a/tests/cypress/integration/core/generateHTML.spec.ts +++ b/tests/cypress/integration/core/generateHTML.spec.ts @@ -1,6 +1,6 @@ /// -import { generateHTML } from '@tiptap/html' +import { generateHTML } from '@tiptap/core' import Document from '@tiptap/extension-document' import Paragraph from '@tiptap/extension-paragraph' import Text from '@tiptap/extension-text' diff --git a/tests/cypress/integration/core/generateJSON.spec.ts b/tests/cypress/integration/core/generateJSON.spec.ts new file mode 100644 index 00000000..d589dc2b --- /dev/null +++ b/tests/cypress/integration/core/generateJSON.spec.ts @@ -0,0 +1,29 @@ +/// + +import { generateJSON } from '@tiptap/core' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' + +describe('generateJSON', () => { + it('generate JSON from HTML without an editor instance', () => { + const html = '

Example Text

' + + const json = generateJSON(html, [ + Document, + Paragraph, + Text, + ]) + + expect(JSON.stringify(json)).to.eq(JSON.stringify({ + type: 'doc', + content: [{ + type: 'paragraph', + content: [{ + type: 'text', + text: 'Example Text', + }], + }], + })) + }) +}) diff --git a/tests/cypress/integration/html/generateHTML.spec.ts b/tests/cypress/integration/html/generateHTML.spec.ts new file mode 100644 index 00000000..1cf1a467 --- /dev/null +++ b/tests/cypress/integration/html/generateHTML.spec.ts @@ -0,0 +1,29 @@ +/// + +import { generateHTML } from '@tiptap/html' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' + +describe('generateHTML', () => { + it('generate HTML from JSON without an editor instance', () => { + const json = { + type: 'doc', + content: [{ + type: 'paragraph', + content: [{ + type: 'text', + text: 'Example Text', + }], + }], + } + + const html = generateHTML(json, [ + Document, + Paragraph, + Text, + ]) + + expect(html).to.eq('

Example Text

') + }) +}) diff --git a/tests/cypress/integration/html/generateJSON.spec.ts b/tests/cypress/integration/html/generateJSON.spec.ts new file mode 100644 index 00000000..b3a31e4f --- /dev/null +++ b/tests/cypress/integration/html/generateJSON.spec.ts @@ -0,0 +1,29 @@ +/// + +import { generateJSON } from '@tiptap/html' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' + +describe('generateJSON', () => { + it('generate JSON from HTML without an editor instance', () => { + const html = '

Example Text

' + + const json = generateJSON(html, [ + Document, + Paragraph, + Text, + ]) + + expect(JSON.stringify(json)).to.eq(JSON.stringify({ + type: 'doc', + content: [{ + type: 'paragraph', + content: [{ + type: 'text', + text: 'Example Text', + }], + }], + })) + }) +})