From f41a2ac76184d41a995ee8032b84f6125ecf7feb Mon Sep 17 00:00:00 2001 From: shuai Date: Wed, 29 Mar 2023 15:40:27 +0800 Subject: [PATCH] fix: use htmlRender control rich-text style and delet dompurify html-react-parser --- ui/package.json | 2 - ui/pnpm-lock.yaml | 109 ------------------ ui/src/components/Editor/Viewer.tsx | 7 +- ui/src/components/Editor/utils/index.ts | 30 +++-- ui/src/pages/Legal/Privacy/index.tsx | 22 +++- ui/src/pages/Legal/Tos/index.tsx | 23 +++- .../Detail/components/Answer/index.tsx | 9 +- .../Detail/components/Question/index.tsx | 10 +- ui/src/pages/Questions/EditAnswer/index.tsx | 17 ++- ui/src/pages/Tags/Info/index.tsx | 19 ++- ui/src/utils/common.ts | 29 ----- 11 files changed, 98 insertions(+), 179 deletions(-) diff --git a/ui/package.json b/ui/package.json index 8e72060b..772d2f14 100644 --- a/ui/package.json +++ b/ui/package.json @@ -22,9 +22,7 @@ "copy-to-clipboard": "^3.3.2", "dayjs": "^1.11.5", "diff": "^5.1.0", - "dompurify": "^2.4.3", "emoji-regex": "^10.2.1", - "html-react-parser": "^3.0.8", "i18next": "^21.9.0", "katex": "^0.16.2", "lodash": "^4.17.21", diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index 569d1f00..d2f74e88 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -29,7 +29,6 @@ specifiers: customize-cra: ^1.0.0 dayjs: ^1.11.5 diff: ^5.1.0 - dompurify: ^2.4.3 emoji-regex: ^10.2.1 eslint: ^8.0.1 eslint-config-airbnb: ^19.0.4 @@ -43,7 +42,6 @@ specifiers: eslint-plugin-promise: ^6.0.0 eslint-plugin-react: ^7.30.1 eslint-plugin-react-hooks: ^4.6.0 - html-react-parser: ^3.0.8 husky: ^8.0.1 i18next: ^21.9.0 katex: ^0.16.2 @@ -83,9 +81,7 @@ dependencies: copy-to-clipboard: 3.3.2 dayjs: 1.11.5 diff: 5.1.0 - dompurify: registry.npmjs.org/dompurify/2.4.3 emoji-regex: 10.2.1 - html-react-parser: registry.npmjs.org/html-react-parser/3.0.8_react@18.2.0 i18next: 21.9.2 katex: 0.16.2 lodash: 4.17.21 @@ -11303,16 +11299,6 @@ packages: domhandler: registry.npmjs.org/domhandler/4.3.1 entities: registry.npmjs.org/entities/2.2.0 - registry.npmjs.org/dom-serializer/2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz} - name: dom-serializer - version: 2.0.0 - dependencies: - domelementtype: registry.npmjs.org/domelementtype/2.3.0 - domhandler: registry.npmjs.org/domhandler/5.0.3 - entities: registry.npmjs.org/entities/4.4.0 - dev: false - registry.npmjs.org/domelementtype/1.3.1: resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz} name: domelementtype @@ -11331,27 +11317,12 @@ packages: dependencies: domelementtype: registry.npmjs.org/domelementtype/2.3.0 - registry.npmjs.org/domhandler/5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz} - name: domhandler - version: 5.0.3 - engines: {node: '>= 4'} - dependencies: - domelementtype: registry.npmjs.org/domelementtype/2.3.0 - dev: false - registry.npmjs.org/dompurify/2.4.0: resolution: {integrity: sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz} name: dompurify version: 2.4.0 dev: false - registry.npmjs.org/dompurify/2.4.3: - resolution: {integrity: sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz} - name: dompurify - version: 2.4.3 - dev: false - registry.npmjs.org/domutils/1.7.0: resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz} name: domutils @@ -11369,52 +11340,11 @@ packages: domelementtype: registry.npmjs.org/domelementtype/2.3.0 domhandler: registry.npmjs.org/domhandler/4.3.1 - registry.npmjs.org/domutils/3.0.1: - resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz} - name: domutils - version: 3.0.1 - dependencies: - dom-serializer: registry.npmjs.org/dom-serializer/2.0.0 - domelementtype: registry.npmjs.org/domelementtype/2.3.0 - domhandler: registry.npmjs.org/domhandler/5.0.3 - dev: false - registry.npmjs.org/entities/2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/entities/-/entities-2.2.0.tgz} name: entities version: 2.2.0 - registry.npmjs.org/entities/4.4.0: - resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/entities/-/entities-4.4.0.tgz} - name: entities - version: 4.4.0 - engines: {node: '>=0.12'} - dev: false - - registry.npmjs.org/html-dom-parser/3.1.3: - resolution: {integrity: sha512-fI0yyNlIeSboxU+jnrA4v8qj4+M8SI9/q6AKYdwCY2qki22UtKCDTxvagHniECu7sa5/o2zFRdLleA67035lsA==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-3.1.3.tgz} - name: html-dom-parser - version: 3.1.3 - dependencies: - domhandler: registry.npmjs.org/domhandler/5.0.3 - htmlparser2: registry.npmjs.org/htmlparser2/8.0.1 - dev: false - - registry.npmjs.org/html-react-parser/3.0.8_react@18.2.0: - resolution: {integrity: sha512-eIxPq/3Ja3+nZkx6X/BNpFy0lNuW+v3V4nzABzuL8KkRVdYY/2KyXO42epKonsEVVRSBxm2zsxWcOkT6fYL+iQ==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/html-react-parser/-/html-react-parser-3.0.8.tgz} - id: registry.npmjs.org/html-react-parser/3.0.8 - name: html-react-parser - version: 3.0.8 - peerDependencies: - react: 0.14 || 15 || 16 || 17 || 18 - dependencies: - domhandler: registry.npmjs.org/domhandler/5.0.3 - html-dom-parser: registry.npmjs.org/html-dom-parser/3.1.3 - react: 18.2.0 - react-property: registry.npmjs.org/react-property/2.0.0 - style-to-js: registry.npmjs.org/style-to-js/1.1.3 - dev: false - registry.npmjs.org/htmlparser2/6.1.0: resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz} name: htmlparser2 @@ -11424,42 +11354,3 @@ packages: domhandler: registry.npmjs.org/domhandler/4.3.1 domutils: registry.npmjs.org/domutils/2.8.0 entities: registry.npmjs.org/entities/2.2.0 - - registry.npmjs.org/htmlparser2/8.0.1: - resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz} - name: htmlparser2 - version: 8.0.1 - dependencies: - domelementtype: registry.npmjs.org/domelementtype/2.3.0 - domhandler: registry.npmjs.org/domhandler/5.0.3 - domutils: registry.npmjs.org/domutils/3.0.1 - entities: registry.npmjs.org/entities/4.4.0 - dev: false - - registry.npmjs.org/inline-style-parser/0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz} - name: inline-style-parser - version: 0.1.1 - dev: false - - registry.npmjs.org/react-property/2.0.0: - resolution: {integrity: sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz} - name: react-property - version: 2.0.0 - dev: false - - registry.npmjs.org/style-to-js/1.1.3: - resolution: {integrity: sha512-zKI5gN/zb7LS/Vm0eUwjmjrXWw8IMtyA8aPBJZdYiQTXj4+wQ3IucOLIOnF7zCHxvW8UhIGh/uZh/t9zEHXNTQ==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.3.tgz} - name: style-to-js - version: 1.1.3 - dependencies: - style-to-object: registry.npmjs.org/style-to-object/0.4.1 - dev: false - - registry.npmjs.org/style-to-object/0.4.1: - resolution: {integrity: sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==, registry: https://registry.yarnpkg.com/, tarball: https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz} - name: style-to-object - version: 0.4.1 - dependencies: - inline-style-parser: registry.npmjs.org/inline-style-parser/0.1.1 - dev: false diff --git a/ui/src/components/Editor/Viewer.tsx b/ui/src/components/Editor/Viewer.tsx index 217c0986..db036f90 100644 --- a/ui/src/components/Editor/Viewer.tsx +++ b/ui/src/components/Editor/Viewer.tsx @@ -8,7 +8,6 @@ import { } from 'react'; import { markdownToHtml } from '@/services'; -import { htmlToReact } from '@/utils'; import { htmlRender } from './utils'; @@ -51,9 +50,9 @@ const Index = ({ value }, ref) => { return (
- {htmlToReact(html)} -
+ className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt" + dangerouslySetInnerHTML={{ __html: html }} + /> ); }; diff --git a/ui/src/components/Editor/utils/index.ts b/ui/src/components/Editor/utils/index.ts index a1ed6ff8..a617989b 100644 --- a/ui/src/components/Editor/utils/index.ts +++ b/ui/src/components/Editor/utils/index.ts @@ -82,9 +82,11 @@ export function htmlRender(el: HTMLElement | null) { if (!el) return; // Replace all br tags with newlines // Fixed an issue where the BR tag in the editor block formula HTML caused rendering errors. - el.querySelectorAll('br').forEach((br) => { - br.parentNode?.insertBefore(document.createTextNode('\n'), br); - br.parentNode?.removeChild(br); + el.querySelectorAll('p').forEach((p) => { + if (p.innerHTML.startsWith('$$') && p.innerHTML.endsWith('$$')) { + const str = p.innerHTML.replace(/
/g, '\n'); + p.innerHTML = str; + } }); import('mermaid').then(({ default: mermaid }) => { @@ -106,6 +108,7 @@ export function htmlRender(el: HTMLElement | null) { render(el, { delimiters: [ { left: '$$', right: '$$', display: true }, + { left: '$$
', right: '
$$', display: true }, { left: '\\begin{equation}', right: '\\end{equation}', @@ -121,8 +124,21 @@ export function htmlRender(el: HTMLElement | null) { }, ); - // remove change table style to htmlToReact function - /** - * @description: You modify the DOM with other scripts after React has rendered the DOM. This way, on the next render cycle (re-render), React cannot find the DOM node it rendered before, because it has been modified or removed by other scripts. - */ + // change table style + + el.querySelectorAll('table').forEach((table) => { + if ( + (table.parentNode as HTMLDivElement)?.classList.contains( + 'table-responsive', + ) + ) { + return; + } + + table.classList.add('table', 'table-bordered'); + const div = document.createElement('div'); + div.className = 'table-responsive'; + table.parentNode?.replaceChild(div, table); + div.appendChild(table); + }); } diff --git a/ui/src/pages/Legal/Privacy/index.tsx b/ui/src/pages/Legal/Privacy/index.tsx index eb36de32..3cd251a6 100644 --- a/ui/src/pages/Legal/Privacy/index.tsx +++ b/ui/src/pages/Legal/Privacy/index.tsx @@ -1,9 +1,9 @@ -import { FC } from 'react'; +import { FC, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { usePageTags } from '@/hooks'; import { useLegalPrivacy } from '@/services'; -import { htmlToReact } from '@/utils'; +import { htmlRender } from '@/components'; const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'nav_menus' }); @@ -13,6 +13,15 @@ const Index: FC = () => { const { data: privacy } = useLegalPrivacy(); const contentText = privacy?.privacy_policy_original_text; let matchUrl: URL | undefined; + + useEffect(() => { + const fmt = document.querySelector('.fmt') as HTMLElement; + if (!fmt) { + return; + } + htmlRender(fmt); + }, [privacy?.privacy_policy_parsed_text]); + try { if (contentText) { matchUrl = new URL(contentText); @@ -26,9 +35,12 @@ const Index: FC = () => { return ( <>

{t('privacy')}

-
- {htmlToReact(privacy?.privacy_policy_parsed_text || '')} -
+
); }; diff --git a/ui/src/pages/Legal/Tos/index.tsx b/ui/src/pages/Legal/Tos/index.tsx index 1b02d74d..32aeb11d 100644 --- a/ui/src/pages/Legal/Tos/index.tsx +++ b/ui/src/pages/Legal/Tos/index.tsx @@ -1,9 +1,9 @@ -import { FC } from 'react'; +import { FC, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { usePageTags } from '@/hooks'; import { useLegalTos } from '@/services'; -import { htmlToReact } from '@/utils'; +import { htmlRender } from '@/components'; const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'nav_menus' }); @@ -13,6 +13,15 @@ const Index: FC = () => { const { data: tos } = useLegalTos(); const contentText = tos?.terms_of_service_original_text; let matchUrl: URL | undefined; + + useEffect(() => { + const fmt = document.querySelector('.fmt') as HTMLElement; + if (!fmt) { + return; + } + htmlRender(fmt); + }, [tos?.terms_of_service_parsed_text]); + try { if (contentText) { matchUrl = new URL(contentText); @@ -23,12 +32,16 @@ const Index: FC = () => { window.location.replace(matchUrl.toString()); return null; } + return (

{t('tos')}

-
- {htmlToReact(tos?.terms_of_service_parsed_text || '')} -
+
); }; diff --git a/ui/src/pages/Questions/Detail/components/Answer/index.tsx b/ui/src/pages/Questions/Detail/components/Answer/index.tsx index ba1a7d50..89be1784 100644 --- a/ui/src/pages/Questions/Detail/components/Answer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Answer/index.tsx @@ -12,7 +12,7 @@ import { FormatTime, htmlRender, } from '@/components'; -import { scrollToElementTop, bgFadeOut, htmlToReact } from '@/utils'; +import { scrollToElementTop, bgFadeOut } from '@/utils'; import { AnswerItem } from '@/common/interface'; import { acceptanceAnswer } from '@/services'; @@ -84,9 +84,10 @@ const Index: FC = ({
)} -
- {htmlToReact(data?.html)} -
+
= ({ data, initPage, hasAnswer, isLogged }) => { return ; })}
-
- {htmlToReact(data?.html)} -
+
{ const questionContentRef = useRef(null); + useEffect(() => { + if (!questionContentRef?.current) { + return; + } + htmlRender(questionContentRef.current); + }, [questionContentRef]); + usePromptWithUnload({ when: contentChanged, }); @@ -195,9 +202,9 @@ const Index = () => {
- {htmlToReact(data?.question.html)} -
+ className="content position-absolute top-0 w-100" + dangerouslySetInnerHTML={{ __html: data?.question.html }} + />
{ const userInfo = loggedUserInfoStore((state) => state.user); @@ -45,6 +44,15 @@ const TagIntroduction = () => { }); } }, [locationState]); + + useEffect(() => { + const fmt = document.querySelector('.content.fmt') as HTMLElement; + if (!fmt) { + return; + } + htmlRender(fmt); + }, [tagInfo?.parsed_text]); + if (!tagInfo) { return null; } @@ -145,9 +153,10 @@ const TagIntroduction = () => { />
-
- {htmlToReact(tagInfo?.parsed_text)} -
+
{tagInfo?.member_actions.map((action, index) => { return ( diff --git a/ui/src/utils/common.ts b/ui/src/utils/common.ts index bd2f61a3..ce950ab7 100644 --- a/ui/src/utils/common.ts +++ b/ui/src/utils/common.ts @@ -1,6 +1,4 @@ import i18next from 'i18next'; -import parse from 'html-react-parser'; -import * as DOMPurify from 'dompurify'; const Diff = require('diff'); @@ -235,32 +233,6 @@ function diffText(newText: string, oldText?: string): string { return result.join(''); } -function htmlToReact(html: string) { - const cleanedHtml = DOMPurify.sanitize(html, { - USE_PROFILES: { html: true }, - }); - - const ele = document.createElement('div'); - ele.innerHTML = cleanedHtml; - - ele.querySelectorAll('table').forEach((table) => { - if ( - (!table || (table.parentNode as HTMLDivElement))?.classList.contains( - 'table-responsive', - ) - ) { - return; - } - - table.classList.add('table', 'table-bordered'); - const div = document.createElement('div'); - div.className = 'table-responsive'; - table.parentNode?.replaceChild(div, table); - div.appendChild(table); - }); - return parse(ele.innerHTML); -} - export { thousandthDivision, formatCount, @@ -276,5 +248,4 @@ export { labelStyle, handleFormError, diffText, - htmlToReact, };