Skip to content

Commit c09917b

Browse files
Merge pull request #97 from contentstack/RT-659-1
feat: indent support
2 parents 3a3ab9d + ecca7c9 commit c09917b

File tree

8 files changed

+171
-10
lines changed

8 files changed

+171
-10
lines changed

.talismanrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,8 @@ fileignoreconfig:
2828
checksum: cccb3cd93c499acc87593eca5cc032e256c11cf530d4de67ece09e57fc430215
2929
- filename: test/expectedJson.ts
3030
checksum: a1966b0b3993c8e3a0e9e45de49204e7788ba74ba0089a8a6b6eba0729f990bd
31+
- filename: package-lock.json
32+
checksum: 96da2dcdb517a744b09062fad7fbe38f49e4efe5535a3e9e65a94805e7e4808c
33+
- filename: src/toRedactor.tsx
34+
checksum: c6792b5b19cf89024ab33333a77d22af0375d4976c2fc64dae1d2f5971493397
35+
version: "1.0"

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024-2025 Contentstack
3+
Copyright (c) 2021-2026 Contentstack
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

package-lock.json

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
},
5050
"dependencies": {
5151
"array-flat-polyfill": "^1.0.1",
52-
"lodash": "^4.17.21",
52+
"lodash": "^4.17.23",
5353
"lodash.clonedeep": "^4.5.0",
5454
"lodash.flatten": "^4.4.0",
5555
"lodash.isempty": "^4.4.0",

src/fromRedactor.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,13 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
452452
}
453453
if (el.style) {
454454
let allStyleAttrs: { [key: string]: any } = {}
455+
const hasIndentLevel = redactor['data-indent-level']
455456
Array.from({ length: el.style.length }).forEach((child, index) => {
456457
let property = el.style.item(index)
458+
// If data-indent-level is present, skip margin-left as indent-level is source of truth
459+
if (hasIndentLevel && kebabCase(property) === 'margin-left') {
460+
return
461+
}
457462
allStyleAttrs[kebabCase(property)] = el.style.getPropertyValue(property)
458463
})
459464
elementAttrs = {
@@ -476,6 +481,16 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
476481
attrs: { ...elementAttrs['attrs'], 'data-sys-asset-uid': redactor['data-sys-asset-uid'] }
477482
}
478483
}
484+
// If data-indent-level is present, keep it as a proper attribute (not in redactor-attributes)
485+
// Also delete style from redactor to prevent margin-left from appearing in redactor-attributes
486+
if (redactor['data-indent-level']) {
487+
elementAttrs = {
488+
...elementAttrs,
489+
attrs: { ...elementAttrs['attrs'], 'data-indent-level': redactor['data-indent-level'] }
490+
}
491+
delete redactor['data-indent-level']
492+
delete redactor['style']
493+
}
479494

480495
elementAttrs = { ...elementAttrs, attrs: { ...elementAttrs['attrs'], "redactor-attributes": redactor } }
481496
}

src/toRedactor.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,20 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
211211
}
212212
if (allattrs['style'] && jsonValue['type'] !== 'img') {
213213
Object.keys(allattrs['style']).forEach((key) => {
214+
// If data-indent-level is present, skip margin-left from style as indent-level is source of truth
215+
if (allattrs['data-indent-level'] && kebbab(key) === 'margin-left') {
216+
return
217+
}
214218
style += `${kebbab(key)}: ${allattrs.style[key]};`
215219
})
216220
delete allattrs['style']
217221
}
222+
if (allattrs['data-indent-level']) {
223+
const indentLevel = Number(allattrs['data-indent-level'])
224+
if (!isNaN(indentLevel) && indentLevel > 0) {
225+
style += `margin-left: ${indentLevel * 30}px;`
226+
}
227+
}
218228
if (allattrs['rows'] && allattrs['cols'] && allattrs['colWidths']) {
219229
delete allattrs['rows']
220230
delete allattrs['cols']

test/fromRedactor.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,58 @@ describe("ELEMENT_TAGS", () => {
421421
})
422422
})
423423

