murlsh 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -11,6 +11,7 @@ Site for sharing and archiving links.
11
11
  * plugin interface
12
12
  * rack interface
13
13
  * Gravatar support
14
+ * imports Netscape bookmark format files
14
15
 
15
16
  See "http://urls.matthewm.boedicker.org/":http://urls.matthewm.boedicker.org/ for example.
16
17
 
data/Rakefile CHANGED
@@ -4,9 +4,11 @@ require 'cgi'
4
4
  require 'digest/md5'
5
5
  require 'net/http'
6
6
  require 'pp'
7
+ require 'set'
7
8
  require 'uri'
8
9
  require 'yaml'
9
10
 
11
+ require 'active_record'
10
12
  require 'RMagick'
11
13
  require 'sqlite3'
12
14
 
@@ -70,7 +72,7 @@ namespace :db do
70
72
  task :init do
71
73
  puts "creating #{config.fetch('db_file')}"
72
74
  db = SQLite3::Database.new(config.fetch('db_file'))
73
- db.execute('CREATE TABLE urls (
75
+ db.execute 'CREATE TABLE urls (
74
76
  id INTEGER PRIMARY KEY,
75
77
  time TIMESTAMP,
76
78
  url TEXT,
@@ -81,7 +83,8 @@ namespace :db do
81
83
  content_type TEXT,
82
84
  via TEXT,
83
85
  thumbnail_url TEXT);
84
- ')
86
+ '
87
+ db.execute 'CREATE INDEX IF NOT EXISTS urls_time_desc ON urls (time DESC);'
85
88
  end
86
89
 
87
90
  desc 'Interact with the database.'
@@ -133,13 +136,6 @@ begin
133
136
  Spec::Rake::SpecTask.new('test') do |t|
134
137
  t.spec_files = FileList['spec/*_spec.rb']
135
138
  t.spec_opts = %w{--color}
