diff --git a/package.json b/package.json index 779e112..fbd41f5 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "react-dom": "17.0.2", "react-modal": "3.16.1", "sanitize-html": "2.8.0", - "tippy.js": "6.3.7" + "tippy.js": "6.3.7", + "twemoji": "14.0.2" }, "devDependencies": { "@esbuild-plugins/node-globals-polyfill": "0.2.3", diff --git a/src/app/atoms/text/Text.scss b/src/app/atoms/text/Text.scss index 256bf6e..0da7cc7 100644 --- a/src/app/atoms/text/Text.scss +++ b/src/app/atoms/text/Text.scss @@ -9,6 +9,10 @@ } } +.emoji { + content-visibility: auto; +} + .text { margin: 0; padding: 0; diff --git a/src/app/organisms/emoji-board/EmojiBoard.jsx b/src/app/organisms/emoji-board/EmojiBoard.jsx index 1e17b09..7d2461b 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.jsx +++ b/src/app/organisms/emoji-board/EmojiBoard.jsx @@ -131,7 +131,7 @@ function SearchedEmoji() { function EmojiBoard({ onSelect, searchRef }) { const scrollEmojisRef = useRef(null); - const emojiInfo = useRef(null); + const [emojiInfo, setEmojiInfo] = useState(null); function isTargetNotEmoji(target) { return target.classList.contains('emoji') === false; @@ -159,25 +159,15 @@ function EmojiBoard({ onSelect, searchRef }) { if (emoji.hexcode) addRecentEmoji(emoji.unicode); } - function setEmojiInfo(emoji) { - const infoEmoji = emojiInfo.current.firstElementChild.firstElementChild; - const infoShortcode = emojiInfo.current.lastElementChild; - - infoEmoji.src = emoji.src; - infoEmoji.alt = emoji.unicode; - infoShortcode.textContent = `:${emoji.shortcode}:`; - } - function hoverEmoji(e) { if (isTargetNotEmoji(e.target)) return; const emoji = e.target; const { shortcodes, unicode } = getEmojiDataFromTarget(emoji); - const { src } = e.target; if (searchRef.current.placeholder === shortcodes[0]) return; searchRef.current.setAttribute('placeholder', shortcodes[0]); - setEmojiInfo({ shortcode: shortcodes[0], src, unicode }); + setEmojiInfo({ shortcode: shortcodes[0], unicode }); } function handleSearchChange() { @@ -318,9 +308,9 @@ function EmojiBoard({ onSelect, searchRef }) { -
-
🙂
- :slight_smile: +
+
{ emojiInfo ? emojiInfo.unicode : '' }
+ {emojiInfo ? emojiInfo.shortcode : ''}
diff --git a/src/app/organisms/emoji-board/EmojiBoard.scss b/src/app/organisms/emoji-board/EmojiBoard.scss index 683026f..4818b42 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.scss +++ b/src/app/organisms/emoji-board/EmojiBoard.scss @@ -4,7 +4,7 @@ .emoji-board { --emoji-board-height: 390px; - --emoji-board-width: 286px; + --emoji-board-width: 218px; display: flex; max-width: 90vw; max-height: 90vh; @@ -121,6 +121,7 @@ @include dir.side(margin, var(--left-margin), var(--right-margin)); } & .emoji { + display: block; max-width: 38px; max-height: 38px; width: 100%; diff --git a/src/app/organisms/room/PeopleDrawer.jsx b/src/app/organisms/room/PeopleDrawer.jsx index 3c3984a..c7a1f8b 100644 --- a/src/app/organisms/room/PeopleDrawer.jsx +++ b/src/app/organisms/room/PeopleDrawer.jsx @@ -128,7 +128,7 @@ function PeopleDrawer({ roomId }) {
- + People {`${room.getJoinedMemberCount()} members`} diff --git a/src/app/organisms/room/RoomViewCmdBar.jsx b/src/app/organisms/room/RoomViewCmdBar.jsx index 27b8544..0d39de9 100644 --- a/src/app/organisms/room/RoomViewCmdBar.jsx +++ b/src/app/organisms/room/RoomViewCmdBar.jsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './RoomViewCmdBar.scss'; -import { twemojify } from '../../../util/twemojify'; +import { singleEmojiToJSX, twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import { getEmojiForCompletion } from '../emoji-board/custom-emoji'; @@ -49,6 +49,34 @@ function renderSuggestions({ prefix, option, suggestions }, fireCmd) { function renderEmojiSuggestion(emPrefix, emos) { const mx = initMatrix.matrixClient; + // Renders a small Twemoji + function renderTwemoji(emoji) { + return singleEmojiToJSX(emoji); + } + + // Render a custom emoji + function renderCustomEmoji(emoji) { + return ( + {`:${emoji.shortcode}:`} + ); + } + + // Dynamically render either a custom emoji or twemoji based on what the input is + function renderEmoji(emoji) { + if (emoji.mxc) { + return renderCustomEmoji(emoji); + } + return renderTwemoji(emoji); + } + return emos.map((emoji) => ( - {emoji} + {renderEmoji(emoji)} {`:${emoji.shortcode}:`} )); diff --git a/src/index.jsx b/src/index.jsx index a252f6f..ae4baa5 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { StrictMode } from 'react'; import ReactDom from 'react-dom'; import './font'; import './index.scss'; @@ -9,4 +9,9 @@ import App from './app/pages/App'; settings.applyTheme(); -ReactDom.render(, document.getElementById('root')); +ReactDom.render( + + + , + document.getElementById('root') +); diff --git a/src/util/twemojify.jsx b/src/util/twemojify.jsx index b8cd6a5..699c255 100644 --- a/src/util/twemojify.jsx +++ b/src/util/twemojify.jsx @@ -1,20 +1,65 @@ import linkifyHtml from 'linkify-html'; import parse from 'html-react-parser'; +import twemoji from 'twemoji'; import { sanitizeText } from './sanitize'; -export function twemojify(text, _opts=null, linkify=false, sanitize=true, _maths=false) { +export const TWEMOJI_BASE_URL = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/'; + +// Start modified block from `twemoji` code: +// MIT License +// Copyright (c) 2021 Twitter +const UFE0Fg = /\uFE0F/g; +const U200D = String.fromCharCode(0x200D); + +function grabTheRightIcon(rawText) { + // if variant is present as \uFE0F + return twemoji.convert.toCodePoint(rawText.indexOf(U200D) < 0 ? + rawText.replace(UFE0Fg, '') : + rawText + ); +} +// End function from `twemoji` + +export function singleEmojiToJSX({ shortcodes, unicode, hexcode }) { + return {unicode}; +} + +/** + * @param {string} text - text to twemojify + * @param {object|undefined} opts - options for tweomoji.parse + * @param {boolean} [linkify=false] - convert links to html tags (default: false) + * @param {boolean} [sanitize=true] - sanitize html text (default: true) + * @returns React component + */ +export function twemojify(text, opts, linkify = false, sanitize = true) { 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, options); if (linkify) { content = linkifyHtml(content, { target: '_blank', rel: 'noreferrer noopener', }); } - return parse(content); + return parse(content, null); } diff --git a/yarn.lock b/yarn.lock index 3af7a2d..2a0e344 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1633,6 +1633,15 @@ fs-extra@^11.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2080,6 +2089,22 @@ json5@^2.2.2: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-5.0.0.tgz#e6b718f73da420d612823996fdf14a03f6ff6922" + integrity sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w== + dependencies: + universalify "^0.1.2" + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -2906,6 +2931,21 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +twemoji-parser@14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" + integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== + +twemoji@14.0.2: + version "14.0.2" + resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-14.0.2.tgz#c53adb01dab22bf4870f648ca8cc347ce99ee37e" + integrity sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA== + dependencies: + fs-extra "^8.0.1" + jsonfile "^5.0.0" + twemoji-parser "14.0.0" + universalify "^0.1.2" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -2952,6 +2992,11 @@ unhomoglyph@^1.0.6: resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3" integrity sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg== +universalify@^0.1.0, universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"