m.text and m.image events
parent
983004927a
commit
b4baae8023
@ -0,0 +1,27 @@
|
|||||||
|
'use strict'
|
||||||
|
const React = require('react')
|
||||||
|
const ReactDOM = require('react-dom')
|
||||||
|
const create = require('create-react-class')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
let Event = create({
|
||||||
|
displayName: "Event",
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
let eventBody = this.props.event.content.split("\n").map((line, id) => {
|
||||||
|
if (line.startsWith("image")) {
|
||||||
|
return <img key={id} src="neo.png"/>
|
||||||
|
}
|
||||||
|
return <span key={id}>{line}<br/></span>
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div className="event">
|
||||||
|
<div className="body">
|
||||||
|
{eventBody}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Event;
|
@ -0,0 +1,38 @@
|
|||||||
|
'use strict'
|
||||||
|
const React = require('react')
|
||||||
|
const ReactDOM = require('react-dom')
|
||||||
|
const create = require('create-react-class')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
const Text = require('./text.js')
|
||||||
|
|
||||||
|
let Event = create({
|
||||||
|
displayName: "m.image",
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
let hs = this.props.backend.getHS()
|
||||||
|
let event = this.props.event;
|
||||||
|
let media_mxc = event.content.url.slice(7);
|
||||||
|
let thumb_mxc = event.content.info.thumbnail_url.slice(6);
|
||||||
|
let base = `${hs}/_matrix/media/v1/download`
|
||||||
|
return {
|
||||||
|
url: {
|
||||||
|
media: `${base}/${media_mxc}`,
|
||||||
|
thumb: `${base}/${thumb_mxc}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return <div className="event">
|
||||||
|
<div className="body">
|
||||||
|
<a href={this.state.url.media} target="_blank">
|
||||||
|
<img src={this.state.url.thumb}/>
|
||||||
|
</a>
|
||||||
|
<Text event={this.props.event} nested={true}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = Event;
|
@ -0,0 +1,30 @@
|
|||||||
|
'use strict'
|
||||||
|
const React = require('react')
|
||||||
|
const ReactDOM = require('react-dom')
|
||||||
|
const create = require('create-react-class')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
const riot = require('../../lib/riot-utils.js')
|
||||||
|
|
||||||
|
let Event = create({
|
||||||
|
displayName: "m.text",
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
let event = this.props.event;
|
||||||
|
// let eventBody = event.content.body.split("\n").map((line, id) => {
|
||||||
|
// return <span key={id}>{line}<br/></span>
|
||||||
|
// })
|
||||||
|
|
||||||
|
let eventBody = riot.sanitize(event.content.body)
|
||||||
|
|
||||||
|
return <div className="event">
|
||||||
|
<div
|
||||||
|
className={this.props.nested ? "nested" : "body"}
|
||||||
|
dangerouslySetInnerHTML={{__html: eventBody}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Event;
|
@ -0,0 +1,227 @@
|
|||||||
|
'use strict';
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
const sanitize = require('sanitize-html');
|
||||||
|
require("blueimp-canvas-to-blob");
|
||||||
|
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a thumbnail for a image DOM element.
|
||||||
|
* The image will be smaller than MAX_WIDTH and MAX_HEIGHT.
|
||||||
|
* The thumbnail will have the same aspect ratio as the original.
|
||||||
|
* Draws the element into a canvas using CanvasRenderingContext2D.drawImage
|
||||||
|
* Then calls Canvas.toBlob to get a blob object for the image data.
|
||||||
|
*
|
||||||
|
* Since it needs to calculate the dimensions of the source image and the
|
||||||
|
* thumbnailed image it returns an info object filled out with information
|
||||||
|
* about the original image and the thumbnail.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} element The element to thumbnail.
|
||||||
|
* @param {integer} inputWidth The width of the image in the input element.
|
||||||
|
* @param {integer} inputHeight the width of the image in the input element.
|
||||||
|
* @param {String} mimeType The mimeType to save the blob as.
|
||||||
|
* @return {Promise} A promise that resolves with an object with an info key
|
||||||
|
* and a thumbnail key.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createThumbnail: function(element, inputWidth, inputHeight, mimeType) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
const MAX_WIDTH = 800;
|
||||||
|
const MAX_HEIGHT = 600;
|
||||||
|
|
||||||
|
let targetWidth = inputWidth;
|
||||||
|
let targetHeight = inputHeight;
|
||||||
|
if (targetHeight > MAX_HEIGHT) {
|
||||||
|
targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
|
||||||
|
targetHeight = MAX_HEIGHT;
|
||||||
|
}
|
||||||
|
if (targetWidth > MAX_WIDTH) {
|
||||||
|
targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
|
||||||
|
targetWidth = MAX_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = targetWidth;
|
||||||
|
canvas.height = targetHeight;
|
||||||
|
canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
|
||||||
|
canvas.toBlob(function(thumbnail) {
|
||||||
|
resolve({
|
||||||
|
info: {
|
||||||
|
thumbnail_info: {
|
||||||
|
w: targetWidth,
|
||||||
|
h: targetHeight,
|
||||||
|
mimetype: thumbnail.type,
|
||||||
|
size: thumbnail.size,
|
||||||
|
},
|
||||||
|
w: inputWidth,
|
||||||
|
h: inputHeight,
|
||||||
|
},
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
});
|
||||||
|
}, mimeType);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a file into a newly created image element.
|
||||||
|
*
|
||||||
|
* @param {File} file The file to load in an image element.
|
||||||
|
* @return {Promise} A promise that resolves with the html image element.
|
||||||
|
*/
|
||||||
|
loadImageElement: function(imageFile) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
// Load the file into an html element
|
||||||
|
const img = document.createElement("img");
|
||||||
|
const objectUrl = URL.createObjectURL(imageFile);
|
||||||
|
img.src = objectUrl;
|
||||||
|
|
||||||
|
// Once ready, create a thumbnail
|
||||||
|
img.onload = function() {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
img.onerror = function(e) {
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a file into a newly created video element.
|
||||||
|
*
|
||||||
|
* @param {File} file The file to load in an video element.
|
||||||
|
* @return {Promise} A promise that resolves with the video image element.
|
||||||
|
*/
|
||||||
|
loadVideoElement: function(videoFile) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
// Load the file into an html element
|
||||||
|
const video = document.createElement("video");
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
video.src = e.target.result;
|
||||||
|
|
||||||
|
// Once ready, returns its size
|
||||||
|
// Wait until we have enough data to thumbnail the first frame.
|
||||||
|
video.onloadeddata = function() {
|
||||||
|
resolve(video);
|
||||||
|
};
|
||||||
|
video.onerror = function(e) {
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
reader.onerror = function(e) {
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(videoFile);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sanitize: function(html) {
|
||||||
|
return sanitize(html, this.sanitizeHtmlParams);
|
||||||
|
},
|
||||||
|
|
||||||
|
sanitizeHtmlParams: {
|
||||||
|
allowedTags: [
|
||||||
|
'font', // custom to matrix for IRC-style font coloring
|
||||||
|
'del', // for markdown
|
||||||
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'a', 'ul', 'ol', 'sup', 'sub',
|
||||||
|
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||||
|
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
|
||||||
|
'mx-reply', 'mx-rainbow'
|
||||||
|
],
|
||||||
|
allowedAttributes: {
|
||||||
|
// custom ones first:
|
||||||
|
font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
||||||
|
span: ['data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
||||||
|
a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix
|
||||||
|
img: ['src', 'width', 'height', 'alt', 'title'],
|
||||||
|
ol: ['start'],
|
||||||
|
code: ['class'], // We don't actually allow all classes, we filter them in transformTags
|
||||||
|
},
|
||||||
|
// Lots of these won't come up by default because we don't allow them
|
||||||
|
selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
|
||||||
|
// URL schemes we permit
|
||||||
|
allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'magnet'],
|
||||||
|
|
||||||
|
allowProtocolRelative: false,
|
||||||
|
|
||||||
|
transformTags: { // custom to matrix
|
||||||
|
// add blank targets to all hyperlinks except vector URLs
|
||||||
|
'img': function(tagName, attribs) {
|
||||||
|
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
|
||||||
|
// because transformTags is used _before_ we filter by allowedSchemesByTag and
|
||||||
|
// we don't want to allow images with `https?` `src`s.
|
||||||
|
//if (!attribs.src || !attribs.src.startsWith('mxc://')) {
|
||||||
|
return { tagName, attribs: {}};
|
||||||
|
//}
|
||||||
|
//attribs.src = MatrixClientPeg.get().mxcUrlToHttp(
|
||||||
|
// attribs.src,
|
||||||
|
// attribs.width || 800,
|
||||||
|
// attribs.height || 600
|
||||||
|
//);
|
||||||
|
//return { tagName: tagName, attribs: attribs };
|
||||||
|
},
|
||||||
|
|
||||||
|
'code': function(tagName, attribs) {
|
||||||
|
if (typeof attribs.class !== 'undefined') {
|
||||||
|
// Filter out all classes other than ones starting with language- for syntax highlighting.
|
||||||
|
const classes = attribs.class.split(/\s+/).filter(function(cl) {
|
||||||
|
return cl.startsWith('language-');
|
||||||
|
});
|
||||||
|
attribs.class = classes.join(' ');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tagName: tagName,
|
||||||
|
attribs: attribs,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
'*': function(tagName, attribs) {
|
||||||
|
// Delete any style previously assigned, style is an allowedTag for font and span
|
||||||
|
// because attributes are stripped after transforming
|
||||||
|
delete attribs.style;
|
||||||
|
|
||||||
|
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
|
||||||
|
// equivalents
|
||||||
|
const customCSSMapper = {
|
||||||
|
'data-mx-color': 'color',
|
||||||
|
'data-mx-bg-color': 'background-color',
|
||||||
|
// $customAttributeKey: $cssAttributeKey
|
||||||
|
};
|
||||||
|
|
||||||
|
let style = "";
|
||||||
|
Object.keys(customCSSMapper).forEach((customAttributeKey) => {
|
||||||
|
const cssAttributeKey = customCSSMapper[customAttributeKey];
|
||||||
|
const customAttributeValue = attribs[customAttributeKey];
|
||||||
|
if (customAttributeValue &&
|
||||||
|
typeof customAttributeValue === 'string' &&
|
||||||
|
COLOR_REGEX.test(customAttributeValue)
|
||||||
|
) {
|
||||||
|
style += cssAttributeKey + ":" + customAttributeValue + ";";
|
||||||
|
delete attribs[customAttributeKey];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (style) {
|
||||||
|
attribs.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { tagName: tagName, attribs: attribs };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue