diff --git a/server/camlistored/ui/blob_item_foursquare_content.js b/server/camlistored/ui/blob_item_foursquare_content.js index 3d3ae35c7..8aaeb8d31 100644 --- a/server/camlistored/ui/blob_item_foursquare_content.js +++ b/server/camlistored/ui/blob_item_foursquare_content.js @@ -21,6 +21,7 @@ goog.require('goog.math.Size'); goog.require('goog.object'); goog.require('goog.string'); +goog.require('cam.dateUtils'); goog.require('cam.math'); goog.require('cam.permanodeUtils'); goog.require('cam.Thumber'); @@ -55,38 +56,10 @@ cam.BlobItemFoursquareContent = React.createClass({ ) ) ), - React.DOM.div({className:'cam-blobitem-fs-checkin-when'}, this.formatDate_()) + React.DOM.div({className:'cam-blobitem-fs-checkin-when'}, cam.dateUtils.formatDateShort(this.props.date)) ) ); }, - - formatDate_: function() { - var seconds = Math.floor((Date.now() - this.props.date) / 1000); - var interval = Math.floor(seconds / 31536000); - - return (function() { - if (interval > 1) { - return interval + ' years'; - } - interval = Math.floor(seconds / 2592000); - if (interval > 1) { - return interval + ' months'; - } - interval = Math.floor(seconds / 86400); - if (interval > 1) { - return interval + ' days'; - } - interval = Math.floor(seconds / 3600); - if (interval > 1) { - return interval + ' hours'; - } - interval = Math.floor(seconds / 60); - if (interval > 1) { - return interval + ' minutes'; - } - return Math.floor(seconds) + ' seconds'; - })() + ' ago'; - }, }); // Blech, we need this to prevent images from flashing when data changes server-side. diff --git a/server/camlistored/ui/blob_item_twitter.css b/server/camlistored/ui/blob_item_twitter.css new file mode 100644 index 000000000..a58eea73c --- /dev/null +++ b/server/camlistored/ui/blob_item_twitter.css @@ -0,0 +1,63 @@ +/* +Copyright 2014 The Camlistore Authors + +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. +*/ + +.cam-blobitem-twitter-tweet { + color: black; + display: block; + font-size: 90%; + position: relative; + overflow: hidden; + white-space: normal; + border-radius: 7%; +} + +.cam-blobitem-twitter-tweet table { + border-spacing: 0; + background-color: #e1e8ed; + width: 100%; + height: 100%; +} + +.cam-blobitem-twitter-tweet-icon { + position: absolute; + width: 100%; + bottom: 0; +} + +.cam-blobitem-twitter-tweet-icon img { + width: 4em; + height: 4em; + position: absolute; + bottom: 1em; + right: 1em; + opacity: 1; +} + +.cam-blobitem-twitter-tweet-meta { + text-align: left; + vertical-align: top; + padding: 0.8em; +} + +.cam-blobitem-twitter-tweet-date { + color: #aaa; +} + +.cam-blobitem-twitter-tweet-image { + background-position: top; + background-size: cover; + height: 100%; +} diff --git a/server/camlistored/ui/blob_item_twitter_content.js b/server/camlistored/ui/blob_item_twitter_content.js new file mode 100644 index 000000000..60f24453c --- /dev/null +++ b/server/camlistored/ui/blob_item_twitter_content.js @@ -0,0 +1,122 @@ +/* +Copyright 2014 The Camlistore Authors + +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. +*/ + +goog.provide('cam.BlobItemTwitterContent'); + +goog.require('goog.math.Size'); + +goog.require('cam.dateUtils'); +goog.require('cam.math'); +goog.require('cam.permanodeUtils'); +goog.require('cam.Thumber'); + +cam.BlobItemTwitterContent = React.createClass({ + propTypes: { + date: React.PropTypes.number.isRequired, + href: React.PropTypes.string.isRequired, + image: React.PropTypes.string, + size: React.PropTypes.instanceOf(goog.math.Size).isRequired, + username: React.PropTypes.string.isRequired, + }, + + getImageRow_: function() { + if (!this.props.image) { + return null; + } + + return React.DOM.tr(null, + React.DOM.td({ + className: 'cam-blobitem-twitter-tweet-image', + colSpan: 2, + src: 'twitter-icon.png', + style: { + backgroundImage: 'url(' + this.props.image + ')', + }, + }) + ); + }, + + render: function() { + return React.DOM.a({ + href: this.props.href, + className: 'cam-blobitem-twitter-tweet', + style: { + width: this.props.size.width, + height: this.props.size.height, + }, + }, + React.DOM.table({height: this.props.image ? '100%' : ''}, + React.DOM.tr(null, + React.DOM.td({className: 'cam-blobitem-twitter-tweet-meta'}, + React.DOM.span({className: 'cam-blobitem-twitter-tweet-date'}, cam.dateUtils.formatDateShort(this.props.date)), + React.DOM.br(), + React.DOM.span({className: ' cam-blobitem-twitter-tweet-content'}, this.props.content) + ) + ), + this.getImageRow_(), + React.DOM.tr(null, + React.DOM.td({className: 'cam-blobitem-twitter-tweet-icon'}, + React.DOM.img({src: 'twitter-logo.png'}) + ) + ) + ) + ); + }, +}); + +cam.BlobItemTwitterContent.getHandler = function(blobref, searchSession, href) { + var m = searchSession.getMeta(blobref); + if (m.camliType != 'permanode') { + return null; + } + + if (cam.permanodeUtils.getSingleAttr(m.permanode, 'camliNodeType') != 'twitter.com:tweet') { + return null; + } + + var content = cam.permanodeUtils.getSingleAttr(m.permanode, 'content'); + var date = cam.permanodeUtils.getSingleAttr(m.permanode, 'startDate'); + var username = cam.permanodeUtils.getSingleAttr(m.permanode, 'url').match(/^https:\/\/twitter.com\/(.+?)\//)[1]; + var imageMeta = cam.permanodeUtils.getSingleAttr(m.permanode, 'camliContentImage'); + if (imageMeta) { + imageMeta = searchSession.getResolvedMeta(imageMeta); + } + + return new cam.BlobItemTwitterContent.Handler(content, Date.parse(date), href, imageMeta, username); +}; + +cam.BlobItemTwitterContent.Handler = function(content, date, href, imageMeta, username) { + this.content_ = content; + this.date_ = date; + this.href_ = href; + this.username_ = username; + this.thumber_ = imageMeta ? new cam.Thumber.fromImageMeta(imageMeta) : null; +}; + +cam.BlobItemTwitterContent.Handler.prototype.getAspectRatio = function() { + return 1.0; +}; + +cam.BlobItemTwitterContent.Handler.prototype.createContent = function(size) { + return cam.BlobItemTwitterContent({ + content: this.content_, + date: this.date_, + href: this.href_, + image: this.thumber_ ? this.thumber_.getSrc(size) : null, + size: size, + username: this.username_, + }); +}; diff --git a/server/camlistored/ui/date_utils.js b/server/camlistored/ui/date_utils.js new file mode 100644 index 000000000..7dc839d57 --- /dev/null +++ b/server/camlistored/ui/date_utils.js @@ -0,0 +1,47 @@ +/* +Copyright 2014 The Camlistore Authors + +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. +*/ + +goog.provide('cam.dateUtils'); + +cam.dateUtils.formatDateShort = function(date) { + // TODO(aa): Do something better based on Closure date/i18n utils. + // I think I would prefer this to return (in en-us) either '11:18 PM', 'Jun 11', or 'June 11 1952', depending on how far back it is. I don't find '5 hours ago' that useful. + var seconds = Math.floor((Date.now() - date) / 1000); + var interval = Math.floor(seconds / 31536000); + + return (function() { + if (interval > 1) { + return interval + ' years'; + } + interval = Math.floor(seconds / 2592000); + if (interval > 1) { + return interval + ' months'; + } + interval = Math.floor(seconds / 86400); + if (interval > 1) { + return interval + ' days'; + } + interval = Math.floor(seconds / 3600); + if (interval > 1) { + return interval + ' hours'; + } + interval = Math.floor(seconds / 60); + if (interval > 1) { + return interval + ' minutes'; + } + return Math.floor(seconds) + ' seconds'; + })() + ' ago'; +}; diff --git a/server/camlistored/ui/index.html b/server/camlistored/ui/index.html index 6a279a1ca..4f761c557 100644 --- a/server/camlistored/ui/index.html +++ b/server/camlistored/ui/index.html @@ -40,6 +40,7 @@ limitations under the License. + diff --git a/server/camlistored/ui/index.js b/server/camlistored/ui/index.js index c7b72af41..7ca17fc7d 100644 --- a/server/camlistored/ui/index.js +++ b/server/camlistored/ui/index.js @@ -26,11 +26,12 @@ goog.require('goog.Uri'); goog.require('cam.BlobDetail'); goog.require('cam.BlobItemContainerReact'); +goog.require('cam.BlobItemDemoContent'); goog.require('cam.BlobItemFoursquareContent'); goog.require('cam.BlobItemGenericContent'); -goog.require('cam.BlobItemVideoContent'); goog.require('cam.BlobItemImageContent'); -goog.require('cam.BlobItemDemoContent'); +goog.require('cam.BlobItemTwitterContent'); +goog.require('cam.BlobItemVideoContent'); goog.require('cam.ContainerDetail'); goog.require('cam.DetailView'); goog.require('cam.DirectoryDetail'); @@ -53,12 +54,14 @@ cam.IndexPage = React.createClass({ RAW: 'raw' }, + // Note that these are ordered by priority. BLOB_ITEM_HANDLERS_: [ cam.BlobItemDemoContent.getHandler, cam.BlobItemFoursquareContent.getHandler, + cam.BlobItemTwitterContent.getHandler, cam.BlobItemImageContent.getHandler, cam.BlobItemVideoContent.getHandler, - cam.BlobItemGenericContent.getHandler // must be last + cam.BlobItemGenericContent.getHandler ], propTypes: { diff --git a/server/camlistored/ui/twitter-logo.png b/server/camlistored/ui/twitter-logo.png new file mode 100644 index 000000000..1b1562d43 Binary files /dev/null and b/server/camlistored/ui/twitter-logo.png differ