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 { 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[],
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
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