From ce3dab3c5be05cd5be6513cc7f740ba7d89fd4c8 Mon Sep 17 00:00:00 2001
From: Erik Johnston <erikj@matrix.org>
Date: Thu, 16 Jul 2015 17:42:33 +0100
Subject: [PATCH 1/9] Make room name editable

---
 skins/base/views/molecules/RoomHeader.js | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/skins/base/views/molecules/RoomHeader.js b/skins/base/views/molecules/RoomHeader.js
index 62f24484..33d43302 100644
--- a/skins/base/views/molecules/RoomHeader.js
+++ b/skins/base/views/molecules/RoomHeader.js
@@ -17,14 +17,20 @@ limitations under the License.
 'use strict';
 
 var React = require('react');
+var ComponentBroker = require('../../../../src/ComponentBroker');
 
 var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
 var RoomHeaderController = require("../../../../src/controllers/molecules/RoomHeader");
+var EditableText = ComponentBroker.get("atoms/EditableText");
 
 module.exports = React.createClass({
     displayName: 'RoomHeader',
     mixins: [RoomHeaderController],
 
+    onNameChange: function(new_name) {
+        MatrixClientPeg.get().setRoomName(this.props.room.roomId, new_name);
+    },
+
     render: function() {
 
         var topic = this.props.room.currentState.getStateEvents('m.room.topic', '');
@@ -52,7 +58,9 @@ module.exports = React.createClass({
                             <img src={ MatrixClientPeg.get().getAvatarUrlForRoom(this.props.room, 48, 48, "crop") } width="48" height="48"/>
                         </div>
                         <div className="mx_RoomHeader_info">
-                            <div className="mx_RoomHeader_name">{ this.props.room.name }</div>
+                            <div className="mx_RoomHeader_name">
+                                <EditableText initalValue={this.props.room.name} onValueChanged={this.onNameChange} />
+                            </div>
                             { topic }
                         </div>
                     </div>
@@ -76,4 +84,3 @@ module.exports = React.createClass({
         );
     },
 });
-

From cfbef0177ee706a226d4eda9b7d4bc5a095100cb Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Thu, 16 Jul 2015 17:51:21 +0100
Subject: [PATCH 2/9] Fix custom server or registration & do some of new login
 UI

---
 skins/base/views/templates/Login.js    | 63 +++++++++++++++++++++++---
 skins/base/views/templates/Register.js | 13 ++++--
 src/controllers/templates/Login.js     |  3 +-
 3 files changed, 67 insertions(+), 12 deletions(-)

diff --git a/skins/base/views/templates/Login.js b/skins/base/views/templates/Login.js
index f71e3070..ba75e5d1 100644
--- a/skins/base/views/templates/Login.js
+++ b/skins/base/views/templates/Login.js
@@ -28,15 +28,44 @@ var LoginController = require("../../../../src/controllers/templates/Login");
 var ServerConfig = ComponentBroker.get("molecules/ServerConfig");
 
 module.exports = React.createClass({
+    DEFAULT_HS_URL: 'https://matrix.org',
+    DEFAULT_IS_URL: 'https://matrix.org',
+
     displayName: 'Login',
     mixins: [LoginController],
 
+    getInitialState: function() {
+        return {
+            serverConfigVisible: false
+        };
+    },
+
+    componentWillMount: function() {
+        this.onHSChosen();
+        this.customHsUrl = this.DEFAULT_HS_URL;
+        this.customIsUrl = this.DEFAULT_IS_URL;
+    },
+
     getHsUrl: function() {
-        return this.refs.serverConfig.getHsUrl();
+        if (this.state.serverConfigVisible) {
+            return this.refs.serverConfig.getHsUrl();
+        } else {
+            return this.DEFAULT_HS_URL;
+        }
     },
 
     getIsUrl: function() {
-        return this.refs.serverConfig.getIsUrl();
+        if (this.state.serverConfigVisible) {
+            return this.refs.serverConfig.getIsUrl();
+        } else {
+            return this.DEFAULT_IS_URL;
+        }
+    },
+
+    onServerConfigVisibleChange: function(ev) {
+        this.setState({
+            serverConfigVisible: ev.target.checked
+        });
     },
 
     /**
@@ -49,15 +78,35 @@ module.exports = React.createClass({
         };
     },
 
+    onHsUrlChanged: function() {
+        this.customHsUrl = this.getHsUrl();
+        this.customIsUrl = this.getIsUrl();
+        if (this.updateHsTimeout) {
+            clearTimeout(this.updateHsTimeout);
+        }
+        /*var self = this;
+        this.updateHsTimeout = setTimeout(function() {
+            self.onHSChosen();
+        }, 500);*/
+    },
+
     componentForStep: function(step) {
         switch (step) {
             case 'choose_hs':
+                var serverConfigStyle = {};
+                if (!this.state.serverConfigVisible) {
+                    serverConfigStyle.display = 'none';
+                }
                 return (
                     <div>
-                        <form onSubmit={this.onHSChosen}>
-                        <ServerConfig ref="serverConfig" />
-                        <input type="submit" value="Continue" />
-                        </form>
+                        <input type="checkbox" value={this.state.serverConfigVisible} onChange={this.onServerConfigVisibleChange} />
+                        Use custom server options (advanced)
+                        <div style={serverConfigStyle}>
+                        <ServerConfig ref="serverConfig"
+                            defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl}
+                            onHsUrlChanged={this.onHsUrlChanged}
+                        />
+                        </div>
                     </div>
                 );
             // XXX: clearly these should be separate organisms
@@ -67,6 +116,7 @@ module.exports = React.createClass({
                         <form onSubmit={this.onUserPassEntered}>
                         <input ref="user" type="text" placeholder="username" /><br />
                         <input ref="pass" type="password" placeholder="password" /><br />
+                        {this.componentForStep('choose_hs')}
                         <input type="submit" value="Log in" />
                         </form>
                     </div>
@@ -94,7 +144,6 @@ module.exports = React.createClass({
     render: function() {
         return (
             <div className="mx_Login">
-            <ProgressBar value={this.state.currentStep} max={this.state.totalSteps} />
             {this.loginContent()}
             </div>
         );
diff --git a/skins/base/views/templates/Register.js b/skins/base/views/templates/Register.js
index 930228b8..23367378 100644
--- a/skins/base/views/templates/Register.js
+++ b/skins/base/views/templates/Register.js
@@ -39,6 +39,11 @@ module.exports = React.createClass({
         };
     },
 
+    componentWillMount: function() {
+        this.customHsUrl = this.DEFAULT_HS_URL;
+        this.customIsUrl = this.DEFAULT_IS_URL;
+    },
+
     getRegFormVals: function() {
         return {
             email: this.refs.email.getDOMNode().value,
@@ -50,7 +55,7 @@ module.exports = React.createClass({
 
     getHsUrl: function() {
         if (this.state.serverConfigVisible) {
-            return this.refs.serverConfig.getHsUrl();
+            return this.customHsUrl;
         } else {
             return this.DEFAULT_HS_URL;
         }
@@ -58,7 +63,7 @@ module.exports = React.createClass({
 
     getIsUrl: function() {
         if (this.state.serverConfigVisible) {
-            return this.refs.serverConfig.getIsUrl();
+            return this.customIsUrl;
         } else {
             return this.DEFAULT_IS_URL;
         }
@@ -82,6 +87,8 @@ module.exports = React.createClass({
     },
 
     onServerUrlChanged: function(newUrl) {
+        this.customHsUrl = this.refs.serverConfig.getHsUrl();
+        this.customIsUrl = this.refs.serverConfig.getIsUrl();
         this.forceUpdate();
     },
 
@@ -104,7 +111,7 @@ module.exports = React.createClass({
                         Use custom server options (advanced)
                         <div style={serverConfigStyle}>
                         <ServerConfig ref="serverConfig"
-                            defaultHsUrl={this.default_hs_url} defaultIsUrl={this.DEFAULT_IS_URL}
+                            defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl}
                             onHsUrlChanged={this.onServerUrlChanged} onIsUrlChanged={this.onServerUrlChanged} />
                         </div>
                         <br />
diff --git a/src/controllers/templates/Login.js b/src/controllers/templates/Login.js
index 07ace740..3d25a910 100644
--- a/src/controllers/templates/Login.js
+++ b/src/controllers/templates/Login.js
@@ -38,8 +38,7 @@ module.exports = {
         this.setState({ step: step, errorText: '', busy: false });
     },
 
-    onHSChosen: function(ev) {
-        ev.preventDefault();
+    onHSChosen: function() {
         MatrixClientPeg.replaceUsingUrls(
             this.getHsUrl(),
             this.getIsUrl()

From d08c47a3284b00fe04a7f6bcc0580e3208d4728d Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Thu, 16 Jul 2015 21:45:59 +0100
Subject: [PATCH 3/9] Fix npe

---
 src/controllers/organisms/MemberList.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js
index a511816d..1213db9b 100644
--- a/src/controllers/organisms/MemberList.js
+++ b/src/controllers/organisms/MemberList.js
@@ -62,6 +62,7 @@ module.exports = {
     },
 
     roomMembers: function(limit) {
+        if (!this.props.roomId) return {};
         var cli = MatrixClientPeg.get();
         var all_members = cli.getRoom(this.props.roomId).currentState.members;
         var all_user_ids = Object.keys(all_members);

From 8ccce4d7027ff1919d1d126024c29d1ed1151410 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Thu, 16 Jul 2015 21:46:39 +0100
Subject: [PATCH 4/9] Make new login style work

---
 skins/base/views/templates/Login.js | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/skins/base/views/templates/Login.js b/skins/base/views/templates/Login.js
index ba75e5d1..0fd64ce5 100644
--- a/skins/base/views/templates/Login.js
+++ b/skins/base/views/templates/Login.js
@@ -19,6 +19,7 @@ limitations under the License.
 var React = require('react');
 
 var ComponentBroker = require("../../../../src/ComponentBroker");
+var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
 
 var ProgressBar = ComponentBroker.get("molecules/ProgressBar");
 var Loader = require("react-loader");
@@ -48,7 +49,7 @@ module.exports = React.createClass({
 
     getHsUrl: function() {
         if (this.state.serverConfigVisible) {
-            return this.refs.serverConfig.getHsUrl();
+            return this.customHsUrl;
         } else {
             return this.DEFAULT_HS_URL;
         }
@@ -56,7 +57,7 @@ module.exports = React.createClass({
 
     getIsUrl: function() {
         if (this.state.serverConfigVisible) {
-            return this.refs.serverConfig.getIsUrl();
+            return this.customIsUrl;
         } else {
             return this.DEFAULT_IS_URL;
         }
@@ -65,7 +66,7 @@ module.exports = React.createClass({
     onServerConfigVisibleChange: function(ev) {
         this.setState({
             serverConfigVisible: ev.target.checked
-        });
+        }, this.onHsUrlChanged);
     },
 
     /**
@@ -79,12 +80,23 @@ module.exports = React.createClass({
     },
 
     onHsUrlChanged: function() {
-        this.customHsUrl = this.getHsUrl();
-        this.customIsUrl = this.getIsUrl();
-        if (this.updateHsTimeout) {
+        this.customHsUrl = this.refs.serverConfig.getHsUrl();
+        this.customIsUrl = this.refs.serverConfig.getIsUrl();
+        MatrixClientPeg.replaceUsingUrls(
+            this.getHsUrl(),
+            this.getIsUrl()
+        );
+        this.setState({
+            hs_url: this.getHsUrl(),
+            is_url: this.getIsUrl()
+        });
+        // XXX: HSes do not have to offer password auth, so we
+        // need to update and maybe show a different component
+        // when a new HS is entered.
+        /*if (this.updateHsTimeout) {
             clearTimeout(this.updateHsTimeout);
         }
-        /*var self = this;
+        var self = this;
         this.updateHsTimeout = setTimeout(function() {
             self.onHSChosen();
         }, 500);*/

From 0f39ec580f01b62cc7685bfd18411a0b20071803 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Thu, 16 Jul 2015 22:06:00 +0100
Subject: [PATCH 5/9] Slightly improve error messages

---
 src/controllers/templates/Register.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js
index 664b36b3..89a3872d 100644
--- a/src/controllers/templates/Register.js
+++ b/src/controllers/templates/Register.js
@@ -337,6 +337,14 @@ module.exports = {
                     });
                 } else if (error.httpStatus == 401) {
                     newState.errorText = "Authorisation failed!";
+                } else if (error.httpStatus >= 400 && error.httpStatus < 500) {
+                    newState.errorText = "Registration failed!";
+                } else if (error.httpStatus >= 500 && error.httpStatus < 600) {
+                    newState.errorText = "Server error during registration!";
+                } else if (error.name == "M_MISSING_PARAM") {
+                    // The HS hasn't remembered the login params from
+                    // the first try when the login email was sent.
+                    newState.errorText = "This home server does not support resuming registration.";
                 }
                 self.setState(newState);
             }

From 28dcfb2f12fb8cc539ccfef800cb52bdccc39099 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Thu, 16 Jul 2015 22:39:10 +0100
Subject: [PATCH 6/9] make it work on ff

---
 skins/base/css/molecules/RoomHeader.css | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/skins/base/css/molecules/RoomHeader.css b/skins/base/css/molecules/RoomHeader.css
index d0e86ad9..dbb7910f 100644
--- a/skins/base/css/molecules/RoomHeader.css
+++ b/skins/base/css/molecules/RoomHeader.css
@@ -27,11 +27,10 @@ limitations under the License.
     display: -moz-box;
     display: -ms-flexbox;
     display: -webkit-flex;
-    display: flex;    
+    display: flex;
 }
 
 .mx_RoomHeader_leftRow {
-    display: table-row;
     height: 48px;
 
     -webkit-box-ordinal-group: 1;
@@ -44,7 +43,6 @@ limitations under the License.
 }
 
 .mx_RoomHeader_rightRow {
-    display: table-row;
     height: 48px;
     background-color: #fff;
     border-radius: 48px;
@@ -88,6 +86,7 @@ limitations under the License.
 
 .mx_RoomHeader_avatar {
     display: table-cell;
+    width: 48px;
     height: 50px;
     vertical-align: middle;
 }

From a2ca5f28476194d2185b7abfba46683c53b85312 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Thu, 16 Jul 2015 22:39:38 +0100
Subject: [PATCH 7/9] improve badges and room tile layout

---
 skins/base/css/molecules/RoomTile.css  | 17 ++++++++++++++---
 skins/base/views/molecules/RoomTile.js |  9 ++++++++-
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/skins/base/css/molecules/RoomTile.css b/skins/base/css/molecules/RoomTile.css
index 6e19395e..b258ea33 100644
--- a/skins/base/css/molecules/RoomTile.css
+++ b/skins/base/css/molecules/RoomTile.css
@@ -29,8 +29,8 @@ limitations under the License.
     padding-top: 3px;
     padding-bottom: 3px;
     vertical-align: middle;
-    width: 32px;
-    height: 32px;
+    width: 40px;
+    height: 40px;
 }
 
 .mx_RoomTile_avatar img {
@@ -38,6 +38,12 @@ limitations under the License.
     background-color: #dbdbdb;
 }
 
+.mx_RoomTile_nameBadge {
+    display: table;
+    width: 100%;
+    height: 50px;
+}
+
 .mx_RoomTile_name {
     display: table-cell;
     vertical-align: middle;
@@ -45,8 +51,13 @@ limitations under the License.
     text-overflow: ellipsis;
 }
 
+.mx_RoomTile_badgeCell {
+    display: table-cell;
+    vertical-align: middle;
+    width: 26px;
+}
+
 .mx_RoomTile_badge {    
-    float: right;
     background-color: #80cef4;
     color: #fff;
     border-radius: 26px;
diff --git a/skins/base/views/molecules/RoomTile.js b/skins/base/views/molecules/RoomTile.js
index 00d16cf0..44709577 100644
--- a/skins/base/views/molecules/RoomTile.js
+++ b/skins/base/views/molecules/RoomTile.js
@@ -43,10 +43,17 @@ module.exports = React.createClass({
         else if (this.props.unread) {
             badge = <div className="mx_RoomTile_badge">1</div>;
         }
+        var nameCell;
+        if (badge) {
+            nameCell = <div className="mx_RoomTile_nameBadge"><div className="mx_RoomTile_name">{name}</div><div className="mx_RoomTile_badgeCell">{badge}</div></div>;
+        }
+        else {
+            nameCell = <div className="mx_RoomTile_name">{name}</div>;
+        }
         return (
             <div className={classes} onClick={this.onClick}>
                 <div className="mx_RoomTile_avatar"><img src={ MatrixClientPeg.get().getAvatarUrlForRoom(this.props.room, 40, 40, "crop") } width="40" height="40"/></div>
-                <div className="mx_RoomTile_name">{name}{ badge }</div>
+                { nameCell }
             </div>
         );
     }

From 1a95148dae48945a3b3639c1f1b1447b27825f45 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Fri, 17 Jul 2015 00:12:36 +0100
Subject: [PATCH 8/9] basic date separator support

---
 skins/base/css/common.css                     |  1 +
 skins/base/css/organisms/LeftPanel.css        |  1 -
 skins/base/css/organisms/RoomView.css         |  9 +++++-
 skins/base/views/molecules/MRoomMemberTile.js |  4 ++-
 src/ComponentBroker.js                        |  1 +
 src/controllers/organisms/RoomView.js         | 29 ++++++++++++++-----
 6 files changed, 34 insertions(+), 11 deletions(-)

diff --git a/skins/base/css/common.css b/skins/base/css/common.css
index 4c2e7c0b..aa10e409 100644
--- a/skins/base/css/common.css
+++ b/skins/base/css/common.css
@@ -28,6 +28,7 @@ div.error {
 }
 
 h2 {
+    color: #80cef4;
     font-weight: 400;
     font-size: 20px;
     margin-top: 16px;
diff --git a/skins/base/css/organisms/LeftPanel.css b/skins/base/css/organisms/LeftPanel.css
index 70a4b6f7..bfe26f11 100644
--- a/skins/base/css/organisms/LeftPanel.css
+++ b/skins/base/css/organisms/LeftPanel.css
@@ -44,7 +44,6 @@ limitations under the License.
     padding-left: 16px;
     padding-right: 16px;
 
-    /* background-color: #0ff; */
     height: 100%;
     overflow-y: scroll;
 }
diff --git a/skins/base/css/organisms/RoomView.css b/skins/base/css/organisms/RoomView.css
index bc1a1692..e1f3ea6b 100644
--- a/skins/base/css/organisms/RoomView.css
+++ b/skins/base/css/organisms/RoomView.css
@@ -64,7 +64,6 @@ limitations under the License.
     width: 100%;
     height: 100%;
     margin-bottom: 60px;
-    /* background-color: #ff0; */
 
     overflow-y: scroll;
 }
@@ -78,6 +77,14 @@ limitations under the License.
     width: 100%;
 }
 
+.mx_RoomView_MessageList h2 {
+    clear: both;
+    margin-top: 32px;
+    margin-bottom: 8px;
+    padding-bottom: 6px;
+    border-bottom: 1px solid #a8dbf3;
+}
+
 .mx_RoomView_invitePrompt {
 }
 
diff --git a/skins/base/views/molecules/MRoomMemberTile.js b/skins/base/views/molecules/MRoomMemberTile.js
index 92b6b119..996b6f9c 100644
--- a/skins/base/views/molecules/MRoomMemberTile.js
+++ b/skins/base/views/molecules/MRoomMemberTile.js
@@ -45,12 +45,14 @@ module.exports = React.createClass({
 
     render: function() {
         // XXX: for now, just cheekily borrow the css from message tile...
+        var timestamp = this.props.last ? <MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
+
         return (
             <div className="mx_MessageTile">
                 <div className="mx_MessageTile_avatar">
                     <img src={ this.props.mxEvent.target ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.target, 40, 40, "crop") : null } width="40" height="40"/>
                 </div>            
-                <MessageTimestamp ts={this.props.mxEvent.getTs()} />
+                { timestamp }
                 <span className="mx_SenderProfile"></span>
                 <span className="mx_MessageTile_content">
                     {this.getMemberEventText()}
diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js
index 431f4766..0f606dd3 100644
--- a/src/ComponentBroker.js
+++ b/src/ComponentBroker.js
@@ -98,6 +98,7 @@ require('../skins/base/views/organisms/RightPanel');
 require('../skins/base/views/molecules/RoomCreate');
 require('../skins/base/views/molecules/RoomDropTarget');
 require('../skins/base/views/molecules/DirectoryMenu');
+require('../skins/base/views/molecules/DateSeparator');
 require('../skins/base/views/atoms/voip/VideoFeed');
 require('../skins/base/views/molecules/voip/VideoView');
 require('../skins/base/views/molecules/voip/CallView');
diff --git a/src/controllers/organisms/RoomView.js b/src/controllers/organisms/RoomView.js
index 9078d558..f1a26ee4 100644
--- a/src/controllers/organisms/RoomView.js
+++ b/src/controllers/organisms/RoomView.js
@@ -36,6 +36,8 @@ var tileTypes = {
     'm.call.hangup': ComponentBroker.get('molecules/voip/MCallHangupTile')
 };
 
+var DateSeparator = ComponentBroker.get('molecules/DateSeparator');
+
 module.exports = {
     getInitialState: function() {
         return {
@@ -231,22 +233,33 @@ module.exports = {
             var TileType = tileTypes[mxEv.getType()];
             var continuation = false;
             var last = false;
+            var dateSeparator = null;
             if (i == this.state.room.timeline.length - 1) {
                 last = true;
             }
-            if (i > 0 &&
-                count < this.state.messageCap - 1 &&
-                this.state.room.timeline[i].sender &&
-                this.state.room.timeline[i - 1].sender &&
-                this.state.room.timeline[i].sender.userId ===
+            if (i > 0 && count < this.state.messageCap - 1) {
+                if (this.state.room.timeline[i].sender &&
+                    this.state.room.timeline[i - 1].sender &&
+                    this.state.room.timeline[i].sender.userId ===
                     this.state.room.timeline[i - 1].sender.userId)
-            {
-                continuation = true;
-            }
+                {
+                    continuation = true;
+                }
+
+                var ts0 = this.state.room.timeline[i - 1].getTs();
+                var ts1 = this.state.room.timeline[i].getTs();
+                if (new Date(ts0).toDateString() !== new Date(ts1).toDateString()) {
+                    dateSeparator = <DateSeparator key={ts1} ts={ts1}/>;
+                    continuation = false;
+                }
+            } 
             if (!TileType) continue;
             ret.unshift(
                 <TileType key={mxEv.getId()} mxEvent={mxEv} continuation={continuation} last={last}/>
             );
+            if (dateSeparator) {
+                ret.unshift(dateSeparator);
+            }
             ++count;
         }
         return ret;

From 891ba401142bb25408e9d467b84295c51a94528b Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Fri, 17 Jul 2015 00:12:42 +0100
Subject: [PATCH 9/9] basic date separator support

---
 skins/base/views/molecules/DateSeparator.js | 56 +++++++++++++++++++++
 1 file changed, 56 insertions(+)
 create mode 100644 skins/base/views/molecules/DateSeparator.js

diff --git a/skins/base/views/molecules/DateSeparator.js b/skins/base/views/molecules/DateSeparator.js
new file mode 100644
index 00000000..061ce66d
--- /dev/null
+++ b/skins/base/views/molecules/DateSeparator.js
@@ -0,0 +1,56 @@
+/*
+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 days = [
+    "Sunday",
+    "Monday",
+    "Tuesday",
+    "Wednesday",
+    "Thursday",
+    "Friday",
+    "Saturday"
+];
+
+module.exports = React.createClass({
+    displayName: 'DateSeparator',
+    render: function() {
+        var date = new Date(this.props.ts);
+        var today = new Date();
+        var yesterday = new Date();
+        yesterday.setDate(today.getDate() - 1);
+        var label;
+        if (date.toDateString() === today.toDateString()) {
+            label = "Today";
+        }
+        else if (date.toDateString() === yesterday.toDateString()) {
+            label = "Yesterday";
+        }
+        else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
+            label = days[date.getDay()];
+        }
+        else {
+            label = date.toDateString();
+        }
+
+        return (
+            <h2>{ label }</h2>
+        );
+    }
+});