diff --git a/.talismanrc b/.talismanrc index baebc93..77ed317 100644 --- a/.talismanrc +++ b/.talismanrc @@ -28,3 +28,8 @@ fileignoreconfig: checksum: cccb3cd93c499acc87593eca5cc032e256c11cf530d4de67ece09e57fc430215 - filename: test/expectedJson.ts checksum: a1966b0b3993c8e3a0e9e45de49204e7788ba74ba0089a8a6b6eba0729f990bd +- filename: package-lock.json + checksum: 96da2dcdb517a744b09062fad7fbe38f49e4efe5535a3e9e65a94805e7e4808c +- filename: src/toRedactor.tsx + checksum: c6792b5b19cf89024ab33333a77d22af0375d4976c2fc64dae1d2f5971493397 +version: "1.0" \ No newline at end of file diff --git a/LICENSE b/LICENSE index 60c49c4..ecd01a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Contentstack +Copyright (c) 2021-2026 Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package-lock.json b/package-lock.json index 4047ef4..375799e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "array-flat-polyfill": "^1.0.1", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lodash.clonedeep": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isempty": "^4.4.0", @@ -3836,9 +3836,10 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -7947,9 +7948,9 @@ } }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" }, "lodash.clonedeep": { "version": "4.5.0", diff --git a/package.json b/package.json index d2e3a7d..a71effc 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ }, "dependencies": { "array-flat-polyfill": "^1.0.1", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lodash.clonedeep": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isempty": "^4.4.0", diff --git a/src/fromRedactor.tsx b/src/fromRedactor.tsx index 21c4808..0439144 100644 --- a/src/fromRedactor.tsx +++ b/src/fromRedactor.tsx @@ -452,8 +452,13 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject } if (el.style) { let allStyleAttrs: { [key: string]: any } = {} + const hasIndentLevel = redactor['data-indent-level'] Array.from({ length: el.style.length }).forEach((child, index) => { let property = el.style.item(index) + // If data-indent-level is present, skip margin-left as indent-level is source of truth + if (hasIndentLevel && kebabCase(property) === 'margin-left') { + return + } allStyleAttrs[kebabCase(property)] = el.style.getPropertyValue(property) }) elementAttrs = { @@ -476,6 +481,16 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject attrs: { ...elementAttrs['attrs'], 'data-sys-asset-uid': redactor['data-sys-asset-uid'] } } } + // If data-indent-level is present, keep it as a proper attribute (not in redactor-attributes) + // Also delete style from redactor to prevent margin-left from appearing in redactor-attributes + if (redactor['data-indent-level']) { + elementAttrs = { + ...elementAttrs, + attrs: { ...elementAttrs['attrs'], 'data-indent-level': redactor['data-indent-level'] } + } + delete redactor['data-indent-level'] + delete redactor['style'] + } elementAttrs = { ...elementAttrs, attrs: { ...elementAttrs['attrs'], "redactor-attributes": redactor } } } diff --git a/src/toRedactor.tsx b/src/toRedactor.tsx index 6ff92a0..1bfd4b5 100644 --- a/src/toRedactor.tsx +++ b/src/toRedactor.tsx @@ -211,10 +211,20 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string } if (allattrs['style'] && jsonValue['type'] !== 'img') { Object.keys(allattrs['style']).forEach((key) => { + // If data-indent-level is present, skip margin-left from style as indent-level is source of truth + if (allattrs['data-indent-level'] && kebbab(key) === 'margin-left') { + return + } style += `${kebbab(key)}: ${allattrs.style[key]};` }) delete allattrs['style'] } + if (allattrs['data-indent-level']) { + const indentLevel = Number(allattrs['data-indent-level']) + if (!isNaN(indentLevel) && indentLevel > 0) { + style += `margin-left: ${indentLevel * 30}px;` + } + } if (allattrs['rows'] && allattrs['cols'] && allattrs['colWidths']) { delete allattrs['rows'] delete allattrs['cols'] diff --git a/test/fromRedactor.test.ts b/test/fromRedactor.test.ts index 01b3f0e..b13ef08 100644 --- a/test/fromRedactor.test.ts +++ b/test/fromRedactor.test.ts @@ -421,10 +421,58 @@ describe("ELEMENT_TAGS", () => { }) }) -function htmlToJson (html: string, options: IHtmlToJsonOptions) { +function htmlToJson (html: string, options?: IHtmlToJsonOptions) { const dom = new JSDOM(html); let htmlDoc = dom.window.document.querySelector("body"); return fromRedactor(htmlDoc, options); } +describe("data-indent-level handling", () => { + test("should keep data-indent-level as a proper attribute, not in redactor-attributes", () => { + const html = `

