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:
parent
3713125f57
commit
c854c7f9d2
7 changed files with 65 additions and 30 deletions
|
@ -3,7 +3,8 @@ import { Descendant, Text } from 'slate';
|
|||
import { sanitizeText } from '../../utils/sanitize';
|
||||
import { BlockType } from './types';
|
||||
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 = {
|
||||
allowTextFormatting?: boolean;
|
||||
|
@ -69,14 +70,14 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
|
|||
}
|
||||
};
|
||||
|
||||
const HTML_TAG_REG = /<([\w-]+)(?: [^>]*)?(?:(?:\/>)|(?:>.*?<\/\1>))/;
|
||||
const ignoreHTMLParseInlineMD = (text: string): string => {
|
||||
if (text === '') return text;
|
||||
const match = text.match(HTML_TAG_REG);
|
||||
if (!match) return parseInlineMD(text);
|
||||
const [matchedTxt] = match;
|
||||
return replaceMatch((txt) => [ignoreHTMLParseInlineMD(txt)], text, match, matchedTxt).join('');
|
||||
};
|
||||
const HTML_TAG_REG_G = /<([\w-]+)(?: [^>]*)?(?:(?:\/>)|(?:>.*?<\/\1>))/g;
|
||||
const ignoreHTMLParseInlineMD = (text: string): string =>
|
||||
findAndReplace(
|
||||
text,
|
||||
HTML_TAG_REG_G,
|
||||
(match) => match[0],
|
||||
(txt) => parseInlineMD(txt)
|
||||
).join('');
|
||||
|
||||
export const toMatrixCustomHTML = (
|
||||
node: Descendant | Descendant[],
|
||||
|
|
|
@ -345,7 +345,6 @@ const useTimelinePagination = (
|
|||
|
||||
return async (backwards: boolean) => {
|
||||
if (fetching) return;
|
||||
const targetTimeline = timelineRef.current;
|
||||
const { linkedTimelines: lTimelines } = timelineRef.current;
|
||||
const timelinesEventsCount = lTimelines.map(timelineToEventsCount);
|
||||
|
||||
|
@ -385,7 +384,6 @@ const useTimelinePagination = (
|
|||
}
|
||||
|
||||
fetching = false;
|
||||
if (targetTimeline !== timelineRef.current) return;
|
||||
if (alive()) {
|
||||
recalibratePagination(lTimelines, timelinesEventsCount, backwards);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,13 @@ export const ImageContent = as<'div', ImageContentProps>(
|
|||
</Overlay>
|
||||
)}
|
||||
{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 && (
|
||||
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
||||
|
|
|
@ -88,7 +88,13 @@ export const VideoContent = as<'div', VideoContentProps>(
|
|||
return (
|
||||
<Box className={classNames(css.RelativeBase, className)} {...props} ref={ref}>
|
||||
{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 && (
|
||||
<Box className={css.AbsoluteContainer} alignItems="Center" justifyContent="Center">
|
||||
|
|
|
@ -18,11 +18,11 @@ import { getMxIdLocalPart, getRoomWithCanonicalAlias } from '../utils/matrix';
|
|||
import { getMemberDisplayName } from '../utils/room';
|
||||
import { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex';
|
||||
import { getHexcodeForEmoji, getShortcodeFor } from './emoji';
|
||||
import { replaceMatch } from '../utils/markdown';
|
||||
import { findAndReplace } from '../utils/findAndReplace';
|
||||
|
||||
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 = {
|
||||
attributes: {
|
||||
|
@ -35,26 +35,22 @@ export const LINKIFY_OPTS: LinkifyOpts = {
|
|||
ignoreTags: ['span'],
|
||||
};
|
||||
|
||||
const stringToEmojifyJSX = (text: string): (string | JSX.Element)[] => {
|
||||
const match = text.match(EMOJI_REG);
|
||||
if (!match) return [text];
|
||||
|
||||
const [emoji] = match;
|
||||
|
||||
return replaceMatch(
|
||||
stringToEmojifyJSX,
|
||||
const textToEmojifyJSX = (text: string): (string | JSX.Element)[] =>
|
||||
findAndReplace(
|
||||
text,
|
||||
match,
|
||||
<span className={css.EmoticonBase}>
|
||||
<span className={css.Emoticon()} title={getShortcodeFor(getHexcodeForEmoji(emoji))}>
|
||||
{emoji}
|
||||
EMOJI_REG_G,
|
||||
(match, pushIndex) => (
|
||||
<span key={pushIndex} className={css.EmoticonBase}>
|
||||
<span className={css.Emoticon()} title={getShortcodeFor(getHexcodeForEmoji(match[0]))}>
|
||||
{match[0]}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
),
|
||||
(txt) => txt
|
||||
);
|
||||
};
|
||||
|
||||
export const emojifyAndLinkify = (text: string, linkify?: boolean) => {
|
||||
const emojifyJSX = stringToEmojifyJSX(text);
|
||||
const emojifyJSX = textToEmojifyJSX(text);
|
||||
|
||||
if (linkify) {
|
||||
return <Linkify options={LINKIFY_OPTS}>{emojifyJSX}</Linkify>;
|
||||
|
|
28
src/app/utils/findAndReplace.ts
Normal file
28
src/app/utils/findAndReplace.ts
Normal 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;
|
||||
};
|
Loading…
Reference in a new issue