diff --git a/package.json b/package.json index 2746f10..f95fd8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/json-rte-serializer", - "version": "2.0.4", + "version": "2.0.5", "description": "This Package converts Html Document to Json and vice-versa.", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/fromRedactor.tsx b/src/fromRedactor.tsx index 2609d54..27242cd 100644 --- a/src/fromRedactor.tsx +++ b/src/fromRedactor.tsx @@ -51,8 +51,8 @@ const ELEMENT_TAGS: IHtmlToJsonElementTags = { THEAD: (el: HTMLElement) => ({ type: 'thead', attrs: {} }), TBODY: (el: HTMLElement) => ({ type: 'tbody', attrs: {} }), TR: (el: HTMLElement) => ({ type: 'tr', attrs: {} }), - TD: (el: HTMLElement) => ({ type: 'td', attrs: {} }), - TH: (el: HTMLElement) => ({ type: 'th', attrs: {} }), + TD: (el: HTMLElement) => ({ type: 'td', attrs: { ...spanningAttrs(el) } }), + TH: (el: HTMLElement) => ({ type: 'th', attrs: { ...spanningAttrs(el) } }), // FIGURE: (el: HTMLElement) => ({ type: 'reference', attrs: { default: true, "display-type": "display", "type": "asset" } }), FIGURE: (el: HTMLElement) => { @@ -70,6 +70,18 @@ const ELEMENT_TAGS: IHtmlToJsonElementTags = { DIV: (el: HTMLElement) => { return { type: 'div', attrs: {} } }, + VIDEO: (el: HTMLElement) => { + const srcArray = Array.from(el.querySelectorAll("source")).map((source) => + source.getAttribute("src") + ); + + return { + type: 'embed', + attrs: { + src: srcArray.length > 0 ? srcArray[0] : null, + }, + } + }, STYLE: (el: HTMLElement) => { return { type: 'style', attrs: { "style-text": el.textContent } } }, @@ -208,6 +220,28 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject } else if (el.nodeName === 'COLGROUP') { return null } + else if (el.nodeName === "TABLE") { + const tbody = el.querySelector('tbody') + const thead = el.querySelector('thead') + + if (!tbody && !thead) { + el.innerHTML += "" + } + } + else if (['TBODY', 'THEAD'].includes(el.nodeName)) { + const row = el.querySelector('tr') + if (!row) { + el.innerHTML += "" + } + } + else if (el.nodeName === 'TR') { + const cell = el.querySelector('th, td') + if (!cell) { + const cellType = el.parentElement.nodeName === 'THEAD' ? 'th' : 'td' + el.innerHTML += `<${cellType}>` + + } + } const { nodeName } = el let parent = el if(el.nodeName === "BODY"){ @@ -381,6 +415,9 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject if (nodeName === 'FIGCAPTION') { return null } + if (nodeName === 'SOURCE') { + return null; + } if (nodeName === 'DIV') { const dataType = el.attributes['data-type']?.value if (dataType === 'row') { @@ -588,7 +625,7 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject return jsx('element', elementAttrs, [{ text: '' }]) } } - if (nodeName === 'IMG' || nodeName === 'IFRAME') { + if (nodeName === 'IMG' || nodeName === 'IFRAME' || nodeName === 'VIDEO') { if (elementAttrs?.attrs?.["redactor-attributes"]?.width) { let width = elementAttrs.attrs["redactor-attributes"].width if (width.slice(width.length - 1) === '%') { @@ -613,46 +650,111 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject }) } if (nodeName === 'TABLE') { - let row = 0 + const row = el.querySelectorAll('TR').length let table_child = ['THEAD', 'TBODY'] let cell_type = ['TH', 'TD'] let col = 0 - Array.from(el.childNodes).forEach((child: any) => { - if (table_child.includes(child.nodeName)) { - row += child.childNodes.length - } - }) - let rowElement = el.getElementsByTagName('TR')[0] - if (rowElement) - Array.from(rowElement.childNodes).forEach((child: any) => { - if (cell_type.includes(child.nodeName)) { - col += 1 - } - }) + + const colElementLength = el.getElementsByTagName('COLGROUP')[0]?.children?.length ?? 0 + col = Math.max(...Array.from(el.getElementsByTagName('TR')).map((row: any) => row.children.length), colElementLength) let colWidths: Array = Array.from({ length: col }).fill(250) - if (el?.childNodes?.[0]?.nodeName === 'COLGROUP') { - let colGroupWidth: Array = [] - let totalWidth = parseFloat(el.childNodes[0].getAttribute('data-width')) || col * 250 - Array.from(el.childNodes[0].childNodes).forEach((child: any) => { - let width = child?.style?.width || '250px' - if (width.slice(width.length - 1) === '%') { - colGroupWidth.push((parseFloat(width.slice(0, width.length - 1)) * totalWidth) / 100) - } else if (width.slice(width.length - 2) === 'px') { - colGroupWidth.push(parseFloat(width.slice(0, width.length - 2))) + + Array.from(el.childNodes).forEach((child: any) => { + if (child?.nodeName === 'COLGROUP') { + let colGroupWidth = Array(col).fill(250) + let totalWidth = parseFloat(child.getAttribute('data-width')) || col * 250 + Array.from(child.children).forEach((child: any, index) => { + if (child?.nodeName === 'COL') { + let width = child?.style?.width ?? '250px' + if (width.substr(-1) === '%') { + colGroupWidth[index] = (parseFloat(width.slice(0, width.length - 1)) * totalWidth) / 100 + } else if (width.substr(-2) === 'px') { + colGroupWidth[index] = parseFloat(width.slice(0, width.length - 2)) + } } }) colWidths = colGroupWidth } + }) + let tableHead : any + let tableBody: any + + children.forEach((tableChild: any) => { + if (tableChild?.type === 'thead') { + tableHead = tableChild + return + } + if (tableChild?.type === 'tbody') { + tableBody = tableChild + return + } + }); + + let disabledCols = [...tableHead?.attrs?.disabledCols ?? [], ...tableBody?.attrs?.disabledCols ?? []] + delete tableHead?.attrs?.disabledCols + delete tableBody?.attrs?.disabledCols + + const tableAttrs = { + ...elementAttrs.attrs, + rows: row, + cols: col, + colWidths: colWidths, + } + + if(!isEmpty(disabledCols)){ + tableAttrs['disabledCols'] = Array.from(new Set(disabledCols)) + } + elementAttrs = { ...elementAttrs, - attrs: { - ...elementAttrs.attrs, - rows: row, - cols: col, - colWidths: colWidths - } + attrs: tableAttrs } } + if (["THEAD", "TBODY"].includes(nodeName)) { + const rows = children as any[] + const disabledCols = rows.flatMap(row => { + const { disabledCols } = row.attrs + delete row['attrs']['disabledCols'] + return disabledCols ?? [] + }) + elementAttrs.attrs['disabledCols'] = disabledCols + } + if (nodeName === "TBODY") { + + const rows = children + + addVoidCellsAndApplyAttributes(rows) + + children = getTbodyChildren(rows) + } + + if (nodeName === "TR") { + + const cells = children.filter((child:any) => ['th', 'td'].includes(child.type)) as any[] + + + const disabledCols = cells.flatMap((cell, cellIndex) => { + let { colSpan } = cell.attrs + if (!colSpan) return [] + colSpan = parseInt(colSpan) + return Array(colSpan).fill(0).map((_, i) => cellIndex + i) + }) + + if (disabledCols?.length) + elementAttrs.attrs['disabledCols'] = disabledCols + + + } + if (['TD', 'TH'].includes(nodeName)) { + const { colSpan = 1, rowSpan } = elementAttrs?.['attrs'] + + return [ + jsx('element', elementAttrs, children), + ...Array(colSpan - 1) + .fill(0) + .map((_) => emptyCell(nodeName.toLowerCase(), rowSpan ? { inducedRowSpan: rowSpan } : {})) + ] + } if (nodeName === 'P') { if ( elementAttrs?.attrs?.["redactor-attributes"]?.['data-checked'] && @@ -809,3 +911,84 @@ export const getNestedValueIfAvailable = (value: string) => { return value; } }; + + +const spanningAttrs = (el: HTMLElement) => { + const attrs = {} + const rowSpan = parseInt(el.getAttribute('rowspan') ?? '1') + const colSpan = parseInt(el.getAttribute('colspan') ?? '1') + if (rowSpan > 1) attrs['rowSpan'] = rowSpan + if (colSpan > 1) attrs['colSpan'] = colSpan + + return attrs +} +const emptyCell = (cellType: string, attrs = {}) => { + return jsx('element', { type: cellType, attrs: { void: true, ...attrs } }, [{ text: '' }]) +} + +const addVoidCellsAndApplyAttributes = (rows: any[]) => { + rows.forEach((row, currIndex) => { + const cells = row.children as any[] + + cells.forEach((cell, cellIndex) => { + if (!cell || !cell.attrs) return + + const { rowSpan, inducedRowSpan } = cell.attrs + + let span = rowSpan ?? inducedRowSpan ?? 0 + + if (!span || span < 2) return + const nextRow = rows[currIndex + 1] + if (!nextRow) { + delete cell?.attrs?.inducedRowSpan + return + } + + // set inducedRowSpan on cell in row at cellIndex + span-- + nextRow?.children?.splice(cellIndex, 0, + emptyCell('td', + (span > 1) ? { inducedRowSpan: span } : {} + )) + + // Include next row in trgrp + nextRow['attrs']['included'] = true + + // Make a new trgrp + if (rowSpan) { + row['attrs']['origin'] = true + } + + delete cell?.['attrs']?.['inducedRowSpan'] + + }) + }) +} + + +function getTbodyChildren (rows: any[]) { + const newTbodyChildren = rows.reduce((tBodyChildren, row, rowIndex) => { + + const { included, origin } = row.attrs + const l = tBodyChildren.length + + if (included || origin) { + + if (origin && !included) { + tBodyChildren.push(jsx('element', { type: 'trgrp' }, row)) + } + if (included) { + tBodyChildren[l - 1].children.push(row) + + } + delete row['attrs']['included'] + delete row['attrs']['origin'] + return tBodyChildren + } + + tBodyChildren.push(row) + return tBodyChildren + + }, []) + return newTbodyChildren +} \ No newline at end of file diff --git a/src/toRedactor.tsx b/src/toRedactor.tsx index 829a7d8..0be5c64 100644 --- a/src/toRedactor.tsx +++ b/src/toRedactor.tsx @@ -80,6 +80,9 @@ const ELEMENT_TYPES: IJsonToHtmlElementTags = { tr: (attrs: any, child: any) => { return `${child}` }, + trgrp: (attrs: any, child: any) => { + return child + }, td: (attrs: any, child: any) => { return `${child}` }, @@ -399,6 +402,15 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string delete allattrs['cols'] delete allattrs['colWidths'] } + if (allattrs['disabledCols']) { + delete allattrs['disabledCols'] + } + if (allattrs['colSpan']) { + delete allattrs['colSpan'] + } + if (allattrs['rowSpan']) { + delete allattrs['rowSpan'] + } attrsJson = { ...attrsJson, ...allattrs, style: style } if (jsonValue['type'] === 'reference') { @@ -482,7 +494,10 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string if (!(setCol.size === 1 && jsonValue.attrs.cols * setCol.values().next().value === totalWidth)) { let col = '' Array.from(colWidths).forEach( - (child, index) => (col += ``) + (colWidth, index) => { + const width = (colWidth as number / totalWidth) * 100 + col += `` + } ) let colgroup = `${col}` children = colgroup + children @@ -521,6 +536,11 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string return children } } + + if(['td','th'].includes(jsonValue['type'])){ + if(jsonValue?.['attrs']?.['void']) return '' + } + attrs = (attrs.trim() ? ' ' : '') + attrs.trim() return ELEMENT_TYPES[orgType || jsonValue['type']](attrs, children,jsonValue, figureStyles) diff --git a/test/expectedJson.ts b/test/expectedJson.ts index d2e0986..f2584d0 100644 --- a/test/expectedJson.ts +++ b/test/expectedJson.ts @@ -1755,5 +1755,117 @@ export default { json: {"type":"doc","uid":"uid","attrs":{},"children":[{"type":"hangout-module","attrs":{},"children":[{"type":"hangout-chat","attrs":{"from":"Paul, Addy","nested-json":{"to":"Hello World","more-nesting":{"from":"Beautiful World"}}},"children":[{"type":"hangout-discussion","attrs":{},"children":[{"type":"hangout-message","attrs":{"from":"Paul","profile":"profile.png","datetime":"2013-07-17T12:02"},"children":[{"type":"p","uid":"uid","attrs":{},"children":[{"text":"Feelin' this Web Components thing."}]},{"type":"p","uid":"uid","attrs":{},"children":[{"text":"Heard of it?"}]}]}]}]},{"type":"hangout-chat","attrs":{},"children":[{"text":"Hi There!"}]}]}]}, html : `

