mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-02-02 14:16:39 +01:00
Add experimental highlighting to composer textarea
This commit is contained in:
parent
8aaba24d1f
commit
017b138d4b
4 changed files with 128 additions and 25 deletions
67
package-lock.json
generated
67
package-lock.json
generated
|
@ -25,6 +25,7 @@
|
||||||
"react-hotkeys-hook": "~4.3.7",
|
"react-hotkeys-hook": "~4.3.7",
|
||||||
"react-intersection-observer": "~9.4.2",
|
"react-intersection-observer": "~9.4.2",
|
||||||
"react-router-dom": "6.6.2",
|
"react-router-dom": "6.6.2",
|
||||||
|
"rich-textarea": "~0.19.5",
|
||||||
"string-length": "~5.0.1",
|
"string-length": "~5.0.1",
|
||||||
"swiped-events": "~1.1.7",
|
"swiped-events": "~1.1.7",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
|
@ -3200,6 +3201,20 @@
|
||||||
"@babel/core": "^7.12.10"
|
"@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": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
@ -5733,6 +5748,14 @@
|
||||||
"safe-buffer": "^5.1.0"
|
"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": {
|
"node_modules/react": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
|
@ -5957,6 +5980,18 @@
|
||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/rollup": {
|
||||||
"version": "3.12.1",
|
"version": "3.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
||||||
|
@ -9146,6 +9181,21 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"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": {
|
"balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
@ -10892,6 +10942,14 @@
|
||||||
"safe-buffer": "^5.1.0"
|
"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": {
|
"react": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
|
@ -11057,6 +11115,15 @@
|
||||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||||
"dev": true
|
"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": {
|
"rollup": {
|
||||||
"version": "3.12.1",
|
"version": "3.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"react-hotkeys-hook": "~4.3.7",
|
"react-hotkeys-hook": "~4.3.7",
|
||||||
"react-intersection-observer": "~9.4.2",
|
"react-intersection-observer": "~9.4.2",
|
||||||
"react-router-dom": "6.6.2",
|
"react-router-dom": "6.6.2",
|
||||||
|
"rich-textarea": "~0.19.5",
|
||||||
"string-length": "~5.0.1",
|
"string-length": "~5.0.1",
|
||||||
"swiped-events": "~1.1.7",
|
"swiped-events": "~1.1.7",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
|
|
|
@ -466,6 +466,12 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compose-highlight,
|
||||||
|
.compose-link {
|
||||||
|
color: var(--link-color);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 50em) {
|
@media (min-width: 50em) {
|
||||||
#media-sheet main {
|
#media-sheet main {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import equal from 'fast-deep-equal';
|
||||||
import { forwardRef } from 'preact/compat';
|
import { forwardRef } from 'preact/compat';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { createRegexRenderer, RichTextarea } from 'rich-textarea';
|
||||||
import stringLength from 'string-length';
|
import stringLength from 'string-length';
|
||||||
import { uid } from 'uid/single';
|
import { uid } from 'uid/single';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
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 (
|
||||||
|
<>
|
||||||
|
<mark class="compose-highlight">{first}</mark>
|
||||||
|
{rest}
|
||||||
|
{last && <mark class="compose-highlight">{last}</mark>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[urlRegexObj, (props) => <mark class="compose-link" {...props} />],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const RICH_TEXTAREA = true;
|
||||||
|
|
||||||
const Textarea = forwardRef((props, ref) => {
|
const Textarea = forwardRef((props, ref) => {
|
||||||
const { masto } = api();
|
const { masto } = api();
|
||||||
const [text, setText] = useState(ref.current?.value || '');
|
const [text, setText] = useState(ref.current?.value || '');
|
||||||
|
@ -1216,33 +1239,39 @@ const Textarea = forwardRef((props, ref) => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
const fieldProps = {
|
||||||
<text-expander ref={textExpanderRef} keys="@ # :">
|
autoCapitalize: 'sentences',
|
||||||
<textarea
|
autoComplete: 'on',
|
||||||
autoCapitalize="sentences"
|
autoCorrect: 'on',
|
||||||
autoComplete="on"
|
spellCheck: 'true',
|
||||||
autoCorrect="on"
|
dir: 'auto',
|
||||||
spellCheck="true"
|
rows: '6',
|
||||||
dir="auto"
|
cols: '50',
|
||||||
rows="6"
|
...textareaProps,
|
||||||
cols="50"
|
ref,
|
||||||
{...textareaProps}
|
name: 'status',
|
||||||
ref={ref}
|
value: text,
|
||||||
name="status"
|
onInput: (e) => {
|
||||||
value={text}
|
|
||||||
onInput={(e) => {
|
|
||||||
const { scrollHeight, offsetHeight, clientHeight, value } = e.target;
|
const { scrollHeight, offsetHeight, clientHeight, value } = e.target;
|
||||||
setText(value);
|
setText(value);
|
||||||
const offset = offsetHeight - clientHeight;
|
const offset = offsetHeight - clientHeight;
|
||||||
e.target.style.height = value ? scrollHeight + offset + 'px' : null;
|
e.target.style.height = value ? scrollHeight + offset + 'px' : null;
|
||||||
props.onInput?.(e);
|
props.onInput?.(e);
|
||||||
}}
|
},
|
||||||
style={{
|
style: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '4em',
|
height: '4em',
|
||||||
'--text-weight': (1 + charCount / 140).toFixed(1) || 1,
|
'--text-weight': (1 + charCount / 140).toFixed(1) || 1,
|
||||||
}}
|
},
|
||||||
/>
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<text-expander ref={textExpanderRef} keys="@ # :">
|
||||||
|
{RICH_TEXTAREA ? (
|
||||||
|
<RichTextarea {...fieldProps}>{highlightRenderer}</RichTextarea>
|
||||||
|
) : (
|
||||||
|
<textarea {...fieldProps} />
|
||||||
|
)}
|
||||||
</text-expander>
|
</text-expander>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue