Timeline Perf Improvement (#1521)

* emojify msg txt find&replace instead of recursion

* move findAndReplace func in its own file

* improve find and replace

* move markdown file to plugins

* make find and replace work without g flag regex

* fix pagination stop on msg arrive

* render blurhash in small size
This commit is contained in:
Ajay Bura 2023-10-30 16:58:47 +11:00 committed by GitHub
parent 3713125f57
commit c854c7f9d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 65 additions and 30 deletions

View file

@ -3,7 +3,8 @@ import { Descendant, Text } from 'slate';
import { sanitizeText } from '../../utils/sanitize'; import { sanitizeText } from '../../utils/sanitize';
import { BlockType } from './types'; import { BlockType } from './types';
import { CustomElement } from './slate'; import { CustomElement } from './slate';
import { parseBlockMD, parseInlineMD, replaceMatch } from '../../utils/markdown'; import { parseBlockMD, parseInlineMD } from '../../plugins/markdown';
import { findAndReplace } from '../../utils/findAndReplace';
export type OutputOptions = { export type OutputOptions = {
allowTextFormatting?: boolean; allowTextFormatting?: boolean;
@ -69,14 +70,14 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
} }
}; };
const HTML_TAG_REG = /<([\w-]+)(?: [^>]*)?(?:(?:\/>)|(?:>.*?<\/\1>))/; const HTML_TAG_REG_G = /<([\w-]+)(?: [^>]*)?(?:(?:\/>)|(?:>.*?<\/\1>))/g;
const ignoreHTMLParseInlineMD = (text: string): string => { const ignoreHTMLParseInlineMD = (text: string): string =>
if (text === '') return text; findAndReplace(
const match = text.match(HTML_TAG_REG); text,
if (!match) return parseInlineMD(text); HTML_TAG_REG_G,
const [matchedTxt] = match; (match) => match[0],
return replaceMatch((txt) => [ignoreHTMLParseInlineMD(txt)], text, match, matchedTxt).join(''); (txt) => parseInlineMD(txt)
}; ).join('');
export const toMatrixCustomHTML = ( export const toMatrixCustomHTML = (
node: Descendant | Descendant[], node: Descendant | Descendant[],

View file

@ -345,7 +345,6 @@ const useTimelinePagination = (
return async (backwards: boolean) => { return async (backwards: boolean) => {
if (fetching) return; if (fetching) return;
const targetTimeline = timelineRef.current;
const { linkedTimelines: lTimelines } = timelineRef.current; const { linkedTimelines: lTimelines } = timelineRef.current;
const timelinesEventsCount = lTimelines.map(timelineToEventsCount); const timelinesEventsCount = lTimelines.map(timelineToEventsCount);
@ -385,7 +384,6 @@ const useTimelinePagination = (
} }
fetching = false; fetching = false;
if (targetTimeline !== timelineRef.current) return;
if (alive()) { if (alive()) {
recalibratePagination(lTimelines, timelinesEventsCount, backwards); recalibratePagination(lTimelines, timelinesEventsCount, backwards);
} }

View file

@ -98,7 +98,13 @@ export const ImageContent = as<'div', ImageContentProps>(
</Overlay> </Overlay>
)} )}
{typeof blurHash === 'string' && !load && ( {typeof blurHash === 'string' && !load && (
<BlurhashCanvas style={{ width: '100%', height: '100%' }} hash={blurHash} punch={1} /> <BlurhashCanvas
style={{ width: '100%', height: '100%' }}
width={32}
height={32}
hash={blurHash}
punch={1}
/>
)} )}
{!autoPlay && srcState.status === AsyncStatus.Idle && ( {!autoPlay && srcState.status === AsyncStatus.Idle && (
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center"> <Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">

View file

@ -88,7 +88,13 @@ export const VideoContent = as<'div', VideoContentProps>(
return ( return (
<Box className={classNames(css.RelativeBase, className)} {...props} ref={ref}> <Box className={classNames(css.RelativeBase, className)} {...props} ref={ref}>
{typeof blurHash === 'string' && !load && ( {typeof blurHash === 'string' && !load && (
<BlurhashCanvas style={{ width: '100%', height: '100%' }} hash={blurHash} punch={1} /> <BlurhashCanvas
style={{ width: '100%', height: '100%' }}
width={32}
height={32}
hash={blurHash}
punch={1}
/>
)} )}
{thumbSrcState.status === AsyncStatus.Success && !load && ( {thumbSrcState.status === AsyncStatus.Success && !load && (
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center"> <Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">

View file

@ -18,11 +18,11 @@ import { getMxIdLocalPart, getRoomWithCanonicalAlias } from '../utils/matrix';
import { getMemberDisplayName } from '../utils/room'; import { getMemberDisplayName } from '../utils/room';
import { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex'; import { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex';
import { getHexcodeForEmoji, getShortcodeFor } from './emoji'; import { getHexcodeForEmoji, getShortcodeFor } from './emoji';
import { replaceMatch } from '../utils/markdown'; import { findAndReplace } from '../utils/findAndReplace';
const ReactPrism = lazy(() => import('./react-prism/ReactPrism')); const ReactPrism = lazy(() => import('./react-prism/ReactPrism'));
const EMOJI_REG = new RegExp(`${URL_NEG_LB}(${EMOJI_PATTERN})`); const EMOJI_REG_G = new RegExp(`${URL_NEG_LB}(${EMOJI_PATTERN})`, 'g');
export const LINKIFY_OPTS: LinkifyOpts = { export const LINKIFY_OPTS: LinkifyOpts = {
attributes: { attributes: {
@ -35,26 +35,22 @@ export const LINKIFY_OPTS: LinkifyOpts = {
ignoreTags: ['span'], ignoreTags: ['span'],
}; };
const stringToEmojifyJSX = (text: string): (string | JSX.Element)[] => { const textToEmojifyJSX = (text: string): (string | JSX.Element)[] =>
const match = text.match(EMOJI_REG); findAndReplace(
if (!match) return [text];
const [emoji] = match;
return replaceMatch(
stringToEmojifyJSX,
text, text,
match, EMOJI_REG_G,
<span className={css.EmoticonBase}> (match, pushIndex) => (
<span className={css.Emoticon()} title={getShortcodeFor(getHexcodeForEmoji(emoji))}> <span key={pushIndex} className={css.EmoticonBase}>
{emoji} <span className={css.Emoticon()} title={getShortcodeFor(getHexcodeForEmoji(match[0]))}>
{match[0]}
</span> </span>
</span> </span>
),
(txt) => txt
); );
};
export const emojifyAndLinkify = (text: string, linkify?: boolean) => { export const emojifyAndLinkify = (text: string, linkify?: boolean) => {
const emojifyJSX = stringToEmojifyJSX(text); const emojifyJSX = textToEmojifyJSX(text);
if (linkify) { if (linkify) {
return <Linkify options={LINKIFY_OPTS}>{emojifyJSX}</Linkify>; return <Linkify options={LINKIFY_OPTS}>{emojifyJSX}</Linkify>;

View file

@ -0,0 +1,28 @@
export type ReplaceCallback<R> = (
match: RegExpExecArray | RegExpMatchArray,
pushIndex: number
) => R;
export type ConvertPartCallback<R> = (text: string, pushIndex: number) => R;
export const findAndReplace = <ReplaceReturnType, ConvertReturnType>(
text: string,
regex: RegExp,
replace: ReplaceCallback<ReplaceReturnType>,
convertPart: ConvertPartCallback<ConvertReturnType>
): Array<ReplaceReturnType | ConvertReturnType> => {
const result: Array<ReplaceReturnType | ConvertReturnType> = [];
let lastEnd = 0;
let match: RegExpExecArray | RegExpMatchArray | null = regex.exec(text);
while (match !== null && typeof match.index === 'number') {
result.push(convertPart(text.slice(lastEnd, match.index), result.length));
result.push(replace(match, result.length));
lastEnd = match.index + match[0].length;
if (regex.global) match = regex.exec(text);
}
result.push(convertPart(text.slice(lastEnd), result.length));
return result;
};