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