From f59f2205a97cf7c1d5fa42b0602cb638e9f31731 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 16 Jan 2024 16:55:41 -0500 Subject: [PATCH 1/9] Fix RSS parsing with new package --- frontend/package.json | 3 +- frontend/src/components/FeedArticle.js | 19 +++++- frontend/src/components/FeedArticle.scss | 42 ++---------- frontend/yarn.lock | 84 +++++++++++++++++++++++- 4 files changed, 108 insertions(+), 40 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 44ba348edb..015e1e19d8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "draftjs-to-html": "^0.9.1", "ejs": "^3.1.7", "follow-redirects": "^1.15.4", + "html-react-parser": "^5.1.1", "html-to-draftjs": "^1.5.0", "html2canvas": "^1.4.1", "http-proxy-middleware": "^2.0.1", @@ -162,8 +163,8 @@ "@testing-library/dom": "^8.11.1", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^12.1.2", - "@testing-library/user-event": "^13.5.0", "@testing-library/react-hooks": "^8.0.1", + "@testing-library/user-event": "^13.5.0", "cross-env": "^7.0.2", "eslint": "^7.20.0", "eslint-config-airbnb": "^18.2.0", diff --git a/frontend/src/components/FeedArticle.js b/frontend/src/components/FeedArticle.js index 0cae988d34..def1803744 100644 --- a/frontend/src/components/FeedArticle.js +++ b/frontend/src/components/FeedArticle.js @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useLayoutEffect } from 'react'; import PropTypes from 'prop-types'; -import ReadOnlyEditor from './ReadOnlyEditor'; +import parse from 'html-react-parser'; import './FeedArticle.scss'; const FeedArticle = ({ @@ -9,13 +9,26 @@ const FeedArticle = ({ unread, partial, }) => { + /** + * to match the styling in the design system + * we need to add the usa-prose class to all + * paragraphs in the article content + */ + useLayoutEffect(() => { + const paragraphs = document.querySelectorAll('.ttahub-feed-article-content p'); + const paragraphsArray = Array.from(paragraphs); + paragraphsArray.forEach((p) => { + p.classList.add('usa-prose'); + }); + }); + const className = `ttahub-feed-article ${partial ? 'ttahub-feed-article--partial' : ''}`; return (

{title}

