olelo 0.9.0

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 (282) hide show
  1. data/.gitignore +10 -0
  2. data/.gitmodules +3 -0
  3. data/Gemfile +3 -0
  4. data/README.markdown +104 -0
  5. data/Rakefile +92 -0
  6. data/bin/olelo +5 -0
  7. data/config.ru +86 -0
  8. data/config/aspects.rb +256 -0
  9. data/config/config.yml.default +154 -0
  10. data/config/initializers/00-mime_types.rb +29 -0
  11. data/config/initializers/01-slim.rb +2 -0
  12. data/config/interwiki.yml +11 -0
  13. data/doc/AUTHORS +7 -0
  14. data/doc/LICENSE +22 -0
  15. data/lib/olelo.rb +39 -0
  16. data/lib/olelo/application.rb +294 -0
  17. data/lib/olelo/attributes.rb +285 -0
  18. data/lib/olelo/config.rb +88 -0
  19. data/lib/olelo/extensions.rb +252 -0
  20. data/lib/olelo/helper.rb +290 -0
  21. data/lib/olelo/hooks.rb +142 -0
  22. data/lib/olelo/html_safe.rb +29 -0
  23. data/lib/olelo/initializer.rb +76 -0
  24. data/lib/olelo/locale.rb +63 -0
  25. data/lib/olelo/locale.yml +284 -0
  26. data/lib/olelo/menu.rb +101 -0
  27. data/lib/olelo/middleware/blacklist.rb +25 -0
  28. data/lib/olelo/middleware/degrade_mime_type.rb +19 -0
  29. data/lib/olelo/middleware/flash.rb +97 -0
  30. data/lib/olelo/middleware/force_encoding.rb +41 -0
  31. data/lib/olelo/page.rb +266 -0
  32. data/lib/olelo/patch.rb +311 -0
  33. data/lib/olelo/plugin.rb +188 -0
  34. data/lib/olelo/repository.rb +225 -0
  35. data/lib/olelo/routing.rb +223 -0
  36. data/lib/olelo/templates.rb +30 -0
  37. data/lib/olelo/user.rb +132 -0
  38. data/lib/olelo/util.rb +233 -0
  39. data/lib/olelo/version.rb +3 -0
  40. data/lib/olelo/virtualfs.rb +161 -0
  41. data/lib/rack/olelo_patches.rb +33 -0
  42. data/lib/rack/relative_redirect.rb +44 -0
  43. data/lib/rack/static_cache.rb +93 -0
  44. data/lib/yard/addons.rb +1 -0
  45. data/lib/yard/addons/hook_handler.rb +25 -0
  46. data/lib/yard/addons/override_tag.rb +14 -0
  47. data/lib/yard/addons/route_handler.rb +33 -0
  48. data/lib/yard/addons/sanitize_anchor.rb +16 -0
  49. data/olelo.gemspec +31 -0
  50. data/plugins/aspects/changelog.rb +45 -0
  51. data/plugins/aspects/documentbrowser.rb +57 -0
  52. data/plugins/aspects/download.rb +11 -0
  53. data/plugins/aspects/highlight.rb +8 -0
  54. data/plugins/aspects/image.rb +41 -0
  55. data/plugins/aspects/imageinfo.rb +64 -0
  56. data/plugins/aspects/locale.yml +60 -0
  57. data/plugins/aspects/main.rb +199 -0
  58. data/plugins/aspects/pageinfo.rb +37 -0
  59. data/plugins/aspects/source.rb +6 -0
  60. data/plugins/aspects/subpages.rb +44 -0
  61. data/plugins/aspects/text.rb +9 -0
  62. data/plugins/blog/blog.css +1 -0
  63. data/plugins/blog/blog.scss +37 -0
  64. data/plugins/blog/locale.yml +12 -0
  65. data/plugins/blog/main.rb +85 -0
  66. data/plugins/editor/locale.yml +18 -0
  67. data/plugins/editor/markup/main.rb +3 -0
  68. data/plugins/editor/markup/script.js +10 -0
  69. data/plugins/editor/markup/script/00-jquery.textselection.js +267 -0
  70. data/plugins/editor/markup/script/01-olelo.markupeditor.js +116 -0
  71. data/plugins/editor/markup/script/init.js +10 -0
  72. data/plugins/editor/preview.rb +52 -0
  73. data/plugins/editor/recaptcha.rb +56 -0
  74. data/plugins/filters/creole.rb +37 -0
  75. data/plugins/filters/disposition.rb +9 -0
  76. data/plugins/filters/editsection.rb +67 -0
  77. data/plugins/filters/fix_img_tag.rb +16 -0
  78. data/plugins/filters/html_wrapper.rb +12 -0
  79. data/plugins/filters/interwiki.rb +19 -0
  80. data/plugins/filters/link_classifier.rb +26 -0
  81. data/plugins/filters/locale.yml +15 -0
  82. data/plugins/filters/main.rb +202 -0
  83. data/plugins/filters/markdown_nowiki.rb +15 -0
  84. data/plugins/filters/numbering.xsl +93 -0
  85. data/plugins/filters/orgmode.rb +6 -0
  86. data/plugins/filters/rubypants.rb +6 -0
  87. data/plugins/filters/s5/main.rb +32 -0
  88. data/plugins/filters/s5/s5.xsl +118 -0
  89. data/plugins/filters/tilt.rb +17 -0
  90. data/plugins/filters/toc.rb +50 -0
  91. data/plugins/filters/xhtml2latex.xsl +232 -0
  92. data/plugins/filters/xslt.rb +22 -0
  93. data/plugins/gallery/gallery.css +1 -0
  94. data/plugins/gallery/gallery.scss +28 -0
  95. data/plugins/gallery/main.rb +34 -0
  96. data/plugins/misc/fancybox/images/blank.gif +0 -0
  97. data/plugins/misc/fancybox/images/fancy_close.png +0 -0
  98. data/plugins/misc/fancybox/images/fancy_loading.png +0 -0
  99. data/plugins/misc/fancybox/images/fancy_nav_left.png +0 -0
  100. data/plugins/misc/fancybox/images/fancy_nav_right.png +0 -0
  101. data/plugins/misc/fancybox/images/fancy_shadow_e.png +0 -0
  102. data/plugins/misc/fancybox/images/fancy_shadow_n.png +0 -0
  103. data/plugins/misc/fancybox/images/fancy_shadow_ne.png +0 -0
  104. data/plugins/misc/fancybox/images/fancy_shadow_nw.png +0 -0
  105. data/plugins/misc/fancybox/images/fancy_shadow_s.png +0 -0
  106. data/plugins/misc/fancybox/images/fancy_shadow_se.png +0 -0
  107. data/plugins/misc/fancybox/images/fancy_shadow_sw.png +0 -0
  108. data/plugins/misc/fancybox/images/fancy_shadow_w.png +0 -0
  109. data/plugins/misc/fancybox/images/fancy_title_left.png +0 -0
  110. data/plugins/misc/fancybox/images/fancy_title_main.png +0 -0
  111. data/plugins/misc/fancybox/images/fancy_title_over.png +0 -0
  112. data/plugins/misc/fancybox/images/fancy_title_right.png +0 -0
  113. data/plugins/misc/fancybox/images/fancybox-x.png +0 -0
  114. data/plugins/misc/fancybox/images/fancybox-y.png +0 -0
  115. data/plugins/misc/fancybox/images/fancybox.png +0 -0
  116. data/plugins/misc/fancybox/jquery.fancybox.css +1 -0
  117. data/plugins/misc/fancybox/jquery.fancybox.scss +323 -0
  118. data/plugins/misc/fancybox/main.rb +4 -0
  119. data/plugins/misc/fancybox/script.js +37 -0
  120. data/plugins/misc/fancybox/script/00-jquery.mousewheel.js +84 -0
  121. data/plugins/misc/fancybox/script/01-jquery.easing.js +205 -0
  122. data/plugins/misc/fancybox/script/02-jquery.fancybox.js +1156 -0
  123. data/plugins/misc/fancybox/script/init.js +18 -0
  124. data/plugins/misc/system.rb +192 -0
  125. data/plugins/misc/variables.rb +29 -0
  126. data/plugins/misc/webdav.rb +45 -0
  127. data/plugins/repositories/git_grep.rb +69 -0
  128. data/plugins/repositories/gitrb_repository.rb +204 -0
  129. data/plugins/repositories/locale.yml +12 -0
  130. data/plugins/repositories/rugged_repository.rb +454 -0
  131. data/plugins/security/acl.rb +57 -0
  132. data/plugins/security/basic_auth.rb +21 -0
  133. data/plugins/security/locale.yml +21 -0
  134. data/plugins/security/persistent_login.rb +32 -0
  135. data/plugins/security/portal.rb +28 -0
  136. data/plugins/security/private_wiki.rb +24 -0
  137. data/plugins/security/readonly_wiki.rb +25 -0
  138. data/plugins/security/stack.rb +20 -0
  139. data/plugins/security/yamlfile.rb +66 -0
  140. data/plugins/tags/code.rb +6 -0
  141. data/plugins/tags/footnotes.rb +35 -0
  142. data/plugins/tags/gist-embed.css +123 -0
  143. data/plugins/tags/gist.rb +13 -0
  144. data/plugins/tags/html.rb +57 -0
  145. data/plugins/tags/include.rb +20 -0
  146. data/plugins/tags/main.rb +353 -0
  147. data/plugins/tags/math.rb +117 -0
  148. data/plugins/tags/scripting.rb +64 -0
  149. data/plugins/tags/sort.rb +7 -0
  150. data/plugins/tags/tabs.rb +20 -0
  151. data/plugins/treeview/images/collapsed.png +0 -0
  152. data/plugins/treeview/images/expanded.png +0 -0
  153. data/plugins/treeview/images/menu.png +0 -0
  154. data/plugins/treeview/images/tree.png +0 -0
  155. data/plugins/treeview/images/wait.gif +0 -0
  156. data/plugins/treeview/main.rb +16 -0
  157. data/plugins/treeview/script.js +5 -0
  158. data/plugins/treeview/script/00-jquery.treeview.js +164 -0
  159. data/plugins/treeview/script/init.js +25 -0
  160. data/plugins/treeview/treeview.css +1 -0
  161. data/plugins/treeview/treeview.scss +113 -0
  162. data/plugins/utils/assets.rb +74 -0
  163. data/plugins/utils/cache.rb +53 -0
  164. data/plugins/utils/image_magick.rb +34 -0
  165. data/plugins/utils/pygments.css +1 -0
  166. data/plugins/utils/pygments.rb +50 -0
  167. data/plugins/utils/pygments.scss +83 -0
  168. data/plugins/utils/semaphore.rb +50 -0
  169. data/plugins/utils/shell.rb +45 -0
  170. data/plugins/utils/store.rb +315 -0
  171. data/plugins/utils/worker.rb +36 -0
  172. data/plugins/utils/xml.rb +29 -0
  173. data/static/images/favicon.png +0 -0
  174. data/static/script.js +267 -0
  175. data/static/script/00-json2.js +486 -0
  176. data/static/script/01-jstorage.js +217 -0
  177. data/static/script/02-jquery.js +9440 -0
  178. data/static/script/03-jquery.ui.core.js +337 -0
  179. data/static/script/04-jquery.ui.widget.js +502 -0
  180. data/static/script/05-jquery.ui.position.js +517 -0
  181. data/static/script/06-jquery.ui.menu.js +609 -0
  182. data/static/script/07-jquery.ui.autocomplete.js +601 -0
  183. data/static/script/08-olelo.i18n.js +37 -0
  184. data/static/script/09-olelo.unsaved.js +68 -0
  185. data/static/script/10-olelo.historytable.js +40 -0
  186. data/static/script/11-olelo.pagination.js +18 -0
  187. data/static/script/13-olelo.tabwidget.js +57 -0
  188. data/static/script/14-olelo.timeago.js +70 -0
  189. data/static/script/15-olelo.underliner.js +31 -0
  190. data/static/script/16-olelo.ui.combobox.js +32 -0
  191. data/static/script/init.js +48 -0
  192. data/static/themes/atlantis/constants.scss +15 -0
  193. data/static/themes/atlantis/iehacks.scss +38 -0
  194. data/static/themes/atlantis/images/actions/delete.png +0 -0
  195. data/static/themes/atlantis/images/actions/edit.png +0 -0
  196. data/static/themes/atlantis/images/actions/history.png +0 -0
  197. data/static/themes/atlantis/images/actions/home.png +0 -0
  198. data/static/themes/atlantis/images/actions/move.png +0 -0
  199. data/static/themes/atlantis/images/actions/new.png +0 -0
  200. data/static/themes/atlantis/images/actions/page.png +0 -0
  201. data/static/themes/atlantis/images/bg/button.png +0 -0
  202. data/static/themes/atlantis/images/bg/container.png +0 -0
  203. data/static/themes/atlantis/images/bg/content.png +0 -0
  204. data/static/themes/atlantis/images/bg/footer.png +0 -0
  205. data/static/themes/atlantis/images/bg/header.jpg +0 -0
  206. data/static/themes/atlantis/images/bg/header.orig.jpg +0 -0
  207. data/static/themes/atlantis/images/bg/header_gray.jpg +0 -0
  208. data/static/themes/atlantis/images/bug.png +0 -0
  209. data/static/themes/atlantis/images/filetypes/7z.png +0 -0
  210. data/static/themes/atlantis/images/filetypes/_archive.png +0 -0
  211. data/static/themes/atlantis/images/filetypes/_audio.png +0 -0
  212. data/static/themes/atlantis/images/filetypes/_code.png +0 -0
  213. data/static/themes/atlantis/images/filetypes/_linux.png +0 -0
  214. data/static/themes/atlantis/images/filetypes/_picture.png +0 -0
  215. data/static/themes/atlantis/images/filetypes/_video.png +0 -0
  216. data/static/themes/atlantis/images/filetypes/bz2.png +0 -0
  217. data/static/themes/atlantis/images/filetypes/doc.png +0 -0
  218. data/static/themes/atlantis/images/filetypes/flac.png +0 -0
  219. data/static/themes/atlantis/images/filetypes/gz.png +0 -0
  220. data/static/themes/atlantis/images/filetypes/html.png +0 -0
  221. data/static/themes/atlantis/images/filetypes/java.png +0 -0
  222. data/static/themes/atlantis/images/filetypes/jpg.png +0 -0
  223. data/static/themes/atlantis/images/filetypes/midi.png +0 -0
  224. data/static/themes/atlantis/images/filetypes/mp3.png +0 -0
  225. data/static/themes/atlantis/images/filetypes/ogg.png +0 -0
  226. data/static/themes/atlantis/images/filetypes/pdf.png +0 -0
  227. data/static/themes/atlantis/images/filetypes/php.png +0 -0
  228. data/static/themes/atlantis/images/filetypes/png.png +0 -0
  229. data/static/themes/atlantis/images/filetypes/ppt.png +0 -0
  230. data/static/themes/atlantis/images/filetypes/psd.png +0 -0
  231. data/static/themes/atlantis/images/filetypes/rar.png +0 -0
  232. data/static/themes/atlantis/images/filetypes/rb.png +0 -0
  233. data/static/themes/atlantis/images/filetypes/sh.png +0 -0
  234. data/static/themes/atlantis/images/filetypes/tar.png +0 -0
  235. data/static/themes/atlantis/images/filetypes/txt.png +0 -0
  236. data/static/themes/atlantis/images/filetypes/wma.png +0 -0
  237. data/static/themes/atlantis/images/filetypes/xls.png +0 -0
  238. data/static/themes/atlantis/images/filetypes/zip.png +0 -0
  239. data/static/themes/atlantis/images/folder.png +0 -0
  240. data/static/themes/atlantis/images/folder_open.png +0 -0
  241. data/static/themes/atlantis/images/loading.gif +0 -0
  242. data/static/themes/atlantis/images/loading.xcf +0 -0
  243. data/static/themes/atlantis/images/not_found.png +0 -0
  244. data/static/themes/atlantis/images/page.png +0 -0
  245. data/static/themes/atlantis/images/search.png +0 -0
  246. data/static/themes/atlantis/layout.scss +115 -0
  247. data/static/themes/atlantis/menu.scss +99 -0
  248. data/static/themes/atlantis/print.scss +129 -0
  249. data/static/themes/atlantis/screen.scss +495 -0
  250. data/static/themes/atlantis/style.css +3 -0
  251. data/static/themes/lib/autocomplete.scss +39 -0
  252. data/static/themes/lib/headlines.scss +10 -0
  253. data/static/themes/lib/horizontal-list.scss +31 -0
  254. data/static/themes/lib/patch.scss +88 -0
  255. data/static/themes/lib/reset.scss +114 -0
  256. data/static/themes/lib/rounded.scss +46 -0
  257. data/static/themes/lib/shadow.scss +14 -0
  258. data/test/config_test.rb +28 -0
  259. data/test/factory_test.rb +29 -0
  260. data/test/hash_extensions_test.rb +16 -0
  261. data/test/helper.rb +38 -0
  262. data/test/hooks_test.rb +85 -0
  263. data/test/object_extensions_test.rb +20 -0
  264. data/test/page_test.rb +168 -0
  265. data/test/request_test.rb +166 -0
  266. data/test/string_extensions_test.rb +32 -0
  267. data/test/templates_test.rb +39 -0
  268. data/test/util_test.rb +71 -0
  269. data/views/changes.slim +22 -0
  270. data/views/compare.slim +8 -0
  271. data/views/delete.slim +9 -0
  272. data/views/deleted.slim +2 -0
  273. data/views/edit.slim +65 -0
  274. data/views/error.slim +6 -0
  275. data/views/history.slim +20 -0
  276. data/views/layout.slim +38 -0
  277. data/views/login.slim +37 -0
  278. data/views/move.slim +10 -0
  279. data/views/not_found.slim +6 -0
  280. data/views/profile.slim +26 -0
  281. data/views/show.slim +9 -0
  282. metadata +488 -0
