diff --git a/.github/workflows/check-branch.yml b/.github/workflows/check-branch.yml index 1e2d24a..e79864e 100644 --- a/.github/workflows/check-branch.yml +++ b/.github/workflows/check-branch.yml @@ -8,13 +8,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Comment PR - if: github.base_ref == 'master' && github.head_ref != 'next' + if: github.base_ref == 'master' && github.head_ref != 'staging' uses: thollander/actions-comment-pull-request@v2 with: message: | We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the next branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch. - name: Check branch - if: github.base_ref == 'master' && github.head_ref != 'next' + if: github.base_ref == 'master' && github.head_ref != 'staging' run: | echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the next branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch." exit 1 \ No newline at end of file diff --git a/.github/workflows/issues-jira.yml b/.github/workflows/issues-jira.yml new file mode 100644 index 0000000..7bf0469 --- /dev/null +++ b/.github/workflows/issues-jira.yml @@ -0,0 +1,31 @@ +name: Create Jira Ticket for Github Issue + +on: + issues: + types: [opened] + +jobs: + issue-jira: + runs-on: ubuntu-latest + steps: + + - name: Login to Jira + uses: atlassian/gajira-login@master + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Create Jira Issue + id: create_jira + uses: atlassian/gajira-create@master + with: + project: ${{ secrets.JIRA_PROJECT }} + issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} + summary: Github | Issue | ${{ github.event.repository.name }} | ${{ github.event.issue.title }} + description: | + *GitHub Issue:* ${{ github.event.issue.html_url }} + + *Description:* + ${{ github.event.issue.body }} + fields: "${{ secrets.ISSUES_JIRA_FIELDS }}" \ No newline at end of file diff --git a/.github/workflows/jira.yml b/.github/workflows/jira.yml deleted file mode 100644 index caa4bbd..0000000 --- a/.github/workflows/jira.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Create JIRA ISSUE -on: - pull_request: - types: [opened] -jobs: - security-jira: - if: ${{ github.actor == 'dependabot[bot]' || github.actor == 'snyk-bot' || contains(github.event.pull_request.head.ref, 'snyk-fix-') || contains(github.event.pull_request.head.ref, 'snyk-upgrade-')}} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Login into JIRA - uses: atlassian/gajira-login@master - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - name: Create a JIRA Issue - id: create - uses: atlassian/gajira-create@master - with: - project: ${{ secrets.JIRA_PROJECT }} - issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} - summary: | - ${{ github.event.pull_request.title }} - description: | - PR: ${{ github.event.pull_request.html_url }} - - fields: "${{ secrets.JIRA_FIELDS }}" - - name: Transition issue - uses: atlassian/gajira-transition@v3 - with: - issue: ${{ steps.create.outputs.issue }} - transition: ${{ secrets.JIRA_TRANSITION }} diff --git a/.github/workflows/policy-scan.yml b/.github/workflows/policy-scan.yml new file mode 100644 index 0000000..ff25923 --- /dev/null +++ b/.github/workflows/policy-scan.yml @@ -0,0 +1,46 @@ +name: Checks the security policy and configurations +on: + pull_request: + types: [opened, synchronize, reopened] +jobs: + security-policy: + if: github.event.repository.visibility == 'public' + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@master + - name: Checks for SECURITY.md policy file + run: | + if ! [[ -f "SECURITY.md" || -f ".github/SECURITY.md" ]]; then exit 1; fi + security-license: + if: github.event.repository.visibility == 'public' + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@master + - name: Checks for License file + run: | + expected_license_files=("LICENSE" "LICENSE.txt" "LICENSE.md" "License.txt") + license_file_found=false + current_year=$(date +"%Y") + + for license_file in "${expected_license_files[@]}"; do + if [ -f "$license_file" ]; then + license_file_found=true + # check the license file for the current year, if not exists, exit with error + if ! grep -q "$current_year" "$license_file"; then + echo "License file $license_file does not contain the current year." + exit 2 + fi + break + fi + done + + if [ "$license_file_found" = false ]; then + echo "No license file found. Please add a license file to the repository." + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/sast-scan.yml b/.github/workflows/sast-scan.yml deleted file mode 100644 index 3b9521a..0000000 --- a/.github/workflows/sast-scan.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: SAST Scan -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - security-sast: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Semgrep Scan - run: docker run -v /var/run/docker.sock:/var/run/docker.sock -v "${PWD}:/src" returntocorp/semgrep semgrep scan --config auto \ No newline at end of file diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index a642aef..485f1a5 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -3,7 +3,7 @@ on: pull_request: types: [opened, synchronize, reopened] jobs: - security-sca: + security: runs-on: ubuntu-latest steps: - name: Checkout repository @@ -16,4 +16,4 @@ jobs: - name: Run Snyk to check for vulnerabilities run: cd Contentstack.Utils && snyk test --fail-on=all env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml new file mode 100644 index 0000000..049c02f --- /dev/null +++ b/.github/workflows/secrets-scan.yml @@ -0,0 +1,29 @@ +name: Secrets Scan +on: + pull_request: + types: [opened, synchronize, reopened] +jobs: + security-secrets: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: '2' + ref: '${{ github.event.pull_request.head.ref }}' + - run: | + git reset --soft HEAD~1 + - name: Install Talisman + run: | + # Download Talisman + wget https://github.com/thoughtworks/talisman/releases/download/v1.37.0/talisman_linux_amd64 -O talisman + + # Checksum verification + checksum=$(sha256sum ./talisman | awk '{print $1}') + if [ "$checksum" != "8e0ae8bb7b160bf10c4fa1448beb04a32a35e63505b3dddff74a092bccaaa7e4" ]; then exit 1; fi + + # Make it executable + chmod +x talisman + - name: Run talisman + run: | + # Run Talisman with the pre-commit hook + ./talisman --githook pre-commit \ No newline at end of file diff --git a/.talismanrc b/.talismanrc index 4628b50..f94199e 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,4 +1,7 @@ fileignoreconfig: +- filename: .github/workflows/secrets-scan.yml + ignore_detectors: + - filecontent - filename: Contentstack.Utils/Models/Options.cs checksum: 3dc51f0de02429ef9a43b66e666ac4dbde41195e245f8ecc0094548ca8603245 - filename: Contentstack.Utils/Utils.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d6f68e..ac3b4b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### Version: 1.0.6 +#### Date: June-09-2025 +- Used 'title' and 'target' in a tags + +### Version: 1.0.5 +#### Date: Oct-10-2024 +- Used Class Name and Id property in JsonToHTML converter ### Version: 1.0.4 #### Date: Aug-21-2024 diff --git a/CODEOWNERS b/CODEOWNERS index 0773923..1be7e0d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @contentstack/security-admin \ No newline at end of file +* @contentstack/security-admin diff --git a/Contentstack.Utils.Tests/Constants/JsonToHtmlConstants.cs b/Contentstack.Utils.Tests/Constants/JsonToHtmlConstants.cs index 061336e..2d6ecdb 100644 --- a/Contentstack.Utils.Tests/Constants/JsonToHtmlConstants.cs +++ b/Contentstack.Utils.Tests/Constants/JsonToHtmlConstants.cs @@ -16,10 +16,13 @@ public static class JsonToHtmlResultConstants public const string kTableHtml = "

