From 933e2f5599d4fcc9e770e47490ead0c797066e30 Mon Sep 17 00:00:00 2001 From: Ashwini-Rathod Date: Fri, 7 Jun 2024 14:27:07 +0530 Subject: [PATCH 1/2] chore: updated package with new version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 34daa2b..f88724b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/json-rte-serializer", - "version": "2.0.6", + "version": "2.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@contentstack/json-rte-serializer", - "version": "2.0.6", + "version": "2.0.7", "license": "MIT", "dependencies": { "array-flat-polyfill": "^1.0.1", diff --git a/package.json b/package.json index 9d54d47..1fe2e40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/json-rte-serializer", - "version": "2.0.6", + "version": "2.0.7", "description": "This Package converts Html Document to Json and vice-versa.", "main": "lib/index.js", "module": "lib/index.mjs", From 70a3a9d1d866618064c7ef893fc30e95689bddec Mon Sep 17 00:00:00 2001 From: Ashwini-Rathod Date: Fri, 7 Jun 2024 15:03:46 +0530 Subject: [PATCH 2/2] feat: json to markdown serializer --- README.md | 42 ++- package-lock.json | 65 +++- package.json | 2 + src/index.tsx | 3 +- src/jsonToMarkdown.tsx | 239 +++++++++++++ src/types.ts | 2 + test/expectedMarkdown.ts | 650 ++++++++++++++++++++++++++++++++++++ test/jsonToMarkdown.test.ts | 12 + 8 files changed, 1009 insertions(+), 6 deletions(-) create mode 100644 src/jsonToMarkdown.tsx create mode 100644 test/expectedMarkdown.ts create mode 100644 test/jsonToMarkdown.test.ts diff --git a/README.md b/README.md index 7377114..5921e5d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. [Read more](https://www.contentstack.com/docs/). -The JSON RTE Serializer package helps you convert the data inside your JSON Rich Text Editor field from JSON to HTML format and vice versa. +The JSON RTE Serializer package assists in converting the content within your JSON Rich Text Editor field between JSON and HTML formats. This means you can easily change your data from JSON format to HTML format for display purposes, and vice versa, for data storage or processing. +If you need to convert JSON to Markdown format, we offer the Markdown Serializer function. This function is specifically designed to transform your JSON data into Markdown, making it easier to handle text formatting for platforms that use Markdown. # Installation @@ -102,6 +103,45 @@ The resulting JSON-formatted data will look as follows: } ``` +### JSON to Markdown Conversion Code + +You can use the following JSON RTE Serializer code to convert your JSON RTE field data into Markdown format. + +```javascript +import { jsonToMarkdown } from "@contentstack/json-rte-serializer"; + +const markdownValue = jsonToHtml({ + type: "doc", + attrs: {}, + uid: "547a479c68824767ce1d9725852f042b", + children: [ + { + uid: "767a479c6882471d9725852f042b67ce", + type: "p", + attrs: {}, + children: [ + { text: "This is Markdown-formatted content which has some " }, + { text: "BOLD", bold: true }, + { text: " text and some "}, + { text: "Italic", italic: true }, + { text: " text."} + ] + }, + ], +}); + +console.log(markdownValue); +``` + +### Result of Conversion + +The resulting Markdown data will look as follows: + +```MARKDOWN + +This is Markdown-formatted content which has some **BOLD** text and some *Italic* text. +``` + ## Custom Conversion For customized conversion scenarios, you can customize your JSON RTE Serializer code to allow the support for additional tags or element types in the JSON Rich Text Editor field. Pass an `options` field (optional) within the `jsonToHtml` or `htmlToJson` method to manipulate the working of the JSON RTE Serializer package as per your requirements. diff --git a/package-lock.json b/package-lock.json index 4604283..f88724b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "array-flat-polyfill": "^1.0.1", + "lodash": "^4.17.21", "lodash.clonedeep": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isempty": "^4.4.0", @@ -18,6 +19,7 @@ "lodash.isplainobject": "^4.0.6", "lodash.isundefined": "^3.0.1", "lodash.kebabcase": "^4.1.1", + "slate": "^0.103.0", "uuid": "^8.3.2" }, "devDependencies": { @@ -2673,6 +2675,15 @@ "node": ">=0.10.0" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -2762,6 +2773,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -3819,8 +3838,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -4444,6 +4462,16 @@ "node": ">=8" } }, + "node_modules/slate": { + "version": "0.103.0", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.103.0.tgz", + "integrity": "sha512-eCUOVqUpADYMZ59O37QQvUdnFG+8rin0OGQAXNHvHbQeVJ67Bu0spQbcy621vtf8GQUXTEQBlk6OP9atwwob4w==", + "dependencies": { + "immer": "^10.0.3", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4629,6 +4657,11 @@ "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==", "dev": true }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -7017,6 +7050,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==" + }, "import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -7082,6 +7120,11 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -7906,8 +7949,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.clonedeep": { "version": "4.5.0", @@ -8391,6 +8433,16 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slate": { + "version": "0.103.0", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.103.0.tgz", + "integrity": "sha512-eCUOVqUpADYMZ59O37QQvUdnFG+8rin0OGQAXNHvHbQeVJ67Bu0spQbcy621vtf8GQUXTEQBlk6OP9atwwob4w==", + "requires": { + "immer": "^10.0.3", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8528,6 +8580,11 @@ "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==", "dev": true }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/package.json b/package.json index ef21d74..1fe2e40 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ }, "dependencies": { "array-flat-polyfill": "^1.0.1", + "lodash": "^4.17.21", "lodash.clonedeep": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isempty": "^4.4.0", @@ -57,6 +58,7 @@ "lodash.isplainobject": "^4.0.6", "lodash.isundefined": "^3.0.1", "lodash.kebabcase": "^4.1.1", + "slate": "^0.103.0", "uuid": "^8.3.2" }, "files": [ diff --git a/src/index.tsx b/src/index.tsx index 4a1da63..d1e48ff 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,6 @@ import "array-flat-polyfill" import { fromRedactor } from "./fromRedactor" import { toRedactor } from "./toRedactor" +import {jsonToMarkdownSerializer} from './jsonToMarkdown' export * from "./types" -export { fromRedactor as htmlToJson, toRedactor as jsonToHtml } \ No newline at end of file +export { fromRedactor as htmlToJson, toRedactor as jsonToHtml, jsonToMarkdownSerializer as jsonToMarkdown } \ No newline at end of file diff --git a/src/jsonToMarkdown.tsx b/src/jsonToMarkdown.tsx new file mode 100644 index 0000000..1a7b84b --- /dev/null +++ b/src/jsonToMarkdown.tsx @@ -0,0 +1,239 @@ +import {IJsonToMarkdownElementTags, IJsonToMarkdownTextTags} from './types' +import {cloneDeep} from 'lodash' +import {Node} from 'slate' + +let listTypes = ['ol', 'ul'] +const elementsToAvoidWithinMarkdownTable = ['ol', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'reference', 'img', 'fragment'] + +const ELEMENT_TYPES: IJsonToMarkdownElementTags = { + 'blockquote': (attrs: any, child: any) => { + return ` + +> ${child}` + }, + 'h1': (attrs: any, child: string) => { + return ` + +#${child}#` + }, + 'h2': (attrs: any, child: any) => { + return ` + +##${child}##` + }, + 'h3': (attrs: any, child: any) => { + return ` + +###${child}###` + }, + 'h4': (attrs: any, child: any) => { + return ` + +####${child}####` + }, + 'h5': (attrs: any, child: any) => { + return ` + +#####${child}#####` + }, + 'h6': (attrs: any, child: any) => { + return ` + +######${child}######` + }, + img: (attrsJson: any, child: any) => { + if(attrsJson) { + let imageAlt = attrsJson?.['alt'] ? attrsJson['alt'] : 'enter image description here' + let imageURL = attrsJson?.['url'] ? attrsJson['url'] : '' + return ` + +![${imageAlt}] +(${imageURL})` + } + return '' + }, + p: (attrs: any, child: any) => { + return ` + +${child}` + }, + code: (attrs: any, child: any) => { + return ` + + ${child} ` + }, + ol: (attrs: any, child: any) => { + return `${child}` + }, + ul: (attrs: any, child: any) => { + return `${child}` + }, + li: (attrs: any, child: any) => { + return `${child}` + }, + a: (attrsJson: any, child: any) => { + return `[${child}](${attrsJson.url})` + }, + hr: (attrs: any, child: any) => { + return ` + +----------` + }, + span: (attrs: any, child: any) => { + return `${child}` + }, + reference: (attrsJson: any, child: any): any => { + if(attrsJson?.['display-type'] === 'display') { + if(attrsJson) { + let assetName = attrsJson?.['asset-name'] ? attrsJson['asset-name'] : 'enter image description here' + let assetURL = attrsJson?.['asset-link'] ? attrsJson['asset-link'] : '' + return ` + +![${assetName}] +(${assetURL})` + } + } + else if(attrsJson?.['display-type'] === 'link') { + if(attrsJson) { + return `[${child}](${attrsJson?.['href'] ? attrsJson['href'] : "#"})` + } + } + }, + fragment: (attrs: any, child: any) => { + return child + }, + table: (attrs: any, child: any) => { + return `${child}` + }, + tbody: (attrs: any, child: any) => { + return `${child}` + }, + thead: (attrs: any, child: any) => { + let tableBreak = '|' + if(attrs.cols) { + if(attrs.addEmptyThead) { + let tHeadChildren = '| ' + for(let i = 0; i < attrs.cols; i++) { + tHeadChildren += '| ' + tableBreak += ' ----- |' + } + return `${tHeadChildren}\n${tableBreak}\n` + } + else{ + for(let i = 0; i < attrs.cols; i++) { + tableBreak += ' ----- |' + } + return `${child}\n${tableBreak}\n` + } + } + + return `${child}` + }, + tr: (attrs: any, child: any) => { + return `| ${child}\n` + }, + td: (attrs: any, child: any) => { + return ` ${child.trim()} |` + }, + th: (attrs: any, child: any) => { + return ` ${child.trim()} |` + } +} +const TEXT_WRAPPERS: IJsonToMarkdownTextTags = { + 'bold': (child: any, value: any) => { + return `**${child}**`; + }, + 'italic': (child: any, value: any) => { + return `*${child}*`; + }, + 'strikethrough': (child: any, value: any) => { + return `~~${child}~~`; + }, + 'inlineCode': (child: any, value: any) => { + return `\`${child}\`` + }, +} + +const getOLOrULStringFromJson = (value: any) => { + let child = '' + let nestedListFound = false + if(listTypes.includes(value.type)){ + let start = parseInt(value?.attrs?.start || 1) + let symbol = value?.attrs?.listStyleType || '- ' + Array.from(value.children).forEach((val: any, index) => { + if(val.hasOwnProperty('type') && val.type === 'li' && value.children[index + 1] && value.children[index + 1]?.type && listTypes.includes(value.children[index + 1].type)){ + let liChildren = jsonToMarkdownSerializer(val) + let nestedListChildren = getOLOrULStringFromJson(value.children[index + 1]) + let indentedNestedListChildren = nestedListChildren.split('\n').filter((child) => child.length).map((child) => ` ${child}`).join('\n') + if(value.type === 'ol') { + child += `${index + start}. ${liChildren}\n${indentedNestedListChildren}\n` + nestedListFound = true + } + if(value.type === 'ul') child += `${symbol}${liChildren}\n${indentedNestedListChildren}\n` + } + else if(val.hasOwnProperty('type') && !listTypes.includes(val.type)){ + let liChildren = jsonToMarkdownSerializer(val) + if(value.type === 'ol') child += `${nestedListFound ? (index + start - 1): index + start}. ${liChildren}\n` + if(value.type === 'ul') child += `${symbol}${liChildren}\n` + } + }) + } + return ` + +${child}` +} + +export const jsonToMarkdownSerializer = (jsonValue: any): string => { + if (jsonValue.hasOwnProperty('text')) { + let text = jsonValue['text'].replace(//g, '>') + if (jsonValue['break']) { + text += `
` + } + if (jsonValue.text.includes('\n') && !jsonValue['break']) { + text = text.replace(/\n/g, '
') + } + Object.entries(jsonValue).forEach(([key, value]) => { + if (TEXT_WRAPPERS.hasOwnProperty(key)) { + text = TEXT_WRAPPERS[key](text, value) + } + }) + return text + } + let children: any = '' + if(!jsonValue['type']) return children + if (jsonValue.children) { + children = Array.from(jsonValue.children).map((child) => jsonToMarkdownSerializer(child)) + if (jsonValue['type'] === 'blockquote') { + children = children.map((child: any) => { + if (child === '\n') { + return '
' + } + return child + }) + } + children = children.join('') + } + + if (ELEMENT_TYPES[jsonValue['type']]) { + let tableAttrs = {} + if(jsonValue['type'] === 'ol' || jsonValue['type'] === 'ul') { + //@ts-ignore + return getOLOrULStringFromJson(jsonValue) + } + if(jsonValue['type'] === 'table') { + tableAttrs = cloneDeep(jsonValue['attrs']) + let thead = Array.from(jsonValue['children']).find((child: any) => child.type && child.type === 'thead') + if(!thead) { + tableAttrs['addEmptyThead'] = true + let emptyTableHead = ELEMENT_TYPES['thead'](tableAttrs, children) + if(emptyTableHead) children = emptyTableHead + children + } + } + if(jsonValue['type'] === 'td' || jsonValue['type'] === 'th') { + let NonAllowedTableChild = Array.from(jsonValue['children']).find((child: any) => elementsToAvoidWithinMarkdownTable.includes(child.type)) + if(NonAllowedTableChild) children = Node.string(jsonValue) + } + return ELEMENT_TYPES[jsonValue['type']](jsonValue['attrs'], children) + } + return children +} diff --git a/src/types.ts b/src/types.ts index f5c79a3..adb3785 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,8 @@ export interface IHtmlToJsonElementTags { [key: string]: (el:HTMLElement) => IHt export interface IJsonToHtmlTextTags { [key: string]: (child:any, value:any) => string } export interface IJsonToHtmlElementTags { [key: string]: (attrs:string,child:string,jsonBlock:IAnyObject,extraProps?:object) => string } +export interface IJsonToMarkdownElementTags{[key: string]: (attrsJson:IAnyObject,child:string) => string} +export interface IJsonToMarkdownTextTags{ [key: string]: (child:any, value:any) => string } export interface IJsonToHtmlOptions { customElementTypes?: IJsonToHtmlElementTags, customTextWrapper?: IJsonToHtmlTextTags, diff --git a/test/expectedMarkdown.ts b/test/expectedMarkdown.ts new file mode 100644 index 0000000..9078c3c --- /dev/null +++ b/test/expectedMarkdown.ts @@ -0,0 +1,650 @@ +export default [ + { + "title": "Inline Element Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [{ + "type": "p", + "attrs": {}, + "uid": "c914a0768a1e4ade85f3a9a3aa74d962", + "children": [ + { + "text": "Hello world this is a good day. I have some " + }, + { + "text": "Bold", + "bold": true + }, + { + "text": " text, some " + }, + { + "text": "Italic", + "italic": true + }, + { + "text": " text, some " + }, + { + "text": "underlined", + "underline": true + }, + { + "text": " text." + } + ] + }, + { + "uid": "1fc3e96a71a94d56824997623d078835", + "type": "p", + "children": [ + { + "text": "Also, I have " + }, + { + "text": "sup", + "superscript": true + }, + { + "text": "superscript, " + }, + { + "text": "sub", + "subscript": true + }, + { + "text": "subscript and a line break\nand after a linebreak, we have a " + }, + { + "text": "strikethrough", + "strikethrough": true + }, + { + "text": "." + } + ], + "attrs": {} + }, + { + "uid": "5ead7fa9afe6497098827eda3a9f3667", + "type": "p", + "children": [ + { + "text": "This is a paragraph with " + }, + { + "text": "inline code", + "inlineCode": true + }, + { + "text": "." + } + ], + "attrs": {} + }] + }], + "markdown": ` + +Hello world this is a good day. I have some **Bold** text, some *Italic* text, some underlined text. + +Also, I have supsuperscript, subsubscript and a line break
and after a linebreak, we have a ~~strikethrough~~. + +This is a paragraph with \`inline code\`.` + + }, + { + "title": "Heading Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [ { + "uid": "cdfd3344c6314ee3835a7e6c5617de50", + "type": "h1", + "children": [ + { + "text": "Heading 1" + } + ], + "attrs": {} + }, + { + "uid": "32e6564f1b1d41328698cb57b1bf47ed", + "type": "h2", + "children": [ + { + "text": "Heading 2" + } + ], + "attrs": {} + }, + { + "uid": "984f55b404254e5d9ee08b67a9b63cc4", + "type": "h3", + "children": [ + { + "text": "Heading 3" + } + ], + "attrs": {} + }, + { + "uid": "53d56e38f5e04bcf894b6544e82d1df2", + "type": "h4", + "children": [ + { + "text": "Heading 4" + } + ], + "attrs": {} + }, + { + "uid": "31264ba132284ae189b70aa18db7a6d4", + "type": "h5", + "children": [ + { + "text": "Heading 5" + } + ], + "attrs": {} + }, + { + "uid": "c0a6defec7c14978b6f9da2400a5beba", + "type": "h6", + "children": [ + { + "text": "Heading 6" + } + ], + "attrs": {} + }] + }], + "markdown": ` + +#Heading 1# + +##Heading 2## + +###Heading 3### + +####Heading 4#### + +#####Heading 5##### + +######Heading 6######` +}, +{ + "title": "Block Quote Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [{ + "uid": "ed1bb8de67eb437da903bae0aa9242b5", + "type": "blockquote", + "children": [ + { + "text": "Block Quote" + } + ], + "attrs": {} + }] + }], + "markdown": ` + +> Block Quote` +}, +{ + "title": "Code Block Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [{ + "uid": "303de6b72d994df99e396abc1f87d3c0", + "type": "code", + "children": [ + { + "text": "Code Block" + } + ], + "attrs": { + "language": "html" + } + }] + }], + "markdown": ` + + Code Block ` +}, +{ + "title": "Divider Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [ + { + "uid": "f4b1d34cdd3a46519844232052f3aad6", + "type": "hr", + "children": [ + { + "text": "" + } + ], + "attrs": {} + }] + }], + "markdown": ` + +----------` +}, +{ + "title": "Reference Conversion", + "json": [ { + "uid": "6e24d3bb8d5f43e7ba1aef4faba22986", + "type": "p", + "attrs": {}, + "children": [ + { + "text": "" + }, + { + "uid": "9d496b31bfa64f0fbf4beb9859bef8de", + "type": "reference", + "attrs": { + "display-type": "display", + "asset-uid": "blt3108327519cf8663", + "content-type-uid": "sys_assets", + "asset-link": "https://images.contentstack.io/v3/assets/blt2cf669e5016d5e07/blt3108327519cf8663/65a8be2d14eace6980a5abd6/test-base-64.jpeg", + "asset-name": "test-base-64.jpeg", + "asset-type": "image/jpeg", + "type": "asset", + "class-name": "embedded-asset", + "alt": "test-base-64.jpeg", + "asset-alt": "test-base-64.jpeg", + "inline": true, + "redactor-attributes": { + "alt": "test-base-64.jpeg", + "position": "none", + "inline": true + }, + "style": {}, + "position": "none" + }, + "children": [ + { + "text": "" + } + ] + }, + { + "text": "" + } + ] + }], + "markdown": ` + + + +![test-base-64.jpeg] +(https://images.contentstack.io/v3/assets/blt2cf669e5016d5e07/blt3108327519cf8663/65a8be2d14eace6980a5abd6/test-base-64.jpeg)` +}, +{ + "title": "Anchor Link Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [ + { + "uid": "00ee249466604fe3bc1e9bc9c360c0c9", + "type": "p", + "children": [ + { + "text": "Some Paragraph and adding a " + }, + { + "uid": "2aef414d0d5d49038d472c2e59660941", + "type": "a", + "attrs": { + "url": "www.google.com", + "target": "_self" + }, + "children": [ + { + "text": "link" + } + ] + }, + { + "text": " in the middle." + } + ], + "attrs": {} + }] + }], + "markdown": ` + +Some Paragraph and adding a [link](www.google.com) in the middle.` +}, +{ + "title": "Asset as Link Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [ + { + "uid": "67a215fc034f4daf867ebc393fe7fd45", + "type": "p", + "children": [ + { + "text": "Also adding an asset as a link here. Here are the " + }, + { + "uid": "edaaa544ae9842d9b2ecd6d773b1a815", + "type": "reference", + "attrs": { + "display-type": "link", + "type": "asset", + "class-name": "embedded-entry redactor-component undefined-entry", + "asset-uid": "blt6a5e908abbd88573", + "content-type-uid": "sys_assets", + "target": "_self", + "href": "https://images.contentstack.io/v3/assets/blt2cf669e5016d5e07/blt6a5e908abbd88573/65a8bd5e14eace2183a5abcc/Phillip-Island-Penguin-Parade-10.jpeg" + }, + "children": [ + { + "text": "Penguins." + } + ] + }, + { + "text": " Enjoy!" + } + ], + "attrs": {} + }] + }], + "markdown": ` + +Also adding an asset as a link here. Here are the [Penguins.](https://images.contentstack.io/v3/assets/blt2cf669e5016d5e07/blt6a5e908abbd88573/65a8bd5e14eace2183a5abcc/Phillip-Island-Penguin-Parade-10.jpeg) Enjoy!` +}, +{ + "title": "Ordered List Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [ + { + "uid": "de365962da4a4c9eb2121d7face56b1d", + "type": "ol", + "children": [ + { + "uid": "1edc6c9df32747ca84aa16a6258d9b94", + "type": "li", + "children": [ + { + "text": "One" + } + ], + "attrs": {} + }, + { + "uid": "fa667acc02734b24bb4fb0eb4391e5f7", + "type": "ol", + "attrs": {}, + "children": [ + { + "uid": "d25d22e6485742b3a28816b84015522d", + "type": "li", + "children": [ + { + "text": "Nested One" + } + ], + "attrs": {} + }, + { + "uid": "d2ae3bacc14440c5b58656d6eebd1d29", + "type": "li", + "children": [ + { + "text": "Nested Two" + } + ], + "attrs": {} + } + ] + }, + { + "uid": "1daeae84b7504a9bb0ae3649d53e4731", + "type": "li", + "children": [ + { + "text": "Two" + } + ], + "attrs": {} + }, + { + "uid": "13d19bda8d6c4d889ad185b38961d5aa", + "type": "li", + "children": [ + { + "text": "Three" + } + ], + "attrs": {} + } + ], + "id": "e28ec771d8114561a3b7816370340166", + "attrs": {} + }] + }], + "markdown": ` + +1. One + 1. Nested One + 2. Nested Two +2. Two +3. Three +` +}, +{ + "title": "Unordered List Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [ + { + "uid": "aff3137e07be4d9b9684b5afb59d6a91", + "type": "ul", + "children": [ + { + "uid": "f2d239cc5bf043ecaf3e24cf68f894e9", + "type": "li", + "children": [ + { + "text": "UL One" + } + ], + "attrs": {} + }, + { + "uid": "98619c145a5145c6b5400a6dd38da02e", + "type": "li", + "children": [ + { + "text": "UL Two" + } + ], + "attrs": {} + }, + { + "uid": "7116006cfa0348eb93925bb3f8734631", + "type": "ul", + "attrs": {}, + "children": [ + { + "uid": "2856e1550cfb448380ac4fe1199e2d7c", + "type": "li", + "children": [ + { + "text": "nested ul one" + } + ], + "attrs": {} + } + ] + }, + { + "uid": "d1ebffb3bead4a18bc909b7217c335d6", + "type": "li", + "children": [ + { + "text": "UL Three" + } + ], + "attrs": {} + } + ], + "id": "e38afd21166441b984173ffd1b7d0b89", + "attrs": {} + }] + }], + "markdown": ` + +- UL One +- UL Two + - nested ul one +- UL Three +` +}, +{ + "title": "Table Conversion", + "json": [{ + "type": "doc", + "attrs": {}, + "uid": "08560d5073684e608977ef6d332cf02d", + "children": [ + { + "uid": "ae13dae85fd04e4584e1f677dcc03828", + "type": "table", + "attrs": { + "rows": 2, + "cols": 2, + "colWidths": [ + 250, + 250 + ], + "disabledCols": [ + 0, + 1 + ] + }, + "children": [ + { + "type": "tbody", + "attrs": {}, + "children": [ + { + "type": "tr", + "attrs": {}, + "children": [ + { + "type": "td", + "attrs": {}, + "children": [ + { + "type": "p", + "attrs": {}, + "children": [ + { + "text": "Cell 1" + } + ], + "uid": "9f2a19d96b1e4ce4b7fa6e65a04b5809" + } + ], + "uid": "acf20185ccba45729315fa6c2ebedf89" + }, + { + "type": "td", + "attrs": {}, + "children": [ + { + "type": "p", + "attrs": {}, + "children": [ + { + "text": "Cell 2" + } + ], + "uid": "058aba304d084b76a048d2b3b4591029" + } + ], + "uid": "3b99a891f8ae408199b73ad1fd0e2326" + } + ], + "uid": "597ccff75f4d4587882cdbdfaa43d2db" + }, + { + "type": "tr", + "attrs": {}, + "children": [ + { + "type": "td", + "attrs": { + "colSpan": 2, + "redactor-attributes": { + "colspan": 2 + } + }, + "children": [ + { + "type": "p", + "attrs": {}, + "children": [ + { + "text": "Cell 3" + } + ], + "uid": "d3a7b3cc7419402bac83adc91f3bddaa" + } + ], + "uid": "0504ac4bc6db4b7e848912e76aa04d6c" + }, + { + "uid": "17e95a5eb8e740f58e3034530b1227ef", + "type": "td", + "attrs": { + "void": true + }, + "children": [ + { + "text": "" + } + ] + } + ], + "uid": "eb9515fa140f484b92a61b1ed9b06063" + } + ], + "uid": "a3e2a861a2e1486e9c3a7c2a3882c449" + } + ] + }] + }], + "markdown": `| | | +| ----- | ----- | +| Cell 1 | Cell 2 | +| Cell 3 | | +` +}, +] \ No newline at end of file diff --git a/test/jsonToMarkdown.test.ts b/test/jsonToMarkdown.test.ts new file mode 100644 index 0000000..b759426 --- /dev/null +++ b/test/jsonToMarkdown.test.ts @@ -0,0 +1,12 @@ +import { jsonToMarkdownSerializer } from "../src/jsonToMarkdown" +import expectedMarkdown from "./expectedMarkdown" + +describe("Testing json to markdown conversion", () => { + for (const testCase of [...expectedMarkdown]) { + it(testCase.title, () => { + let jsonValue = testCase.json + let markdownValue = jsonToMarkdownSerializer(jsonValue[0]) + expect(markdownValue).toEqual(testCase.markdown) + }) + } +}) \ No newline at end of file