diff --git a/package-lock.json b/package-lock.json
index 3cd0cd2a..7b4c0624 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",
@@ -42,6 +42,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",
@@ -54,6 +55,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",
@@ -1055,6 +1057,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",
@@ -3777,6 +3785,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 9ff1bdc5..9ef13878 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": {
@@ -52,6 +52,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",
@@ -64,6 +65,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/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'}
)}
-
+
);
}
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.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
+
+ )}
+
);
}
- 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/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',
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',
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;
+ },
+});