commit
						f9c7ae1ab9
					
				|  | @ -0,0 +1,57 @@ | |||
| /* | ||||
| 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. | ||||
| */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports = { | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the actual height that an image of dimensions (fullWidth, fullHeight) | ||||
|      * will occupy if resized to fit inside a thumbnail bounding box of size | ||||
|      * (thumbWidth, thumbHeight). | ||||
|      * | ||||
|      * If the aspect ratio of the source image is taller than the aspect ratio of | ||||
|      * the thumbnail bounding box, then we return the thumbHeight parameter unchanged. | ||||
|      * Otherwise we return the thumbHeight parameter scaled down appropriately to | ||||
|      * reflect the actual height the scaled thumbnail occupies. | ||||
|      * | ||||
|      * This is very useful for calculating how much height a thumbnail will actually | ||||
|      * consume in the timeline, when performing scroll offset calcuations | ||||
|      * (e.g. scroll locking) | ||||
|      */ | ||||
|     thumbHeight: 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 Math.floor(widthMulti * fullHeight); | ||||
|         } | ||||
|         else { | ||||
|             // height is the dominant dimension so scaling will be fixed on that
 | ||||
|             return Math.floor(heightMulti * fullHeight); | ||||
|         } | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
|  | @ -26,10 +26,6 @@ limitations under the License. | |||
| 
 | ||||