Feelin' this Web Components thing.

Heard of it?

Hi There!
` }, - ] + ], + "video-tag": [ + { + json: {"type":"doc","uid":"cfd06695ff85459c90f2d2d4da01af10","attrs":{},"children":[{"type":"embed","attrs":{"src":"https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"},"uid":"2c144d07b3a14d8f98c78125b964edcb","children":[{"text":""}]}]}, + html: ``}, + { + json: {"type":"doc","uid":"00459467e3184fdcab0ba1819f0e3645","attrs":{},"children":[{"type":"embed","attrs":{"src":"https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm","style":{},"redactor-attributes":{"controls":"","width":"250"},"width":250},"uid":"83fb5d7ce52c4f78872d33478bb12f2f","children":[{"text":""}]}]}, + html: ``, + } + ], + 'table-rowspan-colspan': { + html: `

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
h1
12
34
567
8
910
11121314
+

`, + expectedJson: {"type":"doc","uid":"uid","attrs":{},"children":[{"type":"p","attrs":{},"uid":"uid","children":[{"text":""}]},{"type":"table","attrs":{"style":{"text-align":"center"},"redactor-attributes":{"style":"text-align: center;"},"rows":7,"cols":4,"colWidths":[116.99979767422,109.00003591007999,114.00005388759998,104.9999325281],"disabledCols":[0,1,2,3]},"uid":"uid","children":[{"type":"thead","attrs":{"style":{"text-align":"center"},"redactor-attributes":{"style":"text-align: center;"}},"uid":"uid","children":[{"type":"tr","attrs":{"style":{"text-align":"center"},"redactor-attributes":{"style":"text-align: center;"}},"uid":"uid","children":[{"type":"th","attrs":{"colSpan":4,"style":{"text-align":"center"},"redactor-attributes":{"colspan":"4","style":"text-align: center;"}},"uid":"uid","children":[{"text":"h1"}]},{"type":"th","attrs":{"void":true},"children":[{"text":""}]},{"type":"th","attrs":{"void":true},"children":[{"text":""}]},{"type":"th","attrs":{"void":true},"children":[{"text":""}]}]}]},{"type":"tbody","attrs":{},"uid":"uid","children":[{"type":"trgrp","children":[{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"rowSpan":2,"colSpan":2,"style":{},"redactor-attributes":{"rowspan":"2","colspan":"2"}},"uid":"uid","children":[{"text":"1"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{"colSpan":2,"style":{},"redactor-attributes":{"colspan":"2"}},"uid":"uid","children":[{"text":"2"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"3"}]},{"type":"td","attrs":{"rowSpan":4,"style":{},"redactor-attributes":{"rowspan":"4"}},"uid":"uid","children":[{"text":"4"}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"rowSpan":3,"style":{},"redactor-attributes":{"rowspan":"3"}},"uid":"uid","children":[{"text":"5"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"6"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"7"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{"colSpan":2,"style":{},"redactor-attributes":{"colspan":"2"}},"uid":"uid","children":[{"text":"8"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"9"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"10"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{},"uid":"uid","children":[{"text":"11"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"12"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"13"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"14"}]}]}]}]},{"type":"p","attrs":{},"uid":"uid","children":[{"text":""}]}]} + }, + 'table-rowspan-colspan-2': { + html: `

