Merge branch 'develop' into matthew/retina

This commit is contained in:
Bruno Windels 2019-04-05 13:35:54 +02:00
commit f1b00dff35
622 changed files with 40536 additions and 16475 deletions

View file

@ -81,7 +81,7 @@ export default class MAudioBody extends React.Component {
if (this.state.error !== null) {
return (
<span className="mx_MAudioBody" ref="body">
<img src="img/warning.svg" width="16" height="16" />
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error decrypting audio") }
</span>
);
@ -94,7 +94,7 @@ export default class MAudioBody extends React.Component {
// Not sure how tall the audio player is so not sure how tall it should actually be.
return (
<span className="mx_MAudioBody">
<img src="img/spinner.gif" alt={content.body} width="16" height="16" />
<img src={require("../../../../res/img/spinner.gif")} alt={content.body} width="16" height="16" />
</span>
);
}

View file

@ -29,15 +29,15 @@ import request from 'browser-request';
import Modal from '../../../Modal';
// A cached tinted copy of "img/download.svg"
// A cached tinted copy of require("../../../../res/img/download.svg")
let tintedDownloadImageURL;
// Track a list of mounted MFileBody instances so that we can update
// the "img/download.svg" when the tint changes.
// the require("../../../../res/img/download.svg") when the tint changes.
let nextMountId = 0;
const mounts = {};
/**
* Updates the tinted copy of "img/download.svg" when the tint changes.
* Updates the tinted copy of require("../../../../res/img/download.svg") when the tint changes.
*/
function updateTintedDownloadImage() {
// Download the svg as an XML document.
@ -46,7 +46,7 @@ function updateTintedDownloadImage() {
// Also note that we can't use fetch here because fetch doesn't support
// file URLs, which the download image will be if we're running from
// the filesystem (like in an Electron wrapper).
request({uri: "img/download.svg"}, (err, response, body) => {
request({uri: require("../../../../res/img/download.svg")}, (err, response, body) => {
if (err) return;
const svg = new DOMParser().parseFromString(body, "image/svg+xml");
@ -203,6 +203,17 @@ module.exports = React.createClass({
};
},
propTypes: {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
/* already decrypted blob */
decryptedBlob: PropTypes.object,
/* called when the download link iframe is shown */
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
},
contextTypes: {
appConfig: PropTypes.object,
},
@ -248,13 +259,19 @@ module.exports = React.createClass({
this.tint();
},
componentDidUpdate: function(prevProps, prevState) {
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
this.props.onHeightChanged();
}
},
componentWillUnmount: function() {
// Remove this from the list of mounted components
delete mounts[this.id];
},
tint: function() {
// Update our tinted copy of "img/download.svg"
// Update our tinted copy of require("../../../../res/img/download.svg")
if (this.refs.downloadImage) {
this.refs.downloadImage.src = tintedDownloadImageURL;
}
@ -277,6 +294,8 @@ module.exports = React.createClass({
const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
const contentUrl = this._getContentUrl();
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const fileSize = content.info ? content.info.size : null;
const fileType = content.info ? content.info.mimetype : "application/octet-stream";
if (isEncrypted) {
if (this.state.decryptedBlob === null) {
@ -355,6 +374,50 @@ module.exports = React.createClass({
</span>
);
} else if (contentUrl) {
const downloadProps = {
target: "_blank",
rel: "noopener",
// We set the href regardless of whether or not we intercept the download
// because we don't really want to convert the file to a blob eagerly, and
// still want "open in new tab" and "save link as" to work.
href: contentUrl,
};
// Blobs can only have up to 500mb, so if the file reports as being too large then
// we won't try and convert it. Likewise, if the file size is unknown then we'll assume
// it is too big. There is the risk of the reported file size and the actual file size
// being different, however the user shouldn't normally run into this problem.
const fileTooBig = typeof(fileSize) === 'number' ? fileSize > 524288000 : true;
if (["application/pdf"].includes(fileType) && !fileTooBig) {
// We want to force a download on this type, so use an onClick handler.
downloadProps["onClick"] = (e) => {
console.log(`Downloading ${fileType} as blob (unencrypted)`);
// Avoid letting the <a> do its thing
e.preventDefault();
e.stopPropagation();
// Start a fetch for the download
// Based upon https://stackoverflow.com/a/49500465
fetch(contentUrl).then((response) => response.blob()).then((blob) => {
const blobUrl = URL.createObjectURL(blob);
// We have to create an anchor to download the file
const tempAnchor = document.createElement('a');
tempAnchor.download = fileName;
tempAnchor.href = blobUrl;
document.body.appendChild(tempAnchor); // for firefox: https://stackoverflow.com/a/32226068
tempAnchor.click();
tempAnchor.remove();
});
};
} else {
// Else we are hoping the browser will do the right thing
downloadProps["download"] = fileName;
}
// If the attachment is not encrypted then we check whether we
// are being displayed in the room timeline or in a list of
// files in the right hand side of the screen.
@ -362,7 +425,7 @@ module.exports = React.createClass({
return (
<span className="mx_MFileBody">
<div className="mx_MFileBody_download">
<a className="mx_MFileBody_downloadLink" href={contentUrl} download={fileName} target="_blank">
<a className="mx_MFileBody_downloadLink" {...downloadProps}>
{ fileName }
</a>
<div className="mx_MImageBody_size">
@ -375,7 +438,7 @@ module.exports = React.createClass({
return (
<span className="mx_MFileBody">
<div className="mx_MFileBody_download">
<a href={contentUrl} download={fileName} target="_blank" rel="noopener">
<a {...downloadProps}>
<img src={tintedDownloadImageURL} width="12" height="14" ref="downloadImage" />
{ _t("Download %(text)s", { text: text }) }
</a>

View file

@ -34,7 +34,7 @@ export default class MImageBody extends React.Component {
mxEvent: PropTypes.object.isRequired,
/* called when the image has loaded */
onWidgetLoad: PropTypes.func.isRequired,
onHeightChanged: PropTypes.func.isRequired,
/* the maximum image height to use */
maxImageHeight: PropTypes.number,
@ -144,7 +144,7 @@ export default class MImageBody extends React.Component {
}
onImageLoad() {
this.props.onWidgetLoad();
this.props.onHeightChanged();
let loadedImageDimensions;
@ -343,7 +343,12 @@ export default class MImageBody extends React.Component {
// e2e image hasn't been decrypted yet
if (content.file !== undefined && this.state.decryptedUrl === null) {
placeholder = <img src="img/spinner.gif" alt={content.body} width="32" height="32" />;
placeholder = <img
src={require("../../../../res/img/spinner.gif")}
alt={content.body}
width="32"
height="32"
/>;
} else if (!this.state.imgLoaded) {
// Deliberately, getSpinner is left unimplemented here, MStickerBody overides
placeholder = this.getPlaceholder();
@ -424,7 +429,7 @@ export default class MImageBody extends React.Component {
if (this.state.error !== null) {
return (
<span className="mx_MImageBody" ref="body">
<img src="img/warning.svg" width="16" height="16" />
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error decrypting image") }
</span>
);

View file

@ -35,7 +35,7 @@ export default class MStickerBody extends MImageBody {
// img onLoad hasn't fired yet.
getPlaceholder() {
const TintableSVG = sdk.getComponent('elements.TintableSvg');
return <TintableSVG src="img/icons-show-stickers.svg" width="75" height="75" />;
return <TintableSVG src={require("../../../../res/img/icons-show-stickers.svg")} width="75" height="75" />;
}
// Tooltip to show on mouse over
@ -44,9 +44,9 @@ export default class MStickerBody extends MImageBody {
if (!content || !content.body || !content.info || !content.info.w) return null;
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
const Tooltip = sdk.getComponent('elements.Tooltip');
return <div style={{left: content.info.w + 'px'}} className="mx_MStickerBody_tooltip">
<RoomTooltip label={content.body} />
<Tooltip label={content.body} />
</div>;
}

View file

@ -33,7 +33,7 @@ module.exports = React.createClass({
mxEvent: PropTypes.object.isRequired,
/* called when the video has loaded */
onWidgetLoad: PropTypes.func.isRequired,
onHeightChanged: PropTypes.func.isRequired,
},
getInitialState: function() {
@ -108,7 +108,7 @@ module.exports = React.createClass({
decryptedThumbnailUrl: thumbnailUrl,
decryptedBlob: decryptedBlob,
});
this.props.onWidgetLoad();
this.props.onHeightChanged();
});
}).catch((err) => {
console.warn("Unable to decrypt attachment: ", err);
@ -135,7 +135,7 @@ module.exports = React.createClass({
if (this.state.error !== null) {
return (
<span className="mx_MVideoBody" ref="body">
<img src="img/warning.svg" width="16" height="16" />
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error decrypting video") }
</span>
);
@ -148,7 +148,7 @@ module.exports = React.createClass({
return (
<span className="mx_MVideoBody" ref="body">
<div className="mx_MImageBody_thumbnail mx_MImageBody_thumbnail_spinner" ref="image">
<img src="img/spinner.gif" alt={content.body} width="16" height="16" />
<img src={require("../../../../res/img/spinner.gif")} alt={content.body} width="16" height="16" />
</div>
</span>
);

View file

@ -37,7 +37,7 @@ module.exports = React.createClass({
showUrlPreview: PropTypes.bool,
/* callback called when dynamic content in events are loaded */
onWidgetLoad: PropTypes.func,
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
@ -89,6 +89,6 @@ module.exports = React.createClass({
showUrlPreview={this.props.showUrlPreview}
tileShape={this.props.tileShape}
maxImageHeight={this.props.maxImageHeight}
onWidgetLoad={this.props.onWidgetLoad} />;
onHeightChanged={this.props.onHeightChanged} />;
},
});

View file

@ -18,8 +18,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import { makeEventPermalink } from '../../../matrix-to';
import { RoomPermalinkCreator } from '../../../matrix-to';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
module.exports = React.createClass({
displayName: 'RoomCreate',
@ -47,13 +48,17 @@ module.exports = React.createClass({
if (predecessor === undefined) {
return <div />; // We should never have been instaniated in this case
}
const prevRoom = MatrixClientPeg.get().getRoom(predecessor['room_id']);
const permalinkCreator = new RoomPermalinkCreator(prevRoom);
permalinkCreator.load();
const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']);
return <div className="mx_CreateEvent">
<img className="mx_CreateEvent_image" src="img/room-continuation.svg" />
<div className="mx_CreateEvent_image" />
<div className="mx_CreateEvent_header">
{_t("This room is a continuation of another conversation.")}
</div>
<a className="mx_CreateEvent_link"
href={makeEventPermalink(predecessor['room_id'], predecessor['event_id'])}
href={predecessorPermalink}
onClick={this._onLinkClicked}
>
{_t("Click here to see older messages.")}

View file

@ -97,7 +97,7 @@ export default React.createClass({
render() {
const EmojiText = sdk.getComponent('elements.EmojiText');
const {mxEvent} = this.props;
const colorNumber = hashCode(mxEvent.getSender()) % 8;
const colorNumber = (hashCode(mxEvent.getSender()) % 8) + 1;
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
const {msgtype} = mxEvent.getContent();

View file

@ -22,9 +22,6 @@ import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils';
import * as linkify from 'linkifyjs';
import linkifyElement from 'linkifyjs/element';
import linkifyMatrix from '../../../linkify-matrix';
import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import Modal from '../../../Modal';
@ -38,8 +35,6 @@ import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
import ReplyThread from "../elements/ReplyThread";
import {host as matrixtoHost} from '../../../matrix-to';
linkifyMatrix(linkify);
module.exports = React.createClass({
displayName: 'TextualBody',
@ -57,7 +52,7 @@ module.exports = React.createClass({
showUrlPreview: PropTypes.bool,
/* callback for when our widget has loaded */
onWidgetLoad: PropTypes.func,
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
@ -98,7 +93,7 @@ module.exports = React.createClass({
// are still sent as plaintext URLs. If these are ever pillified in the composer,
// we should be pillify them here by doing the linkifying BEFORE the pillifying.
this.pillifyLinks(this.refs.content.children);
linkifyElement(this.refs.content, linkifyMatrix.options);
HtmlUtils.linkifyElement(this.refs.content);
this.calculateUrlPreview();
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
@ -174,7 +169,7 @@ module.exports = React.createClass({
},
pillifyLinks: function(nodes) {
const shouldShowPillAvatar = !SettingsStore.getValue("Pill.shouldHidePillAvatar");
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
let node = nodes[0];
while (node) {
let pillified = false;
@ -436,7 +431,7 @@ module.exports = React.createClass({
const stripReply = ReplyThread.getParentEventId(mxEvent);
let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
disableBigEmoji: SettingsStore.getValue('TextualBody.disableBigEmoji'),
disableBigEmoji: content.msgtype === "m.emote" || !SettingsStore.getValue('TextualBody.enableBigEmoji'),
// Part of Replies fallback support
stripReplyFallback: stripReply,
});
@ -456,7 +451,7 @@ module.exports = React.createClass({
link={link}
mxEvent={this.props.mxEvent}
onCancelClick={this.onCancelClick}
onWidgetLoad={this.props.onWidgetLoad} />;
onHeightChanged={this.props.onHeightChanged} />;
});
}