Skip to content

Commit

Permalink
Merge pull request #26 from contentstack/development
Browse files Browse the repository at this point in the history
Support for nested attributes
  • Loading branch information
Jayesh2812 committed Oct 11, 2023
2 parents 3085336 + fa960c2 commit 06da814
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 38 deletions.
11 changes: 0 additions & 11 deletions .github/workflows/sast-scan.yml

This file was deleted.

11 changes: 0 additions & 11 deletions .github/workflows/secrets-scan.yml

This file was deleted.

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021-2022 Contentstack
Copyright (c) 2022-2023 Contentstack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/json-rte-serializer",
"version": "2.0.2",
"version": "2.0.3",
"description": "This Package converts Html Document to Json and vice-versa.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down
31 changes: 23 additions & 8 deletions src/fromRedactor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,17 @@ export const fromRedactor = (el: any, options?:IHtmlToJsonOptions) : IAnyObject
return jsx('element', { type: "doc", uid: generateId(), attrs: {} }, children)
}
if (options?.allowNonStandardTags && !Object.keys(ELEMENT_TAGS).includes(nodeName) && !Object.keys(TEXT_TAGS).includes(nodeName)) {
const attributes = el.attributes
const attribute = {}
Array.from(attributes).forEach((child: any) => {
attribute[child.nodeName] = child.nodeValue
})
const attributes = (el as HTMLElement).attributes
const attributeMap = {}
Array.from(attributes).forEach((attribute) => {
let { nodeName, nodeValue } = attribute;
if (typeof nodeValue === "string") {
nodeValue = getNestedValueIfAvailable(nodeValue);
}
attributeMap[nodeName] = nodeValue;
});
console.warn(`${nodeName} is not a standard tag of JSON RTE.`)
return jsx('element', { type: nodeName.toLowerCase(), attrs: { ...attribute } }, children)
return jsx('element', { type: nodeName.toLowerCase(), attrs: { ...attributeMap } }, children)
}
const isEmbedEntry = el.attributes['data-sys-entry-uid']?.value
const type = el.attributes['type']?.value
Expand Down Expand Up @@ -781,7 +785,7 @@ const getFinalImageAttributes = ({elementAttrs, newChildren, extraAttrs, sizeAtt
sizeAttrs.width = newChildren[0].attrs.width.toString();
}

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

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

return imageAttrs
}
}

export const getNestedValueIfAvailable = (value: string) => {
try {
if (typeof value === "string" && value.trim().match(/^{|\[/i)) {
return JSON.parse(value);
}
return value
} catch {
return value;
}
};
9 changes: 8 additions & 1 deletion src/toRedactor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import kebbab from 'lodash/kebabCase'
import isEmpty from 'lodash/isEmpty'

import {IJsonToHtmlElementTags, IJsonToHtmlOptions, IJsonToHtmlTextTags} from './types'
import { isPlainObject } from 'lodash'

const ELEMENT_TYPES: IJsonToHtmlElementTags = {
'blockquote': (attrs: string, child: string) => {
Expand Down Expand Up @@ -223,7 +224,13 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
if (options?.allowNonStandardTypes && !Object.keys(ELEMENT_TYPES).includes(jsonValue['type']) && jsonValue['type'] !== 'doc') {
let attrs = ''
Object.entries(jsonValue?.attrs|| {}).forEach(([key, val]) => {
attrs += val ? ` ${key}="${val}"` : ` ${key}`;
if(isPlainObject(val)){
val = JSON.stringify(val)
attrs += ` ${key}='${val}'`
}
else{
attrs += val ? ` ${key}="${val}"` : ` ${key}`;
}
})
attrs = (attrs.trim() ? ' ' : '') + attrs.trim()
console.warn(`${jsonValue['type']} is not a valid element type.`)
Expand Down
12 changes: 11 additions & 1 deletion test/expectedJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1745,5 +1745,15 @@ export default {
]
}
]
}
},
"nested-attrs": [
{
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!"}]}]}]},
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>`
},
{
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!"}]}]}]},
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>`
},
]
}
49 changes: 47 additions & 2 deletions test/fromRedactor.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-nocheck
import { fromRedactor } from "../src/fromRedactor"
import { fromRedactor, getNestedValueIfAvailable } from "../src/fromRedactor"
import { JSDOM } from "jsdom"
import { isEqual } from "lodash"
import omitdeep from "omit-deep-lodash"
Expand All @@ -15,6 +15,8 @@ const docWrapper = (children: any) => {
const compareValue = (json1,json2) => {
return isEqual(JSON.stringify(omitdeep(json1, "uid")), JSON.stringify(omitdeep(docWrapper(json2), "uid")))
}

jest.mock('uuid', () => ({ v4: () => 'uid' }));
describe("Testing html to json conversion", () => {
it("paragraph conversion", () => {
let html = "<p>This is test</p>"
Expand Down Expand Up @@ -224,4 +226,47 @@ describe("Testing html to json conversion", () => {
], "type": "p" }]))
expect(testResult).toBe(true)
})
})