136
- # list of places to check for unicode_formatter.rb and use it if found
137
- %w{unicode_formatter.rb}.map { |x| File.expand_path(x) }.each do |f|
138
- if File.exists?(f)
139
- t.spec_opts.push(*%W{--require #{f} --format UnicodeFormatter})
140
- break
141
- end
142
- end
143
139
  t.verbose = true
144
140
  t.warning = true
145
141
  end
@@ -183,7 +179,7 @@ namespace :user do
183
179
  end
184
180
 
185
181
  # Validate a document with the W3C validation service.
186
- def validate(check_url, options={})
182
+ def validate_html(check_url, options={})
187
183
  opts = {
188
184
  :validator_host => 'validator.w3.org',
189
185
  :validator_port => 80,
@@ -217,7 +213,7 @@ namespace :validate do
217
213
  task :html do
218
214
  check_url = config['root_url']
219
215
  print "validating #{check_url} : "
220
- result = validate(check_url)
216
+ result = validate_html(check_url)
221
217
  if Net::HTTPSuccess === result[:response]
222
218
  puts "#{result[:status]} (#{result[:errors]} errors, #{result[:warnings]} warnings)"
223
219
  else
@@ -346,11 +342,13 @@ namespace :thumb do
346
342
  task :check do
347
343
  ActiveRecord::Base.establish_connection :adapter => 'sqlite3',
348
344
  :database => config.fetch('db_file')
345
+ used_thumbnails = Set.new
349
346
  Murlsh::Url.all(
350
347
  :conditions => "thumbnail_url like 'img/thumb/%'").each do |u|
351
348
  identity = "url #{u.id} (#{u.url})"
352
349
 
353
350
  path = File.join(%w{public}.concat(File.split(u.thumbnail_url)))
351
+ used_thumbnails.add(path)
354
352
  if File.readable?(path)
355
353
  img_data = open(path) { |f| f.read }
356
354
 
@@ -376,6 +374,40 @@ namespace :thumb do
376
374
  puts "#{identity} thumbnail #{path} does not exist or is not readable"
377
375
  end
378
376
  end
377
+ # check if all thumbnail files that exist are in the database
378
+ (Dir['public/img/thumb/*'] - used_thumbnails.to_a).each do |t|
379
+ puts "thumbnail #{t} is not used"
380
+ end
381
+ end
382
+
383
+ end
384
+
385
+ namespace :import do
386
+
387
+ desc 'Convert a delicious xml export into an import shell script.'
388
+ task :delicious, :source do |t, args|
389
+ puts <<EOS
390
+ #!/bin/sh
391
+
392
+ # murlsh import, source #{args.source}
393
+
394
+ PASSWORD="$1"
395
+ if [ -z "${PASSWORD}" ] ; then
396
+ echo 'Password not set, pass as command line argument or hardcode in script'
397
+ exit 1
398
+ fi
399
+
400
+ SITE_URL='#{config.fetch('root_url')}'
401
+
402
+ EOS
403
+ Murlsh.delicious_parse(args.source) do |b|
404
+ # escape single quotes because these will be in single quotes in output
405
+ href_escaped = b[:href].to_s.gsub("'", "'\"'\"'")
406
+ via_url_escaped = b[:via_url].to_s.gsub("'", "'\"'\"'")
407
+ puts <<EOS
408
+ curl --data-urlencode 'url=#{href_escaped}' --data-urlencode "auth=${PASSWORD}" --data-urlencode 'via=#{via_url_escaped}' --data-urlencode 'time=#{b[:time].to_i}' ${SITE_URL}
409
+ EOS
410
+ end
379
411
  end
380
412
 
381
413
  end
@@ -422,6 +454,7 @@ begin
422
454
  }.each_slice(3) { |g,o,v| gemspec.add_dependency(g, "#{o} #{v}") }
423
455
  %w{
424
456
  flog >= 2.5.0
457
+ rack-test ~> 0.5
425
458
  rspec ~> 1.3
426
459
  }.each_slice(3) do |g,o,v|
427
460
  gemspec.add_development_dependency(g, "#{o} #{v}")
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.2.1
data/config.ru CHANGED
@@ -41,6 +41,6 @@ end
41
41
 
42
42
  # use Rack::Lint
43
43
 
44
- Dir['plugins/*.rb'].each { |p| require p }
44
+ Dir['plugins/*.rb'].each { |p| require "./#{p}" }
45
45
 
46
46
  run Murlsh::Dispatch.new(config)
data/config.yaml CHANGED
@@ -20,7 +20,7 @@ meta_tag_description: URLs found interesting by Matthew M. Boedicker
20
20
  meta_tag_verify-v1:
21
21
  meta_tag_viewport: width=device-width,minimum-scale=1.0,maximum-scale=1.0
22
22
  num_posts_feed: 25
23
- num_posts_page: 100
23
+ num_posts_page: 25
24
24
  page_title: mmb url share
25
25
  pubsubhubbub_hubs: []
26
26
 
data/lib/murlsh/auth.rb CHANGED
@@ -17,9 +17,20 @@ module Murlsh
17
17
 
18
18
  def initialize(file); @file = file; end
19
19
 
20
+ # Handle differences in csv interface between ruby 1.8 and 1.9.
21
+ def self.csv_iter(csv_file, &block)
22
+ if defined?(CSV::Reader)
23
+ # ruby 1.8
24
+ CSV::Reader.parse(open(csv_file), &block)
25
+ else
26
+ # ruby 1.9
27
+ CSV.foreach(csv_file, &block)
28
+ end
29
+ end
30
+
20
31
  # Authenticate a user by password. Return their name and email if correct.
21
32
  def auth(password)
22
- CSV::Reader.parse(open(@file)) do |row|
33
+ self.class.csv_iter(@file) do |row|
23
34
  return { :name => row[0], :email => row[1] } if
24
35
  BCrypt::Password.new(row[2]) == password
25
36
  end
@@ -0,0 +1,38 @@
1
+ require 'open-uri'
2
+ require 'uri'
3
+
4
+ require 'nokogiri'
5
+
6
+ module Murlsh
7
+
8
+ module_function
9
+
10
+ # Parse a delicious xml export and yield a hash for each bookmark.
11
+ #
12
+ # To export your delicious bookmarks:
13
+ # curl https://user:password@api.del.icio.us/v1/posts/all > delicious.xml
14
+ def delicious_parse(source)
15
+ doc = Nokogiri::XML(open(source))
16
+
17
+ doc.xpath('//post').each do |p|
18
+ result = {}
19
+ p.each { |k,v| result[k.to_sym] = v }
20
+
21
+ result[:tag] = result[:tag].split
22
+ result[:time] = Time.parse(result[:time])
23
+
24
+ # extract via information from extended
25
+ result[:via] = result[:extended].chomp(')')[%r{via\s+([^\s]+)}, 1]
26
+ result[:via_url] = begin
27
+ if result[:via] and
28
+ %w{http https}.include?(URI(result[:via]).scheme.to_s.downcase)
29
+ result[:via]
30
+ end
31
+ rescue URI::InvalidURIError
32
+ end
33
+
34
+ yield result
35
+ end
36
+ end
37
+
38
+ end
@@ -35,6 +35,7 @@ module Murlsh
35
35
 
36
36
  ActiveRecord::Base.default_timezone = :utc
37
37
  ActiveRecord::Base.include_root_in_json = false
38
+ # ActiveRecord::Base.logger = Logger.new(STDERR)
38
39
  end
39
40
 
40
41
  # Figure out which method will handle request.
data/lib/murlsh/doc.rb CHANGED
@@ -10,7 +10,7 @@ module Murlsh
10
10
  unless content_type.nil?
11
11
  content = content_type['content']
12
12
  unless content.nil?
13
- charset = content[/charset=([\w_.:-]+)/, 1]
13
+ charset = content[/charset=([\w.:-]+)/, 1]
14
14
  return charset if charset
15
15
  end
16
16
  end
@@ -99,7 +99,7 @@ module Murlsh
99
99
  'User-Agent' =>
100
100
  'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4) Gecko/20030624',
101
101
  }
102
- if (host || '')[/^www\.nytimes\.com/]
102
+ if host.to_s[/^www\.nytimes\.com/]
103
103
  result['Referer'] = 'http://news.google.com/'
104
104
  end
105
105
 
@@ -125,7 +125,7 @@ module Murlsh
125
125
  def header(header_name, options={})
126
126
  result = [*head_headers(options)[header_name]][0]
127
127
  result = get_headers(options)[header_name] if not result or result.empty?
128
- result || ''
128
+ result.to_s
129
129
  end
130
130
 
131
131
  # Get and cache response headers returned by HTTP HEAD for this URI.
@@ -9,14 +9,44 @@ module Murlsh
9
9
  def initialize(config, req, content_type='text/html')
10
10
  @config, @req, @q, @content_type =
11
11
  config, req, req.params['q'], content_type
12
+ @page = [req.params['p'].to_i, 1].max
13
+
14
+ @first_href, @prev_href, @next_href = page_href(1), nil, nil
15
+
16
+ @total_entries, @total_pages = 0, 0
17
+
18
+ @per_page = @req.params['pp'] ? @req.params['pp'].to_i :
19
+ config.fetch('num_posts_page', 25)
20
+
12
21
  super(:indent => @config['html_indent'] || 0)
13
22
  end
14
23
 
24
+ # Get the href of a page in the same result set as this page.
25
+ def page_href(page)
26
+ query = @req.params.dup
27
+ query['p'] = page
28
+ Murlsh.build_query(query)
29
+ end
30
+
15
31
  # Fetch urls based on query string parameters.
16
32
  def urls
17
- Murlsh::Url.all(:conditions => search_conditions, :order => 'id DESC',
18
- :limit => @req.params['n'] ? @req.params['n'].to_i :
19
- @config.fetch('num_posts_page', 100))
33
+ search = search_conditions
34
+
35
+ @total_entries = Murlsh::Url.count(:conditions => search)
36
+ @total_pages = (@total_entries / @per_page.to_f).ceil
37
+
38
+ if @page > 1 and @page <= @total_pages
39
+ @prev_href = page_href(@page - 1)
40
+ end
41
+
42
+ if @page < @total_pages
43
+ @next_href = page_href(@page + 1)
44
+ end
45
+
46
+ offset = (@page - 1) * @per_page
47
+
48
+ Murlsh::Url.all(:conditions => search_conditions, :order => 'time DESC',
49
+ :limit => @per_page, :offset => offset)
20
50
  end
21
51
 
22
52
  # Search conditions builder for ActiveRecord conditions.
@@ -32,6 +62,8 @@ module Murlsh
32
62
 
33
63
  # Url list page body builder.
34
64
  def each
65
+ mus = urls
66
+
35
67
  declare! :DOCTYPE, :html
36
68
 
37
69
  yield html(:lang => 'en') {
@@ -41,7 +73,8 @@ module Murlsh
41
73
  li { feed_icon ; search_form }
42
74
 
43
75
  last = nil
44
- urls.each do |mu|
76
+
77
+ mus.each do |mu|
45
78
  li {
46
79
  unless mu.same_author?(last)
47
80
  avatar_url = Murlsh::Plugin.hooks('avatar').inject(
@@ -70,6 +103,8 @@ module Murlsh
70
103
  }
71
104
  end
72
105
 
106
+ li { paging_nav }
107
+
73
108
  li { add_form }
74
109
  }
75
110
 
@@ -90,6 +125,9 @@ module Murlsh
90
125
  map { |k,v| [k.sub('meta_tag_', ''), v] })
91
126
  css(@config['css_compressed'] || @config['css_files'])
92
127
  atom @config.fetch('feed_file')
128
+ link :rel => 'first', :href => @first_href
129
+ link :rel => 'prev', :href => @prev_href if @prev_href
130
+ link :rel => 'next', :href => @next_href if @next_href
93
131
  }
