From 08a41bf0938e8cf5ad70534482130ae74c29d406 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Fri, 1 Apr 2016 02:16:29 +0100
Subject: [PATCH 01/11] improve layout for LinkPreviewWidget

---
 .../views/rooms/UrlPreviewWidget.css          | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css

diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
new file mode 100644
index 0000000000..e8ef26b77e
--- /dev/null
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
@@ -0,0 +1,49 @@
+/*
+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.
+*/
+
+.mx_LinkPreviewWidget {
+    padding-left: 15px;
+    margin-top: 15px;
+    margin-right: 15px;
+    margin-bottom: 15px;
+    display: -webkit-flex;
+    display: flex;
+    border-left: 5px solid #ddd;
+    color: #888;
+}
+
+.mx_LinkPreviewWidget_image {
+    -webkit-flex: 0 0 100px;
+    flex: 0 0 100px;
+    text-align: center;
+}
+
+.mx_LinkPreviewWidget_caption {
+    margin-left: 15px;
+}
+
+.mx_LinkPreviewWidget_title {
+    display: inline;
+    font-weight: bold;
+}
+
+.mx_LinkPreviewWidget_siteName {
+    display: inline;
+}
+
+.mx_LinkPreviewWidget_description {
+    margin-top: 8px;
+}

From 8247bb4a76c23f442ec39c83a5fff68d71af3018 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Sat, 2 Apr 2016 00:36:53 +0100
Subject: [PATCH 02/11] match style for markdown quotes

---
 .../css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
index e8ef26b77e..ae54b900c1 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
@@ -21,7 +21,7 @@ limitations under the License.
     margin-bottom: 15px;
     display: -webkit-flex;
     display: flex;
-    border-left: 5px solid #ddd;
+    border-left: 4px solid #ddd;
     color: #888;
 }
 

From 8c0a23dd8b50cd506f01245fae78d9f6982e8d48 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Sun, 3 Apr 2016 02:06:24 +0100
Subject: [PATCH 03/11] fix widget layout

---
 .../css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css      | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
