opengraph POC
Fixed small-frame opengraph view Fixed incompletely saved OpenGraphCache bug
This commit is contained in:
parent
9d6ac1abe5
commit
176c6826e0
19 changed files with 200 additions and 6 deletions
1
Gemfile
1
Gemfile
|
|
@ -64,6 +64,7 @@ gem 'rails_autolink', '1.1.0'
|
||||||
gem 'redcarpet', '3.0.0'
|
gem 'redcarpet', '3.0.0'
|
||||||
gem 'roxml', '3.1.6'
|
gem 'roxml', '3.1.6'
|
||||||
gem 'ruby-oembed', '0.8.8'
|
gem 'ruby-oembed', '0.8.8'
|
||||||
|
gem 'opengraph', '0.0.4'
|
||||||
|
|
||||||
|
|
||||||
# Please remove when migrating to Rails 4
|
# Please remove when migrating to Rails 4
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,10 @@ GEM
|
||||||
omniauth-twitter (1.0.0)
|
omniauth-twitter (1.0.0)
|
||||||
multi_json (~> 1.3)
|
multi_json (~> 1.3)
|
||||||
omniauth-oauth (~> 1.0)
|
omniauth-oauth (~> 1.0)
|
||||||
|
opengraph (0.0.4)
|
||||||
|
hashie
|
||||||
|
nokogiri (~> 1.5.0)
|
||||||
|
rest-client (~> 1.6.0)
|
||||||
orm_adapter (0.4.0)
|
orm_adapter (0.4.0)
|
||||||
polyglot (0.3.3)
|
polyglot (0.3.3)
|
||||||
pry (0.9.12.2)
|
pry (0.9.12.2)
|
||||||
|
|
@ -322,6 +326,8 @@ GEM
|
||||||
redis-namespace (1.3.0)
|
redis-namespace (1.3.0)
|
||||||
redis (~> 3.0.0)
|
redis (~> 3.0.0)
|
||||||
remotipart (1.2.1)
|
remotipart (1.2.1)
|
||||||
|
rest-client (1.6.7)
|
||||||
|
mime-types (>= 1.16)
|
||||||
rmagick (2.13.2)
|
rmagick (2.13.2)
|
||||||
roxml (3.1.6)
|
roxml (3.1.6)
|
||||||
activesupport (>= 2.3.0)
|
activesupport (>= 2.3.0)
|
||||||
|
|
@ -464,6 +470,7 @@ DEPENDENCIES
|
||||||
omniauth-facebook (= 1.4.1)
|
omniauth-facebook (= 1.4.1)
|
||||||
omniauth-tumblr (= 1.1)
|
omniauth-tumblr (= 1.1)
|
||||||
omniauth-twitter (= 1.0.0)
|
omniauth-twitter (= 1.0.0)
|
||||||
|
opengraph (= 0.0.4)
|
||||||
rack-cors (= 0.2.8)
|
rack-cors (= 0.2.8)
|
||||||
rack-google-analytics (= 0.11.0)
|
rack-google-analytics (= 0.11.0)
|
||||||
rack-piwik (= 0.2.2)
|
rack-piwik (= 0.2.2)
|
||||||
|
|
|
||||||
8
app/assets/javascripts/app/helpers/open_graph.js
Normal file
8
app/assets/javascripts/app/helpers/open_graph.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
(function(){
|
||||||
|
app.helpers.openGraph = {
|
||||||
|
html : function (open_graph_cache) {
|
||||||
|
if (!open_graph_cache) { return "" }
|
||||||
|
return '<img src="' + open_graph_cache.image + '" />'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -44,9 +44,13 @@ app.views.Content = app.views.Base.extend({
|
||||||
var collHeight = 200
|
var collHeight = 200
|
||||||
, elem = this.$(".collapsible")
|
, elem = this.$(".collapsible")
|
||||||
, oembed = elem.find(".oembed")
|
, oembed = elem.find(".oembed")
|
||||||
|
, opengraph = elem.find(".opengraph")
|
||||||
, addHeight = 0;
|
, addHeight = 0;
|
||||||
if($.trim(oembed.html()) != "") {
|
if($.trim(oembed.html()) != "") {
|
||||||
addHeight = oembed.height();
|
addHeight += oembed.height();
|
||||||
|
}
|
||||||
|
if($.trim(opengraph.html()) != "") {
|
||||||
|
addHeight += opengraph.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
// only collapse if height exceeds collHeight+20%
|
// only collapse if height exceeds collHeight+20%
|
||||||
|
|
@ -102,3 +106,7 @@ app.views.OEmbed = app.views.Base.extend({
|
||||||
this.$el.html(insertHTML);
|
this.$el.html(insertHTML);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.views.OpenGraph = app.views.Base.extend({
|
||||||
|
templateName : "opengraph"
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ app.views.Post.SmallFrame = app.views.Post.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
subviews : {
|
subviews : {
|
||||||
'.embed-frame' : "oEmbedView"
|
'.embed-frame' : "oEmbedView",
|
||||||
|
'.open-graph-frame' : 'openGraphView'
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize : function(options) {
|
initialize : function(options) {
|
||||||
|
|
@ -22,6 +23,10 @@ app.views.Post.SmallFrame = app.views.Post.extend({
|
||||||
return new app.views.OEmbed({model : this.model})
|
return new app.views.OEmbed({model : this.model})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openGraphView : function(){
|
||||||
|
return new app.views.OpenGraph({model : this.model})
|
||||||
|
},
|
||||||
|
|
||||||
smallFramePresenter : function(){
|
smallFramePresenter : function(){
|
||||||
//todo : we need to have something better for small frame text, probably using the headline() scenario.
|
//todo : we need to have something better for small frame text, probably using the headline() scenario.
|
||||||
return _.extend(this.defaultPresenter(),
|
return _.extend(this.defaultPresenter(),
|
||||||
|
|
@ -50,10 +55,10 @@ app.views.Post.SmallFrame = app.views.Post.extend({
|
||||||
var text = this.model.get("text")
|
var text = this.model.get("text")
|
||||||
, baseClass = $.trim(text).length == 0 ? "no-text" : "has-text";
|
, baseClass = $.trim(text).length == 0 ? "no-text" : "has-text";
|
||||||
|
|
||||||
if(this.model.get("photos").length > 0 || this.model.get("o_embed_cache"))
|
if(this.model.get("photos").length > 0 || this.model.get("o_embed_cache") || this.model.get("open_graph_cache"))
|
||||||
baseClass += " has-media";
|
baseClass += " has-media";
|
||||||
|
|
||||||
if(baseClass == "no-text" || this.model.get("photos").length > 0 || this.model.get("o_embed_cache")) { return baseClass }
|
if(baseClass == "no-text" || this.model.get("photos").length > 0 || this.model.get("o_embed_cache") || this.model.get("open_graph_cache")) { return baseClass }
|
||||||
|
|
||||||
var randomColor = _.first(_.shuffle(['cyan', 'green', 'yellow', 'purple', 'lime-green', 'orange', 'red', 'turquoise', 'sand']));
|
var randomColor = _.first(_.shuffle(['cyan', 'green', 'yellow', 'purple', 'lime-green', 'orange', 'red', 'turquoise', 'sand']));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ app.views.StreamPost = app.views.Post.extend({
|
||||||
".comments" : "commentStreamView",
|
".comments" : "commentStreamView",
|
||||||
".post-content" : "postContentView",
|
".post-content" : "postContentView",
|
||||||
".oembed" : "oEmbedView",
|
".oembed" : "oEmbedView",
|
||||||
|
".opengraph" : "openGraphView",
|
||||||
".status-message-location" : "postLocationStreamView"
|
".status-message-location" : "postLocationStreamView"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -29,6 +30,7 @@ app.views.StreamPost = app.views.Post.extend({
|
||||||
//subviews
|
//subviews
|
||||||
this.commentStreamView = new app.views.CommentStream({model : this.model});
|
this.commentStreamView = new app.views.CommentStream({model : this.model});
|
||||||
this.oEmbedView = new app.views.OEmbed({model : this.model});
|
this.oEmbedView = new app.views.OEmbed({model : this.model});
|
||||||
|
this.openGraphView = new app.views.OpenGraph({model : this.model});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1953,6 +1953,23 @@ ul#press_logos
|
||||||
iframe, .thumb img
|
iframe, .thumb img
|
||||||
:width 100%
|
:width 100%
|
||||||
|
|
||||||
|
.opengraph
|
||||||
|
:float left
|
||||||
|
:width 100%
|
||||||
|
a
|
||||||
|
:display block
|
||||||
|
:text-decoration none
|
||||||
|
:color #000
|
||||||
|
:margin-top 10px
|
||||||
|
:border-top solid 1px #DDD
|
||||||
|
:border-bottom solid 1px #DDD
|
||||||
|
:padding 10px 0px 10px 0px
|
||||||
|
|
||||||
|
img
|
||||||
|
:margin 10px 0px 10px 0px
|
||||||
|
:float left
|
||||||
|
br
|
||||||
|
:clear both
|
||||||
|
|
||||||
.conversation_participants
|
.conversation_participants
|
||||||
:background
|
:background
|
||||||
|
|
|
||||||
10
app/assets/templates/opengraph_tpl.jst.hbs
Normal file
10
app/assets/templates/opengraph_tpl.jst.hbs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{{#if open_graph_cache}}
|
||||||
|
<a href="{{open_graph_cache.url}}" target="_blank">
|
||||||
|
<div>
|
||||||
|
<h3>{{open_graph_cache.title}}</h3>
|
||||||
|
<img src="{{open_graph_cache.image}}" />
|
||||||
|
<p>{{open_graph_cache.description}}</p>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="embed-frame" />
|
<div class="embed-frame" />
|
||||||
|
<div class="open-graph-frame" />
|
||||||
|
|
||||||
{{#if text}}
|
{{#if text}}
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,5 @@
|
||||||
<div class="collapsible">
|
<div class="collapsible">
|
||||||
{{{text}}}
|
{{{text}}}
|
||||||
<div class="oembed"></div>
|
<div class="oembed"></div>
|
||||||
</div>
|
<div class="opengraph"></div>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,25 @@ module OpenGraphHelper
|
||||||
content_tag(:meta, '', :property => name, :content => content)
|
content_tag(:meta, '', :property => name, :content => content)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def og_html(cache)
|
||||||
|
title = cache.title
|
||||||
|
html =
|
||||||
|
"<div class=\"og-concent\">" +
|
||||||
|
"<a class=\"og-link\" href=\"#{cache.url}\">" +
|
||||||
|
"<img class=\"og-image\" src=\"#{cache.image}\"/>"
|
||||||
|
"<h1 class=\"og-title\">#{cache.title}</h1>" +
|
||||||
|
"<p class=\"og-description\">#{cache.description}</p>"
|
||||||
|
"</a></div>"
|
||||||
|
return html
|
||||||
|
end
|
||||||
|
|
||||||
|
def link_to_oembed_image(cache, prefix = 'thumbnail_')
|
||||||
|
link_to(oembed_image_tag(cache, prefix), cache.url, :target => '_blank')
|
||||||
|
end
|
||||||
|
|
||||||
|
def oembed_image_tag(cache, prefix)
|
||||||
|
image_tag(cache.data[prefix + 'url'], cache.options_hash(prefix))
|
||||||
|
end
|
||||||
private
|
private
|
||||||
|
|
||||||
# This method compensates for hosting assets off of s3
|
# This method compensates for hosting assets off of s3
|
||||||
|
|
|
||||||
49
app/models/open_graph_cache.rb
Normal file
49
app/models/open_graph_cache.rb
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
class OpenGraphCache < ActiveRecord::Base
|
||||||
|
attr_accessible :title
|
||||||
|
attr_accessible :ob_type
|
||||||
|
attr_accessible :image
|
||||||
|
attr_accessible :url
|
||||||
|
attr_accessible :description
|
||||||
|
|
||||||
|
validates :title, :presence => true
|
||||||
|
validates :ob_type, :presence => true
|
||||||
|
validates :image, :presence => true
|
||||||
|
validates :url, :presence => true
|
||||||
|
|
||||||
|
has_many :posts
|
||||||
|
|
||||||
|
acts_as_api
|
||||||
|
api_accessible :backbone do |t|
|
||||||
|
t.add :title
|
||||||
|
t.add :ob_type
|
||||||
|
t.add :image
|
||||||
|
t.add :description
|
||||||
|
t.add :url
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.find_or_create_by_url(url)
|
||||||
|
cache = OpenGraphCache.find_or_initialize_by_url(url)
|
||||||
|
return cache if cache.persisted?
|
||||||
|
cache.fetch_and_save_opengraph_data!
|
||||||
|
return cache if cache.persisted?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_and_save_opengraph_data!
|
||||||
|
begin
|
||||||
|
response = OpenGraph.fetch(self.url)
|
||||||
|
if !response
|
||||||
|
return
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
# noop
|
||||||
|
else
|
||||||
|
self.title = response.title
|
||||||
|
self.ob_type = response.type
|
||||||
|
self.image = response.image
|
||||||
|
self.url = response.url
|
||||||
|
self.description = response.description
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -24,13 +24,14 @@ class Post < ActiveRecord::Base
|
||||||
has_many :resharers, :class_name => 'Person', :through => :reshares, :source => :author
|
has_many :resharers, :class_name => 'Person', :through => :reshares, :source => :author
|
||||||
|
|
||||||
belongs_to :o_embed_cache
|
belongs_to :o_embed_cache
|
||||||
|
belongs_to :open_graph_cache
|
||||||
|
|
||||||
after_create do
|
after_create do
|
||||||
self.touch(:interacted_at)
|
self.touch(:interacted_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
#scopes
|
#scopes
|
||||||
scope :includes_for_a_stream, includes(:o_embed_cache, {:author => :profile}, :mentions => {:person => :profile}) #note should include root and photos, but i think those are both on status_message
|
scope :includes_for_a_stream, includes(:o_embed_cache, :open_graph_cache, {:author => :profile}, :mentions => {:person => :profile}) #note should include root and photos, but i think those are both on status_message
|
||||||
|
|
||||||
|
|
||||||
scope :commented_by, lambda { |person|
|
scope :commented_by, lambda { |person|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ class Reshare < Post
|
||||||
self.root ? root.o_embed_cache : super
|
self.root ? root.o_embed_cache : super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def open_graph_cache
|
||||||
|
self.root ? root.open_graph_cache : super
|
||||||
|
end
|
||||||
|
|
||||||
def raw_message
|
def raw_message
|
||||||
self.root ? root.raw_message : super
|
self.root ? root.raw_message : super
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,12 @@ class StatusMessage < Post
|
||||||
before_destroy :presence_of_content
|
before_destroy :presence_of_content
|
||||||
|
|
||||||
attr_accessor :oembed_url
|
attr_accessor :oembed_url
|
||||||
|
attr_accessor :open_graph_url
|
||||||
|
|
||||||
before_create :filter_mentions
|
before_create :filter_mentions
|
||||||
after_create :create_mentions
|
after_create :create_mentions
|
||||||
after_create :queue_gather_oembed_data, :if => :contains_oembed_url_in_text?
|
after_create :queue_gather_oembed_data, :if => :contains_oembed_url_in_text?
|
||||||
|
after_create :queue_gather_open_graph_data, :if => :contains_open_graph_url_in_text?
|
||||||
|
|
||||||
#scopes
|
#scopes
|
||||||
scope :where_person_is_mentioned, lambda { |person|
|
scope :where_person_is_mentioned, lambda { |person|
|
||||||
|
|
@ -143,11 +145,19 @@ class StatusMessage < Post
|
||||||
Workers::GatherOEmbedData.perform_async(self.id, self.oembed_url)
|
Workers::GatherOEmbedData.perform_async(self.id, self.oembed_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def queue_gather_open_graph_data
|
||||||
|
Workers::GatherOpenGraphData.perform_async(self.id, self.open_graph_url)
|
||||||
|
end
|
||||||
|
|
||||||
def contains_oembed_url_in_text?
|
def contains_oembed_url_in_text?
|
||||||
urls = URI.extract(self.raw_message, ['http', 'https'])
|
urls = URI.extract(self.raw_message, ['http', 'https'])
|
||||||
self.oembed_url = urls.find{ |url| !TRUSTED_OEMBED_PROVIDERS.find(url).nil? }
|
self.oembed_url = urls.find{ |url| !TRUSTED_OEMBED_PROVIDERS.find(url).nil? }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contains_open_graph_url_in_text?
|
||||||
|
self.open_graph_url = URI.extract(self.raw_message, ['http', 'https'])[0]
|
||||||
|
end
|
||||||
|
|
||||||
def address
|
def address
|
||||||
location.try(:address)
|
location.try(:address)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class PostPresenter
|
||||||
:nsfw => @post.nsfw,
|
:nsfw => @post.nsfw,
|
||||||
:author => @post.author.as_api_response(:backbone),
|
:author => @post.author.as_api_response(:backbone),
|
||||||
:o_embed_cache => @post.o_embed_cache.try(:as_api_response, :backbone),
|
:o_embed_cache => @post.o_embed_cache.try(:as_api_response, :backbone),
|
||||||
|
:open_graph_cache => @post.open_graph_cache.try(:as_api_response, :backbone),
|
||||||
:mentioned_people => @post.mentioned_people.as_api_response(:backbone),
|
:mentioned_people => @post.mentioned_people.as_api_response(:backbone),
|
||||||
:photos => @post.photos.map {|p| p.as_api_response(:backbone)},
|
:photos => @post.photos.map {|p| p.as_api_response(:backbone)},
|
||||||
:frame_name => @post.frame_name || template_name,
|
:frame_name => @post.frame_name || template_name,
|
||||||
|
|
|
||||||
22
app/workers/gather_open_graph_data.rb
Normal file
22
app/workers/gather_open_graph_data.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||||
|
# licensed under the Affero General Public License version 3 or later. See
|
||||||
|
# the COPYRIGHT file.
|
||||||
|
#
|
||||||
|
|
||||||
|
module Workers
|
||||||
|
class GatherOpenGraphData < Base
|
||||||
|
sidekiq_options queue: :http_service
|
||||||
|
|
||||||
|
def perform(post_id, url, retry_count=1)
|
||||||
|
post = Post.find(post_id)
|
||||||
|
post.open_graph_cache = OpenGraphCache.find_or_create_by_url(url)
|
||||||
|
post.save
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
# User created a post and deleted it right afterwards before we
|
||||||
|
# we had a chance to run the job.
|
||||||
|
# On the other hand sometimes the job runs before the Post is
|
||||||
|
# fully persisted. So we just reduce the amount of retries.
|
||||||
|
GatherOpenGraphData.perform_in(1.minute, post_id, url, retry_count+1) unless retry_count > 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
19
db/migrate/20130608171134_add_open_graph_cache.rb
Normal file
19
db/migrate/20130608171134_add_open_graph_cache.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
class AddOpenGraphCache < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
create_table :open_graph_caches do |t|
|
||||||
|
t.string :title
|
||||||
|
t.string :ob_type
|
||||||
|
t.string :image
|
||||||
|
t.string :url
|
||||||
|
t.text :description
|
||||||
|
end
|
||||||
|
change_table :posts do |t|
|
||||||
|
t.integer :open_graph_cache_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :posts, :open_graph_cache_id
|
||||||
|
drop_table :open_graph_caches
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -215,6 +215,14 @@ ActiveRecord::Schema.define(:version => 20130801063213) do
|
||||||
|
|
||||||
add_index "o_embed_caches", ["url"], :name => "index_o_embed_caches_on_url", :length => {"url"=>255}
|
add_index "o_embed_caches", ["url"], :name => "index_o_embed_caches_on_url", :length => {"url"=>255}
|
||||||
|
|
||||||
|
create_table "open_graph_caches", :force => true do |t|
|
||||||
|
t.string "title"
|
||||||
|
t.string "ob_type"
|
||||||
|
t.string "image"
|
||||||
|
t.string "url"
|
||||||
|
t.text "description"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "participations", :force => true do |t|
|
create_table "participations", :force => true do |t|
|
||||||
t.string "guid"
|
t.string "guid"
|
||||||
t.integer "target_id"
|
t.integer "target_id"
|
||||||
|
|
@ -309,6 +317,7 @@ ActiveRecord::Schema.define(:version => 20130801063213) do
|
||||||
t.string "facebook_id"
|
t.string "facebook_id"
|
||||||
t.string "tweet_id"
|
t.string "tweet_id"
|
||||||
t.text "tumblr_ids"
|
t.text "tumblr_ids"
|
||||||
|
t.integer "open_graph_cache_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "posts", ["author_id", "root_guid"], :name => "index_posts_on_author_id_and_root_guid", :unique => true
|
add_index "posts", ["author_id", "root_guid"], :name => "index_posts_on_author_id_and_root_guid", :unique => true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue