diff --git a/package-lock.json b/package-lock.json index 5c2db9d38e..2b013b359c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fontsource/roboto": "^4.5.8", "@khanacademy/simple-markdown": "^0.8.3", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", + "@tauri-apps/api": "^1.0.2", "@tippyjs/react": "^4.2.6", "babel-polyfill": "^6.26.0", "blurhash": "^1.1.5", @@ -2496,6 +2497,20 @@ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-3.0.1.tgz", "integrity": "sha512-XjDVbs3ZU16CO1h5Q3Ew2RPJqmZBDE/EVf1LYp6ePEffs3V/MX9ZbL5bJr8qiK5SbGmUMuDoaFgyKacYz8prRA==" }, + "node_modules/@tauri-apps/api": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.0.2.tgz", + "integrity": "sha512-yuNW0oeJ1/ZA7wNF1KgxhHrSu5viPVzY/UgUczzN5ptLM8dH15Juy5rEGkoHfeXGju90Y/l22hi3BtIrp/za+w==", + "engines": { + "node": ">= 12.22.0", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, "node_modules/@tippyjs/react": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz", @@ -3025,8 +3040,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3042,9 +3055,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -15347,6 +15358,11 @@ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-3.0.1.tgz", "integrity": "sha512-XjDVbs3ZU16CO1h5Q3Ew2RPJqmZBDE/EVf1LYp6ePEffs3V/MX9ZbL5bJr8qiK5SbGmUMuDoaFgyKacYz8prRA==" }, + "@tauri-apps/api": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.0.2.tgz", + "integrity": "sha512-yuNW0oeJ1/ZA7wNF1KgxhHrSu5viPVzY/UgUczzN5ptLM8dH15Juy5rEGkoHfeXGju90Y/l22hi3BtIrp/za+w==" + }, "@tippyjs/react": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz", @@ -15823,14 +15839,15 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "requires": {}, + "requires": { + "ajv": "^8.0.0" + }, "dependencies": { "ajv": { - "version": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, - "optional": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -15842,9 +15859,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "optional": true, - "peer": true + "dev": true } } }, diff --git a/package.json b/package.json index 63e46a64b6..8d3269a44d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@fontsource/roboto": "^4.5.8", "@khanacademy/simple-markdown": "^0.8.3", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", + "@tauri-apps/api": "^1.0.2", "@tippyjs/react": "^4.2.6", "babel-polyfill": "^6.26.0", "blurhash": "^1.1.5", diff --git a/src/util/linkHandlers.js b/src/util/linkHandlers.js new file mode 100644 index 0000000000..256875d0f1 --- /dev/null +++ b/src/util/linkHandlers.js @@ -0,0 +1,27 @@ +import { invoke } from '@tauri-apps/api/tauri'; +import handleMatrix from './linkHandlers/matrix.to'; + +const handlers = { + 'matrix.to': (url) => handleMatrix(url), +}; + +export default function handleLink(e) { + const url = new URL(e.target.href); + const handler = handlers[url.hostname]; + + if (handler) { + handler(url); + e.preventDefault(); + return; + } + + // if running inside tauri, let the backend handle the opening of the url + // useful so we can open urls in the user's browser specified app or a specified app + if (window.__TAURI__) { + e.preventDefault(); + invoke('open_link', { url: e.target.href, bypassHandlers: false }); + } else { + window.open(e.target.href); + e.preventDefault(); + } +} diff --git a/src/util/linkHandlers/matrix.to.js b/src/util/linkHandlers/matrix.to.js new file mode 100644 index 0000000000..a6f9009c21 --- /dev/null +++ b/src/util/linkHandlers/matrix.to.js @@ -0,0 +1,30 @@ +import initMatrix from '../../client/initMatrix'; +import { selectRoom, selectTab, openJoinAlias } from '../../client/action/navigation'; + +function handleMatrix(url) { + const mx = initMatrix.matrixClient; + const rooms = mx.getRooms(); + + const roomName = url.hash.slice(2); + + let joinedRoom; + + rooms.forEach((room) => { + if (room.roomId === roomName + || room.getCanonicalAlias() === roomName + || roomName in room.getAltAliases()) { + joinedRoom = room; + } + }); + + if (joinedRoom) { + if (joinedRoom.isSpaceRoom()) selectTab(joinedRoom.roomId); + else selectRoom(joinedRoom.roomId); + + return; + } + + openJoinAlias(roomName); +} + +export default handleMatrix; diff --git a/src/util/twemojify.jsx b/src/util/twemojify.jsx index 0a4fede732..1e4d1777e6 100644 --- a/src/util/twemojify.jsx +++ b/src/util/twemojify.jsx @@ -5,27 +5,48 @@ import linkifyHtml from 'linkify-html'; import parse from 'html-react-parser'; import twemoji from 'twemoji'; import { sanitizeText } from './sanitize'; +import handleLink from './linkHandlers'; const Math = lazy(() => import('../app/atoms/math/Math')); -const mathOptions = { - replace: (node) => { - const maths = node.attribs?.['data-mx-maths']; - if (maths) { - return ( - {maths}}> - - - ); - } - return null; - }, -}; +function replaceMath(node) { + const maths = node.attribs?.['data-mx-maths']; + + return ( + {maths}}> + + + ); +} + +function replaceLink(node) { + if (node.children?.[0]?.type !== 'text') { return null; } + + return ( + handleLink(e)}> + { node.children[0].data } + + ); +} + +function parseOptions(maths = false) { + return { + replace: (node) => { + if (maths) { + if (node.attribs?.['data-mx-maths']) { return replaceMath(node); } + } + + if (node.name === 'a') { return replaceLink(node); } + + return null; + }, + }; +} /** * @param {string} text - text to twemojify @@ -49,5 +70,8 @@ export function twemojify(text, opts, linkify = false, sanitize = true, maths = rel: 'noreferrer noopener', }); } - return parse(content, maths ? mathOptions : null); + + const elements = parse(content, parseOptions(maths)); + + return elements; }