m.text and m.image events

master
f0x 5 years ago
parent 983004927a
commit b4baae8023

@ -18,7 +18,7 @@ const Input = require('./components/input.js')
// incoming/outgoing message alignment (split)
let backend = new Matrix("user", "pass", "http://localhost")
let backend = new Matrix("user", "pass", "https://lain.haus")
backend.sync()
let App = create({
@ -48,7 +48,7 @@ let App = create({
<Sidebar rooms={this.state.rooms}/>
<div className="main">
<Info />
<Chat events={this.state.events}/>
<Chat events={this.state.events} backend={backend}/>
<Input />
</div>
</>

@ -1,15 +1,45 @@
'use strict'
const React = require('react')
const ReactDOM = require('react-dom')
const defaultValue = require('default-value');
const components = require('../components/backends/Matrix.js');
const defaultValue = require('default-value')
class Matrix {
constructor(user, password, homeserver) {
this.user = user;
this.password = password;
this.homeserver = homeserver;
this.a = 0
this.events = {
"roomId": [
{
type: "m.room.message",
sender: "@f0x:lain.haus",
content: {
body: "Image caption",
info: {
size: 1331429,
mimetype: "image/png",
thumbnail_info: {
w: 600,
h: 600,
mimetype: "image/png",
size: 151911
},
w: 2000,
h: 2000,
thumbnail_url: "mxc://lain.haus/PnptnVmLprDNICfhCqIIurHZ"
},
msgtype: "m.image",
url: "mxc://lain.haus/MXtCRwxheuSEVsIyHfyUGJNz"
},
event_id: "$155317808164309EPnWP:lain.haus",
origin_server_ts: 1553178081145,
unsigned: {
age: 587,
transaction_id: "m1553178080798.12"
},
room_id: "!bghqZrxFTiDyEUzunK:disroot.org"
}
]
}
@ -35,6 +65,10 @@ class Matrix {
this.updates = true
}
getHS() {
return this.homeserver
}
getEvents(roomId) {
return this.events["roomId"]
}
@ -55,6 +89,10 @@ class Matrix {
return false
}
addEvent(event) {
this.events["roomId"].push(event)
}
sync() {
let rand = this.lastRand
while(rand == this.lastRand) {
@ -80,7 +118,7 @@ class Matrix {
age: 1234
}
}
this.events["roomId"].push(this.getReactEvent(event))
this.events["roomId"].push(event)
setTimeout(() => {this.sync()}, 2000)
}
@ -93,20 +131,6 @@ class Matrix {
}
return id
}
getReactEvent(event) {
let msgTypes = {
"m.text": components.text
}
if (event.type == "m.room.message") {
let msgtype = event.content.msgtype
return React.createElement(
defaultValue(msgTypes[msgtype], components.text),
{event: event, key: event.event_id}
)
}
}
}
module.exports = Matrix

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

@ -5,8 +5,12 @@ const create = require('create-react-class')
const Promise = require('bluebird')
const debounce = require('debounce')
const jdenticon = require('jdenticon')
const defaultValue = require('default-value')
const Matrix = require('./backends/Matrix.js')
const elements = {
"m.text": require('./events/text.js'),
"m.image": require('./events/image.js')
}
jdenticon.config = {
lightness: {
@ -67,7 +71,7 @@ let chat = create({
messageGroups.groups.push(messageGroups.current)
let events = messageGroups.groups.map((events, id) => {
return <EventGroup events={events} key={id}/>
return <EventGroup key={id} events={events} backend={this.props.backend}/>
})
//TODO: replace with something that only renders events in view
@ -83,11 +87,10 @@ let EventGroup = create({
displayName: "EventGroup",
getInitialState: function() {
console.log(this.props.events);
let color = ["red", "green", "yellow", "blue", "purple", "cyan"][Math.floor(Math.random()*6)]
return {
color: color,
sender: this.props.events[0].props.event.sender
sender: this.props.events[0].sender
}
},
@ -96,10 +99,9 @@ let EventGroup = create({
},
render: function() {
let events = this.props.events;
//let events = this.props.events.map((event, id) => {
//return event
//})
let events = this.props.events.map((event, id) => {
return getRenderedEvent(event, id, this.props.backend)
})
return <div className="eventGroup">
<svg id="avatar" ref={this.avatarRef} ></svg>
<div className="col">
@ -110,4 +112,11 @@ let EventGroup = create({
}
})
function getRenderedEvent(event, id, backend) {
if (event.type == "m.room.message") {
let msgtype = event.content.msgtype;
return React.createElement(elements[defaultValue(msgtype, "m.text")], {event: event, key: id, backend: backend})
}
}
module.exports = chat

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

@ -14,6 +14,7 @@
"@babel/preset-react": "^7.0.0",
"babelify": "^10.0.0",
"bluebird": "^3.5.3",
"blueimp-canvas-to-blob": "^3.14.0",
"browserify": "^16.2.3",
"budo": "^11.5.0",
"create-react-class": "^15.6.3",
@ -36,6 +37,7 @@
"livereactload": "^4.0.0-beta.2",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"sanitize-html": "^1.20.0",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"webpack": "^4.27.1"

@ -4,6 +4,7 @@ dependencies:
'@babel/preset-react': 7.0.0
babelify: 10.0.0
bluebird: 3.5.3
blueimp-canvas-to-blob: 3.14.0
browserify: 16.2.3
budo: 11.5.0
create-react-class: 15.6.3
@ -26,6 +27,7 @@ dependencies:
livereactload: 4.0.0-beta.2
react: 16.6.3
react-dom: 16.6.3
sanitize-html: 1.20.0
vinyl-buffer: 1.0.1
vinyl-source-stream: 2.0.0
webpack: 4.27.1
@ -1540,6 +1542,10 @@ packages:
dev: false
resolution:
integrity: sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
/blueimp-canvas-to-blob/3.14.0:
dev: false
resolution:
integrity: sha512-i6I2CiX1VR8YwUNYBo+dM8tg89ns4TTHxSpWjaDeHKcYS3yFalpLCwDaY21/EsJMufLy2tnG4j0JN5L8OVNkKQ==
/bn.js/4.11.8:
dev: false
resolution:
@ -2948,14 +2954,13 @@ packages:
node: '>=4'
resolution:
integrity: sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==
/dom-serializer/0.1.0:
/dom-serializer/0.1.1:
dependencies:
domelementtype: 1.1.3
domelementtype: 1.3.1
entities: 1.1.2
dev: false
optional: true
resolution:
integrity: sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=
integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
/domain-browser/1.1.7:
dev: false
engines:
@ -2970,22 +2975,21 @@ packages:
npm: '>=1.2'
resolution:
integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
/domelementtype/1.1.3:
dev: false
optional: true
resolution:
integrity: sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=
/domelementtype/1.3.1:
dev: false
optional: true
resolution:
integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
/domhandler/2.4.2:
dependencies:
domelementtype: 1.3.1
dev: false
resolution:
integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
/domutils/1.7.0:
dependencies:
dom-serializer: 0.1.0
dom-serializer: 0.1.1
domelementtype: 1.3.1
dev: false
optional: true
resolution:
integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
/download/6.2.5:
@ -3127,7 +3131,6 @@ packages:
integrity: sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
/entities/1.1.2:
dev: false
optional: true
resolution:
integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
/errno/0.1.7:
@ -4634,6 +4637,17 @@ packages:
node: '>=0.10'
resolution:
integrity: sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=
/htmlparser2/3.10.1:
dependencies:
domelementtype: 1.3.1
domhandler: 2.4.2
domutils: 1.7.0
entities: 1.1.2
inherits: 2.0.3
readable-stream: 3.2.0
dev: false
resolution:
integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
/http-browserify/1.3.2:
dependencies:
Base64: 0.2.1
@ -5700,6 +5714,10 @@ packages:
dev: false
resolution:
integrity: sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=
/lodash.escaperegexp/4.1.2:
dev: false
resolution:
integrity: sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
/lodash.isarguments/3.1.0:
dev: false
resolution:
@ -5714,6 +5732,14 @@ packages:
dev: false
resolution:
integrity: sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=
/lodash.isplainobject/4.0.6:
dev: false
resolution:
integrity: sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
/lodash.isstring/4.0.1:
dev: false
resolution:
integrity: sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
/lodash.keys/2.4.1:
dependencies:
lodash._isnative: 2.4.1
@ -7100,6 +7126,16 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
/postcss/7.0.14:
dependencies:
chalk: 2.4.2
source-map: 0.6.1
supports-color: 6.1.0
dev: false
engines:
node: '>=6.0.0'
resolution:
integrity: sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==
/prepend-http/1.0.4:
dev: false
engines:
@ -7437,6 +7473,16 @@ packages:
node: '>= 6'
resolution:
integrity: sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==
/readable-stream/3.2.0:
dependencies:
inherits: 2.0.3
string_decoder: 1.2.0
util-deprecate: 1.0.2
dev: false
engines:
node: '>= 6'
resolution:
integrity: sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==
/readdirp/2.2.1:
dependencies:
graceful-fs: 4.1.15
@ -7749,6 +7795,21 @@ packages:
dev: false
resolution:
integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
/sanitize-html/1.20.0:
dependencies:
chalk: 2.4.2
htmlparser2: 3.10.1
lodash.clonedeep: 4.5.0
lodash.escaperegexp: 4.1.2
lodash.isplainobject: 4.0.6
lodash.isstring: 4.0.1
lodash.mergewith: 4.6.1
postcss: 7.0.14
srcset: 1.0.0
xtend: 4.0.1
dev: false
resolution:
integrity: sha512-BpxXkBoAG+uKCHjoXFmox6kCSYpnulABoGcZ/R3QyY9ndXbIM5S94eOr1IqnzTG8TnbmXaxWoDDzKC5eJv7fEQ==
/sass-graph/2.2.4:
dependencies:
glob: 7.1.3
@ -8158,6 +8219,15 @@ packages:
optional: true
resolution:
integrity: sha1-MwRQN7ZDiLVnZ0uEMiplIQc5FsM=
/srcset/1.0.0:
dependencies:
array-uniq: 1.0.3
number-is-nan: 1.0.1
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-pWad4StC87HV6D7QPHEEb8SPQe8=
/sshpk/1.16.0:
dependencies:
asn1: 0.2.4
@ -8448,6 +8518,14 @@ packages:
node: '>=4'
resolution:
integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
/supports-color/6.1.0:
dependencies:
has-flag: 3.0.0
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
/sver-compat/1.5.0:
dependencies:
es6-iterator: 2.0.3
@ -9446,6 +9524,7 @@ specifiers:
'@babel/preset-react': ^7.0.0
babelify: ^10.0.0
bluebird: ^3.5.3
blueimp-canvas-to-blob: ^3.14.0
browserify: ^16.2.3
budo: ^11.5.0
create-react-class: ^15.6.3
@ -9469,6 +9548,7 @@ specifiers:
livereactload: ^4.0.0-beta.2
react: ^16.6.3
react-dom: ^16.6.3
sanitize-html: ^1.20.0
vinyl-buffer: ^1.0.1
vinyl-source-stream: ^2.0.0
webpack: ^4.27.1

Loading…
Cancel
Save