Skip to content

Commit 06da814

Browse files
authored
Merge pull request #26 from contentstack/development
Support for nested attributes
2 parents 3085336 + fa960c2 commit 06da814

File tree

10 files changed

+112
-38
lines changed

10 files changed

+112
-38
lines changed

.github/workflows/sast-scan.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

.github/workflows/secrets-scan.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

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) 2021-2022 Contentstack
3+
Copyright (c) 2022-2023 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: 2 additions & 2 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
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/json-rte-serializer",
3-
"version": "2.0.2",
3+
"version": "2.0.3",
44
"description": "This Package converts Html Document to Json and vice-versa.",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

src/fromRedactor.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,17 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
232232
return jsx('element', { type: "doc", uid: generateId(), attrs: {} }, children)
233233
}
234234
if (options?.allowNonStandardTags && !Object.keys(ELEMENT_TAGS).includes(nodeName) && !Object.keys(TEXT_TAGS).includes(nodeName)) {
235-
const attributes = el.attributes
236-
const attribute = {}
237-
Array.from(attributes).forEach((child: any) => {
238-
attribute[child.nodeName] = child.nodeValue
239-
})
235+
const attributes = (el as HTMLElement).attributes
236+
const attributeMap = {}
237+
Array.from(attributes).forEach((attribute) => {
238+
let { nodeName, nodeValue } = attribute;
239+
if (typeof nodeValue === "string") {
240+
nodeValue = getNestedValueIfAvailable(nodeValue);
241+
}
242+
attributeMap[nodeName] = nodeValue;
243+
});
240244
console.warn(`${nodeName} is not a standard tag of JSON RTE.`)
241-
return jsx('element', { type: nodeName.toLowerCase(), attrs: { ...attribute } }, children)
245+
return jsx('element', { type: nodeName.toLowerCase(), attrs: { ...attributeMap } }, children)
242246
}
243247
const isEmbedEntry = el.attributes['data-sys-entry-uid']?.value
244248
const type = el.attributes['type']?.value
@@ -781,7 +785,7 @@ const getFinalImageAttributes = ({elementAttrs, newChildren, extraAttrs, sizeAtt
781785
sizeAttrs.width = newChildren[0].attrs.width.toString();
782786
}
783787

784-
const childAttrs = { ...newChildren[0].attrs, ...sizeAttrs, style: { 'text-align': style['text-align'] }, caption: extraAttrs['caption'] }
788+
const childAttrs = { ...newChildren[0].attrs, ...sizeAttrs, style: { 'text-align': style?.['text-align'] }, caption: extraAttrs['caption'] }
785789
extraAttrs = { ...extraAttrs, ...sizeAttrs }
786790

787791
if (!childAttrs.caption) {
@@ -791,4 +795,15 @@ const getFinalImageAttributes = ({elementAttrs, newChildren, extraAttrs, sizeAtt
791795
const imageAttrs = getImageAttributes(elementAttrs, childAttrs, extraAttrs);
792796

793797
return imageAttrs
794-
}
798+
}
799+
800+
export const getNestedValueIfAvailable = (value: string) => {
801+
try {
802+
if (typeof value === "string" && value.trim().match(/^{|\[/i)) {
803+
return JSON.parse(value);
804+
}
805+
return value
806+
} catch {
807+
return value;
808+
}
809+
};

src/toRedactor.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import kebbab from 'lodash/kebabCase'
22
import isEmpty from 'lodash/isEmpty'
33

44
import {IJsonToHtmlElementTags, IJsonToHtmlOptions, IJsonToHtmlTextTags} from './types'
5+
import { isPlainObject } from 'lodash'
56

67
const ELEMENT_TYPES: IJsonToHtmlElementTags = {
78
'blockquote': (attrs: string, child: string) => {
@@ -223,7 +224,13 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
223224
if (options?.allowNonStandardTypes && !Object.keys(ELEMENT_TYPES).includes(jsonValue['type']) && jsonValue['type'] !== 'doc') {
224225
let attrs = ''
225226
Object.entries(jsonValue?.attrs|| {}).forEach(([key, val]) => {
226-
attrs += val ? ` ${key}="${val}"` : ` ${key}`;
227+
if(isPlainObject(val)){
228+
val = JSON.stringify(val)
229+
attrs += ` ${key}='${val}'`
230+
}
231+
else{
232+
attrs += val ? ` ${key}="${val}"` : ` ${key}`;
233+
}
227234
})
228235
attrs = (attrs.trim() ? ' ' : '') + attrs.trim()
229236
console.warn(`${jsonValue['type']} is not a valid element type.`)

test/expectedJson.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1745,5 +1745,15 @@ export default {
17451745
]
17461746
}
17471747
]
1748-
}
1748+
},
1749+
"nested-attrs": [
1750+
{
1751+
json: {"type":"doc","uid":"uid","attrs":{},"children":[{"type":"hangout-module","attrs":{},"children":[{"type":"hangout-chat","attrs":{"from":"Paul, Addy","nested":{"to":"JD"}},"children":[{"type":"hangout-discussion","attrs":{},"children":[{"type":"hangout-message","attrs":{"from":"Paul","profile":"profile.png","datetime":"2013-07-17T12:02"},"children":[{"type":"p","uid":"uid","attrs":{},"children":[{"text":"Feelin' this Web Components thing."}]},{"type":"p","uid":"uid","attrs":{},"children":[{"text":"Heard of it?"}]}]}]}]},{"type":"hangout-chat","attrs":{},"children":[{"text":"Hi There!"}]}]}]},
1752+
html : `<hangout-module><hangout-chat from="Paul, Addy" nested='{"to":"JD"}'><hangout-discussion><hangout-message from="Paul" profile="profile.png" datetime="2013-07-17T12:02"><p>Feelin' this Web Components thing.</p><p>Heard of it?</p></hangout-message></hangout-discussion></hangout-chat><hangout-chat>Hi There!</hangout-chat></hangout-module>`
1753+
},
1754+
{
1755+
json: {"type":"doc","uid":"uid","attrs":{},"children":[{"type":"hangout-module","attrs":{},"children":[{"type":"hangout-chat","attrs":{"from":"Paul, Addy","nested-json":{"to":"Hello World","more-nesting":{"from":"Beautiful World"}}},"children":[{"type":"hangout-discussion","attrs":{},"children":[{"type":"hangout-message","attrs":{"from":"Paul","profile":"profile.png","datetime":"2013-07-17T12:02"},"children":[{"type":"p","uid":"uid","attrs":{},"children":[{"text":"Feelin' this Web Components thing."}]},{"type":"p","uid":"uid","attrs":{},"children":[{"text":"Heard of it?"}]}]}]}]},{"type":"hangout-chat","attrs":{},"children":[{"text":"Hi There!"}]}]}]},
1756+
html : `<hangout-module><hangout-chat from="Paul, Addy" nested-json='{"to":"Hello World","more-nesting":{"from":"Beautiful World"}}'><hangout-discussion><hangout-message from="Paul" profile="profile.png" datetime="2013-07-17T12:02"><p>Feelin' this Web Components thing.</p><p>Heard of it?</p></hangout-message></hangout-discussion></hangout-chat><hangout-chat>Hi There!</hangout-chat></hangout-module>`
1757+
},
1758+
]
17491759
}

test/fromRedactor.test.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-nocheck
2-
import { fromRedactor } from "../src/fromRedactor"
2+
import { fromRedactor, getNestedValueIfAvailable } from "../src/fromRedactor"
33
import { JSDOM } from "jsdom"
44
import { isEqual } from "lodash"
55
import omitdeep from "omit-deep-lodash"
@@ -15,6 +15,8 @@ const docWrapper = (children: any) => {
1515
const compareValue = (json1,json2) => {
1616
return isEqual(JSON.stringify(omitdeep(json1, "uid")), JSON.stringify(omitdeep(docWrapper(json2), "uid")))
1717
}
18+
19+
jest.mock('uuid', () => ({ v4: () => 'uid' }));
1820
describe("Testing html to json conversion", () => {
1921
it("paragraph conversion", () => {
2022
let html = "<p>This is test</p>"
@@ -224,4 +226,47 @@ describe("Testing html to json conversion", () => {
224226
], "type": "p" }]))
225227
expect(testResult).toBe(true)
226228
})
227-
})
229+
230+
describe("Nested attrs", () =>{
231+
232+
test("should convert stringified attrs to proper nested JSON attrs", () => {
233+
for (const testCase of expectedValue["nested-attrs"]) {
234+
const { json, html } = testCase;
235+
const dom = new JSDOM(html);
236+
let htmlDoc = dom.window.document.querySelector("body");
237+
const jsonValue = fromRedactor(htmlDoc, { allowNonStandardTags: true });
238+
expect(jsonValue).toStrictEqual(json);
239+
}
240+
});
241+
242+
test("should not convert stringify attrs when `allowNonStandardTags` is not true", () => {
243+
const html = `<p><span from="Paul, Addy" to="[object Object]">Hi There!</span></p>`;
244+
const json = {"attrs": {}, "children": [{"attrs": {}, "children": [{"attrs": {"redactor-attributes": {"from": "Paul, Addy", "to": "[object Object]"}, "style": {}}, "children": [{"attrs": {"style": {}}, "text": "Hi There!"}], "type": "span", "uid": "uid"}], "type": "p", "uid": "uid"}], "type": "doc", "uid": "uid"};
245+
246+
const dom = new JSDOM(html);
247+
let htmlDoc = dom.window.document.querySelector("body");
248+
const jsonValue = fromRedactor(htmlDoc);
249+
expect(jsonValue).toStrictEqual(json);
250+
});
251+
})
252+
253+
})
254+
255+
256+
describe('getNestedValueIfAvailable', () => {
257+
258+
it('should return the input value when it\'s not a string containing JSON', () => {
259+
expect(getNestedValueIfAvailable(10)).toBe(10);
260+
expect(getNestedValueIfAvailable(null)).toBeNull();
261+
expect(getNestedValueIfAvailable('{ "name": "John", "age": }')).toBe('{ "name": "John", "age": }');
262+
expect(getNestedValueIfAvailable({ "name": "John", "age": 30})).toStrictEqual({ "name": "John", "age": 30});
263+
expect(getNestedValueIfAvailable('[Object Object]')).toBe('[Object Object]');
264+
});
265+
266+
it('should return the parsed JSON when the input value is a string containing JSON', () => {
267+
const value = '{ "name": "John", "age": 30 }';
268+
const result = getNestedValueIfAvailable(value);
269+
expect(result).toEqual({ name: "John", age: 30 });
270+
});
271+
272+
});

test/toRedactor.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,23 @@ describe("Testing json to html conversion", () => {
122122
let testResult = isEqual(htmlValue, expectedValue["inline-classname-and-id"].html)
123123
expect(testResult).toBe(true)
124124
})
125+
126+
describe("Nested attrs", () => {
127+
128+
test("should have stringified attrs for nested json", () => {
129+
for (const testCase of expectedValue["nested-attrs"]) {
130+
const { json, html } = testCase;
131+
const htmlValue = toRedactor(json, { allowNonStandardTypes: true });
132+
expect(htmlValue).toBe(html);
133+
}
134+
});
135+
136+
test("should not convert to stringify attrs when `allowNonStandardTypes` is not true", () => {
137+
const html = `This is HTML-formatted content.`
138+
const json = {"type":"doc","attrs":{}, "children":[{"type":"aprimo","attrs":{ nestedAttrs: { "k1" : "v1"} },"children":[{"text":"This is HTML-formatted content."}]}]};
139+
140+
const htmlValue = toRedactor(json);
141+
expect(htmlValue).toBe(html);
142+
});
143+
})
125144
})

0 commit comments

Comments
 (0)