From ad868818c71e61e53f8374f0ec90d09743e93a8e Mon Sep 17 00:00:00 2001
From: Danila Fedorin hello world has `src`, these have... themselves.
+ */
const basicNodes = ["EM", "STRONG", "CODE", "DEL", "P", "DIV", "SPAN" ]
+/**
+ * Return a builder function for a particular tag.
+ */
function basicWrapper(tag) {
return (_, children) => new FormatPart(tag, children);
}
+/**
+ * Return a builder function for a particular header level.
+ */
function headerWrapper(level) {
return (_, children) => new HeaderBlock(level, children);
}
@@ -74,6 +84,18 @@ function buildNodeMap() {
return map;
}
+/**
+ * Handlers for various nodes.
+ *
+ * Each handler has two properties: `descend` and `parsefn`.
+ * If `descend` is true, the node's children should be
+ * parsed just like any other node, and fed as a second argument
+ * to `parsefn`. If not, the node's children are either to be ignored
+ * (as in
) or processed specially (as in
).
+ *
+ * The `parsefn` combines a node's data and its children into
+ * an internal representation node.
+ */
const nodes = buildNodeMap();
function parseNode(node) {
@@ -95,6 +117,7 @@ function parseNodes(nodes) {
const parsed = [];
for (let i = 0; i < len; i ++) {
let node = parseNode(nodes[i]);
+ // Just ignore invalid / unknown tags.
if (node) {
parsed.push(node);
}
From 8f44cc21db4398af26ff93521a0be44d6bd213df Mon Sep 17 00:00:00 2001
From: Danila Fedorin
code
!";
+ const output = [
+ new TextPart("Here's "),
+ new FormatPart("em", [new TextPart("some")]),
+ new TextPart(" "),
+ new FormatPart("code", [new TextPart("code")]),
+ new TextPart("!")
+ ];
+ test(assert, input, output);
+ },
+ "Text with ordered list with no attributes": assert => {
+ const input = "
";
+ const output = [
+ new ListBlock(1, [
+ [ new TextPart("Lorem") ],
+ [ new TextPart("Ipsum") ]
+ ])
+ ];
+ test(assert, input, output);
+ },
+ "Text with ordered list starting at 3": assert => {
+ const input = '
';
+ const output = [
+ new ListBlock(3, [
+ [ new TextPart("Lorem") ],
+ [ new TextPart("Ipsum") ]
+ ])
+ ];
+ test(assert, input, output);
+ },
+ "Text with unordered list": assert => {
+ const input = '
';
+ const output = [
+ new ListBlock(null, [
+ [ new TextPart("Lorem") ],
+ [ new TextPart("Ipsum") ]
+ ])
+ ];
+ test(assert, input, output);
+ },
+ /* Doesnt work: HTML library doesn't handle properly.
+ "Text with code block": assert => {
+ const code = 'main :: IO ()\nmain = putStrLn "Hello"'
+ const input = `
`;
+ const output = [
+ new CodeBlock(null, code)
+ ];
+ test(assert, input, output);
+ }
+ */
+ };
+}
From eca5308742f239a747bed4fede699ab1255e912f Mon Sep 17 00:00:00 2001
From: Danila Fedorin ${code}
) or processed specially (as in
).
- *
- * The `parsefn` combines a node's data and its children into
- * an internal representation node.
- */
-const nodes = buildNodeMap();
-
-function parseNode(options, node) {
- const { result } = options;
- if (result.isTextNode(node)) {
- return new TextPart(result.getNodeText(node));
- } else if (result.isElementNode(node)) {
- const f = nodes[result.getNodeElementName(node)];
- if (!f) {
+ if (!(codeNode && result.getNodeElementName(codeNode) === "CODE")) {
return null;
}
- const children = f.descend ? parseNodes(options, node.childNodes) : null;
- return f.parsefn(options, node, children);
+ let language = "";
+ const cl = result.getAttributeValue(codeNode, "class") || ""
+ for (const clname of cl.split(" ")) {
+ if (clname.startsWith("language-") && !clname.startsWith("language-_")) {
+ language = clname.substring(9) // "language-".length
+ break;
+ }
+ }
+ return new CodeBlock(language, codeNode.textContent);
}
- return null;
-}
-function parseNodes(options, nodes) {
- const parsed = [];
- for (const htmlNode of nodes) {
- let node = parseNode(options, htmlNode);
- // Just ignore invalid / unknown tags.
- if (node) {
- parsed.push(node);
+ parseImage(node) {
+ const result = this.result;
+ const src = result.getAttributeValue(node, "src") || "";
+ const url = this.mediaRepository.mxcUrl(src);
+ // We just ignore non-mxc `src` attributes.
+ if (!url) {
+ return null;
+ }
+ const width = result.getAttributeValue(node, "width");
+ const height = result.getAttributeValue(node, "height");
+ const alt = result.getAttributeValue(node, "alt");
+ const title = result.getAttributeValue(node, "title");
+ return new ImagePart(url, { width, height, alt, title });
+ }
+
+ parseElement(node) {
+ const result = this.result;
+ const tag = result.getNodeElementName(node);
+ switch (tag) {
+ case "H1":
+ case "H2":
+ case "H3":
+ case "H4":
+ case "H5":
+ case "H6": {
+ const children = this.parseChildNodes(node);
+ return new HeaderBlock(parseInt(tag[1]), children)
+ }
+ case "A": {
+ const children = this.parseChildNodes(node);
+ return this.parseLink(node, children);
+ }
+ case "UL":
+ case "OL":
+ return this.parseList(node);
+ case "PRE":
+ return this.parseCodeBlock(node);
+ case "BR":
+ return new NewLinePart();
+ case "HR":
+ return new RulePart();
+ case "IMG":
+ return this.parseImage(node);
+ default: {
+ if (!basicNodes.includes(tag)) {
+ return null;
+ }
+ const children = this.parseChildNodes(node);
+ return new FormatPart(tag, children);
+ }
}
}
- return parsed;
+
+ parseNode(node) {
+ const result = this.result;
+ if (result.isTextNode(node)) {
+ return new TextPart(result.getNodeText(node));
+ } else if (result.isElementNode(node)) {
+ return this.parseElement(node);
+ }
+ return null;
+ }
+
+ parseChildNodes(node) {
+ const childNodes = this.result.getChildNodes(node);
+ return this.parseNodes(childNodes);
+ }
+
+ parseNodes(nodes) {
+ const parsed = [];
+ for (const htmlNode of nodes) {
+ let node = this.parseNode(htmlNode);
+ // Just ignore invalid / unknown tags.
+ if (node) {
+ parsed.push(node);
+ }
+ }
+ return parsed;
+ }
}
const sanitizeConfig = {
@@ -170,8 +167,8 @@ const sanitizeConfig = {
export function parseHTMLBody({ mediaRepository, platform }, html) {
const parseResult = platform.parseHTML(sanitizeHtml(html, sanitizeConfig));
- const options = { result: parseResult, mediaRepository };
- const parts = parseNodes(options, parseResult.rootNodes);
+ const deserializer = new Deserializer(parseResult, mediaRepository);
+ const parts = deserializer.parseNodes(parseResult.rootNodes);
return new MessageBody(html, parts);
}
From c261b9fb2367026832737545477483e626bdc93e Mon Sep 17 00:00:00 2001
From: Danila Fedorin
properly.
"Text with code block": assert => {
const code = 'main :: IO ()\nmain = putStrLn "Hello"'
From 5e39eb8f6c615552d32ce86a586feac99573261d Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Tue, 13 Jul 2021 14:47:10 -0700
Subject: [PATCH 34/69] Do some additional validation, blocking block nodes
inside inline nodes.
---
.../session/room/timeline/deserialize.js | 149 ++++++++++++++----
1 file changed, 119 insertions(+), 30 deletions(-)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index 33ae7c13..603cb438 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -10,7 +10,8 @@ import { MessageBody, HeaderBlock, ListBlock, CodeBlock, FormatPart, NewLinePart
* Nodes that don't have any properties to them other than their tag.
* While has `href`, and
has `src`, these have... themselves.
*/
-const basicNodes = ["EM", "STRONG", "CODE", "DEL", "P", "DIV", "SPAN" ]
+const basicInline = ["EM", "STRONG", "CODE", "DEL", "SPAN" ];
+const basicBlock = ["DIV"];
class Deserializer {
constructor(result, mediaRepository) {
@@ -21,7 +22,7 @@ class Deserializer {
parseLink(node, children) {
// TODO Not equivalent to `node.href`!
// Add another HTMLParseResult method?
- let href = this.result.getAttributeValue(node, "href");
+ const href = this.result.getAttributeValue(node, "href");
return new LinkPart(href, children);
}
@@ -37,7 +38,7 @@ class Deserializer {
if (result.getNodeElementName(child) !== "LI") {
continue;
}
- const item = this.parseNodes(result.getChildNodes(child));
+ const item = this.parseAnyNodes(result.getChildNodes(child));
nodes.push(item);
}
return new ListBlock(start, nodes);
@@ -79,9 +80,59 @@ class Deserializer {
return new ImagePart(url, width, height, alt, title);
}
- parseElement(node) {
+ /** Once a node is known to be an element,
+ * attempt to interpret it as an inline element.
+ *
+ * @returns the inline message part, or null if the element
+ * is not inline or not allowed.
+ */
+ parseInlineElement(node) {
const result = this.result;
const tag = result.getNodeElementName(node);
+ const children = result.getChildNodes(node);
+ switch (tag) {
+ case "A": {
+ const inlines = this.parseInlineNodes(children);
+ return this.parseLink(node, inlines);
+ }
+ case "BR":
+ return new NewLinePart();
+ default: {
+ if (!basicInline.includes(tag)) {
+ return null;
+ }
+ const inlines = this.parseInlineNodes(children);
+ return new FormatPart(tag, inlines);
+ }
+ }
+ }
+
+ /** Attempt to interpret a node as inline.
+ *
+ * @returns the inline message part, or null if the
+ * element is not inline or not allowed.
+ */
+ parseInlineNode(node) {
+ const result = this.result;
+ if (result.isTextNode(node)) {
+ // TODO Linkify
+ return new TextPart(result.getNodeText(node));
+ } else if (result.isElementNode(node)) {
+ return this.parseInlineElement(node);
+ }
+ return null;
+ }
+
+ /** Once a node is known to be an element, attempt
+ * to interpret it as a block element.
+ *
+ * @returns the block message part, or null of the
+ * element is not a block or not allowed.
+ */
+ parseBlockElement(node) {
+ const result = this.result;
+ const tag = result.getNodeElementName(node);
+ const children = result.getChildNodes(node);
switch (tag) {
case "H1":
case "H2":
@@ -89,66 +140,87 @@ class Deserializer {
case "H4":
case "H5":
case "H6": {
- const children = this.parseChildNodes(node);
- return new HeaderBlock(parseInt(tag[1]), children)
- }
- case "A": {
- const children = this.parseChildNodes(node);
- return this.parseLink(node, children);
+ const inlines = this.parseInlineNodes(children);
+ return new HeaderBlock(parseInt(tag[1]), inlines)
}
case "UL":
case "OL":
return this.parseList(node);
case "PRE":
return this.parseCodeBlock(node);
- case "BR":
- return new NewLinePart();
case "HR":
return new RulePart();
case "IMG":
return this.parseImage(node);
+ case "P": {
+ const inlines = this.parseInlineNodes(children);
+ return new FormatPart(tag, inlines);
+ }
default: {
- if (!basicNodes.includes(tag)) {
+ if (!basicBlock.includes(tag)) {
return null;
}
- const children = this.parseChildNodes(node);
- return new FormatPart(tag, children);
+ const blocks = this.parseAnyNodes(children);
+ return new FormatPart(tag, blocks);
}
}
}
- parseNode(node) {
+ /** Attempt to parse a node as a block.
+ *
+ * @return the block message part, or null if the node
+ * is not a block element.
+ */
+ parseBlockNode(node) {
const result = this.result;
- if (result.isTextNode(node)) {
- return new TextPart(result.getNodeText(node));
- } else if (result.isElementNode(node)) {
- return this.parseElement(node);
+ if (result.isElementNode(node)) {
+ return this.parseBlockElement(node);
}
return null;
}
- parseChildNodes(node) {
- const childNodes = this.result.getChildNodes(node);
- return this.parseNodes(childNodes);
+ _parseInlineNodes(nodes, into) {
+ for (const htmlNode of nodes) {
+ const node = this.parseInlineNode(htmlNode);
+ if (node) {
+ into.push(node);
+ continue;
+ }
+ // Node is either block or unrecognized. In
+ // both cases, just move on to its children.
+ this._parseInlineNodes(this.result.getChildNodes(htmlNode), into);
+ }
}
- parseNodes(nodes) {
- const parsed = [];
+ parseInlineNodes(nodes) {
+ const into = [];
+ this._parseInlineNodes(nodes, into);
+ return into;
+ }
+
+ _parseAnyNodes(nodes, into) {
for (const htmlNode of nodes) {
- let node = this.parseNode(htmlNode);
- // Just ignore invalid / unknown tags.
+ const node = this.parseInlineNode(htmlNode) || this.parseBlockNode(htmlNode);
if (node) {
- parsed.push(node);
+ into.push(node);
+ continue;
}
+ // Node is unrecognized. Just move on to its children.
+ this._parseAnyNodes(this.result.getChildNodes(htmlNode), into);
}
- return parsed;
+ }
+
+ parseAnyNodes(nodes) {
+ const into = [];
+ this._parseAnyNodes(nodes, into);
+ return into;
}
}
export function parseHTMLBody(platform, mediaRepository, html) {
const parseResult = platform.parseHTML(html);
const deserializer = new Deserializer(parseResult, mediaRepository);
- const parts = deserializer.parseNodes(parseResult.rootNodes);
+ const parts = deserializer.parseAnyNodes(parseResult.rootNodes);
return new MessageBody(html, parts);
}
@@ -256,6 +328,23 @@ export function tests() {
];
test(assert, input, output);
},
+ "Block elements ignored inside inline elements": assert => {
+ const input = 'Hello
';
+ const output = [
+ new FormatPart("span", [new FormatPart("code", [new TextPart("Hello")])])
+ ];
+ test(assert, input, output);
+ },
+ "Unknown tags are ignored, but their children are kept": assert => {
+ const input = 'Hello
';
+ const output = [
+ new FormatPart("span", [
+ new FormatPart("code", [new TextPart("Hello")]),
+ new FormatPart("em", [new TextPart("World")])
+ ])
+ ];
+ test(assert, input, output);
+ },
/* Doesnt work: HTML library doesn't handle properly.
"Text with code block": assert => {
const code = 'main :: IO ()\nmain = putStrLn "Hello"'
From 0c05ff459c436d35ea68365d03ec4c38a6f0964b Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Tue, 13 Jul 2021 15:14:03 -0700
Subject: [PATCH 35/69] Add another test.
---
src/domain/session/room/timeline/deserialize.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index 603cb438..02e099b8 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -345,6 +345,13 @@ export function tests() {
];
test(assert, input, output);
},
+ "Unknown and invalid attributes are stripped": assert => {
+ const input = 'Hello';
+ const output = [
+ new FormatPart("em", [new TextPart("Hello")])
+ ];
+ test(assert, input, output);
+ },
/* Doesnt work: HTML library doesn't handle properly.
"Text with code block": assert => {
const code = 'main :: IO ()\nmain = putStrLn "Hello"'
From 763e1cd5de7ca7821ea94a678506d57c8fe9332b Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Tue, 13 Jul 2021 16:56:46 -0700
Subject: [PATCH 36/69] Add tentative version of linkification.
---
.../session/room/timeline/deserialize.js | 38 +++++++++++++++----
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index 02e099b8..dc8a2715 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -1,4 +1,5 @@
import { MessageBody, HeaderBlock, ListBlock, CodeBlock, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
+import { linkify } from "./linkify/linkify.js";
/* At the time of writing (Jul 1 2021), Matrix Spec recommends
* allowing the following HTML tags:
@@ -113,11 +114,7 @@ class Deserializer {
* element is not inline or not allowed.
*/
parseInlineNode(node) {
- const result = this.result;
- if (result.isTextNode(node)) {
- // TODO Linkify
- return new TextPart(result.getNodeText(node));
- } else if (result.isElementNode(node)) {
+ if (this.result.isElementNode(node)) {
return this.parseInlineElement(node);
}
return null;
@@ -172,15 +169,36 @@ class Deserializer {
* is not a block element.
*/
parseBlockNode(node) {
- const result = this.result;
- if (result.isElementNode(node)) {
+ if (this.result.isElementNode(node)) {
return this.parseBlockElement(node);
}
return null;
}
+ _parseTextParts(node, into) {
+ if(!this.result.isTextNode(node)) {
+ return false;
+ }
+
+ // XXX pretty much identical to `MessageBody`'s.
+ const linkifyCallback = (text, isLink) => {
+ if (isLink) {
+ into.push(new LinkPart(text, [new TextPart(text)]));
+ } else {
+ into.push(new TextPart(text));
+ }
+ };
+ linkify(this.result.getNodeText(node), linkifyCallback);
+ return true;
+ }
+
_parseInlineNodes(nodes, into) {
for (const htmlNode of nodes) {
+ if (this._parseTextParts(htmlNode, into)) {
+ // This was a text node, and we already
+ // dumped its parts into our list.
+ continue;
+ }
const node = this.parseInlineNode(htmlNode);
if (node) {
into.push(node);
@@ -198,8 +216,14 @@ class Deserializer {
return into;
}
+ // XXX very similar to `_parseInlineNodes`.
_parseAnyNodes(nodes, into) {
for (const htmlNode of nodes) {
+ if (this._parseTextParts(htmlNode, into)) {
+ // This was a text node, and we already
+ // dumped its parts into our list.
+ continue;
+ }
const node = this.parseInlineNode(htmlNode) || this.parseBlockNode(htmlNode);
if (node) {
into.push(node);
From 78d7d556e4d5ceef31dbea1c53ba5cecfda049df Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Wed, 14 Jul 2021 12:38:55 -0700
Subject: [PATCH 37/69] Add blockquote and del elements.
---
src/domain/session/room/timeline/deserialize.js | 2 +-
src/platform/web/ui/general/html.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index dc8a2715..68a08c9d 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -12,7 +12,7 @@ import { linkify } from "./linkify/linkify.js";
* While has `href`, and
has `src`, these have... themselves.
*/
const basicInline = ["EM", "STRONG", "CODE", "DEL", "SPAN" ];
-const basicBlock = ["DIV"];
+const basicBlock = ["DIV", "BLOCKQUOTE"];
class Deserializer {
constructor(result, mediaRepository) {
diff --git a/src/platform/web/ui/general/html.js b/src/platform/web/ui/general/html.js
index e4d6b383..78a1e83d 100644
--- a/src/platform/web/ui/general/html.js
+++ b/src/platform/web/ui/general/html.js
@@ -93,7 +93,7 @@ export const SVG_NS = "http://www.w3.org/2000/svg";
export const TAG_NAMES = {
[HTML_NS]: [
"br", "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6",
- "p", "strong", "em", "span", "img", "section", "main", "article", "aside",
+ "p", "strong", "em", "span", "img", "section", "main", "article", "aside", "del", "blockquote",
"pre", "code", "button", "time", "input", "textarea", "label", "form", "progress", "output", "video"],
[SVG_NS]: ["svg", "circle"]
};
From 038b101ed7e4accdf0cc436318d27edd9ac2dcb4 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Wed, 14 Jul 2021 17:57:53 -0700
Subject: [PATCH 38/69] Render matrix.to user links as pills
---
.../session/room/timeline/MessageBody.js | 19 +++++++++++++++++++
.../session/room/timeline/deserialize.js | 7 ++++++-
src/domain/session/room/timeline/pills.js | 12 ++++++++++++
src/platform/web/ui/css/avatar.css | 8 ++++++++
.../web/ui/css/themes/element/timeline.css | 15 +++++++++++++++
.../session/room/timeline/TextMessageView.js | 11 ++++++++++-
6 files changed, 70 insertions(+), 2 deletions(-)
create mode 100644 src/domain/session/room/timeline/pills.js
diff --git a/src/domain/session/room/timeline/MessageBody.js b/src/domain/session/room/timeline/MessageBody.js
index 325c79e1..5f346017 100644
--- a/src/domain/session/room/timeline/MessageBody.js
+++ b/src/domain/session/room/timeline/MessageBody.js
@@ -1,4 +1,5 @@
import { linkify } from "./linkify/linkify.js";
+import { getIdentifierColorNumber, avatarInitials } from "../../../avatar.js";
/**
* Parse text into parts such as newline, links and text.
@@ -92,6 +93,24 @@ export class ImagePart {
get type() { return "image"; }
};
+export class PillPart {
+ constructor(id, href, children) {
+ this.id = id;
+ this.href = href;
+ this.children = children;
+ }
+
+ get type() { return "pill"; }
+
+ get avatarColorNumber() {
+ return getIdentifierColorNumber(this.id);
+ }
+
+ get avatarInitials() {
+ return avatarInitials(this.id);
+ }
+}
+
export class LinkPart {
constructor(url, inlines) {
this.url = url;
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index 68a08c9d..c02c4117 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -1,5 +1,6 @@
-import { MessageBody, HeaderBlock, ListBlock, CodeBlock, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
+import { MessageBody, HeaderBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
import { linkify } from "./linkify/linkify.js";
+import { parsePillLink } from "./pills.js"
/* At the time of writing (Jul 1 2021), Matrix Spec recommends
* allowing the following HTML tags:
@@ -24,6 +25,10 @@ class Deserializer {
// TODO Not equivalent to `node.href`!
// Add another HTMLParseResult method?
const href = this.result.getAttributeValue(node, "href");
+ const pillData = href && parsePillLink(href);
+ if (pillData && pillData.userId) {
+ return new PillPart(pillData.userId, href, children);
+ }
return new LinkPart(href, children);
}
diff --git a/src/domain/session/room/timeline/pills.js b/src/domain/session/room/timeline/pills.js
new file mode 100644
index 00000000..a638ea58
--- /dev/null
+++ b/src/domain/session/room/timeline/pills.js
@@ -0,0 +1,12 @@
+const baseUrl = 'https://matrix.to';
+const linkPrefix = `${baseUrl}/#/`;
+
+export function parsePillLink(link) {
+ if (!link.startsWith(linkPrefix)) {
+ return null;
+ }
+ const contents = link.substring(linkPrefix.length);
+ if (contents[0] === '@') {
+ return { userId: contents }
+ }
+}
diff --git a/src/platform/web/ui/css/avatar.css b/src/platform/web/ui/css/avatar.css
index d369f85f..143ea899 100644
--- a/src/platform/web/ui/css/avatar.css
+++ b/src/platform/web/ui/css/avatar.css
@@ -76,3 +76,11 @@ limitations under the License.
line-height: var(--avatar-size);
font-size: calc(var(--avatar-size) * 0.6);
}
+
+.hydrogen .avatar.size-12 {
+ --avatar-size: 12px;
+ width: var(--avatar-size);
+ height: var(--avatar-size);
+ line-height: var(--avatar-size);
+ font-size: calc(var(--avatar-size) * 0.6);
+}
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 07afdf16..e5490570 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -216,6 +216,21 @@ only loads when the top comes into view*/
padding: 10px;
}
+.Timeline_messageBody .pill {
+ padding: 0px 5px 0px 5px;
+ border-radius: 15px;
+ background-color: #f6f6f6;
+ border: 1px solid rgb(206, 206, 206);
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+}
+
+.Timeline_messageBody .pill div.avatar {
+ display: inline-block;
+ margin-right: 3px;
+}
+
.Timeline_message.unsent .Timeline_messageBody {
color: #ccc;
}
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index e244ab3a..ff674753 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -15,7 +15,7 @@ limitations under the License.
*/
import {StaticView} from "../../../general/StaticView.js";
-import {tag, text} from "../../../general/html.js";
+import {tag, text, classNames} from "../../../general/html.js";
import {BaseMessageView} from "./BaseMessageView.js";
export class TextMessageView extends BaseMessageView {
@@ -53,6 +53,14 @@ function renderImage(imagePart) {
return tag.img(attributes, []);
}
+function renderPill(pillPart) {
+ const classes = `avatar size-12 usercolor${pillPart.avatarColorNumber}`;
+ const avatar = tag.div({class: classes}, text(pillPart.avatarInitials));
+ const children = renderParts(pillPart.children);
+ children.unshift(avatar);
+ return tag.a({ class: "pill", href: pillPart.href }, children);
+}
+
/**
* Map from part to function that outputs DOM for the part
*/
@@ -63,6 +71,7 @@ const formatFunction = {
code: codePart => tag.code({}, text(codePart.text)),
text: textPart => text(textPart.text),
link: linkPart => tag.a({ href: linkPart.url, target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
+ pill: renderPill,
format: formatPart => tag[formatPart.format]({}, renderParts(formatPart.children)),
list: renderList,
image: renderImage,
From 025ab166684e4ef98c453f096dae1d1717e806f3 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Wed, 14 Jul 2021 18:10:59 -0700
Subject: [PATCH 39/69] Fix /me body rendering.
---
src/domain/session/room/timeline/tiles/TextTile.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/domain/session/room/timeline/tiles/TextTile.js b/src/domain/session/room/timeline/tiles/TextTile.js
index f94bc18f..ec91ab52 100644
--- a/src/domain/session/room/timeline/tiles/TextTile.js
+++ b/src/domain/session/room/timeline/tiles/TextTile.js
@@ -26,7 +26,7 @@ export class TextTile extends BaseTextTile {
return null;
}
if (content.msgtype === "m.emote") {
- val = `* ${this.displayName} ${body}`;
+ val = `* ${this.displayName} ${val}`;
}
return val;
}
From ba7e86ac8daff7a483560124131a460f769e6911 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Wed, 14 Jul 2021 18:11:09 -0700
Subject: [PATCH 40/69] Add blocquote style.
---
src/platform/web/ui/css/themes/element/timeline.css | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index e5490570..735175b9 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -216,6 +216,12 @@ only loads when the top comes into view*/
padding: 10px;
}
+.Timeline_messageBody blockquote {
+ margin-left: 0;
+ padding-left: 20px;
+ border-left: 4px solid rgb(229, 229, 229);
+}
+
.Timeline_messageBody .pill {
padding: 0px 5px 0px 5px;
border-radius: 15px;
From c13daedcb0e12ab3faeefd32c03f3863d806e925 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Thu, 15 Jul 2021 13:15:56 -0700
Subject: [PATCH 41/69] Ignore non-absolute links.
---
src/domain/session/room/timeline/deserialize.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index c02c4117..4dbb1109 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -22,10 +22,13 @@ class Deserializer {
}
parseLink(node, children) {
- // TODO Not equivalent to `node.href`!
- // Add another HTMLParseResult method?
const href = this.result.getAttributeValue(node, "href");
- const pillData = href && parsePillLink(href);
+ if (!href || !href.match(/^[a-z]+:[\/]{2}/i)) {
+ // Invalid or missing URLs are not turned into links
+ // We throw away relative links, too.
+ return new FormatPart("span", children);
+ }
+ const pillData = parsePillLink(href);
if (pillData && pillData.userId) {
return new PillPart(pillData.userId, href, children);
}
From f6f29adacc408d86750820ac3a4bdf165d7b7f3f Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Thu, 15 Jul 2021 13:16:42 -0700
Subject: [PATCH 42/69] Add a comment about avatar.js
---
src/platform/web/ui/session/room/timeline/TextMessageView.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index ff674753..091947f0 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -54,6 +54,9 @@ function renderImage(imagePart) {
}
function renderPill(pillPart) {
+ // The classes and structure are borrowed from avatar.js;
+ // We don't call renderStaticAvatar because that would require
+ // an intermediate object that has getAvatarUrl etc.
const classes = `avatar size-12 usercolor${pillPart.avatarColorNumber}`;
const avatar = tag.div({class: classes}, text(pillPart.avatarInitials));
const children = renderParts(pillPart.children);
From d69b78469c96f92a8512d678c48e4e872ef9e87e Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Tue, 13 Jul 2021 16:33:14 -0700
Subject: [PATCH 43/69] Add table viewing code.
---
.../session/room/timeline/MessageBody.js | 9 +++
.../session/room/timeline/deserialize.js | 62 ++++++++++++++++++-
src/platform/web/ui/general/html.js | 1 +
.../session/room/timeline/TextMessageView.js | 17 +++++
4 files changed, 87 insertions(+), 2 deletions(-)
diff --git a/src/domain/session/room/timeline/MessageBody.js b/src/domain/session/room/timeline/MessageBody.js
index 5f346017..b739692c 100644
--- a/src/domain/session/room/timeline/MessageBody.js
+++ b/src/domain/session/room/timeline/MessageBody.js
@@ -64,6 +64,15 @@ export class ListBlock {
get type() { return "list"; }
}
+export class TableBlock {
+ constructor(head, body) {
+ this.head = head;
+ this.body = body;
+ }
+
+ get type() { return "table"; }
+}
+
export class RulePart {
get type( ) { return "rule"; }
}
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index 4dbb1109..53c1291c 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -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 { parsePillLink } from "./pills.js"
@@ -53,6 +53,12 @@ class Deserializer {
return new ListBlock(start, nodes);
}
+ _ensureElement(node, tag) {
+ return node &&
+ this.result.isElementNode(node) &&
+ this.result.getNodeElementName(node) === tag;
+ }
+
parseCodeBlock(node) {
const result = this.result;
let codeNode;
@@ -60,7 +66,7 @@ class Deserializer {
codeNode = child;
break;
}
- if (!(codeNode && result.getNodeElementName(codeNode) === "CODE")) {
+ if (!this._ensureElement(codeNode, "CODE")) {
return null;
}
let language = "";
@@ -89,6 +95,56 @@ class Deserializer {
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,
* attempt to interpret it as an inline element.
*
@@ -161,6 +217,8 @@ class Deserializer {
const inlines = this.parseInlineNodes(children);
return new FormatPart(tag, inlines);
}
+ case "TABLE":
+ return this.parseTable(node);
default: {
if (!basicBlock.includes(tag)) {
return null;
diff --git a/src/platform/web/ui/general/html.js b/src/platform/web/ui/general/html.js
index 78a1e83d..4be81e3b 100644
--- a/src/platform/web/ui/general/html.js
+++ b/src/platform/web/ui/general/html.js
@@ -94,6 +94,7 @@ export const TAG_NAMES = {
[HTML_NS]: [
"br", "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6",
"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"],
[SVG_NS]: ["svg", "circle"]
};
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index 091947f0..95d7db75 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -64,12 +64,29 @@ function renderPill(pillPart) {
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
*/
const formatFunction = {
header: headerBlock => tag["h" + Math.min(6,headerBlock.level)]({}, renderParts(headerBlock.inlines)),
codeblock: codeBlock => tag.pre({}, tag.code({}, text(codeBlock.text))),
+ table: tableBlock => renderTable(tableBlock),
emph: emphPart => tag.em({}, renderParts(emphPart.inlines)),
code: codePart => tag.code({}, text(codePart.text)),
text: textPart => text(textPart.text),
From c619eba8cf9178e968300851b3ca0b4d4798baae Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Wed, 14 Jul 2021 22:29:37 -0700
Subject: [PATCH 44/69] Add some styling to the tables.
---
.../web/ui/css/themes/element/timeline.css | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 735175b9..39710239 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -222,6 +222,24 @@ only loads when the top comes into view*/
border-left: 4px solid rgb(229, 229, 229);
}
+.Timeline_messageBody table {
+ border: 1px solid rgb(206, 206, 206);
+ border-radius: 2px;
+ border-spacing: 0;
+}
+
+.Timeline_messageBody thead th {
+ border-bottom: 1px solid rgb(206, 206, 206);
+}
+
+.Timeline_messageBody td, .Timeline_messageBody th {
+ padding: 2px 5px 2px 5px;
+}
+
+.Timeline_messageBody tbody tr:nth-child(2n) {
+ background-color: #f6f6f6;
+}
+
.Timeline_messageBody .pill {
padding: 0px 5px 0px 5px;
border-radius: 15px;
From f012c64fc5be44652bf1d9cd45fae8ffd7c1cdda Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 16:27:22 +0200
Subject: [PATCH 45/69] fix pill styling a bit, and open in new tab/noopener
---
src/platform/web/ui/css/themes/element/timeline.css | 5 ++++-
src/platform/web/ui/session/room/timeline/TextMessageView.js | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 39710239..5bfb1b46 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -241,13 +241,16 @@ only loads when the top comes into view*/
}
.Timeline_messageBody .pill {
- padding: 0px 5px 0px 5px;
+ padding: 0px 5px;
border-radius: 15px;
background-color: #f6f6f6;
border: 1px solid rgb(206, 206, 206);
text-decoration: none;
display: inline-flex;
align-items: center;
+ line-height: 2rem;
+ vertical-align: top;
+ margin: 1px;
}
.Timeline_messageBody .pill div.avatar {
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index 95d7db75..272cab69 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -61,7 +61,7 @@ function renderPill(pillPart) {
const avatar = tag.div({class: classes}, text(pillPart.avatarInitials));
const children = renderParts(pillPart.children);
children.unshift(avatar);
- return tag.a({ class: "pill", href: pillPart.href }, children);
+ return tag.a({class: "pill", href: pillPart.href, rel: "noopener", target: "_blank"}, children);
}
function renderTable(tablePart) {
From 2159b67ba4765ad7ec6ab365fafea69969bce6f4 Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 16:27:43 +0200
Subject: [PATCH 46/69] make link styling like element, blue an no underline
---
src/platform/web/ui/css/themes/element/timeline.css | 5 +++++
src/platform/web/ui/session/room/timeline/TextMessageView.js | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 5bfb1b46..ddbeabc4 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -141,6 +141,11 @@ limitations under the License.
word-break: break-all;
}
+.Timeline_messageBody a.link {
+ color: #238cf5;
+ text-decoration: none;
+}
+
.Timeline_messageBody .media {
display: grid;
margin-top: 4px;
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index 272cab69..bf743f31 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -90,7 +90,7 @@ const formatFunction = {
emph: emphPart => tag.em({}, renderParts(emphPart.inlines)),
code: codePart => tag.code({}, text(codePart.text)),
text: textPart => text(textPart.text),
- link: linkPart => tag.a({ href: linkPart.url, target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
+ link: linkPart => tag.a({href: linkPart.url, className: "link", target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
pill: renderPill,
format: formatPart => tag[formatPart.format]({}, renderParts(formatPart.children)),
list: renderList,
From 47c1737371bbbeecce20eec2a8e75bc1d3ee40da Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 16:28:03 +0200
Subject: [PATCH 47/69] make styling like element
---
src/platform/web/ui/css/themes/element/timeline.css | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index ddbeabc4..07384aec 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -214,9 +214,18 @@ only loads when the top comes into view*/
/* don't stretch height as it is a spacer, just in case it doesn't match with image height */
align-self: start;
}
+.Timeline_messageBody code, .Timeline_messageBody pre {
+ background-color: #f8f8f8;
+ font-family: monospace;
+}
+
+.Timeline_messageBody code {
+ border-radius: 3px;
+ padding: .2em .3em;
+ margin: 0;
+}
.Timeline_messageBody pre {
- background-color: #f7f7f7;
border: 1px solid rgb(229, 229, 229);
padding: 10px;
}
From 1f82aef4ad4ad247cd238de4a00ff55fcf2bc8d0 Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 16:53:33 +0200
Subject: [PATCH 48/69] some header styling in messages
---
src/platform/web/ui/css/themes/element/timeline.css | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 07384aec..55aa54db 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -137,6 +137,18 @@ limitations under the License.
.hydrogen .Timeline_messageSender.usercolor7 { color: var(--usercolor7); }
.hydrogen .Timeline_messageSender.usercolor8 { color: var(--usercolor8); }
+.Timeline_messageBody h1, h2, h3, h4, h5, h6 {
+ font-weight: bold;
+ margin: 0.7em 0;
+}
+
+.Timeline_messageBody h1 { font-size: 1.6em; }
+.Timeline_messageBody h2 { font-size: 1.5em; }
+.Timeline_messageBody h3 { font-size: 1.4em; }
+.Timeline_messageBody h4 { font-size: 1.3em; }
+.Timeline_messageBody h5 { font-size: 1.2em; }
+.Timeline_messageBody h6 { font-size: 1.1em; }
+
.Timeline_messageBody a {
word-break: break-all;
}
From fe3bdda05afa3fd8a16e207dd63e7be2fae9f69b Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 17:27:24 +0200
Subject: [PATCH 49/69] all of these should be scoped to messageBody
---
src/platform/web/ui/css/themes/element/timeline.css | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 55aa54db..9f9ff973 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -137,7 +137,12 @@ limitations under the License.
.hydrogen .Timeline_messageSender.usercolor7 { color: var(--usercolor7); }
.hydrogen .Timeline_messageSender.usercolor8 { color: var(--usercolor8); }
-.Timeline_messageBody h1, h2, h3, h4, h5, h6 {
+.Timeline_messageBody h1,
+.Timeline_messageBody h2,
+.Timeline_messageBody h3,
+.Timeline_messageBody h4,
+.Timeline_messageBody h5,
+.Timeline_messageBody h6 {
font-weight: bold;
margin: 0.7em 0;
}
From b5b19abb240b8c885b7e0831ef60fab783ee08c2 Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 18:32:40 +0200
Subject: [PATCH 50/69] only allow links for the schemas mentioned in the spec
---
src/domain/session/room/timeline/deserialize.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index 53c1291c..d477853d 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -14,6 +14,7 @@ import { parsePillLink } from "./pills.js"
*/
const basicInline = ["EM", "STRONG", "CODE", "DEL", "SPAN" ];
const basicBlock = ["DIV", "BLOCKQUOTE"];
+const safeSchemas = ["https", "http", "ftp", "mailto", "magnet"].map(name => `${name}://`);
class Deserializer {
constructor(result, mediaRepository) {
@@ -23,9 +24,9 @@ class Deserializer {
parseLink(node, children) {
const href = this.result.getAttributeValue(node, "href");
- if (!href || !href.match(/^[a-z]+:[\/]{2}/i)) {
- // Invalid or missing URLs are not turned into links
- // We throw away relative links, too.
+ const lcUrl = href?.toLowerCase();
+ // urls should be absolute and with a safe schema, as listed in the spec
+ if (!lcUrl || !safeSchemas.some(schema => lcUrl.startsWith(schema))) {
return new FormatPart("span", children);
}
const pillData = parsePillLink(href);
From 2ce6cea4ff2a408182e3b0e0727d3518cdf999d4 Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 19:16:06 +0200
Subject: [PATCH 51/69] not used anymore
---
src/platform/web/ui/session/room/timeline/TextMessageView.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index bf743f31..e361ac05 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -87,7 +87,6 @@ const formatFunction = {
header: headerBlock => tag["h" + Math.min(6,headerBlock.level)]({}, renderParts(headerBlock.inlines)),
codeblock: codeBlock => tag.pre({}, tag.code({}, text(codeBlock.text))),
table: tableBlock => renderTable(tableBlock),
- emph: emphPart => tag.em({}, renderParts(emphPart.inlines)),
code: codePart => tag.code({}, text(codePart.text)),
text: textPart => text(textPart.text),
link: linkPart => tag.a({href: linkPart.url, className: "link", target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
From 629d58b041a827826ab69b1c838727e1379576f2 Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 19:16:15 +0200
Subject: [PATCH 52/69] limit code block height
---
src/platform/web/ui/css/themes/element/timeline.css | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 9f9ff973..9167005b 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -244,7 +244,15 @@ only loads when the top comes into view*/
.Timeline_messageBody pre {
border: 1px solid rgb(229, 229, 229);
- padding: 10px;
+ padding: 0.5em;
+ max-height: 30em;
+ overflow: auto;
+}
+
+.Timeline_messageBody pre > code {
+ background-color: unset;
+ border-radius: unset;
+ display: block;
}
.Timeline_messageBody blockquote {
From 9aedc1d5267693f0743d970b59cee2db189421e4 Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 19:30:02 +0200
Subject: [PATCH 53/69] don't put body parts in span
---
.../session/room/timeline/TextMessageView.js | 29 +++++++++----------
1 file changed, 13 insertions(+), 16 deletions(-)
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index e361ac05..5988cb44 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -14,21 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {StaticView} from "../../../general/StaticView.js";
import {tag, text, classNames} from "../../../general/html.js";
import {BaseMessageView} from "./BaseMessageView.js";
export class TextMessageView extends BaseMessageView {
renderMessageBody(t, vm) {
- return t.p({
+ const time = t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time);
+ const container = t.div({
className: {
"Timeline_messageBody": true,
statusMessage: vm => vm.shape === "message-status",
},
- }, [
- t.mapView(vm => vm.body, body => new BodyView(body)),
- t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)
- ]);
+ });
+ t.mapSideEffect(vm => vm.body, body => {
+ while (container.lastChild) {
+ container.removeChild(container.lastChild);
+ }
+ for (const part of body.parts) {
+ container.appendChild(renderPart(part));
+ }
+ container.appendChild(time);
+ });
+ return container;
}
}
@@ -105,13 +112,3 @@ function renderPart(part) {
function renderParts(parts) {
return Array.from(parts, renderPart);
}
-
-class BodyView extends StaticView {
- render(t, messageBody) {
- const container = t.span();
- for (const part of messageBody.parts) {
- container.appendChild(renderPart(part));
- }
- return container;
- }
-}
From 2197d6827972d15af70c8d79b9c456eeabbce9c5 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 11:00:54 -0700
Subject: [PATCH 54/69] Add fix to prevent pre overflow.
---
src/platform/web/ui/css/themes/element/timeline.css | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index 9167005b..e34184ad 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -126,6 +126,8 @@ limitations under the License.
line-height: 2.2rem;
/* so the .media can grow horizontally and its spacer can grow vertically */
width: 100%;
+ /* Fix for pre overflow */
+ min-width: 0;
}
.hydrogen .Timeline_messageSender.usercolor1 { color: var(--usercolor1); }
From eacc0339294765d87f3ffcc2ec881e1a7228dba3 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 11:32:37 -0700
Subject: [PATCH 55/69] Make code blocks accept non-code tags.
---
.../session/room/timeline/deserialize.js | 22 ++++++++++++++++---
src/platform/web/parsehtml.js | 2 +-
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index d477853d..d987332b 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -67,10 +67,10 @@ class Deserializer {
codeNode = child;
break;
}
+ let language = null;
if (!this._ensureElement(codeNode, "CODE")) {
- return null;
+ return new CodeBlock(language, this.result.getNodeText(node));
}
- let language = "";
const cl = result.getAttributeValue(codeNode, "class") || ""
for (const clname of cl.split(" ")) {
if (clname.startsWith("language-") && !clname.startsWith("language-_")) {
@@ -78,7 +78,7 @@ class Deserializer {
break;
}
}
- return new CodeBlock(language, codeNode.textContent);
+ return new CodeBlock(language, this.result.getNodeText(codeNode));
}
parseImage(node) {
@@ -443,6 +443,22 @@ export function tests() {
];
test(assert, input, output);
},
+ "Text with code block but no tag": assert => {
+ const code = 'main :: IO ()\nmain = putStrLn "Hello"'
+ const input = `${code}
`;
+ const output = [
+ new CodeBlock(null, code)
+ ];
+ test(assert, input, output);
+ },
+ "Text with code block and 'unsupported' tag": assert => {
+ const code = 'Hello, world'
+ const input = `${code}
`;
+ const output = [
+ new CodeBlock(null, code)
+ ];
+ test(assert, input, output);
+ }
/* Doesnt work: HTML library doesn't handle properly.
"Text with code block": assert => {
const code = 'main :: IO ()\nmain = putStrLn "Hello"'
diff --git a/src/platform/web/parsehtml.js b/src/platform/web/parsehtml.js
index 0efd7d4f..e2017928 100644
--- a/src/platform/web/parsehtml.js
+++ b/src/platform/web/parsehtml.js
@@ -26,7 +26,7 @@ class HTMLParseResult {
}
getNodeText(node) {
- return node.nodeValue;
+ return node.textContent;
}
isElementNode(node) {
From cdf85edad99e8139da11cfda265fa21586ca0207 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:01:33 -0700
Subject: [PATCH 56/69] Move pill parsing into deserialize and avoid
intermediate objects.
---
.../session/room/timeline/deserialize.js | 24 ++++++++++++++-----
src/domain/session/room/timeline/pills.js | 12 ----------
2 files changed, 18 insertions(+), 18 deletions(-)
delete mode 100644 src/domain/session/room/timeline/pills.js
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index d987332b..ad3eb941 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -1,6 +1,5 @@
import { MessageBody, HeaderBlock, TableBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
import { linkify } from "./linkify/linkify.js";
-import { parsePillLink } from "./pills.js"
/* At the time of writing (Jul 1 2021), Matrix Spec recommends
* allowing the following HTML tags:
@@ -15,6 +14,8 @@ import { parsePillLink } from "./pills.js"
const basicInline = ["EM", "STRONG", "CODE", "DEL", "SPAN" ];
const basicBlock = ["DIV", "BLOCKQUOTE"];
const safeSchemas = ["https", "http", "ftp", "mailto", "magnet"].map(name => `${name}://`);
+const baseUrl = 'https://matrix.to';
+const linkPrefix = `${baseUrl}/#/`;
class Deserializer {
constructor(result, mediaRepository) {
@@ -22,6 +23,17 @@ class Deserializer {
this.mediaRepository = mediaRepository;
}
+ parsePillLink(link) {
+ if (!link.startsWith(linkPrefix)) {
+ return null;
+ }
+ const contents = link.substring(linkPrefix.length);
+ if (contents[0] === '@') {
+ return contents;
+ }
+ return null;
+ }
+
parseLink(node, children) {
const href = this.result.getAttributeValue(node, "href");
const lcUrl = href?.toLowerCase();
@@ -29,9 +41,9 @@ class Deserializer {
if (!lcUrl || !safeSchemas.some(schema => lcUrl.startsWith(schema))) {
return new FormatPart("span", children);
}
- const pillData = parsePillLink(href);
- if (pillData && pillData.userId) {
- return new PillPart(pillData.userId, href, children);
+ const pillId = this.parsePillLink(href);
+ if (pillId) {
+ return new PillPart(pillId, href, children);
}
return new LinkPart(href, children);
}
@@ -43,13 +55,13 @@ class Deserializer {
// Will return 1 for, say, '1A', which may not be intended?
start = parseInt(result.getAttributeValue(node, "start")) || 1;
}
- const nodes = [];
+ const items = [];
for (const child of result.getChildNodes(node)) {
if (result.getNodeElementName(child) !== "LI") {
continue;
}
const item = this.parseAnyNodes(result.getChildNodes(child));
- nodes.push(item);
+ items.push(item);
}
return new ListBlock(start, nodes);
}
diff --git a/src/domain/session/room/timeline/pills.js b/src/domain/session/room/timeline/pills.js
deleted file mode 100644
index a638ea58..00000000
--- a/src/domain/session/room/timeline/pills.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const baseUrl = 'https://matrix.to';
-const linkPrefix = `${baseUrl}/#/`;
-
-export function parsePillLink(link) {
- if (!link.startsWith(linkPrefix)) {
- return null;
- }
- const contents = link.substring(linkPrefix.length);
- if (contents[0] === '@') {
- return { userId: contents }
- }
-}
From 900ebfe28984e273c511112b410a7d687f5ff709 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:05:47 -0700
Subject: [PATCH 57/69] Properly pass children to list block
---
src/domain/session/room/timeline/deserialize.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index ad3eb941..767a7995 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -63,7 +63,7 @@ class Deserializer {
const item = this.parseAnyNodes(result.getChildNodes(child));
items.push(item);
}
- return new ListBlock(start, nodes);
+ return new ListBlock(start, items);
}
_ensureElement(node, tag) {
From 996d0cfea8f6cb5dead9d163b7fe7c402dce03fc Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:07:17 -0700
Subject: [PATCH 58/69] Remove unneded attribute objects
---
.../session/room/timeline/TextMessageView.js | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index 5988cb44..c0429a07 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -40,12 +40,12 @@ export class TextMessageView extends BaseMessageView {
}
function renderList(listBlock) {
- const items = listBlock.items.map(item => tag.li({}, renderParts(item)));
+ const items = listBlock.items.map(item => tag.li(renderParts(item)));
const start = listBlock.startOffset;
if (start) {
return tag.ol({ start }, items);
} else {
- return tag.ul({}, items);
+ return tag.ul(items);
}
}
@@ -57,7 +57,7 @@ function renderImage(imagePart) {
imagePart.alt && { alt: imagePart.alt },
imagePart.title && { title: imagePart.title }
);
- return tag.img(attributes, []);
+ return tag.img(attributes);
}
function renderPill(pillPart) {
@@ -75,30 +75,30 @@ 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)))
+ .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));
+ const data = row.map(cell => tag.td(renderParts(cell)));
+ rows.push(tag.tr(data));
}
- children.push(tag.tbody({}, rows));
- return tag.table({}, children);
+ children.push(tag.tbody(rows));
+ return tag.table(children);
}
/**
* Map from part to function that outputs DOM for the part
*/
const formatFunction = {
- header: headerBlock => tag["h" + Math.min(6,headerBlock.level)]({}, renderParts(headerBlock.inlines)),
- codeblock: codeBlock => tag.pre({}, tag.code({}, text(codeBlock.text))),
+ header: headerBlock => tag["h" + Math.min(6,headerBlock.level)](renderParts(headerBlock.inlines)),
+ codeblock: codeBlock => tag.pre(tag.code(text(codeBlock.text))),
table: tableBlock => renderTable(tableBlock),
- code: codePart => tag.code({}, text(codePart.text)),
+ code: codePart => tag.code(text(codePart.text)),
text: textPart => text(textPart.text),
link: linkPart => tag.a({href: linkPart.url, className: "link", target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
pill: renderPill,
- format: formatPart => tag[formatPart.format]({}, renderParts(formatPart.children)),
+ format: formatPart => tag[formatPart.format](renderParts(formatPart.children)),
list: renderList,
image: renderImage,
newline: () => tag.br()
From 1e2945ca54708ad8743383682171c05f0e6bc772 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:12:26 -0700
Subject: [PATCH 59/69] Add license headers to new files.
---
src/domain/session/room/timeline/deserialize.js | 16 ++++++++++++++++
src/platform/web/parsehtml.js | 16 ++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/src/domain/session/room/timeline/deserialize.js b/src/domain/session/room/timeline/deserialize.js
index 767a7995..d7f81316 100644
--- a/src/domain/session/room/timeline/deserialize.js
+++ b/src/domain/session/room/timeline/deserialize.js
@@ -1,3 +1,19 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
import { MessageBody, HeaderBlock, TableBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
import { linkify } from "./linkify/linkify.js";
diff --git a/src/platform/web/parsehtml.js b/src/platform/web/parsehtml.js
index e2017928..856ca691 100644
--- a/src/platform/web/parsehtml.js
+++ b/src/platform/web/parsehtml.js
@@ -1,3 +1,19 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
import DOMPurify from "../../../../../lib/dompurify/index.js"
class HTMLParseResult {
From 9e1f57a2b1f7d66d1a3cba14df4b9dbec22a4794 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:36:26 -0700
Subject: [PATCH 60/69] Switch tiles to using enums and checking format.
---
.../room/timeline/tiles/BaseTextTile.js | 11 +++++---
.../room/timeline/tiles/EncryptedEventTile.js | 2 +-
.../session/room/timeline/tiles/TextTile.js | 25 +++++++++++--------
3 files changed, 23 insertions(+), 15 deletions(-)
diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js
index 414ecd5d..6ac28413 100644
--- a/src/domain/session/room/timeline/tiles/BaseTextTile.js
+++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js
@@ -16,12 +16,15 @@ limitations under the License.
import {BaseMessageTile} from "./BaseMessageTile.js";
import {stringAsBody} from "../MessageBody.js";
+import {createEnum} from "../../../../../utils/enum.js";
+
+export const TextTileFormat = createEnum("Plain", "Html");
export class BaseTextTile extends BaseMessageTile {
constructor(options) {
super(options);
this._messageBody = null;
- this._messageFormat = null
+ this._format = null
}
get shape() {
@@ -33,7 +36,7 @@ export class BaseTextTile extends BaseMessageTile {
}
_getBodyFormat() {
- return "plain";
+ return TextTileFormat.Plain;
}
get body() {
@@ -43,13 +46,13 @@ export class BaseTextTile extends BaseMessageTile {
// doing an equality check
// Even if the body hasn't changed, but the format has, we need
// to re-fill our cache.
- if (!this._messageBody || this._messageBody.sourceString !== body || this._messageFormat !== format) {
+ if (!this._messageBody || this._messageBody.sourceString !== body || this._format !== format) {
// body with markup is an array of parts,
// so we should not recreate it for the same body string,
// or else the equality check in the binding will always fail.
// So cache it here.
this._messageBody = this._parseBody(body, format);
- this._messageFormat = body.format;
+ this._format = body.format;
}
return this._messageBody;
}
diff --git a/src/domain/session/room/timeline/tiles/EncryptedEventTile.js b/src/domain/session/room/timeline/tiles/EncryptedEventTile.js
index 51788530..07dd763b 100644
--- a/src/domain/session/room/timeline/tiles/EncryptedEventTile.js
+++ b/src/domain/session/room/timeline/tiles/EncryptedEventTile.js
@@ -42,6 +42,6 @@ export class EncryptedEventTile extends BaseTextTile {
} else {
string = decryptionError?.message || this.i18n`Could not decrypt message because of unknown reason.`;
}
- return { string, format: "plain" };
+ return string;
}
}
diff --git a/src/domain/session/room/timeline/tiles/TextTile.js b/src/domain/session/room/timeline/tiles/TextTile.js
index ec91ab52..5054530b 100644
--- a/src/domain/session/room/timeline/tiles/TextTile.js
+++ b/src/domain/session/room/timeline/tiles/TextTile.js
@@ -14,17 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {BaseTextTile} from "./BaseTextTile.js";
+import {BaseTextTile, TextTileFormat} from "./BaseTextTile.js";
import {parsePlainBody} from "../MessageBody.js";
import {parseHTMLBody} from "../deserialize.js";
export class TextTile extends BaseTextTile {
- _getContentString(key, fallback = null) {
+ _getContentString(key) {
const content = this._getContent();
- let val = content?.[key] || fallback;
- if (!val && val !== "") { // empty string is falsy, but OK here.
- return null;
- }
+ let val = content?.[key] || "";
if (content.msgtype === "m.emote") {
val = `* ${this.displayName} ${val}`;
}
@@ -32,7 +29,7 @@ export class TextTile extends BaseTextTile {
}
_getPlainBody() {
- return this._getContentString("body", "");
+ return this._getContentString("body");
}
_getFormattedBody() {
@@ -40,15 +37,23 @@ export class TextTile extends BaseTextTile {
}
_getBody() {
- return this._getFormattedBody() || this._getPlainBody();
+ if (this._getBodyFormat() == TextTileFormat.Html) {
+ return this._getFormattedBody();
+ } else {
+ return this._getPlainBody();
+ }
}
_getBodyFormat() {
- return this._getContent()?.["formatted_body"] ? "html" : "plain"
+ if (this._getContent()?.["format"] === "org.matrix.custom.html") {
+ return TextTileFormat.Html;
+ } else {
+ return TextTileFormat.Plain;
+ }
}
_parseBody(body, format) {
- if (format === "html") {
+ if (format === TextTileFormat.Html) {
return parseHTMLBody(this.platform, this._mediaRepository, body);
} else {
return parsePlainBody(body);
From 031ce42831eac0b3aed76d42071bf7e98a09c635 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:43:40 -0700
Subject: [PATCH 61/69] Properly cache message format.
---
src/domain/session/room/timeline/tiles/BaseTextTile.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js
index 6ac28413..1b11a5d4 100644
--- a/src/domain/session/room/timeline/tiles/BaseTextTile.js
+++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js
@@ -52,7 +52,7 @@ export class BaseTextTile extends BaseMessageTile {
// or else the equality check in the binding will always fail.
// So cache it here.
this._messageBody = this._parseBody(body, format);
- this._format = body.format;
+ this._format = format;
}
return this._messageBody;
}
From 7cfdd4f66363c2d520db3a9a9e7b4febc38bce17 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:46:25 -0700
Subject: [PATCH 62/69] Rename TextTileFormat to BodyFormat
---
.../session/room/timeline/tiles/BaseTextTile.js | 4 ++--
src/domain/session/room/timeline/tiles/TextTile.js | 12 ++++++------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js
index 1b11a5d4..7053d5c6 100644
--- a/src/domain/session/room/timeline/tiles/BaseTextTile.js
+++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js
@@ -18,7 +18,7 @@ import {BaseMessageTile} from "./BaseMessageTile.js";
import {stringAsBody} from "../MessageBody.js";
import {createEnum} from "../../../../../utils/enum.js";
-export const TextTileFormat = createEnum("Plain", "Html");
+export const BodyFormat = createEnum("Plain", "Html");
export class BaseTextTile extends BaseMessageTile {
constructor(options) {
@@ -36,7 +36,7 @@ export class BaseTextTile extends BaseMessageTile {
}
_getBodyFormat() {
- return TextTileFormat.Plain;
+ return BodyFormat.Plain;
}
get body() {
diff --git a/src/domain/session/room/timeline/tiles/TextTile.js b/src/domain/session/room/timeline/tiles/TextTile.js
index 5054530b..11ffefe0 100644
--- a/src/domain/session/room/timeline/tiles/TextTile.js
+++ b/src/domain/session/room/timeline/tiles/TextTile.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {BaseTextTile, TextTileFormat} from "./BaseTextTile.js";
+import {BaseTextTile, BodyFormat} from "./BaseTextTile.js";
import {parsePlainBody} from "../MessageBody.js";
import {parseHTMLBody} from "../deserialize.js";
@@ -37,7 +37,7 @@ export class TextTile extends BaseTextTile {
}
_getBody() {
- if (this._getBodyFormat() == TextTileFormat.Html) {
+ if (this._getBodyFormat() == BodyFormat.Html) {
return this._getFormattedBody();
} else {
return this._getPlainBody();
@@ -45,15 +45,15 @@ export class TextTile extends BaseTextTile {
}
_getBodyFormat() {
- if (this._getContent()?.["format"] === "org.matrix.custom.html") {
- return TextTileFormat.Html;
+ if (this._getContent()?.format === "org.matrix.custom.html") {
+ return BodyFormat.Html;
} else {
- return TextTileFormat.Plain;
+ return BodyFormat.Plain;
}
}
_parseBody(body, format) {
- if (format === TextTileFormat.Html) {
+ if (format === BodyFormat.Html) {
return parseHTMLBody(this.platform, this._mediaRepository, body);
} else {
return parsePlainBody(body);
From da48ddec83c0c02be03e222f124cce8b0c33f73e Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:49:33 -0700
Subject: [PATCH 63/69] Fix == typo
---
src/domain/session/room/timeline/tiles/TextTile.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/domain/session/room/timeline/tiles/TextTile.js b/src/domain/session/room/timeline/tiles/TextTile.js
index 11ffefe0..ffd06c1a 100644
--- a/src/domain/session/room/timeline/tiles/TextTile.js
+++ b/src/domain/session/room/timeline/tiles/TextTile.js
@@ -37,7 +37,7 @@ export class TextTile extends BaseTextTile {
}
_getBody() {
- if (this._getBodyFormat() == BodyFormat.Html) {
+ if (this._getBodyFormat() === BodyFormat.Html) {
return this._getFormattedBody();
} else {
return this._getPlainBody();
From c620e9c930961e96f9ae9a20c621b4cd7d3cd1b4 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:51:34 -0700
Subject: [PATCH 64/69] Move away from Object.assign for image rendering.
---
.../web/ui/session/room/timeline/TextMessageView.js | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index c0429a07..81ed4be0 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -50,13 +50,11 @@ function renderList(listBlock) {
}
function renderImage(imagePart) {
- const attributes = Object.assign(
- { src: imagePart.src },
- imagePart.width && { width: imagePart.width },
- imagePart.height && { height: imagePart.height },
- imagePart.alt && { alt: imagePart.alt },
- imagePart.title && { title: imagePart.title }
- );
+ const attributes = { src: imagePart.src };
+ if (imagePart.width) { attributes.width = imagePart.width }
+ if (imagePart.height) { attributes.height = imagePart.height }
+ if (imagePart.alt) { attributes.alt = imagePart.alt }
+ if (imagePart.title) { attributes.title = imagePart.title }
return tag.img(attributes);
}
From 3e4314e7f3b333995f78874180c707c722735463 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 13:53:14 -0700
Subject: [PATCH 65/69] Update comment based on 'small object' version of code.
---
src/domain/session/room/timeline/tiles/BaseTextTile.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js
index 7053d5c6..4e94c958 100644
--- a/src/domain/session/room/timeline/tiles/BaseTextTile.js
+++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js
@@ -42,7 +42,7 @@ export class BaseTextTile extends BaseMessageTile {
get body() {
const body = this._getBody();
const format = this._getBodyFormat();
- // body.string is a string, so we can check for difference by just
+ // body is a string, so we can check for difference by just
// doing an equality check
// Even if the body hasn't changed, but the format has, we need
// to re-fill our cache.
From fb29913ef03843da663f405c873d4b16e30705a4 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 14:21:39 -0700
Subject: [PATCH 66/69] Add lint fixes
---
src/domain/session/room/timeline/MessageBody.js | 2 +-
src/domain/session/room/timeline/tiles/BaseTextTile.js | 2 +-
src/platform/web/parsehtml.js | 2 +-
src/platform/web/ui/session/room/timeline/TextMessageView.js | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/domain/session/room/timeline/MessageBody.js b/src/domain/session/room/timeline/MessageBody.js
index b739692c..d6a61e69 100644
--- a/src/domain/session/room/timeline/MessageBody.js
+++ b/src/domain/session/room/timeline/MessageBody.js
@@ -100,7 +100,7 @@ export class ImagePart {
}
get type() { return "image"; }
-};
+}
export class PillPart {
constructor(id, href, children) {
diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js
index 4e94c958..c6c8cbae 100644
--- a/src/domain/session/room/timeline/tiles/BaseTextTile.js
+++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js
@@ -31,7 +31,7 @@ export class BaseTextTile extends BaseMessageTile {
return "message";
}
- _parseBody(body, _) {
+ _parseBody(body) {
return stringAsBody(body.string);
}
diff --git a/src/platform/web/parsehtml.js b/src/platform/web/parsehtml.js
index 856ca691..b86ab4d9 100644
--- a/src/platform/web/parsehtml.js
+++ b/src/platform/web/parsehtml.js
@@ -55,7 +55,7 @@ class HTMLParseResult {
}
const sanitizeConfig = {
- ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx|mxc):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
+ ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx|mxc):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i,
}
export function parseHTML(html) {
diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
index 81ed4be0..5a1ac374 100644
--- a/src/platform/web/ui/session/room/timeline/TextMessageView.js
+++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {tag, text, classNames} from "../../../general/html.js";
+import {tag, text} from "../../../general/html.js";
import {BaseMessageView} from "./BaseMessageView.js";
export class TextMessageView extends BaseMessageView {
From 0c0633fdd49f2c988ae08a0b1352b177ff71fb97 Mon Sep 17 00:00:00 2001
From: Danila Fedorin
Date: Fri, 16 Jul 2021 14:28:18 -0700
Subject: [PATCH 67/69] Fix body.string in BaseMessageTile
---
src/domain/session/room/timeline/tiles/BaseTextTile.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js
index c6c8cbae..fb61cb4b 100644
--- a/src/domain/session/room/timeline/tiles/BaseTextTile.js
+++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js
@@ -32,7 +32,7 @@ export class BaseTextTile extends BaseMessageTile {
}
_parseBody(body) {
- return stringAsBody(body.string);
+ return stringAsBody(body);
}
_getBodyFormat() {
From bc69e1cdaeb292306abcff720eb8a5e7c151ef8e Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 23:29:15 +0200
Subject: [PATCH 68/69] make font on code snippets slightly smaller
---
src/platform/web/ui/css/themes/element/timeline.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index e34184ad..fe5e5f8f 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -249,6 +249,7 @@ only loads when the top comes into view*/
padding: 0.5em;
max-height: 30em;
overflow: auto;
+ font-size: 0.9em;
}
.Timeline_messageBody pre > code {
From 9044f4eebee5e2742de8e88a29f28f78bcf6deb5 Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Fri, 16 Jul 2021 23:32:38 +0200
Subject: [PATCH 69/69] also for inline code
---
src/platform/web/ui/css/themes/element/timeline.css | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css
index fe5e5f8f..29e2fbf0 100644
--- a/src/platform/web/ui/css/themes/element/timeline.css
+++ b/src/platform/web/ui/css/themes/element/timeline.css
@@ -236,6 +236,7 @@ only loads when the top comes into view*/
.Timeline_messageBody code, .Timeline_messageBody pre {
background-color: #f8f8f8;
font-family: monospace;
+ font-size: 0.9em;
}
.Timeline_messageBody code {
@@ -249,13 +250,13 @@ only loads when the top comes into view*/
padding: 0.5em;
max-height: 30em;
overflow: auto;
- font-size: 0.9em;
}
.Timeline_messageBody pre > code {
background-color: unset;
border-radius: unset;
display: block;
+ font-size: unset;
}
.Timeline_messageBody blockquote {