@@ -0,0 +1,290 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Olelo
3
+ module BlockHelper
4
+ def blocks
5
+ @blocks ||= Hash.with_indifferent_access('')
6
+ end
7
+
8
+ def define_block(name, content = nil)
9
+ blocks[name] = block_given? ? yield : escape_html(content)
10
+ ''
11
+ end
12
+
13
+ def include_block(name)
14
+ with_hooks(name) { blocks[name] }.join.html_safe
15
+ end
16
+
17
+ def render_block(name)
18
+ with_hooks(name) { yield }.join.html_safe
19
+ end
20
+
21
+ def include_or_define_block(name, content = nil, &block)
22
+ if block_given? || content
23
+ define_block(name, content, &block)
24
+ else
25
+ include_block(name)
26
+ end
27
+ end
28
+ end
29
+
30
+ module FlashHelper
31
+ include Util
32
+
33
+ def flash
34
+ env['olelo.flash']
35
+ end
36
+
37
+ def flash_messages(action = nil)
38
+ if !action || action?(action)
39
+ li = [:error, :warn, :info].map {|level| flash[level].to_a.map {|msg| %{<li class="#{level}">#{escape_html msg}</li>} } }.flatten
40
+ %{<ul class="flash">#{li.join}</ul>}.html_safe if !li.empty?
41
+ end
42
+ end
43
+ end
44
+
45
+ module PageHelper
46
+ include Util
47
+
48
+ def include_page(path)
49
+ page = Page.find(path) rescue nil
50
+ if page
51
+ render_page(page)
52
+ else
53
+ %{<a href="#{escape_html build_path(path, :action => :new)}">#{escape_html :create_page.t(:page => path)}</a>}
54
+ end
55
+ end
56
+
57
+ def render_page(page)
58
+ page.content
59
+ end
60
+
61
+ def pagination(path, page_count, page_nr, options = {})
62
+ return if page_count <= 1
63
+ li = []
64
+ li << if page_nr > 1
65
+ %{<a href="#{escape_html build_path(path, options.merge(:page => page_nr - 1))}">&#9666;</a>}
66
+ else
67
+ %{<span class="disabled">&#9666;</span>}
68
+ end
69
+ min = page_nr - 3
70
+ max = page_nr + 3
71
+ if min > 1
72
+ min -= max - page_count if max > page_count
73
+ else
74
+ max -= min if min < 1
75
+ end
76
+ max = max + 2 < page_count ? max : page_count
77
+ min = min > 3 ? min : 1
78
+ if min != 1
79
+ li << %{<a href="#{escape_html build_path(path, options.merge(:page => 1))}">1</a>} << %{<span class="ellipsis"/>}
80
+ end
81
+ (min..max).each do |i|
82
+ li << if i == page_nr
83
+ %{<span class="current">#{i}</span>}
84
+ else
85
+ %{<a href="#{escape_html build_path(path, options.merge(:page => i))}">#{i}</a>}
86
+ end
87
+ end
88
+ if max != page_count
89
+ li << %{<span class="ellipsis"/>} << %{<a href="#{escape_html build_path(path, options.merge(:page => page_count))}">#{page_count}</a>}
90
+ end
91
+ li << if page_nr < page_count
92
+ %{<a href="#{escape_html build_path(path, options.merge(:page => page_nr + 1))}">&#9656;</a>}
93
+ else
94
+ %{<span class="disabled">&#9656;</span>}
95
+ end
96
+ ('<ul class="pagination">' + li.map {|x| "<li>#{x}</li>"}.join + '</ul>').html_safe
97
+ end
98
+
99
+ def date(t)
100
+ %{<span class="date" data-epoch="#{t.to_i}">#{t.strftime('%d %h %Y %H:%M')}</span>}.html_safe
101
+ end
102
+
103
+ def format_diff(diff)
104
+ summary = PatchSummary.new(:links => true)
105
+ formatter = PatchFormatter.new(:links => true, :header => true)
106
+ PatchParser.parse(diff.patch, summary, formatter)
107
+ (summary.html + formatter.html).html_safe
108
+ end
109
+
110
+ def breadcrumbs(page)
111
+ path = page.try(:path) || ''
112
+ li = [%{<li class="first breadcrumb#{path.empty? ? ' last' : ''}">
113
+ <a accesskey="z" href="#{escape_html build_path(nil, :version => page)}">#{escape_html :root.t}</a></li>}]
114
+ path.split('/').inject('') do |parent,elem|
115
+ current = parent/elem
116
+ li << %{<li class="breadcrumb#{current == path ? ' last' : ''}">
117
+ <a href="#{escape_html build_path(current, :version => page)}">#{escape_html elem}</a></li>}
118
+ current
119
+ end
120
+ li.join('<li class="breadcrumb">/</li>').html_safe
121
+ end
122
+
123
+ def build_path(page, options = {})
124
+ options = options.dup
125
+ action = options.delete(:action)
126
+ version = options.delete(:version)
127
+ path = (page.try(:path) || page).to_s
128
+
129
+ if action
130
+ raise ArgumentError if version
131
+ path = action.to_s/path
132
+ else
133
+ version ||= page if Page === page
134
+ version = version.tree_version if Page === version
135
+ path = 'version'/version/path if version && (options.delete(:force_version) || !version.head?)
136
+ end
137
+
138
+ path += '?' + build_query(options) if !options.empty?
139
+ '/' + (Config['base_path'] / path)
140
+ end
141
+
142
+ def edit_content(page)
143
+ if params[:content]
144
+ params[:content]
145
+ elsif !(String === page.content) || !valid_xml_chars?(page.content)
146
+ :error_binary.t(:page => page.title, :type => "#{page.mime.comment} (#{page.mime})")
147
+ else
148
+ params[:pos] ? page.content[params[:pos].to_i, params[:len].to_i].to_s : page.content
149
+ end
150
+ end
151
+ end
152
+
153
+ module HttpHelper
154
+ include Util
155
+
156
+ # Cache control for page
157
+ def cache_control(options)
158
+ return if !Config['production']
159
+
160
+ if options[:no_cache]
161
+ response.headers.delete('ETag')
162
+ response.headers.delete('Last-Modified')
163
+ response.headers.delete('Cache-Control')
164
+ return
165
+ end
166
+
167
+ last_modified = options.delete(:last_modified)
168
+ modified_since = env['HTTP_IF_MODIFIED_SINCE']
169
+ last_modified = last_modified.try(:to_time) || last_modified
170
+ last_modified = last_modified.try(:httpdate) || last_modified
171
+
172
+ if options[:version]
173
+ options[:etag] = options[:version].cache_id
174
+ options[:last_modified] = options[:version].date
175
+ end
176
+
177
+ if User.logged_in?
178
+ # Always private mode if user is logged in
179
+ options[:private] = true
180
+
181
+ # Special etag for authenticated user
182
+ options[:etag] = "#{User.current.name}-#{options[:etag]}" if options[:etag]
183
+ end
184
+
185
+ if options[:etag]
186
+ value = '"%s"' % options.delete(:etag)
187
+ response['ETag'] = value.to_s
188
+ response['Last-Modified'] = last_modified if last_modified
189
+ if etags = env['HTTP_IF_NONE_MATCH']
190
+ etags = etags.split(/\s*,\s*/)
191
+ # Etag is matching and modification date matches (HTTP Spec §14.26)
192
+ halt :not_modified if (etags.include?(value) || etags.include?('*')) && (!last_modified || last_modified == modified_since)
193
+ end
194
+ elsif last_modified
195
+ # If-Modified-Since is only processed if no etag supplied.
196
+ # If the etag match failed the If-Modified-Since has to be ignored (HTTP Spec §14.26)
197
+ response['Last-Modified'] = last_modified
198
+ halt :not_modified if last_modified == modified_since
199
+ end
200
+
201
+ options[:public] = !options[:private]
202
+ options[:max_age] ||= 0
203
+ options[:must_revalidate] ||= true if !options.include?(:must_revalidate)
204
+
205
+ response['Cache-Control'] = options.map do |k, v|
206
+ if v == true
207
+ k.to_s.tr('_', '-')
208
+ elsif v
209
+ v = 31536000 if v.to_s == 'static'
210
+ "#{k.to_s.tr('_', '-')}=#{v}"
211
+ end
212
+ end.compact.join(', ')
213
+ end
214
+ end
215
+
216
+ module ApplicationHelper
217
+ include BlockHelper
218
+ include FlashHelper
219
+ include PageHelper
220
+ include HttpHelper
221
+ include Templates
222
+
223
+ def tabs(*actions)
224
+ tabs = actions.map do |action|
225
+ %{<li id="tabhead-#{action}"#{action?(action) ? ' class="selected"' : ''}><a href="#tab-#{action}">#{escape_html action.t}</a></li>}
226
+ end
227
+ %{<ul class="tabs">#{tabs.join}</ul>}.html_safe
228
+ end
229
+
230
+ def action?(action)
231
+ if params[:action]
232
+ params[:action].split('-').include?(action.to_s)
233
+ else
234
+ unescape(request.path_info).starts_with?("/#{action}")
235
+ end
236
+ end
237
+
238
+ def footer(content = nil, &block)
239
+ include_or_define_block(:footer, content, &block)
240
+ end
241
+
242
+ def title(content = nil, &block)
243
+ include_or_define_block(:title, content, &block)
244
+ end
245
+
246
+ def script
247
+ invoke_hook(:script).join.html_safe
248
+ end
249
+
250
+ def head
251
+ @@theme_link ||=
252
+ begin
253
+ file = File.join(Config['themes_path'], Config['theme'], 'style.css')
254
+ path = build_path "static/themes/#{Config['theme']}/style.css?#{File.mtime(file).to_i}"
255
+ %{<link rel="stylesheet" href="#{escape_html path}" type="text/css"/>}
256
+ end
257
+ @@script_link ||=
258
+ begin
259
+ path = build_path "static/script.js?#{File.mtime(File.join(Config['app_path'], 'static', 'script.js')).to_i}"
260
+ %{<script src="#{escape_html path}" type="text/javascript"/>}
261
+ end
262
+ base_path = if page && page.root?
263
+ url = request.base_url
264
+ url << '/version'/page.tree_version if !page.head?
265
+ %{<base href="#{escape_html url}/"/>}.html_safe
266
+ end
267
+ [base_path, @@theme_link, @@script_link, *invoke_hook(:head)].join.html_safe
268
+ end
269
+
270
+ def session
271
+ env['rack.session'] ||= {}
272
+ end
273
+
274
+ def menu(name)
275
+ menu = Menu.new(name)
276
+ invoke_hook :menu, menu
277
+ menu.to_html
278
+ end
279
+
280
+ alias render_partial render
281
+
282
+ def render(name, options = {})
283
+ layout = options.delete(:layout) != false && !params[:no_layout]
284
+ output = Symbol === name ? render_partial(name, options) : name
285
+ output = render_partial(:layout, options) { output } if layout
286
+ invoke_hook :render, name, output, layout
287
+ output
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,142 @@
1
+ module Olelo
2
+ module ErrorHandler
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ def handle_error(error)
8
+ type = error.class
9
+ while type
10
+ self.class.error_handler[type].to_a.each {|prio,method| method.bind(self).call(error) }
11
+ type = type.superclass
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def error_handler
17
+ @error_handler ||= {}
18
+ end
19
+
20
+ def error(error, priority = 99, &block)
21
+ handler = (error_handler[error] ||= [])
22
+ method = "ERROR #{error} #{handler.size}"
23
+ define_method(method, &block)
24
+ handler << [priority, instance_method(method)]
25
+ handler.sort_by!(&:first)
26
+ end
27
+ end
28
+ end
29
+
30
+ # Include this module to add hook support to your class.
31
+ # The class will be extended with {ClassMethods} which
32
+ # provides the methods to register hooks.
33
+ module Hooks
34
+ def self.included(base)
35
+ base.extend(ClassMethods)
36
+ end
37
+
38
+ # Execute block surrounded with hooks
39
+ #
40
+ # It calls the hooks that were registered by {ClassMethods#before} and {ClassMethods#after} and
41
+ # returns an array consisting of the before hook results,
42
+ # the block result and the after hook results.
43
+ #
44
+ # @param [Symbol] name of hook to call
45
+ # @param *args Hook arguments
46
+ # @return [Array] [*Before hook results, Block result, *After hook results]
47
+ # @api public
48
+ #
49
+ def with_hooks(name, *args)
50
+ result = []
51
+ result.push(*invoke_hook("BEFORE #{name}", *args))
52
+ result << yield
53
+ ensure
54
+ result.push(*invoke_hook("AFTER #{name}", *args))
55
+ end
56
+
57
+ # Invoke hooks registered for this class
58
+ #
59
+ # The hooks can be registered using {ClassMethods#hook}.
60
+ #
61
+ # @param [Symbol] name of hook to call
62
+ # @param *args Hook arguments
63
+ # @return [Array] [Hook results]
64
+ # @api public
65
+ #
66
+ def invoke_hook(name, *args)
67
+ hooks = self.class.hooks[name.to_sym]
68
+ raise "#{self.class} has no hook '#{name}'" if !hooks
69
+ hooks.map {|prio,method| method.bind(self).(*args) }
70
+ end
71
+
72
+ # Extends class with hook functionality
73
+ module ClassMethods
74
+ # Hash of registered hooks
75
+ # @api private
76
+ # @return [Hash] of hooks
77
+ def hooks
78
+ @hooks ||= {}
79
+ end
80
+
81
+ def has_around_hooks(*names)
82
+ names.each do |name|
83
+ has_hooks "BEFORE #{name}", "AFTER #{name}"
84
+ end
85
+ end
86
+
87
+ def has_hooks(*names)
88
+ names.map(&:to_sym).each do |name|
89
+ raise "#{self} already has hook '#{name}'" if hooks.include?(name)
90
+ hooks[name] = []
91
+ end
92
+ end
93
+
94
+ # Register hook for class
95
+ #
96
+ # The hook will be invoked by {#invoke_hook}. Hooks with lower priority are called first.
97
+ #
98
+ # @param [Symbol, String] name of hook
99
+ # @param [Integer] priority
100
+ # @yield Hook block with arguments matching the hook invocation
101
+ # @return [void]
102
+ # @api public
103
+ #
104
+ def hook(name, priority = 99, &block)
105
+ list = hooks[name.to_sym]
106
+ raise "#{self} has no hook '#{name}'" if !list
107
+ method = "HOOK #{name} #{list.size}"
108
+ define_method(method, &block)
109
+ list << [priority, instance_method(method)]
110
+ list.sort_by!(&:first)
111
+ end
112
+
113
+ # Register before hook
114
+ #
115
+ # The hook will be invoked by {#with_hooks}.
116
+ #
117
+ # @param [Symbol, String] name of hook
118
+ # @param [Integer] priority
119
+ # @yield Hook block with arguments matching the hook invocation
120
+ # @return [void]
121
+ # @api public
122
+ #
123
+ def before(name, priority = 99, &block)
124
+ hook("BEFORE #{name}", priority, &block)
125
+ end
126
+
127
+ # Register before hook
128
+ #
129
+ # The hook will be invoked by {#with_hooks}.
130
+ #
131
+ # @param [Symbol, String] name of hook
132
+ # @param [Integer] priority
133
+ # @yield Hook block with arguments matching the hook invocation
134
+ # @return [void]
135
+ # @api public
136
+ #
137
+ def after(name, priority = 99, &block)
138
+ hook("AFTER #{name}", priority, &block)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,29 @@
1
+ class Object
2
+ def html_safe?
3
+ false
4
+ end
5
+ end
6
+
7
+ class String
8
+ class HtmlString < String
9
+ def html_safe?
10
+ true
11
+ end
12
+
13
+ def html_safe
14
+ self
15
+ end
16
+
17
+ def to_s
18
+ self
19
+ end
20
+ end
21
+
22
+ def html_safe?
23
+ false
24
+ end
25
+
26
+ def html_safe
27
+ HtmlString.new(self)
28
+ end
29
+ end