From c854c7f9d2167e1e38baf90d17afa0ee1f77033b Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:58:47 +1100 Subject: [PATCH] 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 --- src/app/components/editor/output.ts | 19 ++++++------ src/app/organisms/room/RoomTimeline.tsx | 2 -- .../organisms/room/message/ImageContent.tsx | 8 ++++- .../organisms/room/message/VideoContent.tsx | 8 ++++- src/app/{utils => plugins}/markdown.ts | 0 src/app/plugins/react-custom-html-parser.tsx | 30 ++++++++----------- src/app/utils/findAndReplace.ts | 28 +++++++++++++++++ 7 files changed, 65 insertions(+), 30 deletions(-) rename src/app/{utils => plugins}/markdown.ts (100%) create mode 100644 src/app/utils/findAndReplace.ts diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index 9a03604..53ee6dd 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -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[], diff --git a/src/app/organisms/room/RoomTimeline.tsx b/src/app/organisms/room/RoomTimeline.tsx index 9603209..0c74de5 100644 --- a/src/app/organisms/room/RoomTimeline.tsx +++ b/src/app/organisms/room/RoomTimeline.tsx @@ -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); } diff --git a/src/app/organisms/room/message/ImageContent.tsx b/src/app/organisms/room/message/ImageContent.tsx index c8b32cc..6e28802 100644 --- a/src/app/organisms/room/message/ImageContent.tsx +++ b/src/app/organisms/room/message/ImageContent.tsx @@ -98,7 +98,13 @@ export const ImageContent = as<'div', ImageContentProps>( )} {typeof blurHash === 'string' && !load && ( - + )} {!autoPlay && srcState.status === AsyncStatus.Idle && ( diff --git a/src/app/organisms/room/message/VideoContent.tsx b/src/app/organisms/room/message/VideoContent.tsx index 107d5f9..8b3bd34 100644 --- a/src/app/organisms/room/message/VideoContent.tsx +++ b/src/app/organisms/room/message/VideoContent.tsx @@ -88,7 +88,13 @@ export const VideoContent = as<'div', VideoContentProps>( return ( {typeof blurHash === 'string' && !load && ( - + )} {thumbSrcState.status === AsyncStatus.Success && !load && ( diff --git a/src/app/utils/markdown.ts b/src/app/plugins/markdown.ts similarity index 100% rename from src/app/utils/markdown.ts rename to src/app/plugins/markdown.ts diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx index 928419c..ee41687 100644 --- a/src/app/plugins/react-custom-html-parser.tsx +++ b/src/app/plugins/react-custom-html-parser.tsx @@ -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, - - - {emoji} + EMOJI_REG_G, + (match, pushIndex) => ( + + + {match[0]} + - + ), + (txt) => txt ); -}; export const emojifyAndLinkify = (text: string, linkify?: boolean) => { - const emojifyJSX = stringToEmojifyJSX(text); + const emojifyJSX = textToEmojifyJSX(text); if (linkify) { return {emojifyJSX}; diff --git a/src/app/utils/findAndReplace.ts b/src/app/utils/findAndReplace.ts new file mode 100644 index 0000000..a4bd1ed --- /dev/null +++ b/src/app/utils/findAndReplace.ts @@ -0,0 +1,28 @@ +export type ReplaceCallback = ( + match: RegExpExecArray | RegExpMatchArray, + pushIndex: number +) => R; +export type ConvertPartCallback = (text: string, pushIndex: number) => R; + +export const findAndReplace = ( + text: string, + regex: RegExp, + replace: ReplaceCallback, + convertPart: ConvertPartCallback +): Array => { + const result: Array = []; + 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; +};