94
132
  end
95
133
 
@@ -115,6 +153,17 @@ module Murlsh
115
153
  }
116
154
  end
117
155
 
156
+ # Paging navigation.
157
+ def paging_nav
158
+ text! "Page #{@page}/#{@total_pages}"
159
+ if @prev_href
160
+ text! ' | '; a 'previous', :href => @prev_href
161
+ end
162
+ if @next_href
163
+ text! ' | '; a 'next', :href => @next_href
164
+ end
165
+ end
166
+
118
167
  # Url add form builder.
119
168
  def add_form
120
169
  form(:action => '', :method => 'post') {
@@ -36,11 +36,15 @@ module Murlsh
36
36
  @config.fetch('auth_file')).auth(auth)
37
37
 
38
38
  mu = Murlsh::Url.new do |u|
39
- u.time = Time.now.gmtime
39
+ u.time = if req.params['time']
40
+ Time.at(req.params['time'].to_f).utc
41
+ else
42
+ Time.now.utc
43
+ end
40
44
  u.url = req.params['url']
41
45
  u.email = user[:email]
42
46
  u.name = user[:name]
43
- u.via = req.params['via'] unless (req.params['via'] || []).empty?
47
+ u.via = req.params['via'] unless req.params['via'].to_s.empty?
44
48
  end