+ + + + + + + + + + + + + + + +
+

`, + expectedJson: {"type":"doc","uid":"uid","attrs":{},"children":[{"type":"p","attrs":{},"uid":"uid","children":[{"text":""}]},{"type":"table","attrs":{"rows":3,"cols":2,"colWidths":[250,250]},"uid":"uid","children":[{"type":"tbody","attrs":{},"uid":"uid","children":[{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{},"uid":"uid","children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":""}]}]},{"type":"trgrp","children":[{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"rowSpan":4,"style":{},"redactor-attributes":{"rowspan":"4"}},"uid":"uid","children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":""}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":""}]}]}]}]}]},{"type":"p","attrs":{},"uid":"uid","children":[{"text":""}]}]} + }, + 'table-rowspan-colspan-3': { + html: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Header 1Header 2Header 3
Row 1, Col 1Row 1, Col 2Row 1, Col 3Row 1, Col 4
Row 2, Col 2Row 2, Col 3Row 2, Col 4
Row 3, Col 1Row 3, Col 2Row 3, Col 5
Row 4, Col 1Row 4, Col 5
+ `, + expectedJson: {"type":"doc","uid":"uid","attrs":{},"children":[{"type":"table","attrs":{"style":{},"redactor-attributes":{"border":"1"},"rows":5,"cols":4,"colWidths":[250,250,250,250],"disabledCols":[1,2,3]},"uid":"uid","children":[{"type":"thead","attrs":{},"uid":"uid","children":[{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"th","attrs":{},"uid":"uid","children":[{"text":"Header 1"}]},{"type":"th","attrs":{"colSpan":3,"style":{},"redactor-attributes":{"colspan":"3"}},"uid":"uid","children":[{"text":"Header 2"}]},{"type":"th","attrs":{"void":true},"children":[{"text":""}]},{"type":"th","attrs":{"void":true},"children":[{"text":""}]},{"type":"th","attrs":{},"uid":"uid","children":[{"text":"Header 3"}]}]}]},{"type":"tbody","attrs":{},"uid":"uid","children":[{"type":"trgrp","children":[{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"rowSpan":2,"style":{},"redactor-attributes":{"rowspan":"2"}},"uid":"uid","children":[{"text":"Row 1, Col 1"}]},{"type":"td","attrs":{"colSpan":2,"style":{},"redactor-attributes":{"colspan":"2"}},"uid":"uid","children":[{"text":"Row 1, Col 2"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 1, Col 3"}]},{"type":"td","attrs":{"rowSpan":2,"style":{},"redactor-attributes":{"rowspan":"2"}},"uid":"uid","children":[{"text":"Row 1, Col 4"}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 2, Col 2"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 2, Col 3"}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 2, Col 4"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]}]}]},{"type":"trgrp","children":[{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 3, Col 1"}]},{"type":"td","attrs":{"rowSpan":2,"colSpan":3,"style":{},"redactor-attributes":{"colspan":"3","rowspan":"2"}},"uid":"uid","children":[{"text":"Row 3, Col 2"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 3, Col 5"}]}]},{"type":"tr","attrs":{},"uid":"uid","children":[{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 4, Col 1"}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{"void":true},"children":[{"text":""}]},{"type":"td","attrs":{},"uid":"uid","children":[{"text":"Row 4, Col 5"}]}]}]}]}]}]} + }, + } \ No newline at end of file diff --git a/test/fromRedactor.test.ts b/test/fromRedactor.test.ts index e35ffc1..7efee5c 100644 --- a/test/fromRedactor.test.ts +++ b/test/fromRedactor.test.ts @@ -303,5 +303,33 @@ describe("CS-41001", () =>{ const jsonValue = fromRedactor(body); expect(jsonValue).toStrictEqual({"type":"doc","uid":"uid","attrs":{},"children":[{"type":"p","attrs":{},"uid":"uid","children":[{"text":"Hello"}]},{"type":"fragment","attrs":{},"uid":"uid","children":[{"text":" beautiful "}]},{"type":"p","attrs":{},"uid":"uid","children":[{"text":"World"}]}]}) }) + test("should convert video tag into embed", () => { + expectedValue['video-tag'].forEach((testCase) => { + const dom = new JSDOM(testCase.html); + let htmlDoc = dom.window.document.querySelector("body"); + const jsonValue = fromRedactor(htmlDoc); + expect(omitdeep(jsonValue, "uid")).toStrictEqual( omitdeep(testCase.json, "uid")) + }) + }) + + test('table JSON should have proper structure with rowspan and colspan', () => { + const testCases = ['table-rowspan-colspan', 'table-rowspan-colspan-2', 'table-rowspan-colspan-3'] + testCases.forEach(testCase => { + try { + const { html, expectedJson } = expectedValue[testCase] + const json = htmlToJson(html) + expect(json).toStrictEqual(expectedJson) + } + catch (e) { + throw new Error(`Test failed for ${testCase} - ${e}`) + } + }) + }) }) +function htmlToJson (html, options) { + const dom = new JSDOM(html); + let htmlDoc = dom.window.document.querySelector("body"); + return fromRedactor(htmlDoc, options); + +} \ No newline at end of file diff --git a/test/toRedactor.test.ts b/test/toRedactor.test.ts index 38e84bb..df9b87c 100644 --- a/test/toRedactor.test.ts +++ b/test/toRedactor.test.ts @@ -223,4 +223,18 @@ describe("Testing json to html conversion", () => { }); }); }); + + test("should convert rowspan and colspan tables to proper html", () => { + + const { html: expectedHtml, expectedJson: json } = { + html: `

h1
12
34
567
8
910
11121314

`, + expectedJson: [{ "type": "p", "attrs": {}, "uid": "uid", "children": [{ "text": "" }] }, { "type": "table", "attrs": { "style": { "text-align": "center" }, "redactor-attributes": { "style": "text-align: center;" }, "rows": 7, "cols": 4, "colWidths": [116.99979767422, 109.00003591007999, 114.00005388759998, 104.9999325281], "disabledCols": [0, 1, 2, 3] }, "uid": "uid", "children": [{ "type": "thead", "attrs": { "style": { "text-align": "center" }, "redactor-attributes": { "style": "text-align: center;" } }, "uid": "uid", "children": [{ "type": "tr", "attrs": { "style": { "text-align": "center" }, "redactor-attributes": { "style": "text-align: center;" } }, "uid": "uid", "children": [{ "type": "th", "attrs": { "colSpan": 4, "style": { "text-align": "center" }, "redactor-attributes": { "colspan": "4", "style": "text-align: center;" } }, "uid": "uid", "children": [{ "text": "h1" }] }, { "type": "th", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "th", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "th", "attrs": { "void": true }, "children": [{ "text": "" }] }] }] }, { "type": "tbody", "attrs": {}, "uid": "uid", "children": [{ "type": "trgrp", "children": [{ "type": "tr", "attrs": {}, "uid": "uid", "children": [{ "type": "td", "attrs": { "rowSpan": 2, "colSpan": 2, "redactor-attributes": { "rowspan": "2", "colspan": "2" } }, "uid": "uid", "children": [{ "text": "1" }] }, { "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "td", "attrs": { "colSpan": 2, "redactor-attributes": { "colspan": "2" } }, "uid": "uid", "children": [{ "text": "2" }] }, { "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }] }, { "type": "tr", "attrs": {}, "uid": "uid", "children": [{ "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "3" }] }, { "type": "td", "attrs": { "rowSpan": 4, "redactor-attributes": { "rowspan": "4" } }, "uid": "uid", "children": [{ "text": "4" }] }] }, { "type": "tr", "attrs": {}, "uid": "uid", "children": [{ "type": "td", "attrs": { "rowSpan": 3, "redactor-attributes": { "rowspan": "3" } }, "uid": "uid", "children": [{ "text": "5" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "6" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "7" }] }, { "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }] }, { "type": "tr", "attrs": {}, "uid": "uid", "children": [{ "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "td", "attrs": { "colSpan": 2, "redactor-attributes": { "colspan": "2" } }, "uid": "uid", "children": [{ "text": "8" }] }, { "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }] }, { "type": "tr", "attrs": {}, "uid": "uid", "children": [{ "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "9" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "10" }] }, { "type": "td", "attrs": { "void": true }, "children": [{ "text": "" }] }] }] }, { "type": "tr", "attrs": {}, "uid": "uid", "children": [{ "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "11" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "12" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "13" }] }, { "type": "td", "attrs": {}, "uid": "uid", "children": [{ "text": "14" }] }] }] }] }, { "type": "p", "attrs": {}, "uid": "uid", "children": [{ "text": "" }] }] + } + const html = toRedactor({ + type: 'docs', + children: json + }) + expect(html).toStrictEqual(expectedHtml) + }) + }) \ No newline at end of file