diff --git a/public/res/ic/outlined/sticker.svg b/public/res/ic/outlined/sticker.svg
new file mode 100644
index 00000000..bc486e5e
--- /dev/null
+++ b/public/res/ic/outlined/sticker.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/app/organisms/emoji-board/EmojiBoard.scss b/src/app/organisms/emoji-board/EmojiBoard.scss
index 256bdf16..6883e18e 100644
--- a/src/app/organisms/emoji-board/EmojiBoard.scss
+++ b/src/app/organisms/emoji-board/EmojiBoard.scss
@@ -84,6 +84,7 @@
.emoji {
width: 32px;
height: 32px;
+ object-fit: contain;
}
}
& > p:last-child {
diff --git a/src/app/organisms/room/RoomViewInput.jsx b/src/app/organisms/room/RoomViewInput.jsx
index f4ba3f3f..a39f92b4 100644
--- a/src/app/organisms/room/RoomViewInput.jsx
+++ b/src/app/organisms/room/RoomViewInput.jsx
@@ -8,7 +8,7 @@ import TextareaAutosize from 'react-autosize-textarea';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings';
-import { openEmojiBoard } from '../../../client/action/navigation';
+import { openEmojiBoard, openReusableContextMenu } from '../../../client/action/navigation';
import navigation from '../../../client/state/navigation';
import { bytesToSize, getEventCords } from '../../../util/common';
import { getUsername } from '../../../util/matrixUtil';
@@ -20,9 +20,12 @@ import IconButton from '../../atoms/button/IconButton';
import ScrollView from '../../atoms/scroll/ScrollView';
import { MessageReply } from '../../molecules/message/Message';
+import StickerBoard from '../sticker-board/StickerBoard';
+
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
import SendIC from '../../../../public/res/ic/outlined/send.svg';
+import StickerIC from '../../../../public/res/ic/outlined/sticker.svg';
import ShieldIC from '../../../../public/res/ic/outlined/shield.svg';
import VLCIC from '../../../../public/res/ic/outlined/vlc.svg';
import VolumeFullIC from '../../../../public/res/ic/outlined/volume-full.svg';
@@ -203,6 +206,33 @@ function RoomViewInput({
if (replyTo !== null) setReplyTo(null);
};
+ const handleSendSticker = async (data) => {
+ const { mxc: url, body, httpUrl } = data;
+ const info = {};
+
+ const img = new Image();
+ img.src = httpUrl;
+
+ try {
+ const res = await fetch(httpUrl);
+ const blob = await res.blob();
+ info.w = img.width;
+ info.h = img.height;
+ info.mimetype = blob.type;
+ info.size = blob.size;
+ info.thumbnail_info = { ...info };
+ info.thumbnail_url = url;
+ } catch {
+ // send sticker without info
+ }
+
+ mx.sendEvent(roomId, 'm.sticker', {
+ body,
+ url,
+ info,
+ });
+ };
+
function processTyping(msg) {
const isEmptyMsg = msg === '';
@@ -342,6 +372,29 @@ function RoomViewInput({
{isMarkdown && }
+
{
+ openReusableContextMenu(
+ 'top',
+ (() => {
+ const cords = getEventCords(e);
+ cords.y -= 20;
+ return cords;
+ })(),
+ (closeMenu) => (
+ {
+ handleSendSticker(data);
+ closeMenu();
+ }}
+ />
+ ),
+ );
+ }}
+ tooltip="Sticker"
+ src={StickerIC}
+ />
{
const cords = getEventCords(e);
diff --git a/src/app/organisms/sticker-board/StickerBoard.jsx b/src/app/organisms/sticker-board/StickerBoard.jsx
new file mode 100644
index 00000000..53b75635
--- /dev/null
+++ b/src/app/organisms/sticker-board/StickerBoard.jsx
@@ -0,0 +1,88 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+import React from 'react';
+import PropTypes from 'prop-types';
+import './StickerBoard.scss';
+
+import initMatrix from '../../../client/initMatrix';
+import { getRelevantPacks } from '../emoji-board/custom-emoji';
+
+import Text from '../../atoms/text/Text';
+import ScrollView from '../../atoms/scroll/ScrollView';
+
+function StickerBoard({ roomId, onSelect }) {
+ const mx = initMatrix.matrixClient;
+ const room = mx.getRoom(roomId);
+
+ const parentIds = initMatrix.roomList.getAllParentSpaces(room.roomId);
+ const parentRooms = [...parentIds].map((id) => mx.getRoom(id));
+
+ const packs = getRelevantPacks(
+ mx,
+ [room, ...parentRooms],
+ ).filter((pack) => pack.getStickers().length !== 0);
+
+ function isTargetNotSticker(target) {
+ return target.classList.contains('sticker-board__sticker') === false;
+ }
+ function getStickerData(target) {
+ const mxc = target.getAttribute('data-mx-sticker');
+ const body = target.getAttribute('title');
+ const httpUrl = target.getAttribute('src');
+ return { mxc, body, httpUrl };
+ }
+ const handleOnSelect = (e) => {
+ if (isTargetNotSticker(e.target)) return;
+
+ const stickerData = getStickerData(e.target);
+ onSelect(stickerData);
+ };
+
+ const renderPack = (pack) => (
+
+
{pack.displayName ?? 'Unknown'}
+
+ {pack.getStickers().map((sticker) => (
+
+ ))}
+
+
+ );
+
+ return (
+
+
+
+
+ {
+ packs.length > 0
+ ? packs.map(renderPack)
+ : (
+
+ There is no sticker pack.
+
+ )
+ }
+
+
+
+
+
+ );
+}
+StickerBoard.propTypes = {
+ roomId: PropTypes.string.isRequired,
+ onSelect: PropTypes.func.isRequired,
+};
+
+export default StickerBoard;
diff --git a/src/app/organisms/sticker-board/StickerBoard.scss b/src/app/organisms/sticker-board/StickerBoard.scss
new file mode 100644
index 00000000..be8ad35a
--- /dev/null
+++ b/src/app/organisms/sticker-board/StickerBoard.scss
@@ -0,0 +1,60 @@
+@use '../../partials/dir';
+
+.sticker-board {
+ --sticker-board-height: 390px;
+ --sticker-board-width: 286px;
+ display: flex;
+ height: var(--sticker-board-height);
+
+ &__container {
+ flex-grow: 1;
+ min-width: 0;
+ width: var(--sticker-board-width);
+ display: flex;
+ }
+
+ &__content {
+ min-height: 100%;
+ }
+
+ &__pack {
+ margin-bottom: var(--sp-normal);
+ position: relative;
+
+ &-header {
+ position: sticky;
+ top: 0;
+ z-index: 99;
+ background-color: var(--bg-surface);
+
+ @include dir.side(margin, var(--sp-extra-tight), 0);
+ padding: var(--sp-extra-tight) var(--sp-ultra-tight);
+ text-transform: uppercase;
+ box-shadow: 0 -4px 0 0 var(--bg-surface);
+ border-bottom: 1px solid var(--bg-surface-border);
+ }
+ &-items {
+ margin: var(--sp-tight);
+ @include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--sp-normal) var(--sp-tight);
+
+ img {
+ width: 76px;
+ height: 76px;
+ object-fit: contain;
+ cursor: pointer;
+ }
+ }
+ }
+
+ &__empty {
+ width: 100%;
+ height: var(--sticker-board-height);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ }
+}
\ No newline at end of file