describe("Nested attrs", () =>{

test("should convert stringified attrs to proper nested JSON attrs", () => {
for (const testCase of expectedValue["nested-attrs"]) {
const { json, html } = testCase;
const dom = new JSDOM(html);
let htmlDoc = dom.window.document.querySelector("body");
const jsonValue = fromRedactor(htmlDoc, { allowNonStandardTags: true });
expect(jsonValue).toStrictEqual(json);
}
});

test("should not convert stringify attrs when `allowNonStandardTags` is not true", () => {
const html = `<p><span from="Paul, Addy" to="[object Object]">Hi There!</span></p>`;
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"};

const dom = new JSDOM(html);
let htmlDoc = dom.window.document.querySelector("body");
const jsonValue = fromRedactor(htmlDoc);
expect(jsonValue).toStrictEqual(json);
});
})

})


describe('getNestedValueIfAvailable', () => {

it('should return the input value when it\'s not a string containing JSON', () => {
expect(getNestedValueIfAvailable(10)).toBe(10);
expect(getNestedValueIfAvailable(null)).toBeNull();
expect(getNestedValueIfAvailable('{ "name": "John", "age": }')).toBe('{ "name": "John", "age": }');
expect(getNestedValueIfAvailable({ "name": "John", "age": 30})).toStrictEqual({ "name": "John", "age": 30});
expect(getNestedValueIfAvailable('[Object Object]')).toBe('[Object Object]');
});

it('should return the parsed JSON when the input value is a string containing JSON', () => {
const value = '{ "name": "John", "age": 30 }';
const result = getNestedValueIfAvailable(value);
expect(result).toEqual({ name: "John", age: 30 });
});

});
19 changes: 19 additions & 0 deletions test/toRedactor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,23 @@ describe("Testing json to html conversion", () => {
let testResult = isEqual(htmlValue, expectedValue["inline-classname-and-id"].html)
expect(testResult).toBe(true)
})

describe("Nested attrs", () => {

test("should have stringified attrs for nested json", () => {
for (const testCase of expectedValue["nested-attrs"]) {
const { json, html } = testCase;
const htmlValue = toRedactor(json, { allowNonStandardTypes: true });
expect(htmlValue).toBe(html);
}
});

test("should not convert to stringify attrs when `allowNonStandardTypes` is not true", () => {
const html = `This is HTML-formatted content.`
const json = {"type":"doc","attrs":{}, "children":[{"type":"aprimo","attrs":{ nestedAttrs: { "k1" : "v1"} },"children":[{"text":"This is HTML-formatted content."}]}]};

const htmlValue = toRedactor(json);
expect(htmlValue).toBe(html);
});
})
})

0 comments on commit 06da814

Please sign in to comment.