Skip to content

Commit

Permalink
Improve Editor related bugs and add multiline md (#1507)
Browse files Browse the repository at this point in the history
* remove shift from editor hotkeys

* fix inline markdown not working

* add block md parser - WIP

* emojify and linkify text without react-parser

* no need to sanitize text when emojify

* parse block markdown in editor output - WIP

* add inline parser option in block md parser

* improve codeblock regex

* ignore html tag when parsing inline md in block md

* add list markdown rule in block parser

* re-generate block markdown on edit

* change copy from inline markdown to markdown

* fix trim reply from body regex

* fix jumbo emoji in reply message

* fix broken list regex in block markdown

* enable markdown by defualt
  • Loading branch information
ajbura authored Oct 27, 2023
1 parent 72bb5b4 commit b24f858
Show file tree
Hide file tree
Showing 15 changed files with 424 additions and 159 deletions.
19 changes: 7 additions & 12 deletions src/app/components/editor/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export function HeadingBlockButton() {
<Menu style={{ padding: config.space.S100 }}>
<Box gap="100">
<TooltipProvider
tooltip={<BtnTooltip text="Heading 1" shortCode={`${modKey} + Shift + 1`} />}
tooltip={<BtnTooltip text="Heading 1" shortCode={`${modKey} + 1`} />}
delay={500}
>
{(triggerRef) => (
Expand All @@ -163,7 +163,7 @@ export function HeadingBlockButton() {
)}
</TooltipProvider>
<TooltipProvider
tooltip={<BtnTooltip text="Heading 2" shortCode={`${modKey} + Shift + 2`} />}
tooltip={<BtnTooltip text="Heading 2" shortCode={`${modKey} + 2`} />}
delay={500}
>
{(triggerRef) => (
Expand All @@ -178,7 +178,7 @@ export function HeadingBlockButton() {
)}
</TooltipProvider>
<TooltipProvider
tooltip={<BtnTooltip text="Heading 3" shortCode={`${modKey} + Shift + 3`} />}
tooltip={<BtnTooltip text="Heading 3" shortCode={`${modKey} + 3`} />}
delay={500}
>
{(triggerRef) => (
Expand Down Expand Up @@ -277,12 +277,7 @@ export function Toolbar() {
<MarkButton
format={MarkType.StrikeThrough}
icon={Icons.Strike}
tooltip={
<BtnTooltip
text="Strike Through"
shortCode={`${modKey} + ${KeySymbol.Shift} + U`}
/>
}
tooltip={<BtnTooltip text="Strike Through" shortCode={`${modKey} + S`} />}
/>
<MarkButton
format={MarkType.Code}
Expand Down Expand Up @@ -311,12 +306,12 @@ export function Toolbar() {
<BlockButton
format={BlockType.OrderedList}
icon={Icons.OrderList}
tooltip={<BtnTooltip text="Ordered List" shortCode={`${modKey} + Shift + 7`} />}
tooltip={<BtnTooltip text="Ordered List" shortCode={`${modKey} + 7`} />}
/>
<BlockButton
format={BlockType.UnorderedList}
icon={Icons.UnorderList}
tooltip={<BtnTooltip text="Unordered List" shortCode={`${modKey} + Shift + 8`} />}
tooltip={<BtnTooltip text="Unordered List" shortCode={`${modKey} + 8`} />}
/>
<HeadingBlockButton />
</Box>
Expand All @@ -335,7 +330,7 @@ export function Toolbar() {
<Box className={css.MarkdownBtnBox} shrink="No" grow="Yes" justifyContent="End">
<TooltipProvider
align="End"
tooltip={<BtnTooltip text="Inline Markdown" />}
tooltip={<BtnTooltip text="Toggle Markdown" />}
delay={500}
>
{(triggerRef) => (
Expand Down
149 changes: 102 additions & 47 deletions src/app/components/editor/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import {
HeadingElement,
HeadingLevel,
InlineElement,
ListItemElement,
MentionElement,
OrderedListElement,
ParagraphElement,
QuoteLineElement,
UnorderedListElement,
} from './slate';
import { parseMatrixToUrl } from '../../utils/matrix';
Expand Down Expand Up @@ -117,17 +115,14 @@ const parseInlineNodes = (node: ChildNode): InlineElement[] => {
return [];
};

const parseBlockquoteNode = (node: Element): BlockQuoteElement => {
const children: QuoteLineElement[] = [];
const parseBlockquoteNode = (node: Element): BlockQuoteElement[] | ParagraphElement[] => {
const quoteLines: Array<InlineElement[]> = [];
let lineHolder: InlineElement[] = [];

const appendLine = () => {
if (lineHolder.length === 0) return;

children.push({
type: BlockType.QuoteLine,
children: lineHolder,
});
quoteLines.push(lineHolder);
lineHolder = [];
};

Expand All @@ -145,10 +140,7 @@ const parseBlockquoteNode = (node: Element): BlockQuoteElement => {

if (child.name === 'p') {
appendLine();
children.push({
type: BlockType.QuoteLine,
children: child.children.flatMap((c) => parseInlineNodes(c)),
});
quoteLines.push(child.children.flatMap((c) => parseInlineNodes(c)));
return;
}

Expand All @@ -157,42 +149,71 @@ const parseBlockquoteNode = (node: Element): BlockQuoteElement => {
});
appendLine();

return {
type: BlockType.BlockQuote,
children,
};
if (node.attribs['data-md'] !== undefined) {
return quoteLines.map((lineChildren) => ({
type: BlockType.Paragraph,
children: [{ text: `${node.attribs['data-md']} ` }, ...lineChildren],
}));
}

return [
{
type: BlockType.BlockQuote,
children: quoteLines.map((lineChildren) => ({
type: BlockType.QuoteLine,
children: lineChildren,
})),
},
];
};
const parseCodeBlockNode = (node: Element): CodeBlockElement => {
const children: CodeLineElement[] = [];
const parseCodeBlockNode = (node: Element): CodeBlockElement[] | ParagraphElement[] => {
const codeLines = parseNodeText(node).trim().split('\n');

const code = parseNodeText(node).trim();
code.split('\n').forEach((lineTxt) =>
children.push({
type: BlockType.CodeLine,
if (node.attribs['data-md'] !== undefined) {
const pLines = codeLines.map<ParagraphElement>((lineText) => ({
type: BlockType.Paragraph,
children: [
{
text: lineTxt,
text: lineText,
},
],
})
);
}));
const childCode = node.children[0];
const className =
isTag(childCode) && childCode.tagName === 'code' ? childCode.attribs.class ?? '' : '';
const prefix = { text: `${node.attribs['data-md']}${className.replace('language-', '')}` };
const suffix = { text: node.attribs['data-md'] };
return [
{ type: BlockType.Paragraph, children: [prefix] },
...pLines,
{ type: BlockType.Paragraph, children: [suffix] },
];
}

return {
type: BlockType.CodeBlock,
children,
};
return [
{
type: BlockType.CodeBlock,
children: codeLines.map<CodeLineElement>((lineTxt) => ({
type: BlockType.CodeLine,
children: [
{
text: lineTxt,
},
],
})),
},
];
};
const parseListNode = (node: Element): OrderedListElement | UnorderedListElement => {
const children: ListItemElement[] = [];
const parseListNode = (
node: Element
): OrderedListElement[] | UnorderedListElement[] | ParagraphElement[] => {
const listLines: Array<InlineElement[]> = [];
let lineHolder: InlineElement[] = [];

const appendLine = () => {
if (lineHolder.length === 0) return;

children.push({
type: BlockType.ListItem,
children: lineHolder,
});
listLines.push(lineHolder);
lineHolder = [];
};

Expand All @@ -210,10 +231,7 @@ const parseListNode = (node: Element): OrderedListElement | UnorderedListElement

if (child.name === 'li') {
appendLine();
children.push({
type: BlockType.ListItem,
children: child.children.flatMap((c) => parseInlineNodes(c)),
});
listLines.push(child.children.flatMap((c) => parseInlineNodes(c)));
return;
}

Expand All @@ -222,17 +240,54 @@ const parseListNode = (node: Element): OrderedListElement | UnorderedListElement
});
appendLine();

return {
type: node.name === 'ol' ? BlockType.OrderedList : BlockType.UnorderedList,
children,
};
if (node.attribs['data-md'] !== undefined) {
const prefix = node.attribs['data-md'] || '-';
const [starOrHyphen] = prefix.match(/^\*|-$/) ?? [];
return listLines.map((lineChildren) => ({
type: BlockType.Paragraph,
children: [
{ text: `${starOrHyphen ? `${starOrHyphen} ` : `${prefix}. `} ` },
...lineChildren,
],
}));
}

if (node.name === 'ol') {
return [
{
type: BlockType.OrderedList,
children: listLines.map((lineChildren) => ({
type: BlockType.ListItem,
children: lineChildren,
})),
},
];
}

return [
{
type: BlockType.UnorderedList,
children: listLines.map((lineChildren) => ({
type: BlockType.ListItem,
children: lineChildren,
})),
},
];
};
const parseHeadingNode = (node: Element): HeadingElement => {
const parseHeadingNode = (node: Element): HeadingElement | ParagraphElement => {
const children = node.children.flatMap((child) => parseInlineNodes(child));

const headingMatch = node.name.match(/^h([123456])$/);
const [, g1AsLevel] = headingMatch ?? ['h3', '3'];
const level = parseInt(g1AsLevel, 10);

if (node.attribs['data-md'] !== undefined) {
return {
type: BlockType.Paragraph,
children: [{ text: `${node.attribs['data-md']} ` }, ...children],
};
}

return {
type: BlockType.Heading,
level: (level <= 3 ? level : 3) as HeadingLevel,
Expand Down Expand Up @@ -278,17 +333,17 @@ export const domToEditorInput = (domNodes: ChildNode[]): Descendant[] => {

if (node.name === 'blockquote') {
appendLine();
children.push(parseBlockquoteNode(node));
children.push(...parseBlockquoteNode(node));
return;
}
if (node.name === 'pre') {
appendLine();
children.push(parseCodeBlockNode(node));
children.push(...parseCodeBlockNode(node));
return;
}
if (node.name === 'ol' || node.name === 'ul') {
appendLine();
children.push(parseListNode(node));
children.push(...parseListNode(node));
return;
}

Expand Down
12 changes: 6 additions & 6 deletions src/app/components/editor/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ export const INLINE_HOTKEYS: Record<string, MarkType> = {
'mod+b': MarkType.Bold,
'mod+i': MarkType.Italic,
'mod+u': MarkType.Underline,
'mod+shift+u': MarkType.StrikeThrough,
'mod+s': MarkType.StrikeThrough,
'mod+[': MarkType.Code,
'mod+h': MarkType.Spoiler,
};
const INLINE_KEYS = Object.keys(INLINE_HOTKEYS);

export const BLOCK_HOTKEYS: Record<string, BlockType> = {
'mod+shift+7': BlockType.OrderedList,
'mod+shift+8': BlockType.UnorderedList,
'mod+7': BlockType.OrderedList,
'mod+8': BlockType.UnorderedList,
"mod+'": BlockType.BlockQuote,
'mod+;': BlockType.CodeBlock,
};
const BLOCK_KEYS = Object.keys(BLOCK_HOTKEYS);
const isHeading1 = isKeyHotkey('mod+shift+1');
const isHeading2 = isKeyHotkey('mod+shift+2');
const isHeading3 = isKeyHotkey('mod+shift+3');
const isHeading1 = isKeyHotkey('mod+1');
const isHeading2 = isKeyHotkey('mod+2');
const isHeading3 = isKeyHotkey('mod+3');

/**
* @return boolean true if shortcut is toggled.
Expand Down
41 changes: 35 additions & 6 deletions src/app/components/editor/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { Descendant, Text } from 'slate';
import { sanitizeText } from '../../utils/sanitize';
import { BlockType } from './types';
import { CustomElement } from './slate';
import { parseInlineMD } from '../../utils/markdown';
import { parseBlockMD, parseInlineMD, replaceMatch } from '../../utils/markdown';

export type OutputOptions = {
allowTextFormatting?: boolean;
allowMarkdown?: boolean;
allowInlineMarkdown?: boolean;
allowBlockMarkdown?: boolean;
};

const textToCustomHtml = (node: Text, opts: OutputOptions): string => {
Expand All @@ -21,7 +22,7 @@ const textToCustomHtml = (node: Text, opts: OutputOptions): string => {
if (node.spoiler) string = `<span data-mx-spoiler>${string}</span>`;
}

if (opts.allowMarkdown && string === sanitizeText(node.text)) {
if (opts.allowInlineMarkdown && string === sanitizeText(node.text)) {
string = parseInlineMD(string);
}

Expand Down Expand Up @@ -64,14 +65,42 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
}
};

const HTML_TAG_REG = /<([a-z]+)(?![^>]*\/>)[^<]*<\/\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('');
};

export const toMatrixCustomHTML = (
node: Descendant | Descendant[],
opts: OutputOptions
): string => {
const parseNode = (n: Descendant) => {
let markdownLines = '';
const parseNode = (n: Descendant, index: number, targetNodes: Descendant[]) => {
if (opts.allowBlockMarkdown && 'type' in n && n.type === BlockType.Paragraph) {
const line = toMatrixCustomHTML(n, {
...opts,
allowInlineMarkdown: false,
allowBlockMarkdown: false,
})
.replace(/<br\/>$/, '\n')
.replace(/^&gt;/, '>');
markdownLines += line;
if (index === targetNodes.length - 1) {
return parseBlockMD(markdownLines, ignoreHTMLParseInlineMD);
}
return '';
}

const parsedMarkdown = parseBlockMD(markdownLines, ignoreHTMLParseInlineMD);
markdownLines = '';
const isCodeLine = 'type' in n && n.type === BlockType.CodeLine;
if (isCodeLine) return toMatrixCustomHTML(n, {});
return toMatrixCustomHTML(n, opts);
if (isCodeLine) return `${parsedMarkdown}${toMatrixCustomHTML(n, {})}`;

return `${parsedMarkdown}${toMatrixCustomHTML(n, { ...opts, allowBlockMarkdown: false })}`;
};
if (Array.isArray(node)) return node.map(parseNode).join('');
if (Text.isText(node)) return textToCustomHtml(node, opts);
Expand Down
Loading

0 comments on commit b24f858

Please sign in to comment.