diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js
new file mode 100644
index 0000000000..824f59ab20
--- /dev/null
+++ b/src/HtmlUtils.js
@@ -0,0 +1,108 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var sanitizeHtml = require('sanitize-html');
+var highlight = require('highlight.js');
+
+var sanitizeHtmlParams = {
+ allowedTags: [
+ 'font', // custom to matrix. deliberately no h1/h2 to stop people shouting.
+ 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
+ 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
+ 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre'
+ ],
+ allowedAttributes: {
+ // custom ones first:
+ font: [ 'color' ], // custom to matrix
+ a: [ 'href', 'name', 'target' ], // remote target: custom to matrix
+ // We don't currently allow img itself by default, but this
+ // would make sense if we did
+ img: [ 'src' ],
+ },
+ // 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' ],
+ allowedSchemesByTag: {},
+
+ transformTags: { // custom to matrix
+ // add blank targets to all hyperlinks
+ 'a': sanitizeHtml.simpleTransform('a', { target: '_blank'} )
+ },
+};
+
+module.exports = {
+ bodyToHtml: function(content, searchTerm) {
+ var originalBody = content.body;
+ var body;
+
+ if (searchTerm) {
+ var lastOffset = 0;
+ var bodyList = [];
+ var k = 0;
+ var offset;
+
+ // XXX: rather than searching for the search term in the body,
+ // we should be looking at the match delimiters returned by the FTS engine
+ if (content.format === "org.matrix.custom.html") {
+
+ var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
+ var safeSearchTerm = sanitizeHtml(searchTerm, sanitizeHtmlParams);
+ while ((offset = safeBody.indexOf(safeSearchTerm, lastOffset)) >= 0) {
+ // FIXME: we need to apply the search highlighting to only the text elements of HTML, which means
+ // hooking into the sanitizer parser rather than treating it as a string. Otherwise
+ // the act of highlighting a or whatever will break the HTML badly.
+ bodyList.push();
+ bodyList.push();
+ lastOffset = offset + safeSearchTerm.length;
+ }
+ bodyList.push();
+ }
+ else {
+ while ((offset = originalBody.indexOf(searchTerm, lastOffset)) >= 0) {
+ bodyList.push({ originalBody.substring(lastOffset, offset) });
+ bodyList.push({ searchTerm });
+ lastOffset = offset + searchTerm.length;
+ }
+ bodyList.push({ originalBody.substring(lastOffset) });
+ }
+ body = bodyList;
+ }
+ else {
+ if (content.format === "org.matrix.custom.html") {
+ var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
+ body = ;
+ }
+ else {
+ body = originalBody;
+ }
+ }
+
+ return body;
+ },
+
+ highlightDom: function(element) {
+ var blocks = element.getElementsByTagName("code");
+ for (var i = 0; i < blocks.length; i++) {
+ highlight.highlightBlock(blocks[i]);
+ }
+ },
+
+}
+
diff --git a/src/components/views/messages/Event.js b/src/components/views/messages/Event.js
new file mode 100644
index 0000000000..d2862d304a
--- /dev/null
+++ b/src/components/views/messages/Event.js
@@ -0,0 +1,277 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var ReactDom = require('react-dom');
+var classNames = require("classnames");
+
+var sdk = require('../../../index');
+var MatrixClientPeg = require('../../../MatrixClientPeg')
+var TextForEvent = require('../../../TextForEvent');
+
+// FIXME BROKEN IMPORTS
+var ContextualMenu = require('../../../../ContextualMenu');
+var Velociraptor = require('../../../../Velociraptor');
+require('../../../../VelocityBounce');
+
+var bounce = false;
+try {
+ if (global.localStorage) {
+ bounce = global.localStorage.getItem('avatar_bounce') == 'true';
+ }
+} catch (e) {
+}
+
+var eventTileTypes = {
+ 'm.room.message': 'messages.Message',
+ 'm.room.member' : 'messages.TextualEvent',
+ 'm.call.invite' : 'messages.TextualEvent',
+ 'm.call.answer' : 'messages.TextualEvent',
+ 'm.call.hangup' : 'messages.TextualEvent',
+ 'm.room.name' : 'messages.TextualEvent',
+ 'm.room.topic' : 'messages.TextualEvent',
+};
+
+var MAX_READ_AVATARS = 5;
+
+module.exports = React.createClass({
+ displayName: 'Event',
+
+ statics: {
+ haveTileForEvent: function(e) {
+ if (eventTileTypes[e.getType()] == undefined) return false;
+ if (eventTileTypes[e.getType()] == 'messages.TextualEvent') {
+ return TextForEvent.textForEvent(e) !== '';
+ } else {
+ return true;
+ }
+ }
+ },
+
+ getInitialState: function() {
+ return {menu: false, allReadAvatars: false};
+ },
+
+ shouldHighlight: function() {
+ var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
+ if (!actions || !actions.tweaks) { return false; }
+ return actions.tweaks.highlight;
+ },
+
+ onEditClicked: function(e) {
+ var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu');
+ var buttonRect = e.target.getBoundingClientRect()
+ var x = buttonRect.right;
+ var y = buttonRect.top + (e.target.height / 2);
+ var self = this;
+ ContextualMenu.createMenu(MessageContextMenu, {
+ mxEvent: this.props.mxEvent,
+ left: x,
+ top: y,
+ onFinished: function() {
+ self.setState({menu: false});
+ }
+ });
+ this.setState({menu: true});
+ },
+
+ toggleAllReadAvatars: function() {
+ this.setState({
+ allReadAvatars: !this.state.allReadAvatars
+ });
+ },
+
+ getReadAvatars: function() {
+ var avatars = [];
+
+ var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
+
+ if (!room) return [];
+
+ var myUserId = MatrixClientPeg.get().credentials.userId;
+
+ // get list of read receipts, sorted most recent first
+ var receipts = room.getReceiptsForEvent(this.props.mxEvent).filter(function(r) {
+ return r.type === "m.read" && r.userId != myUserId;
+ }).sort(function(r1, r2) {
+ return r2.data.ts - r1.data.ts;
+ });
+
+ var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
+
+ var left = 0;
+
+ var reorderTransitionOpts = {
+ duration: 100,
+ easing: 'easeOut'
+ };
+
+ for (var i = 0; i < receipts.length; ++i) {
+ var member = room.getMember(receipts[i].userId);
+
+ // Using react refs here would mean both getting Velociraptor to expose
+ // them and making them scoped to the whole RoomView. Not impossible, but
+ // getElementById seems simpler at least for a first cut.
+ var oldAvatarDomNode = document.getElementById('mx_readAvatar'+member.userId);
+ var startStyles = [];
+ var enterTransitionOpts = [];
+ var oldNodeTop = -15; // For avatars that weren't on screen, act as if they were just off the top
+ if (oldAvatarDomNode) {
+ oldNodeTop = oldAvatarDomNode.getBoundingClientRect().top;
+ }
+
+ if (this.readAvatarNode) {
+ var topOffset = oldNodeTop - this.readAvatarNode.getBoundingClientRect().top;
+
+ if (oldAvatarDomNode && oldAvatarDomNode.style.left !== '0px') {
+ var leftOffset = oldAvatarDomNode.style.left;
+ // start at the old height and in the old h pos
+ startStyles.push({ top: topOffset, left: leftOffset });
+ enterTransitionOpts.push(reorderTransitionOpts);
+ }
+
+ // then shift to the rightmost column,
+ // and then it will drop down to its resting position
+ startStyles.push({ top: topOffset, left: '0px' });
+ enterTransitionOpts.push({
+ duration: bounce ? Math.min(Math.log(Math.abs(topOffset)) * 200, 3000) : 300,
+ easing: bounce ? 'easeOutBounce' : 'easeOutCubic',
+ });
+ }
+
+ var style = {
+ left: left+'px',
+ top: '0px',
+ visibility: ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) ? 'visible' : 'hidden'
+ };
+
+ //console.log("i = " + i + ", MAX_READ_AVATARS = " + MAX_READ_AVATARS + ", allReadAvatars = " + this.state.allReadAvatars + " visibility = " + style.visibility);
+
+ // add to the start so the most recent is on the end (ie. ends up rightmost)
+ avatars.unshift(
+
+ );
+ // TODO: we keep the extra read avatars in the dom to make animation simpler
+ // we could optimise this to reduce the dom size.
+ if (i < MAX_READ_AVATARS - 1 || this.state.allReadAvatars) { // XXX: where does this -1 come from? is it to make the max'th avatar animate properly?
+ left -= 15;
+ }
+ }
+ var editButton;
+ if (!this.state.allReadAvatars) {
+ var remainder = receipts.length - MAX_READ_AVATARS;
+ var remText;
+ if (i >= MAX_READ_AVATARS - 1) left -= 15;
+ if (remainder > 0) {
+ remText = { remainder }+
+ ;
+ left -= 15;
+ }
+ editButton = (
+
+ );
+ }
+
+ return
+ { editButton }
+ { remText }
+
+ { avatars }
+
+ ;
+ },
+
+ collectReadAvatarNode: function(node) {
+ this.readAvatarNode = ReactDom.findDOMNode(node);
+ },
+
+ render: function() {
+ var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
+ var SenderProfile = sdk.getComponent('molecules.SenderProfile');
+ var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
+
+ var content = this.props.mxEvent.getContent();
+ var msgtype = content.msgtype;
+
+ var EventTileType = sdk.getComponent(eventTileTypes[this.props.mxEvent.getType()]);
+ // This shouldn't happen: the caller should check we support this type
+ // before trying to instantiate us
+ if (!EventTileType) {
+ throw new Error("Event type not supported");
+ }
+
+ var classes = classNames({
+ mx_EventTile: true,
+ mx_EventTile_sending: ['sending', 'queued'].indexOf(
+ this.props.mxEvent.status
+ ) !== -1,
+ mx_EventTile_notSent: this.props.mxEvent.status == 'not_sent',
+ mx_EventTile_highlight: this.shouldHighlight(),
+ mx_EventTile_continuation: this.props.continuation,
+ mx_EventTile_last: this.props.last,
+ mx_EventTile_contextual: this.props.contextual,
+ menu: this.state.menu,
+ });
+ var timestamp =
+
+ var aux = null;
+ if (msgtype === 'm.image') aux = "sent an image";
+ else if (msgtype === 'm.video') aux = "sent a video";
+ else if (msgtype === 'm.file') aux = "uploaded a file";
+
+ var readAvatars = this.getReadAvatars();
+
+ var avatar, sender;
+ if (!this.props.continuation) {
+ if (this.props.mxEvent.sender) {
+ avatar = (
+
+
+ );
+ } else if (content.body) {
+ return (
+
+ Image '{content.body}' cannot be displayed.
+
+ );
+ } else {
+ return (
+
+ This image cannot be displayed.
+
+ );
+ }
+ },
+});
diff --git a/src/components/views/messages/MNoticeMessage.js b/src/components/views/messages/MNoticeMessage.js
new file mode 100644
index 0000000000..3a89d1ff6a
--- /dev/null
+++ b/src/components/views/messages/MNoticeMessage.js
@@ -0,0 +1,59 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var linkify = require('linkifyjs');
+var linkifyElement = require('linkifyjs/element');
+var linkifyMatrix = require('../../../linkify-matrix.js');
+linkifyMatrix(linkify);
+var HtmlUtils = require('../../../HtmlUtils');
+
+module.exports = React.createClass({
+ displayName: 'MNoticeMessage',
+
+ componentDidMount: function() {
+ linkifyElement(this.refs.content, linkifyMatrix.options);
+
+ if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
+ HtmlUtils.highlightDom(this.getDOMNode());
+ },
+
+ componentDidUpdate: function() {
+ if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
+ HtmlUtils.highlightDom(this.getDOMNode());
+ },
+
+ shouldComponentUpdate: function(nextProps) {
+ // exploit that events are immutable :)
+ return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
+ nextProps.searchTerm !== this.props.searchTerm);
+ },
+
+ // XXX: fix horrible duplication with MTextTile
+ render: function() {
+ var content = this.props.mxEvent.getContent();
+ var body = HtmlUtils.bodyToHtml(content, this.props.searchTerm);
+
+ return (
+
+ { body }
+
+ );
+ },
+});
+
diff --git a/src/components/views/messages/MRoomMemberEvent.js b/src/components/views/messages/MRoomMemberEvent.js
new file mode 100644
index 0000000000..6e73519f2e
--- /dev/null
+++ b/src/components/views/messages/MRoomMemberEvent.js
@@ -0,0 +1,52 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+
+var sdk = require('../../../index');
+var TextForEvent = require('../../../TextForEvent');
+
+module.exports = React.createClass({
+ displayName: 'MRoomMemberEvent',
+
+ getMemberEventText: function() {
+ return TextForEvent.textForEvent(this.props.mxEvent);
+ },
+
+ render: function() {
+ // XXX: for now, just cheekily borrow the css from message tile...
+ var timestamp = this.props.last ? : null;
+ var text = this.getMemberEventText();
+ if (!text) return ;
+ var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
+ var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
+ return (
+
+
+
+
+ { timestamp }
+
+
+ { text }
+
+
+ );
+ },
+});
+
diff --git a/src/components/views/messages/MTextMessage.js b/src/components/views/messages/MTextMessage.js
new file mode 100644
index 0000000000..d3b337cbc1
--- /dev/null
+++ b/src/components/views/messages/MTextMessage.js
@@ -0,0 +1,59 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var HtmlUtils = require('../../../HtmlUtils');
+var linkify = require('linkifyjs');
+var linkifyElement = require('linkifyjs/element');
+var linkifyMatrix = require('../../../linkify-matrix');
+
+linkifyMatrix(linkify);
+
+module.exports = React.createClass({
+ displayName: 'MTextMessage',
+
+ componentDidMount: function() {
+ linkifyElement(this.refs.content, linkifyMatrix.options);
+
+ if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
+ HtmlUtils.highlightDom(this.getDOMNode());
+ },
+
+ componentDidUpdate: function() {
+ if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
+ HtmlUtils.highlightDom(this.getDOMNode());
+ },
+
+ shouldComponentUpdate: function(nextProps) {
+ // exploit that events are immutable :)
+ return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
+ nextProps.searchTerm !== this.props.searchTerm);
+ },
+
+ render: function() {
+ var content = this.props.mxEvent.getContent();
+ var body = HtmlUtils.bodyToHtml(content, this.props.searchTerm);
+
+ return (
+
+ { body }
+
+ );
+ },
+});
+
diff --git a/src/components/views/messages/MVideoMessage.js b/src/components/views/messages/MVideoMessage.js
new file mode 100644
index 0000000000..5771ed2172
--- /dev/null
+++ b/src/components/views/messages/MVideoMessage.js
@@ -0,0 +1,83 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var filesize = require('filesize');
+
+var MatrixClientPeg = require('../../../MatrixClientPeg');
+var Modal = require('../../../Modal');
+var sdk = require('../../../index');
+
+module.exports = React.createClass({
+ displayName: 'MVideoMessage',
+
+ thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) {
+ if (!fullWidth || !fullHeight) {
+ // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
+ // log this because it's spammy
+ return undefined;
+ }
+ if (fullWidth < thumbWidth && fullHeight < thumbHeight) {
+ // no scaling needs to be applied
+ return fullHeight;
+ }
+ var widthMulti = thumbWidth / fullWidth;
+ var heightMulti = thumbHeight / fullHeight;
+ if (widthMulti < heightMulti) {
+ // width is the dominant dimension so scaling will be fixed on that
+ return widthMulti;
+ }
+ else {
+ // height is the dominant dimension so scaling will be fixed on that
+ return heightMulti;
+ }
+ },
+
+ render: function() {
+ var content = this.props.mxEvent.getContent();
+ var cli = MatrixClientPeg.get();
+
+ var height = null;
+ var width = null;
+ var poster = null;
+ var preload = "metadata";
+ if (content.info) {
+ var scale = this.thumbScale(content.info.w, content.info.h, 480, 360);
+ if (scale) {
+ width = Math.floor(content.info.w * scale);
+ height = Math.floor(content.info.h * scale);
+ }
+
+ if (content.info.thumbnail_url) {
+ poster = cli.mxcUrlToHttp(content.info.thumbnail_url);
+ preload = "none";
+ }
+ }
+
+
+
+ return (
+
+
+
+ );
+ },
+});
diff --git a/src/components/views/messages/Message.js b/src/components/views/messages/Message.js
new file mode 100644
index 0000000000..fa74a8e137
--- /dev/null
+++ b/src/components/views/messages/Message.js
@@ -0,0 +1,52 @@
+/*
+Copyright 2015 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var sdk = require('../../../index');
+
+module.exports = React.createClass({
+ displayName: 'Message',
+
+ statics: {
+ needsSenderProfile: function() {
+ return true;
+ }
+ },
+
+ render: function() {
+ var UnknownMessageTile = sdk.getComponent('messages.UnknownMessage');
+
+ var tileTypes = {
+ 'm.text': sdk.getComponent('messages.MTextMessage'),
+ 'm.notice': sdk.getComponent('messages.MNoticeMessage'),
+ 'm.emote': sdk.getComponent('messages.MEmoteMessage'),
+ 'm.image': sdk.getComponent('messages.MImageMessage'),
+ 'm.file': sdk.getComponent('messages.MFileMessage'),
+ 'm.video': sdk.getComponent('messages.MVideoMessage')
+ };
+
+ var content = this.props.mxEvent.getContent();
+ var msgtype = content.msgtype;
+ var TileType = UnknownMessageTile;
+ if (msgtype && tileTypes[msgtype]) {
+ TileType = tileTypes[msgtype];
+ }
+
+ return ;
+ },
+});
diff --git a/src/controllers/molecules/MEmoteTile.js b/src/components/views/messages/TextualEvent.js
similarity index 50%
rename from src/controllers/molecules/MEmoteTile.js
rename to src/components/views/messages/TextualEvent.js
index d32d8ae911..5a9a1408c5 100644
--- a/src/controllers/molecules/MEmoteTile.js
+++ b/src/components/views/messages/TextualEvent.js
@@ -16,15 +16,28 @@ limitations under the License.
'use strict';
-var linkify = require('linkifyjs');
-var linkifyElement = require('linkifyjs/element');
-var linkifyMatrix = require('../../linkify-matrix');
+var React = require('react');
-linkifyMatrix(linkify);
+var TextForEvent = require('../../../TextForEvent');
-module.exports = {
- componentDidMount: function() {
- linkifyElement(this.refs.content, linkifyMatrix.options);
- }
-};
+module.exports = React.createClass({
+ displayName: 'TextualEvent',
+
+ statics: {
+ needsSenderProfile: function() {
+ return false;
+ }
+ },
+
+ render: function() {
+ var text = TextForEvent.textForEvent(this.props.mxEvent);
+ if (text == null || text.length == 0) return null;
+
+ return (
+
+ );
+ },
+});
diff --git a/src/controllers/molecules/EventTile.js b/src/components/views/messages/UnknownMessage.js
similarity index 64%
rename from src/controllers/molecules/EventTile.js
rename to src/components/views/messages/UnknownMessage.js
index 953e33b516..c0392cbaf5 100644
--- a/src/controllers/molecules/EventTile.js
+++ b/src/components/views/messages/UnknownMessage.js
@@ -16,13 +16,17 @@ limitations under the License.
'use strict';
-var MatrixClientPeg = require("../../MatrixClientPeg");
+var React = require('react');
-module.exports = {
- shouldHighlight: function() {
- var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
- if (!actions || !actions.tweaks) { return false; }
- return actions.tweaks.highlight;
- }
-};
+module.exports = React.createClass({
+ displayName: 'UnknownMessage',
+ render: function() {
+ var content = this.props.mxEvent.getContent();
+ return (
+
+ {content.body}
+
+ );
+ },
+});
diff --git a/src/controllers/molecules/MNoticeTile.js b/src/controllers/molecules/MNoticeTile.js
deleted file mode 100644
index 597ce3cd10..0000000000
--- a/src/controllers/molecules/MNoticeTile.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
-Copyright 2015 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.
-*/
-
-'use strict';
-
-var linkify = require('linkifyjs');
-var linkifyElement = require('linkifyjs/element');
-var linkifyMatrix = require('../../linkify-matrix.js');
-linkifyMatrix(linkify);
-
-module.exports = {
- componentDidMount: function() {
- linkifyElement(this.refs.content, linkifyMatrix.options);
- }
-};
diff --git a/src/controllers/molecules/MessageTile.js b/src/controllers/molecules/MessageTile.js
deleted file mode 100644
index 7f3416d6db..0000000000
--- a/src/controllers/molecules/MessageTile.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-Copyright 2015 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.
-*/
-
-'use strict';
-
-var MatrixClientPeg = require("../../MatrixClientPeg");
-
-module.exports = {
-};
-