From 017b138d4ba3964f6cccd67ed0ac6b7c773114ac Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 27 Feb 2023 11:20:50 +0800 Subject: [PATCH] Add experimental highlighting to composer textarea --- package-lock.json | 67 ++++++++++++++++++++++++++++++++ package.json | 1 + src/components/compose.css | 6 +++ src/components/compose.jsx | 79 ++++++++++++++++++++++++++------------ 4 files changed, 128 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3adf415..51efccfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "react-hotkeys-hook": "~4.3.7", "react-intersection-observer": "~9.4.2", "react-router-dom": "6.6.2", + "rich-textarea": "~0.19.5", "string-length": "~5.0.1", "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", @@ -3200,6 +3201,20 @@ "@babel/core": "^7.12.10" } }, + "node_modules/babel-runtime": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-4.7.4.tgz", + "integrity": "sha512-0gnK56hiHkbUCwqtaiK15MAsnNxI8T7aOBYUyoAYyNxWs86ExL0NNTDhn0eDO/AIhJf0oXMgV5+1wfSLQ/FMyw==", + "dependencies": { + "core-js": "^0.6.1" + } + }, + "node_modules/babel-runtime/node_modules/core-js": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-0.6.1.tgz", + "integrity": "sha512-ANdRS9QdyvvVCqMD7gvDhgI5T+/t5FELQB1ZLN94oCDXTJLwt4Q1o6Nbc1wnVrhl6QPyJ5mv0k8hMCdAFLNbLg==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js." + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5733,6 +5748,14 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-at-index": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/range-at-index/-/range-at-index-1.0.4.tgz", + "integrity": "sha512-Aob2FK5jL0cCKvIzA0tuRrdsSXTvxLY5p9dr7GrLY31NwKtUk5EhgHwcKi0kbUacJukGVRglLi6MEqBHB4NHMA==", + "dependencies": { + "babel-runtime": "4.7.4" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -5957,6 +5980,18 @@ "node": ">=0.10.0" } }, + "node_modules/rich-textarea": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/rich-textarea/-/rich-textarea-0.19.5.tgz", + "integrity": "sha512-jGzE84BUs0VKLTEIdJaQcToQjRxYO1aNRnPNtnGupkwKWl59dCpp5EWWi5qxU1JMjPtKOBFSSynYG0srr71TJw==", + "dependencies": { + "range-at-index": "^1.0.4", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/rollup": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz", @@ -9146,6 +9181,21 @@ "dev": true, "requires": {} }, + "babel-runtime": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-4.7.4.tgz", + "integrity": "sha512-0gnK56hiHkbUCwqtaiK15MAsnNxI8T7aOBYUyoAYyNxWs86ExL0NNTDhn0eDO/AIhJf0oXMgV5+1wfSLQ/FMyw==", + "requires": { + "core-js": "^0.6.1" + }, + "dependencies": { + "core-js": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-0.6.1.tgz", + "integrity": "sha512-ANdRS9QdyvvVCqMD7gvDhgI5T+/t5FELQB1ZLN94oCDXTJLwt4Q1o6Nbc1wnVrhl6QPyJ5mv0k8hMCdAFLNbLg==" + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -10892,6 +10942,14 @@ "safe-buffer": "^5.1.0" } }, + "range-at-index": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/range-at-index/-/range-at-index-1.0.4.tgz", + "integrity": "sha512-Aob2FK5jL0cCKvIzA0tuRrdsSXTvxLY5p9dr7GrLY31NwKtUk5EhgHwcKi0kbUacJukGVRglLi6MEqBHB4NHMA==", + "requires": { + "babel-runtime": "4.7.4" + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -11057,6 +11115,15 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rich-textarea": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/rich-textarea/-/rich-textarea-0.19.5.tgz", + "integrity": "sha512-jGzE84BUs0VKLTEIdJaQcToQjRxYO1aNRnPNtnGupkwKWl59dCpp5EWWi5qxU1JMjPtKOBFSSynYG0srr71TJw==", + "requires": { + "range-at-index": "^1.0.4", + "use-sync-external-store": "^1.2.0" + } + }, "rollup": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz", diff --git a/package.json b/package.json index e457306e..fe84c7bc 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "react-hotkeys-hook": "~4.3.7", "react-intersection-observer": "~9.4.2", "react-router-dom": "6.6.2", + "rich-textarea": "~0.19.5", "string-length": "~5.0.1", "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", diff --git a/src/components/compose.css b/src/components/compose.css index 8c0d0956..fff6aa54 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -466,6 +466,12 @@ vertical-align: middle; } +.compose-highlight, +.compose-link { + color: var(--link-color); + background-color: transparent; +} + @media (min-width: 50em) { #media-sheet main { flex-direction: row; diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 9fdd23bd..191fd364 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, useRef, useState } from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; +import { createRegexRenderer, RichTextarea } from 'rich-textarea'; import stringLength from 'string-length'; import { uid } from 'uid/single'; import { useDebouncedCallback } from 'use-debounce'; @@ -1038,6 +1039,28 @@ function Compose({ ); } +const HIGHLIGHT_REG = /(#|@|:)[\p{L}\p{M}\p{N}\p{Pc}_@\.]+:?/gu; +const highlightRenderer = createRegexRenderer([ + [ + HIGHLIGHT_REG, + ({ value }) => { + const first = value[0]; + const last = value[value.length - 1] === ':' ? ':' : ''; + const rest = value.slice(1, last ? -1 : undefined); + return ( + <> + {first} + {rest} + {last && {last}} + + ); + }, + ], + [urlRegexObj, (props) => ], +]); + +const RICH_TEXTAREA = true; + const Textarea = forwardRef((props, ref) => { const { masto } = api(); const [text, setText] = useState(ref.current?.value || ''); @@ -1216,33 +1239,39 @@ const Textarea = forwardRef((props, ref) => { }; }, []); + const fieldProps = { + autoCapitalize: 'sentences', + autoComplete: 'on', + autoCorrect: 'on', + spellCheck: 'true', + dir: 'auto', + rows: '6', + cols: '50', + ...textareaProps, + ref, + name: 'status', + value: text, + onInput: (e) => { + const { scrollHeight, offsetHeight, clientHeight, value } = e.target; + setText(value); + const offset = offsetHeight - clientHeight; + e.target.style.height = value ? scrollHeight + offset + 'px' : null; + props.onInput?.(e); + }, + style: { + width: '100%', + height: '4em', + '--text-weight': (1 + charCount / 140).toFixed(1) || 1, + }, + }; + return ( -