georgi-shinmun 0.4.1 → 0.5
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/README.md +23 -31
- data/assets/print.css +76 -0
- data/assets/styles.css +91 -0
- data/bin/shinmun +16 -8
- data/config.ru +16 -0
- data/lib/shinmun.rb +1 -4
- data/lib/shinmun/blog.rb +110 -100
- data/lib/shinmun/comment.rb +1 -1
- data/lib/shinmun/handlers.rb +19 -0
- data/lib/shinmun/helpers.rb +1 -1
- data/lib/shinmun/routes.rb +25 -36
- data/templates/404.rhtml +4 -0
- data/templates/_comment_form.rhtml +21 -0
- data/{test/templates → templates}/_comments.rhtml +0 -0
- data/{example/templates → templates}/archive.rhtml +0 -1
- data/{example/templates → templates}/category.rhtml +1 -2
- data/{example/templates → templates}/category.rxml +0 -0
- data/{example/templates → templates}/index.rhtml +0 -1
- data/{example/templates → templates}/index.rxml +0 -0
- data/templates/layout.rhtml +44 -0
- data/{example/templates → templates}/page.rhtml +0 -2
- data/{example/templates → templates}/post.rhtml +6 -21
- data/test/blog_spec.rb +48 -81
- metadata +17 -44
- data/example/Rakefile +0 -41
- data/example/assets/images/favicon.ico +0 -0
- data/example/assets/images/loading.gif +0 -0
- data/example/assets/javascripts/1-jquery.min.js +0 -32
- data/example/assets/javascripts/2-jquery-form.min.js +0 -5
- data/example/assets/javascripts/3-comments.js +0 -45
- data/example/assets/javascripts/4-coderay.js +0 -13
- data/example/assets/print.css +0 -76
- data/example/assets/stylesheets/1-reset.css +0 -23
- data/example/assets/stylesheets/2-typo.css +0 -40
- data/example/assets/stylesheets/3-table.css +0 -23
- data/example/assets/stylesheets/4-article.css +0 -15
- data/example/assets/stylesheets/5-comments.css +0 -20
- data/example/assets/stylesheets/6-diff.css +0 -25
- data/example/assets/stylesheets/7-blog.css +0 -33
- data/example/config.ru +0 -6
- data/example/config/blog.yml +0 -10
- data/example/pages/about.md +0 -7
- data/example/templates/_comment_form.rhtml +0 -90
- data/example/templates/_comments.rhtml +0 -11
- data/example/templates/_pagination.rhtml +0 -10
- data/example/templates/comments.rhtml +0 -1
- data/example/templates/layout.rhtml +0 -82
- data/lib/shinmun/aggregations/delicious.rb +0 -57
- data/lib/shinmun/aggregations/flickr.rb +0 -81
- data/lib/shinmun/post_handler.rb +0 -16
- data/test/templates/archive.rhtml +0 -6
- data/test/templates/category.rhtml +0 -6
- data/test/templates/category.rxml +0 -20
- data/test/templates/index.rhtml +0 -4
- data/test/templates/index.rxml +0 -21
- data/test/templates/layout.rhtml +0 -9
- data/test/templates/page.rhtml +0 -2
- data/test/templates/post.rhtml +0 -3
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
Shinmun - a git-based blog engine
|
2
|
-
|
2
|
+
=================================
|
3
3
|
|
4
4
|
Shinmun is a small git-based blog engine. Write posts in your favorite
|
5
5
|
editor, git-push it and serve your blog straight from a repository.
|
@@ -11,7 +11,6 @@ editor, git-push it and serve your blog straight from a repository.
|
|
11
11
|
* Deploy via [git-push][11]
|
12
12
|
* Index, category and archive listings
|
13
13
|
* RSS feeds
|
14
|
-
* Flickr and Delicious aggregations
|
15
14
|
* Syntax highlighting provided by [CodeRay][4]
|
16
15
|
* AJAX comment system with Markdown preview
|
17
16
|
|
@@ -21,9 +20,9 @@ editor, git-push it and serve your blog straight from a repository.
|
|
21
20
|
Install the gems:
|
22
21
|
|
23
22
|
$ gem sources -a http://gems.github.com
|
24
|
-
$ gem install rack BlueCloth rubypants coderay
|
23
|
+
$ gem install rack BlueCloth rubypants coderay georgi-git_store georgi-kontrol georgi-shinmun
|
25
24
|
|
26
|
-
Create a sample blog
|
25
|
+
Create a sample blog:
|
27
26
|
|
28
27
|
$ shinmun init myblog
|
29
28
|
|
@@ -48,9 +47,7 @@ folder:
|
|
48
47
|
shinmun post 'The title of the post'
|
49
48
|
|
50
49
|
Shinmun will then create a post file in the right place, for example
|
51
|
-
in `posts/2008/9/the-title-of-the-post.md
|
52
|
-
probably open the file, set the category and tags and start writing
|
53
|
-
your new article.
|
50
|
+
in `posts/2008/9/the-title-of-the-post.md` and open it with $EDITOR.
|
54
51
|
|
55
52
|
|
56
53
|
### Post Format
|
@@ -141,23 +138,15 @@ An example tree:
|
|
141
138
|
|
142
139
|
### Blog configuation
|
143
140
|
|
144
|
-
Inside `config
|
141
|
+
Inside `config.ru` you can set the properties of your blog:
|
145
142
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
### Assets
|
155
|
-
|
156
|
-
Shinmun serves asset files from your assets directory. Files in the
|
157
|
-
directories `assets/stylesheets` and `assets/javascripts` will be
|
158
|
-
served as one file each under the URLs `assets/stylesheets.css` and
|
159
|
-
`assets/javascripts.css`. You have to name them accordingly like
|
160
|
-
`1-reset.css` and `2-typo.css` to define the order.
|
143
|
+
blog.config = {
|
144
|
+
:language => 'en',
|
145
|
+
:title => "Blog Title",
|
146
|
+
:author => "The Author",
|
147
|
+
:categories => ["Ruby", "Javascript"],
|
148
|
+
:description => "Blog description"
|
149
|
+
}
|
161
150
|
|
162
151
|
|
163
152
|
### Templates
|
@@ -176,17 +165,20 @@ Layout and templates are rendered by *ERB*. The layout is defined in
|
|
176
165
|
</body>
|
177
166
|
</html>
|
178
167
|
|
179
|
-
The attributes of a post are accessible
|
180
|
-
template:
|
168
|
+
The attributes of a post are accessible via the @post variable:
|
181
169
|
|
182
|
-
<div class="article">
|
170
|
+
<div class="article">
|
171
|
+
|
172
|
+
<h1><%= @post.title %></h1>
|
173
|
+
|
183
174
|
<div class="date">
|
184
|
-
<%=
|
175
|
+
<%= human_date @post.date %>
|
185
176
|
</div>
|
186
|
-
|
187
|
-
<%= @
|
188
|
-
|
189
|
-
|
177
|
+
|
178
|
+
<%= @post.body_html %>
|
179
|
+
|
180
|
+
...
|
181
|
+
|
190
182
|
</div>
|
191
183
|
|
192
184
|
|
data/assets/print.css
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
body {
|
2
|
+
line-height:1.5;
|
3
|
+
font-family:"Helvetica Neue", "Lucida Grande", Arial, Verdana, sans-serif;
|
4
|
+
color:#000;
|
5
|
+
background:none;
|
6
|
+
font-size:10pt;
|
7
|
+
}
|
8
|
+
|
9
|
+
.container {
|
10
|
+
background:none;
|
11
|
+
}
|
12
|
+
|
13
|
+
h1,h2,h3,h4,h5,h6 {
|
14
|
+
font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;
|
15
|
+
}
|
16
|
+
|
17
|
+
code {
|
18
|
+
font:.9em "Courier New", Monaco, Courier, monospace;
|
19
|
+
}
|
20
|
+
|
21
|
+
img {
|
22
|
+
float:left;
|
23
|
+
margin:1.5em 1.5em 1.5em 0;
|
24
|
+
}
|
25
|
+
|
26
|
+
a img {
|
27
|
+
border:none;
|
28
|
+
}
|
29
|
+
|
30
|
+
p img.top {
|
31
|
+
margin-top:0;
|
32
|
+
}
|
33
|
+
|
34
|
+
hr {
|
35
|
+
background:#ccc;
|
36
|
+
color:#ccc;
|
37
|
+
width:100%;
|
38
|
+
height:2px;
|
39
|
+
border:none;
|
40
|
+
margin:2em 0;
|
41
|
+
padding:0;
|
42
|
+
}
|
43
|
+
|
44
|
+
blockquote {
|
45
|
+
font-style:italic;
|
46
|
+
font-size:.9em;
|
47
|
+
margin:1.5em;
|
48
|
+
padding:1em;
|
49
|
+
}
|
50
|
+
|
51
|
+
.small {
|
52
|
+
font-size:.9em;
|
53
|
+
}
|
54
|
+
|
55
|
+
.large {
|
56
|
+
font-size:1.1em;
|
57
|
+
}
|
58
|
+
|
59
|
+
.quiet {
|
60
|
+
color:#999;
|
61
|
+
}
|
62
|
+
|
63
|
+
.hide {
|
64
|
+
display:none;
|
65
|
+
}
|
66
|
+
|
67
|
+
a:link,a:visited {
|
68
|
+
background:transparent;
|
69
|
+
font-weight:700;
|
70
|
+
text-decoration:underline;
|
71
|
+
}
|
72
|
+
|
73
|
+
a:link:after,a:visited:after {
|
74
|
+
content:" (" attr(href) ") ";
|
75
|
+
font-size:90%;
|
76
|
+
}
|
data/assets/styles.css
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p,
|
2
|
+
blockquote, pre, a, abbr, acronym, address, del, dfn, img,
|
3
|
+
q, fieldset, form, label, legend, table,
|
4
|
+
caption, tbody, tfoot, thead, tr, th, td {
|
5
|
+
margin: 0;
|
6
|
+
padding: 0;
|
7
|
+
border: 0;
|
8
|
+
font-weight: inherit;
|
9
|
+
font-style: inherit;
|
10
|
+
font-size: 100%;
|
11
|
+
font-family: inherit;
|
12
|
+
vertical-align: baseline;
|
13
|
+
}
|
14
|
+
|
15
|
+
body {
|
16
|
+
line-height: 1.5;
|
17
|
+
background: #fff;
|
18
|
+
margin:0.5em 0;
|
19
|
+
font-size: 80%;
|
20
|
+
color: #222;
|
21
|
+
font-family: Arial, sans-serif;
|
22
|
+
}
|
23
|
+
|
24
|
+
p {
|
25
|
+
margin-bottom: 1em;
|
26
|
+
}
|
27
|
+
|
28
|
+
a {
|
29
|
+
color: #444;
|
30
|
+
}
|
31
|
+
|
32
|
+
a:visited {
|
33
|
+
color: #444;
|
34
|
+
}
|
35
|
+
|
36
|
+
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
37
|
+
text-decoration: none;
|
38
|
+
}
|
39
|
+
|
40
|
+
h1 { font-size: 2em; line-height: 1; margin-top: 1em; margin-bottom: 1em; }
|
41
|
+
h2 { font-size: 1.5em; margin-top: 0.75em; margin-bottom: 0.75em; }
|
42
|
+
h3 { font-size: 1.3em; line-height: 1; margin-top: 1.7em; margin-bottom: 1em; }
|
43
|
+
h4 { font-size: 1.2em; line-height: 1.25; margin-top: 1.25em; margin-bottom: 1.25em; }
|
44
|
+
h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
|
45
|
+
h6 { font-size: 1em; font-weight: bold; }
|
46
|
+
|
47
|
+
hr {
|
48
|
+
height: 1px;
|
49
|
+
color: #ccc;
|
50
|
+
}
|
51
|
+
|
52
|
+
.container {
|
53
|
+
width: 600px;
|
54
|
+
margin: 0 auto;
|
55
|
+
}
|
56
|
+
|
57
|
+
.article {
|
58
|
+
}
|
59
|
+
|
60
|
+
.article h2 {
|
61
|
+
margin-top:0px;
|
62
|
+
}
|
63
|
+
|
64
|
+
.article .date {
|
65
|
+
color: #666;
|
66
|
+
}
|
67
|
+
|
68
|
+
.article .tags a {
|
69
|
+
color: #69c;
|
70
|
+
text-decoration: none;
|
71
|
+
}
|
72
|
+
|
73
|
+
.comments {
|
74
|
+
margin-bottom: 2em;
|
75
|
+
}
|
76
|
+
|
77
|
+
.comments .comment, .preview .comment {
|
78
|
+
margin-top: 2em;
|
79
|
+
border: 1px solid #ccc;
|
80
|
+
}
|
81
|
+
|
82
|
+
.comments .comment .top, .preview .comment .top {
|
83
|
+
background: #F0F0F0;
|
84
|
+
color: #333;
|
85
|
+
padding: 3px 5px;
|
86
|
+
}
|
87
|
+
|
88
|
+
.comments .comment .body, .preview .comment .body {
|
89
|
+
background: #F8F8F8;
|
90
|
+
padding: 3px 5px;
|
91
|
+
}
|
data/bin/shinmun
CHANGED
@@ -2,21 +2,29 @@
|
|
2
2
|
|
3
3
|
require 'shinmun'
|
4
4
|
|
5
|
+
ENV['RACK_ENV'] = 'production'
|
6
|
+
|
5
7
|
case ARGV[0]
|
6
8
|
when 'init'
|
7
9
|
Shinmun::Blog.init ARGV[1]
|
8
10
|
|
9
11
|
when 'post'
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
blog = Shinmun::Blog.new('.')
|
13
|
+
post = blog.create_post(:title => ARGV[1], :date => Date.today)
|
14
|
+
path = blog.post_file(post)
|
15
|
+
|
16
|
+
`git checkout master posts`
|
17
|
+
|
18
|
+
exec "#{ENV['EDITOR']} #{path}"
|
14
19
|
|
15
20
|
when 'page'
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
blog = Shinmun::Blog.new('.')
|
22
|
+
post = blog.create_page(:title => ARGV[1])
|
23
|
+
path = blog.post_file(post)
|
24
|
+
|
25
|
+
`git checkout master pages`
|
26
|
+
|
27
|
+
exec "#{ENV['EDITOR']} #{path}"
|
20
28
|
|
21
29
|
else
|
22
30
|
puts "Usage:"
|
data/config.ru
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'shinmun'
|
2
|
+
|
3
|
+
use Rack::Session::Cookie
|
4
|
+
use Rack::Reloader
|
5
|
+
|
6
|
+
blog = Shinmun::Blog.new(File.dirname(__FILE__))
|
7
|
+
|
8
|
+
blog.config = {
|
9
|
+
:language => 'en',
|
10
|
+
:title => "Blog Title",
|
11
|
+
:author => "The Author",
|
12
|
+
:categories => ["Ruby", "Javascript"],
|
13
|
+
:description => "Blog description"
|
14
|
+
}
|
15
|
+
|
16
|
+
run blog
|
data/lib/shinmun.rb
CHANGED
@@ -10,11 +10,8 @@ begin; require 'redcloth'; rescue LoadError; end
|
|
10
10
|
|
11
11
|
require 'shinmun/bluecloth_coderay'
|
12
12
|
require 'shinmun/helpers'
|
13
|
+
require 'shinmun/handlers'
|
13
14
|
require 'shinmun/blog'
|
14
15
|
require 'shinmun/routes'
|
15
16
|
require 'shinmun/post'
|
16
17
|
require 'shinmun/comment'
|
17
|
-
require 'shinmun/post_handler'
|
18
|
-
|
19
|
-
require 'shinmun/aggregations/delicious'
|
20
|
-
require 'shinmun/aggregations/flickr'
|
data/lib/shinmun/blog.rb
CHANGED
@@ -1,104 +1,76 @@
|
|
1
1
|
module Shinmun
|
2
|
+
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
|
2
3
|
|
3
4
|
class Blog < Kontrol::Application
|
4
|
-
|
5
|
-
EXAMPLE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../example')
|
6
|
-
|
7
5
|
include Helpers
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
%w[ assets comments config posts pages templates ].each do |name|
|
12
|
-
define_method(name) { store.root.tree(name) }
|
13
|
-
end
|
7
|
+
attr_accessor :config, :store, :posts, :pages
|
14
8
|
|
15
|
-
%w[ title description language author
|
16
|
-
define_method(name) { config[
|
9
|
+
%w[ base_path title description language author categories ].each do |name|
|
10
|
+
define_method(name) { @config[name.to_sym] }
|
17
11
|
end
|
18
12
|
|
19
13
|
# Initialize the blog
|
20
14
|
def initialize(path)
|
21
15
|
super
|
22
16
|
|
23
|
-
@
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@store = GitStore::FileStore.new(path)
|
29
|
-
end
|
30
|
-
|
31
|
-
@store.load
|
32
|
-
|
33
|
-
@repo = Grit::Repo.new(path) if defined?(Grit)
|
34
|
-
|
35
|
-
Thread.start do
|
36
|
-
loop do
|
37
|
-
load_aggregations
|
38
|
-
sleep 300
|
39
|
-
end
|
40
|
-
end
|
17
|
+
@config = {}
|
18
|
+
@store = GitStore.new(path)
|
19
|
+
@store.handler['md'] = PostHandler.new
|
20
|
+
@store.handler['rhtml'] = ERBHandler.new
|
21
|
+
@store.handler['rxml'] = ERBHandler.new
|
41
22
|
end
|
42
23
|
|
43
|
-
def self.init(
|
44
|
-
|
45
|
-
Dir.
|
46
|
-
FileUtils.cp_r EXAMPLE_DIR + '/.', '.'
|
47
|
-
`git init`
|
48
|
-
`git add .`
|
49
|
-
`git commit -m 'init'`
|
50
|
-
end
|
24
|
+
def self.init(path)
|
25
|
+
path = File.expand_path(path)
|
26
|
+
Dir.mkdir(path)
|
51
27
|
|
52
|
-
|
53
|
-
|
54
|
-
|
28
|
+
FileUtils.cp_r "#{ROOT}/assets", path
|
29
|
+
FileUtils.cp_r "#{ROOT}/templates", path
|
30
|
+
FileUtils.cp "#{ROOT}/config.ru", path
|
55
31
|
|
56
|
-
|
57
|
-
|
58
|
-
|
32
|
+
Dir.mkdir("#{path}/posts")
|
33
|
+
Dir.mkdir("#{path}/pages")
|
34
|
+
Dir.mkdir("#{path}/comments")
|
35
|
+
Dir.mkdir("#{path}/public")
|
59
36
|
|
60
|
-
|
61
|
-
store.refresh!
|
62
|
-
super
|
63
|
-
end
|
37
|
+
FileUtils.ln_s("../assets", "#{path}/public/assets")
|
64
38
|
|
65
|
-
|
66
|
-
|
67
|
-
|
39
|
+
Dir.chdir(path) do
|
40
|
+
`git init`
|
41
|
+
`git add .`
|
42
|
+
`git commit -m 'init'`
|
68
43
|
end
|
69
44
|
end
|
70
45
|
|
71
|
-
def
|
72
|
-
|
46
|
+
def load_template(file)
|
47
|
+
store['templates/' + file]
|
73
48
|
end
|
74
49
|
|
75
|
-
def
|
76
|
-
|
50
|
+
def render(name, vars = {})
|
51
|
+
super(name, vars.merge(:blog => self))
|
77
52
|
end
|
78
53
|
|
79
|
-
|
80
|
-
|
81
|
-
posts_by_date.select { |p| p.year == year and p.month == month }
|
54
|
+
def pages
|
55
|
+
store.tree('pages').values
|
82
56
|
end
|
83
57
|
|
84
|
-
|
85
|
-
|
86
|
-
return [] if tags.nil? or tags.empty?
|
87
|
-
tags = tags.split(',').map { |t| t.strip } if tags.is_a?(String)
|
88
|
-
posts.select do |post|
|
89
|
-
tags.any? do |tag|
|
90
|
-
post.tag_list.include?(tag)
|
91
|
-
end
|
92
|
-
end
|
58
|
+
def posts
|
59
|
+
store.tree('posts').values.sort_by { |post| post.date.to_s }.reverse
|
93
60
|
end
|
94
61
|
|
95
|
-
|
96
|
-
|
97
|
-
|
62
|
+
def call(env)
|
63
|
+
if ENV['RACK_ENV'] == 'production'
|
64
|
+
store.load if store.changed?
|
65
|
+
else
|
66
|
+
store.load(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
super
|
98
70
|
end
|
99
71
|
|
100
|
-
def
|
101
|
-
|
72
|
+
def url
|
73
|
+
"http://#{request.host}"
|
102
74
|
end
|
103
75
|
|
104
76
|
def symbolize_keys(hash)
|
@@ -112,36 +84,54 @@ module Shinmun
|
|
112
84
|
store.transaction(message, &block)
|
113
85
|
end
|
114
86
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
87
|
+
def post_file(post)
|
88
|
+
'posts' + post_path(post) + '.' + post.type
|
89
|
+
end
|
90
|
+
|
91
|
+
def page_file(post)
|
92
|
+
'pages' + page_path(post) + '.' + post.type
|
93
|
+
end
|
94
|
+
|
95
|
+
def comment_file(post)
|
96
|
+
'comments/' + post_path(post) + '.yml'
|
121
97
|
end
|
122
98
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
99
|
+
def create_post(attr)
|
100
|
+
post = Post.new(attr)
|
101
|
+
path = post_file(post)
|
102
|
+
|
103
|
+
transaction "create post `#{post.title}'" do
|
104
|
+
store[path] = post
|
128
105
|
end
|
106
|
+
|
107
|
+
post
|
129
108
|
end
|
130
109
|
|
131
|
-
def
|
132
|
-
|
133
|
-
|
110
|
+
def create_page(attr)
|
111
|
+
post = Post.new(attr)
|
112
|
+
path = page_file(post)
|
113
|
+
|
114
|
+
transaction "create page `#{post.title}'" do
|
115
|
+
store[path] = post
|
134
116
|
end
|
117
|
+
|
118
|
+
post
|
135
119
|
end
|
136
120
|
|
137
|
-
def comments_for(
|
138
|
-
|
121
|
+
def comments_for(post)
|
122
|
+
store[comment_file post] || []
|
139
123
|
end
|
140
124
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
125
|
+
def create_comment(post, params)
|
126
|
+
path = comment_file(post)
|
127
|
+
comments = comments_for(post)
|
128
|
+
comment = Comment.new(params)
|
129
|
+
|
130
|
+
transaction "new comment for `#{post.title}'" do
|
131
|
+
store[path] = comments + [comment]
|
144
132
|
end
|
133
|
+
|
134
|
+
comment
|
145
135
|
end
|
146
136
|
|
147
137
|
def find_page(name)
|
@@ -149,23 +139,43 @@ module Shinmun
|
|
149
139
|
end
|
150
140
|
|
151
141
|
def find_post(year, month, name)
|
152
|
-
|
142
|
+
posts.find { |p| p.year == year and p.month == month and p.name == name }
|
153
143
|
end
|
154
144
|
|
155
145
|
def find_category(permalink)
|
156
|
-
name = categories.find { |name| urlify(name) == permalink }
|
157
|
-
|
158
|
-
{ :name => name,
|
146
|
+
name = categories.find { |name| urlify(name) == permalink }
|
147
|
+
|
148
|
+
{ :name => name,
|
149
|
+
:posts => posts.select { |p| p.category == name },
|
150
|
+
:permalink => permalink }
|
151
|
+
end
|
152
|
+
|
153
|
+
def recent_posts
|
154
|
+
posts[0, 20]
|
159
155
|
end
|
160
156
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
157
|
+
# Return all posts for a given month.
|
158
|
+
def posts_for_month(year, month)
|
159
|
+
posts.select { |p| p.year == year and p.month == month }
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return all posts with any of given tags.
|
163
|
+
def posts_with_tags(tags)
|
164
|
+
return [] if tags.nil? or tags.empty?
|
165
|
+
tags = tags.split(',').map { |t| t.strip } if tags.is_a?(String)
|
166
|
+
|
167
|
+
posts.select do |post|
|
168
|
+
tags.any? do |tag|
|
169
|
+
post.tag_list.include?(tag)
|
170
|
+
end
|
166
171
|
end
|
167
172
|
end
|
168
|
-
|
169
|
-
|
173
|
+
|
174
|
+
# Return all archives as tuples of [year, month].
|
175
|
+
def archives
|
176
|
+
posts.map { |p| [p.year, p.month] }.uniq.sort
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
170
180
|
|
171
181
|
end
|