Indented paragraph

` + const json = htmlToJson(html) + + expect(json.children[0].attrs['data-indent-level']).toBe('2') + expect(json.children[0].attrs['redactor-attributes']['data-indent-level']).toBeUndefined() + }) + + test("should not include margin-left in style when data-indent-level is present", () => { + const html = `

Indented paragraph

` + const json = htmlToJson(html) + + expect(json.children[0].attrs.style['margin-left']).toBeUndefined() + expect(json.children[0].attrs.style['color']).toBe('red') + expect(json.children[0].attrs['data-indent-level']).toBe('2') + }) + + test("should include margin-left in style when data-indent-level is NOT present", () => { + const html = `

Non-indented paragraph

` + const json = htmlToJson(html) + + expect(json.children[0].attrs.style['margin-left']).toBe('60px') + expect(json.children[0].attrs.style['color']).toBe('blue') + expect(json.children[0].attrs['data-indent-level']).toBeUndefined() + }) + + test("should not include style in redactor-attributes when data-indent-level is present", () => { + const html = `

Deeply indented

` + const json = htmlToJson(html) + + expect(json.children[0].attrs['redactor-attributes']['style']).toBeUndefined() + expect(json.children[0].attrs['data-indent-level']).toBe('3') + }) + + test("should handle data-indent-level with multiple style properties correctly", () => { + const html = `

Indented heading

` + const json = htmlToJson(html) + + expect(json.children[0].attrs['data-indent-level']).toBe('1') + expect(json.children[0].attrs.style['margin-left']).toBeUndefined() + expect(json.children[0].attrs.style['text-align']).toBe('center') + expect(json.children[0].attrs.style['font-size']).toBe('24px') + expect(json.children[0].attrs['redactor-attributes']['data-indent-level']).toBeUndefined() + expect(json.children[0].attrs['redactor-attributes']['style']).toBeUndefined() + }) +}) + diff --git a/test/toRedactor.test.ts b/test/toRedactor.test.ts index 97997c7..ef9cbf1 100644 --- a/test/toRedactor.test.ts +++ b/test/toRedactor.test.ts @@ -353,3 +353,85 @@ test("should convert codeblock to proper html, where \n should not be replaced w const html = toRedactor(json); expect(html).toBe(`
Hi\nHello
`); }) + +describe("data-indent-level handling", () => { + test("should generate margin-left based on data-indent-level value", () => { + const json = { + "type": "doc", + "attrs": {}, + "children": [{ + "type": "p", + "attrs": { "data-indent-level": "2" }, + "children": [{ "text": "Indented paragraph" }] + }] + } + const html = toRedactor(json) + expect(html).toBe('

Indented paragraph

') + }) + + test("should use data-indent-level instead of margin-left from style when both are present", () => { + const json = { + "type": "doc", + "attrs": {}, + "children": [{ + "type": "p", + "attrs": { + "data-indent-level": "3", + "style": { "margin-left": "100px", "color": "red" } + }, + "children": [{ "text": "Indented paragraph" }] + }] + } + const html = toRedactor(json) + // margin-left should be 90px (3 * 30px) not 100px, and color should be preserved + expect(html).toBe('

Indented paragraph

') + }) + + test("should preserve margin-left from style when data-indent-level is NOT present", () => { + const json = { + "type": "doc", + "attrs": {}, + "children": [{ + "type": "p", + "attrs": { + "style": { "margin-left": "100px" } + }, + "children": [{ "text": "Non-indented paragraph" }] + }] + } + const html = toRedactor(json) + expect(html).toBe('

Non-indented paragraph

') + }) + + test("should not add margin-left when data-indent-level is 0", () => { + const json = { + "type": "doc", + "attrs": {}, + "children": [{ + "type": "p", + "attrs": { "data-indent-level": "0" }, + "children": [{ "text": "No indent" }] + }] + } + const html = toRedactor(json) + expect(html).toBe('

No indent

') + }) + + test("should handle data-indent-level with multiple style properties correctly", () => { + const json = { + "type": "doc", + "attrs": {}, + "children": [{ + "type": "h2", + "attrs": { + "data-indent-level": "1", + "style": { "margin-left": "50px", "text-align": "center", "font-size": "24px" } + }, + "children": [{ "text": "Indented heading" }] + }] + } + const html = toRedactor(json) + // margin-left should be 30px (1 * 30px), other styles should be preserved + expect(html).toBe('

Indented heading

') + }) +})