45
49
 
46
50
  begin
data/lib/murlsh.rb CHANGED
@@ -3,6 +3,7 @@ require 'murlsh/head_from_get'
3
3
  require 'murlsh/auth'
4
4
  require 'murlsh/build_query'
5
5
  require 'murlsh/config_server'
6
+ require 'murlsh/delicious_parse'
6
7
  require 'murlsh/dispatch'
7
8
  require 'murlsh/doc'
8
9
  require 'murlsh/etag_add_encoding'
data/murlsh.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{murlsh}
8
- s.version = "1.2.0"
8
+ s.version = "1.2.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Matthew M. Boedicker"]
12
- s.date = %q{2010-12-16}
12
+ s.date = %q{2010-12-23}
13
13
  s.default_executable = %q{murlsh}
14
14
  s.description = %q{url sharing site framework with easy adding, title lookup, atom feed, thumbnails and embedding}
15
15
  s.email = %q{matthewm@boedicker.org}
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
30
30
  "lib/murlsh/auth.rb",
31
31
  "lib/murlsh/build_query.rb",
32
32
  "lib/murlsh/config_server.rb",
33
+ "lib/murlsh/delicious_parse.rb",
33
34
  "lib/murlsh/dispatch.rb",
34
35
  "lib/murlsh/doc.rb",
35
36
  "lib/murlsh/etag_add_encoding.rb",
