Add table viewing code.

This commit is contained in:
Danila Fedorin 2021-07-13 16:33:14 -07:00
parent f6f29adacc
commit d69b78469c
4 changed files with 87 additions and 2 deletions

View file

@ -64,6 +64,15 @@ export class ListBlock {
get type() { return "list"; } get type() { return "list"; }
} }
export class TableBlock {
constructor(head, body) {
this.head = head;
this.body = body;
}
get type() { return "table"; }
}
export class RulePart { export class RulePart {
get type( ) { return "rule"; } get type( ) { return "rule"; }
} }

View file

@ -1,4 +1,4 @@
import { MessageBody, HeaderBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js" import { MessageBody, HeaderBlock, TableBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
import { linkify } from "./linkify/linkify.js"; import { linkify } from "./linkify/linkify.js";
import { parsePillLink } from "./pills.js" import { parsePillLink } from "./pills.js"
@ -53,6 +53,12 @@ class Deserializer {
return new ListBlock(start, nodes); return new ListBlock(start, nodes);
} }
_ensureElement(node, tag) {
return node &&
this.result.isElementNode(node) &&
this.result.getNodeElementName(node) === tag;
}
parseCodeBlock(node) { parseCodeBlock(node) {
const result = this.result; const result = this.result;
let codeNode; let codeNode;
@ -60,7 +66,7 @@ class Deserializer {
codeNode = child; codeNode = child;
break; break;
} }
if (!(codeNode && result.getNodeElementName(codeNode) === "CODE")) { if (!this._ensureElement(codeNode, "CODE")) {
return null; return null;
} }
let language = ""; let language = "";
@ -89,6 +95,56 @@ class Deserializer {
return new ImagePart(url, width, height, alt, title); return new ImagePart(url, width, height, alt, title);
} }
parseTableRow(row, tag) {
const cells = [];
for (const node of this.result.getChildNodes(row)) {
if(!this._ensureElement(node, tag)) {
continue;
}
const children = this.result.getChildNodes(node);
const inlines = this.parseInlineNodes(children);
cells.push(inlines);
}
return cells;
}
parseTableHead(head) {
let headRow = null;
for (const node of this.result.getChildNodes(head)) {
headRow = node;
break;
}
if (this._ensureElement(headRow, "TR")) {
return this.parseTableRow(headRow, "TH");
}
return null;
}
parseTableBody(body) {
const rows = [];
for (const node of this.result.getChildNodes(body)) {
if(!this._ensureElement(node, "TR")) {
continue;
}
rows.push(this.parseTableRow(node, "TD"));
}
return rows;
}
parseTable(node) {
// We are only assuming iterable, so convert to arrary for indexing.
const children = Array.from(this.result.getChildNodes(node));
let head, body;
if (this._ensureElement(children[0], "THEAD") && this._ensureElement(children[1], "TBODY")) {
head = this.parseTableHead(children[0]);
body = this.parseTableBody(children[1]);
} else if (this._ensureElement(children[0], "TBODY")) {
head = null;
body = this.parseTableBody(children[0]);
}
return new TableBlock(head, body);
}
/** Once a node is known to be an element, /** Once a node is known to be an element,
* attempt to interpret it as an inline element. * attempt to interpret it as an inline element.
* *
@ -161,6 +217,8 @@ class Deserializer {
const inlines = this.parseInlineNodes(children); const inlines = this.parseInlineNodes(children);
return new FormatPart(tag, inlines); return new FormatPart(tag, inlines);
} }
case "TABLE":
return this.parseTable(node);
default: { default: {
if (!basicBlock.includes(tag)) { if (!basicBlock.includes(tag)) {
return null; return null;

View file

@ -94,6 +94,7 @@ export const TAG_NAMES = {
[HTML_NS]: [ [HTML_NS]: [
"br", "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6", "br", "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6",
"p", "strong", "em", "span", "img", "section", "main", "article", "aside", "del", "blockquote", "p", "strong", "em", "span", "img", "section", "main", "article", "aside", "del", "blockquote",
"table", "thead", "tbody", "tr", "th", "td",
"pre", "code", "button", "time", "input", "textarea", "label", "form", "progress", "output", "video"], "pre", "code", "button", "time", "input", "textarea", "label", "form", "progress", "output", "video"],
[SVG_NS]: ["svg", "circle"] [SVG_NS]: ["svg", "circle"]
}; };

View file

@ -64,12 +64,29 @@ function renderPill(pillPart) {
return tag.a({ class: "pill", href: pillPart.href }, children); return tag.a({ class: "pill", href: pillPart.href }, children);
} }
function renderTable(tablePart) {
const children = [];
if (tablePart.head) {
const headers = tablePart.head
.map(cell => tag.th({}, renderParts(cell)));
children.push(tag.thead({}, tag.tr({}, headers)))
}
const rows = [];
for (const row of tablePart.body) {
const data = row.map(cell => tag.td({}, renderParts(cell)));
rows.push(tag.tr({}, data));
}
children.push(tag.tbody({}, rows));
return tag.table({}, children);
}
/** /**
* Map from part to function that outputs DOM for the part * Map from part to function that outputs DOM for the part
*/ */
const formatFunction = { const formatFunction = {
header: headerBlock => tag["h" + Math.min(6,headerBlock.level)]({}, renderParts(headerBlock.inlines)), header: headerBlock => tag["h" + Math.min(6,headerBlock.level)]({}, renderParts(headerBlock.inlines)),
codeblock: codeBlock => tag.pre({}, tag.code({}, text(codeBlock.text))), codeblock: codeBlock => tag.pre({}, tag.code({}, text(codeBlock.text))),
table: tableBlock => renderTable(tableBlock),
emph: emphPart => tag.em({}, renderParts(emphPart.inlines)), emph: emphPart => tag.em({}, renderParts(emphPart.inlines)),
code: codePart => tag.code({}, text(codePart.text)), code: codePart => tag.code({}, text(codePart.text)),
text: textPart => text(textPart.text), text: textPart => text(textPart.text),