typo 3.99.2 → 3.99.3
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/articles_controller.rb +21 -14
- data/app/controllers/content_controller.rb +3 -5
- data/app/controllers/xml_controller.rb +13 -3
- data/app/helpers/application_helper.rb +7 -1
- data/app/helpers/xml_helper.rb +23 -0
- data/app/models/article.rb +5 -0
- data/app/models/content.rb +2 -0
- data/app/models/ping.rb +32 -22
- data/app/views/admin/content/_form.rhtml +1 -0
- data/app/views/admin/general/index.rhtml +1 -1
- data/app/views/admin/pages/_form.rhtml +1 -0
- data/app/views/articles/_comment_box.rhtml +1 -0
- data/app/views/xml/_googlesitemap_item_article.rxml +5 -0
- data/app/views/xml/_googlesitemap_item_category.rxml +4 -0
- data/app/views/xml/_googlesitemap_item_page.rxml +4 -0
- data/app/views/xml/_googlesitemap_item_tag.rxml +4 -0
- data/app/views/xml/googlesitemap_feed.rxml +7 -0
- data/app/views/xml/itunes_feed.rxml +4 -4
- data/bin/typo +10 -0
- data/components/plugins/sidebars/archives_controller.rb +20 -14
- data/components/plugins/sidebars/delicious/content.rhtml +4 -4
- data/components/plugins/textfilters/amazon_controller.rb +1 -1
- data/config/routes.rb +2 -1
- data/doc/Installer.txt +21 -4
- data/installer/rails-installer.rb +78 -90
- data/installer/rails-installer/commands.rb +117 -0
- data/installer/rails-installer/web-server.rb +108 -0
- data/installer/rails-installer/web-servers.rb +108 -0
- data/installer/rails_installer_defaults.yml +1 -0
- data/lib/sidebars/plugin.rb +1 -1
- data/lib/tasks/release.rake +2 -1
- data/lib/typo_version.rb +1 -1
- data/log/development.log-1 +991 -0
- data/log/development.log-2 +422 -0
- data/log/development.log-3 +429 -0
- data/log/development.log-4 +174 -0
- data/public/javascripts/typo.js +8 -0
- data/test/fixtures/contents.yml +5 -0
- data/test/functional/articles_controller_test.rb +2 -2
- data/test/functional/xml_controller_test.rb +8 -0
- data/test/test_helper.rb +2 -0
- data/themes/scribbish/CONTRIBUTORS +2 -0
- data/themes/scribbish/about.markdown +9 -0
- data/themes/scribbish/images/background.gif +0 -0
- data/themes/scribbish/images/gravatar.gif +0 -0
- data/themes/scribbish/images/header_shadow.gif +0 -0
- data/themes/scribbish/images/spinner.gif +0 -0
- data/themes/scribbish/layouts/default.rhtml +42 -0
- data/themes/scribbish/preview.png +0 -0
- data/themes/scribbish/stylesheets/application.css +27 -0
- data/themes/scribbish/stylesheets/content.css +400 -0
- data/themes/scribbish/stylesheets/layout.css +70 -0
- data/themes/scribbish/views/articles/_article.rhtml +37 -0
- data/themes/scribbish/views/articles/_comment.rhtml +14 -0
- data/themes/scribbish/views/articles/_comment_form.rhtml +47 -0
- data/themes/scribbish/views/articles/_search.rhtml +15 -0
- data/themes/scribbish/views/articles/_trackback.rhtml +9 -0
- data/themes/scribbish/views/articles/comment_preview.rhtml +10 -0
- data/themes/scribbish/views/articles/index.rhtml +5 -0
- data/themes/scribbish/views/articles/read.rhtml +42 -0
- data/tmp/cache/META/DATA/ACTION_PARAM/10.1.0.181/articles/index/.cache +537 -0
- data/tmp/cache/META/DATA/ACTION_PARAM/localhost/articles/index/.cache +537 -0
- data/tmp/cache/META/DATA/ACTION_PARAM/localhost/xml/feed/format=atom&type=feed.cache +671 -0
- data/tmp/cache/META/DATA/ACTION_PARAM/localhost/xml/feed/format=rss20&type=feed.cache +401 -0
- data/tmp/cache/META/META/ACTION_PARAM/10.1.0.181/articles/index/.cache +2 -0
- data/tmp/cache/META/META/ACTION_PARAM/localhost/articles/index/.cache +2 -0
- data/tmp/cache/META/META/ACTION_PARAM/localhost/xml/feed/format=atom&type=feed.cache +2 -0
- data/tmp/cache/META/META/ACTION_PARAM/localhost/xml/feed/format=rss20&type=feed.cache +2 -0
- data/vendor/plugins/expiring_action_cache/lib/actionparamcache.rb +2 -1
- metadata +91 -2
@@ -1,6 +1,5 @@
|
|
1
1
|
class ArticlesController < ContentController
|
2
2
|
before_filter :verify_config
|
3
|
-
before_filter :check_page_query_param_for_missing_routes
|
4
3
|
|
5
4
|
layout :theme_layout, :except => [:comment_preview, :trackback]
|
6
5
|
|
@@ -17,13 +16,23 @@ class ArticlesController < ContentController
|
|
17
16
|
:render => { :text => 'Forbidden', :status => 403 })
|
18
17
|
|
19
18
|
def index
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
# On Postgresql, paginate's default count is *SLOW*, because it does a join against
|
20
|
+
# all of the eager-loaded tables. I've seen it take up to 7 seconds on my test box.
|
21
|
+
#
|
22
|
+
# So, we're going to use the older Paginator class and manually provide a count.
|
23
|
+
# This is a 100x speedup on my box.
|
24
|
+
count = Article.count(:conditions => ['published = ? AND contents.published_at < ? AND blog_id = ?',
|
25
|
+
true, Time.now, this_blog.id])
|
26
|
+
@pages = Paginator.new self, count, this_blog.limit_article_display, @params[:page]
|
27
|
+
@articles = Article.find( :all,
|
28
|
+
:offset => @pages.current.offset,
|
29
|
+
:limit => @pages.items_per_page,
|
30
|
+
:order => "contents.published_at DESC",
|
31
|
+
:include => [:categories, :tags, :user, :blog],
|
32
|
+
:conditions =>
|
33
|
+
['published = ? AND contents.published_at < ? AND blog_id = ?',
|
34
|
+
true, Time.now, this_blog.id]
|
35
|
+
)
|
27
36
|
end
|
28
37
|
|
29
38
|
def search
|
@@ -145,6 +154,10 @@ class ArticlesController < ContentController
|
|
145
154
|
render :nothing => true, :status => 404
|
146
155
|
end
|
147
156
|
end
|
157
|
+
|
158
|
+
def markup_help
|
159
|
+
render :text => TextFilter.find(params[:id]).commenthelp
|
160
|
+
end
|
148
161
|
|
149
162
|
private
|
150
163
|
|
@@ -153,12 +166,6 @@ class ArticlesController < ContentController
|
|
153
166
|
:expires => 6.weeks.from_now }
|
154
167
|
end
|
155
168
|
|
156
|
-
def check_page_query_param_for_missing_routes
|
157
|
-
unless request.path =~ /\/page\//
|
158
|
-
raise "Page param problem" unless params[:page].nil?
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
169
|
def verify_config
|
163
170
|
if User.count == 0
|
164
171
|
redirect_to :controller => "accounts", :action => "signup"
|
@@ -9,10 +9,10 @@ class ContentController < ApplicationController
|
|
9
9
|
def after(controller)
|
10
10
|
future_article =
|
11
11
|
Article.find(:first,
|
12
|
-
:conditions => ['published = ? AND
|
13
|
-
:order => "
|
12
|
+
:conditions => ['published = ? AND published_at > ?', true, @request_time],
|
13
|
+
:order => "published_at ASC" )
|
14
14
|
if future_article
|
15
|
-
delta = future_article.
|
15
|
+
delta = future_article.published_at - Time.now
|
16
16
|
controller.response.lifetime = (delta <= 0) ? 0 : delta
|
17
17
|
end
|
18
18
|
end
|
@@ -20,9 +20,7 @@ class ContentController < ApplicationController
|
|
20
20
|
|
21
21
|
include LoginSystem
|
22
22
|
model :user
|
23
|
-
|
24
23
|
helper :theme
|
25
|
-
|
26
24
|
before_filter :auto_discovery_defaults
|
27
25
|
|
28
26
|
def self.caches_action_with_params(*actions)
|
@@ -3,10 +3,12 @@ class XmlController < ContentController
|
|
3
3
|
session :off
|
4
4
|
|
5
5
|
NORMALIZED_FORMAT_FOR = {'atom' => 'atom10', 'rss' => 'rss20',
|
6
|
-
'atom10' => 'atom10', 'rss20' => 'rss20'
|
6
|
+
'atom10' => 'atom10', 'rss20' => 'rss20',
|
7
|
+
'googlesitemap' => 'googlesitemap' }
|
7
8
|
|
8
9
|
CONTENT_TYPE_FOR = { 'rss20' => 'application/xml',
|
9
|
-
'atom10' => 'application/atom+xml'
|
10
|
+
'atom10' => 'application/atom+xml',
|
11
|
+
'googlesitemap' => 'application/xml' }
|
10
12
|
|
11
13
|
|
12
14
|
def feed
|
@@ -68,7 +70,7 @@ class XmlController < ContentController
|
|
68
70
|
association = this_blog.send(association)
|
69
71
|
end
|
70
72
|
limit ||= this_blog.limit_rss_display
|
71
|
-
@items
|
73
|
+
@items += association.find_already_published(:all, :limit => limit, :order => order)
|
72
74
|
end
|
73
75
|
|
74
76
|
def prep_feed
|
@@ -108,4 +110,12 @@ class XmlController < ContentController
|
|
108
110
|
@link = url_for({:controller => "articles_controller.rb", :action => 'tag', :tag => tag.name},
|
109
111
|
{:only_path => false})
|
110
112
|
end
|
113
|
+
|
114
|
+
def prep_sitemap
|
115
|
+
fetch_items(:articles, 'created_at DESC', 1000)
|
116
|
+
fetch_items(:pages, 'created_at DESC', 1000)
|
117
|
+
@items += Category.find_all_with_article_counters(1000)
|
118
|
+
@items += Tag.find_all_with_article_counters(1000)
|
119
|
+
end
|
120
|
+
|
111
121
|
end
|
@@ -88,5 +88,11 @@ module ApplicationHelper
|
|
88
88
|
page.html(@controller,:body)
|
89
89
|
end
|
90
90
|
|
91
|
-
def strip_html(text)
|
91
|
+
def strip_html(text)
|
92
|
+
text.strip_html
|
93
|
+
end
|
94
|
+
|
95
|
+
def markup_help_popup(markup, text)
|
96
|
+
"<a href=\"#{url_for :controller => '/articles', :action => 'markup_help', :id => markup.id}\" onClick=\"return popup(this, 'Typo Markup Help')\">#{text}</a>"
|
97
|
+
end
|
92
98
|
end
|
data/app/helpers/xml_helper.rb
CHANGED
@@ -18,4 +18,27 @@ module XmlHelper
|
|
18
18
|
def blog_title
|
19
19
|
this_blog.blog_name || "Unnamed blog"
|
20
20
|
end
|
21
|
+
|
22
|
+
def tag_link(tag)
|
23
|
+
url_for :controller => "articles", :action => 'tag', :id => tag.name, :only_path => false
|
24
|
+
end
|
25
|
+
|
26
|
+
def collection_lastmod(collection)
|
27
|
+
article_updated = collection.articles.find(:first, :order => 'updated_at DESC')
|
28
|
+
article_published = collection.articles.find(:first, :order => 'published_at DESC')
|
29
|
+
|
30
|
+
times = []
|
31
|
+
times.push article_updated.updated_at if article_updated
|
32
|
+
times.push article_published.updated_at if article_published
|
33
|
+
|
34
|
+
if times.empty?
|
35
|
+
Time.at(0).xmlschema
|
36
|
+
else
|
37
|
+
times.max.xmlschema
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def category_link(category)
|
42
|
+
url_for :controller => "articles", :action => 'category', :id => category.permalink, :only_path => false
|
43
|
+
end
|
21
44
|
end
|
data/app/models/article.rb
CHANGED
@@ -196,6 +196,11 @@ class Article < Content
|
|
196
196
|
return [from, to]
|
197
197
|
end
|
198
198
|
|
199
|
+
def find_published(what = :all, options = {})
|
200
|
+
options[:include] ||= []
|
201
|
+
options[:include] += [:user]
|
202
|
+
super(what, options)
|
203
|
+
end
|
199
204
|
|
200
205
|
validates_uniqueness_of :guid
|
201
206
|
validates_presence_of :title
|
data/app/models/content.rb
CHANGED
@@ -77,6 +77,8 @@ class Content < ActiveRecord::Base
|
|
77
77
|
options.reverse_merge!(:order => default_order)
|
78
78
|
options[:conditions] = merge_conditions(['published = ?', true],
|
79
79
|
options[:conditions])
|
80
|
+
options[:include] ||= []
|
81
|
+
options[:include] += [:blog]
|
80
82
|
find(what, options)
|
81
83
|
end
|
82
84
|
|
data/app/models/ping.rb
CHANGED
@@ -87,43 +87,53 @@ class Ping < ActiveRecord::Base
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def send_pingback_or_trackback(origin_url)
|
90
|
-
|
90
|
+
t = Thread.start do
|
91
|
+
Pinger.new(origin_url, self).send_pingback_or_trackback
|
92
|
+
end
|
93
|
+
t.join if defined? $TESTING
|
91
94
|
end
|
92
95
|
|
93
96
|
def send_trackback(trackback_url, origin_url)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
97
|
+
t = Thread.start do
|
98
|
+
trackback_uri = URI.parse(trackback_url)
|
99
|
+
|
100
|
+
post = "title=#{URI.escape(article.title)}"
|
101
|
+
post << "&excerpt=#{URI.escape(article.body_html.strip_html[0..254])}"
|
102
|
+
post << "&url=#{origin_url}"
|
103
|
+
post << "&blog_name=#{URI.escape(article.blog.blog_name)}"
|
104
|
+
|
105
|
+
Net::HTTP.start(trackback_uri.host, trackback_uri.port) do |http|
|
106
|
+
path = trackback_uri.path
|
107
|
+
path += "?#{trackback_uri.query}" if trackback_uri.query
|
108
|
+
http.post(path, post, 'Content-type' => 'application/x-www-form-urlencoded; charset=utf-8')
|
109
|
+
end
|
105
110
|
end
|
111
|
+
t.join if defined? $TESTING
|
106
112
|
end
|
107
113
|
|
108
|
-
|
109
|
-
|
110
114
|
def send_weblogupdatesping(server_url, origin_url)
|
111
|
-
|
115
|
+
t = Thread.start do
|
116
|
+
send_xml_rpc(self.url, "weblogUpdates.ping", article.blog.blog_name, server_url, origin_url)
|
117
|
+
end
|
118
|
+
t.join if defined? $TESTING
|
112
119
|
end
|
113
120
|
|
114
121
|
protected
|
115
122
|
|
116
123
|
def send_xml_rpc(xml_rpc_url, name, *args)
|
117
|
-
|
118
|
-
server = XMLRPC::Client.new2(URI.parse(xml_rpc_url).to_s)
|
119
|
-
|
124
|
+
t = Thread.start do
|
120
125
|
begin
|
121
|
-
|
122
|
-
|
126
|
+
server = XMLRPC::Client.new2(URI.parse(xml_rpc_url).to_s)
|
127
|
+
|
128
|
+
begin
|
129
|
+
result = server.call(name, *args)
|
130
|
+
rescue XMLRPC::FaultException => e
|
131
|
+
logger.error(e)
|
132
|
+
end
|
133
|
+
rescue Exception => e
|
123
134
|
logger.error(e)
|
124
135
|
end
|
125
|
-
rescue Exception => e
|
126
|
-
logger.error(e)
|
127
136
|
end
|
137
|
+
t.join if defined? $TESTING
|
128
138
|
end
|
129
139
|
end
|
@@ -12,6 +12,7 @@
|
|
12
12
|
<p>
|
13
13
|
<label for="article_body">Body:</label><br />
|
14
14
|
<%= text_area 'article', 'body', :rows => 25, :style => 'width: 48%;' %>
|
15
|
+
<br/><%= markup_help_popup @article.text_filter, "Markup Help" %>
|
15
16
|
</p>
|
16
17
|
<p>
|
17
18
|
<label for="categories">Categories:</label><br />
|
@@ -176,7 +176,7 @@
|
|
176
176
|
<p>
|
177
177
|
<label for="itunes_summary">Summary:<small>(setting for channel)</small>
|
178
178
|
</label> <br />
|
179
|
-
<textarea name="setting[itunes_summary]" cols="40" rows="3"><%= this_blog.itunes_summary %></textarea>
|
179
|
+
<textarea name="setting[itunes_summary]" cols="40" rows="3"><%=h this_blog.itunes_summary %></textarea>
|
180
180
|
</p>
|
181
181
|
<p>
|
182
182
|
<label for="itunes_email">Email:</label>
|
@@ -11,6 +11,7 @@
|
|
11
11
|
<p>
|
12
12
|
<label for="page_body">Content:</label><br />
|
13
13
|
<%= text_area 'page', 'body', :rows => 25, :style => 'width: 48%;' %>
|
14
|
+
<br/><%= markup_help_popup @page.text_filter, "Markup Help" %>
|
14
15
|
</p>
|
15
16
|
|
16
17
|
<label for="page_text_filter">Textfilter: </label><%= select 'page', 'text_filter', text_filter_options %><br/>
|
@@ -34,6 +34,7 @@
|
|
34
34
|
<tr>
|
35
35
|
<td colspan="2" id="frm-btns">
|
36
36
|
<span id="comment_loading" style="display:none;"><%= image_tag "spinner.gif" %></span>
|
37
|
+
<%= markup_help_popup TextFilter.find_by_name(config[:comment_text_filter]), "Comment Markup Help" %>
|
37
38
|
<a href="#" onclick="new Ajax.Updater('preview', '<%= url_for :action => 'comment_preview' %>', {asynchronous:true, evalScripts:true, parameters:Form.serialize('commentform'), onComplete:function(request){Element.show('preview')}}); return false;">Preview comment</a>
|
38
39
|
<input type="submit" name="submit" id="form-submit-button" value="submit" class="button" />
|
39
40
|
</td>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
2
|
+
xml.urlset "xmlns"=>"http://www.google.com/schemas/sitemap/0.84" do
|
3
|
+
@items.each do |item|
|
4
|
+
render :partial => "googlesitemap_item_#{item.class.name.to_s.downcase}",
|
5
|
+
:locals => {:item => item, :xm => xml}
|
6
|
+
end
|
7
|
+
end
|
@@ -20,10 +20,10 @@ xml.rss "version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/",
|
|
20
20
|
xml.itunes :email,(this_blog.itunes_email)
|
21
21
|
end
|
22
22
|
xml.copyright(this_blog.itunes_copyright)
|
23
|
-
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
@items.each do |item|
|
25
|
+
render :partial => "itunes_item_resource",
|
26
|
+
:locals => {:item => item, :xm => xml}
|
27
|
+
end
|
28
28
|
end
|
29
29
|
end
|
data/bin/typo
CHANGED
@@ -37,6 +37,16 @@ class TypoInstaller < RailsInstaller
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
class SweepCache < RailsInstaller::Command
|
41
|
+
def self.command(installer, *args)
|
42
|
+
installer.sweep_cache
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.help(installer)
|
46
|
+
['',"Sweep Typo's cache"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
40
50
|
# Installer program
|
41
51
|
directory = ARGV[1]
|
42
52
|
|
@@ -5,20 +5,26 @@ class Plugins::Sidebars::ArchivesController < Sidebars::ComponentPlugin
|
|
5
5
|
|
6
6
|
|
7
7
|
def content
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
8
|
+
# The original query that was here instantiated every article and every
|
9
|
+
# tag, and then sorted through them just to do a 'group by date'.
|
10
|
+
# Unfortunately, there's no universally-supported way to do this
|
11
|
+
# across all three of our supported DBs. So, we resort to a bit of
|
12
|
+
# DB-specific code.
|
13
|
+
if Content.connection.kind_of? ActiveRecord::ConnectionAdapters::SQLiteAdapter
|
14
|
+
date_func = "strftime('%Y') as year, strftime('%m') as month"
|
15
|
+
else
|
16
|
+
date_func = "extract(year from published_at) as year,extract(month from published_at) as month"
|
17
|
+
end
|
18
|
+
|
19
|
+
article_counts = Content.find_by_sql(["select count(*) as count, #{date_func} from contents where type='Article' and published = ? and published_at < ? group by year,month order by year desc,month desc limit ?",true,Time.now,count.to_i])
|
20
|
+
|
21
|
+
@archives = article_counts.map do |entry|
|
22
|
+
{
|
23
|
+
:name => "#{Date::MONTHNAMES[entry.month.to_i]} #{entry.year}",
|
24
|
+
:month => entry.month.to_i,
|
25
|
+
:year => entry.year.to_i,
|
26
|
+
:article_count => entry.count
|
27
|
+
}
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
<% if @delicious -%>
|
2
2
|
<div id="delicious">
|
3
|
-
<h3><a href="http://<%= @delicious.title %>"><%= @delicious.title %></a></h3>
|
3
|
+
<h3><a href="http://<%=h @delicious.title %>"><%=h @delicious.title %></a></h3>
|
4
4
|
<% (@sb_config['groupdate'] ? @delicious.days : [{ :container => @delicious.items }]).each do |group| -%>
|
5
5
|
<% if @sb_config['groupdate'] -%>
|
6
|
-
<span class="date"><%= group[:date].to_s.to_date.strftime("%b %d") %></span>
|
6
|
+
<span class="date"><%=h group[:date].to_s.to_date.strftime("%b %d") %></span>
|
7
7
|
<% end -%>
|
8
8
|
<ul>
|
9
9
|
<% for item in group[:container] %>
|
10
10
|
<li>
|
11
|
-
<a href="<%= item.link %>" title="<%=h item.description%>"><%=h item.title %></a>
|
11
|
+
<a href="<%=h item.link %>" title="<%=h item.description%>"><%=h item.title %></a>
|
12
12
|
<% if @sb_config['description'] -%>
|
13
|
-
<br /><span class="desc"><%= @sb_config['desclink'] ? item.description_link : item.description %></span>
|
13
|
+
<br /><span class="desc"><%=h @sb_config['desclink'] ? item.description_link : item.description %></span>
|
14
14
|
<% end -%>
|
15
15
|
</li>
|
16
16
|
<% end -%>
|
data/config/routes.rb
CHANGED
@@ -23,7 +23,8 @@ ActionController::Routing::Routes.draw do |map|
|
|
23
23
|
map.xml 'xml/:format/:type/feed.xml', :controller => 'xml', :action => 'feed'
|
24
24
|
map.xml 'xml/:format/:type/:id/feed.xml', :controller => 'xml', :action => 'feed'
|
25
25
|
map.xml 'xml/rss', :controller => 'xml', :action => 'feed', :type => 'feed', :format => 'rss'
|
26
|
-
|
26
|
+
map.xml 'sitemap.xml', :controller => 'xml', :action => 'feed', :format => 'googlesitemap', :type => 'sitemap'
|
27
|
+
|
27
28
|
# allow neat perma urls
|
28
29
|
map.connect 'articles',
|
29
30
|
:controller => 'articles', :action => 'index'
|