Header 1

Header 2

Body row 1 data 1

Body row 1 data 2

Body row 2 data 1

Body row 2 data 2

"; public const string kBlockquoteHtml = "
Praesent eu ex sed nibh venenatis pretium.
"; public const string kCodeHtml = "Code template."; - public const string kLinkInPHtml = "

LINK

"; - public const string kLinkInPMailToHtml = "

LINK

"; + public const string kLinkInPHtml = "

LINK

"; + public const string kLinkInPMailToHtml = "

LINK

"; public const string kLinkInPMailToTARGEtHtml = "

LINK

"; public const string kEmbedHtml = ""; + public const string stringClassIdResult = "

This is a text to be ignored

"; + public const string kJSONRTEResult = "

demoText is demoText

This is same

demoText2

demoText3demoText4

demoText3demoText2

"; + } public static class JsonToHtmlConstants @@ -47,15 +50,15 @@ public static class JsonToHtmlConstants public const string kEntryReferenceLinkJson = "{ \"uid\":\"06e34a7 5e4 e549d\", \"_version\":1, \"attrs\":{ }, \"children\":[{\"uid\":\"7626ea98e0e95d602210\",\"type\":\"reference\",\"attrs\":{\"target\":\"_self\",\"href\":\"/copy-of-entry-final-02\",\"display-type\":\"link\",\"entry-uid\":\"UID_08\",\"content-type-uid\":\"embeddedrte\",\"locale\":\"en-us\",\"type\":\"entry\",\"class-name\":\"embedded-entry\"},\"children\":[{\"text\":\"/copy-of-entry-final-02\"}]}],\"type\":\"doc\"}"; public const string kEntryReferenceInlineJson = "{ \"uid\":\"06e34a7 5e4 e549d\", \"_version\":1, \"attrs\":{ }, \"children\":[{\"uid\":\"506 4878f3f46 s21f0cbc aff\",\"type\":\"reference\",\"attrs\":{\"display-type\":\"inline\",\"entry-uid\":\"UID_09\",\"content-type-uid\":\"embeddedrte\",\"locale\":\"en-us\",\"type\":\"entry\",\"class-name\":\"embedded-entry\"},\"children\":[{\"text\":\"\"}]}],\"type\":\"doc\"}"; public const string kHRJson = "{ \"uid\":\"06e34a7 5e4 e549d\", \"_version\":1, \"attrs\":{ }, \"children\":[{\"uid\":\"f5a7b57 40a8a5c3 576828276b\",\"type\":\"hr\",\"children\":[{\"text\":\"\"}],\"attrs\":{ }}],\"type\":\"doc\"}"; - - + public const string classId = "{ \"uid\":\"06e34a7 5e4 e549d\", \"_version\":1, \"attrs\":{ }, \"children\":[{\"uid\":\"f5a7b57 40a8a5c3 576828276b\",\"type\":\"doc\",\"children\":[{\"text\":\"demoTextisdemoText\",\"classname\": \"c2\",\"id\": \"i2\"}],\"attrs\":{ }}],\"type\":\"text\"}"; + public const string stringClassId = "{ \"type\": \"doc\", \"attrs\": {}, \"uid\": \"8622288a91dc4c76985d776d2540b395\", \"children\": [ { \"type\": \"p\", \"uid\": \"396ee25abd0f4296a45eac63809450ef\", \"attrs\": {}, \"children\": [ { \"text\": \"This\", \"classname\": \"\", \"id\": \"i3\" }, { \"text\": \" is a \" }, { \"text\": \"text\", \"classname\": \"c1\", \"id\": \"i1\" }, { \"text\": \" to be \" }, { \"text\": \"ignored\", \"classname\": \"c2\", \"id\": \"\" } ] } ], \"_version\": 4 }"; public const string KAssetNode = "\"embedded_itemsConnection\": { \"edges\": [{ \"node\": { \"system\": { \"content_type_uid\": \"sys_assets\", \"uid\": \"UID_12\" }, \"created_at\": \"2020-08-19T09:13:32.785Z\", \"updated_at\": \"2020-08-19T09:13:32.785Z\", \"created_by\": \"Created_at\", \"updated_by\": \"Created_at\", \"content_type\": \"application/pdf\", \"file_size\": \"13264\", \"filename\": \"dummy.pdf\", \"url\":\"/v3/assetsUID_12/dummy.pdf\", \"_version\": 1, \"title\": \"dummy.pdf\" } } ]}"; public const string KEntryBlocNode = "\"embedded_itemsConnection\": { \"edges\": [{ \"node\": { \"title\": \"Update this title\", \"url\": \"\", \"locale\": \"en-us\", \"system\": { \"uid\": \"UID_07\", \"content_type_uid\": \"content_block\" }, \"_version\": 5, \"_in_progress\": false, \"multi_line\": \"\", \"rich_text_editor\": \"\" } } ]}"; public const string KEntryLinkNode = "\"embedded_itemsConnection\": { \"edges\": [{ \"node\": { \"title\": \"Entry with embedded entry\", \"rich_text_editor\": [ \"\" ], \"locale\": \"en-us\", \"system\": { \"uid\": \"UID_08\", \"content_type_uid\": \"embeddedrte\" }, \"_in_progress\": false } } ]}"; public const string KEntryInlineNode = "\"embedded_itemsConnection\": { \"edges\": [{ \"node\": { \"title\": \"updated title\", \"rich_text_editor\": [ \"\" ], \"locale\": \"en-us\", \"system\": { \"uid\": \"UID_09\", \"content_type_uid\": \"embeddedrte\", }, \"_in_progress\": false } } ]}"; public static string KGQLModel(string node, string embedConnection = null) { - return $"{{\"multiplerte\":{{\"json\":[{node}]{(embedConnection != null ? ","+embedConnection : "")} }}, \"singlerte\":{{\"json\":{node}{(embedConnection != null ? "," + embedConnection : "")}}} }}"; + return $"{{\"multiplerte\":{{\"json\":[{node}]{(embedConnection != null ? "," + embedConnection : "")} }}, \"singlerte\":{{\"json\":{node}{(embedConnection != null ? "," + embedConnection : "")}}} }}"; } public const string kAssetMeta = "{\"uid\":\"asset_uid\",\"created_by\":\"create_by\",\"updated_by\":\"update_by\",\"content_type\":\"image/jpeg\",\"file_size\":\"62181\",\"tags\":[],\"filename\":\"crop_area.jpeg\",\"url\":\"http://image.contenstack.com/crop_area.jpeg\",\"is_dir\":false,\"parent_uid\":null,\"path\":[],\"_version\":1,\"title\":\"crop_area.jpeg\",\"dimension\":{\"height\":712,\"width\":864},\"_metadata\":{\"extensions\":{\"extension_uid\":{\"local_metadata\":{\"local_data\":\"main\",\"presets\":[{\"uid\":\"preset_01\",\"name\":\"Local Preset\",\"options\":{\"transform\":{\"height\":500,\"width\":500,\"flip-mode\":\"horiz\",\"rotate\":40},\"focal-point\":{\"x\":-0.668935003427432,\"y\":-0.9220385351936531},\"quality\":\"100\",\"image-type\":\"jpeg\"}},{\"uid\":\"preset_02\",\"name\":\"WithCrop\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":\"569.6\",\"width\":\"569.6\",\"flip-mode\":\"both\"},\"image-type\":\"jpeg\",\"crop\":{\"height\":\"569.6\",\"width\":\"569.6\",\"x\":\"147.2\",\"y\":\"71.2\"}}},{\"uid\":\"preset_03\",\"name\":\"Filter\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":712,\"width\":864},\"image-type\":\"jpeg\",\"effects\":{\"brightness\":52,\"contrast\":15,\"saturate\":-30,\"blur\":16,\"sharpen\":{\"amount\":9,\"radius\":669,\"threshold\":207}}}}]},\"global_metadata\":{\"presets\":[{\"uid\":\"preset_04\",\"name\":\"Global Preset\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":\"712\",\"width\":\"864\",\"flip-mode\":\"verti\"},\"image-type\":\"jpeg\"}}]}}}}}"; @@ -64,6 +67,7 @@ public static string KGQLModel(string node, string embedConnection = null) public const string kAssetPresets = "{\"uid\":\"asset_uid\",\"created_by\":\"create_by\",\"updated_by\":\"update_by\",\"content_type\":\"image/jpeg\",\"file_size\":\"62181\",\"tags\":[],\"filename\":\"crop_area.jpeg\",\"url\":\"http://image.contenstack.com/crop_area.jpeg\",\"is_dir\":false,\"parent_uid\":null,\"path\":[],\"_version\":1,\"title\":\"crop_area.jpeg\",\"dimension\":{\"height\":712,\"width\":864},\"_metadata\":{\"extensions\":{\"extension_uid\":{\"local_metadata\":{\"local_data\":\"main\",\"presets\":[{\"uid\":\"preset_01\",\"name\":\"Local Preset\",\"options\":{}},{\"uid\":\"preset_02\",\"name\":\"WithCrop\",\"options\":{\"quality\":\"100\",\"transform\":{},\"image-type\":\"jpeg\",\"crop\":{}}},{\"uid\":\"preset_03\",\"name\":\"Filter\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":712,\"width\":864},\"image-type\":\"jpeg\",\"effects\":{}}}]},\"global_metadata\":{\"presets\":[{\"uid\":\"preset_04\",\"name\":\"Global Preset\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":\"712\",\"width\":\"864\",\"flip-mode\":\"verti\"},\"image-type\":\"jpeg\"}}]}}}}}"; public const string kAsset = "{\"uid\":\"asset_uid\",\"created_by\":\"create_by\",\"updated_by\":\"update_by\",\"content_type\":\"image/jpeg\",\"file_size\":\"62181\",\"tags\":[],\"filename\":\"crop_area.jpeg\",\"url\":\"http://image.contenstack.com/crop_area.jpeg\",\"is_dir\":false,\"parent_uid\":null,\"path\":[],\"_version\":1,\"title\":\"crop_area.jpeg\",\"dimension\":{\"height\":712,\"width\":864},\"_metadata\":{}}"; public const string kAssetMetaURLQuery = "{\"uid\":\"asset_uid\",\"created_by\":\"create_by\",\"updated_by\":\"update_by\",\"content_type\":\"image/jpeg\",\"file_size\":\"62181\",\"tags\":[],\"filename\":\"crop_area.jpeg\",\"url\":\"http://image.contenstack.com/crop_area.jpeg?render=full&noval\",\"is_dir\":false,\"parent_uid\":null,\"path\":[],\"_version\":1,\"title\":\"crop_area.jpeg\",\"dimension\":{\"height\":712,\"width\":864},\"_metadata\":{\"extensions\":{\"extension_uid\":{\"local_metadata\":{\"local_data\":\"main\",\"presets\":[{\"uid\":\"preset_01\",\"name\":\"Local Preset\",\"options\":{\"transform\":{\"height\":500,\"width\":500,\"flip-mode\":\"horiz\",\"rotate\":40},\"focal-point\":{\"x\":-0.668935003427432,\"y\":-0.9220385351936531},\"quality\":\"100\",\"image-type\":\"jpeg\"}},{\"uid\":\"preset_02\",\"name\":\"WithCrop\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":\"569.6\",\"width\":\"569.6\",\"flip-mode\":\"both\"},\"image-type\":\"jpeg\",\"crop\":{\"height\":\"569.6\",\"width\":\"569.6\",\"x\":\"147.2\",\"y\":\"71.2\"}}},{\"uid\":\"preset_03\",\"name\":\"Filter\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":712,\"width\":864},\"image-type\":\"jpeg\",\"effects\":{\"brightness\":52,\"contrast\":15,\"saturate\":-30,\"blur\":16,\"sharpen\":{\"amount\":9,\"radius\":669,\"threshold\":207}}}}]},\"global_metadata\":{\"presets\":[{\"uid\":\"preset_04\",\"name\":\"Global Preset\",\"options\":{\"quality\":\"100\",\"transform\":{\"height\":\"712\",\"width\":\"864\",\"flip-mode\":\"verti\"},\"image-type\":\"jpeg\"}}]}}}}}"; + public const string kJSONRTE = "{ \"uid\":\"uid\",\"_version\":1,\"locale\":\"en-us\",\"children\":[{\"type\":\"p\",\"children\":[{\"text\":\"demoText\",\"bold\":true},{\"text\":\" is\",\"underline\":true},{\"text\":\" demoText\",\"italic\":true}]},{\"type\":\"p\",\"children\":[{\"text\":\"This\",\"italic\":true,\"subscript\":true},{\"text\":\" \",\"italic\":true},{\"text\":\"is\",\"italic\":true,\"superscript\":true},{\"text\":\" \",\"italic\":true},{\"text\":\"same \",\"italic\":true,\"strikethrough\":true}]},{\"type\":\"p\",\"children\":[{\"text\":\"demoText2\",\"strikethrough\":true,\"inlineCode\":true}]},{\"type\":\"p\",\"children\":[{\"text\":\"demoText3\",\"strikethrough\":true,\"inlineCode\":true},{\"text\":\"demoText4\"}]},{\"type\":\"p\",\"children\":[{\"text\":\"demoText3\"},{\"type\":\"a\",\"attrs\":{\"url\":\"https://www.google.com/\",\"target\":\"_blank\",\"title\":\"demoText5\"},\"children\":[{\"text\":\"demoText2\"}]}]}],\"type\":\"doc\"}"; } } diff --git a/Contentstack.Utils.Tests/DefaultRenderTest.cs b/Contentstack.Utils.Tests/DefaultRenderTest.cs index 776f6c9..a13bd5b 100644 --- a/Contentstack.Utils.Tests/DefaultRenderTest.cs +++ b/Contentstack.Utils.Tests/DefaultRenderTest.cs @@ -1,4 +1,4 @@ -using Contentstack.Utils.Models; +using Contentstack.Utils.Models; using Xunit; using Contentstack.Utils.Tests.Mocks; using Contentstack.Utils.Enums; @@ -178,7 +178,7 @@ public void testLinkhDocument() string result = defaultRender.RenderNode("a", nodeLink, (nodes) => { return text; }); - Assert.Equal($"{text}", result); + Assert.Equal($"Text To set Link", result); } [Fact] diff --git a/Contentstack.Utils.Tests/JsonToHtmlTest.cs b/Contentstack.Utils.Tests/JsonToHtmlTest.cs index 62c06e9..e49da43 100644 --- a/Contentstack.Utils.Tests/JsonToHtmlTest.cs +++ b/Contentstack.Utils.Tests/JsonToHtmlTest.cs @@ -479,5 +479,23 @@ public void Should_Return_Result_For_Array_Hr_Document() Assert.Equal(new List() { "
" }, result); } + + [Fact] + public void TestForClassandId() + { + Node node = NodeParser.parse(JsonToHtmlConstants.stringClassId); + + var result = Utils.JsonToHtml(new List() { node }, defaultRender); + + Assert.Equal(new List() { JsonToHtmlResultConstants.stringClassIdResult }, result); + } + + [Fact] + public void Should_Render_All_Marks_And_Target_Attribute() + { + Node node = NodeParser.parse(JsonToHtmlConstants.kJSONRTE); + var result = Utils.JsonToHtml(node, defaultRender); + Assert.Equal(JsonToHtmlResultConstants.kJSONRTEResult, result); + } } } diff --git a/Contentstack.Utils.sln b/Contentstack.Utils.sln index 85e4cc7..382b162 100644 --- a/Contentstack.Utils.sln +++ b/Contentstack.Utils.sln @@ -21,7 +21,7 @@ Global {EB2B5E23-E45F-4C6C-BF98-FE3971DE4250}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution - version = 1.0.4 + version = 1.0.6 Policies = $0 $0.DotNetNamingPolicy = $1 $1.DirectoryNamespaceAssociation = PrefixedHierarchical diff --git a/Contentstack.Utils/Enums/MarkType.cs b/Contentstack.Utils/Enums/MarkType.cs index 11ff58a..4180f65 100644 --- a/Contentstack.Utils/Enums/MarkType.cs +++ b/Contentstack.Utils/Enums/MarkType.cs @@ -30,6 +30,14 @@ public enum MarkType /// /// This will specify the Mark type of text node as Superscript /// - Superscript - } + Superscript, + /// + /// This will specify the class selector + /// + Class, + /// + /// This will specify the id selector + /// + Id +} } diff --git a/Contentstack.Utils/Interfaces/IOptions.cs b/Contentstack.Utils/Interfaces/IOptions.cs index 6ea9f90..1cccd09 100644 --- a/Contentstack.Utils/Interfaces/IOptions.cs +++ b/Contentstack.Utils/Interfaces/IOptions.cs @@ -10,7 +10,7 @@ namespace Contentstack.Utils.Interfaces public interface IRenderable { string RenderOption(IEmbeddedObject entry, Metadata metadata); - string RenderMark(MarkType markType, string text); + string RenderMark(MarkType markType, string text, string className, string Id); string RenderNode(string nodeType, Node node, NodeChildrenCallBack callBack); } } diff --git a/Contentstack.Utils/LICENSE.txt b/Contentstack.Utils/LICENSE.txt index 3333caa..501f936 100644 --- a/Contentstack.Utils/LICENSE.txt +++ b/Contentstack.Utils/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright © 2012-2024 Contentstack. All Rights Reserved +Copyright © 2012-2025 Contentstack. All Rights Reserved Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Contentstack.Utils/Models/Options.cs b/Contentstack.Utils/Models/Options.cs index 9f741ab..724322a 100644 --- a/Contentstack.Utils/Models/Options.cs +++ b/Contentstack.Utils/Models/Options.cs @@ -1,6 +1,6 @@ -using System; +using System; using System.Collections.Generic; -using Contentstack.Utils.Enums; +using Contentstack.Utils.Enums; using Contentstack.Utils.Interfaces; using Newtonsoft.Json.Linq; @@ -23,14 +23,14 @@ public virtual string RenderOption(IEmbeddedObject embeddedObject, Metadata meta switch (metadata.StyleType) { case Enums.StyleType.Block: - string renderString = "

" + embeddedObject.Uid + "

"; + string renderString = "

" + embeddedObject.Uid + "

"; if (embeddedObject is IEmbeddedEntry) { - renderString += "

Content type: " + ((IEmbeddedEntry)embeddedObject).Title + "

"; + renderString += "

Content type: " + ((IEmbeddedEntry)embeddedObject).Title + "

"; } else { - renderString += "

Content type: " + embeddedObject.ContentTypeUid + "

"; + renderString += "

Content type: " + embeddedObject.ContentTypeUid + "

"; } renderString = renderString + "
"; return renderString; @@ -38,35 +38,35 @@ public virtual string RenderOption(IEmbeddedObject embeddedObject, Metadata meta case Enums.StyleType.Inline: if (embeddedObject is IEmbeddedEntry) { - return "" + ((IEmbeddedEntry)embeddedObject).Title + ""; + return "" + ((IEmbeddedEntry)embeddedObject).Title + ""; } - return "" + embeddedObject.Uid + ""; + return "" + embeddedObject.Uid + ""; case Enums.StyleType.Link: if (embeddedObject is IEmbeddedEntry) { - return "" + (metadata.Text ?? ((IEmbeddedEntry)embeddedObject).Title) + ""; + return "" + (metadata.Text ?? ((IEmbeddedEntry)embeddedObject).Title) + ""; } - return "" + (metadata.Text ?? embeddedObject.Uid) + ""; + return "" + (metadata.Text ?? embeddedObject.Uid) + ""; case Enums.StyleType.Display: if (embeddedObject is IEmbeddedAsset) { - return "\"""; + return "\"""; } - return "\"""; + return "\"""; case Enums.StyleType.Download: if (embeddedObject is IEmbeddedAsset) { - return "" + (metadata.Text ?? ((IEmbeddedAsset)embeddedObject).Title) + ""; + return "" + (metadata.Text ?? ((IEmbeddedAsset)embeddedObject).Title) + ""; } - return "" + (metadata.Text ?? embeddedObject.Uid) + ""; + return "" + (metadata.Text ?? embeddedObject.Uid) + ""; } - return ""; + return ""; } - public virtual string RenderMark(MarkType markType, string text) + public virtual string RenderMark(MarkType markType, string text, string className = "", string id = "") { switch (markType) { @@ -84,6 +84,11 @@ public virtual string RenderMark(MarkType markType, string text) return $"{text}"; case MarkType.Superscript: return $"{text}"; + case MarkType.Id: + case MarkType.Class: + string classAttr = !string.IsNullOrEmpty(className) ? $" class=\"{className}\"" : ""; + string idAttr = !string.IsNullOrEmpty(id) ? $" id=\"{id}\"" : ""; + return $"{text}"; } return text; } @@ -92,8 +97,10 @@ public virtual string RenderNode(string nodeType, Node node, NodeChildrenCallBac { string href = ""; string styleAttrs = ""; + string target = ""; + string title = ""; - if (node.attrs.ContainsKey("style")) + if (node.attrs?.ContainsKey("style") == true) { var styleVal = node.attrs["style"]; if (styleVal != null) @@ -120,11 +127,20 @@ public virtual string RenderNode(string nodeType, Node node, NodeChildrenCallBac case "p": return $"{callBack(node.children)}

"; case "a": - if (node.attrs.ContainsKey("url")) + if (node.attrs?.ContainsKey("url")==true) { href = (string)node.attrs["url"]; } - return $"{callBack(node.children)}"; + if (node.attrs?.ContainsKey("target") == true) + { + target = (string)node.attrs["target"]; + } + if (node.attrs?.ContainsKey("title") == true) + { + title = (string)node.attrs["title"]; + } + return $"{callBack(node.children)}"; + case "img": if (node.attrs.ContainsKey("url")) { diff --git a/Contentstack.Utils/Utils.cs b/Contentstack.Utils/Utils.cs index 34b6a96..81f4c00 100644 --- a/Contentstack.Utils/Utils.cs +++ b/Contentstack.Utils/Utils.cs @@ -159,6 +159,10 @@ private static string textToHtml(TextNode textNode, Options options) { text = options.RenderMark(MarkType.Bold, text: text); } + if (!string.IsNullOrEmpty(textNode.classname) || !string.IsNullOrEmpty(textNode.id)) + { + text = options.RenderMark(MarkType.Class, text: text, textNode.classname, textNode.id); + } return text; } diff --git a/Directory.Build.props b/Directory.Build.props index d5c9309..2a251d5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 1.0.4 + 1.0.6 diff --git a/LICENSE b/LICENSE index 3333caa..501f936 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright © 2012-2024 Contentstack. All Rights Reserved +Copyright © 2012-2025 Contentstack. All Rights Reserved Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal