diff --git a/net/web/package.json b/net/web/package.json index 80105d88..b5234af5 100644 --- a/net/web/package.json +++ b/net/web/package.json @@ -22,6 +22,7 @@ "axios": "^0.27.2", "base-64": "^1.0.0", "crypto-js": "^4.1.1", + "dompurify": "^3.0.1", "jsencrypt": "^2.3.1", "react": "^18.2.0", "react-color": "^2.19.3", diff --git a/net/web/src/session/conversation/topicItem/TopicItem.jsx b/net/web/src/session/conversation/topicItem/TopicItem.jsx index df85f839..4ab16f06 100644 --- a/net/web/src/session/conversation/topicItem/TopicItem.jsx +++ b/net/web/src/session/conversation/topicItem/TopicItem.jsx @@ -7,6 +7,7 @@ import { Space, Skeleton, Button, Modal, Input } from 'antd'; import { ExclamationCircleOutlined, DeleteOutlined, EditOutlined, FireOutlined, PictureOutlined } from '@ant-design/icons'; import { Carousel } from 'carousel/Carousel'; import { useTopicItem } from './useTopicItem.hook'; +import * as DOMPurify from 'dompurify'; export function TopicItem({ host, sealed, topic, update, remove }) { @@ -123,7 +124,7 @@ export function TopicItem({ host, sealed, topic, update, remove }) { )} { !sealed && !state.editing && (
-
{ topic.text }
+
)} { state.editing && ( diff --git a/net/web/src/session/conversation/useConversation.hook.js b/net/web/src/session/conversation/useConversation.hook.js index 02c16da5..58f84f89 100644 --- a/net/web/src/session/conversation/useConversation.hook.js +++ b/net/web/src/session/conversation/useConversation.hook.js @@ -132,6 +132,27 @@ export function useConversation(cardId, channelId) { // eslint-disable-next-line }, [state.contentKey]); + const clickableText = (text) => { + var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string + '(\\#[-a-z\\d_]*)?$','i'); // fragment locator + + let clickable = ''; + const words = text == null ? '' : text.split(' '); + words.forEach(word => { + if (!!pattern.test(word)) { + clickable += `${word} `; + } + else { + clickable += `${word} `; + } + }) + return `

${clickable}

`; + }; + const syncTopic = (item, value) => { const revision = value.data?.detailRevision; const detail = value.data?.topicDetail || {}; @@ -189,14 +210,14 @@ export function useConversation(cardId, channelId) { if (detail.dataType === 'superbasictopic') { const message = JSON.parse(detail.data); item.assets = message.assets; - item.text = message.text; + item.text = clickableText(message.text); item.textColor = message.textColor ? message.textColor : '#444444'; item.textSize = message.textSize ? message.textSize : 14; } if (detail.dataType === 'sealedtopic' && state.contentKey) { const subject = decryptTopicSubject(detail.data, state.contentKey); item.assets = subject.message.assets; - item.text = subject.message.text; + item.text = clickableText(subject.message.text); item.textColor = subject.message.textColor ? subject.message.textColor : '#444444'; item.textSize = subject.message.textSize ? subject.message.textSize : 14; } diff --git a/net/web/yarn.lock b/net/web/yarn.lock index 70453493..09c19758 100644 --- a/net/web/yarn.lock +++ b/net/web/yarn.lock @@ -4366,6 +4366,11 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +dompurify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.1.tgz#a0933f38931b3238934dd632043b727e53004289" + integrity sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw== + domutils@^1.7.0: version "1.7.0" resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz"