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.
Files changed (128) hide show
  1. data/LICENSE +25 -0
  2. data/README +48 -0
  3. data/bin/console +11 -0
  4. data/bin/cortex_reaver +110 -0
  5. data/bin/import.rb +78 -0
  6. data/lib/cortex_reaver.rb +114 -0
  7. data/lib/cortex_reaver/config.rb +21 -0
  8. data/lib/cortex_reaver/controller/comment.rb +113 -0
  9. data/lib/cortex_reaver/controller/config.rb +14 -0
  10. data/lib/cortex_reaver/controller/journal.rb +40 -0
  11. data/lib/cortex_reaver/controller/main.rb +56 -0
  12. data/lib/cortex_reaver/controller/page.rb +34 -0
  13. data/lib/cortex_reaver/controller/photograph.rb +45 -0
  14. data/lib/cortex_reaver/controller/project.rb +40 -0
  15. data/lib/cortex_reaver/controller/tag.rb +67 -0
  16. data/lib/cortex_reaver/controller/user.rb +71 -0
  17. data/lib/cortex_reaver/helper/activity.rb +9 -0
  18. data/lib/cortex_reaver/helper/attachments.rb +139 -0
  19. data/lib/cortex_reaver/helper/auth.rb +45 -0
  20. data/lib/cortex_reaver/helper/canonical.rb +55 -0
  21. data/lib/cortex_reaver/helper/crud.rb +317 -0
  22. data/lib/cortex_reaver/helper/date.rb +29 -0
  23. data/lib/cortex_reaver/helper/error.rb +14 -0
  24. data/lib/cortex_reaver/helper/feeds.rb +88 -0
  25. data/lib/cortex_reaver/helper/form.rb +114 -0
  26. data/lib/cortex_reaver/helper/navigation.rb +142 -0
  27. data/lib/cortex_reaver/helper/photographs.rb +25 -0
  28. data/lib/cortex_reaver/helper/tags.rb +47 -0
  29. data/lib/cortex_reaver/helper/workflow.rb +27 -0
  30. data/lib/cortex_reaver/migrations/001_users.rb +24 -0
  31. data/lib/cortex_reaver/migrations/002_pages.rb +29 -0
  32. data/lib/cortex_reaver/migrations/003_journals.rb +26 -0
  33. data/lib/cortex_reaver/migrations/004_photographs.rb +24 -0
  34. data/lib/cortex_reaver/migrations/005_projects.rb +27 -0
  35. data/lib/cortex_reaver/migrations/006_tags.rb +64 -0
  36. data/lib/cortex_reaver/migrations/007_comments.rb +40 -0
  37. data/lib/cortex_reaver/migrations/008_config.rb +23 -0
  38. data/lib/cortex_reaver/model/comment.rb +109 -0
  39. data/lib/cortex_reaver/model/journal.rb +53 -0
  40. data/lib/cortex_reaver/model/page.rb +87 -0
  41. data/lib/cortex_reaver/model/photograph.rb +133 -0
  42. data/lib/cortex_reaver/model/project.rb +49 -0
  43. data/lib/cortex_reaver/model/tag.rb +72 -0
  44. data/lib/cortex_reaver/model/user.rb +147 -0
  45. data/lib/cortex_reaver/public/css/admin.css +45 -0
  46. data/lib/cortex_reaver/public/css/custom.css +0 -0
  47. data/lib/cortex_reaver/public/css/form.css +51 -0
  48. data/lib/cortex_reaver/public/css/main.css +325 -0
  49. data/lib/cortex_reaver/public/css/photo.css +113 -0
  50. data/lib/cortex_reaver/public/css/ramaze_error.css +90 -0
  51. data/lib/cortex_reaver/public/css/text.css +25 -0
  52. data/lib/cortex_reaver/public/dispatch.fcgi +11 -0
  53. data/lib/cortex_reaver/public/images/CortexReaver.gif +0 -0
  54. data/lib/cortex_reaver/public/images/atom-xml-icon.png +0 -0
  55. data/lib/cortex_reaver/public/images/body.png +0 -0
  56. data/lib/cortex_reaver/public/images/border_bottom.png +0 -0
  57. data/lib/cortex_reaver/public/images/border_bottom_left.png +0 -0
  58. data/lib/cortex_reaver/public/images/border_bottom_right.png +0 -0
  59. data/lib/cortex_reaver/public/images/border_left.png +0 -0
  60. data/lib/cortex_reaver/public/images/border_right.png +0 -0
  61. data/lib/cortex_reaver/public/images/border_top.png +0 -0
  62. data/lib/cortex_reaver/public/images/border_top_left.png +0 -0
  63. data/lib/cortex_reaver/public/images/border_top_right.png +0 -0
  64. data/lib/cortex_reaver/public/images/comment.gif +0 -0
  65. data/lib/cortex_reaver/public/images/dark_trans.png +0 -0
  66. data/lib/cortex_reaver/public/images/delete.gif +0 -0
  67. data/lib/cortex_reaver/public/images/edit.gif +0 -0
  68. data/lib/cortex_reaver/public/images/header.png +0 -0
  69. data/lib/cortex_reaver/public/images/header.xcf +0 -0
  70. data/lib/cortex_reaver/public/images/header_background.png +0 -0
  71. data/lib/cortex_reaver/public/images/parent.gif +0 -0
  72. data/lib/cortex_reaver/public/images/rss-xml-icon.png +0 -0
  73. data/lib/cortex_reaver/public/images/sections.png +0 -0
  74. data/lib/cortex_reaver/public/images/sections_highlight.png +0 -0
  75. data/lib/cortex_reaver/public/js/admin.js +36 -0
  76. data/lib/cortex_reaver/public/js/cookie.js +27 -0
  77. data/lib/cortex_reaver/public/js/jquery.js +32 -0
  78. data/lib/cortex_reaver/public/js/photo.js +33 -0
  79. data/lib/cortex_reaver/snippets/array.rb +7 -0
  80. data/lib/cortex_reaver/snippets/ramaze/dispatcher/file.rb +37 -0
  81. data/lib/cortex_reaver/support/attachments.rb +235 -0
  82. data/lib/cortex_reaver/support/cached_rendering.rb +79 -0
  83. data/lib/cortex_reaver/support/canonical.rb +107 -0
  84. data/lib/cortex_reaver/support/comments.rb +69 -0
  85. data/lib/cortex_reaver/support/pagination.rb +38 -0
  86. data/lib/cortex_reaver/support/renderer.rb +196 -0
  87. data/lib/cortex_reaver/support/sequenceable.rb +248 -0
  88. data/lib/cortex_reaver/support/tags.rb +108 -0
  89. data/lib/cortex_reaver/support/timestamps.rb +33 -0
  90. data/lib/cortex_reaver/version.rb +8 -0
  91. data/lib/cortex_reaver/view/adminbox.rhtml +56 -0
  92. data/lib/cortex_reaver/view/blank_layout.rhtml +46 -0
  93. data/lib/cortex_reaver/view/comments/comment.rhtml +34 -0
  94. data/lib/cortex_reaver/view/comments/form.rhtml +25 -0
  95. data/lib/cortex_reaver/view/comments/list.rhtml +5 -0
  96. data/lib/cortex_reaver/view/comments/post_form.rhtml +36 -0
  97. data/lib/cortex_reaver/view/config/form.rhtml +10 -0
  98. data/lib/cortex_reaver/view/error.rhtml +72 -0
  99. data/lib/cortex_reaver/view/journals/form.rhtml +12 -0
  100. data/lib/cortex_reaver/view/journals/journal.rhtml +39 -0
  101. data/lib/cortex_reaver/view/journals/list.rhtml +33 -0
  102. data/lib/cortex_reaver/view/journals/short.rhtml +3 -0
  103. data/lib/cortex_reaver/view/journals/show.rhtml +5 -0
  104. data/lib/cortex_reaver/view/pages/form.rhtml +12 -0
  105. data/lib/cortex_reaver/view/pages/list.rhtml +26 -0
  106. data/lib/cortex_reaver/view/pages/show.rhtml +12 -0
  107. data/lib/cortex_reaver/view/photographs/atom_fragment.rhtml +82 -0
  108. data/lib/cortex_reaver/view/photographs/form.rhtml +19 -0
  109. data/lib/cortex_reaver/view/photographs/grid.rhtml +36 -0
  110. data/lib/cortex_reaver/view/photographs/list.rhtml +9 -0
  111. data/lib/cortex_reaver/view/photographs/short.rhtml +3 -0
  112. data/lib/cortex_reaver/view/photographs/show.rhtml +114 -0
  113. data/lib/cortex_reaver/view/photographs/sidebar.rhtml +7 -0
  114. data/lib/cortex_reaver/view/projects/form.rhtml +13 -0
  115. data/lib/cortex_reaver/view/projects/list.rhtml +27 -0
  116. data/lib/cortex_reaver/view/projects/show.rhtml +38 -0
  117. data/lib/cortex_reaver/view/tags/form.rhtml +9 -0
  118. data/lib/cortex_reaver/view/tags/list.rhtml +28 -0
  119. data/lib/cortex_reaver/view/tags/show.rhtml +51 -0
  120. data/lib/cortex_reaver/view/text_layout.rhtml +78 -0
  121. data/lib/cortex_reaver/view/users/form.rhtml +16 -0
  122. data/lib/cortex_reaver/view/users/list.rhtml +38 -0
  123. data/lib/cortex_reaver/view/users/login.rhtml +13 -0
  124. data/lib/cortex_reaver/view/users/register.rhtml +13 -0
  125. data/lib/cortex_reaver/view/users/show.rhtml +37 -0
  126. data/lib/cortex_reaver/view/users/user.rhtml +35 -0
  127. data/lib/proto/cortex_reaver.yaml +28 -0
  128. 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,8 @@
1
+ module CortexReaver
2
+ APP_NAME = 'Cortex Reaver'
3
+ APP_VERSION = '0.0.1'
4
+ APP_AUTHOR = 'aphyr'
5
+ APP_EMAIL = 'aphyr@aphyr.com'
6
+ APP_URL = 'http://aphyr.com'
7
+ APP_COPYRIGHT = 'Copyright (c) 2008 aphyr <aphyr@aphyr.com>. All rights reserved.'
8
+ 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>