From 7f727be4f65598457786a4c9fc742fff40394b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 18 Jan 2021 17:44:32 +0100 Subject: [PATCH 01/27] Added expand code block option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + src/settings/Settings.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 4d8493401e..636c5cb7c8 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -46,6 +46,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'alwaysShowTimestamps', 'showRedactions', 'enableSyntaxHighlightLanguageDetection', + 'expandCodeByDefault', 'showJoinLeaves', 'showAvatarChanges', 'showDisplaynameChanges', diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b239b809fe..bef1564e90 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -300,6 +300,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Enable automatic language detection for syntax highlighting'), default: false, }, + "expandCodeByDefault": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Expand code blocks by default'), + default: false, + }, "Pill.shouldShowPillAvatar": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Show avatars in user and room mentions'), From 49dce58027c5f754ad88a2a8b34811cdac6225da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 18 Jan 2021 17:49:09 +0100 Subject: [PATCH 02/27] Added expanding based on the option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_EventTile.scss | 7 ++++++- src/components/views/messages/TextualBody.js | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 429ac7ed4b..5fc7a5f04b 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -493,7 +493,6 @@ $left-gutter: 64px; // https://github.com/vector-im/vector-web/issues/754 overflow-x: overlay; overflow-y: visible; - max-height: 30vh; } code { @@ -502,6 +501,12 @@ $left-gutter: 64px; } } +.mx_EventTile_content_collapsedCode { + pre { + max-height: 30vh; + } +} + .mx_EventTile:hover .mx_EventTile_body pre, .mx_EventTile.focus-visible:focus-within .mx_EventTile_body pre { border: 1px solid #e5e5e5; // deliberate constant as we're behind an invert filter diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 8f153e48e9..ce11e63026 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -35,6 +35,7 @@ import {isPermalinkHost} from "../../../utils/permalinks/Permalinks"; import {toRightOf} from "../../structures/ContextMenu"; import {copyPlaintext} from "../../../utils/strings"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import classNames from "classnames"; export default class TextualBody extends React.Component { static propTypes = { @@ -69,6 +70,7 @@ export default class TextualBody extends React.Component { // track whether the preview widget is hidden widgetHidden: false, + codeBlockExpanded: SettingsStore.getValue("expandCodeByDefault"), }; } @@ -434,6 +436,12 @@ export default class TextualBody extends React.Component { }); } + const defaultCaseClasses = classNames({ + mx_MTextBody: true, + mx_EventTile_content: true, + mx_EventTile_content_collapsedCode: !this.state.codeBlockExpanded, + }); + switch (content.msgtype) { case "m.emote": return ( @@ -459,7 +467,7 @@ export default class TextualBody extends React.Component { ); default: // including "m.text" return ( - + { body } { widgets } From 61281a855c08d9a9eacd4e1da103acc3f59cfb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 19 Jan 2021 16:35:32 +0100 Subject: [PATCH 03/27] Redo expanding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_EventTile.scss | 39 ++++++- src/components/views/messages/TextualBody.js | 111 ++++++++++--------- 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 5fc7a5f04b..c587251d3e 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -501,10 +501,12 @@ $left-gutter: 64px; } } -.mx_EventTile_content_collapsedCode { - pre { - max-height: 30vh; - } +.mx_EventTile_expandedCodeBlock { + max-height: 100vh; +} + +.mx_EventTile_collapsedCodeBlock { + max-height: 30vh; } .mx_EventTile:hover .mx_EventTile_body pre, @@ -531,6 +533,35 @@ $left-gutter: 64px; background-color: $message-action-bar-fg-color; } + +// Inserted adjacent to
 blocks, (See TextualBody)
+.mx_EventTile_expandButton {
+    position: absolute;
+    display: inline-block;
+    visibility: hidden;
+    cursor: pointer;
+    top: 6px;
+    right: 6px;
+    width: 19px;
+    height: 19px;
+    mask-image: url($copy-button-url);
+    background-color: $message-action-bar-fg-color;
+}
+
+// Inserted adjacent to 
 blocks, (See TextualBody)
+.mx_EventTile_collapseButton {
+    position: absolute;
+    display: inline-block;
+    visibility: hidden;
+    cursor: pointer;
+    top: 6px;
+    right: 6px;
+    width: 19px;
+    height: 19px;
+    mask-image: url($copy-button-url);
+    background-color: $message-action-bar-fg-color;
+}
+
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
 .mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton {
     visibility: visible;
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index ff864d9c13..8d341e4a63 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -35,7 +35,6 @@ import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
 import {toRightOf} from "../../structures/ContextMenu";
 import {copyPlaintext} from "../../../utils/strings";
 import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
-import classNames from "classnames";
 
 export default class TextualBody extends React.Component {
     static propTypes = {
@@ -70,7 +69,6 @@ export default class TextualBody extends React.Component {
 
             // track whether the preview widget is hidden
             widgetHidden: false,
-            codeBlockExpanded: SettingsStore.getValue("expandCodeByDefault"),
         };
     }
 
@@ -93,29 +91,70 @@ export default class TextualBody extends React.Component {
         this.calculateUrlPreview();
 
         if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
-            const blocks = ReactDOM.findDOMNode(this).getElementsByTagName("code");
+            const blocks = ReactDOM.findDOMNode(this).getElementsByTagName("pre");
             if (blocks.length > 0) {
+                for (let i = 0; i < blocks.length; i++) {
+                    this._handleCodeBlockExpansion(blocks[i]);
+                    this._addCodeCopyButton(blocks[i]);
+                }
                 // Do this asynchronously: parsing code takes time and we don't
                 // need to block the DOM update on it.
                 setTimeout(() => {
                     if (this._unmounted) return;
                     for (let i = 0; i < blocks.length; i++) {
-                        if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
-                            highlight.highlightBlock(blocks[i]);
-                        } else {
-                            // Only syntax highlight if there's a class starting with language-
-                            const classes = blocks[i].className.split(/\s+/).filter(function(cl) {
-                                return cl.startsWith('language-') && !cl.startsWith('language-_');
-                            });
-
-                            if (classes.length != 0) {
-                                highlight.highlightBlock(blocks[i]);
-                            }
-                        }
+                        this._highlightCode(blocks[i].firstChild);
                     }
                 }, 10);
             }
-            this._addCodeCopyButton();
+        }
+    }
+
+    _addCodeCopyButton(codeBlock) {
+        const button = document.createElement("span");
+        button.className = "mx_EventTile_copyButton";
+        button.onclick = async () => {
+            const copyCode = button.parentNode.getElementsByTagName("pre")[0];
+            const successful = await copyPlaintext(copyCode.textContent);
+
+            const buttonRect = button.getBoundingClientRect();
+            const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
+            const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
+                ...toRightOf(buttonRect, 2),
+                message: successful ? _t('Copied!') : _t('Failed to copy'),
+            });
+            button.onmouseleave = close;
+        };
+
+        // Wrap a div around 
 so that the copy button can be correctly positioned
+        // when the 
 overflows and is scrolled horizontally.
+        const div = document.createElement("div");
+        div.className = "mx_EventTile_pre_container";
+
+        // Insert containing div in place of 
 block
+        codeBlock.parentNode.replaceChild(div, codeBlock);
+
+        // Append 
 block and copy button to container
+        div.appendChild(codeBlock);
+        div.appendChild(button);
+    }
+
+    _handleCodeBlockExpansion(codeBlock) {
+        const expandCodeBlock = SettingsStore.getValue("expandCodeByDefault");
+        codeBlock.className = expandCodeBlock ? "mx_EventTile_expandedCodeBlock" : "mx_EventTile_collapsedCodeBlock";
+    }
+
+    _highlightCode(codeBlock) {
+        if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
+            highlight.highlightBlock(codeBlock);
+        } else {
+            // Only syntax highlight if there's a class starting with language-
+            const classes = codeBlock.className.split(/\s+/).filter(function(cl) {
+                return cl.startsWith('language-') && !cl.startsWith('language-_');
+            });
+
+            if (classes.length != 0) {
+                highlight.highlightBlock(codeBlock);
+            }
         }
     }
 
