cortex-reaver 0.0.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.
- data/LICENSE +25 -0
- data/README +48 -0
- data/bin/console +11 -0
- data/bin/cortex_reaver +110 -0
- data/bin/import.rb +78 -0
- data/lib/cortex_reaver.rb +114 -0
- data/lib/cortex_reaver/config.rb +21 -0
- data/lib/cortex_reaver/controller/comment.rb +113 -0
- data/lib/cortex_reaver/controller/config.rb +14 -0
- data/lib/cortex_reaver/controller/journal.rb +40 -0
- data/lib/cortex_reaver/controller/main.rb +56 -0
- data/lib/cortex_reaver/controller/page.rb +34 -0
- data/lib/cortex_reaver/controller/photograph.rb +45 -0
- data/lib/cortex_reaver/controller/project.rb +40 -0
- data/lib/cortex_reaver/controller/tag.rb +67 -0
- data/lib/cortex_reaver/controller/user.rb +71 -0
- data/lib/cortex_reaver/helper/activity.rb +9 -0
- data/lib/cortex_reaver/helper/attachments.rb +139 -0
- data/lib/cortex_reaver/helper/auth.rb +45 -0
- data/lib/cortex_reaver/helper/canonical.rb +55 -0
- data/lib/cortex_reaver/helper/crud.rb +317 -0
- data/lib/cortex_reaver/helper/date.rb +29 -0
- data/lib/cortex_reaver/helper/error.rb +14 -0
- data/lib/cortex_reaver/helper/feeds.rb +88 -0
- data/lib/cortex_reaver/helper/form.rb +114 -0
- data/lib/cortex_reaver/helper/navigation.rb +142 -0
- data/lib/cortex_reaver/helper/photographs.rb +25 -0
- data/lib/cortex_reaver/helper/tags.rb +47 -0
- data/lib/cortex_reaver/helper/workflow.rb +27 -0
- data/lib/cortex_reaver/migrations/001_users.rb +24 -0
- data/lib/cortex_reaver/migrations/002_pages.rb +29 -0
- data/lib/cortex_reaver/migrations/003_journals.rb +26 -0
- data/lib/cortex_reaver/migrations/004_photographs.rb +24 -0
- data/lib/cortex_reaver/migrations/005_projects.rb +27 -0
- data/lib/cortex_reaver/migrations/006_tags.rb +64 -0
- data/lib/cortex_reaver/migrations/007_comments.rb +40 -0
- data/lib/cortex_reaver/migrations/008_config.rb +23 -0
- data/lib/cortex_reaver/model/comment.rb +109 -0
- data/lib/cortex_reaver/model/journal.rb +53 -0
- data/lib/cortex_reaver/model/page.rb +87 -0
- data/lib/cortex_reaver/model/photograph.rb +133 -0
- data/lib/cortex_reaver/model/project.rb +49 -0
- data/lib/cortex_reaver/model/tag.rb +72 -0
- data/lib/cortex_reaver/model/user.rb +147 -0
- data/lib/cortex_reaver/public/css/admin.css +45 -0
- data/lib/cortex_reaver/public/css/custom.css +0 -0
- data/lib/cortex_reaver/public/css/form.css +51 -0
- data/lib/cortex_reaver/public/css/main.css +325 -0
- data/lib/cortex_reaver/public/css/photo.css +113 -0
- data/lib/cortex_reaver/public/css/ramaze_error.css +90 -0
- data/lib/cortex_reaver/public/css/text.css +25 -0
- data/lib/cortex_reaver/public/dispatch.fcgi +11 -0
- data/lib/cortex_reaver/public/images/CortexReaver.gif +0 -0
- data/lib/cortex_reaver/public/images/atom-xml-icon.png +0 -0
- data/lib/cortex_reaver/public/images/body.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom_right.png +0 -0
- data/lib/cortex_reaver/public/images/border_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_right.png +0 -0
- data/lib/cortex_reaver/public/images/border_top.png +0 -0
- data/lib/cortex_reaver/public/images/border_top_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_top_right.png +0 -0
- data/lib/cortex_reaver/public/images/comment.gif +0 -0
- data/lib/cortex_reaver/public/images/dark_trans.png +0 -0
- data/lib/cortex_reaver/public/images/delete.gif +0 -0
- data/lib/cortex_reaver/public/images/edit.gif +0 -0
- data/lib/cortex_reaver/public/images/header.png +0 -0
- data/lib/cortex_reaver/public/images/header.xcf +0 -0
- data/lib/cortex_reaver/public/images/header_background.png +0 -0
- data/lib/cortex_reaver/public/images/parent.gif +0 -0
- data/lib/cortex_reaver/public/images/rss-xml-icon.png +0 -0
- data/lib/cortex_reaver/public/images/sections.png +0 -0
- data/lib/cortex_reaver/public/images/sections_highlight.png +0 -0
- data/lib/cortex_reaver/public/js/admin.js +36 -0
- data/lib/cortex_reaver/public/js/cookie.js +27 -0
- data/lib/cortex_reaver/public/js/jquery.js +32 -0
- data/lib/cortex_reaver/public/js/photo.js +33 -0
- data/lib/cortex_reaver/snippets/array.rb +7 -0
- data/lib/cortex_reaver/snippets/ramaze/dispatcher/file.rb +37 -0
- data/lib/cortex_reaver/support/attachments.rb +235 -0
- data/lib/cortex_reaver/support/cached_rendering.rb +79 -0
- data/lib/cortex_reaver/support/canonical.rb +107 -0
- data/lib/cortex_reaver/support/comments.rb +69 -0
- data/lib/cortex_reaver/support/pagination.rb +38 -0
- data/lib/cortex_reaver/support/renderer.rb +196 -0
- data/lib/cortex_reaver/support/sequenceable.rb +248 -0
- data/lib/cortex_reaver/support/tags.rb +108 -0
- data/lib/cortex_reaver/support/timestamps.rb +33 -0
- data/lib/cortex_reaver/version.rb +8 -0
- data/lib/cortex_reaver/view/adminbox.rhtml +56 -0
- data/lib/cortex_reaver/view/blank_layout.rhtml +46 -0
- data/lib/cortex_reaver/view/comments/comment.rhtml +34 -0
- data/lib/cortex_reaver/view/comments/form.rhtml +25 -0
- data/lib/cortex_reaver/view/comments/list.rhtml +5 -0
- data/lib/cortex_reaver/view/comments/post_form.rhtml +36 -0
- data/lib/cortex_reaver/view/config/form.rhtml +10 -0
- data/lib/cortex_reaver/view/error.rhtml +72 -0
- data/lib/cortex_reaver/view/journals/form.rhtml +12 -0
- data/lib/cortex_reaver/view/journals/journal.rhtml +39 -0
- data/lib/cortex_reaver/view/journals/list.rhtml +33 -0
- data/lib/cortex_reaver/view/journals/short.rhtml +3 -0
- data/lib/cortex_reaver/view/journals/show.rhtml +5 -0
- data/lib/cortex_reaver/view/pages/form.rhtml +12 -0
- data/lib/cortex_reaver/view/pages/list.rhtml +26 -0
- data/lib/cortex_reaver/view/pages/show.rhtml +12 -0
- data/lib/cortex_reaver/view/photographs/atom_fragment.rhtml +82 -0
- data/lib/cortex_reaver/view/photographs/form.rhtml +19 -0
- data/lib/cortex_reaver/view/photographs/grid.rhtml +36 -0
- data/lib/cortex_reaver/view/photographs/list.rhtml +9 -0
- data/lib/cortex_reaver/view/photographs/short.rhtml +3 -0
- data/lib/cortex_reaver/view/photographs/show.rhtml +114 -0
- data/lib/cortex_reaver/view/photographs/sidebar.rhtml +7 -0
- data/lib/cortex_reaver/view/projects/form.rhtml +13 -0
- data/lib/cortex_reaver/view/projects/list.rhtml +27 -0
- data/lib/cortex_reaver/view/projects/show.rhtml +38 -0
- data/lib/cortex_reaver/view/tags/form.rhtml +9 -0
- data/lib/cortex_reaver/view/tags/list.rhtml +28 -0
- data/lib/cortex_reaver/view/tags/show.rhtml +51 -0
- data/lib/cortex_reaver/view/text_layout.rhtml +78 -0
- data/lib/cortex_reaver/view/users/form.rhtml +16 -0
- data/lib/cortex_reaver/view/users/list.rhtml +38 -0
- data/lib/cortex_reaver/view/users/login.rhtml +13 -0
- data/lib/cortex_reaver/view/users/register.rhtml +13 -0
- data/lib/cortex_reaver/view/users/show.rhtml +37 -0
- data/lib/cortex_reaver/view/users/user.rhtml +35 -0
- data/lib/proto/cortex_reaver.yaml +28 -0
- metadata +285 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
module CortexReaver
|
|
2
|
+
module Model
|
|
3
|
+
# Sequences are models that have order. This module provides class
|
|
4
|
+
# and instance methods that support sequential behavior like pagination, finding
|
|
5
|
+
# the next or previoud record, and finding a "window" of results around a
|
|
6
|
+
# specific record.
|
|
7
|
+
|
|
8
|
+
module Sequenceable
|
|
9
|
+
|
|
10
|
+
# Which column table to order this sequence by
|
|
11
|
+
DEFAULT_SEQUENCE_ORDER = :created_on
|
|
12
|
+
DEFAULT_SEQUENCE_REVERSE = false
|
|
13
|
+
DEFAULT_WINDOW_SIZE = 16
|
|
14
|
+
|
|
15
|
+
# Class methods
|
|
16
|
+
def self.included(base)
|
|
17
|
+
base.instance_eval do
|
|
18
|
+
attr_writer :sequence_order, :sequence_reverse, :window_size
|
|
19
|
+
|
|
20
|
+
# Returns the sequence dataset (optionally, restricted to dataset)
|
|
21
|
+
def sequence(dataset = self.dataset)
|
|
22
|
+
if sequence_reverse
|
|
23
|
+
dataset.reverse_order(sequence_order)
|
|
24
|
+
else
|
|
25
|
+
dataset.order(sequence_order)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns the first record in the sequence
|
|
30
|
+
def first
|
|
31
|
+
sequence.first
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the last record in the sequence
|
|
35
|
+
def last
|
|
36
|
+
sequence.last
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the table column to order by
|
|
40
|
+
def sequence_order
|
|
41
|
+
@sequence_order || DEFAULT_SEQUENCE_ORDER
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sets the table column to order by
|
|
45
|
+
def sequence_order=(order)
|
|
46
|
+
@sequence_order = order
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns whether or not to reverse the order
|
|
50
|
+
def sequence_reverse
|
|
51
|
+
@sequence_reverse || DEFAULT_SEQUENCE_REVERSE
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Sets whether to reverse the sequence
|
|
55
|
+
def sequence_reverse=(reverse)
|
|
56
|
+
@sequence_reverse = reverse
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns an absolute window into this sequence, specified as an offset in
|
|
60
|
+
# window-sized increments from the beginning of the sequence. Hence
|
|
61
|
+
# window(0, 10) returns the first ten records in the sequence, window(1, 10)
|
|
62
|
+
# the next ten, and so on.
|
|
63
|
+
#
|
|
64
|
+
# One can also specify the special page_offsets :first or :last, which
|
|
65
|
+
# return the first and last available windows.
|
|
66
|
+
def window(page_offset, size = self.window_size)
|
|
67
|
+
case page_offset
|
|
68
|
+
when :first
|
|
69
|
+
page_offset = 0
|
|
70
|
+
when :last
|
|
71
|
+
page_offset = window_count(size) - 1
|
|
72
|
+
else
|
|
73
|
+
page_offset = page_offset.to_i
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Don't ask for negative pages!
|
|
77
|
+
page_offset = 0 if page_offset < 0
|
|
78
|
+
|
|
79
|
+
# Calculate offset
|
|
80
|
+
offset = page_offset * size
|
|
81
|
+
|
|
82
|
+
# Limit dataset
|
|
83
|
+
sequence.limit size, offset
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the number of windows required to span this sequence
|
|
87
|
+
def window_count(size = self.window_size)
|
|
88
|
+
(Float(sequence.count) / size).ceil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the size of a window into this sequence
|
|
92
|
+
def window_size
|
|
93
|
+
@window_size || DEFAULT_WINDOW_SIZE
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Sets the size of a window into this sequence
|
|
97
|
+
def window_size=(size)
|
|
98
|
+
@window_size = size
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Instance methods
|
|
104
|
+
|
|
105
|
+
# Convenience references to class methods
|
|
106
|
+
def sequence
|
|
107
|
+
self.class.sequence
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def sequence_order
|
|
111
|
+
self.class.sequence_order
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def sequence_reverse
|
|
115
|
+
self.class.sequence_reverse
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns the next record in the sequence. Caches--use next(true) to refresh.
|
|
119
|
+
def next(refresh_cache = false)
|
|
120
|
+
if refresh_cache
|
|
121
|
+
@next = sequence.filter(sequence_order > send(sequence_order)).limit(1).first
|
|
122
|
+
else
|
|
123
|
+
@next ||= sequence.filter(sequence_order > send(sequence_order)).limit(1).first
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns true if a next record exists. Caches--use next(true) to refresh.
|
|
128
|
+
def next?(refresh_cache = false)
|
|
129
|
+
if refresh_cache
|
|
130
|
+
@next_exists ||= self.next_count > 0 ? true : false
|
|
131
|
+
else
|
|
132
|
+
@next_exists = self.next_count > 0 ? true : false
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns the number of succeeding records
|
|
137
|
+
def next_count
|
|
138
|
+
sequence.filter(sequence_order > send(sequence_order)).count
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns the previous record in the sequence. Caches--use previous(true) to
|
|
142
|
+
# refresh.
|
|
143
|
+
def previous(refresh_cache = false)
|
|
144
|
+
if refresh_cache
|
|
145
|
+
@previous = sequence.filter(sequence_order < send(sequence_order)).reverse.limit(1).first
|
|
146
|
+
else
|
|
147
|
+
@previous ||= sequence.filter(sequence_order < send(sequence_order)).reverse.limit(1).first
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Returns true if a previous record exists. Caches--use previous?(true) to
|
|
152
|
+
# refresh.
|
|
153
|
+
def previous?(refresh_cache = false)
|
|
154
|
+
if refresh_cache
|
|
155
|
+
@previous_exists = self.previous_count > 0 ? true : false
|
|
156
|
+
else
|
|
157
|
+
@previous_exists ||= self.previous_count > 0 ? true : false
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns the number of preceding records
|
|
162
|
+
def previous_count
|
|
163
|
+
sequence.filter(sequence_order < send(sequence_order)).count
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
alias :position :previous_count
|
|
167
|
+
|
|
168
|
+
# Returns a collection of records around this record.
|
|
169
|
+
# Mode is one of:
|
|
170
|
+
# 1. :absolute Returns a window containing this record in even multiples from
|
|
171
|
+
# the first record, like a page of a book.
|
|
172
|
+
# 2. :float Returns a centered window of the provided size. If the edge of
|
|
173
|
+
# the sequence is reached, the window shifts to include more
|
|
174
|
+
# elements of the sequence. If no more elements are available
|
|
175
|
+
# (the window is larger than the size of the sequence), the
|
|
176
|
+
# entire sequence is returned. If the size is even, one more
|
|
177
|
+
# record is returned suceeding this record than preceding this
|
|
178
|
+
# record, if possible.
|
|
179
|
+
# 3. :clip Returns a centered window of or smaller than the given size. If
|
|
180
|
+
# the edge of the sequence is reached, the window is clipped.
|
|
181
|
+
def window(mode = :absolute, size = self.class.window_size)
|
|
182
|
+
case mode
|
|
183
|
+
when :absolute
|
|
184
|
+
# Calculate page offset
|
|
185
|
+
offset = (Float(position) / size).floor * size
|
|
186
|
+
|
|
187
|
+
# Find records
|
|
188
|
+
sequence.limit(size, offset)
|
|
189
|
+
when :float
|
|
190
|
+
count = sequence.count # Total records
|
|
191
|
+
if size >= count
|
|
192
|
+
# Window includes all records in the sequence
|
|
193
|
+
return self.class.find(:all)
|
|
194
|
+
else
|
|
195
|
+
# Window is smaller than the sequence
|
|
196
|
+
position = self.position # This record's position
|
|
197
|
+
previous_size = (Float(size - 1) / 2).floor # How many records before
|
|
198
|
+
next_size = (Float(size - 1) / 2).ceil # How many records after
|
|
199
|
+
|
|
200
|
+
# Shift window if necessary
|
|
201
|
+
if (displacement = previous_size - position) > 0
|
|
202
|
+
# The window extends before the start of the sequence
|
|
203
|
+
previous_size -= displacement
|
|
204
|
+
elsif (displacement = next_size - self.next_count) > 0
|
|
205
|
+
# The window extends beyond the end of the sequence
|
|
206
|
+
previous_size += displacement
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Calculate window offset
|
|
210
|
+
offset = position - previous_size
|
|
211
|
+
|
|
212
|
+
# Find records
|
|
213
|
+
sequence.limit(size, offset)
|
|
214
|
+
end
|
|
215
|
+
when :clip
|
|
216
|
+
position = self.position # Our position in sequence
|
|
217
|
+
previous_size = (Float(size - 1) / 2).floor # How many records before
|
|
218
|
+
|
|
219
|
+
if (displacement = previous_size - position) > 0
|
|
220
|
+
# The window extends before the beginning of the sequence
|
|
221
|
+
size -= displacement
|
|
222
|
+
offset = 0
|
|
223
|
+
else
|
|
224
|
+
# The window doesn't extend before the beginning of the sequence.
|
|
225
|
+
offset = position - previous_size
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Find records.
|
|
229
|
+
sequence.limit(size, offset)
|
|
230
|
+
else
|
|
231
|
+
raise ArgumentError.new('first argument must be one of :absolute, :float, :clip')
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Returns a url for this record, viewed in an absolute window. TODO: handle non-default window sizes.
|
|
236
|
+
def absolute_window_url(size = self.class.window_size)
|
|
237
|
+
page = (Float(position) / size).floor
|
|
238
|
+
self.class.url + '/page/' + page.to_s + '#' + self.class.to_s.underscore +
|
|
239
|
+
'_' + send(self.class.canonical_name_attr)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Returns the absolute window index that this record appears in
|
|
243
|
+
def absolute_window_index(size = self.class.window_size)
|
|
244
|
+
(Float(self.position) / size).floor
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module CortexReaver
|
|
2
|
+
module Model
|
|
3
|
+
module Tags
|
|
4
|
+
# Support for taggable models
|
|
5
|
+
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.class_eval do
|
|
8
|
+
# If tags have changed, make sure to update those tags' counts.
|
|
9
|
+
after_save(:update_tag_counts) do
|
|
10
|
+
if @added_tags
|
|
11
|
+
@added_tags.each do |tag|
|
|
12
|
+
tag.count += 1
|
|
13
|
+
tag.save
|
|
14
|
+
end
|
|
15
|
+
@removed_tags.each do |tag|
|
|
16
|
+
tag.count -= 1
|
|
17
|
+
tag.save
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Remove tags on deletion
|
|
23
|
+
before_destroy(:drop_associated_tags) do
|
|
24
|
+
tags.each do |tag|
|
|
25
|
+
tag.count -= 1
|
|
26
|
+
if tag.count == 0
|
|
27
|
+
tag.destroy
|
|
28
|
+
else
|
|
29
|
+
tag.save
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
remove_all_tags
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns all models with ALL the following tags:
|
|
36
|
+
def self.tagged_with(tags)
|
|
37
|
+
# Find map between this model and tags, e.g. pages_tags
|
|
38
|
+
map = [table_name.to_s, 'tags'].sort.join('_').to_sym
|
|
39
|
+
|
|
40
|
+
# The name in the mapping table which points to us
|
|
41
|
+
own_id = (table_name.to_s.singularize + '_id').to_sym
|
|
42
|
+
|
|
43
|
+
# The tag IDs to search for
|
|
44
|
+
ids = tags.map { |t| t.id }
|
|
45
|
+
|
|
46
|
+
# Now filter this model, finding all ids which appear n times with
|
|
47
|
+
# the same model_id in the mapping table, which has been filtered
|
|
48
|
+
# to contain only rows with one of our interesting tags.
|
|
49
|
+
#
|
|
50
|
+
# Man, don't you wish MySQL had intersect?
|
|
51
|
+
filter(:id =>
|
|
52
|
+
CortexReaver.db[map].filter(:tag_id => ids).group(own_id).having(
|
|
53
|
+
"count(*) = #{ids.size}"
|
|
54
|
+
).select(own_id)
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Set tags from a string, or array of tags. Finds existing tags or
|
|
61
|
+
# creates new ones. Also updates tag counts.
|
|
62
|
+
def tags=(tags)
|
|
63
|
+
if tags.kind_of? String
|
|
64
|
+
tags = tags.squeeze(',').split(',').map do |title|
|
|
65
|
+
title.strip!
|
|
66
|
+
if title.blank?
|
|
67
|
+
# Do nothing
|
|
68
|
+
tag = nil
|
|
69
|
+
else
|
|
70
|
+
# Look up existing tag
|
|
71
|
+
tag = Tag[:title => title]
|
|
72
|
+
# Or create new one
|
|
73
|
+
tag ||= Tag.create(:title => title, :name => Tag.canonicalize(title))
|
|
74
|
+
unless tag
|
|
75
|
+
# Everything failed
|
|
76
|
+
raise RuntimeError.new("couldn't find or create tag for #{title}")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
tag
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
unless tags.respond_to? :each
|
|
84
|
+
raise ArgumentError.new("Needed a string or Enumerable of Tags, got #{tags.class}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get rid of empty tags
|
|
88
|
+
tags.reject do |tag|
|
|
89
|
+
tag.nil?
|
|
90
|
+
end
|
|
91
|
+
tags.uniq!
|
|
92
|
+
|
|
93
|
+
# Find which tags to change. Their counts are updated by an after_save
|
|
94
|
+
# callback.
|
|
95
|
+
old_tags = self.tags
|
|
96
|
+
@removed_tags = old_tags - tags
|
|
97
|
+
@added_tags = tags - old_tags
|
|
98
|
+
|
|
99
|
+
# Change own tags
|
|
100
|
+
remove_all_tags
|
|
101
|
+
tags.each do |tag|
|
|
102
|
+
add_tag tag
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module CortexReaver
|
|
2
|
+
module Model
|
|
3
|
+
# Supports updated_on and created_on behavior
|
|
4
|
+
module Timestamps
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.class_eval do
|
|
7
|
+
# Create
|
|
8
|
+
before_create(:init_timestamp) do
|
|
9
|
+
unless skip_timestamp_update?
|
|
10
|
+
self.created_on = Time.now
|
|
11
|
+
self.updated_on = Time.now
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Update
|
|
16
|
+
before_update(:update_timestamp) do
|
|
17
|
+
unless skip_timestamp_update?
|
|
18
|
+
self.updated_on = Time.now
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def skip_timestamp_update=(boolean)
|
|
25
|
+
@skip_timestamp_update = boolean
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def skip_timestamp_update?
|
|
29
|
+
@skip_timestamp_update
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<div id="adminbox">
|
|
2
|
+
<% if admin? %>
|
|
3
|
+
<table class="actions-table">
|
|
4
|
+
<tr>
|
|
5
|
+
<td class="title">Sections</td>
|
|
6
|
+
<td>
|
|
7
|
+
<ul class="actions">
|
|
8
|
+
<li><a href="/pages">Pages</a></li>
|
|
9
|
+
<li><a href="/projects">Projects</a></li>
|
|
10
|
+
<li><a href="/journals">Journals</a></li>
|
|
11
|
+
<li><a href="/photographs">Photographs</a></li>
|
|
12
|
+
<li><a href="/comments">Comments</a></li>
|
|
13
|
+
<li><a href="/tags">Tags</a></li>
|
|
14
|
+
<li><a href="/users">Users</a></li>
|
|
15
|
+
<li><a href="/config">Config</a></li>
|
|
16
|
+
</ul>
|
|
17
|
+
</td>
|
|
18
|
+
</tr>
|
|
19
|
+
<% if workflows %>
|
|
20
|
+
<tr>
|
|
21
|
+
<td class="title">Actions</td>
|
|
22
|
+
<td>
|
|
23
|
+
<ul class="actions">
|
|
24
|
+
<% workflows.each do |name, href| %>
|
|
25
|
+
<li>
|
|
26
|
+
<a href="<%= href %>"
|
|
27
|
+
<%= name.to_s[/delete|destroy/i] ? "onclick = \"return confirm('Are you sure you want to delete #{@title.gsub(/'"/, '')}?');\"" : '' %>>
|
|
28
|
+
<%= name %>
|
|
29
|
+
</a>
|
|
30
|
+
</li>
|
|
31
|
+
<% end %>
|
|
32
|
+
</ul>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<% end %>
|
|
36
|
+
</table>
|
|
37
|
+
|
|
38
|
+
<div id="admin_user_actions">
|
|
39
|
+
<a href="/users/logout">Log out</a>
|
|
40
|
+
</div>
|
|
41
|
+
<% elsif session[:user] %>
|
|
42
|
+
<div id="admin_user_actions">
|
|
43
|
+
<a href="/users/logout">Log out</a>
|
|
44
|
+
</div>
|
|
45
|
+
<% else %>
|
|
46
|
+
<form action="/users/login" method="post">
|
|
47
|
+
<p>
|
|
48
|
+
<label for="admin_login">Login</label> <input type="text" name="login" id="admin_login" />
|
|
49
|
+
<label for="admin_password">Password</label> <input type="password" name="password" id="admin_password" />
|
|
50
|
+
<input type="hidden" id="target_uri" name="target_uri" value="<%= request.request_uri %>" />
|
|
51
|
+
<input type="submit" name="action" value="Log in" />
|
|
52
|
+
</p>
|
|
53
|
+
</form>
|
|
54
|
+
<% end %>
|
|
55
|
+
<div class="clear"></div>
|
|
56
|
+
</div>
|