Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: retain empty strings value for alt attr for img and asset while … #79

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ On the other hand, the `customTextWrapper` parser function provides the followin
- `value`: The value passed against the child element


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

**Note:**
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.


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

```javascript
Expand Down Expand Up @@ -216,6 +222,10 @@ const htmlValue = jsonToHtml(
return `<color data-color="${value}">${child}</color>`;
},
},
allowedEmptyAttributes : {
"p": ["dir"],
"img" : ["width"]
}
}
);

Expand Down
34 changes: 30 additions & 4 deletions src/toRedactor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import kebbab from 'lodash.kebabcase'
import isEmpty from 'lodash.isempty'
import {IJsonToHtmlElementTags, IJsonToHtmlOptions, IJsonToHtmlTextTags} from './types'
import {IJsonToHtmlElementTags, IJsonToHtmlOptions, IJsonToHtmlTextTags, IJsonToHtmlAllowedEmptyAttributes} from './types'
import isPlainObject from 'lodash.isplainobject'
import {replaceHtmlEntities, forbiddenAttrChars } from './utils'

Expand Down Expand Up @@ -213,11 +213,28 @@ const TEXT_WRAPPERS: IJsonToHtmlTextTags = {
return `<span data-type='inlineCode'>${child}</span>`
},
}
const ALLOWED_EMPTY_ATTRIBUTES: IJsonToHtmlAllowedEmptyAttributes = {
img: ['alt'],
reference: ['alt']
}

export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string => {
//TODO: optimize assign once per function call
if(options?.customTextWrapper && !isEmpty(options.customTextWrapper)){
Object.assign(TEXT_WRAPPERS,options.customTextWrapper)
}
if (options?.allowedEmptyAttributes && !isEmpty(options.allowedEmptyAttributes)) {
Object.keys(options.allowedEmptyAttributes).forEach(key => {
if (key === 'img' || key === 'reference') {
ALLOWED_EMPTY_ATTRIBUTES[key] = [
...ALLOWED_EMPTY_ATTRIBUTES[key],
...(options.allowedEmptyAttributes?.[key] || [])
];
} else {
ALLOWED_EMPTY_ATTRIBUTES[key] = options.allowedEmptyAttributes?.[key] ?? [];
}
});
}
if (jsonValue.hasOwnProperty('text')) {
let text = jsonValue['text'].replace(/</g, '&lt;').replace(/>/g, '&gt;')
if (jsonValue['break']) {
Expand Down Expand Up @@ -506,12 +523,21 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
delete attrsJson['url']
}
delete attrsJson['redactor-attributes']
Object.entries(attrsJson).forEach((key) => {
if (forbiddenAttrChars.some(char => key[0].includes(char))) {

Object.entries(attrsJson).forEach((item) => {
if (forbiddenAttrChars.some(char => item[0].includes(char))) {
return;
}
return key[1] ? (key[1] !== '' ? (attrs += `${key[0]}="${replaceHtmlEntities(key[1])}" `) : '') : ''

if (ALLOWED_EMPTY_ATTRIBUTES.hasOwnProperty(jsonValue['type']) && ALLOWED_EMPTY_ATTRIBUTES[jsonValue['type']].includes(item[0])) {
if ( jsonValue['type'] !== 'reference' || (jsonValue['type'] === 'reference' && jsonValue.attrs['display-type'] === 'display')) {
attrs += `${item[0]}="${replaceHtmlEntities(item[1])}" `;
return;
}
}
return item[1] ? (item[1] !== '' ? (attrs += `${item[0]}="${replaceHtmlEntities(item[1])}" `) : '') : ''
})

attrs = (attrs.trim() ? ' ' : '') + attrs.trim()
}
if (jsonValue['type'] === 'table') {
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ export interface IHtmlToJsonElementTags { [key: string]: (el:HTMLElement) => IHt

export interface IJsonToHtmlTextTags { [key: string]: (child:any, value:any) => string }
export interface IJsonToHtmlElementTags { [key: string]: (attrs:string,child:string,jsonBlock:IAnyObject,extraProps?:object) => string }
export interface IJsonToHtmlAllowedEmptyAttributes { [key: string]: string[]; }
export interface IJsonToMarkdownElementTags{[key: string]: (attrsJson:IAnyObject,child:string) => string}
export interface IJsonToMarkdownTextTags{ [key: string]: (child:any, value:any) => string }
export interface IJsonToHtmlOptions {
customElementTypes?: IJsonToHtmlElementTags,
customTextWrapper?: IJsonToHtmlTextTags,
allowNonStandardTypes?: boolean,
allowedEmptyAttributes?: IJsonToHtmlAllowedEmptyAttributes,
}
142 changes: 142 additions & 0 deletions test/expectedJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2204,6 +2204,148 @@ export default {

]

},
"RT-268":{
"html": [
`<img alt="" src="image_url.jpeg" width="100" style="width: 100; height: auto;" />`,
`<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>`,
`<p dir="">This is for testing purpose</p>`,
`<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>`
],
"json": [
{
"id": "a4794fb7214745a2a47fc24104b762f9",
"type": "docs",
"children": [
{
"type": "img",
"attrs": {
"url": "image_url.jpeg",
"redactor-attributes": {
"alt": "",
"src": "image_url.jpeg",
"width": "100"
},
"width": "100"
},
"uid": "18ff239605014dcaaa23c705caf99403",
"children": [
{
"text": ""
}
]
}
]
},
{
"uid": "a59f9108e99747d4b3358d9e22b7c685",
"type": "doc",
"attrs": {
"dirty": true
},
"children": [
{
"uid": "a41aede53efe46018e00de52b6d0970e",
"type": "reference",
"attrs": {
"display-type": "display",
"asset-uid": "blt8c34458f407b3862",
"content-type-uid": "sys_assets",
"asset-link": "https://stag-images.csnonprod.com/v3/assets/blt3381770ff6804fa1/blt8c34458f407b3862/6572c368e7a0d4196d105010/compass-logo-v2-final.png",
"asset-name": "compass-logo-v2-final.png",
"asset-type": "image/png",
"type": "asset",
"class-name": "embedded-asset",
"alt": "",
"asset-alt": "compass-logo-v2-final.png",
"inline": false
},
"children": [
{
"text": ""
}
]
}
],
"_version": 2
},
{
"uid": "a59f9108e99747d4b3358d9e22b7c685",
"type": "doc",
"attrs": {
"dirty": true
},
"children": [
{
"uid": "8e7309d3c617401898f45c1c3ae62f1e",
"type": "p",
"attrs": {
"style": {},
"redactor-attributes": {},
"dir": ""
},
"children": [
{
"text": "This is for testing purpose"
}
]
}
],
"_version": 2
},
{
"uid": "a59f9108e99747d4b3358d9e22b7c685",
"type": "doc",
"attrs": {
"dirty": true
},
"children": [
{
"uid": "e22e5bcaa65b41beb3cc48a8d8cf175c",
"type": "img",
"attrs": {
"url": "https://images.contentstack.io/v3/assets/blta29a98d37041ffc4/blt0f2e045a5f4ae8bd/646df9c6b8153a80eb810a6e/tony-litvyak-PzZQFFeRt54-unsplash.jpg",
"width": 100,
"dirty": true,
"style": {
"text-align": "left",
"width": "100px",
"max-width": "100px",
"float": "left"
},
"redactor-attributes": {
"position": "left",
"alt": ""
},
"dir": "",
"alt": "",
"max-width": 100,
"height": 150
},
"children": [
{
"text": ""
}
]
},
{
"uid": "8e7309d3c617401898f45c1c3ae62f1e",
"type": "p",
"attrs": {
"style": {},
"redactor-attributes": {},
"dir": ""
},
"children": [
{
"text": "This is for testing purpose"
}
]
}
],
"_version": 2
}
]
}

}
28 changes: 26 additions & 2 deletions test/toRedactor.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { toRedactor } from "../src/toRedactor"
import isEqual from "lodash.isequal"

import expectedValue from "./expectedJson"
import { imageAssetData } from "./testingData"


describe("Testing json to html conversion", () => {
it("heading conversion", () => {
let jsonValue = expectedValue["2"].json
Expand Down Expand Up @@ -292,5 +291,30 @@ describe("Testing json to html conversion", () => {
const html = toRedactor(json);
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;" />`)
})

describe("RT-268", ()=>{
it(' should retain empty string value for alt attribute for img type', () => {
const json = expectedValue['RT-268'].json[0];
const html = toRedactor(json);
expect(html).toBe(expectedValue['RT-268'].html[0]);
})
it(' should retain empty string value for alt attribute for asset reference', () => {
const json = expectedValue['RT-268'].json[1];
const html = toRedactor(json);
expect(html).toBe(expectedValue['RT-268'].html[1]);
})
it(' should retain empty string value for attributes passed through "allowedEmptyAttributes" prop', () => {
const json = expectedValue['RT-268'].json[2];
const html = toRedactor(json, {allowedEmptyAttributes: { p: ["dir"]} });
expect(html).toBe(expectedValue['RT-268'].html[2]);
})
it(' should retain empty string value for attributes passed through "allowedEmptyAttributes" prop, where alt is empty too (default empty)', () => {
const json = expectedValue['RT-268'].json[3];
const html = toRedactor(json, {allowedEmptyAttributes: { "img": ['dir'],"p": ["dir"]} });
expect(html).toBe(expectedValue['RT-268'].html[3]);
})

})

})

Loading