this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Experimental 'More…' for custom emojis suggestions

Also includes small fixes and improvements

+83 -18
+13 -7
src/components/compose.css
··· 298 298 height: 2.2em; 299 299 } 300 300 #compose-container .text-expander-menu li:is(:hover, :focus, [aria-selected]) { 301 - color: var(--bg-color); 302 - background-color: var(--link-color); 303 - } 304 - #compose-container 305 - .text-expander-menu:hover 306 - li[aria-selected]:not(:hover, :focus) { 301 + background-color: var(--link-bg-color); 307 302 color: var(--text-color); 308 - background-color: var(--bg-color); 303 + } 304 + #compose-container .text-expander-menu li[aria-selected] { 305 + box-shadow: inset 4px 0 0 0 var(--button-bg-color); 306 + } 307 + #compose-container .text-expander-menu li[data-more] { 308 + &:not(:hover, :focus, [aria-selected]) { 309 + color: var(--text-insignificant-color); 310 + background-color: var(--bg-faded-color); 311 + } 312 + 313 + font-size: 0.8em; 314 + justify-content: center; 309 315 } 310 316 311 317 #compose-container .form-visibility-direct {
+70 -11
src/components/compose.jsx
··· 378 378 } 379 379 380 380 // check for status and media attachments 381 + const hasValue = (value || '') 382 + .trim() 383 + .replace(/^\p{White_Space}+|\p{White_Space}+$/gu, ''); 381 384 const hasMediaAttachments = mediaAttachments.length > 0; 382 - if (!value && !hasMediaAttachments) { 385 + if (!hasValue && !hasMediaAttachments) { 383 386 console.log('canClose', { value, mediaAttachments }); 384 387 return true; 385 388 } ··· 1119 1122 } 1120 1123 return masto.v2.search.fetch(params); 1121 1124 }} 1125 + onTrigger={(action) => { 1126 + if (action?.name === 'custom-emojis') { 1127 + setShowEmoji2Picker({ 1128 + defaultSearchTerm: action?.defaultSearchTerm || null, 1129 + }); 1130 + } 1131 + }} 1122 1132 /> 1123 1133 {mediaAttachments?.length > 0 && ( 1124 1134 <div class="media-attachments"> ··· 1342 1352 onClose={() => { 1343 1353 setShowEmoji2Picker(false); 1344 1354 }} 1345 - onSelect={(emoji) => { 1346 - const emojiWithSpace = ` ${emoji} `; 1355 + defaultSearchTerm={showEmoji2Picker?.defaultSearchTerm} 1356 + onSelect={(emojiShortcode) => { 1347 1357 const textarea = textareaRef.current; 1348 1358 if (!textarea) return; 1349 1359 const { selectionStart, selectionEnd } = textarea; 1350 1360 const text = textarea.value; 1361 + const textBeforeEmoji = text.slice(0, selectionStart); 1362 + const spaceBeforeEmoji = /[\s\t\n\r]$/.test(textBeforeEmoji) 1363 + ? '' 1364 + : ' '; 1365 + const textAfterEmoji = text.slice(selectionEnd); 1366 + const spaceAfterEmoji = /^[\s\t\n\r]/.test(textAfterEmoji) 1367 + ? '' 1368 + : ' '; 1351 1369 const newText = 1352 - text.slice(0, selectionStart) + 1353 - emojiWithSpace + 1354 - text.slice(selectionEnd); 1370 + textBeforeEmoji + 1371 + spaceBeforeEmoji + 1372 + emojiShortcode + 1373 + spaceAfterEmoji + 1374 + textAfterEmoji; 1355 1375 textarea.value = newText; 1356 1376 textarea.selectionStart = textarea.selectionEnd = 1357 - selectionEnd + emojiWithSpace.length; 1377 + selectionEnd + emojiShortcode.length + spaceAfterEmoji.length; 1358 1378 textarea.focus(); 1359 1379 textarea.dispatchEvent(new Event('input')); 1360 1380 }} ··· 1454 1474 const Textarea = forwardRef((props, ref) => { 1455 1475 const { masto, instance } = api(); 1456 1476 const [text, setText] = useState(ref.current?.value || ''); 1457 - const { maxCharacters, performSearch = () => {}, ...textareaProps } = props; 1477 + const { 1478 + maxCharacters, 1479 + performSearch = () => {}, 1480 + onTrigger = () => {}, 1481 + ...textareaProps 1482 + } = props; 1458 1483 // const snapStates = useSnapshot(states); 1459 1484 // const charCount = snapStates.composerCharacterCount; 1460 1485 ··· 1509 1534 ${encodeHTML(shortcode)} 1510 1535 </li>`; 1511 1536 }); 1537 + html += `<li role="option" data-value="" data-more="${text}">More…</li>`; 1512 1538 // console.log({ emojis, html }); 1513 1539 menu.innerHTML = html; 1514 1540 provide( ··· 1600 1626 1601 1627 handleValue = (e) => { 1602 1628 const { key, item } = e.detail; 1629 + const { value, more } = item.dataset; 1603 1630 if (key === ':') { 1604 - e.detail.value = `:${item.dataset.value}:`; 1631 + e.detail.value = value ? `:${value}:` : '​'; // zero-width space 1632 + if (more) { 1633 + // Prevent adding space after the above value 1634 + e.detail.continue = true; 1635 + 1636 + setTimeout(() => { 1637 + onTrigger?.({ 1638 + name: 'custom-emojis', 1639 + defaultSearchTerm: more, 1640 + }); 1641 + }, 300); 1642 + } 1605 1643 } else { 1606 - e.detail.value = `${key}${item.dataset.value}`; 1644 + e.detail.value = `${key}${value}`; 1607 1645 } 1608 1646 }; 1609 1647 ··· 1748 1786 }} 1749 1787 onInput={(e) => { 1750 1788 const { target } = e; 1751 - const text = target.value; 1789 + // Replace zero-width space 1790 + const text = target.value.replace(/\u200b/g, ''); 1752 1791 setText(text); 1753 1792 autoResizeTextarea(target); 1754 1793 props.onInput?.(e); ··· 2270 2309 instance, 2271 2310 onClose = () => {}, 2272 2311 onSelect = () => {}, 2312 + defaultSearchTerm, 2273 2313 }) { 2274 2314 const [uiState, setUIState] = useState('default'); 2275 2315 const customEmojisList = useRef([]); ··· 2336 2376 }, 2337 2377 [customEmojis], 2338 2378 ); 2379 + useEffect(() => { 2380 + if (defaultSearchTerm && customEmojis?.length) { 2381 + onFind({ target: { value: defaultSearchTerm } }); 2382 + } 2383 + }, [defaultSearchTerm, onFind, customEmojis]); 2339 2384 2340 2385 const onSelectEmoji = useCallback( 2341 2386 (emoji) => { ··· 2371 2416 [onSelect], 2372 2417 ); 2373 2418 2419 + const inputRef = useRef(); 2420 + useEffect(() => { 2421 + if (inputRef.current) { 2422 + inputRef.current.focus(); 2423 + // Put cursor at the end 2424 + if (inputRef.current.value) { 2425 + inputRef.current.selectionStart = inputRef.current.value.length; 2426 + inputRef.current.selectionEnd = inputRef.current.value.length; 2427 + } 2428 + } 2429 + }, []); 2430 + 2374 2431 return ( 2375 2432 <div id="custom-emojis-sheet" class="sheet"> 2376 2433 {!!onClose && ( ··· 2397 2454 }} 2398 2455 > 2399 2456 <input 2457 + ref={inputRef} 2400 2458 type="search" 2401 2459 placeholder="Search emoji" 2402 2460 onInput={onFind} ··· 2405 2463 autocapitalize="off" 2406 2464 spellCheck="false" 2407 2465 dir="auto" 2466 + defaultValue={defaultSearchTerm || ''} 2408 2467 /> 2409 2468 </form> 2410 2469 </header>