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;
+};