From 531147cbc33082925de859694c9fd543ce0f69ba Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 4 Mar 2024 19:38:46 +0800 Subject: [PATCH] It's time for Intl.Segmenter Remove runes2 --- package-lock.json | 49 ++++++++++++++++++++------------------ package.json | 4 ++-- src/components/compose.jsx | 30 +++++++++++++---------- src/main.jsx | 3 +++ 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2bec3ec..d8843901 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@formatjs/intl-localematcher": "~0.5.4", + "@formatjs/intl-segmenter": "~11.5.5", "@formkit/auto-animate": "~0.8.1", "@github/text-expander-element": "~2.6.1", "@iconify-icons/mingcute": "~1.2.9", @@ -33,8 +34,7 @@ "react-intersection-observer": "~9.8.1", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", - "runes2": "~1.1.4", - "string-length": "5.0.1", + "string-length": "6.0.0", "swiped-events": "~1.1.9", "toastify-js": "~1.12.0", "uid": "~2.0.2", @@ -2924,6 +2924,15 @@ "node": ">=12" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", + "integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, "node_modules/@formatjs/intl-localematcher": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", @@ -2932,6 +2941,16 @@ "tslib": "^2.4.0" } }, + "node_modules/@formatjs/intl-segmenter": { + "version": "11.5.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-segmenter/-/intl-segmenter-11.5.5.tgz", + "integrity": "sha512-mMbJKFGzwYJBcwfL9EfqFje75Ce5WPar5rSi7wWvFtBPFY2Zi1cWIss7FSm2MNNM9l1BycBAsBQuXFt+Hd+0tQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, "node_modules/@formkit/auto-animate": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.1.tgz", @@ -3967,15 +3986,6 @@ "tslib": "^2.0.3" } }, - "node_modules/char-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", - "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", - "license": "MIT", - "engines": { - "node": ">=12.20" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -7116,11 +7126,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/runes2": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", - "integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7262,16 +7267,14 @@ "license": "MIT" }, "node_modules/string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "license": "MIT", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", + "integrity": "sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg==", "dependencies": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12.20" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 07d037c8..f4117a77 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@formatjs/intl-localematcher": "~0.5.4", + "@formatjs/intl-segmenter": "~11.5.5", "@formkit/auto-animate": "~0.8.1", "@github/text-expander-element": "~2.6.1", "@iconify-icons/mingcute": "~1.2.9", @@ -35,8 +36,7 @@ "react-intersection-observer": "~9.8.1", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", - "runes2": "~1.1.4", - "string-length": "5.0.1", + "string-length": "6.0.0", "swiped-events": "~1.1.9", "toastify-js": "~1.12.0", "uid": "~2.0.2", diff --git a/src/components/compose.jsx b/src/components/compose.jsx index c075790f..001b0f9c 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -6,7 +6,6 @@ import { deepEqual } from 'fast-equals'; 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,6 +130,7 @@ const SCAN_RE = new RegExp( 'g', ); +const segmenter = new Intl.Segmenter(); function highlightText(text, { maxCharacters = Infinity }) { // Accept text string, return formatted HTML string // Escape all HTML special characters @@ -143,19 +143,25 @@ function highlightText(text, { maxCharacters = Infinity }) { // Exceeded characters limit const { composerCharacterCount } = states; - let leftoverHTML = ''; if (composerCharacterCount > maxCharacters) { - // NOTE: runes2 substring considers surrogate pairs - // const leftoverCount = composerCharacterCount - maxCharacters; // Highlight exceeded characters - leftoverHTML = - '' + - // html.slice(-leftoverCount) + - substring(html, maxCharacters) + - ''; - // html = html.slice(0, -leftoverCount); - html = substring(html, 0, maxCharacters); - return html + leftoverHTML; + let withinLimitHTML = '', + exceedLimitHTML = ''; + const htmlSegments = segmenter.segment(html); + for (const { segment, index } of htmlSegments) { + if (index < maxCharacters) { + withinLimitHTML += segment; + } else { + exceedLimitHTML += segment; + } + } + if (exceedLimitHTML) { + exceedLimitHTML = + '' + + exceedLimitHTML + + ''; + } + return withinLimitHTML + exceedLimitHTML; } return html diff --git a/src/main.jsx b/src/main.jsx index 9c21e63f..19a84c69 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,6 +2,9 @@ import './index.css'; import './cloak-mode.css'; +// Polyfill needed for Firefox < 122 +// https://bugzilla.mozilla.org/show_bug.cgi?id=1423593 +import '@formatjs/intl-segmenter/polyfill'; import { render } from 'preact'; import { HashRouter } from 'react-router-dom';