Skip to content

Commit f46a874

Browse files
Merge pull request #81 from contentstack/RT-268
Rt 268
2 parents 2979273 + a2e3240 commit f46a874

File tree

5 files changed

+204
-6
lines changed

5 files changed

+204
-6
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ On the other hand, the `customTextWrapper` parser function provides the followin
162162
- `value`: The value passed against the child element
163163

164164

165+
You can pass an object to `allowedEmptyAttributes` to retain empty attribute values for specific element types during HTML conversion.
166+
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.
169+
170+
165171
You can use the following customized JSON RTE Serializer code to convert your JSON RTE field data into HTML format.
166172

167173
```javascript
@@ -216,6 +222,10 @@ const htmlValue = jsonToHtml(
216222
return `<color data-color="${value}">${child}</color>`;
217223
},
218224
},
225+
allowedEmptyAttributes : {
226+
"p": ["dir"],
227+
"img" : ["width"]
228+
}
219229
}
220230
);
221231

src/toRedactor.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import kebbab from 'lodash.kebabcase'
22
import isEmpty from 'lodash.isempty'
3-
import {IJsonToHtmlElementTags, IJsonToHtmlOptions, IJsonToHtmlTextTags} from './types'
3+
import {IJsonToHtmlElementTags, IJsonToHtmlOptions, IJsonToHtmlTextTags, IJsonToHtmlAllowedEmptyAttributes} from './types'
44
import isPlainObject from 'lodash.isplainobject'
55
import {replaceHtmlEntities, forbiddenAttrChars } from './utils'
66

@@ -213,11 +213,24 @@ const TEXT_WRAPPERS: IJsonToHtmlTextTags = {
213213
return `<span data-type='inlineCode'>${child}</span>`
214214
},
215215
}
216+
const ALLOWED_EMPTY_ATTRIBUTES: IJsonToHtmlAllowedEmptyAttributes = {
217+
img: ['alt'],
218+
reference: ['alt']
219+
}
220+
216221
export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string => {
217222
//TODO: optimize assign once per function call
218223
if(options?.customTextWrapper && !isEmpty(options.customTextWrapper)){
219224
Object.assign(TEXT_WRAPPERS,options.customTextWrapper)
220225
}
226+
if (options?.allowedEmptyAttributes && !isEmpty(options.allowedEmptyAttributes)) {
227+
Object.keys(options.allowedEmptyAttributes).forEach(key => {
228+
ALLOWED_EMPTY_ATTRIBUTES[key] = [
229+
...(ALLOWED_EMPTY_ATTRIBUTES[key] ?? []),
230+
...(options.allowedEmptyAttributes?.[key] || [])
231+
];
232+
});
233+
}
221234
if (jsonValue.hasOwnProperty('text')) {
222235
let text = jsonValue['text'].replace(/</g, '&lt;').replace(/>/g, '&gt;')
223236
if (jsonValue['break']) {
@@ -506,12 +519,19 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
506519
delete attrsJson['url']
507520
}
508521
delete attrsJson['redactor-attributes']
509-
Object.entries(attrsJson).forEach((key) => {
510-
if (forbiddenAttrChars.some(char => key[0].includes(char))) {
522+
523+
Object.entries(attrsJson).forEach((item) => {
524+
if (forbiddenAttrChars.some(char => item[0].includes(char))) {
511525
return;
512526
}
513-
return key[1] ? (key[1] !== '' ? (attrs += `${key[0]}="${replaceHtmlEntities(key[1])}" `) : '') : ''
527+
528+
if (ALLOWED_EMPTY_ATTRIBUTES.hasOwnProperty(jsonValue['type']) && ALLOWED_EMPTY_ATTRIBUTES[jsonValue['type']].includes(item[0])) {
529+
attrs += `${item[0]}="${replaceHtmlEntities(item[1])}" `;
530+
return;
531+
}
532+
return item[1] ? (item[1] !== '' ? (attrs += `${item[0]}="${replaceHtmlEntities(item[1])}" `) : '') : ''
514533
})
534+
515535
attrs = (attrs.trim() ? ' ' : '') + attrs.trim()
516536
}
517537
if (jsonValue['type'] === 'table') {

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ export interface IHtmlToJsonElementTags { [key: string]: (el:HTMLElement) => IHt
1414

1515
export interface IJsonToHtmlTextTags { [key: string]: (child:any, value:any) => string }
1616
export interface IJsonToHtmlElementTags { [key: string]: (attrs:string,child:string,jsonBlock:IAnyObject,extraProps?:object) => string }
17+
export interface IJsonToHtmlAllowedEmptyAttributes { [key: string]: string[]; }
1718
export interface IJsonToMarkdownElementTags{[key: string]: (attrsJson:IAnyObject,child:string) => string}
1819
export interface IJsonToMarkdownTextTags{ [key: string]: (child:any, value:any) => string }
1920
export interface IJsonToHtmlOptions {
2021
customElementTypes?: IJsonToHtmlElementTags,
2122
customTextWrapper?: IJsonToHtmlTextTags,
2223
allowNonStandardTypes?: boolean,
24+
allowedEmptyAttributes?: IJsonToHtmlAllowedEmptyAttributes,
2325
}

test/expectedJson.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2205,6 +2205,148 @@ export default {
22052205

22062206
]
22072207

2208+
},
2209+
"RT-268":{
2210+
"html": [
2211+
`<img alt="" src="image_url.jpeg" width="100" style="width: 100; height: auto;" />`,
2212+
`<figure style="margin: 0"><img src="https://stag-images.csnonprod.com/v3/assets/blt3381770ff6804fa1/blt8c34458f407b3862/6572c368e7a0d4196d105010/compass-logo-v2-final.png" class="embedded-asset" content-type-uid="sys_assets" type="asset" alt="" asset-alt="compass-logo-v2-final.png" style="width: auto" data-sys-asset-filelink="https://stag-images.csnonprod.com/v3/assets/blt3381770ff6804fa1/blt8c34458f407b3862/6572c368e7a0d4196d105010/compass-logo-v2-final.png" data-sys-asset-uid="blt8c34458f407b3862" data-sys-asset-filename="compass-logo-v2-final.png" data-sys-asset-contenttype="image/png" data-sys-asset-alt="compass-logo-v2-final.png" sys-style-type="display"/></figure>`,
2213+
`<p dir="">This is for testing purpose</p>`,
2214+
`<img position="left" alt="" width="100" dirty="true" dir="" max-width="100" height="150" src="https://images.contentstack.io/v3/assets/blta29a98d37041ffc4/blt0f2e045a5f4ae8bd/646df9c6b8153a80eb810a6e/tony-litvyak-PzZQFFeRt54-unsplash.jpg" /><p dir="">This is for testing purpose</p>`
2215+
],
2216+
"json": [
2217+
{
2218+
"id": "a4794fb7214745a2a47fc24104b762f9",
2219+
"type": "docs",
2220+
"children": [
2221+
{
2222+
"type": "img",
2223+
"attrs": {
2224+
"url": "image_url.jpeg",
2225+
"redactor-attributes": {
2226+
"alt": "",
2227+
"src": "image_url.jpeg",
2228+
"width": "100"
2229+
},
2230+
"width": "100"
2231+
},
2232+
"uid": "18ff239605014dcaaa23c705caf99403",
2233+
"children": [
2234+
{
2235+
"text": ""
2236+
}
2237+
]
2238+
}
2239+
]
2240+
},
2241+
{
2242+
"uid": "a59f9108e99747d4b3358d9e22b7c685",
2243+
"type": "doc",
2244+
"attrs": {
2245+
"dirty": true
2246+
},
2247+
"children": [
2248+
{
2249+
"uid": "a41aede53efe46018e00de52b6d0970e",
2250+
"type": "reference",
2251+
"attrs": {
2252+
"display-type": "display",
2253+
"asset-uid": "blt8c34458f407b3862",
2254+
"content-type-uid": "sys_assets",
2255+
"asset-link": "https://stag-images.csnonprod.com/v3/assets/blt3381770ff6804fa1/blt8c34458f407b3862/6572c368e7a0d4196d105010/compass-logo-v2-final.png",
2256+
"asset-name": "compass-logo-v2-final.png",
2257+
"asset-type": "image/png",
2258+
"type": "asset",
2259+
"class-name": "embedded-asset",
2260+
"alt": "",
2261+
"asset-alt": "compass-logo-v2-final.png",
2262+
"inline": false
2263+
},
2264+
"children": [
2265+
{
2266+
"text": ""
2267+
}
2268+
]
2269+
}
2270+
],
2271+
"_version": 2
2272+
},
2273+
{
2274+
"uid": "a59f9108e99747d4b3358d9e22b7c685",
2275+
"type": "doc",
2276+
"attrs": {
2277+
"dirty": true
2278+
},
2279+
"children": [
2280+
{
2281+
"uid": "8e7309d3c617401898f45c1c3ae62f1e",
2282+
"type": "p",
2283+
"attrs": {
2284+
"style": {},
2285+
"redactor-attributes": {},
2286+
"dir": ""
2287+
},
2288+
"children": [
2289+
{
2290+
"text": "This is for testing purpose"
2291+
}
2292+
]
2293+
}
2294+
],
2295+
"_version": 2
2296+
},
2297+
{
2298+
"uid": "a59f9108e99747d4b3358d9e22b7c685",
2299+
"type": "doc",
2300+
"attrs": {
2301+
"dirty": true
2302+
},
2303+
"children": [
2304+
{
2305+
"uid": "e22e5bcaa65b41beb3cc48a8d8cf175c",
2306+
"type": "img",
2307+
"attrs": {
2308+
"url": "https://images.contentstack.io/v3/assets/blta29a98d37041ffc4/blt0f2e045a5f4ae8bd/646df9c6b8153a80eb810a6e/tony-litvyak-PzZQFFeRt54-unsplash.jpg",
2309+
"width": 100,
2310+
"dirty": true,
2311+
"style": {
2312+
"text-align": "left",
2313+
"width": "100px",
2314+
"max-width": "100px",
2315+
"float": "left"
2316+
},
2317+
"redactor-attributes": {
2318+
"position": "left",
2319+
"alt": ""
2320+
},
2321+
"dir": "",
2322+
"alt": "",
2323+
"max-width": 100,
2324+
"height": 150
2325+
},
2326+
"children": [
2327+
{
2328+
"text": ""
2329+
}
2330+
]
2331+
},
2332+
{
2333+
"uid": "8e7309d3c617401898f45c1c3ae62f1e",
2334+
"type": "p",
2335+
"attrs": {
2336+
"style": {},
2337+
"redactor-attributes": {},
2338+
"dir": ""
2339+
},
2340+
"children": [
2341+
{
2342+
"text": "This is for testing purpose"
2343+
}
2344+
]
2345+
}
2346+
],
2347+
"_version": 2
2348+
}
2349+
]
22082350
}
22092351

22102352
}

test/toRedactor.test.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { toRedactor } from "../src/toRedactor"
2-
import isEqual from "lodash.isequal"
3-
42
import expectedValue from "./expectedJson"
53
import { imageAssetData } from "./testingData"
64

5+
76
describe("Testing json to html conversion", () => {
87
it("heading conversion", () => {
98
let jsonValue = expectedValue["2"].json
@@ -292,5 +291,30 @@ describe("Testing json to html conversion", () => {
292291
const html = toRedactor(json);
293292
expect(html).toBe(`<img alt="Infographic showing 3 results from Forrester study of Contentstack CMS: $3M increase in profit, $507.3K productivity savings and $2.0M savings due to reduced time to publish." src="https://images.contentstack.io/v3/assets/blt7359e2a55efae483/bltea2a11144a2c68b5/63c08b7f438f80612c397994/CS_Infographics_ForresterReport_Data_3_1200x628_(1).png" position="center" width="641" style="width: 641; height: auto;" />`)
294293
})
294+
295+
describe("RT-268", ()=>{
296+
it(' should retain empty string value for alt attribute for img type', () => {
297+
const json = expectedValue['RT-268'].json[0];
298+
const html = toRedactor(json);
299+
expect(html).toBe(expectedValue['RT-268'].html[0]);
300+
})
301+
it(' should retain empty string value for alt attribute for asset reference', () => {
302+
const json = expectedValue['RT-268'].json[1];
303+
const html = toRedactor(json);
304+
expect(html).toBe(expectedValue['RT-268'].html[1]);
305+
})
306+
it(' should retain empty string value for attributes passed through "allowedEmptyAttributes" prop', () => {
307+
const json = expectedValue['RT-268'].json[2];
308+
const html = toRedactor(json, {allowedEmptyAttributes: { p: ["dir"]} });
309+
expect(html).toBe(expectedValue['RT-268'].html[2]);
310+
})
311+
it(' should retain empty string value for attributes passed through "allowedEmptyAttributes" prop, where alt is empty too (default empty)', () => {
312+
const json = expectedValue['RT-268'].json[3];
313+
const html = toRedactor(json, {allowedEmptyAttributes: { "img": ['dir'],"p": ["dir"]} });
314+
expect(html).toBe(expectedValue['RT-268'].html[3]);
315+
})
316+
317+
})
318+
295319
})
296320

0 commit comments

Comments
 (0)