From 2e8970f386eec1271785cf07445501d0cb0350ee Mon Sep 17 00:00:00 2001
From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com>
Date: Sat, 8 Feb 2025 11:34:10 +0530
Subject: [PATCH 1/5] chore: Added support of \n after just enter (#133)
* chore: Added support of \n after just enter
* chore: Updated changelog
---
CHANGELOG.md | 1 +
__test__/json-to-html.test.ts | 9 +++++
__test__/mock/json-element-mock-result.ts | 2 ++
__test__/mock/json-element-mock.ts | 41 ++++++++++++++++++++++-
src/helper/sanitize.ts | 4 +++
5 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5458fb..4bd1d00 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## [1.3.17](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.3.17) (2025-02-11)
- Enh: updateAssetURLForGQL update for multilpe entries
+ - Fix: Added support of br tag (\n) after just enter
## [1.3.16](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.3.16) (2025-01-27)
- Enh: Added support for colgroup and col tags inside table tag
diff --git a/__test__/json-to-html.test.ts b/__test__/json-to-html.test.ts
index f72fd4d..720feac 100644
--- a/__test__/json-to-html.test.ts
+++ b/__test__/json-to-html.test.ts
@@ -17,6 +17,7 @@ import {
linkInPJsonUrl,
orderListJson,
paragraphEntry,
+ paragraphEntryWithNewline,
paragraphJsonArrayEntry,
plainTextJson,
styleinPJson,
@@ -47,6 +48,7 @@ import {
linkInPURLHtml,
orderListHtml,
paragraphHtml,
+ paragraphHtmlWithNewLine,
plainTextHtml,
styleinPHtml,
tableHtml,
@@ -87,6 +89,13 @@ describe('Node parser paragraph content', () => {
done()
})
+ it('Should render Json To html with newline after single enter', done => {
+ const entry = {...paragraphEntryWithNewline}
+ jsonToHTML({entry, paths: ['rich_text_editor']})
+ expect(entry.rich_text_editor).toEqual(paragraphHtmlWithNewLine)
+ done()
+ })
+
it('Should render Json To html for Array of Entries', done => {
const entry = {...paragraphEntry}
diff --git a/__test__/mock/json-element-mock-result.ts b/__test__/mock/json-element-mock-result.ts
index a4fab81..7b1b349 100644
--- a/__test__/mock/json-element-mock-result.ts
+++ b/__test__/mock/json-element-mock-result.ts
@@ -1,5 +1,6 @@
const plainTextHtml = "Aliquam sit amet libero dapibus, eleifend ligula at, varius justo Lorem ipsum dolor sit amet consectetur adipiscing elit.Sed condimentum iaculis magna in vehicula. Vestibulum vitae convallis lacus. "
const paragraphHtml = "
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed condimentum iaculis magna in vehicula. Vestibulum vitae convallis lacus. Praesent a diam iaculis turpis rhoncus faucibus. Aliquam sed pulvinar sem.
"
+const paragraphHtmlWithNewLine = "Ritesh test
Shift enter single enter
"
const h1Html = "Lorem ipsum dolor sit amet. "
const h2Html = "Vestibulum a ligula eget massa sagittis aliquam sit amet quis tortor. "
const h3Html = "Mauris venenatis dui id massa sollicitudin, non bibendum nunc dictum. "
@@ -51,5 +52,6 @@ export {
referenceObjHtml,
referenceObjHtmlBlock,
imagetags,
+ paragraphHtmlWithNewLine,
}
\ No newline at end of file
diff --git a/__test__/mock/json-element-mock.ts b/__test__/mock/json-element-mock.ts
index c5fe596..1863c3f 100644
--- a/__test__/mock/json-element-mock.ts
+++ b/__test__/mock/json-element-mock.ts
@@ -659,6 +659,35 @@ const paragraphJson = {
type: "doc"
}
+const paragraphJsonWithNewline = {
+ "type": "doc",
+ "attrs": {},
+ "uid": "abcdefgh123456",
+ "children": [
+ {
+ "type": "p",
+ "uid": "abcdefgh123456",
+ "attrs": {},
+ "children": [
+ {
+ "text": "Ritesh test"
+ }
+ ]
+ },
+ {
+ "uid": "abcdefgh123456",
+ "type": "p",
+ "children": [
+ {
+ "text": "Shift enter\nsingle enter"
+ }
+ ],
+ "attrs": {}
+ }
+ ],
+ "_version": 26
+}
+
const blockquoteJson = {
uid: "06084d7fd",
_version: 13,
@@ -1128,6 +1157,15 @@ const paragraphEntry = {
uid: 'asset_uid_10',
}
+const paragraphEntryWithNewline = {
+ title: 'entry and assets',
+ url: '/entry-and-assets',
+ rich_text_editor: {...paragraphJsonWithNewline},
+ locale: 'en-us',
+ _in_progress: false,
+ uid: 'asset_uid_10',
+}
+
const paragraphJsonArrayEntry = {
title: 'entry and assets',
url: '/entry-and-assets',
@@ -2393,5 +2431,6 @@ export {
unorderListJson2,
orderListJson2,
testJsonRte,
- testJsonAsset
+ testJsonAsset,
+ paragraphEntryWithNewline
}
\ No newline at end of file
diff --git a/src/helper/sanitize.ts b/src/helper/sanitize.ts
index b2d66ca..48539aa 100644
--- a/src/helper/sanitize.ts
+++ b/src/helper/sanitize.ts
@@ -3,6 +3,10 @@ type AllowedTags = 'p' | 'a' | 'strong' | 'em' | 'ul' | 'ol' | 'li' | 'h1' | 'h2
type AllowedAttributes = 'href' | 'title' | 'target' | 'alt' | 'src' | 'class' | 'id' | 'style' | 'colspan' | 'rowspan' | 'content-type-uid' | 'data-sys-asset-uid' | 'sys-style-type' | 'data-type' | 'data-width' | 'data-rows' | 'data-cols';
export function sanitizeHTML(input: string, allowedTags: AllowedTags[] = ['p', 'a', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'sub', 'u', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'span', 'fragment', 'sup', 'strike', 'br', 'img', 'colgroup', 'col', 'div'], allowedAttributes: AllowedAttributes[] = ['href', 'title', 'target', 'alt', 'src', 'class', 'id', 'style', 'colspan', 'rowspan', 'content-type-uid', 'data-sys-asset-uid', 'sys-style-type', 'data-type', 'data-width', 'data-rows', 'data-cols']): string {
+
+ // Replace newline characters with before processing the HTML tags
+ input = input.replace(/\n/g, ' ');
+
// Regular expression to find and remove all HTML tags except the allowed ones
const sanitized = input.replace(/<\/?([a-z][a-z0-9]*)\b[^<>]*>/gi, (match, tag) => {
return allowedTags.includes(tag.toLowerCase()) ? match : '';
From 38cca9fe31d928221953a1c91525e827e479954e Mon Sep 17 00:00:00 2001
From: "harshitha.d"
Date: Wed, 12 Feb 2025 12:28:55 +0530
Subject: [PATCH 2/5] fix: fix for html injection
---
src/helper/enumerate-entries.ts | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/helper/enumerate-entries.ts b/src/helper/enumerate-entries.ts
index 5e3fd90..c23e5b6 100644
--- a/src/helper/enumerate-entries.ts
+++ b/src/helper/enumerate-entries.ts
@@ -42,7 +42,7 @@ export function enumerateContents(
}
export function textNodeToHTML(node: TextNode, renderOption: RenderOption): string {
- let text = node.text;
+ let text = escapeHtml(node.text);
if (node.classname || node.id) {
text = (renderOption[MarkType.CLASSNAME_OR_ID] as RenderMark)(text, node.classname, node.id);
}
@@ -158,3 +158,10 @@ function nodeToHTML(
}
}
}
+
+function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+}
\ No newline at end of file
From 665b422eb664f3155179f4274dea4a270f0d2810 Mon Sep 17 00:00:00 2001
From: "harshitha.d"
Date: Wed, 12 Feb 2025 12:31:06 +0530
Subject: [PATCH 3/5] test: test for html injection fix
---
__test__/json-to-html.test.ts | 15 ++++++++++--
__test__/mock/json-element-mock-result.ts | 4 +--
__test__/mock/json-element-mock.ts | 30 ++++++++++++++++++++++-
3 files changed, 44 insertions(+), 5 deletions(-)
diff --git a/__test__/json-to-html.test.ts b/__test__/json-to-html.test.ts
index 720feac..de20842 100644
--- a/__test__/json-to-html.test.ts
+++ b/__test__/json-to-html.test.ts
@@ -33,7 +33,8 @@ import {
orderListJson2,
testJsonRte,
testJsonAsset,
- embeddedAssetAsLinkJsonEntry} from './mock/json-element-mock'
+ embeddedAssetAsLinkJsonEntry,
+ escapeJsonHtml } from './mock/json-element-mock'
import {
blockquoteHtml,
codeHtml,
@@ -60,7 +61,8 @@ import {
styleObjHtml,
referenceObjHtml,
referenceObjHtmlBlock,
- imagetags} from './mock/json-element-mock-result'
+ imagetags,
+ escapeHtml } from './mock/json-element-mock-result'
describe('Node parser paragraph content', () => {
it('Should accept proper values', done => {
const entry = { uid: 'uid'}
@@ -122,6 +124,15 @@ describe('Node parser paragraph content', () => {
expect(entry.rich_text_editor).toEqual([paragraphHtml])
done()
})
+
+ it('Should render Json To html', done => {
+ const entry = {...escapeJsonHtml}
+
+ jsonToHTML({entry, paths: ['rich_text_editor']})
+
+ expect(entry.rich_text_editor).toEqual(escapeHtml)
+ done()
+ })
})
diff --git a/__test__/mock/json-element-mock-result.ts b/__test__/mock/json-element-mock-result.ts
index 7b1b349..800d991 100644
--- a/__test__/mock/json-element-mock-result.ts
+++ b/__test__/mock/json-element-mock-result.ts
@@ -23,8 +23,8 @@ const classAndIdAttrsHtml = "
const styleObjHtml = "heading1 heading2 heading3 heading4 heading5 heading6 "
const referenceObjHtml = "Embed entry as a link
Open entry as a link in new tab
Bold entry
Bold entry open in new tab
"
const referenceObjHtmlBlock = "Embed entry as a link
Embed entry as a link open in new tab
"
-
const imagetags = "The Batman "
+const escapeHtml = "<p>Welcome to Contentstack! <script>console.log(/\"Hello from Contentstack!/\");</script> Explore our platform to create, manage, and publish content seamlessly.</p>
"
export {
h1Html,
@@ -53,5 +53,5 @@ export {
referenceObjHtmlBlock,
imagetags,
paragraphHtmlWithNewLine,
-
+ escapeHtml
}
\ No newline at end of file
diff --git a/__test__/mock/json-element-mock.ts b/__test__/mock/json-element-mock.ts
index 1863c3f..463d28e 100644
--- a/__test__/mock/json-element-mock.ts
+++ b/__test__/mock/json-element-mock.ts
@@ -2394,6 +2394,33 @@ const testJsonAsset={
"_version": 22
}
}
+
+const escapeJsonHtml = {
+ title: 'entry and assets',
+ url: '/entry-and-assets',
+ rich_text_editor: {
+ uid: "uid",
+ _version: 13,
+ attrs: {},
+ children: [
+ {
+ type: "p",
+ attrs: {},
+ uid: "0a1b5676aa510e5a",
+ children: [
+ {
+ text: 'Welcome to Contentstack! Explore our platform to create, manage, and publish content seamlessly.
'
+ }
+ ]
+ }
+ ],
+ type: "doc"
+},
+ locale: 'en-us',
+ _in_progress: false,
+ uid: 'asset_uid_10',
+}
+
export {
h1Json,
h2Json,
@@ -2432,5 +2459,6 @@ export {
orderListJson2,
testJsonRte,
testJsonAsset,
- paragraphEntryWithNewline
+ paragraphEntryWithNewline,
+ escapeJsonHtml
}
\ No newline at end of file
From 031e7cd804bc509d25edad77dcb37ccf36f26d31 Mon Sep 17 00:00:00 2001
From: "harshitha.d"
Date: Wed, 12 Feb 2025 12:31:59 +0530
Subject: [PATCH 4/5] chore: update version to 1.3.18 and document html
injection fix in changelog
---
CHANGELOG.md | 3 +++
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bd1d00..0449694 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+## [1.3.18](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.3.17) (2025-02-17)
+ - Fix: Added fix for html injection
+
## [1.3.17](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.3.17) (2025-02-11)
- Enh: updateAssetURLForGQL update for multilpe entries
- Fix: Added support of br tag (\n) after just enter
diff --git a/package-lock.json b/package-lock.json
index c4d63a0..af29222 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@contentstack/utils",
- "version": "1.3.17",
+ "version": "1.3.18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@contentstack/utils",
- "version": "1.3.17",
+ "version": "1.3.18",
"license": "MIT",
"devDependencies": {
"@babel/preset-env": "^7.26.0",
diff --git a/package.json b/package.json
index 5479b66..7dfc8b4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@contentstack/utils",
- "version": "1.3.17",
+ "version": "1.3.18",
"description": "Contentstack utilities for Javascript",
"main": "dist/index.es.js",
"types": "dist/types/index.d.ts",
From a384722b2ea7580d646589578c3a29629dd475d2 Mon Sep 17 00:00:00 2001
From: Nadeem Patwekar
Date: Thu, 13 Feb 2025 11:03:22 +0530
Subject: [PATCH 5/5] fix: update test for asset url method for gql
---
__test__/mock/gql-asset-url-update-mock.ts | 23 ++++++--------
__test__/updateAssetURLForGQL.test.ts | 8 ++---
src/updateAssetURLForGQL.ts | 35 +++++++++++++++-------
3 files changed, 37 insertions(+), 29 deletions(-)
diff --git a/__test__/mock/gql-asset-url-update-mock.ts b/__test__/mock/gql-asset-url-update-mock.ts
index 671ca5f..bc9fdb3 100644
--- a/__test__/mock/gql-asset-url-update-mock.ts
+++ b/__test__/mock/gql-asset-url-update-mock.ts
@@ -264,21 +264,13 @@ export const gqlResponseForAssetUpdateMultipleEntries = {
"title": "merry-marketplace.png",
"url": "actual_asset_url.png",
"content_type": "image/png",
- "description": null,
+ "description": "null",
"file_size": 273858,
"filename": "merry-marketplace.png",
- "permanent_url": "Permanent URL Not Defined!"
- }
- },
- {
- "node": {
- "title": "Screenshot.png",
- "url": "https://azure-na-images.contentstack.com/v3/assets/folder_uid/asset_uid_2/folder_uid_4/Screenshot_2024-12-09_at_7.28.28_PM.png?branch=test2",
- "content_type": "image/png",
- "description": "",
- "file_size": 287954,
- "filename": "Screenshot_2024-12-09_at_7.28.28_PM.png",
- "permanent_url": "Permanent URL Not Defined!"
+ "permanent_url": "Permanent URL Not Defined!",
+ "system" : {
+ "uid": "asset_uid_1"
+ }
}
},
{
@@ -289,7 +281,10 @@ export const gqlResponseForAssetUpdateMultipleEntries = {
"description": "",
"file_size": 1050317,
"filename": "Aadhaar.pdf",
- "permanent_url": "Permanent URL Not Defined!"
+ "permanent_url": "Permanent URL Not Defined!",
+ "system" : {
+ "uid": "asset_uid_2"
+ }
}
}
]
diff --git a/__test__/updateAssetURLForGQL.test.ts b/__test__/updateAssetURLForGQL.test.ts
index fc33f5c..a165271 100644
--- a/__test__/updateAssetURLForGQL.test.ts
+++ b/__test__/updateAssetURLForGQL.test.ts
@@ -3,7 +3,7 @@ import { gqlResponseForAssetUpdate, gqlResponseForAssetUpdateWithoutSystemUid, g
describe('updateAssetURLForGQL test', () => {
- it.skip('should update the asset URL in the GQL response when proper response is passed', done => {
+ it('should update the asset URL in the GQL response when proper response is passed', done => {
const testResponse = { ...gqlResponseForAssetUpdate };
updateAssetURLForGQL(testResponse);
@@ -15,19 +15,19 @@ describe('updateAssetURLForGQL test', () => {
done();
});
- it.skip('should update the asset URL in the GQL response when proper response is passed', done => {
+ it('should update the asset URL in the GQL response with multiple entries when proper response is passed', done => {
const testResponse = { ...gqlResponseForAssetUpdateMultipleEntries };
updateAssetURLForGQL(testResponse);
const rteField = testResponse.data.page_json_rte.items[0].body_new[0].body.body_12;
const assetLink = rteField.json.children[0].attrs['asset-link'];
const expectedUrl = rteField.embedded_itemsConnection.edges[0].node.url;
-
+
expect(assetLink).toBe(expectedUrl);
done();
});
- it.skip('should throw error when system.uid is not present', done => {
+ it('should throw error when system.uid is not present', done => {
jest.spyOn(console, 'error').mockImplementation(() => {});
const testResponse = { ...gqlResponseForAssetUpdateWithoutSystemUid };
diff --git a/src/updateAssetURLForGQL.ts b/src/updateAssetURLForGQL.ts
index cde8fb2..29a3e4f 100644
--- a/src/updateAssetURLForGQL.ts
+++ b/src/updateAssetURLForGQL.ts
@@ -20,21 +20,34 @@ export function updateAssetURLForGQL(gqlResponse:any) {
function processEntry(entry:any) {
for (let field in entry) {
const fieldData = entry[field];
- const rteField = findRTEField(fieldData)
- const edges = rteField?.embedded_itemsConnection?.edges;
- edges.forEach((edge:any) => {
- const node = edge.node;
- if (node?.url && node?.filename) {
-
- if (!node?.system?.uid) throw new Error('Asset UID not found in the response');
-
- const correspondingAsset = rteField?.json?.children?.find((child:any) => child.attrs['asset-uid'] === node.system.uid);
- correspondingAsset.attrs['asset-link'] = node.url;
- }
+ if (fieldData instanceof Array) {
+ fieldData.forEach((data:any) => {
+ findRTEFieldAndUpdateURL(data);
});
+ } else if (fieldData && typeof fieldData === 'object') {
+ findRTEFieldAndUpdateURL(fieldData);
+ }
}
}
+function findRTEFieldAndUpdateURL(fieldData:any) {
+ const rteField = findRTEField(fieldData);
+
+ if (!rteField) return;
+
+ const edges = rteField?.embedded_itemsConnection?.edges;
+ edges.forEach((edge:any) => {
+ const node = edge.node;
+ if (node?.url && node?.filename) {
+
+ if (!node?.system?.uid) throw new Error('Asset UID not found in the response');
+
+ const correspondingAsset = rteField?.json?.children?.find((child:any) => child.attrs['asset-uid'] === node.system.uid);
+ correspondingAsset.attrs['asset-link'] = node.url;
+ }
+ });
+}
+
function findRTEField(fieldData: any): any {
if (fieldData && fieldData.embedded_itemsConnection) {
return fieldData;