Experimental 'More…' for custom emojis suggestions
Also includes small fixes and improvements
This commit is contained in:
parent
ad7cb46547
commit
7053fcc96a
2 changed files with 83 additions and 18 deletions
|
@ -298,14 +298,20 @@
|
||||||
height: 2.2em;
|
height: 2.2em;
|
||||||
}
|
}
|
||||||
#compose-container .text-expander-menu li:is(:hover, :focus, [aria-selected]) {
|
#compose-container .text-expander-menu li:is(:hover, :focus, [aria-selected]) {
|
||||||
color: var(--bg-color);
|
background-color: var(--link-bg-color);
|
||||||
background-color: var(--link-color);
|
|
||||||
}
|
|
||||||
#compose-container
|
|
||||||
.text-expander-menu:hover
|
|
||||||
li[aria-selected]:not(:hover, :focus) {
|
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
background-color: var(--bg-color);
|
}
|
||||||
|
#compose-container .text-expander-menu li[aria-selected] {
|
||||||
|
box-shadow: inset 4px 0 0 0 var(--button-bg-color);
|
||||||
|
}
|
||||||
|
#compose-container .text-expander-menu li[data-more] {
|
||||||
|
&:not(:hover, :focus, [aria-selected]) {
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
font-size: 0.8em;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#compose-container .form-visibility-direct {
|
#compose-container .form-visibility-direct {
|
||||||
|
|
|
@ -378,8 +378,11 @@ function Compose({
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for status and media attachments
|
// check for status and media attachments
|
||||||
|
const hasValue = (value || '')
|
||||||
|
.trim()
|
||||||
|
.replace(/^\p{White_Space}+|\p{White_Space}+$/gu, '');
|
||||||
const hasMediaAttachments = mediaAttachments.length > 0;
|
const hasMediaAttachments = mediaAttachments.length > 0;
|
||||||
if (!value && !hasMediaAttachments) {
|
if (!hasValue && !hasMediaAttachments) {
|
||||||
console.log('canClose', { value, mediaAttachments });
|
console.log('canClose', { value, mediaAttachments });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1119,6 +1122,13 @@ function Compose({
|
||||||
}
|
}
|
||||||
return masto.v2.search.fetch(params);
|
return masto.v2.search.fetch(params);
|
||||||
}}
|
}}
|
||||||
|
onTrigger={(action) => {
|
||||||
|
if (action?.name === 'custom-emojis') {
|
||||||
|
setShowEmoji2Picker({
|
||||||
|
defaultSearchTerm: action?.defaultSearchTerm || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{mediaAttachments?.length > 0 && (
|
{mediaAttachments?.length > 0 && (
|
||||||
<div class="media-attachments">
|
<div class="media-attachments">
|
||||||
|
@ -1342,19 +1352,29 @@ function Compose({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowEmoji2Picker(false);
|
setShowEmoji2Picker(false);
|
||||||
}}
|
}}
|
||||||
onSelect={(emoji) => {
|
defaultSearchTerm={showEmoji2Picker?.defaultSearchTerm}
|
||||||
const emojiWithSpace = ` ${emoji} `;
|
onSelect={(emojiShortcode) => {
|
||||||
const textarea = textareaRef.current;
|
const textarea = textareaRef.current;
|
||||||
if (!textarea) return;
|
if (!textarea) return;
|
||||||
const { selectionStart, selectionEnd } = textarea;
|
const { selectionStart, selectionEnd } = textarea;
|
||||||
const text = textarea.value;
|
const text = textarea.value;
|
||||||
|
const textBeforeEmoji = text.slice(0, selectionStart);
|
||||||
|
const spaceBeforeEmoji = /[\s\t\n\r]$/.test(textBeforeEmoji)
|
||||||
|
? ''
|
||||||
|
: ' ';
|
||||||
|
const textAfterEmoji = text.slice(selectionEnd);
|
||||||
|
const spaceAfterEmoji = /^[\s\t\n\r]/.test(textAfterEmoji)
|
||||||
|
? ''
|
||||||
|
: ' ';
|
||||||
const newText =
|
const newText =
|
||||||
text.slice(0, selectionStart) +
|
textBeforeEmoji +
|
||||||
emojiWithSpace +
|
spaceBeforeEmoji +
|
||||||
text.slice(selectionEnd);
|
emojiShortcode +
|
||||||
|
spaceAfterEmoji +
|
||||||
|
textAfterEmoji;
|
||||||
textarea.value = newText;
|
textarea.value = newText;
|
||||||
textarea.selectionStart = textarea.selectionEnd =
|
textarea.selectionStart = textarea.selectionEnd =
|
||||||
selectionEnd + emojiWithSpace.length;
|
selectionEnd + emojiShortcode.length + spaceAfterEmoji.length;
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
textarea.dispatchEvent(new Event('input'));
|
textarea.dispatchEvent(new Event('input'));
|
||||||
}}
|
}}
|
||||||
|
@ -1454,7 +1474,12 @@ const getCustomEmojis = pmem(_getCustomEmojis, {
|
||||||
const Textarea = forwardRef((props, ref) => {
|
const Textarea = forwardRef((props, ref) => {
|
||||||
const { masto, instance } = api();
|
const { masto, instance } = api();
|
||||||
const [text, setText] = useState(ref.current?.value || '');
|
const [text, setText] = useState(ref.current?.value || '');
|
||||||
const { maxCharacters, performSearch = () => {}, ...textareaProps } = props;
|
const {
|
||||||
|
maxCharacters,
|
||||||
|
performSearch = () => {},
|
||||||
|
onTrigger = () => {},
|
||||||
|
...textareaProps
|
||||||
|
} = props;
|
||||||
// const snapStates = useSnapshot(states);
|
// const snapStates = useSnapshot(states);
|
||||||
// const charCount = snapStates.composerCharacterCount;
|
// const charCount = snapStates.composerCharacterCount;
|
||||||
|
|
||||||
|
@ -1509,6 +1534,7 @@ const Textarea = forwardRef((props, ref) => {
|
||||||
${encodeHTML(shortcode)}
|
${encodeHTML(shortcode)}
|
||||||
</li>`;
|
</li>`;
|
||||||
});
|
});
|
||||||
|
html += `<li role="option" data-value="" data-more="${text}">More…</li>`;
|
||||||
// console.log({ emojis, html });
|
// console.log({ emojis, html });
|
||||||
menu.innerHTML = html;
|
menu.innerHTML = html;
|
||||||
provide(
|
provide(
|
||||||
|
@ -1600,10 +1626,22 @@ const Textarea = forwardRef((props, ref) => {
|
||||||
|
|
||||||
handleValue = (e) => {
|
handleValue = (e) => {
|
||||||
const { key, item } = e.detail;
|
const { key, item } = e.detail;
|
||||||
|
const { value, more } = item.dataset;
|
||||||
if (key === ':') {
|
if (key === ':') {
|
||||||
e.detail.value = `:${item.dataset.value}:`;
|
e.detail.value = value ? `:${value}:` : ''; // zero-width space
|
||||||
|
if (more) {
|
||||||
|
// Prevent adding space after the above value
|
||||||
|
e.detail.continue = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
onTrigger?.({
|
||||||
|
name: 'custom-emojis',
|
||||||
|
defaultSearchTerm: more,
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
e.detail.value = `${key}${item.dataset.value}`;
|
e.detail.value = `${key}${value}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1748,7 +1786,8 @@ const Textarea = forwardRef((props, ref) => {
|
||||||
}}
|
}}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const text = target.value;
|
// Replace zero-width space
|
||||||
|
const text = target.value.replace(/\u200b/g, '');
|
||||||
setText(text);
|
setText(text);
|
||||||
autoResizeTextarea(target);
|
autoResizeTextarea(target);
|
||||||
props.onInput?.(e);
|
props.onInput?.(e);
|
||||||
|
@ -2270,6 +2309,7 @@ function CustomEmojisModal({
|
||||||
instance,
|
instance,
|
||||||
onClose = () => {},
|
onClose = () => {},
|
||||||
onSelect = () => {},
|
onSelect = () => {},
|
||||||
|
defaultSearchTerm,
|
||||||
}) {
|
}) {
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
const customEmojisList = useRef([]);
|
const customEmojisList = useRef([]);
|
||||||
|
@ -2336,6 +2376,11 @@ function CustomEmojisModal({
|
||||||
},
|
},
|
||||||
[customEmojis],
|
[customEmojis],
|
||||||
);
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultSearchTerm && customEmojis?.length) {
|
||||||
|
onFind({ target: { value: defaultSearchTerm } });
|
||||||
|
}
|
||||||
|
}, [defaultSearchTerm, onFind, customEmojis]);
|
||||||
|
|
||||||
const onSelectEmoji = useCallback(
|
const onSelectEmoji = useCallback(
|
||||||
(emoji) => {
|
(emoji) => {
|
||||||
|
@ -2371,6 +2416,18 @@ function CustomEmojisModal({
|
||||||
[onSelect],
|
[onSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const inputRef = useRef();
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
// Put cursor at the end
|
||||||
|
if (inputRef.current.value) {
|
||||||
|
inputRef.current.selectionStart = inputRef.current.value.length;
|
||||||
|
inputRef.current.selectionEnd = inputRef.current.value.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="custom-emojis-sheet" class="sheet">
|
<div id="custom-emojis-sheet" class="sheet">
|
||||||
{!!onClose && (
|
{!!onClose && (
|
||||||
|
@ -2397,6 +2454,7 @@ function CustomEmojisModal({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
ref={inputRef}
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search emoji"
|
placeholder="Search emoji"
|
||||||
onInput={onFind}
|
onInput={onFind}
|
||||||
|
@ -2405,6 +2463,7 @@ function CustomEmojisModal({
|
||||||
autocapitalize="off"
|
autocapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
|
defaultValue={defaultSearchTerm || ''}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</header>
|
</header>
|
||||||
|
|
Loading…
Add table
Reference in a new issue