@@ -57,6 +58,7 @@ Gem::Specification.new do |s|
57
58
  "plugins/add_post_50_update_rss.rb",
58
59
  "plugins/add_post_60_notify_hubs.rb",
59
60
  "plugins/add_pre_40_convert_mobile.rb",
61
+ "plugins/add_pre_41_unajax_twitter.rb",
60
62
  "plugins/add_pre_50_lookup_content_type_title.rb",
61
63
  "plugins/add_pre_50_media_thumbnail.rb",
62
64
  "plugins/add_pre_50_open_graph_image.rb",
@@ -135,6 +137,7 @@ Gem::Specification.new do |s|
135
137
  s.add_runtime_dependency(%q<twitter>, [">= 0.9.12"])
136
138
  s.add_runtime_dependency(%q<vimeo>, [">= 1.2.2"])
137
139
  s.add_development_dependency(%q<flog>, [">= 2.5.0"])
140
+ s.add_development_dependency(%q<rack-test>, ["~> 0.5"])
138
141
  s.add_development_dependency(%q<rspec>, ["~> 1.3"])
139
142
  else
140
143
  s.add_dependency(%q<activerecord>, [">= 2.3.4"])
@@ -157,6 +160,7 @@ Gem::Specification.new do |s|
157
160
  s.add_dependency(%q<twitter>, [">= 0.9.12"])
158
161
  s.add_dependency(%q<vimeo>, [">= 1.2.2"])
159
162
  s.add_dependency(%q<flog>, [">= 2.5.0"])
163
+ s.add_dependency(%q<rack-test>, ["~> 0.5"])
160
164
  s.add_dependency(%q<rspec>, ["~> 1.3"])
161
165
  end
162
166
  else
@@ -180,6 +184,7 @@ Gem::Specification.new do |s|
180
184
  s.add_dependency(%q<twitter>, [">= 0.9.12"])
181
185
  s.add_dependency(%q<vimeo>, [">= 1.2.2"])
182
186
  s.add_dependency(%q<flog>, [">= 2.5.0"])
187
+ s.add_dependency(%q<rack-test>, ["~> 0.5"])
183
188
  s.add_dependency(%q<rspec>, ["~> 1.3"])
184
189
  end
185
190
  end
@@ -26,7 +26,7 @@ module Murlsh
26
26
  :hubs => config.fetch('pubsubhubbub_hubs', []).
27
27
  map { |x| x['subscribe_url'] } )
28
28
 
