radiant-twitter-extension 2.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HELP_admin.md +19 -0
- data/README.md +104 -0
- data/Rakefile +120 -0
- data/app/views/admin/configuration/_twitter_edit.html.haml +6 -0
- data/app/views/admin/configuration/_twitter_show.html.haml +5 -0
- data/app/views/admin/pages/_twitter.html.haml +9 -0
- data/config/initializers/radiant_config.rb +8 -0
- data/config/locales/en.yml +20 -0
- data/db/migrate/001_add_twitter_notification_fields.rb +11 -0
- data/lib/radiant-twitter-extension.rb +8 -0
- data/lib/tasks/twitter_extension_tasks.rake +28 -0
- data/lib/twitter_notification.rb +43 -0
- data/lib/twitter_tags.rb +406 -0
- data/public/images/twitter/bird.png +0 -0
- data/public/images/twitter/sprite.png +0 -0
- data/public/stylesheets/sass/twitter.sass +46 -0
- data/radiant-twitter-extension.gemspec +31 -0
- data/spec/lib/twitter_tags_spec.rb +114 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +37 -0
- data/twitter_extension.rb +18 -0
- data/vendor/plugins/filestore_expires_in-plugin/MIT-LICENSE +20 -0
- data/vendor/plugins/filestore_expires_in-plugin/README.md +56 -0
- data/vendor/plugins/filestore_expires_in-plugin/init.rb +3 -0
- data/vendor/plugins/filestore_expires_in-plugin/lib/active_support/cache/file_store_extras.rb +26 -0
- metadata +116 -0
data/HELP_admin.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
You can have updates to your site automatically send details to your
|
2
|
+
Twitter account. All you'll need to do is provide your account information
|
3
|
+
in the Radiant::Config settings.
|
4
|
+
|
5
|
+
The required keys are `twitter.password`, `twitter.username` and `twitter.url_host`.
|
6
|
+
|
7
|
+
The `username` and `password` are the same for your login details of your Twitter
|
8
|
+
account. The `url_host` will be used when generating links back to your site, this
|
9
|
+
should be your domain name.
|
10
|
+
|
11
|
+
On the edit screen of each page, you can select the option to notify Twitter
|
12
|
+
of all of the updates to the children of that page. You can create a blog page,
|
13
|
+
for example, and have each new post linked in your Twitter account.
|
14
|
+
|
15
|
+
_Built with version `0.6.6` of the `twitter` gem._
|
16
|
+
|
17
|
+
To get latest tweet(s):
|
18
|
+
|
19
|
+
<r:twitter:message [ max="10" ] />
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Twitter
|
2
|
+
|
3
|
+
An extension to Radiant that will automatically tweet the publication of new pages in selected parts of your site, and which provides a set of radius tags that allow you to feature twitter feeds of different kinds on your site.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
sudo gem install radiant-twitter-extension
|
8
|
+
|
9
|
+
add this to your environment.rb
|
10
|
+
|
11
|
+
config.gem 'radiant-twitter-extension', :version => '~> 2.0.0.rc1'
|
12
|
+
|
13
|
+
and then:
|
14
|
+
|
15
|
+
rake radiant:extensions:update_all
|
16
|
+
rake radiant:extensions:twitter:migrate
|
17
|
+
|
18
|
+
You can also vendor the extension in the old-fashioned way:
|
19
|
+
|
20
|
+
git submodule add git://github.com/radiant/radiant-twitter-extension.git vendor/extensions/twitter
|
21
|
+
rake radiant:extensions:twitter:update
|
22
|
+
rake radiant:extensions:twitter:migrate
|
23
|
+
|
24
|
+
## Status
|
25
|
+
|
26
|
+
Nearly stable. I've just made some quite sweeping changes to bring this up to date, so small bugs are likely. Please file issues.
|
27
|
+
|
28
|
+
## Configuration
|
29
|
+
|
30
|
+
You can present twitter searches and feeds without authenticating, but if you want to post automatically to twitter you need to provide login information. The extension adds a 'twitter' block to the main radiant configuration interface: enter your screen name and password. Future versions may integrate with Twitter as an application but for now all we need is the ability to tweet.
|
31
|
+
|
32
|
+
## Tweet on publication
|
33
|
+
|
34
|
+
To post a tweet every time you publish a blog entry, check the 'Notify Twitter of newly published child pages?' box on the parent blog page. The tweet will contain the title of the page and its url.
|
35
|
+
|
36
|
+
## Display a twitter feed
|
37
|
+
|
38
|
+
If radiant is configured to tweet for you, all you need is this radius tag:
|
39
|
+
|
40
|
+
<r:twitter:tweets [max="10"] />
|
41
|
+
|
42
|
+
If it's not configured, or you want to display another user:
|
43
|
+
|
44
|
+
<r:twitter:tweets user="screen_name" />
|
45
|
+
|
46
|
+
To display a hashtag, or any other search:
|
47
|
+
|
48
|
+
<r:twitter:tweets search="#radiant" />
|
49
|
+
|
50
|
+
To display tweets from someone's list:
|
51
|
+
|
52
|
+
<r:twitter:tweets user="screen_name" list="list_name" />
|
53
|
+
|
54
|
+
The default presentation of tweets is exactly as [suggested by Twitter](https://dev.twitter.com/terms/display-guidelines) and if you include their widget script and the provided css it should all just work. If you want to present tweets differently, a range of more detailed radius tags is available. This is a slightly more compact format:
|
55
|
+
|
56
|
+
<r:twitter:tweets user="screen_name" />
|
57
|
+
<li class="tweet">
|
58
|
+
<r:tweet:avatar class="avatar" />
|
59
|
+
<r:tweet:user:screen_name />
|
60
|
+
<span class="block">
|
61
|
+
<r:tweet:text />
|
62
|
+
</span>
|
63
|
+
<span class="hidden">
|
64
|
+
<r:tweet:reply_link />
|
65
|
+
<r:tweet:retweet_link />
|
66
|
+
</span>
|
67
|
+
</li>
|
68
|
+
</r:twitter:tweets>
|
69
|
+
|
70
|
+
## Scripts and styles
|
71
|
+
|
72
|
+
The quick way to format tweets nicely is to include the supplied sass in your site stylesheet. If you're using radiant's built-in stylesheet manager and working in Sass, you can keep everything in one file (and selectively override it) by including this line near the top:
|
73
|
+
|
74
|
+
@import 'twitter.sass'
|
75
|
+
|
76
|
+
You can also link to `/stylesheets/twitter.css` in the usual way.
|
77
|
+
|
78
|
+
The links created by radius tags here are all compatible with twitter's widgeting. To enable basic intent-based popups, just include this line in the head or at the foot of your layout:
|
79
|
+
|
80
|
+
<script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
|
81
|
+
|
82
|
+
## Todo
|
83
|
+
|
84
|
+
* More gratifying twitter integration of admin
|
85
|
+
* Page field to edit tweet text before publication
|
86
|
+
* URL-shortener
|
87
|
+
|
88
|
+
## Copyright and license
|
89
|
+
|
90
|
+
Originally created by Sean Cribbs and now the work of many hands, including:
|
91
|
+
|
92
|
+
* Jim Gay
|
93
|
+
* Edmund Haselwanter
|
94
|
+
* Anna Billstrom
|
95
|
+
* William Ross
|
96
|
+
|
97
|
+
Currently maintained by Will at spanner.org. Issues and comments on github, please:
|
98
|
+
|
99
|
+
https://github.com/radiant/radiant-twitter-extension/issues
|
100
|
+
|
101
|
+
Released under the same terms as Rails and/or Radiant.
|
102
|
+
|
103
|
+
|
104
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# I think this is the one that should be moved to the extension Rakefile template
|
2
|
+
|
3
|
+
# In rails 1.2, plugins aren't available in the path until they're loaded.
|
4
|
+
# Check to see if the rspec plugin is installed first and require
|
5
|
+
# it if it is. If not, use the gem version.
|
6
|
+
|
7
|
+
# Determine where the RSpec plugin is by loading the boot
|
8
|
+
unless defined? RADIANT_ROOT
|
9
|
+
ENV["RAILS_ENV"] = "test"
|
10
|
+
case
|
11
|
+
when ENV["RADIANT_ENV_FILE"]
|
12
|
+
require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
|
13
|
+
when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
|
14
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
|
15
|
+
else
|
16
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake'
|
21
|
+
require 'rake/rdoctask'
|
22
|
+
require 'rake/testtask'
|
23
|
+
|
24
|
+
rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
|
25
|
+
$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
|
26
|
+
require 'spec/rake/spectask'
|
27
|
+
# require 'spec/translator'
|
28
|
+
|
29
|
+
# Cleanup the RADIANT_ROOT constant so specs will load the environment
|
30
|
+
Object.send(:remove_const, :RADIANT_ROOT)
|
31
|
+
|
32
|
+
extension_root = File.expand_path(File.dirname(__FILE__))
|
33
|
+
|
34
|
+
task :default => :spec
|
35
|
+
task :stats => "spec:statsetup"
|
36
|
+
|
37
|
+
desc "Run all specs in spec directory"
|
38
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
39
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
40
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :spec do
|
44
|
+
desc "Run all specs in spec directory with RCov"
|
45
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
46
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
47
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
48
|
+
t.rcov = true
|
49
|
+
t.rcov_opts = ['--exclude', 'spec', '--rails']
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Print Specdoc for all specs"
|
53
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
54
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
55
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
56
|
+
end
|
57
|
+
|
58
|
+
[:models, :controllers, :views, :helpers].each do |sub|
|
59
|
+
desc "Run the specs under spec/#{sub}"
|
60
|
+
Spec::Rake::SpecTask.new(sub) do |t|
|
61
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
62
|
+
t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Hopefully no one has written their extensions in pre-0.9 style
|
67
|
+
# desc "Translate specs from pre-0.9 to 0.9 style"
|
68
|
+
# task :translate do
|
69
|
+
# translator = ::Spec::Translator.new
|
70
|
+
# dir = RAILS_ROOT + '/spec'
|
71
|
+
# translator.translate(dir, dir)
|
72
|
+
# end
|
73
|
+
|
74
|
+
# Setup specs for stats
|
75
|
+
task :statsetup do
|
76
|
+
require 'code_statistics'
|
77
|
+
::STATS_DIRECTORIES << %w(Model\ specs spec/models)
|
78
|
+
::STATS_DIRECTORIES << %w(View\ specs spec/views)
|
79
|
+
::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
|
80
|
+
::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
|
81
|
+
::CodeStatistics::TEST_TYPES << "Model specs"
|
82
|
+
::CodeStatistics::TEST_TYPES << "View specs"
|
83
|
+
::CodeStatistics::TEST_TYPES << "Controller specs"
|
84
|
+
::CodeStatistics::TEST_TYPES << "Helper specs"
|
85
|
+
::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
|
86
|
+
end
|
87
|
+
|
88
|
+
namespace :db do
|
89
|
+
namespace :fixtures do
|
90
|
+
desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
|
91
|
+
task :load => :environment do
|
92
|
+
require 'active_record/fixtures'
|
93
|
+
ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
|
94
|
+
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
|
95
|
+
Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc 'Generate documentation for the twitter extension.'
|
103
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
104
|
+
rdoc.rdoc_dir = 'rdoc'
|
105
|
+
rdoc.title = 'TwitterExtension'
|
106
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
107
|
+
rdoc.rdoc_files.include('README')
|
108
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
109
|
+
end
|
110
|
+
|
111
|
+
# For extensions that are in transition
|
112
|
+
desc 'Test the twitter extension.'
|
113
|
+
Rake::TestTask.new(:test) do |t|
|
114
|
+
t.libs << 'lib'
|
115
|
+
t.pattern = 'test/**/*_test.rb'
|
116
|
+
t.verbose = true
|
117
|
+
end
|
118
|
+
|
119
|
+
# Load any custom rakefiles for extension
|
120
|
+
Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
|
@@ -0,0 +1,9 @@
|
|
1
|
+
%tr
|
2
|
+
%th.label
|
3
|
+
Twitter
|
4
|
+
%td
|
5
|
+
- fields_for @page do |pf|
|
6
|
+
= pf.check_box :notify_twitter_of_children
|
7
|
+
= pf.label :notify_twitter_of_children
|
8
|
+
- if @page.twitter_id
|
9
|
+
= link_to "[#{@page.twitter_id}]", "http://twitter.com/#{Radiant::Config['twitter.username']}/status/#{@page.twitter_id}"
|
@@ -0,0 +1,8 @@
|
|
1
|
+
Radiant.config do |config|
|
2
|
+
config.namespace 'twitter' do |twit|
|
3
|
+
twit.define 'username', :default => '', :allow_blank => true
|
4
|
+
twit.define 'password', :default => '', :allow_blank => true
|
5
|
+
twit.define 'token', :default => '', :allow_blank => true
|
6
|
+
twit.define 'secret', :default => '', :allow_blank => true
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
en:
|
2
|
+
activerecord:
|
3
|
+
attributes:
|
4
|
+
page:
|
5
|
+
notify_twitter_of_children: "Notify Twitter of newly published child pages?"
|
6
|
+
config:
|
7
|
+
twitter:
|
8
|
+
username: "Username (for tweets)"
|
9
|
+
password: "Password (for tweets)"
|
10
|
+
token: "Application token"
|
11
|
+
secret: "Application secret"
|
12
|
+
date:
|
13
|
+
formats:
|
14
|
+
twitter: "%m %B"
|
15
|
+
twitter_extension:
|
16
|
+
favorite: "Favorite"
|
17
|
+
reply: "Reply"
|
18
|
+
retweet: "Retweet"
|
19
|
+
twitter: "Twitter"
|
20
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddTwitterNotificationFields < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :pages, :notify_twitter_of_children, :boolean, :default => false
|
4
|
+
add_column :pages, :twitter_id, :string
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
remove_column :pages, :notify_twitter_of_children
|
9
|
+
remove_column :pages, :twitter_id
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module RadiantTwitterExtension
|
2
|
+
VERSION = '2.0.0.rc1'
|
3
|
+
SUMMARY = %q{Twitter posting and radius tags for twitter feeds.}
|
4
|
+
DESCRIPTION = %q{Posts notification of pages to Twitter and provides radiius tags to display the results of twitter searches.}
|
5
|
+
URL = "http://github.com/ehaselwanter/radiant-twitter-extension"
|
6
|
+
AUTHORS = ["Sean Cribbs", "Edmund Haselwanter", "Jim Gay", "William Ross"]
|
7
|
+
EMAIL = ["radiant@radiantcms.org"]
|
8
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
namespace :radiant do
|
2
|
+
namespace :extensions do
|
3
|
+
namespace :twitter do
|
4
|
+
|
5
|
+
desc "Runs the migration of the Twitter extension"
|
6
|
+
task :migrate => :environment do
|
7
|
+
require 'radiant/extension_migrator'
|
8
|
+
if ENV["VERSION"]
|
9
|
+
TwitterExtension.migrator.migrate(ENV["VERSION"].to_i)
|
10
|
+
else
|
11
|
+
TwitterExtension.migrator.migrate
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Copies public assets of the Twitter to the instance public/ directory."
|
16
|
+
task :update => :environment do
|
17
|
+
is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
|
18
|
+
puts "Copying assets from TwitterExtension"
|
19
|
+
Dir[TwitterExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
|
20
|
+
path = file.sub(TwitterExtension.root, '')
|
21
|
+
directory = File.dirname(path)
|
22
|
+
mkdir_p RAILS_ROOT + directory
|
23
|
+
cp file, RAILS_ROOT + path
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
module TwitterNotification
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval {
|
5
|
+
after_save :notify_twitter
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
def notify_twitter
|
10
|
+
if parent
|
11
|
+
if published? && Radiant.configured? && parent.notify_twitter_of_children? && !self.twitter_id
|
12
|
+
title_length = 138 - absolute_url.length
|
13
|
+
message_title = title.length > title_length ? (title[0..title_length-4] + "...") : title
|
14
|
+
message = "#{message_title}: #{absolute_url}"
|
15
|
+
begin
|
16
|
+
httpauth = Twitter::HTTPAuth.new(Radiant.config['twitter.username'], Radiant.config['twitter.password'])
|
17
|
+
client = Twitter::Base.new(httpauth)
|
18
|
+
status = client.update(message, :source => "radianttwitternotifier")
|
19
|
+
# Don't trigger save callbacks
|
20
|
+
self.class.update_all({:twitter_id => status.id}, :id => self.id)
|
21
|
+
rescue Twitter::Error => e
|
22
|
+
logger.error "Twitter Notification failure: #{e.inspect}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def absolute_url
|
30
|
+
if host = Radiant.config['site.host']
|
31
|
+
if host =~ /^http/
|
32
|
+
"#{host}#{self.url}"
|
33
|
+
else
|
34
|
+
"http://#{host}#{self.url}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def Radiant.configured?
|
40
|
+
!%w(twitter.username twitter.password site.host).any? {|k| Radiant.config[k].blank? }
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/twitter_tags.rb
ADDED
@@ -0,0 +1,406 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
|
3
|
+
module TwitterTags
|
4
|
+
include ActionView::Helpers::DateHelper
|
5
|
+
include Radiant::Taggable
|
6
|
+
class TagError < StandardError; end
|
7
|
+
|
8
|
+
tag 'twitter' do |tag|
|
9
|
+
tag.expand
|
10
|
+
end
|
11
|
+
|
12
|
+
desc %{
|
13
|
+
Retrieve a list of tweets. The minimal default is to return the ten most recent tweets of the
|
14
|
+
Radiant.configured twitter user (that is, the person as whom radiant is set up to tweet).
|
15
|
+
For that, you can use just a single tag:
|
16
|
+
|
17
|
+
<pre><code><r:twitter:tweets /></code></pre>
|
18
|
+
|
19
|
+
or control the presentation of tweets in a more detailed way:
|
20
|
+
|
21
|
+
<r:twitter:tweets:each><r:tweet:user:screen_name /> : <r:tweet:text /></r:twitter:tweets:each>
|
22
|
+
|
23
|
+
You can also specify a search in various ways.
|
24
|
+
|
25
|
+
* Supply a `max` attribute to change the number of tweets displayed. Default is 10.
|
26
|
+
* Supply a `user` attribute to display tweets from a different username
|
27
|
+
* Supply a `list` attribute to display tweets from the named list (see also r:twitter:list for a shortcut)
|
28
|
+
* Supply a `search` attribute to show tweets containing that text (see also r:twitter:search for a shortcut)
|
29
|
+
You don't need to %escape the search string.
|
30
|
+
In a search query the user and list parameters will be ignored.
|
31
|
+
|
32
|
+
<pre><code>
|
33
|
+
<r:twitter:tweets user="spanner_org" max="2" />
|
34
|
+
<r:twitter:tweets search="#radiant" />
|
35
|
+
</code></pre>
|
36
|
+
|
37
|
+
}
|
38
|
+
tag 'twitter:tweets' do |tag|
|
39
|
+
tag.locals.tweets = fetch_and_cache_tweets(tag.attr.slice('user', 'max', 'search', 'list').symbolize_keys)
|
40
|
+
tag.double? ? tag.expand : tag.render('twitter:messages')
|
41
|
+
end
|
42
|
+
|
43
|
+
tag 'twitter:tweets:each' do |tag|
|
44
|
+
tag.locals.tweets ||= fetch_and_cache_tweets(tag.attr.slice('user', 'max', 'search', 'list').symbolize_keys)
|
45
|
+
tag.render('_tweets_list', tag.attr.dup, &tag.block)
|
46
|
+
end
|
47
|
+
|
48
|
+
desc %{
|
49
|
+
Fetches and loops through tweets matching the supplied search string. You don't need to %escape the search string.
|
50
|
+
|
51
|
+
<pre><code>
|
52
|
+
<r:twitter:search for="#radiant"><r:tweets:each>...</r:tweets:each></r:twitter:search>
|
53
|
+
<r:twitter:search for="rails cms" max="1">...</r:twitter:search>
|
54
|
+
<r:twitter:search for="somethinbg" max="20">...</r:twitter:search>
|
55
|
+
</code></pre>
|
56
|
+
|
57
|
+
Short form also works:
|
58
|
+
|
59
|
+
<pre><code><r:twitter:search for="#radiant" /></code></pre>
|
60
|
+
|
61
|
+
and you can go stright into a loop:
|
62
|
+
|
63
|
+
<pre><code><r:twitter:search:each for="#radiant">...</r:twitter:search:each></code></pre>
|
64
|
+
}
|
65
|
+
tag 'twitter:search' do |tag|
|
66
|
+
tag.locals.tweets = fetch_and_cache_tweets(:search => tag.attr['for']) if tag.attr.any?
|
67
|
+
tag.double? ? tag.expand : tag.render('twitter:messages')
|
68
|
+
end
|
69
|
+
|
70
|
+
tag 'twitter:search:each' do |tag|
|
71
|
+
tag.locals.tweets ||= fetch_and_cache_tweets(:search => tag.attr['for'])
|
72
|
+
tag.render('_tweets_list', tag.attr.dup, &tag.block)
|
73
|
+
end
|
74
|
+
|
75
|
+
desc %{
|
76
|
+
Fetches tweets from the specified list belonging to the specified (or default) user.
|
77
|
+
|
78
|
+
<pre><code><r:twitter:list list="listname" [user="username"] [max="10"] /></code></pre>
|
79
|
+
|
80
|
+
Short form also works:
|
81
|
+
|
82
|
+
<pre><code><r:twitter:list list="radiant" /></code></pre>
|
83
|
+
|
84
|
+
and you can go stright into a loop:
|
85
|
+
|
86
|
+
<pre><code><r:twitter:list:each list="radiant">...</r:twitter:list:each></code></pre>
|
87
|
+
}
|
88
|
+
tag 'twitter:list' do |tag|
|
89
|
+
tag.locals.tweets = fetch_and_cache_tweets(:user => tag.attr['user'], :max => tag.attr['max'], :list => tag.attr['list']) if tag.attr.any?
|
90
|
+
tag.double? ? tag.expand : tag.render('twitter:messages')
|
91
|
+
end
|
92
|
+
|
93
|
+
tag 'twitter:list:each' do |tag|
|
94
|
+
tag.locals.tweets ||= fetch_and_cache_tweets(:user => tag.attr['user'], :max => tag.attr['max'], :list => tag.attr['list'])
|
95
|
+
tag.render('_tweets_list', tag.attr.dup, &tag.block)
|
96
|
+
end
|
97
|
+
|
98
|
+
desc %{
|
99
|
+
Returns the number of tweets.
|
100
|
+
}
|
101
|
+
tag 'tweets:length' do |tag|
|
102
|
+
tag.render('_tweets_length', tag.attr.dup, &tag.block)
|
103
|
+
end
|
104
|
+
tag 'twitter:list:length' do |tag|
|
105
|
+
tag.render('_tweets_length', tag.attr.dup, &tag.block)
|
106
|
+
end
|
107
|
+
tag 'twitter:search:length' do |tag|
|
108
|
+
tag.render('_tweets_length', tag.attr.dup, &tag.block)
|
109
|
+
end
|
110
|
+
|
111
|
+
desc %{
|
112
|
+
Loops through the current list of tweets.
|
113
|
+
}
|
114
|
+
tag 'tweets:each' do |tag|
|
115
|
+
tag.render('_tweets_list', tag.attr.dup, &tag.block)
|
116
|
+
end
|
117
|
+
|
118
|
+
# these are just for drying out: they can't be called directly.
|
119
|
+
|
120
|
+
tag '_tweets_list' do |tag|
|
121
|
+
raise TagError, "tweet_list utility tag called without a list of tweets to list" unless tag.locals.tweets
|
122
|
+
out = ""
|
123
|
+
tag.locals.tweets.each do |tweet|
|
124
|
+
tag.locals.tweet = tweet
|
125
|
+
if tag.double?
|
126
|
+
out << tag.expand
|
127
|
+
else
|
128
|
+
out << tag.render('tweet:message')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
out
|
132
|
+
end
|
133
|
+
|
134
|
+
tag '_tweets_length' do |tag|
|
135
|
+
raise TagError, "_tweets_length utility tag called without a list of tweets to length" unless tag.locals.tweets
|
136
|
+
tag.locals.tweets.length
|
137
|
+
end
|
138
|
+
|
139
|
+
desc %{
|
140
|
+
Usage:
|
141
|
+
|
142
|
+
This is a shortcut that displays messages from a twitter user's timeline.
|
143
|
+
The username can be specified with a 'user' parameter, or we will default to the Radiant.configured twitter user.
|
144
|
+
The number of messages is determined by the 'max' parameter, which must be 10 or less. Default is 5.
|
145
|
+
|
146
|
+
<pre><code><r:twitter:messages max="10" /></code></pre>
|
147
|
+
}
|
148
|
+
tag 'twitter:messages' do |tag|
|
149
|
+
out = ""
|
150
|
+
tag.locals.tweets ||= fetch_and_cache_tweets(:user => tag.attr['user'], :max => tag.attr['max'])
|
151
|
+
tag.locals.tweets.each do |tweet|
|
152
|
+
tag.locals.tweet = tweet
|
153
|
+
out << tag.render('tweet:message')
|
154
|
+
end
|
155
|
+
out
|
156
|
+
end
|
157
|
+
|
158
|
+
deprecated_tag 'twitter:message', :substitute => 'twitter:messages'
|
159
|
+
|
160
|
+
tag 'tweet' do |tag|
|
161
|
+
tag.expand if tag.locals.tweet
|
162
|
+
end
|
163
|
+
|
164
|
+
desc %{
|
165
|
+
Shortcut to display a single tweet in the standard way suggested by https://dev.twitter.com/terms/display-guidelines.
|
166
|
+
|
167
|
+
Note that for this to work you will probably want to include the twitter intents javascript in your page, and you may
|
168
|
+
also want to include the supplied `twitter.sass` in your site stylesheets.
|
169
|
+
}
|
170
|
+
tag 'tweet:message' do |tag|
|
171
|
+
if tweet = tag.locals.tweet
|
172
|
+
text = replace_links(tweet.text)
|
173
|
+
screen_name = tweet.from_user || tweet.user.screen_name # search returns a different data structure
|
174
|
+
date = tag.render('tweet:date', tag.attr.dup.merge('format' => "%d %B"))
|
175
|
+
%{
|
176
|
+
<p class="twitter">
|
177
|
+
<a class="twitter_avatar" href="http://twitter.com/#{screen_name}">#{tag.render("tweet:avatar")}</a>
|
178
|
+
<span class="tweet">
|
179
|
+
<a class="twitter_user" href="http://twitter.com/#{screen_name}">#{screen_name}</a>
|
180
|
+
<span class="twitter_name">#{tag.render('tweet:user:name')}</span>
|
181
|
+
<span class="twitter_text">#{text}</span>
|
182
|
+
<span class="twitter_links">
|
183
|
+
#{tag.render('tweet:permalink')}
|
184
|
+
#{tag.render('tweet:reply_link')}
|
185
|
+
#{tag.render('tweet:retweet_link')}
|
186
|
+
#{tag.render('tweet:favorite_link')}
|
187
|
+
</span>
|
188
|
+
</span>
|
189
|
+
</p>
|
190
|
+
}
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
[:coordinates, :in_reply_to_screen_name, :truncated, :in_reply_to_user_id, :in_reply_to_status_id,
|
195
|
+
:source, :place, :geo, :favorited, :contributors, :id].each do |method|
|
196
|
+
desc %{
|
197
|
+
Renders the @#{method.to_s}@ attribute of the tweet
|
198
|
+
<pre><code><r:tweet:#{method.to_s}/></code></pre>
|
199
|
+
}
|
200
|
+
tag "tweet:#{method.to_s}" do |tag|
|
201
|
+
tag.locals.tweet.send(method) if tag.locals.tweet.respond_to? method
|
202
|
+
end
|
203
|
+
|
204
|
+
desc %{
|
205
|
+
expands if the property has a value
|
206
|
+
<pre><code><r:tweet:if_#{method.to_s}/></code></pre>
|
207
|
+
}
|
208
|
+
tag "tweet:if_#{method.to_s}" do |tag|
|
209
|
+
value = tag.locals.tweet.send(method) if tag.locals.tweet.respond_to? method
|
210
|
+
tag.expand if !value.nil? && !value.empty?
|
211
|
+
end
|
212
|
+
|
213
|
+
desc %{
|
214
|
+
expands if the property has no value
|
215
|
+
<pre><code><r:tweet:unless_#{method.to_s}/></code></pre>
|
216
|
+
}
|
217
|
+
tag "tweet:unless_#{method.to_s}" do |tag|
|
218
|
+
value = tag.locals.tweet.send(method) if tag.locals.tweet.respond_to? method
|
219
|
+
tag.expand if value.nil? || value.empty?
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
[:date, :created_at].each do |method|
|
224
|
+
desc %{
|
225
|
+
renders the created_at timestamp of the tweet
|
226
|
+
<pre><code><r:tweet:#{method.to_s} [format="%c"]/></code></pre>
|
227
|
+
}
|
228
|
+
tag "tweet:#{method.to_s}" do |tag|
|
229
|
+
format = tag.attr['format'] || "%c"
|
230
|
+
date = DateTime.parse(tag.locals.tweet.created_at)
|
231
|
+
I18n.l date, :format => format
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
tag 'tweet:user' do |tag|
|
236
|
+
unless tag.locals.twitterer = tag.locals.tweet.user
|
237
|
+
tag.locals.twitterer = fetch_twitter_user(tag.locals.tweet.from_user)
|
238
|
+
end
|
239
|
+
raise TagError, "twitter user could not be found" unless tag.locals.twitterer
|
240
|
+
tag.expand
|
241
|
+
end
|
242
|
+
|
243
|
+
[:time_zone, :description, :lang, :profile_link_color, :profile_background_image_url, :profile_sidebar_fill_color, :following,
|
244
|
+
:profile_background_tile, :created_at, :statuses_count,:profile_sidebar_border_color,:profile_use_background_image,:followers_count,
|
245
|
+
:contributors_enabled,:notifications,:friends_count,:protected,:url,:profile_image_url,:geo_enabled,:profile_background_color,
|
246
|
+
:name,:favourites_count,:location,:screen_name, :id,:verified,:utc_offset,:profile_text_color].each do |method|
|
247
|
+
desc %{
|
248
|
+
Renders the @#{method.to_s}@ attribute of the tweeting user
|
249
|
+
<pre><code><r:tweet:user:#{method.to_s}/></code></pre>
|
250
|
+
}
|
251
|
+
tag "tweet:user:#{method.to_s}" do |tag|
|
252
|
+
tag.locals.twitterer.send(method)
|
253
|
+
end
|
254
|
+
|
255
|
+
desc %{
|
256
|
+
expands if @#{method.to_s}@ attribute of the tweeting user has a value
|
257
|
+
<pre><code><r:tweet:user:if_#{method.to_s}/></code></pre>
|
258
|
+
}
|
259
|
+
tag "tweet:user:if_#{method.to_s}" do |tag|
|
260
|
+
value = tag.locals.twitterer.send(method) rescue nil
|
261
|
+
tag.expand unless value.nil? || value.empty?
|
262
|
+
end
|
263
|
+
|
264
|
+
desc %{
|
265
|
+
expands if @#{method.to_s}@ attribute of the tweeting user has no value
|
266
|
+
<pre><code><r:tweet:user:unless_#{method.to_s}/></code></pre>
|
267
|
+
}
|
268
|
+
tag "tweet:user:unless_#{method.to_s}" do |tag|
|
269
|
+
value = tag.locals.twitterer.send(method) rescue nil
|
270
|
+
tag.expand if value.nil? || value.empty?
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
desc %{
|
275
|
+
Renders an avatar image for the tweeter of the current tweet.
|
276
|
+
}
|
277
|
+
tag 'tweet:avatar' do |tag|
|
278
|
+
url = tag.locals.tweet.profile_image_url || tag.render('tweet:user:profile_image_url')
|
279
|
+
%{<img src="#{url}" class="twitter_avatar" />}
|
280
|
+
end
|
281
|
+
|
282
|
+
desc %{
|
283
|
+
Renders the text for the current tweet.
|
284
|
+
}
|
285
|
+
tag 'tweet:text' do |tag|
|
286
|
+
tweet = tag.locals.tweet
|
287
|
+
replace_links(tweet.text)
|
288
|
+
end
|
289
|
+
|
290
|
+
desc %{
|
291
|
+
Renders the created ago string for the tweet e.g. Created 7 days...
|
292
|
+
}
|
293
|
+
tag 'tweet:created_ago' do |tag|
|
294
|
+
tweet = tag.locals.tweet
|
295
|
+
time_ago_in_words tweet.created_at
|
296
|
+
end
|
297
|
+
|
298
|
+
desc %{
|
299
|
+
Renders a permalink to this tweet with its date as the default link text.
|
300
|
+
}
|
301
|
+
tag 'tweet:permalink' do |tag|
|
302
|
+
cssclass = tag.attr['class'] || 'twitter_permalink'
|
303
|
+
text = tag.double? ? tag.expand : I18n.l(tag.locals.tweet.created_at, :twitter)
|
304
|
+
%{<a class="#{cssclass}" href="http://twitter.com/#!/#{screen_name}/status/#{tweet.id_str}">#{text}</a>}
|
305
|
+
end
|
306
|
+
|
307
|
+
desc %{
|
308
|
+
Renders a 'Reply' link that can be left as it is or hooked up by the twitter javascript.
|
309
|
+
}
|
310
|
+
tag 'tweet:reply_link' do |tag|
|
311
|
+
cssclass = tag.attr['class'] || 'twitter_reply'
|
312
|
+
text = tag.double? ? tag.expand : I18n.t('twitter_extension.reply')
|
313
|
+
%{<a class="#{cssclass}" href="http://twitter.com/intent/tweet?in_reply_to=#{tag.locals.tweet.id_str}">#{text}</a>}
|
314
|
+
end
|
315
|
+
|
316
|
+
desc %{
|
317
|
+
Renders a 'Retweet' link that can be left as it is or hooked up by the twitter javascript.
|
318
|
+
}
|
319
|
+
tag 'tweet:retweet_link' do |tag|
|
320
|
+
cssclass = tag.attr['class'] || 'twitter_retweet'
|
321
|
+
text = tag.double? ? tag.expand : I18n.t('twitter_extension.retweet')
|
322
|
+
%{<a class="#{cssclass}" href="http://twitter.com/intent/retweet?tweet_id=#{tag.locals.tweet.id_str}">#{text}</a>}
|
323
|
+
end
|
324
|
+
|
325
|
+
desc %{
|
326
|
+
Renders a 'Favorite' link that can be left as it is or hooked up by the twitter javascript.
|
327
|
+
}
|
328
|
+
tag 'tweet:favorite_link' do |tag|
|
329
|
+
cssclass = tag.attr['class'] || 'twitter_favorite'
|
330
|
+
text = tag.double? ? tag.expand : I18n.t('twitter_extension.favorite')
|
331
|
+
%{<a class="#{cssclass}" href="http://twitter.com/intent/favorite?tweet_id=#{tag.locals.tweet.id_str}">#{text}</a>}
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
|
336
|
+
# Retained for compatibility
|
337
|
+
#
|
338
|
+
def twitter_status(max = 1)
|
339
|
+
max = 1 if (max > 10) or (max < 1)
|
340
|
+
fetch_and_cache_tweets(:max => max)
|
341
|
+
end
|
342
|
+
|
343
|
+
# General-purpose tweet-fetcher using Rails::Cache to provide a calm-enhancing gap between similar
|
344
|
+
# requests. Set Radiant.config['twitter.expires_in'] to change the gap from 5 minutes.
|
345
|
+
# Always returns an array of tweet hashes (mashes, really).
|
346
|
+
# :max, :user, :list and :search options are used to determine the call we make (and the cache key).
|
347
|
+
# :page and :per_page options are passed through to the search call but not the user or list calls.
|
348
|
+
# other options are passed through (to non-search calls) unchanged.
|
349
|
+
#
|
350
|
+
def fetch_and_cache_tweets(options = {})
|
351
|
+
max = options.delete(:max) || 10
|
352
|
+
user = options.delete(:username) || Radiant.config['twitter.username']
|
353
|
+
list = options.delete(:list) || Radiant.config['twitter.listname']
|
354
|
+
search = options.delete(:search)
|
355
|
+
options[:count] ||= max
|
356
|
+
cache_key = ['twitter', list, user, max, search].compact.join('_')
|
357
|
+
begin
|
358
|
+
tweets = Rails.cache.fetch(cache_key,:expires_in => twitter_cache_duration) do
|
359
|
+
if search
|
360
|
+
Twitter::Search.new.containing(search).page(options[:page] || 1).per_page(options[:per_page] || 10).fetch
|
361
|
+
elsif list
|
362
|
+
twitter_client.list_timeline(user, list, options)
|
363
|
+
else
|
364
|
+
twitter_client.user_timeline(user, options)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
rescue Twitter::Error => e
|
369
|
+
logger.error "Unable to fetch timeline: #{e.inspect}"
|
370
|
+
end
|
371
|
+
|
372
|
+
tweets || []
|
373
|
+
end
|
374
|
+
|
375
|
+
def fetch_twitter_user(screen_name)
|
376
|
+
cache_key = "twitter_user_#{screen_name}"
|
377
|
+
begin
|
378
|
+
twitter_user = Rails.cache.fetch(cache_key,:expires_in => twitter_cache_duration) do
|
379
|
+
twitter_client.user(screen_name)
|
380
|
+
end
|
381
|
+
rescue Twitter::Error => e
|
382
|
+
logger.error "Unable to fetch user '#{screen_name}': #{e.inspect}"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# these operations don't require authentication
|
387
|
+
#
|
388
|
+
def twitter_client
|
389
|
+
@twitter_client ||= Twitter::Client.new
|
390
|
+
end
|
391
|
+
|
392
|
+
# Turns http
|
393
|
+
#
|
394
|
+
def replace_links(text)
|
395
|
+
text = text.gsub(/(https?:\/\/\S*)/, '<a class="twitter_link" href="\1">\1</a>')
|
396
|
+
text = text.gsub(/@(\w*)/, '@<a class="twitter_link" href="http://twitter.com/\1">\1</a>')
|
397
|
+
text = text.gsub(/#(\w*)/, '<a class="twitter_link" href="http://twitter.com/search/#\1">#\1</a>')
|
398
|
+
end
|
399
|
+
|
400
|
+
# The interval between twitter api calls with the same parameters is set by the
|
401
|
+
# `twitter.expires_in` config entry and defaults to 5 minutes.
|
402
|
+
#
|
403
|
+
def twitter_cache_duration
|
404
|
+
@twitter_expires_in ||= (Radiant::Config["twitter.expires_in"] || 5).to_i.minutes
|
405
|
+
end
|
406
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,46 @@
|
|
1
|
+
p.twitter
|
2
|
+
a.twitter_avatar
|
3
|
+
float: left
|
4
|
+
margin-right: 0.8em
|
5
|
+
img
|
6
|
+
margin-top: 0.8em
|
7
|
+
span.tweet
|
8
|
+
display: block
|
9
|
+
overflow: hidden
|
10
|
+
a.twitter_user
|
11
|
+
font-size: 150%
|
12
|
+
font-weight: normal
|
13
|
+
span.twitter_name
|
14
|
+
color: #b2b2b2
|
15
|
+
span.twitter_text
|
16
|
+
display: block
|
17
|
+
span.twitter_links
|
18
|
+
display: block
|
19
|
+
font-size: 80%
|
20
|
+
a
|
21
|
+
padding-left: 18px
|
22
|
+
height: 16px
|
23
|
+
color: #b2b2b2
|
24
|
+
background:
|
25
|
+
position: top left
|
26
|
+
repeat: no-repeat
|
27
|
+
image: url(/images/twitter/sprite.png)
|
28
|
+
&:hover
|
29
|
+
color: #666666
|
30
|
+
a.twitter_permalink
|
31
|
+
a.twitter_reply
|
32
|
+
background-position: 0 -16px
|
33
|
+
&:hover
|
34
|
+
background-position: 0 -32px
|
35
|
+
a.twitter_favourite
|
36
|
+
background-position: 0 -48px
|
37
|
+
&:hover
|
38
|
+
background-position: 0 -64px
|
39
|
+
a.twitter_retweet
|
40
|
+
background-position: 0 -96px
|
41
|
+
&:hover
|
42
|
+
background-position: 0 -112px
|
43
|
+
a.twitter_favorite
|
44
|
+
background-position: 0 -80px
|
45
|
+
&:hover
|
46
|
+
background-position: 0 -112px
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "radiant-twitter-extension"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "radiant-twitter-extension"
|
7
|
+
s.version = RadiantTwitterExtension::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = RadiantTwitterExtension::AUTHORS
|
10
|
+
s.email = RadiantTwitterExtension::EMAIL
|
11
|
+
s.homepage = RadiantTwitterExtension::URL
|
12
|
+
s.summary = RadiantTwitterExtension::SUMMARY
|
13
|
+
s.description = RadiantTwitterExtension::DESCRIPTION
|
14
|
+
|
15
|
+
s.add_dependency 'twitter', "~> 1.6.0"
|
16
|
+
|
17
|
+
ignores = if File.exist?('.gitignore')
|
18
|
+
File.read('.gitignore').split("\n").inject([]) {|a,p| a + Dir[p] }
|
19
|
+
else
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
s.files = Dir['**/*'] - ignores
|
23
|
+
s.test_files = Dir['test/**/*','spec/**/*','features/**/*'] - ignores
|
24
|
+
# s.executables = Dir['bin/*'] - ignores
|
25
|
+
s.require_paths = ["lib"]
|
26
|
+
|
27
|
+
s.post_install_message = %{
|
28
|
+
Add this to your radiant project with:
|
29
|
+
config.gem 'radiant-twitter-extension', :version => '~>#{RadiantTwitterExtension::VERSION}'
|
30
|
+
}
|
31
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe 'TwitterTags' do
|
4
|
+
dataset :pages
|
5
|
+
|
6
|
+
before do
|
7
|
+
Radiant.config['twitter.username'] = 'testy'
|
8
|
+
Radiant.config['twitter.password'] = 'secret'
|
9
|
+
|
10
|
+
@client = mock("HTTPAuth (client)").as_null_object
|
11
|
+
|
12
|
+
# I've been getting odd failures with mock tweets
|
13
|
+
# when they're passed into a radius context
|
14
|
+
|
15
|
+
@tweets = (1..10).collect do |i|
|
16
|
+
OpenStruct.new.tap do |tweet|
|
17
|
+
tweet.text = "tweet #{i}"
|
18
|
+
tweet.created_at = DateTime.new(2010, 2, i+1).to_s
|
19
|
+
tweet.source = "<a href=\"http://www.atebits.com/\" rel=\"nofollow\">Tweetie</a>"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '<r:twitter> The main context' do
|
25
|
+
it 'should give no output' do
|
26
|
+
tag = %{<r:twitter />}
|
27
|
+
pages(:home).should render(tag).as('')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '<r:twitter:tweets> Collect the users recent tweets' do
|
32
|
+
before(:each) do
|
33
|
+
Twitter::Client.stub!(:new).and_return(@client)
|
34
|
+
@client.stub!(:user_timeline).and_return(@tweets)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should give no output' do
|
38
|
+
tag = %{<r:twitter><r:tweets></r:tweets></r:twitter>}
|
39
|
+
pages(:home).should render(tag).as('')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should report the correct number of tweets' do
|
43
|
+
tag = %{<r:twitter><r:tweets:length /></r:twitter>}
|
44
|
+
pages(:home).should render(tag).as("10")
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should return the tweet text and replace links' do
|
48
|
+
tag = %{<r:twitter><r:tweets:each><r:tweet:text /></r:tweets:each></r:twitter>}
|
49
|
+
expected = @tweets.map(&:text).join('')
|
50
|
+
pages(:home).should render(tag).as(expected)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should return the created at timestamp' do
|
54
|
+
tag = %{<r:twitter><r:tweets:each><r:tweet:created_at format="%d %B" /></r:tweets:each></r:twitter>}
|
55
|
+
expected = (1..10).map{|i| "#{'%02d' % (i+1)} February"}.join('')
|
56
|
+
pages(:home).should render(tag).as(expected)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should return the created ago string' do
|
60
|
+
tag = %{<r:twitter><r:tweets:each><r:tweet:created_ago /></r:tweets:each></r:twitter>}
|
61
|
+
Time.stub!(:now).and_return(Time.parse("Feb 12 2010"))
|
62
|
+
expected = '10 days9 days8 days7 days6 days5 days4 days3 days2 days1 day'
|
63
|
+
pages(:home).should render(tag).as(expected)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should return the source' do
|
67
|
+
tag = %{<r:twitter><r:tweets:each><r:tweet:source /></r:tweets:each></r:twitter>}
|
68
|
+
expected = @tweets.map(&:source).join('')
|
69
|
+
pages(:home).should render(tag).as(expected)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '<r:twitter:list list="list"> Collect the users list tweets' do
|
74
|
+
before(:each) do
|
75
|
+
Twitter::Client.stub!(:new).and_return(@client)
|
76
|
+
@client.stub!(:list_timeline).and_return(@tweets)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should give no output' do
|
80
|
+
tag = %{<r:twitter><r:list list="list"></r:list></r:twitter>}
|
81
|
+
pages(:home).should render(tag).as('')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should report the correct number of tweets (default 10)' do
|
85
|
+
tag = %{<r:twitter><r:list list="list"><r:length /></r:list></r:twitter>}
|
86
|
+
pages(:home).should render(tag).as("10")
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should return the tweet text and replace links' do
|
90
|
+
tag = %{<r:twitter><r:list list="list"><r:each><r:tweet:text /></r:each></r:list></r:twitter>}
|
91
|
+
expected = @tweets.map(&:text).join('')
|
92
|
+
pages(:home).should render(tag).as(expected)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should return the created at timestamp' do
|
96
|
+
tag = %{<r:twitter><r:list list="list"><r:each><r:tweet:created_at format="%d %B" /></r:each></r:list></r:twitter>}
|
97
|
+
expected = (1..10).map{|i| "#{'%02d' % (i+1)} February"}.join('')
|
98
|
+
pages(:home).should render(tag).as(expected)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should return the created ago string' do
|
102
|
+
tag = %{<r:twitter><r:list list="list"><r:each><r:tweet:created_ago /></r:each></r:list></r:twitter>}
|
103
|
+
Time.stub!(:now).and_return(Time.parse("Feb 12 2010"))
|
104
|
+
expected = '10 days9 days8 days7 days6 days5 days4 days3 days2 days1 day'
|
105
|
+
pages(:home).should render(tag).as(expected)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should return the source' do
|
109
|
+
tag = %{<r:twitter><r:list list="list"><r:each><r:tweet:source /></r:each></r:list></r:twitter>}
|
110
|
+
expected = @tweets.map(&:source).join('')
|
111
|
+
pages(:home).should render(tag).as(expected)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
unless defined? RADIANT_ROOT
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
case
|
4
|
+
when ENV["RADIANT_ENV_FILE"]
|
5
|
+
require ENV["RADIANT_ENV_FILE"]
|
6
|
+
when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
|
7
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../../")}/config/environment"
|
8
|
+
else
|
9
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../")}/config/environment"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
require "#{RADIANT_ROOT}/spec/spec_helper"
|
13
|
+
|
14
|
+
if File.directory?(File.dirname(__FILE__) + "/scenarios")
|
15
|
+
Scenario.load_paths.unshift File.dirname(__FILE__) + "/scenarios"
|
16
|
+
end
|
17
|
+
if File.directory?(File.dirname(__FILE__) + "/matchers")
|
18
|
+
Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
|
19
|
+
end
|
20
|
+
|
21
|
+
Spec::Runner.configure do |config|
|
22
|
+
# config.use_transactional_fixtures = true
|
23
|
+
# config.use_instantiated_fixtures = false
|
24
|
+
config.fixture_path = File.dirname(__FILE__) + '/spec/fixtures'
|
25
|
+
|
26
|
+
# You can declare fixtures for each behaviour like this:
|
27
|
+
# describe "...." do
|
28
|
+
# fixtures :table_a, :table_b
|
29
|
+
#
|
30
|
+
# Alternatively, if you prefer to declare them only once, you can
|
31
|
+
# do so here, like so ...
|
32
|
+
#
|
33
|
+
# config.global_fixtures = :table_a, :table_b
|
34
|
+
#
|
35
|
+
# If you declare global fixtures, be aware that they will be declared
|
36
|
+
# for all of your examples, even those that don't use them.
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class TwitterExtension < Radiant::Extension
|
2
|
+
version RadiantTwitterExtension::VERSION
|
3
|
+
description RadiantTwitterExtension::DESCRIPTION
|
4
|
+
url RadiantTwitterExtension::URL
|
5
|
+
|
6
|
+
extension_config do |config|
|
7
|
+
config.gem "twitter", :version => "~> 1.6.0"
|
8
|
+
end
|
9
|
+
|
10
|
+
def activate
|
11
|
+
Page.send :include, TwitterNotification # tweet page title upon publication
|
12
|
+
Page.send :include, TwitterTags # radius tags to display twitter search results
|
13
|
+
|
14
|
+
admin.pages.edit.add :extended_metadata, "twitter" # toggle twitter-posting at parent level
|
15
|
+
admin.configuration.show.add :config, 'admin/configuration/twitter_show', :after => 'defaults'
|
16
|
+
admin.configuration.edit.add :form, 'admin/configuration/twitter_edit', :after => 'edit_defaults'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,56 @@
|
|
1
|
+
Filestore Cache Expires_in Plugin
|
2
|
+
============================
|
3
|
+
|
4
|
+
Adds a :expires_in option to Rails filestore caching, in line with memcached store. Providing a very easy way to add high capacity caching to a site.
|
5
|
+
|
6
|
+
Model observers and sweepers are still useable as well, of course.
|
7
|
+
|
8
|
+
Filestore caching is a much easier method of caching to implement quickly than memcached, since it just involves writing the cache data to files in the tmp/ directory (also making it good for shared hosting environments).
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------
|
12
|
+
|
13
|
+
1) Install plugin
|
14
|
+
|
15
|
+
./script/plugin install git://github.com/adamsalter/filestore_expires_in-plugin.git
|
16
|
+
|
17
|
+
2) Enable :file_store caching
|
18
|
+
|
19
|
+
config/environments/production.rb:
|
20
|
+
|
21
|
+
config.action_controller.perform_caching = true
|
22
|
+
config.cache_store = :file_store, "#{RAILS_ROOT}/tmp/cache"
|
23
|
+
|
24
|
+
Useage
|
25
|
+
------
|
26
|
+
|
27
|
+
On controller classes:
|
28
|
+
|
29
|
+
class MyController < ApplicationController
|
30
|
+
caches_action :show, {:if => etc.}, {:expires_in => 1.day}
|
31
|
+
end
|
32
|
+
|
33
|
+
In controller methods:
|
34
|
+
|
35
|
+
unless read_fragment({:controller => 'mycontroller', etc.}, {:expires_in => 1.day})
|
36
|
+
#code here
|
37
|
+
end
|
38
|
+
|
39
|
+
In views:
|
40
|
+
|
41
|
+
cache({:controller => 'mycontroller', etc.}, {:expires_in => 1.day}) do
|
42
|
+
#fragment here
|
43
|
+
end
|
44
|
+
|
45
|
+
In models:
|
46
|
+
|
47
|
+
cache_key = "models/my_model/%s?%s-%s-%s" % ["model_method_name", self.value, page, limit]
|
48
|
+
|
49
|
+
result = Rails.cache.fetch(cache_key, :expires_in => 1.day) do
|
50
|
+
# result data to be cached
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
Copyright (c) 2009 Adam @ [Codebright.net][cb], released under the MIT license
|
55
|
+
|
56
|
+
[cb]:http://codebright.net
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module ActiveSupport
|
3
|
+
module Cache
|
4
|
+
class FileStore < Store
|
5
|
+
def read(name, options = nil)
|
6
|
+
super
|
7
|
+
file_name = real_file_path(name)
|
8
|
+
expires = expires_in(options)
|
9
|
+
|
10
|
+
if exist_without_instrument?(file_name, expires)
|
11
|
+
File.open(file_name, 'rb') { |f| Marshal.load(f) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def exist?(name, options = nil)
|
16
|
+
super
|
17
|
+
File.exist?(real_file_path(name))
|
18
|
+
exist_without_instrument?(real_file_path(name), expires_in(options))
|
19
|
+
end
|
20
|
+
|
21
|
+
def exist_without_instrument?(file_name, expires)
|
22
|
+
File.exist?(file_name) && (expires <= 0 || Time.now - File.mtime(file_name) < expires)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: radiant-twitter-extension
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15424087
|
5
|
+
prerelease: 6
|
6
|
+
segments:
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
- rc
|
11
|
+
- 1
|
12
|
+
version: 2.0.0.rc1
|
13
|
+
platform: ruby
|
14
|
+
authors:
|
15
|
+
- Sean Cribbs
|
16
|
+
- Edmund Haselwanter
|
17
|
+
- Jim Gay
|
18
|
+
- William Ross
|
19
|
+
autorequire:
|
20
|
+
bindir: bin
|
21
|
+
cert_chain: []
|
22
|
+
|
23
|
+
date: 2011-07-28 00:00:00 +01:00
|
24
|
+
default_executable:
|
25
|
+
dependencies:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: twitter
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
hash: 15
|
35
|
+
segments:
|
36
|
+
- 1
|
37
|
+
- 6
|
38
|
+
- 0
|
39
|
+
version: 1.6.0
|
40
|
+
type: :runtime
|
41
|
+
version_requirements: *id001
|
42
|
+
description: Posts notification of pages to Twitter and provides radiius tags to display the results of twitter searches.
|
43
|
+
email:
|
44
|
+
- radiant@radiantcms.org
|
45
|
+
executables: []
|
46
|
+
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
extra_rdoc_files: []
|
50
|
+
|
51
|
+
files:
|
52
|
+
- app/views/admin/configuration/_twitter_edit.html.haml
|
53
|
+
- app/views/admin/configuration/_twitter_show.html.haml
|
54
|
+
- app/views/admin/pages/_twitter.html.haml
|
55
|
+
- config/initializers/radiant_config.rb
|
56
|
+
- config/locales/en.yml
|
57
|
+
- db/migrate/001_add_twitter_notification_fields.rb
|
58
|
+
- HELP_admin.md
|
59
|
+
- lib/radiant-twitter-extension.rb
|
60
|
+
- lib/tasks/twitter_extension_tasks.rake
|
61
|
+
- lib/twitter_notification.rb
|
62
|
+
- lib/twitter_tags.rb
|
63
|
+
- public/images/twitter/bird.png
|
64
|
+
- public/images/twitter/sprite.png
|
65
|
+
- public/stylesheets/sass/twitter.sass
|
66
|
+
- radiant-twitter-extension.gemspec
|
67
|
+
- Rakefile
|
68
|
+
- README.md
|
69
|
+
- spec/lib/twitter_tags_spec.rb
|
70
|
+
- spec/spec.opts
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- twitter_extension.rb
|
73
|
+
- vendor/plugins/filestore_expires_in-plugin/init.rb
|
74
|
+
- vendor/plugins/filestore_expires_in-plugin/lib/active_support/cache/file_store_extras.rb
|
75
|
+
- vendor/plugins/filestore_expires_in-plugin/MIT-LICENSE
|
76
|
+
- vendor/plugins/filestore_expires_in-plugin/README.md
|
77
|
+
has_rdoc: true
|
78
|
+
homepage: http://github.com/ehaselwanter/radiant-twitter-extension
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message: "\n Add this to your radiant project with:\n config.gem 'radiant-twitter-extension', :version => '~>2.0.0.rc1'\n "
|
82
|
+
rdoc_options: []
|
83
|
+
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 25
|
101
|
+
segments:
|
102
|
+
- 1
|
103
|
+
- 3
|
104
|
+
- 1
|
105
|
+
version: 1.3.1
|
106
|
+
requirements: []
|
107
|
+
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.5.3
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: Twitter posting and radius tags for twitter feeds.
|
113
|
+
test_files:
|
114
|
+
- spec/lib/twitter_tags_spec.rb
|
115
|
+
- spec/spec.opts
|
116
|
+
- spec/spec_helper.rb
|