Multiple fixes on composer highlighting

- Hide scrollbar for the faux highlight div
- Use unicode-aware split for highlighting exceeded characters
- Disable highlight of mentions, hashtags, etc if exceeded max characters
- Sync scroll as often as possible
This commit is contained in:
Lim Chee Aun 2023-11-26 18:25:29 +08:00
parent 7019c09e5b
commit 4f41646000
4 changed files with 23 additions and 6 deletions

6
package-lock.json generated
View file

@ -32,6 +32,7 @@
"react-intersection-observer": "~9.5.3",
"react-quick-pinch-zoom": "~5.1.0",
"react-router-dom": "6.6.2",
"runes2": "~1.1.3",
"string-length": "5.0.1",
"swiped-events": "~1.1.9",
"toastify-js": "~1.12.0",
@ -6057,6 +6058,11 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/runes2": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.3.tgz",
"integrity": "sha512-sJ/0iVFLne4f2S7cMB1OckBtC9lqkzP5a/wPnDIkbrWzgUsJ+JMQv6y7hk76U7zvbua+je5GltfpsZazUhG05w=="
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"dev": true,

View file

@ -34,6 +34,7 @@
"react-intersection-observer": "~9.5.3",
"react-quick-pinch-zoom": "~5.1.0",
"react-router-dom": "6.6.2",
"runes2": "~1.1.3",
"string-length": "5.0.1",
"swiped-events": "~1.1.9",
"toastify-js": "~1.12.0",

View file

@ -653,6 +653,11 @@
white-space: pre-wrap;
min-height: 5em;
max-height: 50vh;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
/* Follow textarea styles */
@media (min-width: 40em) {

View file

@ -5,6 +5,7 @@ import equal from 'fast-deep-equal';
import { forwardRef } from 'preact/compat';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { substring } from 'runes2';
import stringLength from 'string-length';
import { uid } from 'uid/single';
import { useDebouncedCallback, useThrottledCallback } from 'use-debounce';
@ -131,16 +132,20 @@ function highlightText(text, { maxCharacters = Infinity }) {
const { composerCharacterCount } = states;
let leftoverHTML = '';
if (composerCharacterCount > maxCharacters) {
const leftoverCount = composerCharacterCount - maxCharacters;
// NOTE: runes2 substring considers surrogate pairs
// const leftoverCount = composerCharacterCount - maxCharacters;
// Highlight exceeded characters
leftoverHTML =
'<mark class="compose-highlight-exceeded">' +
html.slice(-leftoverCount) +
// html.slice(-leftoverCount) +
substring(html, maxCharacters) +
'</mark>';
html = html.slice(0, -leftoverCount);
// html = html.slice(0, -leftoverCount);
html = substring(html, 0, maxCharacters);
return html + leftoverHTML;
}
html = html
return html
.replace(urlRegexObj, '$2<mark class="compose-highlight-url">$3</mark>') // URLs
.replace(MENTION_RE, '$1<mark class="compose-highlight-mention">$2</mark>') // Mentions
.replace(HASHTAG_RE, '$1<mark class="compose-highlight-hashtag">$2</mark>') // Hashtags
@ -148,8 +153,6 @@ function highlightText(text, { maxCharacters = Infinity }) {
SCAN_RE,
'$1<mark class="compose-highlight-emoji-shortcode">$2</mark>',
); // Emoji shortcodes
return html + leftoverHTML;
}
function Compose({
@ -1538,6 +1541,7 @@ const Textarea = forwardRef((props, ref) => {
target.setRangeText('', pos, selectionStart);
}
autoResizeTextarea(target);
target.dispatchEvent(new Event('input'));
}
}
} catch (e) {
@ -1545,6 +1549,7 @@ const Textarea = forwardRef((props, ref) => {
console.error(e);
}
}
composeHighlightRef.current.scrollTop = target.scrollTop;
}}
onInput={(e) => {
const { target } = e;