index ae54b900c1..800883a153 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
@@ -15,7 +15,6 @@ limitations under the License.
 */
 
 .mx_LinkPreviewWidget {
-    padding-left: 15px;
     margin-top: 15px;
     margin-right: 15px;
     margin-bottom: 15px;
@@ -28,6 +27,7 @@ limitations under the License.
 .mx_LinkPreviewWidget_image {
     -webkit-flex: 0 0 100px;
     flex: 0 0 100px;
+    margin-left: 15px;
     text-align: center;
 }
 
@@ -46,4 +46,5 @@ limitations under the License.
 
 .mx_LinkPreviewWidget_description {
     margin-top: 8px;
+    white-space: normal;
 }

From ff2885087da8a87ce40db2942675a09a8dc6422a Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Sun, 3 Apr 2016 02:50:51 +0100
Subject: [PATCH 04/11] support cancelling and uncancelling previews

---
 .../views/rooms/MessageContextMenu.js         | 23 +++++++++++++++++++
 .../views/rooms/UrlPreviewWidget.css          | 11 +++++++++
 2 files changed, 34 insertions(+)

diff --git a/src/components/views/rooms/MessageContextMenu.js b/src/components/views/rooms/MessageContextMenu.js
index a0a3f1cfd1..24948f74f8 100644
--- a/src/components/views/rooms/MessageContextMenu.js
+++ b/src/components/views/rooms/MessageContextMenu.js
@@ -66,6 +66,15 @@ module.exports = React.createClass({
         if (this.props.onFinished) this.props.onFinished();
     },
 
+    onUnhidePreviewClick: function() {
+        if (global.localStorage) {
+            // FIXME: factor this out with LinkPreviewWidget
+            // FIXME: somehow propagate this to the EventTile such that it updates itself and realises the link has rematerialised
+            global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
+            if (this.props.onFinished) this.props.onFinished();
+        }
+    },
+
     render: function() {
         var eventStatus = this.props.mxEvent.status;
         var resendButton;
@@ -73,6 +82,7 @@ module.exports = React.createClass({
         var redactButton;
         var cancelButton;
         var permalinkButton;
+        var unhidePreviewButton;
 
         if (eventStatus === 'not_sent') {
             resendButton = (
@@ -104,6 +114,18 @@ module.exports = React.createClass({
             </div>
         );
 
+
+        if (global.localStorage) {
+            // FIXME: factor this out with LinkPreviewWidget            
+            if (global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()) === "1") {
+                unhidePreviewButton = (
+                    <div className="mx_ContextualMenu_field" onClick={this.onUnhidePreviewClick}>
+                        Unhide Preview
+                    </div>
+                )
+            }
+        }
+
         // XXX: this should be https://matrix.to.
         // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
         permalinkButton = (
@@ -119,6 +141,7 @@ module.exports = React.createClass({
                 {redactButton}
                 {cancelButton}
                 {viewSourceButton}
+                {unhidePreviewButton}
                 {permalinkButton}
             </div>
         );
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
index 800883a153..8a92ae4ce2 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
@@ -33,6 +33,8 @@ limitations under the License.
 
 .mx_LinkPreviewWidget_caption {
     margin-left: 15px;
+    -webkit-flex: 1;
+    flex: 1;
 }
 
 .mx_LinkPreviewWidget_title {
@@ -48,3 +50,12 @@ limitations under the License.
     margin-top: 8px;
     white-space: normal;
 }
+
+.mx_LinkPreviewWidget_cancel {
+    visibility: hidden;
+    cursor: pointer;
+}
+
+.mx_LinkPreviewWidget:hover .mx_LinkPreviewWidget_cancel {
+    visibility: visible;
+}

From d414127f8062ba998789cbea3b86fa3456de4fd2 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Sun, 3 Apr 2016 23:31:42 +0100
Subject: [PATCH 05/11] track whether widget should be hidden on the event, as
 well as persisting it in localStorage

---
 src/components/views/rooms/MessageContextMenu.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/MessageContextMenu.js b/src/components/views/rooms/MessageContextMenu.js
index 24948f74f8..0d162a823c 100644
--- a/src/components/views/rooms/MessageContextMenu.js
+++ b/src/components/views/rooms/MessageContextMenu.js
@@ -71,8 +71,9 @@ module.exports = React.createClass({
             // FIXME: factor this out with LinkPreviewWidget
             // FIXME: somehow propagate this to the EventTile such that it updates itself and realises the link has rematerialised
             global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
-            if (this.props.onFinished) this.props.onFinished();
         }
+        this.props.mxEvent.widgetHidden = false;
+        if (this.props.onFinished) this.props.onFinished();
     },
 
     render: function() {
@@ -116,7 +117,7 @@ module.exports = React.createClass({
 
 
         if (global.localStorage) {
-            // FIXME: factor this out with LinkPreviewWidget            
+            // FIXME: factor this out with LinkPreviewWidget
             if (global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()) === "1") {
                 unhidePreviewButton = (
                     <div className="mx_ContextualMenu_field" onClick={this.onUnhidePreviewClick}>

From 333f1e46ca20af186a6a2243dbd7848f7f856498 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Sun, 3 Apr 2016 23:57:44 +0100
Subject: [PATCH 06/11] document properties and remove spurious 'view full
 screen' button

---
 src/components/views/elements/ImageView.js | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js
index db23e91b8f..a55e59e00b 100644
--- a/src/components/views/elements/ImageView.js
+++ b/src/components/views/elements/ImageView.js
@@ -27,6 +27,10 @@ module.exports = React.createClass({
     displayName: 'ImageView',
 
     propTypes: {
+        mxEvent: React.PropTypes.object,
+        src: React.PropTypes.string.isRequired,
+        width: React.PropTypes.number,
+        height: React.PropTypes.number,
         onFinished: React.PropTypes.func.isRequired,
         name: React.PropTypes.string
     },
@@ -164,11 +168,6 @@ module.exports = React.createClass({
                                          <span className="mx_ImageView_size">{ size } { res }</span>
                                 </div>
                             </a>
-                            <div className="mx_ImageView_button">
-                                <a className="mx_ImageView_link" href={ this.props.src } target="_blank">
-                                    View full screen
-                                </a>
-                            </div>
                             { eventRedact }
                             <div className="mx_ImageView_shim">
                             </div>

From d7eb23db53499244a350dd3247974f8ee05c9747 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Mon, 4 Apr 2016 00:16:52 +0100
Subject: [PATCH 07/11] specify sizes and hyperlinks for non-event images

---
 src/components/views/elements/ImageView.js | 26 ++++++++++++++++------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js
index a55e59e00b..361e18fd35 100644
--- a/src/components/views/elements/ImageView.js
+++ b/src/components/views/elements/ImageView.js
@@ -29,8 +29,10 @@ module.exports = React.createClass({
     propTypes: {
         mxEvent: React.PropTypes.object,
         src: React.PropTypes.string.isRequired,
+        link: React.PropTypes.string,
         width: React.PropTypes.number,
         height: React.PropTypes.number,
+        size: React.PropTypes.number,
         onFinished: React.PropTypes.func.isRequired,
         name: React.PropTypes.string
     },
@@ -76,11 +78,15 @@ module.exports = React.createClass({
         if(this.props.name) {
             name = this.props.name;
         } else if(this.props.mxEvent) {
-            name = props.mxEvent.getContent().body;
+            name = this.props.mxEvent.getContent().body;
         } else {
             name = null;
         }
 
+        if (name && this.props.link) {
+            name = <a href={ this.props.link } target="_blank">{ name }</a>;
+        }
+
         return name;
     },
 
@@ -121,14 +127,20 @@ module.exports = React.createClass({
                 width: this.props.width,
                 height: this.props.height,
             };
-            res = ", " + style.width + "x" + style.height + "px";
+            res = style.width + "x" + style.height + "px";
         }
 
         var size;
-        if (this.props.mxEvent &&
-            this.props.mxEvent.getContent().info &&
-            this.props.mxEvent.getContent().info.size) {
-            size = filesize(this.props.mxEvent.getContent().info.size);
+        if (this.props.size) {
+            size = filesize(this.props.size);
+        }
+
+        var size_res;
+        if (size && res) {
+            size_res = size + ", " + res;
+        }
+        else {
+            size_res = size || res;
         }
 
         var showEventMeta = !!this.props.mxEvent;
@@ -165,7 +177,7 @@ module.exports = React.createClass({
                             <a className="mx_ImageView_link" href={ this.props.src } target="_blank">
                                 <div className="mx_ImageView_download">
                                         Download this file<br/>
-                                         <span className="mx_ImageView_size">{ size } { res }</span>
+                                         <span className="mx_ImageView_size">{ size_res }</span>
                                 </div>
                             </a>
                             { eventRedact }

From 41373f30f7883ed101fad994d1152273d9740db1 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Mon, 4 Apr 2016 00:33:15 +0100
Subject: [PATCH 08/11] oops, name LinkPreviewWidget correctly

---
 .../views/rooms/{UrlPreviewWidget.css => LinkPreviewWidget.css}   | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename src/skins/vector/css/matrix-react-sdk/views/rooms/{UrlPreviewWidget.css => LinkPreviewWidget.css} (100%)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/LinkPreviewWidget.css
similarity index 100%
rename from src/skins/vector/css/matrix-react-sdk/views/rooms/UrlPreviewWidget.css
rename to src/skins/vector/css/matrix-react-sdk/views/rooms/LinkPreviewWidget.css

From d107151f8afa1be5629d1413e70603cb38025190 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Thu, 7 Apr 2016 18:09:50 +0100
Subject: [PATCH 09/11] rename `size` prop as `fileSize`, add comments, and
 honour explicit properties rather than mxEvent fields

---
 src/components/views/elements/ImageView.js | 35 ++++++++++------------
 1 file changed, 15 insertions(+), 20 deletions(-)

diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js
index 361e18fd35..38ca2e5827 100644
--- a/src/components/views/elements/ImageView.js
+++ b/src/components/views/elements/ImageView.js
@@ -27,14 +27,19 @@ module.exports = React.createClass({
     displayName: 'ImageView',
 
     propTypes: {
+        src: React.PropTypes.string.isRequired, // the source of the image being displayed
+        name: React.PropTypes.string, // the main title ('name') for the image
+        link: React.PropTypes.string, // the link (if any) applied to the name of the image
+        width: React.PropTypes.number, // width of the image src in pixels
+        height: React.PropTypes.number, // height of the image src in pixels
+        fileSize: React.PropTypes.number, // size of the image src in bytes
+        onFinished: React.PropTypes.func.isRequired, // callback when the lightbox is dismissed
+
+        // the event (if any) that the Image is displaying. Used for event-specific stuff like
+        // redactions, senders, timestamps etc.  Other descriptors are taken from the explicit
+        // properties above, which let us use lightboxes to display images which aren't associated
+        // with events.
         mxEvent: React.PropTypes.object,
-        src: React.PropTypes.string.isRequired,
-        link: React.PropTypes.string,
-        width: React.PropTypes.number,
-        height: React.PropTypes.number,
-        size: React.PropTypes.number,
-        onFinished: React.PropTypes.func.isRequired,
-        name: React.PropTypes.string
     },
 
     // XXX: keyboard shortcuts for managing dialogs should be done by the modal
@@ -73,20 +78,10 @@ module.exports = React.createClass({
     },
 
     getName: function () {
-        var name;
-
-        if(this.props.name) {
-            name = this.props.name;
-        } else if(this.props.mxEvent) {
-            name = this.props.mxEvent.getContent().body;
-        } else {
-            name = null;
-        }
-
+        var name = this.props.name;
         if (name && this.props.link) {
             name = <a href={ this.props.link } target="_blank">{ name }</a>;
         }
-
         return name;
     },
 
@@ -131,8 +126,8 @@ module.exports = React.createClass({
         }
 
         var size;
-        if (this.props.size) {
-            size = filesize(this.props.size);
+        if (this.props.fileSize) {
+            size = filesize(this.props.fileSize);
         }
 
         var size_res;

From cdc89c062346c80e184aa7ae0dbe5b4d9273a235 Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Fri, 8 Apr 2016 21:42:42 +0100
Subject: [PATCH 10/11] add the concept of eventTileOps for managing widget
 visibility based on vdh's PR feedback

---
 src/components/views/rooms/MessageContextMenu.js | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/MessageContextMenu.js b/src/components/views/rooms/MessageContextMenu.js
index 0d162a823c..1e220fccf0 100644
--- a/src/components/views/rooms/MessageContextMenu.js
+++ b/src/components/views/rooms/MessageContextMenu.js
@@ -27,6 +27,17 @@ var Resend = require("matrix-react-sdk/lib/Resend");
 module.exports = React.createClass({
     displayName: 'MessageContextMenu',
 
+    propTypes: {
+        /* the MatrixEvent associated with the context menu */
+        mxEvent: React.PropTypes.object.isRequired,
+
+        /* an optional EventTileOps implementation that can be used to unhide preview widgets */
+        eventTileOps: React.PropTypes.object,
+
+        /* callback called when the menu is dismissed */
+        onFinished: React.PropTypes.func,
+    },
+
     onResendClick: function() {
         Resend.resend(this.props.mxEvent);
         if (this.props.onFinished) this.props.onFinished();
@@ -69,10 +80,11 @@ module.exports = React.createClass({
     onUnhidePreviewClick: function() {
         if (global.localStorage) {
             // FIXME: factor this out with LinkPreviewWidget
-            // FIXME: somehow propagate this to the EventTile such that it updates itself and realises the link has rematerialised
             global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
         }
-        this.props.mxEvent.widgetHidden = false;
+        if (this.props.eventTileOps) {
+            this.props.eventTileOps.unhideWidget();
+        }
         if (this.props.onFinished) this.props.onFinished();
     },
 

From efd01d69290fbf398401b579703549b25ad3208f Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Mon, 11 Apr 2016 23:54:00 +0100
Subject: [PATCH 11/11] move localstorage crap entirely to TextualBody

---
 src/components/views/rooms/MessageContextMenu.js | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/src/components/views/rooms/MessageContextMenu.js b/src/components/views/rooms/MessageContextMenu.js
index 1e220fccf0..4eea46037c 100644
--- a/src/components/views/rooms/MessageContextMenu.js
+++ b/src/components/views/rooms/MessageContextMenu.js
@@ -78,10 +78,6 @@ module.exports = React.createClass({
     },
 
     onUnhidePreviewClick: function() {
-        if (global.localStorage) {
-            // FIXME: factor this out with LinkPreviewWidget
-            global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
-        }
         if (this.props.eventTileOps) {
             this.props.eventTileOps.unhideWidget();
         }
@@ -127,10 +123,8 @@ module.exports = React.createClass({
             </div>
         );
 
-
-        if (global.localStorage) {
-            // FIXME: factor this out with LinkPreviewWidget
-            if (global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()) === "1") {
+        if (this.props.eventTileOps) {
+            if (this.props.eventTileOps.isWidgetHidden()) {
                 unhidePreviewButton = (
                     <div className="mx_ContextualMenu_field" onClick={this.onUnhidePreviewClick}>
                         Unhide Preview