From 787cf2a9fe1f4afce1cf9d2985369753a555d771 Mon Sep 17 00:00:00 2001 From: Arnaud Brochard Date: Thu, 7 Dec 2023 15:49:49 +0100 Subject: [PATCH] feat: tables support (#2573) * Tables support * Linter fixes * Regex Redos fix * Fix empty row and variables proper naming * Default cell style * Now unncessary * Support rows without a starting pipe char * Striped rows * Fix parsing issues * Support tabs in separators --- web/src/labs/marked/parser/Table.tsx | 81 ++++++++++++++++++++++++++++ web/src/labs/marked/parser/index.ts | 2 + 2 files changed, 83 insertions(+) create mode 100644 web/src/labs/marked/parser/Table.tsx diff --git a/web/src/labs/marked/parser/Table.tsx b/web/src/labs/marked/parser/Table.tsx new file mode 100644 index 00000000..4b83e96d --- /dev/null +++ b/web/src/labs/marked/parser/Table.tsx @@ -0,0 +1,81 @@ +import { CSSProperties } from "react"; +import { inlineElementParserList } from "."; +import { marked } from ".."; +import { matcher } from "../matcher"; + +class TableRegExp extends RegExp { + [Symbol.match](str: string): RegExpMatchArray | null { + const result = RegExp.prototype[Symbol.match].call(this, str); + // regex will only be considered valid if headers and delimiters column count matches + if (!result || splitPipeDelimiter(result[1]).length != splitPipeDelimiter(result[2]).length) { + return null; + } + return result; + } +} + +export const TABLE_REG = new TableRegExp(/^([^\n|]*\|[^\n]*)\n([ \t:-]*(? { + // loose pipe delimiter for markdown tables. escaped pipes are supported. some examples: + // | aaaa | bbbb | cc\|cc | => ["aaaa", "bbbb", "cc|cc"] + // aaaa | bbbb | cc\|cc => ["aaaa", "bbbb", "cc|cc"] + // |a|f => ["a", "f"] + // ||a|f| => ["", "a", "f"] + // |||| => ["", "", ""] + // |\||\||\|| => ["|", "|", "|"] + return ( + rawStr + .replaceAll(/(? cell.replaceAll("\\|", "|").trim()); + // TODO: Need to move backslash escaping (to PlainText ?) for all characters + // described in markdown spec (\`*_{}[]()#+-.!), and not just the pipe symbol here +}; + +const renderer = (rawStr: string) => { + const matchResult = matcher(rawStr, TABLE_REG); + if (!matchResult) { + return rawStr; + } + const headerContents = splitPipeDelimiter(matchResult[1]); + const cellStyles: CSSProperties[] = splitPipeDelimiter(matchResult[2]).map((cell) => { + const left = cell.startsWith(":"); + const right = cell.endsWith(":"); + // github markdown spec says that by default, content is left aligned + return { + textAlign: left && right ? "center" : right ? "right" : "left", + }; + }); + const rowContents = matchResult[3].substring(1).split(/\r?\n/).map(splitPipeDelimiter); + + return ( + + + + {headerContents.map((header, index) => ( + + ))} + + + + {rowContents.map((row, rowIndex) => ( + + {headerContents.map((_, cellIndex) => ( + + ))} + + ))} + +
{marked(header, [], inlineElementParserList)}
+ {cellIndex < row.length ? marked(row[cellIndex], [], inlineElementParserList) : null} +
+ ); +}; + +export default { + name: "table", + regexp: TABLE_REG, + renderer, +}; diff --git a/web/src/labs/marked/parser/index.ts b/web/src/labs/marked/parser/index.ts index 94a26cba..3259ee8e 100644 --- a/web/src/labs/marked/parser/index.ts +++ b/web/src/labs/marked/parser/index.ts @@ -17,6 +17,7 @@ import Paragraph from "./Paragraph"; import PlainLink from "./PlainLink"; import PlainText from "./PlainText"; import Strikethrough from "./Strikethrough"; +import Table from "./Table"; import Tag from "./Tag"; import TodoList from "./TodoList"; import UnorderedList from "./UnorderedList"; @@ -31,6 +32,7 @@ export const blockElementParserList = [ Br, CodeBlock, Blockquote, + Table, Heading, TodoList, DoneList,