diff --git a/src/components/compose.css b/src/components/compose.css
index 7e09c82e..8e058118 100644
--- a/src/components/compose.css
+++ b/src/components/compose.css
@@ -523,3 +523,43 @@
height: auto;
}
}
+
+#custom-emojis-sheet {
+ max-height: 50vh;
+ max-height: 50dvh;
+}
+#custom-emojis-sheet main {
+ mask-image: none;
+}
+#custom-emojis-sheet .custom-emojis-list .section-header {
+ font-size: 80%;
+ text-transform: uppercase;
+ color: var(--text-insignificant-color);
+ padding: 8px 0 4px;
+ position: sticky;
+ top: 0;
+ background-color: var(--bg-blur-color);
+ backdrop-filter: blur(8px);
+}
+#custom-emojis-sheet .custom-emojis-list section {
+ display: flex;
+ flex-wrap: wrap;
+}
+#custom-emojis-sheet .custom-emojis-list button {
+ border-radius: 8px;
+ background-image: radial-gradient(
+ closest-side,
+ var(--img-bg-color),
+ transparent
+ );
+}
+#custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) {
+ filter: none;
+ background-color: var(--bg-faded-color);
+}
+#custom-emojis-sheet .custom-emojis-list button img {
+ transition: transform 0.1s ease-out;
+}
+#custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) img {
+ transform: scale(1.5);
+}
diff --git a/src/components/compose.jsx b/src/components/compose.jsx
index d03e0681..ababec90 100644
--- a/src/components/compose.jsx
+++ b/src/components/compose.jsx
@@ -4,7 +4,7 @@ import { match } from '@formatjs/intl-localematcher';
import '@github/text-expander-element';
import equal from 'fast-deep-equal';
import { forwardRef } from 'preact/compat';
-import { useEffect, useRef, useState } from 'preact/hooks';
+import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import stringLength from 'string-length';
import { uid } from 'uid/single';
@@ -497,6 +497,8 @@ function Compose({
};
}, [mediaAttachments]);
+ const [showEmoji2Picker, setShowEmoji2Picker] = useState(false);
+
return (
@@ -982,65 +984,77 @@ function Compose({
justifyContent: 'flex-end',
}}
>
-
{' '}
-
{' '}
+ >
+
+ {' '}
+
+
{uiState === 'loading' ? (
@@ -1089,6 +1103,40 @@ function Compose({
+ {showEmoji2Picker && (
+ {
+ if (e.target === e.currentTarget) {
+ setShowEmoji2Picker(false);
+ }
+ }}
+ >
+ {
+ setShowEmoji2Picker(false);
+ }}
+ onSelect={(emoji) => {
+ const emojiWithSpace = ` ${emoji} `;
+ const textarea = textareaRef.current;
+ if (!textarea) return;
+ const { selectionStart, selectionEnd } = textarea;
+ const text = textarea.value;
+ const newText =
+ text.slice(0, selectionStart) +
+ emojiWithSpace +
+ text.slice(selectionEnd);
+ textarea.value = newText;
+ textarea.selectionStart = textarea.selectionEnd =
+ selectionEnd + emojiWithSpace.length;
+ textarea.focus();
+ textarea.dispatchEvent(new Event('input'));
+ }}
+ />
+
+ )}
);
}
@@ -1287,6 +1335,7 @@ const Textarea = forwardRef((props, ref) => {
value={text}
onInput={(e) => {
const { scrollHeight, offsetHeight, clientHeight, value } = e.target;
+ console.log('textarea input', value);
setText(value);
const offset = offsetHeight - clientHeight;
e.target.style.height = value ? scrollHeight + offset + 'px' : null;
@@ -1626,4 +1675,147 @@ function removeNullUndefined(obj) {
return obj;
}
+function CustomEmojisModal({
+ masto,
+ instance,
+ onClose = () => {},
+ onSelect = () => {},
+}) {
+ const [uiState, setUIState] = useState('default');
+ const customEmojisList = useRef([]);
+ const [customEmojis, setCustomEmojis] = useState({});
+ const recentlyUsedCustomEmojis = useMemo(
+ () => store.account.get('recentlyUsedCustomEmojis') || [],
+ );
+ useEffect(() => {
+ setUIState('loading');
+ (async () => {
+ try {
+ const emojis = await masto.v1.customEmojis.list();
+ // Group emojis by category
+ const emojisCat = {
+ '--recent--': recentlyUsedCustomEmojis.filter((emoji) =>
+ emojis.find((e) => e.shortcode === emoji.shortcode),
+ ),
+ };
+ const othersCat = [];
+ emojis.forEach((emoji) => {
+ if (!emoji.visibleInPicker) return;
+ customEmojisList.current?.push?.(emoji);
+ if (!emoji.category) {
+ othersCat.push(emoji);
+ return;
+ }
+ if (!emojisCat[emoji.category]) {
+ emojisCat[emoji.category] = [];
+ }
+ emojisCat[emoji.category].push(emoji);
+ });
+ if (othersCat.length) {
+ emojisCat['--others--'] = othersCat;
+ }
+ setCustomEmojis(emojisCat);
+ setUIState('default');
+ } catch (e) {
+ setUIState('error');
+ console.error(e);
+ }
+ })();
+ }, []);
+
+ return (
+
+
+ Custom emojis{' '}
+ {uiState === 'loading' ? (
+
+ ) : (
+ • {instance}
+ )}
+
+
+
+ {uiState === 'error' && (
+
+
Error loading custom emojis
+
+ )}
+ {uiState === 'default' &&
+ Object.entries(customEmojis).map(
+ ([category, emojis]) =>
+ !!emojis?.length && (
+ <>
+
+
+ {emojis.map((emoji) => (
+
+ ))}
+
+ >
+ ),
+ )}
+
+
+
+ );
+}
+
export default Compose;
diff --git a/src/components/icon.jsx b/src/components/icon.jsx
index 9dc81fb7..8d6fcb10 100644
--- a/src/components/icon.jsx
+++ b/src/components/icon.jsx
@@ -73,6 +73,7 @@ const ICONS = {
flag: 'mingcute:flag-4-line',
time: 'mingcute:time-line',
refresh: 'mingcute:refresh-2-line',
+ emoji2: 'mingcute:emoji-2-line',
};
const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js');