-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new Web rich text composer #2557
base: main
Are you sure you want to change the base?
new Web rich text composer #2557
Conversation
the performance is somewhat awful, at least on Firefox. I'm thinking there's two things going on here:
|
patch for skipping React diffing by constructing HTML strings, it seems to be making some difference but I can't really tell how well it's actually working, need a proper bench setup. diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx
index c164938..b7b24bc 100644
--- a/src/view/com/composer/text-input/TextInput.web.tsx
+++ b/src/view/com/composer/text-input/TextInput.web.tsx
@@ -137,30 +137,24 @@ export const TextInput = React.forwardRef<TextInputRef, TextInputProps>(
}, [richtext, cursor, overlayRef, setSuggestion])
const textOverlay = React.useMemo(() => {
+ let html = ''
+
+ for (const segment of richtext.segments()) {
+ const isLink = segment.facet
+ ? !AppBskyRichtextFacet.isTag(segment.facet.features[0])
+ : false
+
+ const klass =
+ `rt-segment ` + (!isLink ? `rt-segment-text` : `rt-segment-link`)
+ const text = escape(segment.text, false) || '​'
+
+ html += `<span class="${klass}">${text}</span>`
+ }
+
return (
- <div className="rt-overlay-inner">
- {Array.from(richtext.segments(), (segment, index) => {
- const isLink = segment.facet
- ? !AppBskyRichtextFacet.isTag(segment.facet.features[0])
- : false
-
- return (
- <span
- key={index}
- className={
- `rt-segment ` +
- (!isLink ? `rt-segment-text` : `rt-segment-link`)
- }>
- {
- // We need React to commit a text node to DOM so we can select
- // it for `getCursorPosition` above, without it, we can't open
- // the emoji picker on an empty input.
- segment.text || '\u200b'
- }
- </span>
- )
- })}
- </div>
+ <div
+ dangerouslySetInnerHTML={{__html: html}}
+ className="rt-overlay-inner"></div>
)
}, [richtext])
@@ -352,3 +346,19 @@ const findNodePosition = (
return
}
+
+const escape = (str: string, attr: boolean) => {
+ let escaped = ''
+ let last = 0
+
+ for (let idx = 0, len = str.length; idx < len; idx++) {
+ const char = str.charCodeAt(idx)
+
+ if (char === 38 || (attr ? char === 34 : char === 60)) {
+ escaped += str.substring(last, idx) + ('&#' + char + ';')
+ last = idx + 1
+ }
+ }
+
+ return escaped + str.substring(last)
+} |
Intriguing. I'll definitely want to play with this at some point. |
234ec79
to
e2a9ae3
Compare
Could you add some screenshots or videos of what it would look like? |
It's intended to look exactly the same as before, so there is no screenshot or video. |
this is an experimental pull request that introduces a new RichText input for web, with the goal of aligning with what's currently on native. This has the benefit of side-stepping any headaches that currently comes with TipTap and other WYSIWYG editors like Lexical, plus, makes a decent dent on reducing bundle size.
this RichText input works pretty similarly to how it is on native, it does this by making the input's text color transparent (and fixing up any adjacent styling like caret, placeholder and selection text), and overlaying our own rendered text below it (such that your cursor will always land on the input)
I know that Paul has said that there might be plans to move away from being entirely driven by syntax, but this was just a fun challenge I came up on a whim, feel free to close this PR if necessary :)
TODO