rethoth 0.4.1
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.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/bin/thoth +233 -0
- data/lib/proto/config.ru +45 -0
- data/lib/proto/thoth.conf.sample +206 -0
- data/lib/thoth/cache.rb +53 -0
- data/lib/thoth/config.rb +158 -0
- data/lib/thoth/controller/admin.rb +75 -0
- data/lib/thoth/controller/api/comment.rb +73 -0
- data/lib/thoth/controller/api/page.rb +134 -0
- data/lib/thoth/controller/api/post.rb +122 -0
- data/lib/thoth/controller/api/tag.rb +59 -0
- data/lib/thoth/controller/archive.rb +50 -0
- data/lib/thoth/controller/comment.rb +173 -0
- data/lib/thoth/controller/main.rb +193 -0
- data/lib/thoth/controller/media.rb +172 -0
- data/lib/thoth/controller/page.rb +167 -0
- data/lib/thoth/controller/post.rb +310 -0
- data/lib/thoth/controller/search.rb +86 -0
- data/lib/thoth/controller/tag.rb +107 -0
- data/lib/thoth/controller.rb +48 -0
- data/lib/thoth/errors.rb +35 -0
- data/lib/thoth/helper/admin.rb +86 -0
- data/lib/thoth/helper/cookie.rb +45 -0
- data/lib/thoth/helper/error.rb +122 -0
- data/lib/thoth/helper/pagination.rb +131 -0
- data/lib/thoth/helper/wiki.rb +77 -0
- data/lib/thoth/helper/ysearch.rb +89 -0
- data/lib/thoth/importer/pants.rb +81 -0
- data/lib/thoth/importer/poseidon.rb +54 -0
- data/lib/thoth/importer/thoth.rb +103 -0
- data/lib/thoth/importer.rb +131 -0
- data/lib/thoth/layout/default.rhtml +47 -0
- data/lib/thoth/middleware/minify.rb +82 -0
- data/lib/thoth/migrate/001_create_schema.rb +108 -0
- data/lib/thoth/migrate/002_add_media_size.rb +37 -0
- data/lib/thoth/migrate/003_add_post_draft.rb +38 -0
- data/lib/thoth/migrate/004_add_comment_email.rb +37 -0
- data/lib/thoth/migrate/005_add_page_position.rb +37 -0
- data/lib/thoth/migrate/006_add_comment_close_delete.rb +43 -0
- data/lib/thoth/migrate/007_add_comment_summary.rb +37 -0
- data/lib/thoth/model/comment.rb +216 -0
- data/lib/thoth/model/media.rb +87 -0
- data/lib/thoth/model/page.rb +204 -0
- data/lib/thoth/model/post.rb +262 -0
- data/lib/thoth/model/tag.rb +80 -0
- data/lib/thoth/model/tags_posts_map.rb +34 -0
- data/lib/thoth/monkeypatch/sequel/model/errors.rb +37 -0
- data/lib/thoth/plugin/thoth_delicious.rb +105 -0
- data/lib/thoth/plugin/thoth_flickr.rb +86 -0
- data/lib/thoth/plugin/thoth_pinboard.rb +98 -0
- data/lib/thoth/plugin/thoth_tags.rb +68 -0
- data/lib/thoth/plugin/thoth_twitter.rb +175 -0
- data/lib/thoth/plugin.rb +59 -0
- data/lib/thoth/public/css/admin.css +223 -0
- data/lib/thoth/public/css/thoth.css +592 -0
- data/lib/thoth/public/images/admin-sprite.png +0 -0
- data/lib/thoth/public/images/thoth-sprite.png +0 -0
- data/lib/thoth/public/js/admin/comments.js +116 -0
- data/lib/thoth/public/js/admin/name.js +244 -0
- data/lib/thoth/public/js/admin/tagcomplete.js +332 -0
- data/lib/thoth/public/js/lazyload-min.js +4 -0
- data/lib/thoth/public/js/thoth.js +203 -0
- data/lib/thoth/public/robots.txt +5 -0
- data/lib/thoth/version.rb +37 -0
- data/lib/thoth/view/admin/index.rhtml +1 -0
- data/lib/thoth/view/admin/login.rhtml +23 -0
- data/lib/thoth/view/admin/toolbar.rhtml +117 -0
- data/lib/thoth/view/admin/welcome.rhtml +58 -0
- data/lib/thoth/view/archive/index.rhtml +24 -0
- data/lib/thoth/view/comment/comment.rhtml +47 -0
- data/lib/thoth/view/comment/delete.rhtml +15 -0
- data/lib/thoth/view/comment/form.rhtml +81 -0
- data/lib/thoth/view/comment/index.rhtml +68 -0
- data/lib/thoth/view/comment/list.rhtml +48 -0
- data/lib/thoth/view/media/delete.rhtml +15 -0
- data/lib/thoth/view/media/edit.rhtml +12 -0
- data/lib/thoth/view/media/form.rhtml +7 -0
- data/lib/thoth/view/media/list.rhtml +35 -0
- data/lib/thoth/view/media/media.rhtml +44 -0
- data/lib/thoth/view/media/new.rhtml +7 -0
- data/lib/thoth/view/page/delete.rhtml +15 -0
- data/lib/thoth/view/page/edit.rhtml +15 -0
- data/lib/thoth/view/page/form.rhtml +57 -0
- data/lib/thoth/view/page/index.rhtml +9 -0
- data/lib/thoth/view/page/list.rhtml +49 -0
- data/lib/thoth/view/page/new.rhtml +15 -0
- data/lib/thoth/view/post/comments.rhtml +12 -0
- data/lib/thoth/view/post/compact.rhtml +48 -0
- data/lib/thoth/view/post/delete.rhtml +15 -0
- data/lib/thoth/view/post/edit.rhtml +15 -0
- data/lib/thoth/view/post/form.rhtml +83 -0
- data/lib/thoth/view/post/index.rhtml +48 -0
- data/lib/thoth/view/post/list.rhtml +61 -0
- data/lib/thoth/view/post/new.rhtml +15 -0
- data/lib/thoth/view/post/tiny.rhtml +4 -0
- data/lib/thoth/view/search/index.rhtml +45 -0
- data/lib/thoth/view/tag/index.rhtml +34 -0
- data/lib/thoth/view/thoth/css.rhtml +9 -0
- data/lib/thoth/view/thoth/footer.rhtml +15 -0
- data/lib/thoth/view/thoth/header.rhtml +23 -0
- data/lib/thoth/view/thoth/index.rhtml +11 -0
- data/lib/thoth/view/thoth/js.rhtml +6 -0
- data/lib/thoth/view/thoth/sidebar.rhtml +38 -0
- data/lib/thoth/view/thoth/util/pager.rhtml +23 -0
- data/lib/thoth/view/thoth/util/simple_pager.rhtml +15 -0
- data/lib/thoth/view/thoth/util/table_sortheader.rhtml +20 -0
- data/lib/thoth.rb +394 -0
- metadata +409 -0
@@ -0,0 +1,262 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
module Thoth
|
30
|
+
class Post < Sequel::Model
|
31
|
+
include Thoth::Helper::Wiki
|
32
|
+
|
33
|
+
plugin :hook_class_methods
|
34
|
+
plugin :validation_helpers
|
35
|
+
|
36
|
+
one_to_many :tags_posts_map, :class => 'Thoth::TagsPostsMap'
|
37
|
+
many_to_many :tags, :class => 'Thoth::Tag', :join_table => :tags_posts_map,
|
38
|
+
:order => :name
|
39
|
+
|
40
|
+
before_create do
|
41
|
+
self.created_at = Time.now
|
42
|
+
end
|
43
|
+
|
44
|
+
before_destroy do
|
45
|
+
TagsPostsMap.filter(:post_id => id).delete
|
46
|
+
Comment.filter(:post_id => id).delete
|
47
|
+
end
|
48
|
+
|
49
|
+
before_save do
|
50
|
+
self.updated_at = Time.now
|
51
|
+
end
|
52
|
+
|
53
|
+
#--
|
54
|
+
# Class Methods
|
55
|
+
#++
|
56
|
+
|
57
|
+
# Gets the published post with the specified name, where _name_ can be
|
58
|
+
# either a name or an id. Does not return drafts.
|
59
|
+
def self.get(name)
|
60
|
+
return Post[:id => name, :is_draft => false] if name.is_a?(Numeric)
|
61
|
+
|
62
|
+
name = name.to_s.downcase
|
63
|
+
name =~ /^\d+$/ ? Post[:id => name, :is_draft => false] :
|
64
|
+
Post[:name => name, :is_draft => false]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns true if the specified post name is already taken or is a reserved
|
68
|
+
# name.
|
69
|
+
def self.name_unique?(name)
|
70
|
+
!PostController.methods.include?(name) &&
|
71
|
+
!PostController.instance_methods.include?(name) &&
|
72
|
+
!Post[:name => name.to_s.downcase]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns true if the specified post name consists of valid characters and
|
76
|
+
# is not too long or too short.
|
77
|
+
def self.name_valid?(name)
|
78
|
+
!!(name =~ /^[0-9a-z_-]{1,64}$/i) && !(name =~ /^[0-9]+$/)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Gets a paginated dataset of recent published posts sorted in reverse order
|
82
|
+
# by creation time. Does not return drafts.
|
83
|
+
def self.recent(page = 1, limit = 10)
|
84
|
+
filter(:is_draft => false).reverse_order(:created_at).paginate(page,
|
85
|
+
limit)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Gets a paginated dataset of recent draft posts sorted in reverse order
|
89
|
+
# by creation time. Does not return published posts.
|
90
|
+
def self.recent_drafts(page = 1, limit = 10)
|
91
|
+
filter(:is_draft => true).reverse_order(:created_at).paginate(page,
|
92
|
+
limit)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns a valid, unique post name based on the specified title. If the
|
96
|
+
# title is empty or cannot be converted into a valid name, an empty string
|
97
|
+
# will be returned.
|
98
|
+
def self.suggest_name(title)
|
99
|
+
index = 1
|
100
|
+
|
101
|
+
# Remove HTML entities and non-alphanumeric characters, replace spaces
|
102
|
+
# with hyphens, and truncate the name at 64 characters.
|
103
|
+
name = title.to_s.strip.downcase.gsub(/&[^\s;]+;/, '_').
|
104
|
+
gsub(/[^\s0-9a-z-]/, '').gsub(/\s+/, '-')[0..63]
|
105
|
+
|
106
|
+
# Strip off any trailing non-alphanumeric characters.
|
107
|
+
name.gsub!(/[_-]+$/, '')
|
108
|
+
|
109
|
+
return '' if name.empty?
|
110
|
+
|
111
|
+
# If the name consists solely of numeric characters, add an alpha
|
112
|
+
# character to prevent name/id ambiguity.
|
113
|
+
name += '_' unless name =~ /[a-z_-]/
|
114
|
+
|
115
|
+
# Ensure that the name doesn't conflict with any methods on the Post
|
116
|
+
# controller and that no two posts have the same name.
|
117
|
+
until self.name_unique?(name)
|
118
|
+
if name[-1] == index
|
119
|
+
name[-1] = (index += 1).to_s
|
120
|
+
else
|
121
|
+
name = name[0..62] if name.size >= 64
|
122
|
+
name += (index += 1).to_s
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
return name
|
127
|
+
end
|
128
|
+
|
129
|
+
#--
|
130
|
+
# Instance Methods
|
131
|
+
#++
|
132
|
+
|
133
|
+
# Gets the Atom feed URL for this post.
|
134
|
+
def atom_url
|
135
|
+
Config.site['url'].chomp('/') + PostController.r(:atom, name).to_s
|
136
|
+
end
|
137
|
+
|
138
|
+
def body=(body)
|
139
|
+
self[:body] = body.strip
|
140
|
+
self[:body_rendered] = RedCloth.new(wiki_to_html(body.dup.strip)).to_html
|
141
|
+
end
|
142
|
+
|
143
|
+
# Gets a dataset of visible comments attached to this post, ordered by
|
144
|
+
# creation time.
|
145
|
+
def comments
|
146
|
+
@comments ||= Comment.filter(:post_id => id, :deleted => false).order(:created_at)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Gets the creation time of this post. If _format_ is provided, the time
|
150
|
+
# will be returned as a formatted String. See Time.strftime for details.
|
151
|
+
def created_at(format = nil)
|
152
|
+
if new?
|
153
|
+
format ? Time.now.strftime(format) : Time.now
|
154
|
+
else
|
155
|
+
format ? self[:created_at].strftime(format) : self[:created_at]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def name=(name)
|
160
|
+
self[:name] = name.to_s.strip.downcase unless name.nil?
|
161
|
+
end
|
162
|
+
|
163
|
+
# Gets an Array of tags attached to this post, ordered by name.
|
164
|
+
def tags
|
165
|
+
if new?
|
166
|
+
@fake_tags || []
|
167
|
+
else
|
168
|
+
@tags ||= tags_dataset.all
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def tags=(tag_names)
|
173
|
+
if tag_names.is_a?(String)
|
174
|
+
tag_names = tag_names.split(',', 64)
|
175
|
+
elsif !tag_names.is_a?(Array)
|
176
|
+
raise ArgumentError, "Expected String or Array, got #{tag_names.class}"
|
177
|
+
end
|
178
|
+
|
179
|
+
tag_names = tag_names.map{|n| n.strip.downcase}.uniq.delete_if{|n|
|
180
|
+
n.empty?}
|
181
|
+
|
182
|
+
if new?
|
183
|
+
# This Post hasn't been saved yet, so instead of attaching actual tags
|
184
|
+
# to it, we'll create a bunch of fake tags just for the preview. We
|
185
|
+
# won't create the real ones until the Post is saved.
|
186
|
+
@fake_tags = []
|
187
|
+
|
188
|
+
tag_names.each {|name| @fake_tags << Tag.new(:name => name)}
|
189
|
+
@fake_tags.sort! {|a, b| a.name <=> b.name }
|
190
|
+
|
191
|
+
return @fake_tags
|
192
|
+
else
|
193
|
+
real_tags = []
|
194
|
+
|
195
|
+
# First delete any existing tag mappings for this post.
|
196
|
+
TagsPostsMap.filter(:post_id => id).delete
|
197
|
+
|
198
|
+
# Create new tags and new mappings.
|
199
|
+
tag_names.each do |name|
|
200
|
+
tag = Tag.find_or_create(:name => name)
|
201
|
+
real_tags << tag
|
202
|
+
TagsPostsMap.create(:post_id => id, :tag_id => tag.id)
|
203
|
+
end
|
204
|
+
|
205
|
+
return real_tags
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def title=(title)
|
210
|
+
title.strip!
|
211
|
+
|
212
|
+
# Set the post's name if it isn't already set.
|
213
|
+
if self[:name].nil? || self[:name].empty?
|
214
|
+
self[:name] = Post.suggest_name(title)
|
215
|
+
end
|
216
|
+
|
217
|
+
self[:title] = title
|
218
|
+
end
|
219
|
+
|
220
|
+
# Gets the time this post was last updated. If _format_ is provided, the
|
221
|
+
# time will be returned as a formatted String. See Time.strftime for
|
222
|
+
# details.
|
223
|
+
def updated_at(format = nil)
|
224
|
+
if new?
|
225
|
+
format ? Time.now.strftime(format) : Time.now
|
226
|
+
else
|
227
|
+
format ? self[:updated_at].strftime(format) : self[:updated_at]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Gets the URL for this post.
|
232
|
+
def url
|
233
|
+
Config.site['url'].chomp('/') + PostController.r(:/, name).to_s
|
234
|
+
end
|
235
|
+
|
236
|
+
def validate
|
237
|
+
validates_presence(:name, :message => 'Please enter a name for this post.')
|
238
|
+
validates_presence(:title, :message => 'Please enter a title for this post.')
|
239
|
+
validates_presence(:body, :message => "What's the matter? Cat got your tongue?")
|
240
|
+
|
241
|
+
validates_max_length(255, :title, :message => 'Please enter a title under 255 characters.')
|
242
|
+
validates_max_length(64, :name, :message => 'Please enter a name under 64 characters.')
|
243
|
+
|
244
|
+
validates_format(/^[0-9a-z_-]+$/i, :name, :message => 'Post names may only contain letters, numbers, underscores, and dashes.')
|
245
|
+
validates_format(/[a-z_-]/i, :name, :message => 'Post names must contain at least one non-numeric character.')
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
unless Post.count > 0
|
251
|
+
Post.create(
|
252
|
+
:title => 'Welcome to your new Thoth blog',
|
253
|
+
:body => %[
|
254
|
+
If you're reading this, you've successfully installed Thoth. Congratulations!
|
255
|
+
|
256
|
+
Once you've <a href="txmt://open/?url=file://#{Thoth.trait[:config_file]}">edited the config file</a> to your liking, you can <a href="/admin">login</a> and begin creating blog posts and pages. You can also delete this post to make way for your own glorious words.
|
257
|
+
|
258
|
+
Enjoy!
|
259
|
+
].unindent
|
260
|
+
)
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
module Thoth
|
30
|
+
class Tag < Sequel::Model
|
31
|
+
plugin :validation_helpers
|
32
|
+
|
33
|
+
one_to_many :tags_posts_map, :class => 'Thoth::TagsPostsMap'
|
34
|
+
many_to_many :posts, :class => 'Thoth::Post',
|
35
|
+
:join_table => :tags_posts_map, :read_only => true
|
36
|
+
|
37
|
+
#--
|
38
|
+
# Class Methods
|
39
|
+
#++
|
40
|
+
|
41
|
+
# Gets an array of tag names and post counts for tags with names that begin
|
42
|
+
# with the specified query string.
|
43
|
+
def self.suggest(query, limit = 1000)
|
44
|
+
tags = []
|
45
|
+
|
46
|
+
self.dataset.grep(:name, "#{query}%").all do |tag|
|
47
|
+
tags << [tag.name, tag.posts.count]
|
48
|
+
end
|
49
|
+
|
50
|
+
tags.sort!{|a, b| b[1] <=> a[1]}
|
51
|
+
tags[0, limit]
|
52
|
+
end
|
53
|
+
|
54
|
+
#--
|
55
|
+
# Instance Methods
|
56
|
+
#++
|
57
|
+
|
58
|
+
# Gets the Atom feed URL for this tag.
|
59
|
+
def atom_url
|
60
|
+
Config.site['url'].chomp('/') + TagController.r(:atom, name).to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
# Gets published posts with this tag.
|
64
|
+
def posts
|
65
|
+
@posts ||= posts_dataset.filter(:is_draft => false).reverse_order(
|
66
|
+
:created_at)
|
67
|
+
end
|
68
|
+
|
69
|
+
# URL for this tag.
|
70
|
+
def url
|
71
|
+
Config.site['url'].chomp('/') + TagController.r(:/, name).to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate
|
75
|
+
validates_presence(:name)
|
76
|
+
validates_max_length(64, :name)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
module Thoth
|
30
|
+
class TagsPostsMap < Sequel::Model(:tags_posts_map)
|
31
|
+
many_to_one :tag, :class => 'Thoth::Tag'
|
32
|
+
many_to_one :post, :class => 'Thoth::Post'
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
module Sequel; class Model
|
30
|
+
|
31
|
+
class Errors
|
32
|
+
def on_field(att)
|
33
|
+
on(att) || []
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end; end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
require 'cgi'
|
30
|
+
require 'json'
|
31
|
+
require 'open-uri'
|
32
|
+
require 'timeout'
|
33
|
+
|
34
|
+
module Thoth; module Plugin
|
35
|
+
|
36
|
+
# Del.icio.us plugin for Thoth.
|
37
|
+
module Delicious
|
38
|
+
FEED_URL = 'http://feeds.delicious.com/feeds/json'
|
39
|
+
|
40
|
+
Config << {
|
41
|
+
'delicious' => {
|
42
|
+
|
43
|
+
# Time in seconds to cache results. It's a good idea to keep this nice
|
44
|
+
# and high both to improve the performance of your site and to avoid
|
45
|
+
# pounding on del.icio.us's servers. Default is 900 seconds (15
|
46
|
+
# minutes).
|
47
|
+
'cache_ttl' => 900,
|
48
|
+
|
49
|
+
# Request timeout in seconds.
|
50
|
+
'request_timeout' => 5
|
51
|
+
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
class << self
|
56
|
+
|
57
|
+
# Gets recent del.icio.us bookmarks for the specified _username_. The
|
58
|
+
# return value of this method is cached to improve performance and to
|
59
|
+
# avoid pounding del.icio.us with excess traffic.
|
60
|
+
#
|
61
|
+
# Available options:
|
62
|
+
# [<tt>:count</tt>] Number of bookmarks to return (default is 5)
|
63
|
+
# [<tt>:tags</tt>] Array of tags to filter by. Only bookmarks with the
|
64
|
+
# specified tags will be returned.
|
65
|
+
#
|
66
|
+
def recent_bookmarks(username, options = {})
|
67
|
+
cache = Ramaze::Cache.plugin
|
68
|
+
options = {:count => 5}.merge(options)
|
69
|
+
request = "#{FEED_URL}/#{::CGI.escape(username)}" <<
|
70
|
+
(options[:tags] ? '/' << ::CGI.escape(options[:tags].join(' ')) : '') <<
|
71
|
+
"?raw&count=#{options[:count]}"
|
72
|
+
|
73
|
+
if value = cache[request]
|
74
|
+
return value
|
75
|
+
end
|
76
|
+
|
77
|
+
response = []
|
78
|
+
|
79
|
+
Timeout.timeout(Config.delicious['request_timeout'], StandardError) do
|
80
|
+
response = JSON.parse(open(request).read)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Parse the response into a more friendly format.
|
84
|
+
data = []
|
85
|
+
|
86
|
+
response.each do |item|
|
87
|
+
data << {
|
88
|
+
:url => item['u'],
|
89
|
+
:desc => item['d'],
|
90
|
+
:note => item['n'] ? item['n'] : '',
|
91
|
+
:tags => item['t'] ? item['t'] : []
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
return cache.store(request, data, :ttl => Config.delicious['cache_ttl'])
|
96
|
+
|
97
|
+
rescue => e
|
98
|
+
Ramaze::Log.error "Thoth::Plugin::Delicious: #{e.message}"
|
99
|
+
return []
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end; end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
require 'timeout'
|
30
|
+
require 'net/flickr'
|
31
|
+
|
32
|
+
module Thoth; module Plugin
|
33
|
+
|
34
|
+
# Flickr plugin for Thoth.
|
35
|
+
module Flickr
|
36
|
+
|
37
|
+
Config << {
|
38
|
+
'flickr' => {
|
39
|
+
|
40
|
+
# Flickr API key. You can either use the default or replace this with
|
41
|
+
# your own key.
|
42
|
+
'api_key' => '5b1d9919cb2d97585bd3d83e05af80b8',
|
43
|
+
|
44
|
+
# Time in seconds to cache results. It's a good idea to keep this nice
|
45
|
+
# and high both to improve the performance of your site and to avoid
|
46
|
+
# pounding on Flickr's servers. Default is 900 seconds (15 minutes).
|
47
|
+
'cache_ttl' => 900,
|
48
|
+
|
49
|
+
# Request timeout in seconds.
|
50
|
+
'request_timeout' => 5
|
51
|
+
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
class << self
|
56
|
+
|
57
|
+
# Gets recent Flickr photos (up to _limit_) for the specified _username_.
|
58
|
+
# The return value of this method is cached to improve performance and to
|
59
|
+
# avoid abusing the Flickr API.
|
60
|
+
def recent_photos(username, limit = 4)
|
61
|
+
cache = Ramaze::Cache.plugin
|
62
|
+
key = "recent_photos_#{username}_#{limit}"
|
63
|
+
|
64
|
+
if value = cache[key]
|
65
|
+
return value
|
66
|
+
end
|
67
|
+
|
68
|
+
@flickr ||= Net::Flickr.new(Config.flickr['api_key'])
|
69
|
+
|
70
|
+
begin
|
71
|
+
Timeout.timeout(Config.flickr['request_timeout'].to_i, StandardError) do
|
72
|
+
value = cache.store(key, @flickr.people.find_by_username(username).
|
73
|
+
photos(:per_page => limit), :ttl => Config.flickr['cache_ttl'])
|
74
|
+
end
|
75
|
+
rescue => e
|
76
|
+
Ramaze::Log.error "Thoth::Plugin::Flickr: #{e.message}"
|
77
|
+
return []
|
78
|
+
else
|
79
|
+
value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end; end
|