From 4f4164600078e7e3a94ac012fd531e2b55287103 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 26 Nov 2023 18:25:29 +0800 Subject: [PATCH] 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 --- package-lock.json | 6 ++++++ package.json | 1 + src/components/compose.css | 5 +++++ src/components/compose.jsx | 17 +++++++++++------ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ccff6e09..42f0e378 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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, diff --git a/package.json b/package.json index bce70b0e..a090fe30 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/compose.css b/src/components/compose.css index 504ee99a..4096c8e4 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -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) { diff --git a/src/components/compose.jsx b/src/components/compose.jsx index bb6542c6..41919a3b 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -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 = '' + - html.slice(-leftoverCount) + + // html.slice(-leftoverCount) + + substring(html, maxCharacters) + ''; - 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$3') // URLs .replace(MENTION_RE, '$1$2') // Mentions .replace(HASHTAG_RE, '$1$2') // Hashtags @@ -148,8 +153,6 @@ function highlightText(text, { maxCharacters = Infinity }) { SCAN_RE, '$1$2', ); // 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;