enki-engine 0.0.2

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 (206) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +284 -0
  6. data/README.textile +112 -0
  7. data/Rakefile +13 -0
  8. data/TODO.textile +8 -0
  9. data/app/assets/images/admin/flash_bg.gif +0 -0
  10. data/app/assets/images/admin/future_bg.png +0 -0
  11. data/app/assets/images/admin/gray_bg.gif +0 -0
  12. data/app/assets/images/admin/new_button.png +0 -0
  13. data/app/assets/images/admin/silver_bg.gif +0 -0
  14. data/app/assets/images/admin/submit_bg.gif +0 -0
  15. data/app/assets/images/admin/subnav_bg.gif +0 -0
  16. data/app/assets/images/openid_icon.png +0 -0
  17. data/app/assets/images/rails.png +0 -0
  18. data/app/assets/images/silk/arrow_undo.png +0 -0
  19. data/app/assets/images/silk/delete.png +0 -0
  20. data/app/assets/images/silk/pencil.png +0 -0
  21. data/app/assets/javascripts/admin/actions.js +18 -0
  22. data/app/assets/javascripts/admin/comments.js +3 -0
  23. data/app/assets/javascripts/admin/common.js +109 -0
  24. data/app/assets/javascripts/admin/dashboard.js +33 -0
  25. data/app/assets/javascripts/admin/edit-preview.js +40 -0
  26. data/app/assets/javascripts/admin/pages.js +1 -0
  27. data/app/assets/javascripts/admin/posts.js +1 -0
  28. data/app/assets/javascripts/admin/shortcut.js +223 -0
  29. data/app/assets/javascripts/admin.js +17 -0
  30. data/app/assets/javascripts/application.js +15 -0
  31. data/app/assets/javascripts/common.js +22 -0
  32. data/app/assets/javascripts/enki.js +13 -0
  33. data/app/assets/javascripts/live-comment-preview.js +36 -0
  34. data/app/assets/stylesheets/admin.css +304 -0
  35. data/app/assets/stylesheets/application.css.scss +486 -0
  36. data/app/assets/stylesheets/humanmsg.css +112 -0
  37. data/app/assets/stylesheets/login.css +65 -0
  38. data/app/controllers/enki/admin/base_controller.rb +15 -0
  39. data/app/controllers/enki/admin/comments_controller.rb +52 -0
  40. data/app/controllers/enki/admin/dashboard_controller.rb +12 -0
  41. data/app/controllers/enki/admin/health_controller.rb +20 -0
  42. data/app/controllers/enki/admin/pages_controller.rb +97 -0
  43. data/app/controllers/enki/admin/posts_controller.rb +104 -0
  44. data/app/controllers/enki/admin/undo_items_controller.rb +43 -0
  45. data/app/controllers/enki/application_controller.rb +21 -0
  46. data/app/controllers/enki/archives_controller.rb +7 -0
  47. data/app/controllers/enki/base_controller.rb +5 -0
  48. data/app/controllers/enki/comments_controller.rb +43 -0
  49. data/app/controllers/enki/pages_controller.rb +7 -0
  50. data/app/controllers/enki/posts_controller.rb +27 -0
  51. data/app/helpers/enki/admin/navigation_helper.rb +10 -0
  52. data/app/helpers/enki/application_helper.rb +35 -0
  53. data/app/helpers/enki/date_helper.rb +15 -0
  54. data/app/helpers/enki/form_helper.rb +7 -0
  55. data/app/helpers/enki/host_helper.rb +19 -0
  56. data/app/helpers/enki/navigation_helper.rb +23 -0
  57. data/app/helpers/enki/page_title_helper.rb +33 -0
  58. data/app/helpers/enki/posts_helper.rb +9 -0
  59. data/app/helpers/enki/tag_helper.rb +7 -0
  60. data/app/helpers/enki/url_helper.rb +25 -0
  61. data/app/models/enki/base/post.rb +153 -0
  62. data/app/models/enki/comment.rb +75 -0
  63. data/app/models/enki/comment_activity.rb +33 -0
  64. data/app/models/enki/delete_comment_undo.rb +33 -0
  65. data/app/models/enki/delete_page_undo.rb +32 -0
  66. data/app/models/enki/delete_post_undo.rb +36 -0
  67. data/app/models/enki/page.rb +42 -0
  68. data/app/models/enki/post.rb +4 -0
  69. data/app/models/enki/stats.rb +19 -0
  70. data/app/models/enki/tag.rb +19 -0
  71. data/app/models/enki/tagging.rb +7 -0
  72. data/app/models/enki/undo_item.rb +10 -0
  73. data/app/views/enki/admin/comments/_comment.html.erb +12 -0
  74. data/app/views/enki/admin/comments/index.html.erb +30 -0
  75. data/app/views/enki/admin/comments/show.html.erb +9 -0
  76. data/app/views/enki/admin/dashboard/show.html.erb +61 -0
  77. data/app/views/enki/admin/health/index.html.erb +3 -0
  78. data/app/views/enki/admin/pages/_form.html.erb +3 -0
  79. data/app/views/enki/admin/pages/_page.html.erb +12 -0
  80. data/app/views/enki/admin/pages/index.html.erb +25 -0
  81. data/app/views/enki/admin/pages/new.html.erb +8 -0
  82. data/app/views/enki/admin/pages/show.html.erb +8 -0
  83. data/app/views/enki/admin/posts/_form.html.erb +11 -0
  84. data/app/views/enki/admin/posts/_post.html.erb +12 -0
  85. data/app/views/enki/admin/posts/_taggings_form.html.erb +4 -0
  86. data/app/views/enki/admin/posts/_upload_form.html.erb +0 -0
  87. data/app/views/enki/admin/posts/index.html.erb +25 -0
  88. data/app/views/enki/admin/posts/new.html.erb +8 -0
  89. data/app/views/enki/admin/posts/show.html.erb +8 -0
  90. data/app/views/enki/admin/undo_items/index.html.erb +24 -0
  91. data/app/views/enki/archives/index.html.erb +17 -0
  92. data/app/views/enki/comments/_comment.html.erb +4 -0
  93. data/app/views/enki/pages/_page.html.erb +3 -0
  94. data/app/views/enki/pages/show.html.erb +5 -0
  95. data/app/views/enki/posts/_post.html.erb +13 -0
  96. data/app/views/enki/posts/index.atom.builder +27 -0
  97. data/app/views/enki/posts/index.html.erb +15 -0
  98. data/app/views/enki/posts/show.html.erb +37 -0
  99. data/autotest/discover.rb +2 -0
  100. data/config/cucumber.yml +8 -0
  101. data/config/enki.yml.sample +16 -0
  102. data/config/initializers/enki_ext.rb +3 -0
  103. data/config/initializers/set_chronic_timezone.rb +1 -0
  104. data/config/initializers/verification.rb +135 -0
  105. data/config/routes.rb +34 -0
  106. data/db/migrate/20110709024316_initialize_db.rb +97 -0
  107. data/db/seeds.rb +8 -0
  108. data/enki-engine.gemspec +47 -0
  109. data/features/admin_dashboard.feature +10 -0
  110. data/features/admin_health.feature +10 -0
  111. data/features/admin_undo.feature +20 -0
  112. data/features/browsing.feature +16 -0
  113. data/features/step_definitions/admin.rb +27 -0
  114. data/features/step_definitions/browsing.rb +3 -0
  115. data/features/step_definitions/posts.rb +11 -0
  116. data/features/step_definitions/web_steps.rb +1 -0
  117. data/features/support/env.rb +59 -0
  118. data/features/support/paths.rb +35 -0
  119. data/features/support/selectors.rb +39 -0
  120. data/lib/core_extensions/object.rb +9 -0
  121. data/lib/core_extensions/string.rb +22 -0
  122. data/lib/enki/config.rb +44 -0
  123. data/lib/enki/engine.rb +19 -0
  124. data/lib/enki/html5_tags.rb +8 -0
  125. data/lib/enki/pagination_shim.rb +25 -0
  126. data/lib/enki/version.rb +3 -0
  127. data/lib/enki.rb +14 -0
  128. data/lib/enki_formatter.rb +11 -0
  129. data/lib/tag_list.rb +2 -0
  130. data/lib/tags_helper.rb +13 -0
  131. data/lib/tasks/cucumber.rake +65 -0
  132. data/lib/tasks/enki.rake +29 -0
  133. data/lib/undo_failed.rb +2 -0
  134. data/script/cucumber +10 -0
  135. data/spec/controllers/admin/comments_controller_spec.rb +140 -0
  136. data/spec/controllers/admin/dashboard_controller_spec.rb +47 -0
  137. data/spec/controllers/admin/health_controller_spec.rb +49 -0
  138. data/spec/controllers/admin/pages_controller_spec.rb +136 -0
  139. data/spec/controllers/admin/posts_controller_spec.rb +183 -0
  140. data/spec/controllers/admin/undo_items_controller_spec.rb +93 -0
  141. data/spec/controllers/archives_controller_spec.rb +37 -0
  142. data/spec/controllers/comments_controller_spec.rb +126 -0
  143. data/spec/controllers/pages_controller_spec.rb +46 -0
  144. data/spec/controllers/posts_controller_spec.rb +168 -0
  145. data/spec/dummy/Gemfile +5 -0
  146. data/spec/dummy/Rakefile +7 -0
  147. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  148. data/spec/dummy/config/application.rb +34 -0
  149. data/spec/dummy/config/boot.rb +10 -0
  150. data/spec/dummy/config/database.yml +6 -0
  151. data/spec/dummy/config/enki.yml +20 -0
  152. data/spec/dummy/config/environment.rb +5 -0
  153. data/spec/dummy/config/environments/development.rb +34 -0
  154. data/spec/dummy/config/environments/test.rb +32 -0
  155. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  156. data/spec/dummy/config/initializers/session_store.rb +8 -0
  157. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  158. data/spec/dummy/config/locales/en.yml +5 -0
  159. data/spec/dummy/config/routes.rb +14 -0
  160. data/spec/dummy/config.ru +6 -0
  161. data/spec/dummy/script/rails +6 -0
  162. data/spec/factories/factories.rb +36 -0
  163. data/spec/helpers/page_title_helper_spec.rb +54 -0
  164. data/spec/helpers/url_helper_spec.rb +23 -0
  165. data/spec/lib/slugorize_spec.rb +44 -0
  166. data/spec/models/comment_activity_spec.rb +60 -0
  167. data/spec/models/comment_spec.rb +125 -0
  168. data/spec/models/delete_comment_undo_spec.rb +52 -0
  169. data/spec/models/delete_post_undo_spec.rb +18 -0
  170. data/spec/models/page_spec.rb +75 -0
  171. data/spec/models/post_spec.rb +257 -0
  172. data/spec/models/stats_spec.rb +28 -0
  173. data/spec/models/tag_spec.rb +13 -0
  174. data/spec/models/tagging_spec.rb +30 -0
  175. data/spec/rcov.opts +2 -0
  176. data/spec/routing/admin/pages_routing_spec.rb +29 -0
  177. data/spec/routing/archives_routing_spec.rb +9 -0
  178. data/spec/routing/comments_routing_spec.rb +17 -0
  179. data/spec/routing/pages_routing_spec.rb +9 -0
  180. data/spec/routing/posts_routing_spec.rb +26 -0
  181. data/spec/spec.opts +4 -0
  182. data/spec/spec_helper.rb +60 -0
  183. data/spec/support/be_valid_html5.rb +150 -0
  184. data/spec/support/be_valid_xhtml.rb +148 -0
  185. data/spec/support/routes_override_helper.rb +12 -0
  186. data/spec/views/admin/comments/index.html_spec.rb +26 -0
  187. data/spec/views/admin/comments/show.html_spec.rb +28 -0
  188. data/spec/views/admin/dashboard/show.html_spec.rb +37 -0
  189. data/spec/views/admin/pages/index.html_spec.rb +23 -0
  190. data/spec/views/admin/pages/new.html_spec.rb +16 -0
  191. data/spec/views/admin/pages/show.html_spec.rb +16 -0
  192. data/spec/views/admin/posts/index.html_spec.rb +24 -0
  193. data/spec/views/admin/posts/new.html_spec.rb +16 -0
  194. data/spec/views/admin/posts/show.html_spec.rb +16 -0
  195. data/spec/views/admin/undo_items/index.html_spec.rb +19 -0
  196. data/spec/views/archives/index.html_spec.rb +34 -0
  197. data/spec/views/pages/show.html_spec.rb +23 -0
  198. data/spec/views/posts/index.atom.builder_spec.rb +36 -0
  199. data/spec/views/posts/index.html_spec.rb +39 -0
  200. data/spec/views/posts/show.html_spec.rb +49 -0
  201. data/vendor/assets/javascripts/humanmsg.js +86 -0
  202. data/vendor/assets/javascripts/jquery.easing.1.3.js +205 -0
  203. data/vendor/assets/javascripts/jquery.form.js +869 -0
  204. data/vendor/assets/javascripts/jquery.jfeed.js +143 -0
  205. data/vendor/assets/javascripts/jquery.livequery.js +250 -0
  206. metadata +464 -0