@@ -256,38 +295,6 @@ export default class TextualBody extends React.Component {
         }
     }
 
-    _addCodeCopyButton() {
-        // Add 'copy' buttons to pre blocks
-        Array.from(ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre')).forEach((p) => {
-            const button = document.createElement("span");
-            button.className = "mx_EventTile_copyButton";
-            button.onclick = async () => {
-                const copyCode = button.parentNode.getElementsByTagName("pre")[0];
-                const successful = await copyPlaintext(copyCode.textContent);
-
-                const buttonRect = button.getBoundingClientRect();
-                const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
-                const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
-                    ...toRightOf(buttonRect, 2),
-                    message: successful ? _t('Copied!') : _t('Failed to copy'),
-                });
-                button.onmouseleave = close;
-            };
-
-            // Wrap a div around 
 so that the copy button can be correctly positioned
-            // when the 
 overflows and is scrolled horizontally.
-            const div = document.createElement("div");
-            div.className = "mx_EventTile_pre_container";
-
-            // Insert containing div in place of 
 block
-            p.parentNode.replaceChild(div, p);
-
-            // Append 
 block and copy button to container
-            div.appendChild(p);
-            div.appendChild(button);
-        });
-    }
-
     onCancelClick = event => {
         this.setState({ widgetHidden: true });
         // FIXME: persist this somewhere smarter than local storage
@@ -439,12 +446,6 @@ export default class TextualBody extends React.Component {
             });
         }
 
-        const defaultCaseClasses = classNames({
-            mx_MTextBody: true,
-            mx_EventTile_content: true,
-            mx_EventTile_content_collapsedCode: !this.state.codeBlockExpanded,
-        });
-
         switch (content.msgtype) {
             case "m.emote":
                 return (
@@ -470,7 +471,7 @@ export default class TextualBody extends React.Component {
                 );
             default: // including "m.text"
                 return (
-                    
+                    
                         { body }
                         { widgets }
                     

From e6ab47ff764b28cdbbba2e190345f95435b5319d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Tue, 19 Jan 2021 17:30:02 +0100
Subject: [PATCH 04/27] Fix bug
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/views/rooms/_EventTile.scss          | 33 --------------------
 src/components/views/messages/TextualBody.js |  5 +--
 2 files changed, 3 insertions(+), 35 deletions(-)

diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index c587251d3e..83aa3ec294 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -501,10 +501,6 @@ $left-gutter: 64px;
     }
 }
 
-.mx_EventTile_expandedCodeBlock {
-    max-height: 100vh;
-}
-
 .mx_EventTile_collapsedCodeBlock {
     max-height: 30vh;
 }
@@ -533,35 +529,6 @@ $left-gutter: 64px;
     background-color: $message-action-bar-fg-color;
 }
 
-
-// Inserted adjacent to 
 blocks, (See TextualBody)
-.mx_EventTile_expandButton {
-    position: absolute;
-    display: inline-block;
-    visibility: hidden;
-    cursor: pointer;
-    top: 6px;
-    right: 6px;
-    width: 19px;
-    height: 19px;
-    mask-image: url($copy-button-url);
-    background-color: $message-action-bar-fg-color;
-}
-
-// Inserted adjacent to 
 blocks, (See TextualBody)
