Skip to content

Commit 302fad6

Browse files
Merge pull request #88 from contentstack/dev
Bug fixes and Enhancements.
2 parents 407773e + 1c21b04 commit 302fad6

11 files changed

+164
-19
lines changed

.talismanrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ fileignoreconfig:
2424
checksum: 3d014702628ad538065c970d988a695af003c61663596a8f6b9267b4e57ef6ea
2525
- filename: test/expectedJson.json
2626
checksum: 9979f84be3e5aa27f24381a0c49e0e6696388d19615c4f3b09082780968236ee
27+
- filename: README.md
28+
checksum: cccb3cd93c499acc87593eca5cc032e256c11cf530d4de67ece09e57fc430215
29+
- filename: test/expectedJson.ts
30+
checksum: a1966b0b3993c8e3a0e9e45de49204e7788ba74ba0089a8a6b6eba0729f990bd

README.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,27 @@ On the other hand, the `customTextWrapper` parser function provides the followin
161161
- `child`: The HTML string that specifies the child element
162162
- `value`: The value passed against the child element
163163

164+
___
164165

165-
You can pass an object to `allowedEmptyAttributes` to retain empty attribute values for specific element types during HTML conversion.
166+
`allowedEmptyAttributes`
166167

167-
**Note:**
168-
By default, if nothing is passed to `allowedEmptyAttributes`, we retain the `alt` attribute for `<img>` and `reference` (asset) element types, even when its value is empty, during HTML conversion.
168+
- Type: `object`
169+
- Default: `{ img: ['alt'], reference: ['alt'] }`
169170

171+
Specifies which empty attributes should be retained for specific HTML elements during the jsonToHtml conversion.
172+
By default, the converter preserves the alt attribute for <img> and reference (asset) elements, even when their values are empty.
173+
This is particularly useful for ensuring semantic correctness and accessibility.
174+
175+
Use this option when you want to retain specific attributes with empty values during the conversion process.
176+
177+
___
178+
179+
`addNbspForEmptyBlocks`
180+
181+
- Type: `boolean`
182+
- Default:`false`
183+
184+
When set to true, this option adds a non-breaking space (nbsp;) to empty blocks during the jsonToHtml conversion. This helps maintain the visual structure of the HTML output—especially useful for preserving spacing in editable content or content editors.
170185

171186
You can use the following customized JSON RTE Serializer code to convert your JSON RTE field data into HTML format.
172187

@@ -196,6 +211,16 @@ const jsonValue = {
196211
},
197212
],
198213
},
214+
{
215+
"type": "p",
216+
"uid": "28c837c127504d3c85b9cb6d7099cb0b",
217+
"attrs": {},
218+
"children": [
219+
{
220+
"text": ""
221+
}
222+
]
223+
},
199224
{
200225
type: "p",
201226
attrs: {},
@@ -225,7 +250,8 @@ const htmlValue = jsonToHtml(
225250
allowedEmptyAttributes : {
226251
"p": ["dir"],
227252
"img" : ["width"]
228-
}
253+
},
254+
addNbspForEmptyBlocks : true
229255
}
230256
);
231257

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/json-rte-serializer",
3-
"version": "3.0.1",
3+
"version": "3.0.2",
44
"description": "This Package converts Html Document to Json and vice-versa.",
55
"main": "lib/index.js",
66
"module": "lib/index.mjs",