| module.exports.components = {}; | ||||
| module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom'); | ||||
| module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword'); | ||||
| module.exports.components['structures.login.Login'] = require('./components/structures/login/Login'); | ||||
| module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration'); | ||||
| module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration'); | ||||
| module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat'); | ||||
| module.exports.components['structures.MessagePanel'] = require('./components/structures/MessagePanel'); | ||||
| module.exports.components['structures.RoomStatusBar'] = require('./components/structures/RoomStatusBar'); | ||||
|  | @ -38,6 +34,10 @@ module.exports.components['structures.ScrollPanel'] = require('./components/stru | |||
| module.exports.components['structures.TimelinePanel'] = require('./components/structures/TimelinePanel'); | ||||
| module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar'); | ||||
| module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings'); | ||||
| module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword'); | ||||
| module.exports.components['structures.login.Login'] = require('./components/structures/login/Login'); | ||||
| module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration'); | ||||
| module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration'); | ||||
| module.exports.components['views.avatars.BaseAvatar'] = require('./components/views/avatars/BaseAvatar'); | ||||
| module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar'); | ||||
| module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar'); | ||||
|  | @ -64,10 +64,10 @@ module.exports.components['views.login.LoginHeader'] = require('./components/vie | |||
| module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin'); | ||||
| module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm'); | ||||
| module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig'); | ||||
| module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent'); | ||||
| module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody'); | ||||
| module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody'); | ||||
| module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody'); | ||||
| module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent'); | ||||
| module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody'); | ||||
| module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent'); | ||||
| module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody'); | ||||
|  | @ -77,6 +77,7 @@ module.exports.components['views.rooms.AuxPanel'] = require('./components/views/ | |||
| module.exports.components['views.rooms.EntityTile'] = require('./components/views/rooms/EntityTile'); | ||||
| module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile'); | ||||
| module.exports.components['views.rooms.InviteMemberList'] = require('./components/views/rooms/InviteMemberList'); | ||||
| module.exports.components['views.rooms.LinkPreviewWidget'] = require('./components/views/rooms/LinkPreviewWidget'); | ||||
| module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo'); | ||||
| module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList'); | ||||
| module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile'); | ||||
|  | @ -90,8 +91,8 @@ module.exports.components['views.rooms.RoomPreviewBar'] = require('./components/ | |||
| module.exports.components['views.rooms.RoomSettings'] = require('./components/views/rooms/RoomSettings'); | ||||
| module.exports.components['views.rooms.RoomTile'] = require('./components/views/rooms/RoomTile'); | ||||
| module.exports.components['views.rooms.RoomTopicEditor'] = require('./components/views/rooms/RoomTopicEditor'); | ||||
| module.exports.components['views.rooms.SearchableEntityList'] = require('./components/views/rooms/SearchableEntityList'); | ||||
| module.exports.components['views.rooms.SearchResultTile'] = require('./components/views/rooms/SearchResultTile'); | ||||
| module.exports.components['views.rooms.SearchableEntityList'] = require('./components/views/rooms/SearchableEntityList'); | ||||
| module.exports.components['views.rooms.SimpleRoomHeader'] = require('./components/views/rooms/SimpleRoomHeader'); | ||||
| module.exports.components['views.rooms.TabCompleteBar'] = require('./components/views/rooms/TabCompleteBar'); | ||||
| module.exports.components['views.rooms.TopUnreadMessagesBar'] = require('./components/views/rooms/TopUnreadMessagesBar'); | ||||
|  |  | |||
|  | @ -337,6 +337,7 @@ module.exports = React.createClass({ | |||
|                         ref={this._collectEventNode.bind(this, eventId)} | ||||
|                         data-scroll-token={scrollToken}> | ||||
|                     <EventTile mxEvent={mxEv} continuation={continuation} | ||||
|                         onWidgetLoad={this._onWidgetLoad} | ||||
|                         last={last} isSelectedEvent={highlight} /> | ||||
|                 </li> | ||||
|         ); | ||||
|  | @ -398,6 +399,15 @@ module.exports = React.createClass({ | |||
|         this.eventNodes[eventId] = node; | ||||
|     }, | ||||
| 
 | ||||
|     // once dynamic content in the events load, make the scrollPanel check the
 | ||||
|     // scroll offsets.
 | ||||
|     _onWidgetLoad: function() { | ||||
|         var scrollPanel = this.refs.scrollPanel; | ||||
|         if (scrollPanel) { | ||||
|             scrollPanel.forceUpdate(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onResize: function() { | ||||
|         dis.dispatch({ action: 'timeline_resize' }, true); | ||||
|     }, | ||||
|  |  | |||
|  | @ -798,9 +798,9 @@ module.exports = React.createClass({ | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // once images in the search results load, make the scrollPanel check
 | ||||
|         // once dynamic content in the search results load, make the scrollPanel check
 | ||||
|         // the scroll offsets.
 | ||||
|         var onImageLoad = () => { | ||||
|         var onWidgetLoad = () => { | ||||
|             var scrollPanel = this.refs.searchResultsPanel; | ||||
|             if (scrollPanel) { | ||||
|                 scrollPanel.checkScroll(); | ||||
|  | @ -844,7 +844,7 @@ module.exports = React.createClass({ | |||
|                      searchResult={result} | ||||
|                      searchHighlights={this.state.searchHighlights} | ||||
|                      resultLink={resultLink} | ||||
|                      onImageLoad={onImageLoad}/>); | ||||
|                      onWidgetLoad={onWidgetLoad}/>); | ||||
|         } | ||||
|         return ret; | ||||
|     }, | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ var React = require('react'); | |||
| var filesize = require('filesize'); | ||||
| 
 | ||||
| var MatrixClientPeg = require('../../../MatrixClientPeg'); | ||||
| var ImageUtils = require('../../../ImageUtils'); | ||||
| var Modal = require('../../../Modal'); | ||||
| var sdk = require('../../../index'); | ||||
| var dis = require("../../../dispatcher"); | ||||
|  | @ -30,31 +31,6 @@ module.exports = React.createClass({ | |||
|     propTypes: { | ||||
|         /* the MatrixEvent to show */ | ||||
|         mxEvent: React.PropTypes.object.isRequired, | ||||
| 
 | ||||
|         /* callback called when images in events are loaded */ | ||||
|         onImageLoad: React.PropTypes.func, | ||||
|     }, | ||||
| 
 | ||||
|     thumbHeight: 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 Math.floor(widthMulti * fullHeight); | ||||
|         } | ||||
|         else { | ||||
|             // height is the dominant dimension so scaling will be fixed on that
 | ||||
|             return Math.floor(heightMulti * fullHeight); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onClick: function onClick(ev) { | ||||
|  | @ -71,6 +47,7 @@ module.exports = React.createClass({ | |||
|             if (content.info) { | ||||
|                 params.width = content.info.w; | ||||
|                 params.height = content.info.h; | ||||
|                 params.fileSize = content.info.size; | ||||
|             } | ||||
| 
 | ||||
|             Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); | ||||
|  | @ -134,7 +111,9 @@ module.exports = React.createClass({ | |||
|         // the alternative here would be 600*timelineWidth/800; to scale them down to fit inside a 4:3 bounding box
 | ||||
| 
 | ||||
|         //console.log("trying to fit image into timelineWidth of " + this.refs.body.offsetWidth + " or " + this.refs.body.clientWidth);
 | ||||
|         if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight); | ||||
|         if (content.info) { | ||||
|             thumbHeight = ImageUtils.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight); | ||||
|         } | ||||
|         this.refs.image.style.height = thumbHeight + "px"; | ||||
|         // console.log("Image height now", thumbHeight);
 | ||||
|     }, | ||||
|  | @ -152,8 +131,7 @@ module.exports = React.createClass({ | |||
|                         <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image" | ||||
|                             alt={content.body} | ||||
|                             onMouseEnter={this.onImageEnter} | ||||
|                             onMouseLeave={this.onImageLeave} | ||||
|                             onLoad={this.props.onImageLoad} /> | ||||
|                             onMouseLeave={this.onImageLeave} /> | ||||
|                     </a> | ||||
|                     <div className="mx_MImageBody_download"> | ||||
|                         <a href={cli.mxcUrlToHttp(content.url)} target="_blank"> | ||||
|  |  | |||
|  | @ -38,15 +38,18 @@ module.exports = React.createClass({ | |||
|         /* link URL for the highlights */ | ||||
|         highlightLink: React.PropTypes.string, | ||||
| 
 | ||||
|         /* callback called when images in events are loaded */ | ||||
|         onImageLoad: React.PropTypes.func, | ||||
|         /* callback called when dynamic content in events are loaded */ | ||||
|         onWidgetLoad: React.PropTypes.func, | ||||
|     }, | ||||
| 
 | ||||
|     getEventTileOps: function() { | ||||
|         return this.refs.body ? this.refs.body.getEventTileOps() : null; | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         var UnknownMessageTile = sdk.getComponent('messages.UnknownBody'); | ||||
|         var UnknownBody = sdk.getComponent('messages.UnknownBody'); | ||||
| 
 | ||||
|         var tileTypes = { | ||||
|         var bodyTypes = { | ||||
|             'm.text': sdk.getComponent('messages.TextualBody'), | ||||
|             'm.notice': sdk.getComponent('messages.TextualBody'), | ||||
|             'm.emote': sdk.getComponent('messages.TextualBody'), | ||||
|  | @ -57,13 +60,13 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         var content = this.props.mxEvent.getContent(); | ||||
|         var msgtype = content.msgtype; | ||||
|         var TileType = UnknownMessageTile; | ||||
|         if (msgtype && tileTypes[msgtype]) { | ||||
|             TileType = tileTypes[msgtype]; | ||||
|         var BodyType = UnknownBody; | ||||
|         if (msgtype && bodyTypes[msgtype]) { | ||||
|             BodyType = bodyTypes[msgtype]; | ||||
|         } | ||||
| 
 | ||||
|         return <TileType mxEvent={this.props.mxEvent} highlights={this.props.highlights}  | ||||
|         return <BodyType ref="body" mxEvent={this.props.mxEvent} highlights={this.props.highlights} | ||||
|                     highlightLink={this.props.highlightLink} | ||||
|                     onImageLoad={this.props.onImageLoad} />; | ||||
|                     onWidgetLoad={this.props.onWidgetLoad} />; | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ var HtmlUtils = require('../../../HtmlUtils'); | |||
| var linkify = require('linkifyjs'); | ||||
| var linkifyElement = require('linkifyjs/element'); | ||||
| var linkifyMatrix = require('../../../linkify-matrix'); | ||||
| var sdk = require('../../../index'); | ||||
| 
 | ||||
| linkifyMatrix(linkify); | ||||
| 
 | ||||
|  | @ -37,28 +38,84 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         /* link URL for the highlights */ | ||||
|         highlightLink: React.PropTypes.string, | ||||
| 
 | ||||
|         /* callback for when our widget has loaded */ | ||||
|         onWidgetLoad: React.PropTypes.func, | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|         return { | ||||
|             // the URL (if any) to be previewed with a LinkPreviewWidget
 | ||||
|             // inside this TextualBody.
 | ||||
|             link: null, | ||||
| 
 | ||||
|             // track whether the preview widget is hidden
 | ||||
|             widgetHidden: false, | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|         linkifyElement(this.refs.content, linkifyMatrix.options); | ||||
| 
 | ||||
|         if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") | ||||
|             HtmlUtils.highlightDom(ReactDOM.findDOMNode(this)); | ||||
|     }, | ||||
|         var link = this.findLink(this.refs.content.children); | ||||
|         if (link) { | ||||
|             this.setState({ link: link.getAttribute("href") }); | ||||
| 
 | ||||
|     componentDidUpdate: function() { | ||||
|         // XXX: why don't we linkify here?
 | ||||
|         // XXX: why do we bother doing this on update at all, given events are immutable?
 | ||||
|             // lazy-load the hidden state of the preview widget from localstorage
 | ||||
|             if (global.localStorage) { | ||||
|                 var hidden = global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()); | ||||
|                 this.setState({ widgetHidden: hidden }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") | ||||
|             HtmlUtils.highlightDom(ReactDOM.findDOMNode(this)); | ||||
|     }, | ||||
| 
 | ||||
|     shouldComponentUpdate: function(nextProps) { | ||||
|     shouldComponentUpdate: function(nextProps, nextState) { | ||||
|         // exploit that events are immutable :)
 | ||||
|         return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() || | ||||
|                 nextProps.highlights !== this.props.highlights || | ||||
|                 nextProps.highlightLink !== this.props.highlightLink); | ||||
|                 nextProps.highlightLink !== this.props.highlightLink || | ||||
|                 nextState.link !== this.state.link || | ||||
|                 nextState.widgetHidden !== this.state.widgetHidden); | ||||
|     }, | ||||
| 
 | ||||
|     findLink: function(nodes) { | ||||
|         for (var i = 0; i < nodes.length; i++) { | ||||
|             var node = nodes[i]; | ||||
|             if (node.tagName === "A" && node.getAttribute("href")) { | ||||
|                 return node; | ||||
|             } | ||||
|             else if (node.children && node.children.length) { | ||||
|                 return this.findLink(node.children) | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onCancelClick: function(event) { | ||||
|         this.setState({ widgetHidden: true }); | ||||
|         // FIXME: persist this somewhere smarter than local storage
 | ||||
|         if (global.localStorage) { | ||||
|             global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1"); | ||||
|         } | ||||
|         this.forceUpdate(); | ||||
|     }, | ||||
| 
 | ||||
|     getEventTileOps: function() { | ||||
|         var self = this; | ||||
|         return { | ||||
|             isWidgetHidden: function() { | ||||
|                 return self.state.widgetHidden; | ||||
|             }, | ||||
| 
 | ||||
|             unhideWidget: function() { | ||||
|                 self.setState({ widgetHidden: false }); | ||||
|                 if (global.localStorage) { | ||||
|                     global.localStorage.removeItem("hide_preview_" + self.props.mxEvent.getId()); | ||||
|                 } | ||||
|             }, | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|  | @ -67,24 +124,38 @@ module.exports = React.createClass({ | |||
|         var body = HtmlUtils.bodyToHtml(content, this.props.highlights, | ||||
|                                        {highlightLink: this.props.highlightLink}); | ||||
| 
 | ||||
| 
 | ||||
|         var widget; | ||||
|         if (this.state.link && !this.state.widgetHidden) { | ||||
|             var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); | ||||
|             widget = <LinkPreviewWidget | ||||
|                 link={ this.state.link } | ||||
|                 mxEvent={ this.props.mxEvent } | ||||
|                 onCancelClick={ this.onCancelClick } | ||||
|                 onWidgetLoad={ this.props.onWidgetLoad }/>; | ||||
|         } | ||||
| 
 | ||||
|         switch (content.msgtype) { | ||||
|             case "m.emote": | ||||
|                 var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); | ||||
|                 return ( | ||||
|                     <span ref="content" className="mx_MEmoteBody mx_EventTile_content"> | ||||
|                         * { name } { body } | ||||
|                         { widget } | ||||
|                     </span> | ||||
|                 ); | ||||
|             case "m.notice": | ||||
|                 return ( | ||||
|                     <span ref="content" className="mx_MNoticeBody mx_EventTile_content"> | ||||
|                         { body } | ||||
|                         { widget } | ||||
|                     </span> | ||||
|                 ); | ||||
|             default: // including "m.text"
 | ||||
|                 return ( | ||||
|                     <span ref="content" className="mx_MTextBody mx_EventTile_content"> | ||||
|                         { body } | ||||
|                         { widget } | ||||
|                     </span> | ||||
|                 ); | ||||
|         } | ||||
|  |  | |||
|  | @ -105,8 +105,8 @@ module.exports = React.createClass({ | |||
|         /* is this the focused event */ | ||||
|         isSelectedEvent: React.PropTypes.bool, | ||||
| 
 | ||||
|         /* callback called when images in events are loaded */ | ||||
|         onImageLoad: React.PropTypes.func, | ||||
|         /* callback called when dynamic content in events are loaded */ | ||||
|         onWidgetLoad: React.PropTypes.func, | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|  | @ -123,7 +123,7 @@ module.exports = React.createClass({ | |||
|         { | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         return actions.tweaks.highlight; | ||||
|     }, | ||||
| 
 | ||||
|  | @ -137,6 +137,7 @@ module.exports = React.createClass({ | |||
|             mxEvent: this.props.mxEvent, | ||||
|             left: x, | ||||
|             top: y, | ||||
|             eventTileOps: this.refs.tile ? this.refs.tile.getEventTileOps() : undefined, | ||||
|             onFinished: function() { | ||||
|                 self.setState({menu: false}); | ||||
|             } | ||||
|  | @ -343,9 +344,9 @@ module.exports = React.createClass({ | |||
|                 { avatar } | ||||
|                 { sender } | ||||
|                 <div className="mx_EventTile_line"> | ||||
|                     <EventTileType mxEvent={this.props.mxEvent} highlights={this.props.highlights} | ||||
|                     <EventTileType ref="tile" mxEvent={this.props.mxEvent} highlights={this.props.highlights} | ||||
|                           highlightLink={this.props.highlightLink} | ||||
|                           onImageLoad={this.props.onImageLoad} /> | ||||
|                           onWidgetLoad={this.props.onWidgetLoad} /> | ||||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
|  |  | |||
|  | @ -0,0 +1,129 @@ | |||
| /* | ||||
| Copyright 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. | ||||
| */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| var React = require('react'); | ||||
| 
 | ||||
| var sdk = require('../../../index'); | ||||
| var MatrixClientPeg = require('../../../MatrixClientPeg'); | ||||
| var ImageUtils = require('../../../ImageUtils'); | ||||
| var Modal = require('../../../Modal'); | ||||
| 
 | ||||
| var linkify = require('linkifyjs'); | ||||
| var linkifyElement = require('linkifyjs/element'); | ||||
| var linkifyMatrix = require('../../../linkify-matrix'); | ||||
| linkifyMatrix(linkify); | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'LinkPreviewWidget', | ||||
| 
 | ||||
|     propTypes: { | ||||
|         link: React.PropTypes.string.isRequired, // the URL being previewed
 | ||||
|         mxEvent: React.PropTypes.object.isRequired, // the Event associated with the preview
 | ||||
|         onCancelClick: React.PropTypes.func, // called when the preview's cancel ('hide') button is clicked
 | ||||
|         onWidgetLoad: React.PropTypes.func, // called when the preview's contents has loaded
 | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|         return { | ||||
|             preview: null | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     componentWillMount: function() { | ||||
|         MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{ | ||||
|             this.setState( | ||||
|                 { preview: res }, | ||||
|                 this.props.onWidgetLoad | ||||
|             ); | ||||
|         }, (error)=>{ | ||||
|             console.error("Failed to get preview for " + this.props.link + " " + error); | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|         if (this.refs.description) | ||||
|             linkifyElement(this.refs.description, linkifyMatrix.options); | ||||
|     }, | ||||
| 
 | ||||
|     componentDidUpdate: function() { | ||||
|         if (this.refs.description) | ||||
|             linkifyElement(this.refs.description, linkifyMatrix.options); | ||||
|     }, | ||||
| 
 | ||||
|     onImageClick: function(ev) { | ||||
|         var p = this.state.preview; | ||||
|         if (ev.button != 0 || ev.metaKey) return; | ||||
|         ev.preventDefault(); | ||||
|         var ImageView = sdk.getComponent("elements.ImageView"); | ||||
| 
 | ||||
|         var src = p["og:image"]; | ||||
|         if (src && src.startsWith("mxc://")) { | ||||
|             src = MatrixClientPeg.get().mxcUrlToHttp(src); | ||||
|         } | ||||
| 
 | ||||
|         var params = { | ||||
|             src: src, | ||||
|             width: p["og:image:width"], | ||||
|             height: p["og:image:height"], | ||||
|             name: p["og:title"] || p["og:description"] || this.props.link, | ||||
|             fileSize: p["matrix:image:size"], | ||||
|             link: this.props.link, | ||||
|         }; | ||||
| 
 | ||||
|         Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         var p = this.state.preview; | ||||
|         if (!p) return <div/>; | ||||
| 
 | ||||
|         // FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
 | ||||
|         var image = p["og:image"]; | ||||
|         var imageMaxWidth = 100, imageMaxHeight = 100; | ||||
|         if (image && image.startsWith("mxc://")) { | ||||
|             image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight); | ||||
|         } | ||||
| 
 | ||||
|         var thumbHeight = imageMaxHeight; | ||||
|         if (p["og:image:width"] && p["og:image:height"]) { | ||||
|             thumbHeight = ImageUtils.thumbHeight(p["og:image:width"], p["og:image:height"], imageMaxWidth, imageMaxHeight); | ||||
|         } | ||||
| 
 | ||||
|         var img; | ||||
|         if (image) { | ||||
|             img = <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}> | ||||
|                     <img style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={ image } onClick={ this.onImageClick }/> | ||||
|                   </div> | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <div className="mx_LinkPreviewWidget" > | ||||
|                 { img } | ||||
|                 <div className="mx_LinkPreviewWidget_caption"> | ||||
|                     <div className="mx_LinkPreviewWidget_title"><a href={ this.props.link } target="_blank">{ p["og:title"] }</a></div> | ||||
|                     <div className="mx_LinkPreviewWidget_siteName">{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }</div> | ||||
|                     <div className="mx_LinkPreviewWidget_description" ref="description"> | ||||
|                         { p["og:description"] } | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <img className="mx_LinkPreviewWidget_cancel" src="img/cancel.svg" width="18" height="18" | ||||
|                      onClick={ this.props.onCancelClick }/> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
| }); | ||||
|  | @ -451,7 +451,7 @@ module.exports = React.createClass({ | |||
|     onMemberAvatarClick: function () { | ||||
|         var avatarUrl = this.props.member.user.avatarUrl; | ||||
|         if(!avatarUrl) return; | ||||
|          | ||||
| 
 | ||||
|         var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(avatarUrl); | ||||
|         var ImageView = sdk.getComponent("elements.ImageView"); | ||||
|         var params = { | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ module.exports = React.createClass({ | |||
|         // href for the highlights in this result
 | ||||
|         resultLink: React.PropTypes.string, | ||||
| 
 | ||||
|         onImageLoad: React.PropTypes.func, | ||||
|         onWidgetLoad: React.PropTypes.func, | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|  | @ -56,7 +56,7 @@ module.exports = React.createClass({ | |||
|             if (EventTile.haveTileForEvent(ev)) { | ||||
|                 ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights} | ||||
|                           highlightLink={this.props.resultLink} | ||||
|                           onImageLoad={this.props.onImageLoad} />); | ||||
|                           onWidgetLoad={this.props.onWidgetLoad} />); | ||||
|             } | ||||
|         } | ||||
|         return ( | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Matthew Hodgson
						Matthew Hodgson