From e5e3f5f0a3542ce9b0c0205d176b4e05adc602e1 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sat, 14 Jan 2023 18:51:42 +0530 Subject: [PATCH 1/4] Add jsdelivr cdn for twemoji --- src/app/organisms/emoji-board/EmojiBoard.jsx | 209 ++++++++++--------- src/app/organisms/room/RoomViewCmdBar.jsx | 39 ++-- src/util/twemojify.jsx | 9 +- 3 files changed, 137 insertions(+), 120 deletions(-) diff --git a/src/app/organisms/emoji-board/EmojiBoard.jsx b/src/app/organisms/emoji-board/EmojiBoard.jsx index d9762323..84c41306 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.jsx +++ b/src/app/organisms/emoji-board/EmojiBoard.jsx @@ -13,6 +13,7 @@ import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; import AsyncSearch from '../../../util/AsyncSearch'; import { addRecentEmoji, getRecentEmojis } from './recent'; +import { TWEMOJI_BASE_URL } from '../../../util/twemojify'; import Text from '../../atoms/text/Text'; import RawIcon from '../../atoms/system-icons/RawIcon'; @@ -46,45 +47,49 @@ const EmojiGroup = React.memo(({ name, groupEmojis }) => { const emoji = groupEmojis[emojiIndex]; emojiRow.push( - { - emoji.hexcode - // This is a unicode emoji, and should be rendered with twemoji - ? parse(twemoji.parse( - emoji.unicode, - { - attributes: () => ({ - unicode: emoji.unicode, - shortcodes: emoji.shortcodes?.toString(), - hexcode: emoji.hexcode, - loading: 'lazy', - }), - }, - )) - // This is a custom emoji, and should be render as an mxc - : ( - {emoji.shortcode} - ) - } - , + {emoji.hexcode ? ( + // This is a unicode emoji, and should be rendered with twemoji + parse( + twemoji.parse(emoji.unicode, { + attributes: () => ({ + unicode: emoji.unicode, + shortcodes: emoji.shortcodes?.toString(), + hexcode: emoji.hexcode, + loading: 'lazy', + }), + base: TWEMOJI_BASE_URL, + }) + ) + ) : ( + // This is a custom emoji, and should be render as an mxc + {emoji.shortcode} + )} + ); } - emojiBoard.push(
{emojiRow}
); + emojiBoard.push( +
+ {emojiRow} +
+ ); } return emojiBoard; } return (
- {name} + + {name} + {groupEmojis.length !== 0 &&
{getEmojiBoard()}
}
); @@ -92,17 +97,16 @@ const EmojiGroup = React.memo(({ name, groupEmojis }) => { EmojiGroup.propTypes = { name: PropTypes.string.isRequired, - groupEmojis: PropTypes.arrayOf(PropTypes.shape({ - length: PropTypes.number, - unicode: PropTypes.string, - hexcode: PropTypes.string, - mxc: PropTypes.string, - shortcode: PropTypes.string, - shortcodes: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - })).isRequired, + groupEmojis: PropTypes.arrayOf( + PropTypes.shape({ + length: PropTypes.number, + unicode: PropTypes.string, + hexcode: PropTypes.string, + mxc: PropTypes.string, + shortcode: PropTypes.string, + shortcodes: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]), + }) + ).isRequired, }; const asyncSearch = new AsyncSearch(); @@ -128,7 +132,13 @@ function SearchedEmoji() { if (searchedEmojis === null) return false; - return ; + return ( + + ); } function EmojiBoard({ onSelect, searchRef }) { @@ -146,7 +156,10 @@ function EmojiBoard({ onSelect, searchRef }) { if (typeof shortcodes === 'undefined') shortcodes = undefined; else shortcodes = shortcodes.split(','); return { - unicode, hexcode, shortcodes, mxc, + unicode, + hexcode, + shortcodes, + mxc, }; } @@ -211,10 +224,9 @@ function EmojiBoard({ onSelect, searchRef }) { const parentIds = initMatrix.roomList.getAllParentSpaces(room.roomId); const parentRooms = [...parentIds].map((id) => mx.getRoom(id)); if (room) { - const packs = getRelevantPacks( - room.client, - [room, ...parentRooms], - ).filter((pack) => pack.getEmojis().length !== 0); + const packs = getRelevantPacks(room.client, [room, ...parentRooms]).filter( + (pack) => pack.getEmojis().length !== 0 + ); // Set an index for each pack so that we know where to jump when the user uses the nav for (let i = 0; i < packs.length; i += 1) { @@ -263,44 +275,41 @@ function EmojiBoard({ onSelect, searchRef }) { /> )}
- { - availableEmojis.map((pack) => { - const src = initMatrix.matrixClient - .mxcUrlToHttp(pack.avatarUrl ?? pack.getEmojis()[0].mxc); - return ( - openGroup(recentOffset + pack.packIndex)} - src={src} - key={pack.packIndex} - tooltip={pack.displayName ?? 'Unknown'} - tooltipPlacement="left" - isImage - /> - ); - }) - } + {availableEmojis.map((pack) => { + const src = initMatrix.matrixClient.mxcUrlToHttp( + pack.avatarUrl ?? pack.getEmojis()[0].mxc + ); + return ( + openGroup(recentOffset + pack.packIndex)} + src={src} + key={pack.packIndex} + tooltip={pack.displayName ?? 'Unknown'} + tooltipPlacement="left" + isImage + /> + ); + })}
- { - [ - [0, EmojiIC, 'Smilies'], - [1, DogIC, 'Animals'], - [2, CupIC, 'Food'], - [3, BallIC, 'Activities'], - [4, PhotoIC, 'Travel'], - [5, BulbIC, 'Objects'], - [6, PeaceIC, 'Symbols'], - [7, FlagIC, 'Flags'], - ].map(([indx, ico, name]) => ( - openGroup(recentOffset + availableEmojis.length + indx)} - key={indx} - src={ico} - tooltip={name} - tooltipPlacement="left" - /> - )) - } + {[ + [0, EmojiIC, 'Smilies'], + [1, DogIC, 'Animals'], + [2, CupIC, 'Food'], + [3, BallIC, 'Activities'], + [4, PhotoIC, 'Travel'], + [5, BulbIC, 'Objects'], + [6, PeaceIC, 'Symbols'], + [7, FlagIC, 'Flags'], + ].map(([indx, ico, name]) => ( + openGroup(recentOffset + availableEmojis.length + indx)} + key={indx} + src={ico} + tooltip={name} + tooltipPlacement="left" + /> + ))}
@@ -313,27 +322,25 @@ function EmojiBoard({ onSelect, searchRef }) {
- {recentEmojis.length > 0 && } - { - availableEmojis.map((pack) => ( - - )) - } - { - emojiGroups.map((group) => ( - - )) - } + {recentEmojis.length > 0 && ( + + )} + {availableEmojis.map((pack) => ( + + ))} + {emojiGroups.map((group) => ( + + ))}
-
{ parse(twemoji.parse('🙂')) }
+
{parse(twemoji.parse('🙂', { base: TWEMOJI_BASE_URL }))}
:slight_smile:
diff --git a/src/app/organisms/room/RoomViewCmdBar.jsx b/src/app/organisms/room/RoomViewCmdBar.jsx index 8c390a06..0d21123b 100644 --- a/src/app/organisms/room/RoomViewCmdBar.jsx +++ b/src/app/organisms/room/RoomViewCmdBar.jsx @@ -5,7 +5,7 @@ import './RoomViewCmdBar.scss'; import parse from 'html-react-parser'; import twemoji from 'twemoji'; -import { twemojify } from '../../../util/twemojify'; +import { twemojify, TWEMOJI_BASE_URL } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import { getEmojiForCompletion } from '../emoji-board/custom-emoji'; @@ -31,7 +31,7 @@ CmdItem.propTypes = { function renderSuggestions({ prefix, option, suggestions }, fireCmd) { function renderCmdSuggestions(cmdPrefix, cmds) { - const cmdOptString = (typeof option === 'string') ? `/${option}` : '/?'; + const cmdOptString = typeof option === 'string' ? `/${option}` : '/?'; return cmds.map((cmd) => ( ({ unicode: emoji.unicode, shortcodes: emoji.shortcodes?.toString(), }), - }, - )); + base: TWEMOJI_BASE_URL, + }) + ); } // Render a custom emoji @@ -87,10 +87,12 @@ function renderSuggestions({ prefix, option, suggestions }, fireCmd) { return emos.map((emoji) => ( fireCmd({ - prefix: emPrefix, - result: emoji, - })} + onClick={() => + fireCmd({ + prefix: emPrefix, + result: emoji, + }) + } > {renderEmoji(emoji)} {`:${emoji.shortcode}:`} @@ -187,10 +189,13 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) { }); }, '@': () => { - const members = mx.getRoom(roomId).getJoinedMembers().map((member) => ({ - name: member.name, - userId: member.userId.slice(1), - })); + const members = mx + .getRoom(roomId) + .getJoinedMembers() + .map((member) => ({ + name: member.name, + userId: member.userId.slice(1), + })); asyncSearch.setup(members, { keys: ['name', 'userId'], limit: 20 }); const endIndex = members.length > 20 ? 20 : members.length; setCmd({ prefix, suggestions: members.slice(0, endIndex) }); @@ -277,9 +282,7 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
-
- { renderSuggestions(cmd, fireCmd) } -
+
{renderSuggestions(cmd, fireCmd)}
diff --git a/src/util/twemojify.jsx b/src/util/twemojify.jsx index 0a4fede7..abe82a66 100644 --- a/src/util/twemojify.jsx +++ b/src/util/twemojify.jsx @@ -6,6 +6,8 @@ import parse from 'html-react-parser'; import twemoji from 'twemoji'; import { sanitizeText } from './sanitize'; +export const TWEMOJI_BASE_URL = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/'; + const Math = lazy(() => import('../app/atoms/math/Math')); const mathOptions = { @@ -38,11 +40,16 @@ const mathOptions = { export function twemojify(text, opts, linkify = false, sanitize = true, maths = false) { if (typeof text !== 'string') return text; let content = text; + const options = opts ?? { base: TWEMOJI_BASE_URL }; + if (!options.base) { + options.base = TWEMOJI_BASE_URL; + } if (sanitize) { content = sanitizeText(content); } - content = twemoji.parse(content, opts); + + content = twemoji.parse(content, options); if (linkify) { content = linkifyHtml(content, { target: '_blank', From 9a34daa2bc2d3d0a2a1ebadb419b916c7ba1bedd Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Sun, 15 Jan 2023 05:14:16 +0100 Subject: [PATCH 2/4] Set `accept` attribute to `image/*` in ImageUpload (#989) That way, browsers will suggest to the users to upload an image file instead of any kind of file. The behaviour is in-line with Element's, which specifies the same attribute when selecting an avatar. Please note that it does not prevent users from uploading non-image files as avatars, as browsers interpret that attribute as a mere suggestion, which can be bypassed in the file select dialog. Partially fixes #982. --- src/app/molecules/image-upload/ImageUpload.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/molecules/image-upload/ImageUpload.jsx b/src/app/molecules/image-upload/ImageUpload.jsx index 34d0d4be..53fc7e16 100644 --- a/src/app/molecules/image-upload/ImageUpload.jsx +++ b/src/app/molecules/image-upload/ImageUpload.jsx @@ -74,7 +74,7 @@ function ImageUpload({ {uploadPromise ? 'Cancel' : 'Remove'} )} - + ); } From 38bbc1c6f5b4eca8a6052cbce0d72df7434fe2b4 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sun, 15 Jan 2023 09:52:58 +0530 Subject: [PATCH 3/4] Vite plugin to add svg as inline data (#1072) * add vite plugin to add svg as inline data * Add node types package --- package-lock.json | 17 +++++++++++++++++ package.json | 2 ++ src/app/atoms/system-icons/RawIcon.jsx | 12 +++++------- vite.config.js | 2 ++ viteSvgLoader.ts | 16 ++++++++++++++++ 5 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 viteSvgLoader.ts diff --git a/package-lock.json b/package-lock.json index b60abb5c..673ba846 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ }, "devDependencies": { "@rollup/plugin-wasm": "6.1.1", + "@types/node": "18.11.18", "@types/react": "18.0.26", "@types/react-dom": "18.0.9", "@typescript-eslint/eslint-plugin": "5.46.1", @@ -53,6 +54,7 @@ "eslint-plugin-jsx-a11y": "6.6.1", "eslint-plugin-react": "7.31.11", "eslint-plugin-react-hooks": "4.6.0", + "mini-svg-data-uri": "1.4.4", "prettier": "2.8.1", "sass": "1.56.2", "typescript": "4.9.4", @@ -1054,6 +1056,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -3768,6 +3776,15 @@ "node": ">=8.6" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index 5ba7933d..8ea3c31c 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ }, "devDependencies": { "@rollup/plugin-wasm": "6.1.1", + "@types/node": "18.11.18", "@types/react": "18.0.26", "@types/react-dom": "18.0.9", "@typescript-eslint/eslint-plugin": "5.46.1", @@ -63,6 +64,7 @@ "eslint-plugin-jsx-a11y": "6.6.1", "eslint-plugin-react": "7.31.11", "eslint-plugin-react-hooks": "4.6.0", + "mini-svg-data-uri": "1.4.4", "prettier": "2.8.1", "sass": "1.56.2", "typescript": "4.9.4", diff --git a/src/app/atoms/system-icons/RawIcon.jsx b/src/app/atoms/system-icons/RawIcon.jsx index 08acc66b..a6697f4f 100644 --- a/src/app/atoms/system-icons/RawIcon.jsx +++ b/src/app/atoms/system-icons/RawIcon.jsx @@ -2,20 +2,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import './RawIcon.scss'; -function RawIcon({ - color, size, src, isImage, -}) { +function RawIcon({ color, size, src, isImage }) { const style = {}; if (color !== null) style.backgroundColor = color; if (isImage) { style.backgroundColor = 'transparent'; - style.backgroundImage = `url(${src})`; + style.backgroundImage = `url("${src}")`; } else { - style.WebkitMaskImage = `url(${src})`; - style.maskImage = `url(${src})`; + style.WebkitMaskImage = `url("${src}")`; + style.maskImage = `url("${src}")`; } - return ; + return ; } RawIcon.defaultProps = { diff --git a/vite.config.js b/vite.config.js index b46913be..7ca1baff 100644 --- a/vite.config.js +++ b/vite.config.js @@ -2,6 +2,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { wasm } from '@rollup/plugin-wasm'; import { viteStaticCopy } from 'vite-plugin-static-copy'; +import { svgLoader } from './viteSvgLoader'; const copyFiles = { targets: [ @@ -33,6 +34,7 @@ export default defineConfig({ }, plugins: [ viteStaticCopy(copyFiles), + svgLoader(), wasm(), react(), ], diff --git a/viteSvgLoader.ts b/viteSvgLoader.ts new file mode 100644 index 00000000..a119e3ed --- /dev/null +++ b/viteSvgLoader.ts @@ -0,0 +1,16 @@ +import svgToMiniDataURI from 'mini-svg-data-uri'; +import type { Plugin } from 'rollup'; +import fs from 'fs'; + +// TODO: remove this once https://github.com/vitejs/vite/pull/2909 gets merged +export const svgLoader = (): Plugin => ({ + name: 'vite-svg-patch-plugin', + transform: (code, id) => { + if (id.endsWith('.svg')) { + const extractedSvg = fs.readFileSync(id, 'utf8'); + const datauri = svgToMiniDataURI.toSrcset(extractedSvg); + return `export default "${datauri}"`; + } + return code; + }, +}); From 4ea14c853ee7b9c1a211a08e386fb323870e2ec2 Mon Sep 17 00:00:00 2001 From: Krishan <33421343+kfiven@users.noreply.github.com> Date: Sun, 15 Jan 2023 16:16:40 +1100 Subject: [PATCH 4/4] Release v2.2.3 --- package-lock.json | 4 ++-- package.json | 2 +- src/client/state/cons.js | 10 ++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 673ba846..ffea90a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cinny", - "version": "2.2.2", + "version": "2.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cinny", - "version": "2.2.2", + "version": "2.2.3", "license": "MIT", "dependencies": { "@fontsource/inter": "4.5.14", diff --git a/package.json b/package.json index 8ea3c31c..4082853c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cinny", - "version": "2.2.2", + "version": "2.2.3", "description": "Yet another matrix client", "main": "index.js", "engines": { diff --git a/src/client/state/cons.js b/src/client/state/cons.js index 785047d7..584eaba4 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -1,5 +1,5 @@ const cons = { - version: '2.2.2', + version: '2.2.3', secretKey: { ACCESS_TOKEN: 'cinny_access_token', DEVICE_ID: 'cinny_device_id', @@ -12,7 +12,13 @@ const cons = { HOME: 'home', DIRECTS: 'dm', }, - supportEventTypes: ['m.room.create', 'm.room.message', 'm.room.encrypted', 'm.room.member', 'm.sticker'], + supportEventTypes: [ + 'm.room.create', + 'm.room.message', + 'm.room.encrypted', + 'm.room.member', + 'm.sticker', + ], notifs: { DEFAULT: 'default', ALL_MESSAGES: 'all_messages',