424-
function htmlToJson (html: string, options: IHtmlToJsonOptions) {
424+
function htmlToJson (html: string, options?: IHtmlToJsonOptions) {
425425
const dom = new JSDOM(html);
426426
let htmlDoc = dom.window.document.querySelector("body");
427427
return fromRedactor(htmlDoc, options);
428428

429429
}
430430

431+
describe("data-indent-level handling", () => {
432+
test("should keep data-indent-level as a proper attribute, not in redactor-attributes", () => {
433+
const html = `<p data-indent-level="2" style="margin-left: 60px;">Indented paragraph</p>`
434+
const json = htmlToJson(html)
435+
436+
expect(json.children[0].attrs['data-indent-level']).toBe('2')
437+
expect(json.children[0].attrs['redactor-attributes']['data-indent-level']).toBeUndefined()
438+
})
439+
440+
test("should not include margin-left in style when data-indent-level is present", () => {
441+
const html = `<p data-indent-level="2" style="margin-left: 60px; color: red;">Indented paragraph</p>`
442+
const json = htmlToJson(html)
443+
444+
expect(json.children[0].attrs.style['margin-left']).toBeUndefined()
445+
expect(json.children[0].attrs.style['color']).toBe('red')
446+
expect(json.children[0].attrs['data-indent-level']).toBe('2')
447+
})
448+
449+
test("should include margin-left in style when data-indent-level is NOT present", () => {
450+
const html = `<p style="margin-left: 60px; color: blue;">Non-indented paragraph</p>`
451+
const json = htmlToJson(html)
452+
453+
expect(json.children[0].attrs.style['margin-left']).toBe('60px')
454+
expect(json.children[0].attrs.style['color']).toBe('blue')
455+
expect(json.children[0].attrs['data-indent-level']).toBeUndefined()
456+
})
457+
458+
test("should not include style in redactor-attributes when data-indent-level is present", () => {
459+
const html = `<p data-indent-level="3" style="margin-left: 90px;">Deeply indented</p>`
460+
const json = htmlToJson(html)
461+
462+
expect(json.children[0].attrs['redactor-attributes']['style']).toBeUndefined()
463+
expect(json.children[0].attrs['data-indent-level']).toBe('3')
464+
})
465+
466+
test("should handle data-indent-level with multiple style properties correctly", () => {
467+
const html = `<h2 data-indent-level="1" style="margin-left: 30px; text-align: center; font-size: 24px;">Indented heading</h2>`
468+
const json = htmlToJson(html)
469+
470+
expect(json.children[0].attrs['data-indent-level']).toBe('1')
471+
expect(json.children[0].attrs.style['margin-left']).toBeUndefined()
472+
expect(json.children[0].attrs.style['text-align']).toBe('center')
473+
expect(json.children[0].attrs.style['font-size']).toBe('24px')
474+
expect(json.children[0].attrs['redactor-attributes']['data-indent-level']).toBeUndefined()
475+
expect(json.children[0].attrs['redactor-attributes']['style']).toBeUndefined()
476+
})
477+
})
478+

test/toRedactor.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,85 @@ test("should convert codeblock to proper html, where \n should not be replaced w
353353
const html = toRedactor(json);
354354
expect(html).toBe(`<pre>Hi\nHello</pre>`);
355355
})
356+
357+
describe("data-indent-level handling", () => {
358+
test("should generate margin-left based on data-indent-level value", () => {
359+
const json = {
360+
"type": "doc",
361+
"attrs": {},
362+
"children": [{
363+
"type": "p",
364+
"attrs": { "data-indent-level": "2" },
365+
"children": [{ "text": "Indented paragraph" }]
366+
}]
367+
}
368+
const html = toRedactor(json)
369+
expect(html).toBe('<p data-indent-level="2" style="margin-left: 60px;">Indented paragraph</p>')
370+
})
371+
372+
test("should use data-indent-level instead of margin-left from style when both are present", () => {
373+
const json = {
374+
"type": "doc",
375+
"attrs": {},
376+
"children": [{
377+
"type": "p",
378+
"attrs": {
379+
"data-indent-level": "3",
380+
"style": { "margin-left": "100px", "color": "red" }
381+
},
382+
"children": [{ "text": "Indented paragraph" }]
383+
}]
384+
}
385+
const html = toRedactor(json)
386+
// margin-left should be 90px (3 * 30px) not 100px, and color should be preserved
387+
expect(html).toBe('<p data-indent-level="3" style="color: red;margin-left: 90px;">Indented paragraph</p>')
388+
})
389+
390+
test("should preserve margin-left from style when data-indent-level is NOT present", () => {
391+
const json = {
392+
"type": "doc",
393+
"attrs": {},
394+
"children": [{
395+
"type": "p",
396+
"attrs": {
397+
"style": { "margin-left": "100px" }
398+
},
399+
"children": [{ "text": "Non-indented paragraph" }]
400+
}]
401+
}
402+
const html = toRedactor(json)
403+
expect(html).toBe('<p style="margin-left: 100px;">Non-indented paragraph</p>')
404+
})
405+
406+
test("should not add margin-left when data-indent-level is 0", () => {
407+
const json = {
408+
"type": "doc",
409+
"attrs": {},
410+
"children": [{
411+
"type": "p",
412+
"attrs": { "data-indent-level": "0" },
413+
"children": [{ "text": "No indent" }]
414+
}]
415+
}
416+
const html = toRedactor(json)
417+
expect(html).toBe('<p data-indent-level="0">No indent</p>')
418+
})
419+
420+
test("should handle data-indent-level with multiple style properties correctly", () => {
421+
const json = {
422+
"type": "doc",
423+
"attrs": {},
424+
"children": [{
425+
"type": "h2",
426+
"attrs": {
427+
"data-indent-level": "1",
428+
"style": { "margin-left": "50px", "text-align": "center", "font-size": "24px" }
429+
},
430+
"children": [{ "text": "Indented heading" }]
431+
}]
432+
}
433+
const html = toRedactor(json)
434+
// margin-left should be 30px (1 * 30px), other styles should be preserved
435+
expect(html).toBe('<h2 data-indent-level="1" style="text-align: center;font-size: 24px;margin-left: 30px;">Indented heading</h2>')
436+
})
437+
})

0 commit comments

Comments
 (0)