src/fromRedactor.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ export const ELEMENT_TAGS: IHtmlToJsonElementTags = {
2020
const attrs: Record<string, string> = {}
2121
const target = el.getAttribute('target');
2222
const href = el.getAttribute('href');
23+
const title = el.getAttribute('title');
2324

2425
attrs.url = href ? href : '#';
2526

2627
if(target && target !== '') {
2728
attrs.target = target;
2829
}
30+
if(title && title !== '') {
31+
attrs.title = title;
32+
}
2933

3034
return {
3135
type: "a",
@@ -206,6 +210,9 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
206210
if (el.textContent === '\n') {
207211
return null
208212
}
213+
if (options?.addNbspForEmptyBlocks && el.textContent.trim() === '') {
214+
return { text: '' }
215+
}
209216
if (el.parentNode.nodeName === 'SPAN') {
210217
let attrs = { style: {} }
211218
const metadata = {}
@@ -297,6 +304,7 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
297304
let children: any = flatten(Array.from(parent.childNodes).map((child) => fromRedactor(child, options)))
298305
children = children.filter((child: any) => child !== null)
299306
children = traverseChildAndWarpChild(children, options?.allowNonStandardTags)
307+
300308
if (children.length === 0) {
301309
children = [{ text: '' }]
302310
}

src/jsonToMarkdown.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,32 @@ const ELEMENT_TYPES: IJsonToMarkdownElementTags = {
1414
'h1': (attrs: any, child: string) => {
1515
return `
1616
17-
#${child}#`
17+
# ${child} #`
1818
},
1919
'h2': (attrs: any, child: any) => {
2020
return `
2121
22-
##${child}##`
22+
## ${child} ##`
2323
},
2424
'h3': (attrs: any, child: any) => {
2525
return `
2626
27-
###${child}###`
27+
### ${child} ###`
2828
},
2929
'h4': (attrs: any, child: any) => {
3030
return `
3131
32-
####${child}####`
32+
#### ${child} ####`
3333
},
3434
'h5': (attrs: any, child: any) => {
3535
return `
3636
37-
#####${child}#####`
37+
##### ${child} #####`
3838
},
3939
'h6': (attrs: any, child: any) => {
4040
return `
4141
42-
######${child}######`
42+
###### ${child} ######`
4343
},
4444
img: (attrsJson: any, child: any) => {
4545
if(attrsJson) {

src/toRedactor.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,13 @@ const ALLOWED_EMPTY_ATTRIBUTES: IJsonToHtmlAllowedEmptyAttributes = {
218218
reference: ['alt']
219219
}
220220

221+
let ADD_NBSP_FOR_EMPTY_BLOCKS : boolean = false
222+
221223
export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string => {
222224
//TODO: optimize assign once per function call
225+
if(options?.addNbspForEmptyBlocks){
226+
ADD_NBSP_FOR_EMPTY_BLOCKS = options?.addNbspForEmptyBlocks
227+
}
223228
if(options?.customTextWrapper && !isEmpty(options.customTextWrapper)){
224229
Object.assign(TEXT_WRAPPERS,options.customTextWrapper)
225230
}
@@ -590,7 +595,12 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
590595

591596
attrs = (attrs.trim() ? ' ' : '') + attrs.trim()
592597

593-
return ELEMENT_TYPES[orgType || jsonValue['type']](attrs, children,jsonValue, figureStyles)
598+
return ELEMENT_TYPES[orgType || jsonValue['type']](
599+
attrs,
600+
ADD_NBSP_FOR_EMPTY_BLOCKS && !children ? '&nbsp;' : children,
601+
jsonValue,
602+
figureStyles
603+
)
594604
}
595605

596606
return children

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ export interface IAnyObject {[key:string]:any}
22
export interface IHtmlToJsonOptions {
33
allowNonStandardTags?: boolean,
44
customElementTags?: IHtmlToJsonElementTags,
5-
customTextTags?: IHtmlToJsonTextTags
5+
customTextTags?: IHtmlToJsonTextTags,
6+
addNbspForEmptyBlocks?: boolean
67
}
78
export interface IHtmlToJsonElementTagsAttributes {
89
type:string,
@@ -22,4 +23,5 @@ export interface IJsonToHtmlOptions {
2223
customTextWrapper?: IJsonToHtmlTextTags,
2324
allowNonStandardTypes?: boolean,
2425
allowedEmptyAttributes?: IJsonToHtmlAllowedEmptyAttributes,
26+
addNbspForEmptyBlocks?: boolean
2527
}

test/expectedJson.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2416,6 +2416,77 @@ export default {
24162416
"_version": 2
24172417
}
24182418
]
2419+
},
2420+
"RT-501":{
2421+
"html" : [
2422+
`<p><a href="google.in" target="_blank" title="google">ABC</a></p>`
2423+
],
2424+
"json" : [
2425+
{
2426+
"type": "doc",
2427+
"attrs": {},
2428+
"uid": "bd63f151aa8d402cae046c8dae440134",
2429+
"children": [
2430+
{
2431+
"type": "p",
2432+
"uid": "d2949ce0e0974ce783543edd37410c71",
2433+
"attrs": {},
2434+
"children": [
2435+
{
2436+
"uid": "7a2fd904668447ca8720428cbd2b0acc",
2437+
"type": "a",
2438+
"attrs": {
2439+
"url": "google.in",
2440+
"target": "_blank",
2441+
"title": "google"
2442+
},
2443+
"children": [
2444+
{
2445+
"text": "ABC"
2446+
}
2447+
]
2448+
}
2449+
]
2450+
}
2451+
],
2452+
}
2453+
],
2454+
"jsonWithRedactorAttributes": [
2455+
{
2456+
"type": "doc",
2457+
"attrs": {},
2458+
"uid": "bd63f151aa8d402cae046c8dae440134",
2459+
"children": [
2460+
{
2461+
"type": "p",
2462+
"uid": "d2949ce0e0974ce783543edd37410c71",
2463+
"attrs": {},
2464+
"children": [
2465+
{
2466+
"uid": "7a2fd904668447ca8720428cbd2b0acc",
2467+
"type": "a",
2468+
"attrs": {
2469+
"url": "google.in",
2470+
"target": "_blank",
2471+
"title": "google",
2472+
"style": {},
2473+
"redactor-attributes": {
2474+
"href": "google.in",
2475+
"target": "_blank",
2476+
"title": "google"
2477+
}
2478+
},
2479+
"children": [
2480+
{
2481+
"text": "ABC"
2482+
}
2483+
]
2484+
}
2485+
]
2486+
}
2487+
]
2488+
}
2489+
]
24192490
}
24202491

24212492
}

test/expectedMarkdown.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,17 @@ This is a paragraph with \`inline code\`.`
163163
}],
164164
"markdown": `
165165
166-
#Heading 1#
166+
# Heading 1 #
167167
168-
##Heading 2##
168+
## Heading 2 ##
169169
170-
###Heading 3###
170+
### Heading 3 ###
171171
172-
####Heading 4####
172+
#### Heading 4 ####
173173
174-
#####Heading 5#####
174+
##### Heading 5 #####
175175
176-
######Heading 6######`
176+
###### Heading 6 ######`
177177
},
178178
{
179179
"title": "Block Quote Conversion",

test/fromRedactor.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,13 @@ describe("Testing html to json conversion", () => {
328328
expect(json).toStrictEqual({"type":"doc","uid":"uid","attrs":{},"children":[{"type":"p","attrs":{},"uid":"uid","children":[{"text":"Hello","attrs":{"style":{}},"bold":true},{"text":" Hii"}]}]})
329329

330330
})
331+
test("should add title attr to anchor tag", () => {
332+
const html = expectedValue["RT-501"].html[0];
333+
const json =expectedValue["RT-501"].jsonWithRedactorAttributes[0];
334+
335+
let jsonValue = htmlToJson(html)
336+
expect(omitdeep(jsonValue, "uid")).toStrictEqual(omitdeep(json, "uid"))
337+
})
331338
})
332339

333340

test/toRedactor.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,5 +329,22 @@ describe("Testing json to html conversion", () => {
329329
})
330330
})
331331

332+
describe("RT-501",()=>{
333+
it("should add title attr to anchor tag",()=>{
334+
const html = expectedValue["RT-501"].html[0];
335+
const json =expectedValue["RT-501"].json[0];
336+
337+
let htmlValue = toRedactor(json)
338+
expect(htmlValue).toBe(html);
339+
})
340+
341+
})
342+
332343
})
333344

345+
test("should add nbsp for empty blocks", () => {
346+
const json = {"type":"doc","uid":"uid","attrs":{},"children":[{"type":"p","attrs":{},"uid":"uid","children":[{"text":"Hi"}]},{"type":"p","attrs":{},"uid":"uid","children":[{"text":""}]},{"type":"p","attrs":{},"uid":"uid","children":[{"text":""}]},{"type":"p","attrs":{},"uid":"uid","children":[{"text":"Hello"}]}]};
347+
const html = toRedactor(json, {addNbspForEmptyBlocks: true});
348+
expect(html).toBe(`<p>Hi</p><p>&nbsp;</p><p>&nbsp;</p><p>Hello</p>`);
349+
});
350+

0 commit comments

Comments
 (0)