- + {parse(content)}
); diff --git a/frontend/src/components/FeedArticle.scss b/frontend/src/components/FeedArticle.scss index d6a891c27b..a5bdf974f1 100644 --- a/frontend/src/components/FeedArticle.scss +++ b/frontend/src/components/FeedArticle.scss @@ -1,39 +1,11 @@ -@use '../colors.scss' as *; - -/** -this hides the initial "author inforation" blocks" -**/ -.ttahub-feed-article:not(.ttahub-feed-article--partial) div[data-contents="true"] > [data-block="true"]:nth-child(1), -.ttahub-feed-article:not(.ttahub-feed-article--partial) div[data-contents="true"] > [data-block="true"]:nth-child(2), -.ttahub-feed-article:not(.ttahub-feed-article--partial) div[data-contents="true"] > [data-block="true"]:nth-child(3), -.ttahub-feed-article:not(.ttahub-feed-article--partial) div[data-contents="true"] > [data-block="true"]:last-child, -.ttahub-feed-article div[data-contents="true"] > [data-block="true"]:empty, -.ttahub-feed-article h4:empty { +// hide the "blog post by" text +.ttahub-feed-article-content p:has(a:not([href])){ display: none; } - -// fix the margins and spacing of the read only editor -.ttahub-feed-article .public-DraftStyleDefault-block { - font-size: 1.06rem; - line-height: 1.5; - margin: 0; - white-space: normal; -} - -// hide the big old link icon that pops up -.ttahub-feed-article .rdw-link-decorator-icon { - display: none; -} - -.ttahub-feed-article .public-DraftStyleDefault-block a:after, -.ttahub-read-more--external:after { - content: "\f35d"; - display: inline-block; - font: normal normal normal 16px/1 FontAwesome; - margin: 0 8px; -} - -.ttahub-feed-article .public-DraftStyleDefault-block a:hover:after { - opacity: 0.75; +// remove inline styles from feed +// - since they are inline, we have to use !important +.ttahub-feed-article-content .feed > div { + border: none !important; + padding: 0 !important; } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 08e8abb7bd..1bc096d7ab 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4909,12 +4909,21 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domelementtype@^2.0.1, domelementtype@^2.2.0: +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -4926,6 +4935,13 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +domhandler@5.0.3, domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -4955,6 +4971,15 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -5126,6 +5151,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.2.0, entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -6575,6 +6605,14 @@ hsluv@^0.0.3: resolved "https://registry.yarnpkg.com/hsluv/-/hsluv-0.0.3.tgz#829107dafb4a9f8b52a1809ed02e091eade6754c" integrity sha512-08iL2VyCRbkQKBySkSh6m8zMUa3sADAxGVWs3Z1aPcUkTJeK0ETG4Fc27tEmQBGUAXZjIsXOZqBvacuVNSC/fQ== +html-dom-parser@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-5.0.7.tgz#33f029b34e8ec4076b7e2624c97debc79abb0c75" + integrity sha512-2YD2/yB0QgrlkBIn0CsGaRXC89E1gtuPVpiOGC52NTzPCC83n0WMdGD+5q7lpcKqbCpnWValQbovuy/NI/0kag== + dependencies: + domhandler "5.0.3" + htmlparser2 "9.1.0" + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -6605,6 +6643,16 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" +html-react-parser@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-5.1.1.tgz#9863709ab1bb5d15bce273253b539a6dd4fa28e2" + integrity sha512-L5VK0rKN3VM7uzRH+4wxAL9elvHuCNDjyWKKjcCDR+YWW5Qr7WWSK7+e627DcePVAFi5IMqc+rAU8j/1DpC/Tw== + dependencies: + domhandler "5.0.3" + html-dom-parser "5.0.7" + react-property "2.0.2" + style-to-js "1.1.10" + html-to-draftjs@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz#0df0eabf429deaedb63f5c859889e2c983606e86" @@ -6629,6 +6677,16 @@ html2canvas@^1.4.1: css-line-break "^2.1.0" text-segmentation "^1.0.3" +htmlparser2@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" + integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -6837,6 +6895,11 @@ ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +inline-style-parser@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.2.tgz#d498b4e6de0373458fc610ff793f6b14ebf45633" + integrity sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ== + internal-slot@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3" @@ -10017,6 +10080,11 @@ react-plotly.js@^2.5.1: dependencies: prop-types "^15.8.1" +react-property@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6" + integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug== + react-redux@^7.1.0: version "7.2.9" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" @@ -11203,6 +11271,20 @@ style-loader@^3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== +style-to-js@1.1.10: + version "1.1.10" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.10.tgz#ec20e1264ba11dc7f71b94b3a3a05566ed856e54" + integrity sha512-VC7MBJa+y0RZhpnLKDPmVRLRswsASLmixkiZ5R8xZpNT9VyjeRzwnXd2pBzAWdgSGv/pCNNH01gPCCUsB9exYg== + dependencies: + style-to-object "1.0.5" + +style-to-object@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.5.tgz#5e918349bc3a39eee3a804497d97fcbbf2f0d7c0" + integrity sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ== + dependencies: + inline-style-parser "0.2.2" + stylehacks@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" From 1da6d16b1da1fd3907b7a00e3a10de9bfaf63315 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 12 Sep 2024 12:00:06 -0400 Subject: [PATCH 2/9] Re-run yarn to generate new lockfile --- frontend/yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 00faadfc6a..6558c2fb5e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -5167,7 +5167,7 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -ejs@^3.1.10, ejs@^3.1.6: +ejs@^3.1.10, ejs@^3.1.6, ejs@^3.1.7: version "3.1.10" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== From 18e1d4ce99e8a462528f4c3f7ba6d4122a010b41 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 12 Sep 2024 13:12:36 -0400 Subject: [PATCH 3/9] Update existing implementations --- frontend/src/components/FeedArticle.js | 12 ++++ frontend/src/components/FeedArticle.scss | 4 ++ .../components/GoalForm/ObjectiveTopics.js | 1 - .../components/GoalForm/ObjectiveTopics.scss | 9 --- frontend/src/components/SupportTypeDrawer.js | 1 - .../src/components/SupportTypeDrawer.scss | 65 ------------------- .../Notifications/components/WhatsNew.scss | 4 ++ .../pages/components/ClassReview.scss | 7 ++ 8 files changed, 27 insertions(+), 76 deletions(-) delete mode 100644 frontend/src/components/GoalForm/ObjectiveTopics.scss delete mode 100644 frontend/src/components/SupportTypeDrawer.scss diff --git a/frontend/src/components/FeedArticle.js b/frontend/src/components/FeedArticle.js index def1803744..bcd938ed20 100644 --- a/frontend/src/components/FeedArticle.js +++ b/frontend/src/components/FeedArticle.js @@ -20,6 +20,18 @@ const FeedArticle = ({ paragraphsArray.forEach((p) => { p.classList.add('usa-prose'); }); + + const tables = document.querySelectorAll('.ttahub-feed-article-content table'); + const tablesArray = Array.from(tables); + tablesArray.forEach((table) => { + table.classList.add('usa-table'); + }); + + const lists = document.querySelectorAll('.ttahub-feed-article-content ul'); + const listsArray = Array.from(lists); + listsArray.forEach((list) => { + list.classList.add('usa-list'); + }); }); const className = `ttahub-feed-article ${partial ? 'ttahub-feed-article--partial' : ''}`; diff --git a/frontend/src/components/FeedArticle.scss b/frontend/src/components/FeedArticle.scss index a5bdf974f1..9160599c04 100644 --- a/frontend/src/components/FeedArticle.scss +++ b/frontend/src/components/FeedArticle.scss @@ -9,3 +9,7 @@ border: none !important; padding: 0 !important; } + +.ttahub-feed-article-content .feed div:last-child{ + display: none; +} \ No newline at end of file diff --git a/frontend/src/components/GoalForm/ObjectiveTopics.js b/frontend/src/components/GoalForm/ObjectiveTopics.js index 5f7424d038..7bb5ba7971 100644 --- a/frontend/src/components/GoalForm/ObjectiveTopics.js +++ b/frontend/src/components/GoalForm/ObjectiveTopics.js @@ -9,7 +9,6 @@ import Drawer from '../Drawer'; import Req from '../Req'; import ContentFromFeedByTag from '../ContentFromFeedByTag'; import DrawerTriggerButton from '../DrawerTriggerButton'; -import './ObjectiveTopics.scss'; export default function ObjectiveTopics({ error, diff --git a/frontend/src/components/GoalForm/ObjectiveTopics.scss b/frontend/src/components/GoalForm/ObjectiveTopics.scss deleted file mode 100644 index 7758fc0545..0000000000 --- a/frontend/src/components/GoalForm/ObjectiveTopics.scss +++ /dev/null @@ -1,9 +0,0 @@ -.ttahub-drawer--objective-topics-guidance div.rdw-center-aligned-block:nth-child(1), -.ttahub-drawer--objective-topics-guidance div.rdw-center-aligned-block:nth-child(2) { - display: none; -} - -.ttahub-drawer--objective-topics-guidance [data-block="true"]:nth-child(even) { - margin-bottom: 1rem; -} - diff --git a/frontend/src/components/SupportTypeDrawer.js b/frontend/src/components/SupportTypeDrawer.js index 853236dde4..744e2565e6 100644 --- a/frontend/src/components/SupportTypeDrawer.js +++ b/frontend/src/components/SupportTypeDrawer.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import Drawer from './Drawer'; import ContentFromFeedByTag from './ContentFromFeedByTag'; -import './SupportTypeDrawer.scss'; export default function SupportTypeDrawer({ drawerTriggerRef, diff --git a/frontend/src/components/SupportTypeDrawer.scss b/frontend/src/components/SupportTypeDrawer.scss deleted file mode 100644 index fa5bbc54d8..0000000000 --- a/frontend/src/components/SupportTypeDrawer.scss +++ /dev/null @@ -1,65 +0,0 @@ -/* hides centered header and misaligned bullet points */ -.ttahub-drawer--objective-support-type-guidance div.rdw-center-aligned-block:nth-child(2), -.ttahub-drawer--objective-support-type-guidance div.rdw-center-aligned-block:nth-child(1), -.ttahub-drawer--objective-support-type-guidance .public-DraftStyleDefault-ul { - display: none; -} - -/* creates left margin for bullet points */ -.ttahub-drawer--objective-support-type-guidance .public-DraftStyleDefault-ul + div > div > span > span { - margin-left: 2em; -} - -/* creates bullet points */ -.ttahub-drawer--objective-support-type-guidance .public-DraftStyleDefault-ul + div > div > span > span:before { - content: '•'; - margin-right: 1em; -} - -/* creates margin between bold headers */ -.ttahub-drawer--objective-support-type-guidance .rdw-editor-main > div > div > div > div > div:nth-child(12), -.ttahub-drawer--objective-support-type-guidance .rdw-editor-main > div > div > div > div > div:nth-child(15), -.ttahub-drawer--objective-support-type-guidance .rdw-editor-main > div > div > div > div > div:nth-child(22) { - margin-top: 1rem; -} - -/* creates margin between text and bullets */ -.ttahub-drawer--objective-support-type-guidance .rdw-editor-main > div > div > div > div > div:nth-child(4), -.ttahub-drawer--objective-support-type-guidance .rdw-editor-main > div > div > div > div > div:nth-child(13), -.ttahub-drawer--objective-support-type-guidance .rdw-editor-main > div > div > div > div > div:nth-child(16), -.ttahub-drawer--objective-support-type-guidance .rdw-editor-main > div > div > div > div > div:nth-child(23) { - margin-bottom: 10px; -} - -/* hides centered header and misaligned bullet points */ -.ttahub-drawer--objective-support-type-guidance div.rdw-center-aligned-block:nth-child(2), -.ttahub-drawer--objective-support-type-guidance div.rdw-center-aligned-block:nth-child(1), -.ttahub-drawer--objective-support-type-guidance .public-DraftStyleDefault-ul { - display: none; -} - -/* creates left margin for bullet points */ -.ttahub-drawer--objective-support-type-guidance .public-DraftStyleDefault-ul + div > div > span > span { - margin-left: 2em; -} - -/* creates bullet points */ -.ttahub-drawer--objective-support-type-guidance .public-DraftStyleDefault-ul + div > div > span > span:before { - content: '•'; - margin-right: 1em; -} - -/* creates margin between bold headers */ -.rdw-editor-main > div > div > div > div > div:nth-child(12), -.rdw-editor-main > div > div > div > div > div:nth-child(15), -.rdw-editor-main > div > div > div > div > div:nth-child(22) { - margin-top: 1rem; -} - -/* creates margin between text and bullets */ -.rdw-editor-main > div > div > div > div > div:nth-child(4), -.rdw-editor-main > div > div > div > div > div:nth-child(13), -.rdw-editor-main > div > div > div > div > div:nth-child(16), -.rdw-editor-main > div > div > div > div > div:nth-child(23) { - margin-bottom: 10px; -} diff --git a/frontend/src/pages/Notifications/components/WhatsNew.scss b/frontend/src/pages/Notifications/components/WhatsNew.scss index 8836a4d33d..d03a54ac3c 100644 --- a/frontend/src/pages/Notifications/components/WhatsNew.scss +++ b/frontend/src/pages/Notifications/components/WhatsNew.scss @@ -8,6 +8,10 @@ padding-left: 2rem; } +.ttahub-feed-whats-new .ttahub-feed-article-content .feed div:last-child{ + display: none; +} + // this adds the little green circle to the left of the "new" notification .ttahub-feed-whats-new .ttahub-feed-article--unread .ttahub-feed-article-content:before { content: ''; diff --git a/frontend/src/pages/RecipientRecord/pages/components/ClassReview.scss b/frontend/src/pages/RecipientRecord/pages/components/ClassReview.scss index f19b9ee26b..7673166e7c 100644 --- a/frontend/src/pages/RecipientRecord/pages/components/ClassReview.scss +++ b/frontend/src/pages/RecipientRecord/pages/components/ClassReview.scss @@ -24,3 +24,10 @@ margin-bottom: 12px; padding-bottom: 0; } + +.ttahub-class-feed-article .ttahub-feed-article p:has(strong){ + text-align: left; +} +.ttahub-class-feed-article .ttahub-feed-article th .usa-prose { + margin: 0; +} From b6c60055ed88800732f9a423a234904ea49b5739 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 12 Sep 2024 13:18:58 -0400 Subject: [PATCH 4/9] Refactor for fewer iterations --- frontend/src/components/FeedArticle.js | 33 +++++++++++++------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/FeedArticle.js b/frontend/src/components/FeedArticle.js index bcd938ed20..e61aaf9777 100644 --- a/frontend/src/components/FeedArticle.js +++ b/frontend/src/components/FeedArticle.js @@ -3,6 +3,12 @@ import PropTypes from 'prop-types'; import parse from 'html-react-parser'; import './FeedArticle.scss'; +const TAG_CLASSES = { + P: 'usa-prose', + TABLE: 'usa-table', + UL: 'usa-list', +}; + const FeedArticle = ({ title, content, @@ -15,22 +21,17 @@ const FeedArticle = ({ * paragraphs in the article content */ useLayoutEffect(() => { - const paragraphs = document.querySelectorAll('.ttahub-feed-article-content p'); - const paragraphsArray = Array.from(paragraphs); - paragraphsArray.forEach((p) => { - p.classList.add('usa-prose'); - }); - - const tables = document.querySelectorAll('.ttahub-feed-article-content table'); - const tablesArray = Array.from(tables); - tablesArray.forEach((table) => { - table.classList.add('usa-table'); - }); - - const lists = document.querySelectorAll('.ttahub-feed-article-content ul'); - const listsArray = Array.from(lists); - listsArray.forEach((list) => { - list.classList.add('usa-list'); + const tags = document.querySelectorAll(` + .ttahub-feed-article-content p, + .ttahub-feed-article-content table, + .ttahub-feed-article-content ul + `); + + Array.from(tags).forEach((tag) => { + const tagClass = TAG_CLASSES[tag.tagName]; + if (tagClass) { + tag.classList.add(tagClass); + } }); }); From 9e907f49e3a8ca9d6b5ffbd24e34b0e6c6480d75 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 12 Sep 2024 13:19:58 -0400 Subject: [PATCH 5/9] Update inaccurate comment --- frontend/src/components/FeedArticle.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/FeedArticle.js b/frontend/src/components/FeedArticle.js index e61aaf9777..a746c84a31 100644 --- a/frontend/src/components/FeedArticle.js +++ b/frontend/src/components/FeedArticle.js @@ -16,9 +16,8 @@ const FeedArticle = ({ partial, }) => { /** - * to match the styling in the design system - * we need to add the usa-prose class to all - * paragraphs in the article content + * to match the styling in the design system, we attach USWDS classes to the + * appropriate elements */ useLayoutEffect(() => { const tags = document.querySelectorAll(` From f8c614bcecf8c320e5628583b2d039a0752fb798 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 17 Sep 2024 14:17:11 -0400 Subject: [PATCH 6/9] Tweaks, fixes, and a test --- .../src/components/ContentFromFeedByTag.js | 5 +- frontend/src/components/FeedArticle.js | 11 +++ frontend/src/components/SupportTypeDrawer.js | 2 +- .../__tests__/ContentFromFeedByTag.js | 84 ++++++++++++++++++- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ContentFromFeedByTag.js b/frontend/src/components/ContentFromFeedByTag.js index 5c21c4049b..24838f713b 100644 --- a/frontend/src/components/ContentFromFeedByTag.js +++ b/frontend/src/components/ContentFromFeedByTag.js @@ -20,6 +20,7 @@ export default function ContentFromFeedByTag({ tagName, contentSelector, className, + openLinksInNewTab, }) { const [content, setContent] = useState(''); @@ -62,7 +63,7 @@ export default function ContentFromFeedByTag({ return (
- +
); } @@ -71,9 +72,11 @@ ContentFromFeedByTag.propTypes = { tagName: PropTypes.string.isRequired, contentSelector: PropTypes.string, className: PropTypes.string, + openLinksInNewTab: PropTypes.bool, }; ContentFromFeedByTag.defaultProps = { contentSelector: '', className: '', + openLinksInNewTab: false, }; diff --git a/frontend/src/components/FeedArticle.js b/frontend/src/components/FeedArticle.js index a746c84a31..d4a464a5c7 100644 --- a/frontend/src/components/FeedArticle.js +++ b/frontend/src/components/FeedArticle.js @@ -14,6 +14,7 @@ const FeedArticle = ({ content, unread, partial, + openLinksInNewTab, }) => { /** * to match the styling in the design system, we attach USWDS classes to the @@ -32,6 +33,14 @@ const FeedArticle = ({ tag.classList.add(tagClass); } }); + + if (openLinksInNewTab) { + const links = document.querySelectorAll('.ttahub-feed-article-content a'); + Array.from(links).forEach((link) => { + link.setAttribute('target', '_blank'); + link.setAttribute('rel', 'noopener noreferrer'); + }); + } }); const className = `ttahub-feed-article ${partial ? 'ttahub-feed-article--partial' : ''}`; @@ -51,10 +60,12 @@ FeedArticle.propTypes = { content: PropTypes.string.isRequired, unread: PropTypes.bool.isRequired, partial: PropTypes.bool, + openLinksInNewTab: PropTypes.bool, }; FeedArticle.defaultProps = { partial: false, + openLinksInNewTab: false, }; export default FeedArticle; diff --git a/frontend/src/components/SupportTypeDrawer.js b/frontend/src/components/SupportTypeDrawer.js index 744e2565e6..756f954d47 100644 --- a/frontend/src/components/SupportTypeDrawer.js +++ b/frontend/src/components/SupportTypeDrawer.js @@ -13,7 +13,7 @@ export default function SupportTypeDrawer({ stickyFooter title="Support type guidance" > - + ); } diff --git a/frontend/src/components/__tests__/ContentFromFeedByTag.js b/frontend/src/components/__tests__/ContentFromFeedByTag.js index 57bc59f169..354e88a38a 100644 --- a/frontend/src/components/__tests__/ContentFromFeedByTag.js +++ b/frontend/src/components/__tests__/ContentFromFeedByTag.js @@ -45,9 +45,15 @@ const DEFAULT_RESPONSE = ` describe('ContentFromFeedByTag', () => { afterEach(() => fetchMock.restore()); - const renderContentFromFeed = (tag = 'tag', content = DEFAULT_RESPONSE, selector = null) => { + const renderContentFromFeed = ( + tag = 'tag', + content = DEFAULT_RESPONSE, + selector = null, + openLinksInNewTab = false, + ) => { fetchMock.get(`/api/feeds/item?tag=${tag}`, content); - render(); + // eslint-disable-next-line max-len + render(); }; it('renders a feed', async () => { @@ -112,4 +118,78 @@ describe('ContentFromFeedByTag', () => { expect(readOnly).toBeTruthy(); }); }); + + it('properly formats support type response (as an example)', async () => { + const supportTypeResponse = ` + + Tag ttahub-tta-support-type + + Confluence Syndication Feed + https://acf-ohs.atlassian.net/wiki + + OHS guidance on TTA support types + + + + + + + + + User Author + + tag:acf-ohs.atlassian.net,2009:page-184516720-11 + 2024-09-13T15:11:34Z + 2024-09-13T15:11:34Z + <div class="feed"> <p> + Page + <b>edited</b> by + <a >User Author</a> + </p> + <div style="border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; padding: 10px;"> + <p>Support types are assigned at the objective level for each AR or TR. </p><p /><h2 id="OHSguidanceonTTAsupporttypes-TTAsupporttype"><strong>TTA support type</strong></h2><p>TTA support types describe the content of the TTA session and what that content was intended to support the recipient to accomplish. While the support types do build upon each other, there is no expectation that every goal will start with “Introducing” and end with “Maintaining”. Specialists should use the support types to describe the highest level of support offered during the session. When selecting support types consider how the content supports the recipient with:</p><h3 id="OHSguidanceonTTAsupporttypes-Introducing"><strong>Introducing</strong></h3><p>Introducing and assessing knowledge and/or awareness of concepts, subject matter, and practices.</p><p>Use this type for TTA activities that include content such as:</p><ul><li><p>introducing concepts or new practices</p></li><li><p>assessing participant knowledge</p></li><li><p>needs assessments</p></li></ul><h3 id="OHSguidanceonTTAsupporttypes-Planning"><strong>Planning</strong></h3><p>Planning to initiate new/revised services and systems.</p><p>Use this type for TTA activities that include content that involves participants developing a plan such as a coaching plan, staff wellness plan, corrective action plan, etc.</p><h3 id="OHSguidanceonTTAsupporttypes-Implementing"><strong>Implementing</strong></h3><p>Implementing new/revised services and systems.</p><p>Use this type for TTA activities that include content such as:</p><ul><li><p>developing or revising policies and procedures to address a service/system</p></li><li><p>training for staff to implement a new initiative</p></li></ul><h3 id="OHSguidanceonTTAsupporttypes-Maintaining"><strong>Maintaining</strong></h3><p>Maintaining and monitoring services and systems and ensuring ongoing quality improvement.</p><p>Use this type for TTA activities that include content such as:</p><ul><li><p>reviewing data to identify needed course corrections</p></li><li><p>reviewing ongoing monitoring data</p></li><li><p>conducting ongoing monitoring</p></li><li><p>reviewing progress on program goals</p></li></ul><hr/><h2 style="text-align: center;" id="OHSguidanceonTTAsupporttypes-Needmorehelp?">Need more help?</h2><p style="text-align: center;">If you can’t find an answer, <a class="external-link" href="https://app.smartsheetgov.com/b/form/f0b4725683f04f349a939bd2e3f5425a" rel="nofollow">contact support</a>.</p> + </div> + <div style="padding: 10px 0;"> + <a href="https://acf-ohs.atlassian.net/wiki/spaces/OHSTTA/pages/184516720/OHS+guidance+on+TTA+support+types">email.notification.view.online</a> + &middot; + <a href="https://acf-ohs.atlassian.net/wiki/pages/diffpagesbyversion.action?pageId=184516720&revisedVersion=11&originalVersion=10">View Changes Online</a> + </div> +</div> + User Author + 2024-09-13T15:11:34Z + +`; + + act(() => { + renderContentFromFeed( + 'tag', + supportTypeResponse, + null, + true, + ); + }); + + const article = document.querySelector('.ttahub-feed-article-content'); + expect(article).not.toBeNull(); + const lists = article.querySelectorAll('ul'); + lists.forEach((list) => { + expect(list).toHaveClass('usa-list'); + }); + + const tables = article.querySelectorAll('table'); + tables.forEach((table) => { + expect(table).toHaveClass('usa-table'); + }); + + const paragraphs = article.querySelectorAll('p'); + paragraphs.forEach((paragraph) => { + expect(paragraph).toHaveClass('usa-prose'); + }); + + const links = article.querySelectorAll('a'); + links.forEach((link) => { + expect(link).toHaveAttribute('target', '_blank'); + expect(link).toHaveAttribute('rel', 'noopener noreferrer'); + }); + }); }); From f6fc09abe76b37ccf2d8e489f55f699812b97f0c Mon Sep 17 00:00:00 2001 From: GarrettEHill Date: Fri, 20 Sep 2024 09:44:03 -0700 Subject: [PATCH 7/9] Fix copy paste typo --- frontend/src/components/GoalForm/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/GoalForm/index.js b/frontend/src/components/GoalForm/index.js index 3f89df0267..345e601cc8 100644 --- a/frontend/src/components/GoalForm/index.js +++ b/frontend/src/components/GoalForm/index.js @@ -100,7 +100,7 @@ export default function GoalForm({ useDeepCompareEffect(() => { const newPrompts = grantsToMultiValue(selectedGrants, { ...prompts }); if ((!isEqual(newPrompts, prompts))) { - setSource(newPrompts); + setPrompts(newPrompts); } }, [prompts, selectedGrants]); From 59090a708d7b027269c926ae7b4af860dba819f9 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 24 Sep 2024 11:43:17 -0400 Subject: [PATCH 8/9] Trim title on RTR objective creation --- src/goalServices/goals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/goalServices/goals.js b/src/goalServices/goals.js index d7b5a84d93..707163c98b 100644 --- a/src/goalServices/goals.js +++ b/src/goalServices/goals.js @@ -584,7 +584,7 @@ export async function createOrUpdateGoals(goals) { if (!objective) { objective = await Objective.create({ status: objectiveStatus, - title, + title: title.trim(), goalId: newGoal.id, createdVia: 'rtr', }); From 408c5b8cb11b62bea426d50b04c789a6e9876f72 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 24 Sep 2024 11:59:59 -0400 Subject: [PATCH 9/9] Update known issues, react script --- frontend/yarn-audit-known-issues | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/yarn-audit-known-issues b/frontend/yarn-audit-known-issues index 697c6815af..afca62462a 100644 --- a/frontend/yarn-audit-known-issues +++ b/frontend/yarn-audit-known-issues @@ -3,3 +3,4 @@ {"type":"auditAdvisory","data":{"resolution":{"id":1097682,"path":"react-scripts>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-environment-jsdom>jsdom>tough-cookie","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"4.0.0","paths":["react-scripts>jest>@jest/core>jest-config>jest-environment-jsdom>jsdom>tough-cookie","react-scripts>jest>jest-cli>@jest/core>jest-config>jest-environment-jsdom>jsdom>tough-cookie","react-scripts>jest>jest-cli>@jest/core>jest-config>jest-runner>jest-environment-jsdom>jsdom>tough-cookie"]}],"found_by":null,"deleted":null,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2023-26136\n- https://github.com/salesforce/tough-cookie/issues/282\n- https://github.com/salesforce/tough-cookie/commit/12d474791bb856004e858fdb1c47b7608d09cf6e\n- https://github.com/salesforce/tough-cookie/releases/tag/v4.1.3\n- https://security.snyk.io/vuln/SNYK-JS-TOUGHCOOKIE-5672873\n- https://lists.debian.org/debian-lts-announce/2023/07/msg00010.html\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3HUE6ZR5SL73KHL7XUPAOEL6SB7HUDT2\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/6PVVPNSAGSDS63HQ74PJ7MZ3MU5IYNVZ\n- https://security.netapp.com/advisory/ntap-20240621-0006\n- https://github.com/advisories/GHSA-72xf-g2v4-qvf3","created":"2023-07-01T06:30:16.000Z","id":1097682,"npm_advisory_id":null,"overview":"Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in `rejectPublicSuffixes=false` mode. This issue arises from the manner in which the objects are initialized.","reported_by":null,"title":"tough-cookie Prototype Pollution vulnerability","metadata":null,"cves":["CVE-2023-26136"],"access":"public","severity":"moderate","module_name":"tough-cookie","vulnerable_versions":"<4.1.3","github_advisory_id":"GHSA-72xf-g2v4-qvf3","recommendation":"Upgrade to version 4.1.3 or later","patched_versions":">=4.1.3","updated":"2024-06-21T21:33:53.000Z","cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"},"cwe":["CWE-1321"],"url":"https://github.com/advisories/GHSA-72xf-g2v4-qvf3"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1099525,"path":"react-scripts>webpack-dev-server>express>serve-static>send","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"0.18.0","paths":["react-scripts>webpack-dev-server>express>serve-static>send"]}],"found_by":null,"deleted":null,"references":"- https://github.com/pillarjs/send/security/advisories/GHSA-m6fv-jmcg-4jfg\n- https://nvd.nist.gov/vuln/detail/CVE-2024-43799\n- https://github.com/pillarjs/send/commit/ae4f2989491b392ae2ef3b0015a019770ae65d35\n- https://github.com/advisories/GHSA-m6fv-jmcg-4jfg","created":"2024-09-10T19:42:41.000Z","id":1099525,"npm_advisory_id":null,"overview":"### Impact\n\npassing untrusted user input - even after sanitizing it - to `SendStream.redirect()` may execute untrusted code\n\n### Patches\n\nthis issue is patched in send 0.19.0\n\n### Workarounds\n\nusers are encouraged to upgrade to the patched version of express, but otherwise can workaround this issue by making sure any untrusted inputs are safe, ideally by validating them against an explicit allowlist\n\n### Details\n\nsuccessful exploitation of this vector requires the following:\n\n1. The attacker MUST control the input to response.redirect()\n1. express MUST NOT redirect before the template appears\n1. the browser MUST NOT complete redirection before:\n1. the user MUST click on the link in the template\n","reported_by":null,"title":"send vulnerable to template injection that can lead to XSS","metadata":null,"cves":["CVE-2024-43799"],"access":"public","severity":"moderate","module_name":"send","vulnerable_versions":"<0.19.0","github_advisory_id":"GHSA-m6fv-jmcg-4jfg","recommendation":"Upgrade to version 0.19.0 or later","patched_versions":">=0.19.0","updated":"2024-09-10T19:42:42.000Z","cvss":{"score":5,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L"},"cwe":["CWE-79"],"url":"https://github.com/advisories/GHSA-m6fv-jmcg-4jfg"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1099597,"path":"react-admin>ra-ui-materialui>dompurify","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"2.4.4","paths":["react-admin>ra-ui-materialui>dompurify"]}],"found_by":null,"deleted":null,"references":"- https://github.com/cure53/DOMPurify/security/advisories/GHSA-mmhx-hmjr-r674\n- https://github.com/cure53/DOMPurify/commit/1e520262bf4c66b5efda49e2316d6d1246ca7b21\n- https://github.com/cure53/DOMPurify/commit/26e1d69ca7f769f5c558619d644d90dd8bf26ebc\n- https://nvd.nist.gov/vuln/detail/CVE-2024-45801\n- https://github.com/advisories/GHSA-mmhx-hmjr-r674","created":"2024-09-16T20:34:26.000Z","id":1099597,"npm_advisory_id":null,"overview":"It has been discovered that malicious HTML using special nesting techniques can bypass the depth checking added to DOMPurify in recent releases. It was also possible to use Prototype Pollution to weaken the depth check.\n\nThis renders dompurify unable to avoid XSS attack.\n\nFixed by https://github.com/cure53/DOMPurify/commit/1e520262bf4c66b5efda49e2316d6d1246ca7b21 (3.x branch) and https://github.com/cure53/DOMPurify/commit/26e1d69ca7f769f5c558619d644d90dd8bf26ebc (2.x branch).","reported_by":null,"title":"DOMPurify allows tampering by prototype pollution","metadata":null,"cves":["CVE-2024-45801"],"access":"public","severity":"high","module_name":"dompurify","vulnerable_versions":"<2.5.4","github_advisory_id":"GHSA-mmhx-hmjr-r674","recommendation":"Upgrade to version 2.5.4 or later","patched_versions":">=2.5.4","updated":"2024-09-16T22:37:33.000Z","cvss":{"score":7,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L"},"cwe":["CWE-1321","CWE-1333"],"url":"https://github.com/advisories/GHSA-mmhx-hmjr-r674"}}} +{"type":"auditAdvisory","data":{"resolution":{"id":1099718,"path":"react-scripts>workbox-webpack-plugin>workbox-build>rollup","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"2.79.1","paths":["react-scripts>workbox-webpack-plugin>workbox-build>rollup"]}],"found_by":null,"deleted":null,"references":"- https://github.com/rollup/rollup/security/advisories/GHSA-gcx4-mw62-g8wm\n- https://nvd.nist.gov/vuln/detail/CVE-2024-47068\n- https://github.com/rollup/rollup/commit/2ef77c00ec2635d42697cff2c0567ccc8db34fb4\n- https://github.com/rollup/rollup/commit/e2552c9e955e0a61f70f508200ee9f752f85a541\n- https://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L157-L162\n- https://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L180-L185\n- https://github.com/advisories/GHSA-gcx4-mw62-g8wm","created":"2024-09-23T22:11:02.000Z","id":1099718,"npm_advisory_id":null,"overview":"### Summary\n\nA DOM Clobbering vulnerability was discovered in rollup when bundling scripts that use `import.meta.url` or with plugins that emit and reference asset files from code in `cjs`/`umd`/`iife` format. The DOM Clobbering gadget can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an `img` tag with an unsanitized `name` attribute) are present.\n\nIt's worth noting that similar issues in other popular bundlers like Webpack ([CVE-2024-43788](https://github.com/webpack/webpack/security/advisories/GHSA-4vvj-4cpr-p986)) have been reported, which might serve as a good reference.\n\n### Details\n\n#### Backgrounds\n\nDOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:\n\n[1] https://scnps.co/papers/sp23_domclob.pdf\n[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/\n\n#### Gadget found in `rollup`\n\nA DOM Clobbering vulnerability in `rollup` bundled scripts was identified, particularly when the scripts uses `import.meta` and set output in format of `cjs`/`umd`/`iife`. In such cases, `rollup` replaces meta property with the URL retrieved from `document.currentScript`.\n\nhttps://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L157-L162\n\nhttps://github.com/rollup/rollup/blob/b86ffd776cfa906573d36c3f019316d02445d9ef/src/ast/nodes/MetaProperty.ts#L180-L185\n\nHowever, this implementation is vulnerable to a DOM Clobbering attack. The `document.currentScript` lookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the `src` attribute of the attacker-controlled element (e.g., an `img` tag ) is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.\n\n### PoC\n\nConsidering a website that contains the following `main.js` script, the devloper decides to use the `rollup` to bundle up the program: `rollup main.js --format cjs --file bundle.js`.\n\n```\nvar s = document.createElement('script')\ns.src = import.meta.url + 'extra.js'\ndocument.head.append(s)\n```\n\nThe output `bundle.js` is shown in the following code snippet.\n\n```\n'use strict';\n\nvar _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;\nvar s = document.createElement('script');\ns.src = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && False && _documentCurrentScript.src || new URL('bundle.js', document.baseURI).href)) + 'extra.js';\ndocument.head.append(s);\n```\n\nAdding the `rollup` bundled script, `bundle.js`, as part of the web page source code, the page could load the `extra.js` file from the attacker's domain, `attacker.controlled.server` due to the introduced gadget during bundling. The attacker only needs to insert an `img` tag with the name attribute set to `currentScript`. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.\n\n```\n\n\n\n rollup Example\n \n \n \n\n\n\n\n\n```\n\n### Impact\n\nThis vulnerability can result in cross-site scripting (XSS) attacks on websites that include rollup-bundled files (configured with an output format of `cjs`, `iife`, or `umd` and use `import.meta`) and allow users to inject certain scriptless HTML tags without properly sanitizing the `name` or `id` attributes.\n\n### Patch\n\nPatching the following two functions with type checking would be effective mitigations against DOM Clobbering attack.\n\n```\nconst getRelativeUrlFromDocument = (relativePath: string, umd = false) =>\n\tgetResolveUrl(\n\t\t`'${escapeId(relativePath)}', ${\n\t\t\tumd ? `typeof document === 'undefined' ? location.href : ` : ''\n\t\t}document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`\n\t);\n```\n\n```\nconst getUrlFromDocument = (chunkId: string, umd = false) =>\n\t`${\n\t\tumd ? `typeof document === 'undefined' ? location.href : ` : ''\n\t}(${DOCUMENT_CURRENT_SCRIPT} && ${DOCUMENT_CURRENT_SCRIPT}.tagName.toUpperCase() === 'SCRIPT' &&${DOCUMENT_CURRENT_SCRIPT}.src || new URL('${escapeId(\n\t\tchunkId\n\t)}', document.baseURI).href)`;\n```\n","reported_by":null,"title":"DOM Clobbering Gadget found in rollup bundled scripts that leads to XSS","metadata":null,"cves":["CVE-2024-47068"],"access":"public","severity":"high","module_name":"rollup","vulnerable_versions":"<3.29.5","github_advisory_id":"GHSA-gcx4-mw62-g8wm","recommendation":"Upgrade to version 3.29.5 or later","patched_versions":">=3.29.5","updated":"2024-09-23T22:11:05.000Z","cvss":{"score":6.4,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:H"},"cwe":["CWE-79"],"url":"https://github.com/advisories/GHSA-gcx4-mw62-g8wm"}}}