From 3b2ff786195c1364b51ea6f4c1ee84846e7708ae Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 11:30:15 +0530
Subject: [PATCH 1/9] add inline markdown in editor
---
.vscode/settings.json | 2 +-
src/app/components/editor/output.ts | 19 ++--
src/app/organisms/room/RoomInput.tsx | 5 +-
src/app/organisms/settings/Settings.jsx | 11 +--
src/app/utils/markdown.ts | 115 ++++++++++++++++++++++++
5 files changed, 139 insertions(+), 13 deletions(-)
create mode 100644 src/app/utils/markdown.ts
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8272ea1e42..88a83d6e95 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,5 @@
{
- "editor.formatOnSave": true,
+ "editor.formatOnSave": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "node_modules/typescript/lib"
}
diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts
index 5d0443fa84..cec49aaff5 100644
--- a/src/app/components/editor/output.ts
+++ b/src/app/components/editor/output.ts
@@ -2,8 +2,9 @@ import { Descendant, Text } from 'slate';
import { sanitizeText } from '../../utils/sanitize';
import { BlockType } from './Elements';
import { CustomElement, FormattedText } from './slate';
+import { parseInlineMD } from '../../utils/markdown';
-const textToCustomHtml = (node: FormattedText): string => {
+const textToCustomHtml = (node: FormattedText, allowMarkdown?: boolean): string => {
let string = sanitizeText(node.text);
if (node.bold) string = `${string}`;
if (node.italic) string = `${string}`;
@@ -11,6 +12,11 @@ const textToCustomHtml = (node: FormattedText): string => {
if (node.strikeThrough) string = `${string}`;
if (node.code) string = `${string}
`;
if (node.spoiler) string = `${string}`;
+
+ if (allowMarkdown && string === sanitizeText(node.text)) {
+ string = parseInlineMD(string);
+ }
+
return string;
};
@@ -47,11 +53,14 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
}
};
-export const toMatrixCustomHTML = (node: Descendant | Descendant[]): string => {
- if (Array.isArray(node)) return node.map((n) => toMatrixCustomHTML(n)).join('');
- if (Text.isText(node)) return textToCustomHtml(node);
+export const toMatrixCustomHTML = (
+ node: Descendant | Descendant[],
+ allowMarkdown?: boolean
+): string => {
+ if (Array.isArray(node)) return node.map((n) => toMatrixCustomHTML(n, allowMarkdown)).join('');
+ if (Text.isText(node)) return textToCustomHtml(node, allowMarkdown);
- const children = node.children.map((n) => toMatrixCustomHTML(n)).join('');
+ const children = node.children.map((n) => toMatrixCustomHTML(n, allowMarkdown)).join('');
return elementToCustomHtml(node, children);
};
diff --git a/src/app/organisms/room/RoomInput.tsx b/src/app/organisms/room/RoomInput.tsx
index efef03a270..06ff8ce51c 100644
--- a/src/app/organisms/room/RoomInput.tsx
+++ b/src/app/organisms/room/RoomInput.tsx
@@ -108,6 +108,7 @@ export const RoomInput = forwardRef(
({ editor, roomViewRef, roomId }, ref) => {
const mx = useMatrixClient();
const room = mx.getRoom(roomId);
+ const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const [msgDraft, setMsgDraft] = useAtom(roomIdToMsgDraftAtomFamily(roomId));
const [replyDraft, setReplyDraft] = useAtom(roomIdToReplyDraftAtomFamily(roomId));
@@ -251,7 +252,7 @@ export const RoomInput = forwardRef(
uploadBoardHandlers.current?.handleSend();
const plainText = toPlainText(editor.children).trim();
- const customHtml = trimCustomHtml(toMatrixCustomHTML(editor.children));
+ const customHtml = trimCustomHtml(toMatrixCustomHTML(editor.children, isMarkdown));
if (plainText === '') return;
@@ -288,7 +289,7 @@ export const RoomInput = forwardRef(
resetEditorHistory(editor);
setReplyDraft();
sendTypingStatus(false);
- }, [mx, roomId, editor, replyDraft, sendTypingStatus, setReplyDraft]);
+ }, [mx, roomId, editor, replyDraft, sendTypingStatus, setReplyDraft, isMarkdown]);
const handleKeyDown: KeyboardEventHandler = useCallback(
(evt) => {
diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx
index fef158675d..bd9ce0441c 100644
--- a/src/app/organisms/settings/Settings.jsx
+++ b/src/app/organisms/settings/Settings.jsx
@@ -6,7 +6,7 @@ import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings';
import navigation from '../../../client/state/navigation';
import {
- toggleSystemTheme, toggleMarkdown,
+ toggleSystemTheme,
toggleNotifications, toggleNotificationSounds,
} from '../../../client/action/settings';
import { usePermission } from '../../hooks/usePermission';
@@ -52,6 +52,7 @@ function AppearanceSection() {
const [messageLayout, setMessageLayout] = useSetting(settingsAtom, 'messageLayout');
const [messageSpacing, setMessageSpacing] = useSetting(settingsAtom, 'messageSpacing');
const [useSystemEmoji, setUseSystemEmoji] = useSetting(settingsAtom, 'useSystemEmoji');
+ const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const [hideMembershipEvents, setHideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents');
const [hideNickAvatarEvents, setHideNickAvatarEvents] = useSetting(settingsAtom, 'hideNickAvatarEvents');
const [mediaAutoLoad, setMediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
@@ -138,14 +139,14 @@ function AppearanceSection() {
}
/>
{ toggleMarkdown(); updateState({}); }}
+ isActive={isMarkdown}
+ onToggle={() => setIsMarkdown(!isMarkdown) }
/>
)}
- content={Format messages with markdown syntax before sending.}
+ content={Format messages with inline markdown syntax before sending.}
/>
+ text.slice(0, match.index);
+const afterMatch = (text: string, match: RegExpMatchArray | RegExpExecArray): string =>
+ text.slice((match.index ?? 0) + match[0].length);
+
+export const parseInlineMD = (text: string): string => {
+ const linkMatch = text.match(LINK_REG_1);
+ if (linkMatch) {
+ const [, g1, g2] = linkMatch;
+ const before = parseInlineMD(beforeMatch(text, linkMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, linkMatch));
+
+ return `${before}${child}${after}`;
+ }
+
+ const boldMatch = text.match(BOLD_REG_1);
+ if (boldMatch) {
+ const [, g1] = boldMatch;
+ const before = parseInlineMD(beforeMatch(text, boldMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, boldMatch));
+
+ return `${before}${child}${after}`;
+ }
+
+ const underlineMatch = text.match(UNDERLINE_REG_1);
+ if (underlineMatch) {
+ const [, g1] = underlineMatch;
+ const before = parseInlineMD(beforeMatch(text, underlineMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, underlineMatch));
+
+ return `${before}${child}${after}`;
+ }
+
+ const italicMatch = text.match(ITALIC_REG_1) ?? text.match(ITALIC_REG_2);
+ if (italicMatch) {
+ const [, g1] = italicMatch;
+ const before = parseInlineMD(beforeMatch(text, italicMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, italicMatch));
+
+ return `${before}${child}${after}`;
+ }
+
+ const strikeMatch = text.match(STRIKE_REG_1);
+ if (strikeMatch) {
+ const [, g1] = strikeMatch;
+ const before = parseInlineMD(beforeMatch(text, strikeMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, strikeMatch));
+
+ return `${before}${child}${after}`;
+ }
+
+ const codeMatch = text.match(CODE_REG_1);
+ if (codeMatch) {
+ const [, g1] = codeMatch;
+ const before = parseInlineMD(beforeMatch(text, codeMatch));
+ const child = g1;
+ const after = parseInlineMD(afterMatch(text, codeMatch));
+
+ return `${before}${child}
${after}`;
+ }
+
+ const spoilerMatch = text.match(SPOILER_REG_1);
+ if (spoilerMatch) {
+ const [, g1] = spoilerMatch;
+ const before = parseInlineMD(beforeMatch(text, spoilerMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, spoilerMatch));
+
+ return `${before}${child}${after}`;
+ }
+
+ return text;
+};
From c64d0526d266a7a1105adcddcb91af5a90e6ad59 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 11:44:40 +0530
Subject: [PATCH 2/9] send markdown re-generative data in tags
---
src/app/utils/markdown.ts | 39 +++++++++++++++++++++++----------------
1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/src/app/utils/markdown.ts b/src/app/utils/markdown.ts
index 0036725f49..77567d472b 100644
--- a/src/app/utils/markdown.ts
+++ b/src/app/utils/markdown.ts
@@ -1,30 +1,37 @@
const MIN_ANY = '(.+?)';
+const BOLD_MD_1 = '**'
const BOLD_PREFIX_1 = '\\*{2}';
const BOLD_NEG_LA_1 = '(?!\\*)';
const BOLD_REG_1 = new RegExp(`${BOLD_PREFIX_1}${MIN_ANY}${BOLD_PREFIX_1}${BOLD_NEG_LA_1}`);
+const ITALIC_MD_1 = '*'
const ITALIC_PREFIX_1 = '\\*';
const ITALIC_NEG_LA_1 = '(?!\\*)';
const ITALIC_REG_1 = new RegExp(`${ITALIC_PREFIX_1}${MIN_ANY}${ITALIC_PREFIX_1}${ITALIC_NEG_LA_1}`);
+const ITALIC_MD_2 = '_'
const ITALIC_PREFIX_2 = '_';
const ITALIC_NEG_LA_2 = '(?!_)';
const ITALIC_REG_2 = new RegExp(`${ITALIC_PREFIX_2}${MIN_ANY}${ITALIC_PREFIX_2}${ITALIC_NEG_LA_2}`);
+const UNDERLINE_MD_1 = '__';
const UNDERLINE_PREFIX_1 = '_{2}';
const UNDERLINE_NEG_LA_1 = '(?!_)';
const UNDERLINE_REG_1 = new RegExp(
`${UNDERLINE_PREFIX_1}${MIN_ANY}${UNDERLINE_PREFIX_1}${UNDERLINE_NEG_LA_1}`
);
+const STRIKE_MD_1 = '~~';
const STRIKE_PREFIX_1 = '~{2}';
const STRIKE_NEG_LA_1 = '(?!~)';
const STRIKE_REG_1 = new RegExp(`${STRIKE_PREFIX_1}${MIN_ANY}${STRIKE_PREFIX_1}${STRIKE_NEG_LA_1}`);
+const CODE_MD_1 = '`';
const CODE_PREFIX_1 = '`';
const CODE_NEG_LA_1 = '(?!`)';
const CODE_REG_1 = new RegExp(`${CODE_PREFIX_1}${MIN_ANY}${CODE_PREFIX_1}${CODE_NEG_LA_1}`);
+const SPOILER_MD_1 = '||';
const SPOILER_PREFIX_1 = '\\|{2}';
const SPOILER_NEG_LA_1 = '(?!\\|)';
const SPOILER_REG_1 = new RegExp(
@@ -41,16 +48,6 @@ const afterMatch = (text: string, match: RegExpMatchArray | RegExpExecArray): st
text.slice((match.index ?? 0) + match[0].length);
export const parseInlineMD = (text: string): string => {
- const linkMatch = text.match(LINK_REG_1);
- if (linkMatch) {
- const [, g1, g2] = linkMatch;
- const before = parseInlineMD(beforeMatch(text, linkMatch));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, linkMatch));
-
- return `${before}${child}${after}`;
- }
-
const boldMatch = text.match(BOLD_REG_1);
if (boldMatch) {
const [, g1] = boldMatch;
@@ -58,7 +55,7 @@ export const parseInlineMD = (text: string): string => {
const child = parseInlineMD(g1);
const after = parseInlineMD(afterMatch(text, boldMatch));
- return `${before}${child}${after}`;
+ return `${before}${child}${after}`;
}
const underlineMatch = text.match(UNDERLINE_REG_1);
@@ -68,7 +65,7 @@ export const parseInlineMD = (text: string): string => {
const child = parseInlineMD(g1);
const after = parseInlineMD(afterMatch(text, underlineMatch));
- return `${before}${child}${after}`;
+ return `${before}${child}${after}`;
}
const italicMatch = text.match(ITALIC_REG_1) ?? text.match(ITALIC_REG_2);
@@ -78,7 +75,7 @@ export const parseInlineMD = (text: string): string => {
const child = parseInlineMD(g1);
const after = parseInlineMD(afterMatch(text, italicMatch));
- return `${before}${child}${after}`;
+ return `${before}${child}${after}`;
}
const strikeMatch = text.match(STRIKE_REG_1);
@@ -88,7 +85,7 @@ export const parseInlineMD = (text: string): string => {
const child = parseInlineMD(g1);
const after = parseInlineMD(afterMatch(text, strikeMatch));
- return `${before}${child}${after}`;
+ return `${before}${child}${after}`;
}
const codeMatch = text.match(CODE_REG_1);
@@ -98,7 +95,7 @@ export const parseInlineMD = (text: string): string => {
const child = g1;
const after = parseInlineMD(afterMatch(text, codeMatch));
- return `${before}${child}
${after}`;
+ return `${before}${child}
${after}`;
}
const spoilerMatch = text.match(SPOILER_REG_1);
@@ -108,8 +105,18 @@ export const parseInlineMD = (text: string): string => {
const child = parseInlineMD(g1);
const after = parseInlineMD(afterMatch(text, spoilerMatch));
- return `${before}${child}${after}`;
+ return `${before}${child}${after}`;
}
+ const linkMatch = text.match(LINK_REG_1);
+ if (linkMatch) {
+ const [, g1, g2] = linkMatch;
+ const before = parseInlineMD(beforeMatch(text, linkMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, linkMatch));
+
+ return `${before}${child}${after}`;
+ }
+
return text;
};
From ccfa4fc78daddb75a405895dd852436878935f20 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 12:03:30 +0530
Subject: [PATCH 3/9] enable vscode format on save
---
.vscode/settings.json | 2 +-
src/app/utils/markdown.ts | 12 +++++++-----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 88a83d6e95..8272ea1e42 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,5 @@
{
- "editor.formatOnSave": false,
+ "editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "node_modules/typescript/lib"
}
diff --git a/src/app/utils/markdown.ts b/src/app/utils/markdown.ts
index 77567d472b..7b12a111d9 100644
--- a/src/app/utils/markdown.ts
+++ b/src/app/utils/markdown.ts
@@ -1,15 +1,15 @@
const MIN_ANY = '(.+?)';
-const BOLD_MD_1 = '**'
+const BOLD_MD_1 = '**';
const BOLD_PREFIX_1 = '\\*{2}';
const BOLD_NEG_LA_1 = '(?!\\*)';
const BOLD_REG_1 = new RegExp(`${BOLD_PREFIX_1}${MIN_ANY}${BOLD_PREFIX_1}${BOLD_NEG_LA_1}`);
-const ITALIC_MD_1 = '*'
+const ITALIC_MD_1 = '*';
const ITALIC_PREFIX_1 = '\\*';
const ITALIC_NEG_LA_1 = '(?!\\*)';
const ITALIC_REG_1 = new RegExp(`${ITALIC_PREFIX_1}${MIN_ANY}${ITALIC_PREFIX_1}${ITALIC_NEG_LA_1}`);
-const ITALIC_MD_2 = '_'
+const ITALIC_MD_2 = '_';
const ITALIC_PREFIX_2 = '_';
const ITALIC_NEG_LA_2 = '(?!_)';
const ITALIC_REG_2 = new RegExp(`${ITALIC_PREFIX_2}${MIN_ANY}${ITALIC_PREFIX_2}${ITALIC_NEG_LA_2}`);
@@ -75,7 +75,9 @@ export const parseInlineMD = (text: string): string => {
const child = parseInlineMD(g1);
const after = parseInlineMD(afterMatch(text, italicMatch));
- return `${before}${child}${after}`;
+ return `${before}${child}${after}`;
}
const strikeMatch = text.match(STRIKE_REG_1);
@@ -117,6 +119,6 @@ export const parseInlineMD = (text: string): string => {
return `${before}${child}${after}`;
}
-
+
return text;
};
From 4decaeca1b0f7faddd3797cdaa2a46df01ac7c38 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 12:10:18 +0530
Subject: [PATCH 4/9] fix match italic and diff order
---
src/app/utils/markdown.ts | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/src/app/utils/markdown.ts b/src/app/utils/markdown.ts
index 7b12a111d9..becc0183e9 100644
--- a/src/app/utils/markdown.ts
+++ b/src/app/utils/markdown.ts
@@ -58,6 +58,16 @@ export const parseInlineMD = (text: string): string => {
return `${before}${child}${after}`;
}
+ const italicMatch = text.match(ITALIC_REG_1);
+ if (italicMatch) {
+ const [, g1] = italicMatch;
+ const before = parseInlineMD(beforeMatch(text, italicMatch));
+ const child = parseInlineMD(g1);
+ const after = parseInlineMD(afterMatch(text, italicMatch));
+
+ return `${before}${child}${after}`;
+ }
+
const underlineMatch = text.match(UNDERLINE_REG_1);
if (underlineMatch) {
const [, g1] = underlineMatch;
@@ -68,16 +78,14 @@ export const parseInlineMD = (text: string): string => {
return `${before}${child}${after}`;
}
- const italicMatch = text.match(ITALIC_REG_1) ?? text.match(ITALIC_REG_2);
- if (italicMatch) {
- const [, g1] = italicMatch;
- const before = parseInlineMD(beforeMatch(text, italicMatch));
+ const italicMatch2 = text.match(ITALIC_REG_2);
+ if (italicMatch2) {
+ const [, g1] = italicMatch2;
+ const before = parseInlineMD(beforeMatch(text, italicMatch2));
const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, italicMatch));
+ const after = parseInlineMD(afterMatch(text, italicMatch2));
- return `${before}${child}${after}`;
+ return `${before}${child}${after}`;
}
const strikeMatch = text.match(STRIKE_REG_1);
From a32a663841361158e92d2b4304bb43b30ed93e0d Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 13:35:56 +0530
Subject: [PATCH 5/9] prevent formatting in code block
---
src/app/components/editor/output.ts | 34 ++++++++++++++++++----------
src/app/organisms/room/RoomInput.tsx | 7 +++++-
2 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts
index cec49aaff5..e7bc3ab9ae 100644
--- a/src/app/components/editor/output.ts
+++ b/src/app/components/editor/output.ts
@@ -4,16 +4,21 @@ import { BlockType } from './Elements';
import { CustomElement, FormattedText } from './slate';
import { parseInlineMD } from '../../utils/markdown';
-const textToCustomHtml = (node: FormattedText, allowMarkdown?: boolean): string => {
+export type OutputOptions = {
+ allowTextFormatting?: boolean;
+ allowMarkdown?: boolean;
+};
+
+const textToCustomHtml = (node: FormattedText, opts: OutputOptions): string => {
let string = sanitizeText(node.text);
- if (node.bold) string = `${string}`;
- if (node.italic) string = `${string}`;
- if (node.underline) string = `${string}`;
- if (node.strikeThrough) string = `${string}`;
- if (node.code) string = `${string}
`;
- if (node.spoiler) string = `${string}`;
+ if (opts.allowTextFormatting && node.bold) string = `${string}`;
+ if (opts.allowTextFormatting && node.italic) string = `${string}`;
+ if (opts.allowTextFormatting && node.underline) string = `${string}`;
+ if (opts.allowTextFormatting && node.strikeThrough) string = `${string}`;
+ if (opts.allowTextFormatting && node.code) string = `${string}
`;
+ if (opts.allowTextFormatting && node.spoiler) string = `${string}`;
- if (allowMarkdown && string === sanitizeText(node.text)) {
+ if (opts.allowMarkdown && string === sanitizeText(node.text)) {
string = parseInlineMD(string);
}
@@ -55,12 +60,17 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
export const toMatrixCustomHTML = (
node: Descendant | Descendant[],
- allowMarkdown?: boolean
+ opts: OutputOptions
): string => {
- if (Array.isArray(node)) return node.map((n) => toMatrixCustomHTML(n, allowMarkdown)).join('');
- if (Text.isText(node)) return textToCustomHtml(node, allowMarkdown);
+ const parseNode = (n: Descendant) => {
+ const isCodeLine = 'type' in n && n.type === BlockType.CodeLine;
+ if (isCodeLine) return toMatrixCustomHTML(n, {});
+ return toMatrixCustomHTML(n, opts);
+ };
+ if (Array.isArray(node)) return node.map(parseNode).join('');
+ if (Text.isText(node)) return textToCustomHtml(node, opts);
- const children = node.children.map((n) => toMatrixCustomHTML(n, allowMarkdown)).join('');
+ const children = node.children.map(parseNode).join('');
return elementToCustomHtml(node, children);
};
diff --git a/src/app/organisms/room/RoomInput.tsx b/src/app/organisms/room/RoomInput.tsx
index 06ff8ce51c..7564d5f440 100644
--- a/src/app/organisms/room/RoomInput.tsx
+++ b/src/app/organisms/room/RoomInput.tsx
@@ -252,7 +252,12 @@ export const RoomInput = forwardRef(
uploadBoardHandlers.current?.handleSend();
const plainText = toPlainText(editor.children).trim();
- const customHtml = trimCustomHtml(toMatrixCustomHTML(editor.children, isMarkdown));
+ const customHtml = trimCustomHtml(
+ toMatrixCustomHTML(editor.children, {
+ allowTextFormatting: true,
+ allowMarkdown: isMarkdown,
+ })
+ );
if (plainText === '') return;
From 83002214742ff272aa683d341e35c677f735d7c0 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 13:51:21 +0530
Subject: [PATCH 6/9] make code md rule highest
---
src/app/utils/markdown.ts | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/app/utils/markdown.ts b/src/app/utils/markdown.ts
index becc0183e9..e386bf7dd5 100644
--- a/src/app/utils/markdown.ts
+++ b/src/app/utils/markdown.ts
@@ -48,6 +48,16 @@ const afterMatch = (text: string, match: RegExpMatchArray | RegExpExecArray): st
text.slice((match.index ?? 0) + match[0].length);
export const parseInlineMD = (text: string): string => {
+ const codeMatch = text.match(CODE_REG_1);
+ if (codeMatch) {
+ const [, g1] = codeMatch;
+ const before = parseInlineMD(beforeMatch(text, codeMatch));
+ const child = g1;
+ const after = parseInlineMD(afterMatch(text, codeMatch));
+
+ return `${before}${child}
${after}`;
+ }
+
const boldMatch = text.match(BOLD_REG_1);
if (boldMatch) {
const [, g1] = boldMatch;
@@ -98,16 +108,6 @@ export const parseInlineMD = (text: string): string => {
return `${before}${child}${after}`;
}
- const codeMatch = text.match(CODE_REG_1);
- if (codeMatch) {
- const [, g1] = codeMatch;
- const before = parseInlineMD(beforeMatch(text, codeMatch));
- const child = g1;
- const after = parseInlineMD(afterMatch(text, codeMatch));
-
- return `${before}${child}
${after}`;
- }
-
const spoilerMatch = text.match(SPOILER_REG_1);
if (spoilerMatch) {
const [, g1] = spoilerMatch;
From 2d001cb05872ca8a46981d9bd1743fa74ac3188e Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 15:11:26 +0530
Subject: [PATCH 7/9] improve inline markdown parsing
---
src/app/utils/markdown.ts | 199 ++++++++++++++++++++++++--------------
1 file changed, 127 insertions(+), 72 deletions(-)
diff --git a/src/app/utils/markdown.ts b/src/app/utils/markdown.ts
index e386bf7dd5..ea4b968bdf 100644
--- a/src/app/utils/markdown.ts
+++ b/src/app/utils/markdown.ts
@@ -1,18 +1,65 @@
+export type PlainMDParser = (text: string) => string;
+export type MatchResult = RegExpMatchArray | RegExpExecArray;
+export type RuleMatch = (text: string) => MatchResult | null;
+export type MatchConverter = (parse: PlainMDParser, match: MatchResult) => string;
+
+export type MDRule = {
+ match: RuleMatch;
+ html: MatchConverter;
+};
+
+export type MatchReplacer = (
+ parse: PlainMDParser,
+ text: string,
+ match: MatchResult,
+ content: string
+) => string;
+
+export type RuleRunner = (parse: PlainMDParser, text: string, rule: MDRule) => string | undefined;
+export type RulesRunner = (
+ parse: PlainMDParser,
+ text: string,
+ rules: MDRule[]
+) => string | undefined;
+
const MIN_ANY = '(.+?)';
const BOLD_MD_1 = '**';
const BOLD_PREFIX_1 = '\\*{2}';
const BOLD_NEG_LA_1 = '(?!\\*)';
const BOLD_REG_1 = new RegExp(`${BOLD_PREFIX_1}${MIN_ANY}${BOLD_PREFIX_1}${BOLD_NEG_LA_1}`);
+const BoldRule: MDRule = {
+ match: (text) => text.match(BOLD_REG_1),
+ html: (parse, match) => {
+ const [, g1] = match;
+ const child = parse(g1);
+ return `${child}`;
+ },
+};
const ITALIC_MD_1 = '*';
const ITALIC_PREFIX_1 = '\\*';
const ITALIC_NEG_LA_1 = '(?!\\*)';
const ITALIC_REG_1 = new RegExp(`${ITALIC_PREFIX_1}${MIN_ANY}${ITALIC_PREFIX_1}${ITALIC_NEG_LA_1}`);
+const ItalicRule1: MDRule = {
+ match: (text) => text.match(ITALIC_REG_1),
+ html: (parse, match) => {
+ const [, g1] = match;
+ return `${parse(g1)}`;
+ },
+};
+
const ITALIC_MD_2 = '_';
const ITALIC_PREFIX_2 = '_';
const ITALIC_NEG_LA_2 = '(?!_)';
const ITALIC_REG_2 = new RegExp(`${ITALIC_PREFIX_2}${MIN_ANY}${ITALIC_PREFIX_2}${ITALIC_NEG_LA_2}`);
+const ItalicRule2: MDRule = {
+ match: (text) => text.match(ITALIC_REG_2),
+ html: (parse, match) => {
+ const [, g1] = match;
+ return `${parse(g1)}`;
+ },
+};
const UNDERLINE_MD_1 = '__';
const UNDERLINE_PREFIX_1 = '_{2}';
@@ -20,16 +67,37 @@ const UNDERLINE_NEG_LA_1 = '(?!_)';
const UNDERLINE_REG_1 = new RegExp(
`${UNDERLINE_PREFIX_1}${MIN_ANY}${UNDERLINE_PREFIX_1}${UNDERLINE_NEG_LA_1}`
);
+const UnderlineRule: MDRule = {
+ match: (text) => text.match(UNDERLINE_REG_1),
+ html: (parse, match) => {
+ const [, g1] = match;
+ return `${parse(g1)}`;
+ },
+};
const STRIKE_MD_1 = '~~';
const STRIKE_PREFIX_1 = '~{2}';
const STRIKE_NEG_LA_1 = '(?!~)';
const STRIKE_REG_1 = new RegExp(`${STRIKE_PREFIX_1}${MIN_ANY}${STRIKE_PREFIX_1}${STRIKE_NEG_LA_1}`);
+const StrikeRule: MDRule = {
+ match: (text) => text.match(STRIKE_REG_1),
+ html: (parse, match) => {
+ const [, g1] = match;
+ return `${parse(g1)}`;
+ },
+};
const CODE_MD_1 = '`';
const CODE_PREFIX_1 = '`';
const CODE_NEG_LA_1 = '(?!`)';
const CODE_REG_1 = new RegExp(`${CODE_PREFIX_1}${MIN_ANY}${CODE_PREFIX_1}${CODE_NEG_LA_1}`);
+const CodeRule: MDRule = {
+ match: (text) => text.match(CODE_REG_1),
+ html: (parse, match) => {
+ const [, g1] = match;
+ return `${g1}
`;
+ },
+};
const SPOILER_MD_1 = '||';
const SPOILER_PREFIX_1 = '\\|{2}';
@@ -37,96 +105,83 @@ const SPOILER_NEG_LA_1 = '(?!\\|)';
const SPOILER_REG_1 = new RegExp(
`${SPOILER_PREFIX_1}${MIN_ANY}${SPOILER_PREFIX_1}${SPOILER_NEG_LA_1}`
);
+const SpoilerRule: MDRule = {
+ match: (text) => text.match(SPOILER_REG_1),
+ html: (parse, match) => {
+ const [, g1] = match;
+ return `${parse(g1)}`;
+ },
+};
const LINK_ALT = `\\[${MIN_ANY}\\]`;
const LINK_URL = `\\((https?:\\/\\/.+?)\\)`;
const LINK_REG_1 = new RegExp(`${LINK_ALT}${LINK_URL}`);
+const LinkRule: MDRule = {
+ match: (text) => text.match(LINK_REG_1),
+ html: (parse, match) => {
+ const [, g1, g2] = match;
+ return `${parse(g1)}`;
+ },
+};
const beforeMatch = (text: string, match: RegExpMatchArray | RegExpExecArray): string =>
text.slice(0, match.index);
const afterMatch = (text: string, match: RegExpMatchArray | RegExpExecArray): string =>
text.slice((match.index ?? 0) + match[0].length);
-export const parseInlineMD = (text: string): string => {
- const codeMatch = text.match(CODE_REG_1);
- if (codeMatch) {
- const [, g1] = codeMatch;
- const before = parseInlineMD(beforeMatch(text, codeMatch));
- const child = g1;
- const after = parseInlineMD(afterMatch(text, codeMatch));
-
- return `${before}${child}
${after}`;
- }
-
- const boldMatch = text.match(BOLD_REG_1);
- if (boldMatch) {
- const [, g1] = boldMatch;
- const before = parseInlineMD(beforeMatch(text, boldMatch));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, boldMatch));
-
- return `${before}${child}${after}`;
- }
-
- const italicMatch = text.match(ITALIC_REG_1);
- if (italicMatch) {
- const [, g1] = italicMatch;
- const before = parseInlineMD(beforeMatch(text, italicMatch));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, italicMatch));
+const replaceMatch: MatchReplacer = (parse, text, match, content) =>
+ `${parse(beforeMatch(text, match))}${content}${parse(afterMatch(text, match))}`;
- return `${before}${child}${after}`;
+const runRule: RuleRunner = (parse, text, rule) => {
+ const matchResult = rule.match(text);
+ if (matchResult) {
+ const content = rule.html(parse, matchResult);
+ return replaceMatch(parse, text, matchResult, content);
}
+ return undefined;
+};
- const underlineMatch = text.match(UNDERLINE_REG_1);
- if (underlineMatch) {
- const [, g1] = underlineMatch;
- const before = parseInlineMD(beforeMatch(text, underlineMatch));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, underlineMatch));
-
- return `${before}${child}${after}`;
- }
-
- const italicMatch2 = text.match(ITALIC_REG_2);
- if (italicMatch2) {
- const [, g1] = italicMatch2;
- const before = parseInlineMD(beforeMatch(text, italicMatch2));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, italicMatch2));
-
- return `${before}${child}${after}`;
+const runRules: RulesRunner = (parse, text, rules) => {
+ const matchResults = rules.map((rule) => rule.match(text));
+
+ let targetRule: MDRule | undefined;
+ let targetResult: MatchResult | undefined;
+
+ for (let i = 0; i < matchResults.length; i += 1) {
+ const currentResult = matchResults[i];
+ if (currentResult && typeof currentResult.index === 'number') {
+ if (
+ !targetResult ||
+ (typeof targetResult?.index === 'number' && currentResult.index < targetResult.index)
+ ) {
+ targetResult = currentResult;
+ targetRule = rules[i];
+ }
+ }
}
- const strikeMatch = text.match(STRIKE_REG_1);
- if (strikeMatch) {
- const [, g1] = strikeMatch;
- const before = parseInlineMD(beforeMatch(text, strikeMatch));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, strikeMatch));
-
- return `${before}${child}${after}`;
+ if (targetRule && targetResult) {
+ const content = targetRule.html(parse, targetResult);
+ return replaceMatch(parse, text, targetResult, content);
}
+ return undefined;
+};
- const spoilerMatch = text.match(SPOILER_REG_1);
- if (spoilerMatch) {
- const [, g1] = spoilerMatch;
- const before = parseInlineMD(beforeMatch(text, spoilerMatch));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, spoilerMatch));
+const LeveledRules = [
+ BoldRule,
+ ItalicRule1,
+ UnderlineRule,
+ ItalicRule2,
+ StrikeRule,
+ SpoilerRule,
+ LinkRule,
+];
- return `${before}${child}${after}`;
- }
-
- const linkMatch = text.match(LINK_REG_1);
- if (linkMatch) {
- const [, g1, g2] = linkMatch;
- const before = parseInlineMD(beforeMatch(text, linkMatch));
- const child = parseInlineMD(g1);
- const after = parseInlineMD(afterMatch(text, linkMatch));
+export const parseInlineMD = (text: string): string => {
+ let result: string | undefined;
+ if (!result) result = runRule(parseInlineMD, text, CodeRule);
- return `${before}${child}${after}`;
- }
+ if (!result) result = runRules(parseInlineMD, text, LeveledRules);
- return text;
+ return result ?? text;
};
From bc5a9f5c0a4f567f8f366bbaff6aed603afde0c5 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 15:18:12 +0530
Subject: [PATCH 8/9] add comment
---
src/app/utils/markdown.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/app/utils/markdown.ts b/src/app/utils/markdown.ts
index ea4b968bdf..e4294d7d6c 100644
--- a/src/app/utils/markdown.ts
+++ b/src/app/utils/markdown.ts
@@ -141,6 +141,10 @@ const runRule: RuleRunner = (parse, text, rule) => {
return undefined;
};
+/**
+ * Runs multiple rules at the same time to better handle nested rules.
+ * Rules will be run in the order they appear.
+ */
const runRules: RulesRunner = (parse, text, rules) => {
const matchResults = rules.map((rule) => rule.match(text));
From bcb8d6aea05e0c556530227d3f41616ab2f737ff Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Mon, 9 Oct 2023 16:42:12 +0530
Subject: [PATCH 9/9] improve code logic
---
src/app/components/editor/output.ts | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts
index e7bc3ab9ae..92c86dd86b 100644
--- a/src/app/components/editor/output.ts
+++ b/src/app/components/editor/output.ts
@@ -11,12 +11,14 @@ export type OutputOptions = {
const textToCustomHtml = (node: FormattedText, opts: OutputOptions): string => {
let string = sanitizeText(node.text);
- if (opts.allowTextFormatting && node.bold) string = `${string}`;
- if (opts.allowTextFormatting && node.italic) string = `${string}`;
- if (opts.allowTextFormatting && node.underline) string = `${string}`;
- if (opts.allowTextFormatting && node.strikeThrough) string = `${string}`;
- if (opts.allowTextFormatting && node.code) string = `${string}
`;
- if (opts.allowTextFormatting && node.spoiler) string = `${string}`;
+ if (opts.allowTextFormatting) {
+ if (node.bold) string = `${string}`;
+ if (node.italic) string = `${string}`;
+ if (node.underline) string = `${string}`;
+ if (node.strikeThrough) string = `${string}`;
+ if (node.code) string = `${string}
`;
+ if (node.spoiler) string = `${string}`;
+ }
if (opts.allowMarkdown && string === sanitizeText(node.text)) {
string = parseInlineMD(string);