29
- latest = Murlsh::Url.all(:order => 'id DESC',
29
+ latest = Murlsh::Url.all(:order => 'time DESC',
30
30
  :limit => config.fetch('num_posts_feed', 25))
31
31
 
32
32
  latest.each do |mu|
@@ -21,7 +21,7 @@ module Murlsh
21
21
  f.items.do_sort = true
22
22
 
23
23
  Murlsh::Url.all(:conditions => { :content_type => 'audio/mpeg' },
24
- :order => 'id DESC',
24
+ :order => 'time DESC',
25
25
  :limit => config.fetch('num_posts_feed', 25)).each do |mu|
26
26
  i = f.items.new_item
27
27
  i.title = mu.title_stripped
@@ -27,7 +27,7 @@ module Murlsh
27
27
  f.channel.link = URI.join(config.fetch('root_url'), output_file)
28
28
  f.items.do_sort = true
29
29
 
30
- Murlsh::Url.all(:order => 'id DESC',
30
+ Murlsh::Url.all(:order => 'time DESC',
31
31
  :limit => config.fetch('num_posts_feed', 25)).each do |mu|
32
32
  i = f.items.new_item
33
33
  i.title = mu.title_stripped
@@ -11,14 +11,19 @@ module Murlsh
11
11
  TwitterRe = %r{^(http://)mobile\.(twitter\.com/.*)$}i
12
12
  WikipediaRe = %r{^(http://[a-z]+\.)m\.(wikipedia\.org/.*)$}i
13
13
 
14
- def self.run(url, config)
15
- url.url = case
16
- when match = TwitterRe.match(url.url); "#{match[1]}#{match[2]}"
17
- when match = WikipediaRe.match(url.url); "#{match[1]}#{match[2]}"
18
- else; url.url
14
+ def self.unmobile(url)
15
+ case
16
+ when match = TwitterRe.match(url); "#{match[1]}#{match[2]}"
17
+ when match = WikipediaRe.match(url); "#{match[1]}#{match[2]}"
18
+ else; url
19
19
  end
20
20
  end
21
21
 
22
+ def self.run(url, config)
23
+ url.url = unmobile(url.url)
24
+ url.via = unmobile(url.via)
25
+ end
26
+
22
27
  end
23
28
 
24
29
  end
@@ -0,0 +1,19 @@
1
+ require 'murlsh'
2
+
3
+ module Murlsh
4
+
5
+ # Convert Ajax friendly Twitter urls (with #!) into usable urls.
6
+ class AddPre41UnajaxTwitter < Plugin
7
+
8
+ @hook = 'add_pre'
9
+
10
+ TwitterAjaxRe = %r{^(https?://twitter\.com/)#!/}i
11
+
12
+ def self.run(url, config)
13
+ url.url.sub!(TwitterAjaxRe, '\1')
14
+ url.via.sub!(TwitterAjaxRe, '\1') if url.via
15
+ end
16
+
17
+ end
18
+
19
+ end
data/spec/uri_ask_spec.rb CHANGED
@@ -2,7 +2,7 @@ require 'uri'
2
2
 
3
3
  require 'murlsh'
4
4
 
5
- Dir['plugins/*.rb'].each { |p| require p }
5
+ Dir['plugins/*.rb'].each { |p| require "./#{p}" }
6
6
 
7
7
  describe Murlsh::UriAsk do
8
8
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: murlsh
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 29
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 2
9
- - 0
10
- version: 1.2.0
9
+ - 1
10
+ version: 1.2.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matthew M. Boedicker
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-16 00:00:00 -05:00
18
+ date: 2010-12-23 00:00:00 -05:00
19
19
  default_executable: murlsh
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -337,9 +337,24 @@ dependencies:
337
337
  type: :development
338
338
  version_requirements: *id020
339
339
  - !ruby/object:Gem::Dependency
340
- name: rspec
340
+ name: rack-test
341
341
  prerelease: false
342
342
  requirement: &id021 !ruby/object:Gem::Requirement
343
+ none: false
344
+ requirements:
345
+ - - ~>
346
+ - !ruby/object:Gem::Version
347
+ hash: 1
348
+ segments:
349
+ - 0
350
+ - 5
351
+ version: "0.5"
352
+ type: :development
353
+ version_requirements: *id021
354
+ - !ruby/object:Gem::Dependency
355
+ name: rspec
356
+ prerelease: false
357
+ requirement: &id022 !ruby/object:Gem::Requirement
343
358
  none: false
344
359
  requirements:
345
360
  - - ~>
@@ -350,7 +365,7 @@ dependencies:
350
365
  - 3
351
366
  version: "1.3"
352
367
  type: :development
353
- version_requirements: *id021
368
+ version_requirements: *id022
354
369
  description: url sharing site framework with easy adding, title lookup, atom feed, thumbnails and embedding
355
370
  email: matthewm@boedicker.org
356
371
  executables:
@@ -372,6 +387,7 @@ files:
372
387
  - lib/murlsh/auth.rb
373
388
  - lib/murlsh/build_query.rb
374
389
  - lib/murlsh/config_server.rb
390
+ - lib/murlsh/delicious_parse.rb
375
391
  - lib/murlsh/dispatch.rb
376
392
  - lib/murlsh/doc.rb
377
393
  - lib/murlsh/etag_add_encoding.rb
@@ -399,6 +415,7 @@ files:
399
415
  - plugins/add_post_50_update_rss.rb
400
416
  - plugins/add_post_60_notify_hubs.rb
401
417
  - plugins/add_pre_40_convert_mobile.rb
418
+ - plugins/add_pre_41_unajax_twitter.rb
402
419
  - plugins/add_pre_50_lookup_content_type_title.rb
403
420
  - plugins/add_pre_50_media_thumbnail.rb
404
421
  - plugins/add_pre_50_open_graph_image.rb