From 0604c86779cdea98ace30bdd78eb4db6888ffc40 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 19 Sep 2020 15:30:00 +0100 Subject: [PATCH 01/79] added katex package and import --- package.json | 1 + src/HtmlUtils.tsx | 1 + yarn.lock | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/package.json b/package.json index 156cbb1bc8..7aa3df136b 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "highlight.js": "^10.1.2", "html-entities": "^1.3.1", "is-ip": "^2.0.0", + "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index bd314c2e5f..99acbfcb0c 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -26,6 +26,7 @@ import _linkifyString from 'linkifyjs/string'; import classNames from 'classnames'; import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; +import katex from 'katex'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; diff --git a/yarn.lock b/yarn.lock index efc1f0eae1..34b99708fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5607,6 +5607,13 @@ jsx-ast-utils@^2.4.1: array-includes "^3.1.1" object.assign "^4.1.0" +katex@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9" + integrity sha512-y+8btoc/CK70XqcHqjxiGWBOeIL8upbS0peTPXTvgrh21n1RiWWcIpSWM+4uXq+IAgNh9YYQWdc7LVDPDAEEAg== + dependencies: + commander "^2.19.0" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" From becc79d67a29a0886f4a6f800daabebae16d655c Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 12:59:22 +0100 Subject: [PATCH 02/79] send tex math as data-mx-maths attribute --- src/HtmlUtils.tsx | 26 +++++++++++++++++++++++++- src/editor/serialize.ts | 23 ++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 99acbfcb0c..344fb3514c 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -27,6 +27,7 @@ import classNames from 'classnames'; import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; import katex from 'katex'; +import { AllHtmlEntities } from 'html-entities'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -236,7 +237,8 @@ const sanitizeHtmlParams: sanitizeHtml.IOptions = { allowedAttributes: { // custom ones first: font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix - span: ['data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style'], // custom to matrix + span: ['data-mx-maths', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style'], // custom to matrix + div: ['data-mx-maths'], a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix img: ['src', 'width', 'height', 'alt', 'title'], ol: ['start'], @@ -409,6 +411,27 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); + if (true) { // TODO: add katex setting + const mathDelimiters = [ + { left: "
.*?
", display: true }, + { left: ".*?", display: false } + ]; + + mathDelimiters.forEach(function (d) { + var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + + safeBody = safeBody.replace(reg, function(match, p1) { + return katex.renderToString( + AllHtmlEntities.decode(p1), + { + throwOnError: false, + displayMode: d.display, + output: "mathml" + }) + }); + }); + } + } } finally { delete sanitizeParams.textFilter; @@ -450,6 +473,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts 'markdown-body': isHtmlMessage && !emojiBody, }); + return isDisplayedWithHtml ? { @@ -38,7 +39,27 @@ export function mdSerialize(model: EditorModel) { } export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { - const md = mdSerialize(model); + var md = mdSerialize(model); + + if (true) { // TODO: add katex setting + const mathDelimiters = [ // TODO: make customizable + { left: "\\$\\$\\$", right: "\\$\\$\\$", display: true }, + { left: "\\$\\$", right: "\\$\\$", display: false } + ]; + + mathDelimiters.forEach(function (d) { + var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + md = md.replace(reg, function(match, p1) { + const p1e = AllHtmlEntities.encode(p1); + if (d.display == true) { + return `
${p1e}
`; + } else { + return `${p1e}`; + } + }); + }); + } + const parser = new Markdown(md); if (!parser.isPlainText() || forceHTML) { return parser.toHTML(); From e78734bbf6b2fbf1ebee530921998ff97c56f203 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 14:20:35 +0100 Subject: [PATCH 03/79] Deserialize back to math delimiters for editing --- src/HtmlUtils.tsx | 4 +++- src/editor/deserialize.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 344fb3514c..46bc7b441c 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -534,7 +534,6 @@ export function checkBlockNode(node: Node) { case "H6": case "PRE": case "BLOCKQUOTE": - case "DIV": case "P": case "UL": case "OL": @@ -547,6 +546,9 @@ export function checkBlockNode(node: Node) { case "TH": case "TD": return true; + case "DIV": + // don't treat math nodes as block nodes for deserializing + return !(node as HTMLElement).hasAttribute("data-mx-maths"); default: return false; } diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index ec697b193c..edaa330e50 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -130,6 +130,18 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl } break; } + case "DIV": + case "SPAN": { + // math nodes are translated back into delimited latex strings + if (n.hasAttribute("data-mx-maths")) { + const delim = (n.nodeName == "SPAN") ? "$$" : "$$$"; + const tex = n.getAttribute("data-mx-maths"); + return partCreator.plain(delim + tex + delim); + } else if (!checkDescendInto(n)) { + return partCreator.plain(n.textContent); + } + break; + } case "OL": state.listIndex.push((n).start || 1); /* falls through */ From 428a6b94ff5c34533b8684e5ae8b019a4dbec07c Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 15:07:12 +0100 Subject: [PATCH 04/79] math off by default, enable with latex_maths flag --- src/HtmlUtils.tsx | 4 +++- src/editor/serialize.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 46bc7b441c..047a891847 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -28,6 +28,7 @@ import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; import katex from 'katex'; import { AllHtmlEntities } from 'html-entities'; +import SdkConfig from './SdkConfig'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -50,6 +51,7 @@ const ZWJ_REGEX = new RegExp("\u200D|\u2003", "g"); // Regex pattern for whitespace characters const WHITESPACE_REGEX = new RegExp("\\s", "g"); + const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; @@ -411,7 +413,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); - if (true) { // TODO: add katex setting + if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ { left: "
.*?
", display: true }, { left: ".*?", display: false } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 8ec590cba5..72a551a4a3 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -19,6 +19,7 @@ import Markdown from '../Markdown'; import {makeGenericPermalink} from "../utils/permalinks/Permalinks"; import EditorModel from "./model"; import { AllHtmlEntities } from 'html-entities'; +import SdkConfig from '../SdkConfig'; export function mdSerialize(model: EditorModel) { return model.parts.reduce((html, part) => { @@ -41,7 +42,7 @@ export function mdSerialize(model: EditorModel) { export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { var md = mdSerialize(model); - if (true) { // TODO: add katex setting + if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ // TODO: make customizable { left: "\\$\\$\\$", right: "\\$\\$\\$", display: true }, { left: "\\$\\$", right: "\\$\\$", display: false } From e4448ae1ad87cbd3e47c73a589012494ec7d4189 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 16:52:29 +0100 Subject: [PATCH 05/79] send fallback in pre tags, not code --- src/editor/serialize.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 72a551a4a3..c0d9509ffa 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -53,9 +53,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(reg, function(match, p1) { const p1e = AllHtmlEntities.encode(p1); if (d.display == true) { - return `
${p1e}
`; + return `
${p1e}
`; } else { - return `${p1e}`; + return `
${p1e}
`; } }); }); From 7e6d7053e0a6c55f082153a521de079c7db2d77c Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 17:02:27 +0100 Subject: [PATCH 06/79] Revert "send fallback in pre tags, not code" (code looks better) This reverts commit e4448ae1ad87cbd3e47c73a589012494ec7d4189. --- src/editor/serialize.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index c0d9509ffa..72a551a4a3 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -53,9 +53,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(reg, function(match, p1) { const p1e = AllHtmlEntities.encode(p1); if (d.display == true) { - return `
${p1e}
`; + return `
${p1e}
`; } else { - return `
${p1e}
`; + return `${p1e}`; } }); }); From 1f24b5b90c9fe6a743db17d14b726e1aefd15f6f Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 17:48:42 +0100 Subject: [PATCH 07/79] made math display slightly larger --- res/css/structures/_RoomView.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 572c7166d2..571c34fcb0 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -205,6 +205,10 @@ limitations under the License. clear: both; } +.mx_RoomView_MessageList .katex { + font-size: 1.3em; +} + li.mx_RoomView_myReadMarker_container { height: 0px; margin: 0px; From 24a1834f9b37993b79ec92c1c3081d6aa7777d37 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 21 Sep 2020 09:00:24 +0100 Subject: [PATCH 08/79] support multi-line and escaped $ --- src/HtmlUtils.tsx | 6 +++--- src/editor/serialize.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 047a891847..569b1662fe 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -415,12 +415,12 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts safeBody = sanitizeHtml(formattedBody, sanitizeParams); if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ - { left: "
.*?
", display: true }, - { left: ".*?", display: false } + { pattern: "
(.|\\s)*?
", display: true }, + { pattern: "(.|\\s)*?", display: false } ]; mathDelimiters.forEach(function (d) { - var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + var reg = RegExp(d.pattern, "gm"); safeBody = safeBody.replace(reg, function(match, p1) { return katex.renderToString( diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 72a551a4a3..d0a28266eb 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -44,12 +44,12 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ // TODO: make customizable - { left: "\\$\\$\\$", right: "\\$\\$\\$", display: true }, - { left: "\\$\\$", right: "\\$\\$", display: false } + { pattern: "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$", display: true }, + { pattern: "\\$\\$(([^$]|\\\\\\$)*)\\$\\$", display: false } ]; mathDelimiters.forEach(function (d) { - var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + var reg = RegExp(d.pattern, "gm"); md = md.replace(reg, function(match, p1) { const p1e = AllHtmlEntities.encode(p1); if (d.display == true) { From 4df8754aad0333c840eceb1892faa9f3c90f2405 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 21 Sep 2020 11:00:39 +0100 Subject: [PATCH 09/79] allow custom latex delimiters in config.json --- src/editor/deserialize.ts | 10 ++++++++-- src/editor/serialize.ts | 26 ++++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index edaa330e50..e27eecd2db 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -21,6 +21,7 @@ import { walkDOMDepthFirst } from "./dom"; import { checkBlockNode } from "../HtmlUtils"; import { getPrimaryPermalinkEntity } from "../utils/permalinks/Permalinks"; import { PartCreator } from "./parts"; +import SdkConfig from "../SdkConfig"; function parseAtRoomMentions(text: string, partCreator: PartCreator) { const ATROOM = "@room"; @@ -134,9 +135,14 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl case "SPAN": { // math nodes are translated back into delimited latex strings if (n.hasAttribute("data-mx-maths")) { - const delim = (n.nodeName == "SPAN") ? "$$" : "$$$"; + const delimLeft = (n.nodeName == "SPAN") ? + (SdkConfig.get()['latex_maths_delims'] || {})['inline_left'] || "$$" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_left'] || "$$$"; + const delimRight = (n.nodeName == "SPAN") ? + (SdkConfig.get()['latex_maths_delims'] || {})['inline_right'] || "$$" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_right'] || "$$$"; const tex = n.getAttribute("data-mx-maths"); - return partCreator.plain(delim + tex + delim); + return partCreator.plain(delimLeft + tex + delimRight); } else if (!checkDescendInto(n)) { return partCreator.plain(n.textContent); } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index d0a28266eb..da8ae4e820 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -43,21 +43,19 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = var md = mdSerialize(model); if (SdkConfig.get()['latex_maths']) { - const mathDelimiters = [ // TODO: make customizable - { pattern: "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$", display: true }, - { pattern: "\\$\\$(([^$]|\\\\\\$)*)\\$\\$", display: false } - ]; + const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || + "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$"; + const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || + "\\$\\$(([^$]|\\\\\\$)*)\\$\\$"; - mathDelimiters.forEach(function (d) { - var reg = RegExp(d.pattern, "gm"); - md = md.replace(reg, function(match, p1) { - const p1e = AllHtmlEntities.encode(p1); - if (d.display == true) { - return `
${p1e}
`; - } else { - return `${p1e}`; - } - }); + md = md.replace(RegExp(displayPattern, "gm"), function(m,p1) { + const p1e = AllHtmlEntities.encode(p1); + return `
${p1e}
`; + }); + + md = md.replace(RegExp(inlinePattern, "gm"), function(m,p1) { + const p1e = AllHtmlEntities.encode(p1); + return `${p1e}`; }); } From 1b689bb4e11c1329072a85002ea90abfaf9043df Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 21 Sep 2020 22:02:19 +0100 Subject: [PATCH 10/79] tell markdown to ignore math tags --- src/Markdown.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Markdown.js b/src/Markdown.js index 492450e87d..dc15e7d6b3 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -16,13 +16,19 @@ limitations under the License. import commonmark from 'commonmark'; import {escape} from "lodash"; +import SdkConfig from './SdkConfig'; -const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; +const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u', 'code']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; function is_allowed_html_tag(node) { + if (SdkConfig.get()['latex_maths'] && + node.literal.match(/^<\/?(div|span)( data-mx-maths="[^"]*")?>$/) != null) { + return true; + } + // Regex won't work for tags with attrs, but we only // allow anyway. const matches = /^<\/?(.*)>$/.exec(node.literal); @@ -30,6 +36,7 @@ function is_allowed_html_tag(node) { const tag = matches[1]; return ALLOWED_HTML_TAGS.indexOf(tag) > -1; } + return false; } From aded3c9de2b14010612b7d9581b10366d9dc3be2 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Tue, 22 Sep 2020 11:54:23 +0100 Subject: [PATCH 11/79] cosmetic changes (lint) --- src/HtmlUtils.tsx | 13 +++++-------- src/editor/serialize.ts | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 569b1662fe..7bccd47622 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -416,24 +416,21 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ { pattern: "
(.|\\s)*?
", display: true }, - { pattern: "(.|\\s)*?", display: false } + { pattern: "(.|\\s)*?", display: false }, ]; - mathDelimiters.forEach(function (d) { - var reg = RegExp(d.pattern, "gm"); - - safeBody = safeBody.replace(reg, function(match, p1) { + mathDelimiters.forEach(function(d) { + safeBody = safeBody.replace(RegExp(d.pattern, "gm"), function(m, p1) { return katex.renderToString( AllHtmlEntities.decode(p1), { throwOnError: false, displayMode: d.display, - output: "mathml" + output: "mathml", }) }); }); - } - + } } } finally { delete sanitizeParams.textFilter; diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index da8ae4e820..02194a1d59 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -40,7 +40,7 @@ export function mdSerialize(model: EditorModel) { } export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { - var md = mdSerialize(model); + let md = mdSerialize(model); if (SdkConfig.get()['latex_maths']) { const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || @@ -48,12 +48,12 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || "\\$\\$(([^$]|\\\\\\$)*)\\$\\$"; - md = md.replace(RegExp(displayPattern, "gm"), function(m,p1) { + md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); return `
${p1e}
`; }); - md = md.replace(RegExp(inlinePattern, "gm"), function(m,p1) { + md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); return `${p1e}`; }); From d2054ea685bad49af11ec9a64b5aa4218bc204c0 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 25 Sep 2020 09:05:22 +0100 Subject: [PATCH 12/79] HTML output for cross-browser support --- res/css/structures/_RoomView.scss | 4 ---- src/HtmlUtils.tsx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 571c34fcb0..572c7166d2 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -205,10 +205,6 @@ limitations under the License. clear: both; } -.mx_RoomView_MessageList .katex { - font-size: 1.3em; -} - li.mx_RoomView_myReadMarker_container { height: 0px; margin: 0px; diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 7bccd47622..70a2a3f000 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -426,7 +426,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts { throwOnError: false, displayMode: d.display, - output: "mathml", + output: "htmlAndMathml", }) }); }); From 65c4460abcdb64bac14bdd72e3b970a96dd52299 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 9 Oct 2020 15:47:11 +0100 Subject: [PATCH 13/79] whitespace fixes --- src/HtmlUtils.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 70a2a3f000..da3eb3b128 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -51,7 +51,6 @@ const ZWJ_REGEX = new RegExp("\u200D|\u2003", "g"); // Regex pattern for whitespace characters const WHITESPACE_REGEX = new RegExp("\\s", "g"); - const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; @@ -472,7 +471,6 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts 'markdown-body': isHtmlMessage && !emojiBody, }); - return isDisplayedWithHtml ? Date: Sat, 10 Oct 2020 09:12:53 +0100 Subject: [PATCH 14/79] only allow code tags inside math tag --- src/Markdown.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index dc15e7d6b3..9914cff85a 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -18,14 +18,21 @@ import commonmark from 'commonmark'; import {escape} from "lodash"; import SdkConfig from './SdkConfig'; -const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u', 'code']; +const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; +function is_math_node(node) { + return node != null && + node.literal != null && + node.literal.match(/^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$/) != null; +} + function is_allowed_html_tag(node) { if (SdkConfig.get()['latex_maths'] && - node.literal.match(/^<\/?(div|span)( data-mx-maths="[^"]*")?>$/) != null) { + (is_math_node(node) || + (node.literal.match(/^<\/?code>$/) && is_math_node(node.parent)))) { return true; } From 96742fc3093cc88cd609d731d932a05ab094262f Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 10 Oct 2020 16:32:49 +0100 Subject: [PATCH 15/79] latex math as labs setting --- src/HtmlUtils.tsx | 4 ++-- src/Markdown.js | 4 ++-- src/editor/serialize.ts | 3 ++- src/settings/Settings.ts | 6 ++++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index da3eb3b128..ca718cd9aa 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -28,7 +28,7 @@ import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; import katex from 'katex'; import { AllHtmlEntities } from 'html-entities'; -import SdkConfig from './SdkConfig'; +import SettingsStore from './settings/SettingsStore'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -412,7 +412,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); - if (SdkConfig.get()['latex_maths']) { + if (SettingsStore.getValue("feature_latex_maths")) { const mathDelimiters = [ { pattern: "
(.|\\s)*?
", display: true }, { pattern: "(.|\\s)*?", display: false }, diff --git a/src/Markdown.js b/src/Markdown.js index 9914cff85a..329dcdd996 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -16,7 +16,7 @@ limitations under the License. import commonmark from 'commonmark'; import {escape} from "lodash"; -import SdkConfig from './SdkConfig'; +import SettingsStore from './settings/SettingsStore'; const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; @@ -30,7 +30,7 @@ function is_math_node(node) { } function is_allowed_html_tag(node) { - if (SdkConfig.get()['latex_maths'] && + if (SettingsStore.getValue("feature_latex_maths") && (is_math_node(node) || (node.literal.match(/^<\/?code>$/) && is_math_node(node.parent)))) { return true; diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 02194a1d59..9f24cd5eb2 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -19,6 +19,7 @@ import Markdown from '../Markdown'; import {makeGenericPermalink} from "../utils/permalinks/Permalinks"; import EditorModel from "./model"; import { AllHtmlEntities } from 'html-entities'; +import SettingsStore from '../settings/SettingsStore'; import SdkConfig from '../SdkConfig'; export function mdSerialize(model: EditorModel) { @@ -42,7 +43,7 @@ export function mdSerialize(model: EditorModel) { export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { let md = mdSerialize(model); - if (SdkConfig.get()['latex_maths']) { + if (SettingsStore.getValue("feature_latex_maths")) { const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$"; const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 737c882919..2f817c264c 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -116,6 +116,12 @@ export interface ISetting { } export const SETTINGS: {[setting: string]: ISetting} = { + "feature_latex_maths": { + isFeature: true, + displayName: _td("LaTeX math in messages"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_communities_v2_prototypes": { isFeature: true, displayName: _td( From a89adb86a5912d3ce71171583181175fe2564a23 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 10 Oct 2020 16:33:25 +0100 Subject: [PATCH 16/79] i18n en+nl for latex math labs setting --- src/i18n/strings/en_EN.json | 1 + src/i18n/strings/en_US.json | 1 + src/i18n/strings/nl.json | 1 + 3 files changed, 3 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d7360430ae..d7b40fc198 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -856,6 +856,7 @@ "click to reveal": "click to reveal", "Clear cache and reload": "Clear cache and reload", "Labs": "Labs", + "LaTeX math in messages": "LaTeX math in messages", "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", "Ignored/Blocked": "Ignored/Blocked", "Error adding ignored user/server": "Error adding ignored user/server", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index a1275fb089..c00bf03b29 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -128,6 +128,7 @@ "Kick": "Kick", "Kicks user with given id": "Kicks user with given id", "Labs": "Labs", + "LaTeX math in messages": "LaTeX math in messages", "Ignore": "Ignore", "Unignore": "Unignore", "You are now ignoring %(userId)s": "You are now ignoring %(userId)s", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index bb0fb5def6..d991962eec 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -199,6 +199,7 @@ "%(targetName)s joined the room.": "%(targetName)s is tot het gesprek toegetreden.", "Jump to first unread message.": "Spring naar het eerste ongelezen bericht.", "Labs": "Experimenteel", + "LaTeX math in messages": "LaTeX wiskunde in berichten", "Last seen": "Laatst gezien", "Leave room": "Gesprek verlaten", "%(targetName)s left the room.": "%(targetName)s heeft het gesprek verlaten.", From bdd332c8b5366398d4af166b49b3eaf1cddb6230 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 10 Oct 2020 20:05:35 +0100 Subject: [PATCH 17/79] ran yarn i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a33104ab12..b41a19aa21 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -438,6 +438,7 @@ "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "Change notification settings": "Change notification settings", + "LaTeX math in messages": "LaTeX math in messages", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.", "New spinner design": "New spinner design", "Message Pinning": "Message Pinning", @@ -848,7 +849,6 @@ "click to reveal": "click to reveal", "Clear cache and reload": "Clear cache and reload", "Labs": "Labs", - "LaTeX math in messages": "LaTeX math in messages", "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", "Ignored/Blocked": "Ignored/Blocked", "Error adding ignored user/server": "Error adding ignored user/server", From f0c4473107d0c3589479809d8accd79b9c4dba08 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 12 Oct 2020 21:01:11 +0100 Subject: [PATCH 18/79] tell markdown parser to ignore properly-formatted math tags --- src/Markdown.js | 51 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index 329dcdd996..564a2ed0a8 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -23,19 +23,47 @@ const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; -function is_math_node(node) { - return node != null && - node.literal != null && - node.literal.match(/^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$/) != null; +// prevent renderer from interpreting contents of AST node +function freeze_node(walker, node) { + const newNode = new commonmark.Node('custom_inline', node.sourcepos); + newNode.onEnter = node.literal; + node.insertAfter(newNode); + node.unlink(); + walker.resumeAt(newNode.next, true); +} + +// prevent renderer from interpreting contents of latex math tags +function freeze_math(parsed) { + const walker = parsed.walker(); + let ev; + let inMath = false; + while ( (ev = walker.next()) ) { + const node = ev.node; + if (ev.entering) { + if (!inMath) { + // entering a math tag + if (node.literal != null && node.literal.match('^<(div|span) data-mx-maths="[^"]*">$') != null) { + inMath = true; + freeze_node(walker, node); + } + } else { + // math tags should only contain a single code block, with URL-escaped latex as fallback output + if (node.literal != null && node.literal.match('^(||[^<>]*)$')) { + freeze_node(walker, node); + // leave when span or div is closed + } else if (node.literal == '
' || node.literal == '') { + inMath = false; + freeze_node(walker, node); + // this case only happens if we have improperly formatted math tags, so bail + } else { + inMath = false; + } + } + } + } } function is_allowed_html_tag(node) { - if (SettingsStore.getValue("feature_latex_maths") && - (is_math_node(node) || - (node.literal.match(/^<\/?code>$/) && is_math_node(node.parent)))) { - return true; - } - // Regex won't work for tags with attrs, but we only // allow anyway. const matches = /^<\/?(.*)>$/.exec(node.literal); @@ -173,6 +201,9 @@ export default class Markdown { */ }; + // prevent strange behaviour when mixing latex math and markdown + freeze_math(this.parsed); + return renderer.render(this.parsed); } From 38d1aac978d49160bed9c96b2a1205a4e7fb707f Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 12 Oct 2020 21:15:38 +0100 Subject: [PATCH 19/79] removed useless import and whitespace --- src/Markdown.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index 564a2ed0a8..2e6f391818 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -16,7 +16,6 @@ limitations under the License. import commonmark from 'commonmark'; import {escape} from "lodash"; -import SettingsStore from './settings/SettingsStore'; const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; @@ -71,7 +70,6 @@ function is_allowed_html_tag(node) { const tag = matches[1]; return ALLOWED_HTML_TAGS.indexOf(tag) > -1; } - return false; } From cc713aff72c56478edb4f1eafbdc55b8c9fd4248 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Wed, 14 Oct 2020 09:35:57 +0100 Subject: [PATCH 20/79] add fallback output in code block AFTER markdown processing --- src/Markdown.js | 49 +++++------------------------------------ src/editor/serialize.ts | 18 ++++++++++++--- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index 2e6f391818..dc4d442aff 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -22,47 +22,12 @@ const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; -// prevent renderer from interpreting contents of AST node -function freeze_node(walker, node) { - const newNode = new commonmark.Node('custom_inline', node.sourcepos); - newNode.onEnter = node.literal; - node.insertAfter(newNode); - node.unlink(); - walker.resumeAt(newNode.next, true); -} - -// prevent renderer from interpreting contents of latex math tags -function freeze_math(parsed) { - const walker = parsed.walker(); - let ev; - let inMath = false; - while ( (ev = walker.next()) ) { - const node = ev.node; - if (ev.entering) { - if (!inMath) { - // entering a math tag - if (node.literal != null && node.literal.match('^<(div|span) data-mx-maths="[^"]*">$') != null) { - inMath = true; - freeze_node(walker, node); - } - } else { - // math tags should only contain a single code block, with URL-escaped latex as fallback output - if (node.literal != null && node.literal.match('^(||[^<>]*)$')) { - freeze_node(walker, node); - // leave when span or div is closed - } else if (node.literal == '
' || node.literal == '') { - inMath = false; - freeze_node(walker, node); - // this case only happens if we have improperly formatted math tags, so bail - } else { - inMath = false; - } - } - } - } -} - function is_allowed_html_tag(node) { + if (node.literal != null && + node.literal.match('^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$') != null) { + return true; + } + // Regex won't work for tags with attrs, but we only // allow anyway. const matches = /^<\/?(.*)>$/.exec(node.literal); @@ -70,6 +35,7 @@ function is_allowed_html_tag(node) { const tag = matches[1]; return ALLOWED_HTML_TAGS.indexOf(tag) > -1; } + return false; } @@ -199,9 +165,6 @@ export default class Markdown { */ }; - // prevent strange behaviour when mixing latex math and markdown - freeze_math(this.parsed); - return renderer.render(this.parsed); } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 9f24cd5eb2..88fd1c90fc 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -21,6 +21,7 @@ import EditorModel from "./model"; import { AllHtmlEntities } from 'html-entities'; import SettingsStore from '../settings/SettingsStore'; import SdkConfig from '../SdkConfig'; +import cheerio from 'cheerio'; export function mdSerialize(model: EditorModel) { return model.parts.reduce((html, part) => { @@ -51,18 +52,29 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); - return `
${p1e}
`; + return `
`; }); md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); - return `${p1e}`; + return ``; }); } const parser = new Markdown(md); if (!parser.isPlainText() || forceHTML) { - return parser.toHTML(); + // feed Markdown output to HTML parser + const phtml = cheerio.load(parser.toHTML(), + { _useHtmlParser2: true, decodeEntities: false }) + + // add fallback output for latex math, which should not be interpreted as markdown + phtml('div, span').each(function() { + const tex = phtml(this).attr('data-mx-maths') + if (tex) { + phtml(this).html(`${tex}`) + } + }); + return phtml.html(); } // ensure removal of escape backslashes in non-Markdown messages if (md.indexOf("\\") > -1) { From 10b732131a7315aca652677857a285d7dabb243b Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Wed, 14 Oct 2020 22:16:28 +0100 Subject: [PATCH 21/79] use html parser rather than regexes --- src/HtmlUtils.tsx | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 6bae0b25b6..dc2f45210b 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -30,6 +30,7 @@ import url from 'url'; import katex from 'katex'; import { AllHtmlEntities } from 'html-entities'; import SettingsStore from './settings/SettingsStore'; +import cheerio from 'cheerio'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -414,23 +415,20 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); - if (SettingsStore.getValue("feature_latex_maths")) { - const mathDelimiters = [ - { pattern: "
(.|\\s)*?
", display: true }, - { pattern: "(.|\\s)*?", display: false }, - ]; + const phtml = cheerio.load(safeBody, + { _useHtmlParser2: true, decodeEntities: false }) - mathDelimiters.forEach(function(d) { - safeBody = safeBody.replace(RegExp(d.pattern, "gm"), function(m, p1) { - return katex.renderToString( - AllHtmlEntities.decode(p1), - { - throwOnError: false, - displayMode: d.display, - output: "htmlAndMathml", - }) - }); + if (SettingsStore.getValue("feature_latex_maths")) { + phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) { + return katex.renderToString( + AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')), + { + throwOnError: false, + displayMode: e.name == 'div', + output: "htmlAndMathml", + }); }); + safeBody = phtml.html(); } } } finally { From 173d79886544bc57c8de0b1ae4b16a346cd73bae Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 23 Oct 2020 18:41:24 +0100 Subject: [PATCH 22/79] added cheerio as explicit dep in package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 0a3fd7a8b7..ca7d6ee0b7 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "html-entities": "^1.3.1", "is-ip": "^2.0.0", "katex": "^0.12.0", + "cheerio": "^1.0.0-rc.3", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", From 06b20fad9543063409823540fcd4416a12c3ee21 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 23 Oct 2020 18:49:56 +0100 Subject: [PATCH 23/79] removed implicit "this" --- src/editor/serialize.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 88fd1c90fc..f31dd67ae7 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -68,10 +68,10 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = { _useHtmlParser2: true, decodeEntities: false }) // add fallback output for latex math, which should not be interpreted as markdown - phtml('div, span').each(function() { - const tex = phtml(this).attr('data-mx-maths') + phtml('div, span').each(function(i, e) { + const tex = phtml(e).attr('data-mx-maths') if (tex) { - phtml(this).html(`${tex}`) + phtml(e).html(`${tex}`) } }); return phtml.html(); From 2204e6c64e0042e0b937cf7d42e07816608e0234 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 25 Oct 2020 18:32:24 +0000 Subject: [PATCH 24/79] generate valid block html for commonmark spec --- src/editor/serialize.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index f31dd67ae7..bd7845315e 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -52,13 +52,17 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); - return `
`; + return `
\n\n
\n\n`; }); md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); return ``; }); + + // make sure div tags always start on a new line, otherwise it will confuse + // the markdown parser + md = md.replace(/(.)
Date: Thu, 29 Oct 2020 13:22:09 +0000 Subject: [PATCH 25/79] stubbed isGuest for unit tests --- test/components/views/messages/TextualBody-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/components/views/messages/TextualBody-test.js b/test/components/views/messages/TextualBody-test.js index 07cd51edbd..bf55e9c430 100644 --- a/test/components/views/messages/TextualBody-test.js +++ b/test/components/views/messages/TextualBody-test.js @@ -36,6 +36,7 @@ describe("", () => { MatrixClientPeg.matrixClient = { getRoom: () => mkStubRoom("room_id"), getAccountData: () => undefined, + isGuest: () => false, }; const ev = mkEvent({ @@ -59,6 +60,7 @@ describe("", () => { MatrixClientPeg.matrixClient = { getRoom: () => mkStubRoom("room_id"), getAccountData: () => undefined, + isGuest: () => false, }; const ev = mkEvent({ @@ -83,6 +85,7 @@ describe("", () => { MatrixClientPeg.matrixClient = { getRoom: () => mkStubRoom("room_id"), getAccountData: () => undefined, + isGuest: () => false, }; }); @@ -135,6 +138,7 @@ describe("", () => { getHomeserverUrl: () => "https://my_server/", on: () => undefined, removeListener: () => undefined, + isGuest: () => false, }; }); From 839bae21ae5078e25b7e6a03cc4a99725014b029 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Tue, 10 Nov 2020 18:18:53 +0000 Subject: [PATCH 26/79] made single and double $ default delimiters --- src/editor/deserialize.ts | 8 ++++---- src/editor/serialize.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index e27eecd2db..6336b4c46b 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -136,11 +136,11 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl // math nodes are translated back into delimited latex strings if (n.hasAttribute("data-mx-maths")) { const delimLeft = (n.nodeName == "SPAN") ? - (SdkConfig.get()['latex_maths_delims'] || {})['inline_left'] || "$$" : - (SdkConfig.get()['latex_maths_delims'] || {})['display_left'] || "$$$"; + (SdkConfig.get()['latex_maths_delims'] || {})['inline_left'] || "$" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_left'] || "$$"; const delimRight = (n.nodeName == "SPAN") ? - (SdkConfig.get()['latex_maths_delims'] || {})['inline_right'] || "$$" : - (SdkConfig.get()['latex_maths_delims'] || {})['display_right'] || "$$$"; + (SdkConfig.get()['latex_maths_delims'] || {})['inline_right'] || "$" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_right'] || "$$"; const tex = n.getAttribute("data-mx-maths"); return partCreator.plain(delimLeft + tex + delimRight); } else if (!checkDescendInto(n)) { diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index bd7845315e..c1f4da306b 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -46,9 +46,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = if (SettingsStore.getValue("feature_latex_maths")) { const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || - "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$"; - const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || "\\$\\$(([^$]|\\\\\\$)*)\\$\\$"; + const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || + "\\$(([^$]|\\\\\\$)*)\\$"; md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); From 8233ce77cbeda9706932a5ff5d7083a6775a52e0 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Tue, 10 Nov 2020 18:26:09 +0000 Subject: [PATCH 27/79] fixed duplicate import from merge --- src/HtmlUtils.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index d25c420bc9..44fbffb97f 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -33,7 +33,6 @@ import SettingsStore from './settings/SettingsStore'; import cheerio from 'cheerio'; import {MatrixClientPeg} from './MatrixClientPeg'; -import SettingsStore from './settings/SettingsStore'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji"; import ReplyThread from "./components/views/elements/ReplyThread"; From 5f23c9499c6a60ae52de1663724a712bf2749a11 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 12 Nov 2020 12:46:55 +0000 Subject: [PATCH 28/79] Simplify UserMenu for Guests as they can't use most of the options --- res/css/structures/_UserMenu.scss | 20 ++++++++++ src/Lifecycle.ts | 6 +-- src/components/structures/UserMenu.tsx | 53 +++++++++++++++++++++++--- src/i18n/strings/en_EN.json | 2 + 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 6a352d46a3..84c21364ce 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -231,9 +231,29 @@ limitations under the License. justify-content: center; } + &.mx_UserMenu_contextMenu_guestPrompts, &.mx_UserMenu_contextMenu_hostingLink { padding-top: 0; } + + &.mx_UserMenu_contextMenu_guestPrompts { + display: inline-block; + + > span { + font-weight: 600; + display: block; + + & + span { + margin-top: 8px; + } + } + + .mx_AccessibleButton_kind_link { + font-weight: normal; + font-size: inherit; + padding: 0; + } + } } .mx_IconizedContextMenu_icon { diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 7469624f5c..73c0ccce6d 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -588,9 +588,9 @@ export function logout(): void { if (MatrixClientPeg.get().isGuest()) { // logout doesn't work for guest sessions - // Also we sometimes want to re-log in a guest session - // if we abort the login - onLoggedOut(); + // Also we sometimes want to re-log in a guest session if we abort the login. + // defer until next tick because it calls a synchronous dispatch and we are likely here from a dispatch. + setImmediate(() => onLoggedOut()); return; } diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 75208b8cfe..e38dd5c2b9 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -29,7 +29,7 @@ import LogoutDialog from "../views/dialogs/LogoutDialog"; import SettingsStore from "../../settings/SettingsStore"; import {getCustomTheme} from "../../theme"; import {getHostingLink} from "../../utils/HostingLink"; -import {ButtonEvent} from "../views/elements/AccessibleButton"; +import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton"; import SdkConfig from "../../SdkConfig"; import {getHomePageUrl} from "../../utils/pages"; import { OwnProfileStore } from "../../stores/OwnProfileStore"; @@ -205,6 +205,16 @@ export default class UserMenu extends React.Component { this.setState({contextMenuPosition: null}); // also close the menu }; + private onSignInClick = () => { + dis.dispatch({ action: 'start_login' }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onRegisterClick = () => { + dis.dispatch({ action: 'start_registration' }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + private onHomeClick = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -261,10 +271,29 @@ export default class UserMenu extends React.Component { const prototypeCommunityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); - let hostingLink; + let topSection; const signupLink = getHostingLink("user-context-menu"); - if (signupLink) { - hostingLink = ( + if (MatrixClientPeg.get().isGuest()) { + topSection = ( +
+ {_t("Not you? Sign in", {}, { + a: sub => ( + + {sub} + + ), + })} + {_t("New here? Create an account", {}, { + a: sub => ( + + {sub} + + ), + })} +
+ ) + } else if (signupLink) { + topSection = (
{_t( "Upgrade to your own domain", {}, @@ -422,6 +451,20 @@ export default class UserMenu extends React.Component { ) + } else if (MatrixClientPeg.get().isGuest()) { + primaryOptionList = ( + + + { homeButton } + this.onSettingsOpen(e, null)} + /> + { feedbackButton } + + + ); } const classes = classNames({ @@ -451,7 +494,7 @@ export default class UserMenu extends React.Component { />
- {hostingLink} + {topSection} {primaryOptionList} {secondarySection} ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 830d3cdee4..4de5c297dd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2408,6 +2408,8 @@ "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", "Failed to find the general chat for this community": "Failed to find the general chat for this community", + "Not you? Sign in": "Not you? Sign in", + "New here? Create an account": "New here? Create an account", "Notification settings": "Notification settings", "Security & privacy": "Security & privacy", "All settings": "All settings", From c9215678314f5b34e0272de7f84cb882988e8c76 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 12 Nov 2020 18:09:56 +0000 Subject: [PATCH 29/79] WIP: the new call views work now just need to add the buttons and then get rid of the status bar --- res/css/views/avatars/_BaseAvatar.scss | 2 +- res/css/views/voip/_CallView.scss | 145 +++++++++++---- res/css/views/voip/_VideoFeed.scss | 15 +- src/components/views/avatars/PulsedAvatar.tsx | 28 --- src/components/views/voip/CallPreview.tsx | 12 +- src/components/views/voip/CallView.tsx | 175 ++++++++++++------ src/components/views/voip/IncomingCallBox.tsx | 13 +- src/components/views/voip/VideoFeed.tsx | 4 +- src/i18n/strings/en_EN.json | 5 +- 9 files changed, 257 insertions(+), 142 deletions(-) delete mode 100644 src/components/views/avatars/PulsedAvatar.tsx diff --git a/res/css/views/avatars/_BaseAvatar.scss b/res/css/views/avatars/_BaseAvatar.scss index 1a1e14e7ac..cbddd97e18 100644 --- a/res/css/views/avatars/_BaseAvatar.scss +++ b/res/css/views/avatars/_BaseAvatar.scss @@ -41,7 +41,7 @@ limitations under the License. .mx_BaseAvatar_image { object-fit: cover; - border-radius: 40px; + border-radius: 125px; vertical-align: top; background-color: $avatar-bg-color; } diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 2aeaaa87dc..eadad831ab 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -15,47 +15,46 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_CallView { + border-radius: 10px; + background-color: $input-lighter-bg-color; + padding-left: 8px; + padding-right: 8px; + // XXX: CallContainer sets pointer-events: none - should probably be set back in a better place + pointer-events: initial; +} + +.mx_CallView_large { + padding-bottom: 10px; + + .mx_CallView_voice { + height: 360px; + } +} + +.mx_CallView_pip { + width: 320px; + + .mx_CallView_voice { + height: 180px + } +} + +.mx_CallView_voice { + display: flex; + align-items: center; + justify-content: center; + background-color: $inverted-bg-color; +} + +/* .mx_CallView_voice { - background-color: $accent-color; - color: $accent-fg-color; - cursor: pointer; padding: 6px; font-weight: bold; - border-radius: 8px; min-width: 200px; - - display: flex; - align-items: center; - - img { - margin: 4px; - margin-right: 10px; - } - - > div { - display: flex; - flex-direction: column; - // Hacky vertical align - padding-top: 3px; - } - - > div > p, - > div > h1 { - padding: 0; - margin: 0; - font-size: $font-13px; - line-height: $font-15px; - } - - > div > p { - font-weight: bold; - } - - > * { - flex-grow: 0; - flex-shrink: 0; - } + text-align: center; + vertical-align: middle; } .mx_CallView_hangup { @@ -92,6 +91,7 @@ limitations under the License. background-color: $primary-fg-color; } } +*/ .mx_CallView_video { width: 100%; @@ -99,3 +99,76 @@ limitations under the License. z-index: 30; } +.mx_CallView_header { + height: 44px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: left; + + .mx_BaseAvatar { + margin-right: 12px; + } +} + +.mx_CallView_header_callType { + font-weight: bold; + vertical-align: middle; +} + +.mx_CallView_header_controls { + margin-left: auto; +} + +.mx_CallView_header_control_fullscreen { + display: inline-block; + vertical-align: middle; + cursor: pointer; + + &::before { + content: ''; + display: inline-block; + height: 20px; + width: 20px; + vertical-align: middle; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + mask-image: url('$(res)/img/element-icons/call/fullscreen.svg'); + } +} + +.mx_CallView_header_roomName { + font-weight: bold; + font-size: 12px; + line-height: initial; +} + +.mx_CallView_header_callTypeSmall { + font-size: 12px; + color: $secondary-fg-color; + line-height: initial; +} + +.mx_CallView_header_phoneIcon { + display: inline-block; + margin-right: 6px; + height: 16px; + width: 16px; + vertical-align: middle; + + &::before { + content: ''; + display: inline-block; + vertical-align: top; + + height: 16px; + width: 16px; + background-color: $warning-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } +} diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index e5e3587dac..1368ead02f 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VideoFeed video { +/*.mx_VideoFeed video { width: 100%; -} +}*/ .mx_VideoFeed_remote { width: 100%; @@ -28,16 +28,17 @@ limitations under the License. width: 25%; height: 25%; position: absolute; - left: 10px; - bottom: 10px; + right: 10px; + top: 10px; z-index: 100; + border-radius: 4px; } -.mx_VideoFeed_local video { +/*.mx_VideoFeed_local video { width: auto; height: 100%; -} +}*/ -.mx_VideoFeed_mirror video { +.mx_VideoFeed_mirror { transform: scale(-1, 1); } diff --git a/src/components/views/avatars/PulsedAvatar.tsx b/src/components/views/avatars/PulsedAvatar.tsx deleted file mode 100644 index b4e876b9f6..0000000000 --- a/src/components/views/avatars/PulsedAvatar.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2020 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 React from 'react'; - -interface IProps { -} - -const PulsedAvatar: React.FC = (props) => { - return
- {props.children} -
; -}; - -export default PulsedAvatar; diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 3d9235792b..8e1b0dd963 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -26,6 +26,15 @@ import PersistentApp from "../elements/PersistentApp"; import SettingsStore from "../../../settings/SettingsStore"; import { CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; +const SHOW_CALL_IN_STATES = [ + CallState.Connected, + CallState.InviteSent, + CallState.Connecting, + CallState.CreateAnswer, + CallState.CreateOffer, + CallState.WaitLocalMedia, +]; + interface IProps { } @@ -94,14 +103,13 @@ export default class CallPreview extends React.Component { const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId); const showCall = ( this.state.activeCall && - this.state.activeCall.state === CallState.Connected && + SHOW_CALL_IN_STATES.includes(this.state.activeCall.state) && !callForRoom ); if (showCall) { return ( diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 653a72cca0..7288cd1d5b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -21,10 +21,8 @@ import dis from '../../../dispatcher/dispatcher'; import CallHandler from '../../../CallHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import AccessibleButton from '../elements/AccessibleButton'; import VideoFeed, { VideoFeedType } from "./VideoFeed"; import RoomAvatar from "../avatars/RoomAvatar"; -import PulsedAvatar from '../avatars/PulsedAvatar'; import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { CallEvent } from 'matrix-js-sdk/src/webrtc/call'; @@ -43,9 +41,6 @@ interface IProps { // in a way that is likely to cause a resize. onResize?: any; - // classname applied to view, - className?: string; - // Whether to show the hang up icon:W showHangup?: boolean; } @@ -85,7 +80,7 @@ function exitFullscreen() { export default class CallView extends React.Component { private dispatcherRef: string; - private container = createRef(); + private contentRef = createRef(); constructor(props: IProps) { super(props); @@ -111,11 +106,11 @@ export default class CallView extends React.Component { private onAction = (payload) => { switch (payload.action) { case 'video_fullscreen': { - if (!this.container.current) { + if (!this.contentRef.current) { return; } if (payload.fullscreen) { - requestFullscreen(this.container.current); + requestFullscreen(this.contentRef.current); } else if (getFullScreenElement()) { exitFullscreen(); } @@ -144,11 +139,6 @@ export default class CallView extends React.Component { if (this.props.room) { const roomId = this.props.room.roomId; call = CallHandler.sharedInstance().getCallForRoom(roomId); - - // We don't currently show voice calls in this view when in the room: - // they're represented in the room status bar at the bottom instead - // (but this will all change with the new designs) - if (call && call.type == CallType.Voice) call = null; } else { call = CallHandler.sharedInstance().getAnyActiveCall(); // Ignore calls if we can't get the room associated with them. @@ -160,7 +150,7 @@ export default class CallView extends React.Component { } } - if (call && call.state == CallState.Ended) return null; + if (call && [CallState.Ended, CallState.Ringing].includes(call.state)) return null; return call; } @@ -177,51 +167,91 @@ export default class CallView extends React.Component { }); }; + private onFullscreenClick = () => { + dis.dispatch({ + action: 'video_fullscreen', + fullscreen: true, + }); + }; + public render() { - let view: React.ReactNode; + if (!this.state.call) return null; - if (this.state.call) { - if (this.state.call.type === "voice") { - const client = MatrixClientPeg.get(); - const callRoom = client.getRoom(this.state.call.roomId); + const client = MatrixClientPeg.get(); + const callRoom = client.getRoom(this.state.call.roomId); - let caption = _t("Active call"); - if (this.state.isLocalOnHold) { - // we currently have no UI for holding / unholding a call (apart from slash - // commands) so we don't disintguish between when we've put the call on hold - // (ie. we'd show an unhold button) and when the other side has put us on hold - // (where obviously we would not show such a button). - caption = _t("Call Paused"); - } + //const callControls =
- view = - - - -
-

{callRoom.name}

-

{ caption }

-
-
; - } else { - // For video calls, we currently ignore the call hold state altogether - // (the video will just go black) + //
; - // if we're fullscreen, we don't want to set a maxHeight on the video element. - const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight; - view =
- - -
; - } + // The 'content' for the call, ie. the videos for a video call and profile picture + // for voice calls (fills the bg) + let contentView: React.ReactNode; + + if (this.state.call.type === CallType.Video) { + // if we're fullscreen, we don't want to set a maxHeight on the video element. + const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight; + contentView =
+ + +
; + } else { + const avatarSize = this.props.room ? 200 : 75; + contentView =
+ +
; } + /* + if (!this.props.room) { + const client = MatrixClientPeg.get(); + const callRoom = client.getRoom(this.state.call.roomId); + + let caption = _t("Active call"); + if (this.state.isLocalOnHold) { + // we currently have no UI for holding / unholding a call (apart from slash + // commands) so we don't disintguish between when we've put the call on hold + // (ie. we'd show an unhold button) and when the other side has put us on hold + // (where obviously we would not show such a button). + caption = _t("Call Paused"); + } + + view = + + + +
+

{callRoom.name}

+

{ caption }

+
+
; + } else { + // For video calls, we currently ignore the call hold state altogether + // (the video will just go black) + + // if we're fullscreen, we don't want to set a maxHeight on the video element. + const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight; + view =
+ + +
; + } + */ + + + /* let hangup: React.ReactNode; if (this.props.showHangup) { hangup =
{ }} />; } + */ - return
- {view} - {hangup} + const callTypeText = this.state.call.type === CallType.Video ? _t("Video Call") : _t("Voice Call"); + let myClassName; + + let fullScreenButton; + if (this.state.call.type === CallType.Video) { + fullScreenButton =
; + } + + const headerControls =
+ {fullScreenButton} +
; + + let header: React.ReactNode; + if (this.props.room) { + header =
+
+ {callTypeText} + {headerControls} +
; + myClassName = 'mx_CallView_large'; + } else { + header =
+ +
+
{callRoom.name}
+
{callTypeText}
+
+ {headerControls} +
; + myClassName = 'mx_CallView_pip'; + } + + return
+ {header} + {contentView}
; } } diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 355dff9ff6..0403a9eb75 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -22,7 +22,6 @@ import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; import { ActionPayload } from '../../../dispatcher/payloads'; import CallHandler from '../../../CallHandler'; -import PulsedAvatar from '../avatars/PulsedAvatar'; import RoomAvatar from '../avatars/RoomAvatar'; import FormButton from '../elements/FormButton'; import { CallState } from 'matrix-js-sdk/lib/webrtc/call'; @@ -108,13 +107,11 @@ export default class IncomingCallBox extends React.Component { return
- - - +

{caller}

{incomingCallText}

diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 9dba9fa9c8..5fb71a6d69 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -73,8 +73,6 @@ export default class VideoFeed extends React.Component { let videoStyle = {}; if (this.props.maxHeight) videoStyle = { maxHeight: this.props.maxHeight }; - return
- -
; + return