@@ -0,0 +1,153 @@
1
+ module Enki
2
+ module Base
3
+ class Post < ActiveRecord::Base
4
+
5
+ extend PaginationShim
6
+
7
+ DEFAULT_LIMIT = 15
8
+
9
+ acts_as_taggable
10
+
11
+ has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
12
+ has_many :base_tags, :through => :taggings, :source => :tag
13
+
14
+ has_many :comments, :dependent => :destroy
15
+ has_many :approved_comments, :class_name => 'Comment'
16
+
17
+ attr_accessible :body, :edited_at, :minor_edit, :published_at, :published_at_natural, :slug, :tag_list, :title,
18
+ :body_html, :active, :cached_tag_list, :created_at, :updated_at, :approved_comments_count, :author
19
+
20
+ before_validation :generate_slug
21
+ before_validation :set_dates
22
+ before_save :apply_filter
23
+
24
+ validates_presence_of :title, :slug, :body
25
+
26
+ validate :validate_published_at_natural
27
+
28
+ #------------------------------
29
+ # Scopes
30
+ #------------------------------
31
+ def self.published_desc
32
+ order('posts.published_at DESC')
33
+ end
34
+
35
+ def self.only_published
36
+ where(['published_at < ?', Time.zone.now])
37
+ end
38
+
39
+ def self.find_recent(options = {})
40
+ tag = options.delete(:tag)
41
+ limit = options.delete(:limit) || DEFAULT_LIMIT
42
+
43
+ query = tag ? tagged_with(tag) : self
44
+ query = query.only_published.published_desc.limit(limit)
45
+
46
+ return options.empty? ? query : all(options)
47
+ end
48
+
49
+
50
+ #------------------------------
51
+ # Class Methods
52
+ #------------------------------
53
+ def self.default_limit
54
+ DEFAULT_LIMIT
55
+ end
56
+
57
+
58
+ #------------------------------
59
+ # Instance Methods
60
+ #------------------------------
61
+ def validate_published_at_natural
62
+ errors.add("published_at_natural", "Unable to parse time") unless published?
63
+ end
64
+
65
+ attr_accessor :minor_edit
66
+ def minor_edit
67
+ @minor_edit ||= "1"
68
+ end
69
+
70
+ def minor_edit?
71
+ self.minor_edit == "1"
72
+ end
73
+
74
+ def published?
75
+ published_at?
76
+ end
77
+
78
+ attr_accessor :published_at_natural
79
+ def published_at_natural
80
+ @published_at_natural ||= published_at.send_with_default(:strftime, 'now', "%Y-%m-%d %H:%M")
81
+ end
82
+
83
+ class << self
84
+ def build_for_preview(params)
85
+ post = self.new(params)
86
+ post.generate_slug
87
+ post.set_dates
88
+ post.apply_filter
89
+ TagList.from(params[:tag_list]).each do |tag|
90
+ post.tags << Tag.new(:name => tag)
91
+ end
92
+ post
93
+ end
94
+
95
+ def find_by_permalink(year, month, day, slug, options = {})
96
+ begin
97
+ day = Time.parse([year, month, day].collect(&:to_i).join("-")).midnight
98
+ post = find_all_by_slug(slug, options).detect do |post|
99
+ [:year, :month, :day].all? {|time|
100
+ post.published_at.send(time) == day.send(time)
101
+ }
102
+ end
103
+ rescue ArgumentError # Invalid time
104
+ post = nil
105
+ end
106
+ post || raise(ActiveRecord::RecordNotFound)
107
+ end
108
+
109
+ def find_all_grouped_by_month
110
+ posts = find(
111
+ :all,
112
+ :order => 'posts.published_at DESC',
113
+ :conditions => ['published_at < ?', Time.now]
114
+ )
115
+ month = Struct.new(:date, :posts)
116
+ posts.group_by(&:month).inject([]) {|a, v| a << month.new(v[0], v[1])}
117
+ end
118
+ end
119
+
120
+ def destroy_with_undo
121
+ transaction do
122
+ undo = DeletePostUndo.create_undo(self)
123
+ self.destroy
124
+ return undo
125
+ end
126
+ end
127
+
128
+ def month
129
+ published_at.beginning_of_month
130
+ end
131
+
132
+ def apply_filter
133
+ self.body_html = EnkiFormatter.format_as_xhtml(self.body)
134
+ end
135
+
136
+ def set_dates
137
+ self.edited_at = Time.now if self.edited_at.nil? || !minor_edit?
138
+ if new_published_at = Chronic.parse(self.published_at_natural)
139
+ self.published_at = new_published_at
140
+ end
141
+ end
142
+
143
+ def denormalize_comments_count!
144
+ Post.update_all(["approved_comments_count = ?", self.approved_comments.count], ["id = ?", self.id])
145
+ end
146
+
147
+ def generate_slug
148
+ self.slug = self.title.dup if self.slug.blank?
149
+ self.slug.slugorize!
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,75 @@
1
+ module Enki
2
+ class Comment < ActiveRecord::Base
3
+ DEFAULT_LIMIT = 15
4
+
5
+ extend PaginationShim
6
+
7
+ belongs_to :post
8
+
9
+ attr_accessible :author, :author_url, :author_email, :body, :post, :created_at, :body_html, :updated_at
10
+
11
+ before_save :apply_filter
12
+ after_save :denormalize
13
+ after_destroy :denormalize
14
+
15
+ validates_presence_of :author, :body, :post
16
+
17
+ def apply_filter
18
+ self.body_html = Lesstile.format_as_xhtml(self.body, :code_formatter => Lesstile::CodeRayFormatter)
19
+ end
20
+
21
+ def trusted_user?
22
+ false
23
+ end
24
+
25
+ def user_logged_in?
26
+ false
27
+ end
28
+
29
+ def approved?
30
+ true
31
+ end
32
+
33
+ def denormalize
34
+ self.post.denormalize_comments_count!
35
+ end
36
+
37
+ def destroy_with_undo
38
+ undo_item = nil
39
+ transaction do
40
+ self.destroy
41
+ undo_item = DeleteCommentUndo.create_undo(self)
42
+ end
43
+ undo_item
44
+ end
45
+
46
+ # Delegates
47
+ def post_title
48
+ post.title
49
+ end
50
+
51
+ class << self
52
+ def protected_attribute?(attribute)
53
+ [:author, :body].include?(attribute.to_sym)
54
+ end
55
+
56
+ def new_with_filter(params)
57
+ comment = Comment.new(params)
58
+ comment.created_at = Time.now
59
+ comment.apply_filter
60
+ comment
61
+ end
62
+
63
+ def build_for_preview(params)
64
+ Comment.new_with_filter(params)
65
+ end
66
+
67
+ def find_recent(options = {})
68
+ find(:all, {
69
+ :limit => DEFAULT_LIMIT,
70
+ :order => 'created_at DESC'
71
+ }.merge(options))
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,33 @@
1
+ module Enki
2
+ class CommentActivity
3
+ attr_accessor :post
4
+
5
+ def initialize(post)
6
+ self.post = post
7
+ end
8
+
9
+ def comments
10
+ @comments ||= post.approved_comments.find_recent(:limit => 5)
11
+ end
12
+
13
+ def most_recent_comment
14
+ comments.first
15
+ end
16
+
17
+ class << self
18
+ def find_recent
19
+ Post.find(:all,
20
+ :group => "comments.post_id, posts." + Post.column_names.join(", posts."),
21
+ :select => 'posts.*, max(comments.created_at), comments.post_id',
22
+ :joins => 'INNER JOIN comments ON comments.post_id = posts.id',
23
+ :order => 'max(comments.created_at) desc',
24
+ :limit => 5
25
+ ).collect {|post|
26
+ CommentActivity.new(post)
27
+ }.sort_by {|activity|
28
+ activity.most_recent_comment.created_at
29
+ }.reverse
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Enki
2
+ class DeleteCommentUndo < UndoItem
3
+ def process!
4
+ raise(UndoFailed) if Comment.find_by_id(loaded_data.delete('id').to_i)
5
+
6
+ comment = nil
7
+ transaction do
8
+ comment = Comment.create(loaded_data)
9
+ raise UndoFailed if comment.new_record?
10
+ self.destroy
11
+ end
12
+ comment
13
+ end
14
+
15
+ def loaded_data
16
+ @loaded_data ||= YAML.load(data)
17
+ end
18
+
19
+ def description
20
+ "Deleted comment by '#{loaded_data['author']}'"
21
+ end
22
+
23
+ def complete_description
24
+ "Recreated comment by '#{loaded_data['author']}'"
25
+ end
26
+
27
+ class << self
28
+ def create_undo(comment)
29
+ DeleteCommentUndo.create!(:data => comment.attributes.to_yaml)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ module Enki
2
+ class DeletePageUndo < UndoItem
3
+ def process!
4
+ raise('Page already exists') if Page.find_by_id(loaded_data.delete('id').to_i)
5
+
6
+ page = nil
7
+ transaction do
8
+ page = Page.create!(loaded_data)
9
+ self.destroy
10
+ end
11
+ page
12
+ end
13
+
14
+ def loaded_data
15
+ @loaded_data ||= YAML.load(data)
16
+ end
17
+
18
+ def description
19
+ "Deleted page '#{loaded_data['title']}'"
20
+ end
21
+
22
+ def complete_description
23
+ "Recreated page '#{loaded_data['title']}'"
24
+ end
25
+
26
+ class << self
27
+ def create_undo(page)
28
+ DeletePageUndo.create!(:data => page.attributes.to_yaml)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ module Enki
2
+ class DeletePostUndo < UndoItem
3
+ def process!
4
+ post_attributes = loaded_data[:post]
5
+ raise('Post already exists') if Post.find_by_id(post_attributes.delete('id').to_i)
6
+
7
+ post = nil
8
+ transaction do
9
+ post = Post.create!(post_attributes)
10
+ loaded_data[:comments].each do |comment|
11
+ post.comments.create!(comment.except('id', 'post_id'))
12
+ end
13
+ self.destroy
14
+ end
15
+ post
16
+ end
17
+
18
+ def loaded_data
19
+ @loaded_data ||= YAML.load(data)
20
+ end
21
+
22
+ def description
23
+ "Deleted post '#{loaded_data[:post]["title"]}'"
24
+ end
25
+
26
+ def complete_description
27
+ "Recreated post '#{loaded_data[:post]["title"]}'"
28
+ end
29
+
30
+ class << self
31
+ def create_undo(post)
32
+ DeletePostUndo.create!(:data => {:post => post.attributes, :comments => post.comments.collect(&:attributes)}.to_yaml)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ module Enki
2
+ class Page < ActiveRecord::Base
3
+
4
+ extend PaginationShim
5
+
6
+ attr_accessible :slug, :title, :body
7
+
8
+ validates_presence_of :title, :slug, :body
9
+
10
+ before_validation :generate_slug
11
+
12
+ before_save :apply_filter
13
+
14
+ class << self
15
+ def build_for_preview(params)
16
+ page = Page.new(params)
17
+ page.apply_filter
18
+ page
19
+ end
20
+ end
21
+
22
+ def apply_filter
23
+ self.body_html = EnkiFormatter.format_as_xhtml(self.body)
24
+ end
25
+
26
+ def active?
27
+ true
28
+ end
29
+
30
+ def destroy_with_undo
31
+ transaction do
32
+ self.destroy
33
+ return DeletePageUndo.create_undo(self)
34
+ end
35
+ end
36
+
37
+ def generate_slug
38
+ self.slug = self.title.dup if self.slug.blank?
39
+ self.slug.slugorize!
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ module Enki
2
+ class Post < Base::Post
3
+ end
4
+ end
@@ -0,0 +1,19 @@
1
+ module Enki
2
+ class Stats
3
+ def post_count
4
+ Post.count
5
+ end
6
+
7
+ def page_count
8
+ Page.count
9
+ end
10
+
11
+ def comment_count
12
+ Comment.count
13
+ end
14
+
15
+ def tag_count
16
+ Tag.count
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Enki
2
+ class Tag < ActsAsTaggableOn::Tag
3
+
4
+ has_many :taggings, :dependent => :destroy
5
+
6
+ # TODO: Contribute this back to acts_as_taggable_on
7
+ # Update taggables' cached_tag_list
8
+ after_destroy do |tag|
9
+ tag.taggings.each do |tagging|
10
+ taggable = tagging.taggable
11
+ if taggable.class.caching_tag_list?
12
+ taggable.tag_list = TagList.new(*taggable.tags.map(&:name))
13
+ taggable.save
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ module Enki
2
+ class Tagging < ActsAsTaggableOn::Tagging
3
+
4
+ belongs_to :tag, :counter_cache => true
5
+
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Enki
2
+ class UndoItem < ActiveRecord::Base
3
+
4
+ attr_accessible :data, :type
5
+
6
+ def process!
7
+ raise("#process must be implemented by subclasses")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ <tr id="comment-<%= comment.id %>" class="<%= cycle('', 'alt') %>">
2
+ <td class='created_at'><%= comment.created_at.strftime('%d %b, %Y') %></td>
3
+ <td class='author'><%= link_to(comment.author, enki.admin_comment_path(comment)) %></td>
4
+ <td><%= truncate(comment.body, :length => 40) %></td>
5
+ <td><%= link_to(comment.post_title, enki.admin_post_path(comment.post))%></td>
6
+ <td>
7
+ <%= link_to(image_tag('silk/pencil.png', :alt => 'edit'), enki.admin_comment_path(comment)) %>
8
+ <%= form_for(comment, :as => :comment, :url => enki.admin_comment_path(comment), :html => {:class => 'delete-item', :method => :delete}) do |form| -%>
9
+ <%= image_submit_tag("silk/delete.png", :alt => "Delete Comment") %>
10
+ <% end -%>
11
+ </td>
12
+ </tr>
@@ -0,0 +1,30 @@
1
+ <h1>Comments</h1>
2
+ <table>
3
+ <thead>
4
+ <tr>
5
+ <th>Created At</th>
6
+ <th>Commenter</th>
7
+ <th>Excerpt</th>
8
+ <th>Post</th>
9
+ <th>Actions</th>
10
+ </tr>
11
+ </thead>
12
+
13
+ <tbody>
14
+ <% if @comments.empty? -%>
15
+ <tr><td colspan="5">There are no comments at this time.</td></tr>
16
+ <% else -%>
17
+ <%= render :partial => 'enki/admin/comments/comment', :collection => @comments %>
18
+ <% end -%>
19
+ </tbody>
20
+
21
+ <tfoot>
22
+ <% if @comments.paginated_pages > 1 -%>
23
+ <tr class="pagination">
24
+ <td colspan='5'>
25
+ <%= paginated @comments %>
26
+ </td>
27
+ </tr>
28
+ <% end -%>
29
+ </tfoot>
30
+ </table>
@@ -0,0 +1,9 @@
1
+ <h1>Editing comment by <%= @comment.author %></h1>
2
+
3
+ <%= simple_form_for @comment, :url => enki.admin_comments_path(@comment) do |form| -%>
4
+ <%= form.input :author %>
5
+ <%= form.input :author_url, :required => false %>
6
+ <%= form.input :author_email, :required => false %>
7
+ <%= form.input :body, :hint => "<a href='http://lesstile.rubyforge.org'>Lesstile enabled</a>.".html_safe %>
8
+ <%= form.button :submit, "Save" %>
9
+ <% end -%>
@@ -0,0 +1,61 @@
1
+ <div id="summary">
2
+ <h1><%= link_to(Enki.config[:title], '/') %></h1>
3
+ <p class="stats"><%= pluralize(@stats.post_count, 'post') %>, <%= pluralize(@stats.page_count, 'page') %>, <%= pluralize(@stats.comment_count, 'comment') %>, <%= pluralize(@stats.tag_count, 'tag') %></p>
4
+ </div>
5
+
6
+ <div id="recent-posts" class="panel">
7
+ <h2>Latest Posts</h2>
8
+ <ul>
9
+ <% @posts.each_with_index do |post, i| -%>
10
+ <li class="<%= i == 0 ? 'first ' : '' %>item">
11
+ <h3><%= link_to(truncate(post.title, :length => 50), enki.admin_post_path(post)) %></h3>
12
+ <span class='date'><%= post.published_at.strftime("%b %e") %></span>
13
+ <% if comments? -%><span class='count'><%= link_to(post.approved_comments.size, post_path(post)) %></span><% end -%>
14
+ </li>
15
+ <% end -%>
16
+ </ul>
17
+ </div>
18
+
19
+ <div id="recent-pages" class="panel">
20
+ <h2>Latest Pages</h2>
21
+ <ul>
22
+ <% @pages.each_with_index do |page, i| -%>
23
+ <li class="<%= i == 0 ? 'first ' : '' %>item">
24
+ <h3><%= link_to(truncate(page.title, :length => 50), enki.admin_page_path(page)) %></h3>
25
+ </li>
26
+ <% end -%>
27
+ </ul>
28
+ </div>
29
+
30
+ <% if comments? -%>
31
+ <div id="recent-comments" class="panel">
32
+ <h2>Latest Comments</h2>
33
+ <ul>
34
+ <% @comment_activity.each_with_index do |activity, i| -%>
35
+ <li class="<%= i == 0 ? 'first ' : '' %>item">
36
+ <h3><%= link_to(truncate(activity.post.title, :length => 50), post_path(activity.post)) %></h3>
37
+ <span class='date'><%= activity.most_recent_comment.created_at.strftime("%b %e") %></span>
38
+ <span class='count'><%= activity.post.approved_comments.size %></span>
39
+ <ul>
40
+ <% activity.comments.each_with_index do |comment, index| -%>
41
+ <%= content_tag :li, :class => activity.comments.size == index + 1 ? 'last' : nil do -%>
42
+ <%= link_to(comment.author, enki.admin_comment_path(comment), {:id => "comment-link-#{comment.id}", :class => 'comment-link'}) -%>
43
+ <% end -%>
44
+ <% end -%>
45
+ </ul>
46
+ <% activity.comments.each do |comment| -%>
47
+ <div class='comment-body' id='comment-body-<%= comment.id %>'>
48
+ <div class='comment-actions'>
49
+ <%= link_to(image_tag('silk/pencil.png', :alt => 'edit'), enki.admin_comment_path(comment)) %>
50
+ <%= form_for(comment, :as => :comment, :url => enki.admin_comment_path(comment), :html => {:class => 'delete-item', :id => "delete-comment-#{comment.id}", :method => :delete}) do |form| -%>
51
+ <%= image_submit_tag("silk/delete.png", :alt => 'Delete Comment') %>
52
+ <% end -%>
53
+ </div>
54
+ <%= comment.body_html %>
55
+ </div>
56
+ <% end -%>
57
+ </li>
58
+ <% end -%>
59
+ </ul>
60
+ </div>
61
+ <% end -%>
@@ -0,0 +1,3 @@
1
+ <h1>Health</h1>
2
+
3
+ <%= button_to("Throw exception", enki.admin_health_path(:action => 'generate_exception')) %>
@@ -0,0 +1,3 @@
1
+ <%= form.input :title %>
2
+ <%= form.input :slug, :hint => "Leave blank for an auto-generated slug based on the title." %>
3
+ <%= form.input :body, :hint => "<a href='http://textile.thresholdstate.com/'>Textile enabled</a>. Use Ctrl+E to switch between preview and edit mode.".html_safe %>
@@ -0,0 +1,12 @@
1
+ <tr class="<%= cycle('', 'alt') %>">
2
+ <td><%= page.created_at.strftime('%d %b, %Y') %></td>
3
+ <td><%= link_to(page.title, enki.admin_page_path(page)) %></td>
4
+ <td><%= truncate(page.body, :length => 50) %></td>
5
+ <td><%= page.slug %></td>
6
+ <td>
7
+ <%= link_to(image_tag('silk/pencil.png', :alt => 'edit'), enki.admin_page_path(page)) %>
8
+ <%= form_for(page, :as => :page, :url => enki.admin_page_path(page), :html => {:class => 'delete-item', :method => :delete}) do |form| -%>
9
+ <%= image_submit_tag("silk/delete.png", :alt => 'Delete Comment') %>
10
+ <% end -%>
11
+ </td>
12
+ </tr>
@@ -0,0 +1,25 @@
1
+ <h1>Your pages</h1>
2
+
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th>Created At</th>
7
+ <th>Title</th>
8
+ <th>Excerpt</th>
9
+ <th>Link</th>
10
+ <th>Actions</th>
11
+ </tr>
12
+ </thead>
13
+
14
+ <tbody>
15
+ <% if @pages.empty? -%>
16
+ <tr><td colspan="5">There are no pages at this time.</td></tr>
17
+ <% else -%>
18
+ <%= render :partial => 'enki/admin/pages/page', :collection => @pages %>
19
+ <% end -%>
20
+ </tbody>
21
+ </table>
22
+
23
+ <div class="pagination">
24
+ <%= paginated @pages %>
25
+ </div>
@@ -0,0 +1,8 @@
1
+ <h1>New page</h1>
2
+
3
+ <%= simple_form_for(@page, :url => enki.admin_pages_path) do |form| -%>
4
+ <%= render :partial => 'enki/admin/pages/form', :locals => {:form => form} %>
5
+ <div class="actions">
6
+ <button type="submit" class="button">Save</button>
7
+ </div>
8
+ <% end -%>
@@ -0,0 +1,8 @@
1
+ <h1>Editing page - <%= link_to(@page.title, enki.page_path(@page.slug)) %></h1>
2
+
3
+ <%= simple_form_for(@page, :url => enki.admin_page_path(@page)) do |form| -%>
4
+ <%= render :partial => 'enki/admin/pages/form', :locals => {:form => form} %>
5
+ <div class="actions">
6
+ <button type="submit" class="button">Save</button>
7
+ </div>
8
+ <% end -%>
@@ -0,0 +1,11 @@
1
+ <fieldset>
2
+ <%= form.input :title -%>
3
+ <%= form.input :slug, :hint => "Leave blank for an auto-generated slug based on the title." -%>
4
+ <%= form.input :body, :hint => "<a href='http://textile.thresholdstate.com/'>Textile enabled</a>. Use Ctrl+E to switch between preview and edit mode.".html_safe -%>
5
+ </fieldset>
6
+ <%= render :partial => 'enki/admin/posts/upload_form', :locals => { :form => form } -%>
7
+ <%= render :partial => 'enki/admin/posts/taggings_form', :locals => { :form => form } if tags? -%>
8
+ <fieldset>
9
+ <%= form.input :published_at_natural, :label => 'Published at', :as => 'string', :hint => 'Example: now, yesterday, 1 hour from now, '.html_safe + link_to("more&hellip;".html_safe, "http://chronic.rubyforge.org/") -%>
10
+ <%= form.input :minor_edit, :as => 'boolean', :hint => 'Minor edits will not show up as refreshed in feed readers. Use this to fix spelling mistakes and the like.' unless @post.new_record? -%>
11
+ </fieldset>