track incoming links, amend share link to include user
fix pm styling
This commit is contained in:
parent
27edebfaef
commit
37867af1bb
|
@ -8,6 +8,10 @@
|
||||||
**/
|
**/
|
||||||
Discourse.Post = Discourse.Model.extend({
|
Discourse.Post = Discourse.Model.extend({
|
||||||
|
|
||||||
|
shareUrl: function(){
|
||||||
|
var user = Discourse.get('currentUser');
|
||||||
|
return '/p/' + this.get('id') + (user ? '/' + user.get('id') : '');
|
||||||
|
}.property('id'),
|
||||||
|
|
||||||
new_user:(function(){
|
new_user:(function(){
|
||||||
return this.get('trust_level') === 0;
|
return this.get('trust_level') === 0;
|
||||||
|
|
|
@ -36,13 +36,18 @@ Discourse.Topic = Discourse.Model.extend({
|
||||||
}
|
}
|
||||||
}).property('categoryName', 'categories'),
|
}).property('categoryName', 'categories'),
|
||||||
|
|
||||||
url: (function() {
|
shareUrl: function(){
|
||||||
|
var user = Discourse.get('currentUser');
|
||||||
|
return '/st/' + this.get('id') + (user ? '/' + user.get('id') : '');
|
||||||
|
}.property('id'),
|
||||||
|
|
||||||
|
url: function() {
|
||||||
var slug = this.get('slug');
|
var slug = this.get('slug');
|
||||||
if (slug.isBlank()) {
|
if (slug.isBlank()) {
|
||||||
slug = "topic";
|
slug = "topic";
|
||||||
}
|
}
|
||||||
return Discourse.getURL("/t/") + slug + "/" + (this.get('id'));
|
return Discourse.getURL("/t/") + slug + "/" + (this.get('id'));
|
||||||
}).property('id', 'slug'),
|
}.property('id', 'slug'),
|
||||||
|
|
||||||
// Helper to build a Url with a post number
|
// Helper to build a Url with a post number
|
||||||
urlForPostNumber: function(postNumber) {
|
urlForPostNumber: function(postNumber) {
|
||||||
|
@ -53,20 +58,20 @@ Discourse.Topic = Discourse.Model.extend({
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
lastReadUrl: (function() {
|
lastReadUrl: function() {
|
||||||
return this.urlForPostNumber(this.get('last_read_post_number'));
|
return this.urlForPostNumber(this.get('last_read_post_number'));
|
||||||
}).property('url', 'last_read_post_number'),
|
}.property('url', 'last_read_post_number'),
|
||||||
|
|
||||||
lastPostUrl: (function() {
|
lastPostUrl: function() {
|
||||||
return this.urlForPostNumber(this.get('highest_post_number'));
|
return this.urlForPostNumber(this.get('highest_post_number'));
|
||||||
}).property('url', 'highest_post_number'),
|
}.property('url', 'highest_post_number'),
|
||||||
|
|
||||||
// The last post in the topic
|
// The last post in the topic
|
||||||
lastPost: function() {
|
lastPost: function() {
|
||||||
return this.get('posts').last();
|
return this.get('posts').last();
|
||||||
},
|
},
|
||||||
|
|
||||||
postsChanged: (function() {
|
postsChanged: function() {
|
||||||
var last, posts;
|
var last, posts;
|
||||||
posts = this.get('posts');
|
posts = this.get('posts');
|
||||||
last = posts.last();
|
last = posts.last();
|
||||||
|
@ -76,12 +81,12 @@ Discourse.Topic = Discourse.Model.extend({
|
||||||
});
|
});
|
||||||
last.set('lastPost', true);
|
last.set('lastPost', true);
|
||||||
return true;
|
return true;
|
||||||
}).observes('posts.@each', 'posts'),
|
}.observes('posts.@each', 'posts'),
|
||||||
|
|
||||||
// The amount of new posts to display. It might be different than what the server
|
// The amount of new posts to display. It might be different than what the server
|
||||||
// tells us if we are still asynchronously flushing our "recently read" data.
|
// tells us if we are still asynchronously flushing our "recently read" data.
|
||||||
// So take what the browser has seen into consideration.
|
// So take what the browser has seen into consideration.
|
||||||
displayNewPosts: (function() {
|
displayNewPosts: function() {
|
||||||
var delta, highestSeen, result;
|
var delta, highestSeen, result;
|
||||||
if (highestSeen = Discourse.get('highestSeenByTopic')[this.get('id')]) {
|
if (highestSeen = Discourse.get('highestSeenByTopic')[this.get('id')]) {
|
||||||
delta = highestSeen - this.get('last_read_post_number');
|
delta = highestSeen - this.get('last_read_post_number');
|
||||||
|
@ -94,10 +99,10 @@ Discourse.Topic = Discourse.Model.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.get('new_posts');
|
return this.get('new_posts');
|
||||||
}).property('new_posts', 'id'),
|
}.property('new_posts', 'id'),
|
||||||
|
|
||||||
// The coldmap class for the age of the topic
|
// The coldmap class for the age of the topic
|
||||||
ageCold: (function() {
|
ageCold: function() {
|
||||||
var createdAt, createdAtDays, daysSinceEpoch, lastPost, nowDays;
|
var createdAt, createdAtDays, daysSinceEpoch, lastPost, nowDays;
|
||||||
if (!(lastPost = this.get('last_posted_at'))) return;
|
if (!(lastPost = this.get('last_posted_at'))) return;
|
||||||
if (!(createdAt = this.get('created_at'))) return;
|
if (!(createdAt = this.get('created_at'))) return;
|
||||||
|
@ -115,7 +120,7 @@ Discourse.Topic = Discourse.Model.extend({
|
||||||
if (createdAtDays < nowDays - 14) return 'coldmap-low';
|
if (createdAtDays < nowDays - 14) return 'coldmap-low';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}).property('age', 'created_at'),
|
}.property('age', 'created_at'),
|
||||||
|
|
||||||
viewsHeat: function() {
|
viewsHeat: function() {
|
||||||
var v = this.get('views');
|
var v = this.get('views');
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<h3 {{bindAttr class="moderator new_user"}}><a href='{{unbound usernameUrl}}'>{{breakUp username}}</a></h3>
|
<h3 {{bindAttr class="moderator new_user"}}><a href='{{unbound usernameUrl}}'>{{breakUp username}}</a></h3>
|
||||||
|
|
||||||
<div class='post-info'>
|
<div class='post-info'>
|
||||||
<a href='#' class='post-date' {{bindAttr data-share-url="url"}}>{{date created_at}}</a>
|
<a href='#' class='post-date' {{bindAttr data-share-url="shareUrl"}}>{{date created_at}}</a>
|
||||||
</div>
|
</div>
|
||||||
{{#if hasHistory}}
|
{{#if hasHistory}}
|
||||||
<div class='post-info'>
|
<div class='post-info'>
|
||||||
|
|
|
@ -132,7 +132,7 @@ Discourse.PostMenuView = Discourse.View.extend({
|
||||||
renderShare: function(post, buffer) {
|
renderShare: function(post, buffer) {
|
||||||
buffer.push("<button title=\"" +
|
buffer.push("<button title=\"" +
|
||||||
(Em.String.i18n("post.controls.share")) +
|
(Em.String.i18n("post.controls.share")) +
|
||||||
"\" data-share-url=\"" + (post.get('url')) + "\"><i class=\"icon-link\"></i></button>");
|
"\" data-share-url=\"" + (post.get('shareUrl')) + "\"><i class=\"icon-link\"></i></button>");
|
||||||
},
|
},
|
||||||
|
|
||||||
// Reply button
|
// Reply button
|
||||||
|
|
|
@ -62,7 +62,7 @@ Discourse.TopicFooterButtonsView = Ember.ContainerView.extend({
|
||||||
this.addObject(Discourse.ButtonView.create({
|
this.addObject(Discourse.ButtonView.create({
|
||||||
textKey: 'topic.share.title',
|
textKey: 'topic.share.title',
|
||||||
helpKey: 'topic.share.help',
|
helpKey: 'topic.share.help',
|
||||||
'data-share-url': topic.get('url'),
|
'data-share-url': topic.get('shareUrl'),
|
||||||
|
|
||||||
renderIcon: function(buffer) {
|
renderIcon: function(buffer) {
|
||||||
buffer.push("<i class='icon icon-share'></i>");
|
buffer.push("<i class='icon icon-share'></i>");
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
@import "foundation/variables";
|
@import "foundation/variables";
|
||||||
@import "foundation/mixins";
|
@import "foundation/mixins";
|
||||||
|
|
||||||
|
// hack, this needs to be done cleaner
|
||||||
|
.private-message input.span8 {
|
||||||
|
width: 47%;
|
||||||
|
}
|
||||||
|
|
||||||
.composer-popup {
|
.composer-popup {
|
||||||
|
|
||||||
|
|
|
@ -240,12 +240,7 @@ class ApplicationController < ActionController::Base
|
||||||
alias :requires_parameter :requires_parameters
|
alias :requires_parameter :requires_parameters
|
||||||
|
|
||||||
def store_incoming_links
|
def store_incoming_links
|
||||||
if request.referer.present?
|
IncomingLink.add(request)
|
||||||
parsed = URI.parse(request.referer)
|
|
||||||
if parsed.host != request.host
|
|
||||||
IncomingLink.create(url: request.url, referer: request.referer[0..999])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_xhr
|
def check_xhr
|
||||||
|
|
|
@ -4,8 +4,17 @@ require_dependency 'post_destroyer'
|
||||||
class PostsController < ApplicationController
|
class PostsController < ApplicationController
|
||||||
|
|
||||||
# Need to be logged in for all actions here
|
# Need to be logged in for all actions here
|
||||||
before_filter :ensure_logged_in, except: [:show, :replies, :by_number]
|
before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link]
|
||||||
|
|
||||||
|
skip_before_filter :store_incoming_links, only: [:short_link]
|
||||||
|
skip_before_filter :check_xhr, only: [:short_link]
|
||||||
|
|
||||||
|
def short_link
|
||||||
|
post = Post.find(params[:post_id].to_i)
|
||||||
|
user = User.select(:id).where(id: params[:user_id].to_i).first
|
||||||
|
IncomingLink.add(request, user ? user.id : nil)
|
||||||
|
redirect_to post.url
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
requires_parameter(:post)
|
requires_parameter(:post)
|
||||||
|
|
|
@ -19,9 +19,16 @@ class TopicsController < ApplicationController
|
||||||
|
|
||||||
before_filter :consider_user_for_promotion, only: :show
|
before_filter :consider_user_for_promotion, only: :show
|
||||||
|
|
||||||
skip_before_filter :check_xhr, only: [:avatar, :show, :feed]
|
skip_before_filter :check_xhr, only: [:avatar, :show, :feed, :short_link]
|
||||||
|
skip_before_filter :store_incoming_links, only: [:short_link]
|
||||||
caches_action :avatar, cache_path: Proc.new {|c| "#{c.params[:post_number]}-#{c.params[:topic_id]}" }
|
caches_action :avatar, cache_path: Proc.new {|c| "#{c.params[:post_number]}-#{c.params[:topic_id]}" }
|
||||||
|
|
||||||
|
def short_link
|
||||||
|
topic = Topic.find(params[:topic_id].to_i)
|
||||||
|
user = User.select(:id).where(id: params[:user_id].to_i).first
|
||||||
|
IncomingLink.add(request, user ? user.id : nil)
|
||||||
|
redirect_to topic.relative_url
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
create_topic_view
|
create_topic_view
|
||||||
|
|
|
@ -9,6 +9,20 @@ class IncomingLink < ActiveRecord::Base
|
||||||
before_validation :extract_topic_and_post
|
before_validation :extract_topic_and_post
|
||||||
after_create :update_link_counts
|
after_create :update_link_counts
|
||||||
|
|
||||||
|
def self.add(request, user_id = nil)
|
||||||
|
host, referer = nil
|
||||||
|
|
||||||
|
if request.referer.present?
|
||||||
|
host = URI.parse(request.referer).host
|
||||||
|
referer = request.referer[0..999]
|
||||||
|
|
||||||
|
if host != request.host
|
||||||
|
IncomingLink.create(url: request.url, referer: referer, user_id: user_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
# Internal: Extract the domain from link.
|
# Internal: Extract the domain from link.
|
||||||
def extract_domain
|
def extract_domain
|
||||||
if referer.present?
|
if referer.present?
|
||||||
|
|
|
@ -270,11 +270,11 @@ class Post < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def url
|
def url
|
||||||
Post.url(topic.title, topic.id, post_number)
|
Post.url(topic.slug, topic.id, post_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.url(title, topic_id, post_number)
|
def self.url(slug, topic_id, post_number)
|
||||||
"/t/#{Slug.for(title)}/#{topic_id}/#{post_number}"
|
"/t/#{slug}/#{topic_id}/#{post_number}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.urls(post_ids)
|
def self.urls(post_ids)
|
||||||
|
@ -282,9 +282,9 @@ class Post < ActiveRecord::Base
|
||||||
if ids.length > 0
|
if ids.length > 0
|
||||||
urls = {}
|
urls = {}
|
||||||
Topic.joins(:posts).where('posts.id' => ids).
|
Topic.joins(:posts).where('posts.id' => ids).
|
||||||
select(['posts.id as post_id','post_number', 'topics.title', 'topics.id']).
|
select(['posts.id as post_id','post_number', 'topics.slug', 'topics.title', 'topics.id']).
|
||||||
each do |t|
|
each do |t|
|
||||||
urls[t.post_id.to_i] = url(t.title, t.id, t.post_number)
|
urls[t.post_id.to_i] = url(t.slug, t.id, t.post_number)
|
||||||
end
|
end
|
||||||
urls
|
urls
|
||||||
else
|
else
|
||||||
|
|
|
@ -17,7 +17,7 @@ class SiteSetting < ActiveRecord::Base
|
||||||
setting(:company_domain, 'www.example.com')
|
setting(:company_domain, 'www.example.com')
|
||||||
setting(:api_key, '')
|
setting(:api_key, '')
|
||||||
client_setting(:traditional_markdown_linebreaks, false)
|
client_setting(:traditional_markdown_linebreaks, false)
|
||||||
client_setting(:top_menu, 'latest|hot|new|unread|favorited|categories')
|
client_setting(:top_menu, 'latest|new|unread|favorited|categories')
|
||||||
client_setting(:post_menu, 'like|edit|flag|delete|share|bookmark|reply')
|
client_setting(:post_menu, 'like|edit|flag|delete|share|bookmark|reply')
|
||||||
client_setting(:share_links, 'twitter|facebook|google+|email')
|
client_setting(:share_links, 'twitter|facebook|google+|email')
|
||||||
client_setting(:track_external_right_clicks, false)
|
client_setting(:track_external_right_clicks, false)
|
||||||
|
|
|
@ -129,6 +129,9 @@ Discourse::Application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get 'p/:post_id/:user_id' => 'posts#short_link'
|
||||||
|
get 'st/:topic_id/:user_id' => 'topics#short_link'
|
||||||
|
|
||||||
resources :notifications
|
resources :notifications
|
||||||
resources :categories
|
resources :categories
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddUserIdToIncomingLinks < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :incoming_links, :user_id, :integer
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,6 +3,15 @@ require 'spec_helper'
|
||||||
describe PostsController do
|
describe PostsController do
|
||||||
|
|
||||||
|
|
||||||
|
describe 'short_link' do
|
||||||
|
it 'logs the incoming link once' do
|
||||||
|
IncomingLink.expects(:add).once.returns(true)
|
||||||
|
p = Fabricate(:post)
|
||||||
|
get :short_link, post_id: p.id, user_id: 999
|
||||||
|
response.should be_redirect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'show' do
|
describe 'show' do
|
||||||
|
|
||||||
let(:post) { Fabricate(:post, user: log_in) }
|
let(:post) { Fabricate(:post, user: log_in) }
|
||||||
|
|
|
@ -8,68 +8,75 @@ describe IncomingLink do
|
||||||
it { should ensure_length_of(:referer).is_at_least(3).is_at_most(1000) }
|
it { should ensure_length_of(:referer).is_at_least(3).is_at_most(1000) }
|
||||||
it { should ensure_length_of(:domain).is_at_least(1).is_at_most(100) }
|
it { should ensure_length_of(:domain).is_at_least(1).is_at_most(100) }
|
||||||
|
|
||||||
|
let :post do
|
||||||
|
Fabricate(:post)
|
||||||
|
end
|
||||||
|
|
||||||
|
let :topic do
|
||||||
|
post.topic
|
||||||
|
end
|
||||||
|
|
||||||
|
let :incoming_link do
|
||||||
|
IncomingLink.create(url: "/t/slug/#{topic.id}/#{post.post_number}",
|
||||||
|
referer: "http://twitter.com")
|
||||||
|
end
|
||||||
|
|
||||||
describe 'local topic link' do
|
describe 'local topic link' do
|
||||||
|
|
||||||
it 'should validate properly' do
|
it 'should validate properly' do
|
||||||
Fabricate.build(:incoming_link).should be_valid
|
Fabricate.build(:incoming_link).should be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'tracking link counts' do
|
||||||
|
it "increases the incoming link counts" do
|
||||||
|
incoming_link
|
||||||
|
lambda { post.reload }.should change(post, :incoming_link_count).by(1)
|
||||||
|
lambda { topic.reload }.should change(topic, :incoming_link_count).by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'saving local link' do
|
describe 'saving local link' do
|
||||||
|
it 'has correct info set' do
|
||||||
before do
|
incoming_link.domain.should == "twitter.com"
|
||||||
@post = Fabricate(:post)
|
incoming_link.topic_id.should == topic.id
|
||||||
@topic = @post.topic
|
incoming_link.post_number.should == post.post_number
|
||||||
@incoming_link = IncomingLink.create(url: "/t/slug/#{@topic.id}/#{@post.post_number}",
|
|
||||||
referer: "http://twitter.com")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'incoming link counts' do
|
end
|
||||||
it "increases the post's incoming link count" do
|
end
|
||||||
lambda { @incoming_link.save; @post.reload }.should change(@post, :incoming_link_count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "increases the topic's incoming link count" do
|
describe 'add' do
|
||||||
lambda { @incoming_link.save; @topic.reload }.should change(@topic, :incoming_link_count).by(1)
|
it "does nothing if referer is empty" do
|
||||||
end
|
env = Rack::MockRequest.env_for("http://somesite.com")
|
||||||
|
request = Rack::Request.new(env)
|
||||||
|
IncomingLink.expects(:create).never
|
||||||
|
IncomingLink.add(request)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
it "does nothing if referer is same as host" do
|
||||||
|
env = Rack::MockRequest.env_for("http://somesite.com")
|
||||||
describe 'after save' do
|
env['HTTP_REFERER'] = 'http://somesite.com'
|
||||||
before do
|
request = Rack::Request.new(env)
|
||||||
@incoming_link.save
|
IncomingLink.expects(:create).never
|
||||||
end
|
IncomingLink.add(request)
|
||||||
|
end
|
||||||
it 'has a domain' do
|
|
||||||
@incoming_link.domain.should == "twitter.com"
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has the topic_id' do
|
|
||||||
@incoming_link.topic_id.should == @topic.id
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has the post_number' do
|
|
||||||
@incoming_link.post_number.should == @post.post_number
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
it "expects to be called with referer and user id" do
|
||||||
|
env = Rack::MockRequest.env_for("http://somesite.com")
|
||||||
|
env['HTTP_REFERER'] = 'http://some.other.site.com'
|
||||||
|
request = Rack::Request.new(env)
|
||||||
|
IncomingLink.expects(:create).once.returns(true)
|
||||||
|
IncomingLink.add(request, 100)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'non-topic url' do
|
describe 'non-topic url' do
|
||||||
|
it 'has nothing set' do
|
||||||
before do
|
link = Fabricate(:incoming_link_not_topic)
|
||||||
@link = Fabricate(:incoming_link_not_topic)
|
link.topic_id.should be_blank
|
||||||
end
|
link.user_id.should be_blank
|
||||||
|
|
||||||
it 'has no topic_id' do
|
|
||||||
@link.topic_id.should be_blank
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has no post_number' do
|
|
||||||
@link.topic_id.should be_blank
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue