Merge pull request #5559 from SimonBrandner/improve-codeblock

Improve displaying of code blocks
This commit is contained in:
J. Ryan Stinnett 2021-02-08 14:44:48 +00:00 committed by GitHub
commit 6d01de3bfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 190 additions and 61 deletions

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -491,7 +491,6 @@ $left-gutter: 64px;
// https://github.com/vector-im/vector-web/issues/754
overflow-x: overlay;
overflow-y: visible;
max-height: 30vh;
}
code {
@ -500,6 +499,22 @@ $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;
}
.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
@ -511,7 +526,7 @@ $left-gutter: 64px;
}
// Inserted adjacent to <pre> blocks, (See TextualBody)
.mx_EventTile_copyButton {
.mx_EventTile_button {
position: absolute;
display: inline-block;
visibility: hidden;
@ -520,12 +535,33 @@ $left-gutter: 64px;
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 {
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);
}
.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;
}

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -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/minimise.svg";
$expand-button-url: "$(res)/img/feather-customised/maximise.svg";
// e2e
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color

View File

@ -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/minimise.svg";
$expand-button-url: "$(res)/img/feather-customised/maximise.svg";
// e2e
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color

View File

@ -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
@ -91,29 +92,136 @@ 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");
if (blocks.length > 0) {
// 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]);
}
}
// 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 <pre> so that the copy button can be correctly positioned
// when the <pre> overflows and is scrolled horizontally.
const div = this._wrapInDiv(pres[i]);
this._handleCodeBlockExpansion(pres[i]);
this._addCodeExpansionButton(div, pres[i]);
this._addCodeCopyButton(div);
if (showLineNumbers) {
this._addLineNumbers(pres[i]);
}
}, 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(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.
const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100;
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";
} else {
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";
} else {
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);
}
_addCodeCopyButton(div) {
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
const expansionButtonExists = div.getElementsByClassName("mx_EventTile_button");
if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
button.onclick = async () => {
const copyCode = button.parentNode.getElementsByTagName("code")[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;
};
div.appendChild(button);
}
_wrapInDiv(pre) {
const div = document.createElement("div");
div.className = "mx_EventTile_pre_container";
// Insert containing div in place of <pre> block
pre.parentNode.replaceChild(div, pre);
// Append <pre> block and copy button to container
div.appendChild(pre);
return div;
}
_handleCodeBlockExpansion(pre) {
if (!SettingsStore.getValue("expandCodeByDefault")) {
pre.className = "mx_EventTile_collapsedCodeBlock";
}
}
_addLineNumbers(pre) {
pre.innerHTML = '<span class="mx_EventTile_lineNumbers"></span>' + pre.innerHTML + '<span></span>';
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 += '<span class="mx_EventTile_lineNumber">' + i + '</span>';
}
}
_highlightCode(code) {
if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
highlight.highlightBlock(code);
} else {
// Only syntax highlight if there's a class starting with language-
const classes = code.className.split(/\s+/).filter(function(cl) {
return cl.startsWith('language-') && !cl.startsWith('language-_');
});
if (classes.length != 0) {
highlight.highlightBlock(code);
}
this._addCodeCopyButton();
}
}
@ -254,38 +362,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 <pre> so that the copy button can be correctly positioned
// when the <pre> overflows and is scrolled horizontally.
const div = document.createElement("div");
div.className = "mx_EventTile_pre_container";
// Insert containing div in place of <pre> block
p.parentNode.replaceChild(div, p);
// Append <pre> 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

View File

@ -47,6 +47,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
'alwaysShowTimestamps',
'showRedactions',
'enableSyntaxHighlightLanguageDetection',
'expandCodeByDefault',
'showCodeLineNumbers',
'showJoinLeaves',
'showAvatarChanges',
'showDisplaynameChanges',

View File

@ -806,6 +806,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",

View File

@ -305,6 +305,16 @@ 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,
},
"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'),