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 toblocks, (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 aroundso that the copy button can be correctly positioned + // when theoverflows and is scrolled horizontally. + const div = document.createElement("div"); + div.className = "mx_EventTile_pre_container"; + + // Insert containing div in place ofblock + codeBlock.parentNode.replaceChild(div, codeBlock); + + // Appendblock 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 aroundso that the copy button can be correctly positioned - // when theoverflows and is scrolled horizontally. - const div = document.createElement("div"); - div.className = "mx_EventTile_pre_container"; - - // Insert containing div in place ofblock - p.parentNode.replaceChild(div, p); - - // Appendblock 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 }