-.mx_EventTile_collapseButton {
-    position: absolute;
-    display: inline-block;
-    visibility: hidden;
-    cursor: pointer;
-    top: 6px;
-    right: 6px;
-    width: 19px;
-    height: 19px;
-    mask-image: url($copy-button-url);
-    background-color: $message-action-bar-fg-color;
-}
-
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
 .mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton {
     visibility: visible;
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 8d341e4a63..84a21c0da3 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -139,8 +139,9 @@ export default class TextualBody extends React.Component {
     }
 
     _handleCodeBlockExpansion(codeBlock) {
-        const expandCodeBlock = SettingsStore.getValue("expandCodeByDefault");
-        codeBlock.className = expandCodeBlock ? "mx_EventTile_expandedCodeBlock" : "mx_EventTile_collapsedCodeBlock";
+        if (!SettingsStore.getValue("expandCodeByDefault")) {
+            codeBlock.className = "mx_EventTile_collapsedCodeBlock";
+        }
     }
 
     _highlightCode(codeBlock) {

From 95939f3d6ca34c1757903f1efa28aeca4c911fd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Tue, 19 Jan 2021 18:21:59 +0100
Subject: [PATCH 05/27] Added _wrapInDiv() method
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 84a21c0da3..a6b32baea2 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -94,8 +94,11 @@ export default class TextualBody extends React.Component {
             const blocks = ReactDOM.findDOMNode(this).getElementsByTagName("pre");
             if (blocks.length > 0) {
                 for (let i = 0; i < blocks.length; i++) {
-                    this._handleCodeBlockExpansion(blocks[i]);
-                    this._addCodeCopyButton(blocks[i]);
+                    // Wrap a div around 
 so that the copy button can be correctly positioned
+                    // when the 
 overflows and is scrolled horizontally.
+                    const div = this._wrapInDiv(blocks[i]);
+                    this._handleCodeBlockExpansion(div);
+                    this._addCodeCopyButton(div);
                 }
                 // Do this asynchronously: parsing code takes time and we don't
                 // need to block the DOM update on it.
@@ -125,17 +128,19 @@ export default class TextualBody extends React.Component {
             button.onmouseleave = close;
         };
 
-        // Wrap a div around 
 so that the copy button can be correctly positioned
-        // when the 
 overflows and is scrolled horizontally.
+        codeBlock.appendChild(button);
+    }
+
+    _wrapInDiv(codeBlock) {
         const div = document.createElement("div");
         div.className = "mx_EventTile_pre_container";
 
         // Insert containing div in place of 
 block
         codeBlock.parentNode.replaceChild(div, codeBlock);
-
         // Append 
 block and copy button to container
         div.appendChild(codeBlock);
-        div.appendChild(button);
+
+        return div;
     }
 
     _handleCodeBlockExpansion(codeBlock) {

From 58b2c18cf55845715b8b6261f476caee86701cc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Tue, 19 Jan 2021 21:02:39 +0100
Subject: [PATCH 06/27] This somehow works
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/views/rooms/_EventTile.scss           | 48 ++++++++++++++++++-
 res/img/feather-customised/maximize.svg       | 24 ++++++++++
 res/img/feather-customised/minimize.svg       | 24 ++++++++++
 .../legacy-light/css/_legacy-light.scss       |  3 +-
 res/themes/light/css/_light.scss              |  2 +
 src/components/views/messages/TextualBody.js  | 27 ++++++++++-
 6 files changed, 124 insertions(+), 4 deletions(-)
 create mode 100644 res/img/feather-customised/maximize.svg
 create mode 100644 res/img/feather-customised/minimize.svg

diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 83aa3ec294..d80d6703a7 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -521,16 +521,60 @@ $left-gutter: 64px;
     display: inline-block;
     visibility: hidden;
     cursor: pointer;
-    top: 6px;
+    top: 31px;
     right: 6px;
     width: 19px;
     height: 19px;
     mask-image: url($copy-button-url);
     background-color: $message-action-bar-fg-color;
 }
+.mx_EventTile_collapseButton {
+    position: absolute;
+    display: inline-block;
+    visibility: hidden;
+    cursor: pointer;
+    top: 6px;
+    right: 6px;
+    width: 19px;
+    height: 19px;
+    mask-image: url($collapse-button-url);
+    background-color: $message-action-bar-fg-color;
+}
+.mx_EventTile_expandButton {
+    position: absolute;
+    display: inline-block;
+    visibility: hidden;
+    cursor: pointer;
+    top: 6px;
+    right: 6px;
+    width: 19px;
+    height: 19px;
+    mask-image: url($expand-button-url);
+    background-color: $message-action-bar-fg-color;
+}
+
+/*.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton {
+    visibility: visible;
+}
+
+.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_collapseButton,
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_collapseButton {
+    visibility: visible;
+}
+
+.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_expandButton,
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_expandButton {
+    visibility: visible;
+}*/
+
 
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
-.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton {
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton, 
+.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_collapseButton,
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_collapseButton, 
+.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_expandButton,
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_expandButton {
     visibility: visible;
 }
 
diff --git a/res/img/feather-customised/maximize.svg b/res/img/feather-customised/maximize.svg
new file mode 100644
index 0000000000..ad74c8b1eb
--- /dev/null
+++ b/res/img/feather-customised/maximize.svg
@@ -0,0 +1,24 @@
+
+  
+  
+  
+  
+
diff --git a/res/img/feather-customised/minimize.svg b/res/img/feather-customised/minimize.svg
new file mode 100644
index 0000000000..102af6df95
--- /dev/null
+++ b/res/img/feather-customised/minimize.svg
@@ -0,0 +1,24 @@
+
+  
+  
+  
+  
+
diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss
index 085d6d7f10..6219d5a5bf 100644
--- a/res/themes/legacy-light/css/_legacy-light.scss
+++ b/res/themes/legacy-light/css/_legacy-light.scss
@@ -237,7 +237,8 @@ $event-redacted-border-color: #cccccc;
 $event-timestamp-color: #acacac;
 
 $copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
-
+$collapse-button-url: "$(res)/img/feather-customised/minimize.svg";
+$expand-button-url: "$(res)/img/feather-customised/maximize.svg";
 
 // e2e
 $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index 4cfeeae05e..77c0e72726 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -237,6 +237,8 @@ $event-redacted-border-color: #cccccc;
 $event-timestamp-color: #acacac;
 
 $copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
+$collapse-button-url: "$(res)/img/feather-customised/minimize.svg";
+$expand-button-url: "$(res)/img/feather-customised/maximize.svg";
 
 // e2e
 $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index a6b32baea2..f7d66c3308 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -97,8 +97,9 @@ export default class TextualBody extends React.Component {
                     // Wrap a div around 
 so that the copy button can be correctly positioned
                     // when the 
 overflows and is scrolled horizontally.
                     const div = this._wrapInDiv(blocks[i]);
-                    this._handleCodeBlockExpansion(div);
+                    this._handleCodeBlockExpansion(div.firstChild);
                     this._addCodeCopyButton(div);
+                    this._addCodeExpansionButton(div);
                 }
                 // Do this asynchronously: parsing code takes time and we don't
                 // need to block the DOM update on it.
@@ -112,6 +113,30 @@ export default class TextualBody extends React.Component {
         }
     }
 
+    _addCodeExpansionButton(codeBlock) {
+        // TODO: What if the block is small and the we don't need the icon?
+
+        const pre = codeBlock.getElementsByTagName("pre")[0];
+        const button = document.createElement("span");
+        if (pre.className == "mx_EventTile_collapsedCodeBlock") {
+            button.className = "mx_EventTile_expandButton";
+        } else {
+            button.className = "mx_EventTile_collapseButton";
+        }
+
+        button.onclick = async () => {
+            if (pre.className == "mx_EventTile_collapsedCodeBlock") {
+                pre.className = "";
+                button.className = "mx_EventTile_expandButton";
+            } else {
+                pre.className = "mx_EventTile_collapsedCodeBlock";
+                button.className = "mx_EventTile_collapseButton";
+            }
+        };
+
+        codeBlock.appendChild(button);
+    }
+
     _addCodeCopyButton(codeBlock) {
         const button = document.createElement("span");
         button.className = "mx_EventTile_copyButton";

From 8535a11dd27569121f823dcbc6d25123e48e8358 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Wed, 20 Jan 2021 08:44:32 +0100
Subject: [PATCH 07/27] Rename some variable and cleanup a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 65 +++++++++++---------
 1 file changed, 36 insertions(+), 29 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index f7d66c3308..afda815e6d 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -91,32 +91,39 @@ export default class TextualBody extends React.Component {
         this.calculateUrlPreview();
 
         if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
-            const blocks = ReactDOM.findDOMNode(this).getElementsByTagName("pre");
-            if (blocks.length > 0) {
-                for (let i = 0; i < blocks.length; i++) {
-                    // Wrap a div around 
 so that the copy button can be correctly positioned
-                    // when the 
 overflows and is scrolled horizontally.
-                    const div = this._wrapInDiv(blocks[i]);
-                    this._handleCodeBlockExpansion(div.firstChild);
+            // Handle expansion and add buttons
+            const pres = ReactDOM.findDOMNode(this).getElementsByTagName("pre");
+            if (pres.length > 0) {
+                for (let i = 0; i < pres.length; i++) {
+                    /* Wrap a div around 
 so that the copy button can be correctly positioned
+                     * when the 
 overflows and is scrolled horizontally. */
+                    const div = this._wrapInDiv(pres[i]);
+                    this._handleCodeBlockExpansion(pres[i]);
                     this._addCodeCopyButton(div);
                     this._addCodeExpansionButton(div);
                 }
-                // Do this asynchronously: parsing code takes time and we don't
-                // need to block the DOM update on it.
-                setTimeout(() => {
-                    if (this._unmounted) return;
-                    for (let i = 0; i < blocks.length; i++) {
-                        this._highlightCode(blocks[i].firstChild);
-                    }
-                }, 10);
+            }
+            // Highlight code
+            const codes = ReactDOM.findDOMNode(this).getElementsByTagName("code");
+            if (codes.length >0) {
+                for (let i = 0; i < codes.length; i++) {
+                    /* Do this asynchronously: parsing code takes time and we don't
+                     * need to block the DOM update on it. */
+                    setTimeout(() => {
+                        if (this._unmounted) return;
+                            for (let i = 0; i < pres.length; i++) {
+                                this._highlightCode(codes[i]);
+                            }
+                    }, 10);
+                }
             }
         }
     }
 
-    _addCodeExpansionButton(codeBlock) {
+    _addCodeExpansionButton(div) {
         // TODO: What if the block is small and the we don't need the icon?
 
-        const pre = codeBlock.getElementsByTagName("pre")[0];
+        const pre = div.getElementsByTagName("pre")[0];
         const button = document.createElement("span");
         if (pre.className == "mx_EventTile_collapsedCodeBlock") {
             button.className = "mx_EventTile_expandButton";
@@ -134,10 +141,10 @@ export default class TextualBody extends React.Component {
             }
         };
 
-        codeBlock.appendChild(button);
+        div.appendChild(button);
     }
 
-    _addCodeCopyButton(codeBlock) {
+    _addCodeCopyButton(div) {
         const button = document.createElement("span");
         button.className = "mx_EventTile_copyButton";
         button.onclick = async () => {
@@ -153,38 +160,38 @@ export default class TextualBody extends React.Component {
             button.onmouseleave = close;
         };
 
-        codeBlock.appendChild(button);
+        div.appendChild(button);
     }
 
-    _wrapInDiv(codeBlock) {
+    _wrapInDiv(pre) {
         const div = document.createElement("div");
         div.className = "mx_EventTile_pre_container";
 
         // Insert containing div in place of 
 block
-        codeBlock.parentNode.replaceChild(div, codeBlock);
+        pre.parentNode.replaceChild(div, pre);
         // Append 
 block and copy button to container
-        div.appendChild(codeBlock);
+        div.appendChild(pre);
 
         return div;
     }
 
-    _handleCodeBlockExpansion(codeBlock) {
+    _handleCodeBlockExpansion(pre) {
         if (!SettingsStore.getValue("expandCodeByDefault")) {
-            codeBlock.className = "mx_EventTile_collapsedCodeBlock";
+            pre.className = "mx_EventTile_collapsedCodeBlock";
         }
     }
 
-    _highlightCode(codeBlock) {
+    _highlightCode(code) {
         if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
-            highlight.highlightBlock(codeBlock);
+            highlight.highlightBlock(code);
         } else {
             // Only syntax highlight if there's a class starting with language-
-            const classes = codeBlock.className.split(/\s+/).filter(function(cl) {
+            const classes = code.className.split(/\s+/).filter(function(cl) {
                 return cl.startsWith('language-') && !cl.startsWith('language-_');
             });
 
             if (classes.length != 0) {
-                highlight.highlightBlock(codeBlock);
+                highlight.highlightBlock(code);
             }
         }
     }

From 072cbe98b5c0dbfee81383a5b4785c24a5673711 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Wed, 20 Jan 2021 13:03:05 +0100
Subject: [PATCH 08/27] Simplifie
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index afda815e6d..5dc7d21176 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -100,7 +100,7 @@ export default class TextualBody extends React.Component {
                     const div = this._wrapInDiv(pres[i]);
                     this._handleCodeBlockExpansion(pres[i]);
                     this._addCodeCopyButton(div);
-                    this._addCodeExpansionButton(div);
+                    this._addCodeExpansionButton(div, pres[i]);
                 }
             }
             // Highlight code
@@ -120,10 +120,9 @@ export default class TextualBody extends React.Component {
         }
     }
 
-    _addCodeExpansionButton(div) {
+    _addCodeExpansionButton(div, pre) {
         // TODO: What if the block is small and the we don't need the icon?
 
-        const pre = div.getElementsByTagName("pre")[0];
         const button = document.createElement("span");
         if (pre.className == "mx_EventTile_collapsedCodeBlock") {
             button.className = "mx_EventTile_expandButton";

From 25f30ca79f81361bcfa2813de57d0d8b97d3e21b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Wed, 20 Jan 2021 13:05:23 +0100
Subject: [PATCH 09/27] Fix indent
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 5dc7d21176..902271a29e 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -111,9 +111,9 @@ export default class TextualBody extends React.Component {
                      * need to block the DOM update on it. */
                     setTimeout(() => {
                         if (this._unmounted) return;
-                            for (let i = 0; i < pres.length; i++) {
-                                this._highlightCode(codes[i]);
-                            }
+                        for (let i = 0; i < pres.length; i++) {
+                            this._highlightCode(codes[i]);
+                        }
                     }, 10);
                 }
             }

From 19be3293e0e10af6df269620aff30a53cbbacdfc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Wed, 20 Jan 2021 13:06:08 +0100
Subject: [PATCH 10/27] Remove commented code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/views/rooms/_EventTile.scss | 16 ----------------
 1 file changed, 16 deletions(-)

diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index d80d6703a7..3acebd03e9 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -553,22 +553,6 @@ $left-gutter: 64px;
     background-color: $message-action-bar-fg-color;
 }
 
-/*.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
-.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton {
-    visibility: visible;
-}
-
-.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_collapseButton,
-.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_collapseButton {
-    visibility: visible;
-}
-
-.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_expandButton,
-.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_expandButton {
-    visibility: visible;
-}*/
-
-
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
 .mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton, 
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_collapseButton,

From f8179f0c7a2c7cfb92d9cd3d6f6ecfdeb5a45513 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Wed, 20 Jan 2021 14:08:57 +0100
Subject: [PATCH 11/27] Fix inverted icon
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 902271a29e..511ea61904 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -133,10 +133,10 @@ export default class TextualBody extends React.Component {
         button.onclick = async () => {
             if (pre.className == "mx_EventTile_collapsedCodeBlock") {
                 pre.className = "";
-                button.className = "mx_EventTile_expandButton";
+                button.className = "mx_EventTile_collapseButton";
             } else {
                 pre.className = "mx_EventTile_collapsedCodeBlock";
-                button.className = "mx_EventTile_collapseButton";
+                button.className = "mx_EventTile_expandButton";
             }
         };
 

From 14bf1696315093e019c68088d1d65c54f0615808 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Wed, 20 Jan 2021 15:01:23 +0100
Subject: [PATCH 12/27] Handle small codeblocks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/views/rooms/_EventTile.scss          | 29 ++++++--------------
 src/components/views/messages/TextualBody.js | 26 +++++++++++++-----
 2 files changed, 27 insertions(+), 28 deletions(-)

diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 3acebd03e9..2d986a6fc6 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -516,41 +516,28 @@ $left-gutter: 64px;
 }
 
 // Inserted adjacent to 
 blocks, (See TextualBody)
-.mx_EventTile_copyButton {
+.mx_EventTile_button {
     position: absolute;
     display: inline-block;
     visibility: hidden;
     cursor: pointer;
-    top: 31px;
+    top: 6px;
     right: 6px;
     width: 19px;
     height: 19px;
-    mask-image: url($copy-button-url);
     background-color: $message-action-bar-fg-color;
 }
+.mx_EventTile_buttonBottom {
+    top: 31px;
+}
+.mx_EventTile_copyButton {
+    mask-image: url($copy-button-url);
+}
 .mx_EventTile_collapseButton {
-    position: absolute;
-    display: inline-block;
-    visibility: hidden;
-    cursor: pointer;
-    top: 6px;
-    right: 6px;
-    width: 19px;
-    height: 19px;
     mask-image: url($collapse-button-url);
-    background-color: $message-action-bar-fg-color;
 }
 .mx_EventTile_expandButton {
-    position: absolute;
-    display: inline-block;
-    visibility: hidden;
-    cursor: pointer;
-    top: 6px;
-    right: 6px;
-    width: 19px;
-    height: 19px;
     mask-image: url($expand-button-url);
-    background-color: $message-action-bar-fg-color;
 }
 
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 511ea61904..da6aaa2830 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -99,8 +99,8 @@ export default class TextualBody extends React.Component {
                      * when the 
 overflows and is scrolled horizontally. */
                     const div = this._wrapInDiv(pres[i]);
                     this._handleCodeBlockExpansion(pres[i]);
-                    this._addCodeCopyButton(div);
                     this._addCodeExpansionButton(div, pres[i]);
+                    this._addCodeCopyButton(div);
                 }
             }
             // Highlight code
@@ -121,22 +121,28 @@ export default class TextualBody extends React.Component {
     }
 
     _addCodeExpansionButton(div, pre) {
-        // TODO: What if the block is small and the we don't need the icon?
+        /* Calculate how many percent does the pre element take up.
+         * If it's less than 30% we don't add the expansion button. */
+        const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100;
+        console.log("expansionButtonExists", percentageOfViewport);
+        if (percentageOfViewport < 30) return;
 
         const button = document.createElement("span");
+        button.className = "mx_EventTile_button ";
         if (pre.className == "mx_EventTile_collapsedCodeBlock") {
-            button.className = "mx_EventTile_expandButton";
+            button.className += "mx_EventTile_expandButton";
         } else {
-            button.className = "mx_EventTile_collapseButton";
+            button.className += "mx_EventTile_collapseButton";
         }
 
         button.onclick = async () => {
+            button.className = "mx_EventTile_button ";
             if (pre.className == "mx_EventTile_collapsedCodeBlock") {
                 pre.className = "";
-                button.className = "mx_EventTile_collapseButton";
+                button.className += "mx_EventTile_collapseButton";
             } else {
                 pre.className = "mx_EventTile_collapsedCodeBlock";
-                button.className = "mx_EventTile_expandButton";
+                button.className += "mx_EventTile_expandButton";
             }
         };
 
@@ -145,7 +151,13 @@ export default class TextualBody extends React.Component {
 
     _addCodeCopyButton(div) {
         const button = document.createElement("span");
-        button.className = "mx_EventTile_copyButton";
+        button.className = "mx_EventTile_button mx_EventTile_copyButton ";
+
+        /* Check if expansion button exists. If so
+         * we put the copy button to the bottom */
+        const expansionButtonExists = div.getElementsByClassName("mx_EventTile_button");
+        if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
+
         button.onclick = async () => {
             const copyCode = button.parentNode.getElementsByTagName("pre")[0];
             const successful = await copyPlaintext(copyCode.textContent);

From bd2423a52c9d7712179b2a519af42de80bfe99df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 21 Jan 2021 10:53:18 +0100
Subject: [PATCH 13/27] Added line numbering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/views/rooms/_EventTile.scss          | 12 ++++++++++++
 src/components/views/messages/TextualBody.js | 17 ++++++++++++++++-
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 2d986a6fc6..826604c081 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -501,6 +501,18 @@ $left-gutter: 64px;
     }
 }
 
+.mx_EventTile_lineNumbers {
+    float: left;
+    margin: 0 0.5em 0 -1.5em;
+    color: gray;
+}
+
+.mx_EventTile_lineNumber {
+    text-align: right;
+    display: block;
+    padding-left: 1em;
+}
+
 .mx_EventTile_collapsedCodeBlock {
     max-height: 30vh;
 }
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index da6aaa2830..18f1302a4a 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -101,6 +101,8 @@ export default class TextualBody extends React.Component {
                     this._handleCodeBlockExpansion(pres[i]);
                     this._addCodeExpansionButton(div, pres[i]);
                     this._addCodeCopyButton(div);
+                    // TODO: Add option to disable this
+                    this._addLineNumbers(pres[i]);
                 }
             }
             // Highlight code
@@ -159,7 +161,7 @@ export default class TextualBody extends React.Component {
         if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
 
         button.onclick = async () => {
-            const copyCode = button.parentNode.getElementsByTagName("pre")[0];
+            const copyCode = button.parentNode.getElementsByTagName("code")[0];
             const successful = await copyPlaintext(copyCode.textContent);
 
             const buttonRect = button.getBoundingClientRect();
@@ -192,6 +194,19 @@ export default class TextualBody extends React.Component {
         }
     }
 
+    _addLineNumbers(pre) {
+        //const lineNumbers = document.createElement("span");
+        //lineNumbers.className = "mx_EventTile_lineNumbers";
+        pre.innerHTML = '' + pre.innerHTML + '';
+        const lineNumbers = pre.getElementsByClassName("mx_EventTile_lineNumbers")[0];
+        // Calculate number of lines in pre
+        const number = pre.innerHTML.split(/\n/).length;
+        // Iterate through lines starting with 1 (number of the first line is 1)
+        for (let i = 1; i < number; i++) {
+            lineNumbers.innerHTML += '' + i + '';
+        }
+    }
+
     _highlightCode(code) {
         if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
             highlight.highlightBlock(code);

From c7375431146a17697ba13bbdacdbd9ca40104572 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 21 Jan 2021 13:04:51 +0100
Subject: [PATCH 14/27] Remove commented lines
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 18f1302a4a..e0931df097 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -195,9 +195,7 @@ export default class TextualBody extends React.Component {
     }
 
     _addLineNumbers(pre) {
-        //const lineNumbers = document.createElement("span");
-        //lineNumbers.className = "mx_EventTile_lineNumbers";
-        pre.innerHTML = '' + pre.innerHTML + '';
+        pre.innerHTML = '' + pre.innerHTML + '';
         const lineNumbers = pre.getElementsByClassName("mx_EventTile_lineNumbers")[0];
         // Calculate number of lines in pre
         const number = pre.innerHTML.split(/\n/).length;

From da09362f99fc477220b42f5921c96075b065c570 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 21 Jan 2021 13:08:55 +0100
Subject: [PATCH 15/27] Added option to hide line numbers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js                | 6 ++++--
 .../views/settings/tabs/user/PreferencesUserSettingsTab.js  | 1 +
 src/settings/Settings.ts                                    | 5 +++++
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index e0931df097..a019b1a47b 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -81,6 +81,7 @@ export default class TextualBody extends React.Component {
     }
 
     _applyFormatting() {
+        const showLineNumbers = SettingsStore.getValue("showCodeLineNumbers");
         this.activateSpoilers([this._content.current]);
 
         // pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer
@@ -101,8 +102,9 @@ export default class TextualBody extends React.Component {
                     this._handleCodeBlockExpansion(pres[i]);
                     this._addCodeExpansionButton(div, pres[i]);
                     this._addCodeCopyButton(div);
-                    // TODO: Add option to disable this
-                    this._addLineNumbers(pres[i]);
+                    if (showLineNumbers) {
+                        this._addLineNumbers(pres[i]);
+                    }
                 }
             }
             // Highlight code
diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
index 636c5cb7c8..e9abe4dbd3 100644
--- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
@@ -47,6 +47,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
         'showRedactions',
         'enableSyntaxHighlightLanguageDetection',
         'expandCodeByDefault',
+        'showCodeLineNumbers',
         'showJoinLeaves',
         'showAvatarChanges',
         'showDisplaynameChanges',
diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts
index f6af68bedd..b3601213bf 100644
--- a/src/settings/Settings.ts
+++ b/src/settings/Settings.ts
@@ -305,6 +305,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
         displayName: _td('Expand code blocks by default'),
         default: false,
     },
+    "showCodeLineNumbers": {
+        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
+        displayName: _td('Show line numbers in code blocks'),
+        default: true,
+    },
     "Pill.shouldShowPillAvatar": {
         supportedLevels: LEVELS_ACCOUNT_SETTINGS,
         displayName: _td('Show avatars in user and room mentions'),

From 97c5058f009c6981474189fd07395df0da1140ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 21 Jan 2021 13:10:21 +0100
Subject: [PATCH 16/27] Removed log
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index a019b1a47b..a9f73404e9 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -128,7 +128,6 @@ export default class TextualBody extends React.Component {
         /* Calculate how many percent does the pre element take up.
          * If it's less than 30% we don't add the expansion button. */
         const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100;
-        console.log("expansionButtonExists", percentageOfViewport);
         if (percentageOfViewport < 30) return;
 
         const button = document.createElement("span");

From c7e61ac71d090be8f4880af02edc461447026953 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 21 Jan 2021 17:32:57 +0100
Subject: [PATCH 17/27] Rerender MessagePanel
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/structures/MessagePanel.js    | 11 +++++++++++
 src/components/views/messages/TextualBody.js |  4 ++++
 2 files changed, 15 insertions(+)

diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index 375545f819..4298e75b32 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -32,6 +32,7 @@ import {textForEvent} from "../../TextForEvent";
 import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
 import DMRoomMap from "../../utils/DMRoomMap";
 import NewRoomIntro from "../views/rooms/NewRoomIntro";
+import dis from "../../dispatcher/dispatcher";
 
 const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
 const continuedTypes = ['m.sticker', 'm.room.message'];
@@ -203,6 +204,16 @@ export default class MessagePanel extends React.Component {
 
         this._showTypingNotificationsWatcherRef =
             SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
+
+        dis.register(this.onAction);
+    }
+
+    onAction = payload => {
+        switch (payload.action) {
+            case "rerender_MessagePanel":
+                this.forceUpdate();
+                break;
+        }
     }
 
     componentDidMount() {
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index a9f73404e9..45df78d480 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -147,6 +147,10 @@ export default class TextualBody extends React.Component {
                 pre.className = "mx_EventTile_collapsedCodeBlock";
                 button.className += "mx_EventTile_expandButton";
             }
+            /* Now we need to rerender the MessagePanel because the
+             * content's size has changed. Otherwise scrolling could
+             * get broken */
+            dis.dispatch({action: "rerender_MessagePanel"});
         };
 
         div.appendChild(button);

From 828d401c6c953f17b4c941871041c06f03b708fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 21 Jan 2021 17:37:22 +0100
Subject: [PATCH 18/27] i18n
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/i18n/strings/en_EN.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 274bd247e2..05faf26a8d 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -794,6 +794,8 @@
     "Always show message timestamps": "Always show message timestamps",
     "Autoplay GIFs and videos": "Autoplay GIFs and videos",
     "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
+    "Expand code blocks by default": "Expand code blocks by default",
+    "Show line numbers in code blocks": "Show line numbers in code blocks",
     "Show avatars in user and room mentions": "Show avatars in user and room mentions",
     "Enable big emoji in chat": "Enable big emoji in chat",
     "Send typing notifications": "Send typing notifications",

From 5ca9e8d32394c0d0838d285d6a18d9cbb284c3b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 21 Jan 2021 18:18:07 +0100
Subject: [PATCH 19/27] Remove spaces
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/views/rooms/_EventTile.scss | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 826604c081..187e207d15 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -553,9 +553,9 @@ $left-gutter: 64px;
 }
 
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
-.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton, 
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton,
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_collapseButton,
-.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_collapseButton, 
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_collapseButton,
 .mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_expandButton,
 .mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_expandButton {
     visibility: visible;

From 9db86a38f628d270b7d26c2ab18616d88849fe39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Thu, 4 Feb 2021 17:58:21 +0100
Subject: [PATCH 20/27] Update src/components/views/messages/TextualBody.js

Co-authored-by: J. Ryan Stinnett 
---
 src/components/views/messages/TextualBody.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 45df78d480..30fbc08fd3 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -109,7 +109,7 @@ export default class TextualBody extends React.Component {
             }
             // Highlight code
             const codes = ReactDOM.findDOMNode(this).getElementsByTagName("code");
-            if (codes.length >0) {
+            if (codes.length > 0) {
                 for (let i = 0; i < codes.length; i++) {
                     /* Do this asynchronously: parsing code takes time and we don't
                      * need to block the DOM update on it. */

From 9bd8fef6b3603c3ca9fd301fd66266a551ba41d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Fri, 5 Feb 2021 08:22:02 +0100
Subject: [PATCH 21/27] Change comment styling

Co-authored-by: J. Ryan Stinnett 
---
 src/components/views/messages/TextualBody.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 30fbc08fd3..68e018631a 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -96,8 +96,8 @@ export default class TextualBody extends React.Component {
             const pres = ReactDOM.findDOMNode(this).getElementsByTagName("pre");
             if (pres.length > 0) {
                 for (let i = 0; i < pres.length; i++) {
-                    /* Wrap a div around 
 so that the copy button can be correctly positioned
-                     * when the 
 overflows and is scrolled horizontally. */
+                    // Wrap a div around 
 so that the copy button can be correctly positioned
+                    // when the 
 overflows and is scrolled horizontally.
                     const div = this._wrapInDiv(pres[i]);
                     this._handleCodeBlockExpansion(pres[i]);
                     this._addCodeExpansionButton(div, pres[i]);

From ad5c8fe7e4f8caac9015c48eca0e060f38a7f340 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Fri, 5 Feb 2021 08:27:07 +0100
Subject: [PATCH 22/27] Revert "Rerender MessagePanel"

This reverts commit c7e61ac71d090be8f4880af02edc461447026953.
---
 src/components/structures/MessagePanel.js    | 11 -----------
 src/components/views/messages/TextualBody.js |  4 ----
 2 files changed, 15 deletions(-)

diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index 4298e75b32..375545f819 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -32,7 +32,6 @@ import {textForEvent} from "../../TextForEvent";
 import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
 import DMRoomMap from "../../utils/DMRoomMap";
 import NewRoomIntro from "../views/rooms/NewRoomIntro";
-import dis from "../../dispatcher/dispatcher";
 
 const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
 const continuedTypes = ['m.sticker', 'm.room.message'];
@@ -204,16 +203,6 @@ export default class MessagePanel extends React.Component {
 
         this._showTypingNotificationsWatcherRef =
             SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
-
-        dis.register(this.onAction);
-    }
-
-    onAction = payload => {
-        switch (payload.action) {
-            case "rerender_MessagePanel":
-                this.forceUpdate();
-                break;
-        }
     }
 
     componentDidMount() {
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 68e018631a..a2ed349812 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -147,10 +147,6 @@ export default class TextualBody extends React.Component {
                 pre.className = "mx_EventTile_collapsedCodeBlock";
                 button.className += "mx_EventTile_expandButton";
             }
-            /* Now we need to rerender the MessagePanel because the
-             * content's size has changed. Otherwise scrolling could
-             * get broken */
-            dis.dispatch({action: "rerender_MessagePanel"});
         };
 
         div.appendChild(button);

From 77248725edd68ef2f8cb3b64d313f7eea33ec5ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Fri, 5 Feb 2021 08:29:24 +0100
Subject: [PATCH 23/27] Added onHeightChanged call
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index a2ed349812..bd0d0e3457 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -147,6 +147,10 @@ export default class TextualBody extends React.Component {
                 pre.className = "mx_EventTile_collapsedCodeBlock";
                 button.className += "mx_EventTile_expandButton";
             }
+
+            // By expanding/collapsing we changed
+            // the height, therefore we call this
+            this.props.onHeightChanged();
         };
 
         div.appendChild(button);

From ec73f2ec49f4a49e8bf09126bda5240177c32231 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Fri, 5 Feb 2021 08:30:54 +0100
Subject: [PATCH 24/27] Change comment styling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 src/components/views/messages/TextualBody.js | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index bd0d0e3457..71f7572a25 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -111,8 +111,8 @@ export default class TextualBody extends React.Component {
             const codes = ReactDOM.findDOMNode(this).getElementsByTagName("code");
             if (codes.length > 0) {
                 for (let i = 0; i < codes.length; i++) {
-                    /* Do this asynchronously: parsing code takes time and we don't
-                     * need to block the DOM update on it. */
+                    // Do this asynchronously: parsing code takes time and we don't
+                    // need to block the DOM update on it.
                     setTimeout(() => {
                         if (this._unmounted) return;
                         for (let i = 0; i < pres.length; i++) {
@@ -125,8 +125,8 @@ export default class TextualBody extends React.Component {
     }
 
     _addCodeExpansionButton(div, pre) {
-        /* Calculate how many percent does the pre element take up.
-         * If it's less than 30% we don't add the expansion button. */
+        // Calculate how many percent does the pre element take up.
+        // If it's less than 30% we don't add the expansion button.
         const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100;
         if (percentageOfViewport < 30) return;
 
@@ -160,8 +160,8 @@ export default class TextualBody extends React.Component {
         const button = document.createElement("span");
         button.className = "mx_EventTile_button mx_EventTile_copyButton ";
 
-        /* Check if expansion button exists. If so
-         * we put the copy button to the bottom */
+        // Check if expansion button exists. If so
+        // we put the copy button to the bottom
         const expansionButtonExists = div.getElementsByClassName("mx_EventTile_button");
         if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
 

From cdb0d1c12eae29a172aae0a5ab6835cdd354ebd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Mon, 8 Feb 2021 15:22:10 +0100
Subject: [PATCH 25/27] Move and use icons from widget dir
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/structures/_LeftPanelWidget.scss             | 2 +-
 res/css/views/messages/_ViewSourceEvent.scss         | 4 ++--
 res/img/feather-customised/{widget => }/maximise.svg | 0
 res/img/feather-customised/{widget => }/minimise.svg | 0
 res/themes/legacy-light/css/_legacy-light.scss       | 4 ++--
 res/themes/light/css/_light.scss                     | 4 ++--
 6 files changed, 7 insertions(+), 7 deletions(-)
 rename res/img/feather-customised/{widget => }/maximise.svg (100%)
 rename res/img/feather-customised/{widget => }/minimise.svg (100%)

diff --git a/res/css/structures/_LeftPanelWidget.scss b/res/css/structures/_LeftPanelWidget.scss
index 4df651d7b6..6e2d99bb37 100644
--- a/res/css/structures/_LeftPanelWidget.scss
+++ b/res/css/structures/_LeftPanelWidget.scss
@@ -134,7 +134,7 @@ limitations under the License.
             mask-position: center;
             mask-size: contain;
             mask-repeat: no-repeat;
-            mask-image: url('$(res)/img/feather-customised/widget/maximise.svg');
+            mask-image: url('$(res)/img/feather-customised/maximise.svg');
             background: $muted-fg-color;
         }
     }
diff --git a/res/css/views/messages/_ViewSourceEvent.scss b/res/css/views/messages/_ViewSourceEvent.scss
index 076932ee97..66825030e0 100644
--- a/res/css/views/messages/_ViewSourceEvent.scss
+++ b/res/css/views/messages/_ViewSourceEvent.scss
@@ -35,13 +35,13 @@ limitations under the License.
         mask-size: auto 12px;
         visibility: hidden;
         background-color: $accent-color;
-        mask-image: url('$(res)/img/feather-customised/widget/maximise.svg');
+        mask-image: url('$(res)/img/feather-customised/maximise.svg');
     }
 
     &.mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle {
         mask-position: 0 bottom;
         margin-bottom: 7px;
-        mask-image: url('$(res)/img/feather-customised/widget/minimise.svg');
+        mask-image: url('$(res)/img/feather-customised/minimise.svg');
     }
 
     &:hover .mx_ViewSourceEvent_toggle {
diff --git a/res/img/feather-customised/widget/maximise.svg b/res/img/feather-customised/maximise.svg
similarity index 100%
rename from res/img/feather-customised/widget/maximise.svg
rename to res/img/feather-customised/maximise.svg
diff --git a/res/img/feather-customised/widget/minimise.svg b/res/img/feather-customised/minimise.svg
similarity index 100%
rename from res/img/feather-customised/widget/minimise.svg
rename to res/img/feather-customised/minimise.svg
diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss
index 6219d5a5bf..a740ba155c 100644
--- a/res/themes/legacy-light/css/_legacy-light.scss
+++ b/res/themes/legacy-light/css/_legacy-light.scss
@@ -237,8 +237,8 @@ $event-redacted-border-color: #cccccc;
 $event-timestamp-color: #acacac;
 
 $copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
-$collapse-button-url: "$(res)/img/feather-customised/minimize.svg";
-$expand-button-url: "$(res)/img/feather-customised/maximize.svg";
+$collapse-button-url: "$(res)/img/feather-customised/minimise.svg";
+$expand-button-url: "$(res)/img/feather-customised/maximise.svg";
 
 // e2e
 $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index 77c0e72726..1c89d83c01 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -237,8 +237,8 @@ $event-redacted-border-color: #cccccc;
 $event-timestamp-color: #acacac;
 
 $copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
-$collapse-button-url: "$(res)/img/feather-customised/minimize.svg";
-$expand-button-url: "$(res)/img/feather-customised/maximize.svg";
+$collapse-button-url: "$(res)/img/feather-customised/minimise.svg";
+$expand-button-url: "$(res)/img/feather-customised/maximise.svg";
 
 // e2e
 $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color

From 9a97b40ce93bd76f0864ca8a4d34ca8ea1e32e92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Mon, 8 Feb 2021 15:22:17 +0100
Subject: [PATCH 26/27] Remove old icons
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/img/feather-customised/maximize.svg | 24 ------------------------
 res/img/feather-customised/minimize.svg | 24 ------------------------
 2 files changed, 48 deletions(-)
 delete mode 100644 res/img/feather-customised/maximize.svg
 delete mode 100644 res/img/feather-customised/minimize.svg

diff --git a/res/img/feather-customised/maximize.svg b/res/img/feather-customised/maximize.svg
deleted file mode 100644
index ad74c8b1eb..0000000000
--- a/res/img/feather-customised/maximize.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
-  
-  
-  
-  
-
diff --git a/res/img/feather-customised/minimize.svg b/res/img/feather-customised/minimize.svg
deleted file mode 100644
index 102af6df95..0000000000
--- a/res/img/feather-customised/minimize.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
-  
-  
-  
-  
-

From 257b8819b36895f5b36966ffc5f2ef8875980692 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= 
Date: Mon, 8 Feb 2021 15:22:30 +0100
Subject: [PATCH 27/27] Change icon size
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner 
---
 res/css/views/rooms/_EventTile.scss | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 187e207d15..5a69fa4b1a 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -546,9 +546,15 @@ $left-gutter: 64px;
     mask-image: url($copy-button-url);
 }
 .mx_EventTile_collapseButton {
+    mask-size: 75%;
+    mask-position: center;
+    mask-repeat: no-repeat;
     mask-image: url($collapse-button-url);
 }
 .mx_EventTile_expandButton {
+    mask-size: 75%;
+    mask-position: center;
+    mask-repeat: no-repeat;
     mask-image: url($expand-button-url);
 }