vector-im-hydrogen-web/src/platform/web/dom/deserialize.js

109 lines
3.2 KiB
JavaScript
Raw Normal View History

import { HeaderBlock, ListBlock, CodeBlock, FormatPart, NewLinePart, RulePart, TextPart, LinkPart } from "../../../domain/session/room/timeline/MessageBody.js"
/* At the time of writing (Jul 1 2021), Matrix Spec recommends
* allowing the following HTML tags:
* font, del, h1, h2, h3, h4, h5, h6, blockquote, p, a, ul, ol, sup, sub, li, b, i, u,
* strong, em, strike, code, hr, br, div, table, thead, tbody, tr, th, td, caption, pre, span, img
*/
const basicNodes = ["EM", "STRONG", "CODE", "DEL", "P", "DIV", "SPAN" ]
function basicWrapper(tag) {
return (_, children) => new FormatPart(tag, children);
}
function headerWrapper(level) {
return (_, children) => new HeaderBlock(level, children);
}
function parseLink(node, children) {
return new LinkPart(node.href, children);
}
function parseList(node) {
const start = node.getAttribute("start") || 1;
const nodes = [];
const len = node.childNodes.length;
for (let i = 0; i < len; i += 1) {
const child = node.childNodes[i];
if (child.tagName !== "LI") {
continue;
}
nodes.push(parseNodes(child.childNodes));
}
return new ListBlock(start, nodes);
}
function parseCodeBlock(node) {
let codeNode;
if (!((codeNode = node.firstChild) && codeNode.nodeName == "CODE")) {
return null;
}
let language = "";
for (const clname of codeNode.classList) {
if (clname.startsWith("language-") && !clname.startsWith("language-_")) {
language = clname.substring(9) // "language-".length
break;
}
}
return new CodeBlock(language, codeNode.textContent);
}
function parseImage(node) {
return null;
}
function buildNodeMap() {
let map = {
A: { descend: true, parsefn: parseLink },
UL: { descend: false, parsefn: parseList },
OL: { descend: false, parsefn: parseList },
PRE: { descend: false, parsefn: parseCodeBlock },
BR: { descend: false, parsefn: () => new NewLinePart() },
HR: { descend: false, parsefn: () => new RulePart() },
IMG: { descend: false, parsefn: parseImage }
}
for (const tag of basicNodes) {
map[tag] = { descend: true, parsefn: basicWrapper(tag) }
}
for (let level = 1; level <= 6; level++) {
const tag = "h" + level;
map[tag] = { descend: true, parsefn: headerWrapper(level) }
}
return map;
}
const nodes = buildNodeMap();
function parseNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
return new TextPart(node.nodeValue);
} else if (node.nodeType == Node.ELEMENT_NODE) {
const f = nodes[node.nodeName];
if (!f) {
return null;
}
let result = f.parsefn(node, f.descend ? parseNodes(node.childNodes) : null);
return result;
}
return null;
}
function parseNodes(nodes) {
const len = nodes.length;
const parsed = [];
for (let i = 0; i < len; i ++) {
let node = parseNode(nodes[i]);
if (node) {
parsed.push(node);
}
}
return parsed;
}
export function parse(html) {
const rootNode = new DOMParser().parseFromString(html, "text/html").body;
return parseNodes(rootNode.childNodes);
}