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,10 @@
1
+ if (window.Olelo) {
2
+ var mime = Olelo.page_mime;
3
+ if (mime == 'application/x-empty' || mime == 'inode/directory') {
4
+ mime = Olelo.default_mime;
5
+ }
6
+ var match = /text\/x-(\w+)/.exec(mime);
7
+ if (match) {
8
+ $('#edit-content').markupEditor(match[1]);
9
+ }
10
+ }
@@ -0,0 +1,52 @@
1
+ description 'Enhance editor form with preview and diff'
2
+ dependencies 'aspects'
3
+
4
+ class ::Olelo::Application
5
+ before :edit_buttons, 1000 do
6
+ %{<button data-target="enhanced-edit" type="submit" name="action" value="preview" accesskey="p">#{:preview.t}</button>
7
+ <button data-target="enhanced-edit" type="submit" name="action" value="changes" accesskey="c">#{:changes.t}</button>}
8
+ end
9
+
10
+ after :edit_buttons do
11
+ %{<div id="enhanced-edit">#{flash[:preview] || flash[:changes]}</div>}
12
+ end
13
+
14
+ def post_preview
15
+ raise 'No content' if !params[:content]
16
+ params[:content].gsub!("\r\n", "\n")
17
+
18
+ if page.new? || !params[:pos]
19
+ # Whole page edited, assign new content before aspect search
20
+ page.content = params[:content]
21
+ aspect = Aspects::Aspect.find(page, :layout => true)
22
+ else
23
+ # We assume that aspect stays the same if section is edited
24
+ aspect = Aspects::Aspect.find(page, :layout => true)
25
+ page.content = params[:content]
26
+ end
27
+ context = Aspects::Context.new(:page => page, :request => request, :private => {:preview => true})
28
+ preview = aspect && aspect.call(context, page)
29
+ flash.now[:preview] = preview ? %{<hr/>#{preview}} : nil
30
+ halt render(request.xhr? ? flash.now[:preview] : :edit)
31
+ end
32
+
33
+ def post_changes
34
+ raise 'No content' if !params[:content]
35
+ params[:content].gsub!("\r\n", "\n")
36
+
37
+ original = Tempfile.new('original')
38
+ original.write(params[:pos] ? page.content[params[:pos].to_i, params[:len].to_i] : page.content)
39
+ original.close
40
+
41
+ new = Tempfile.new('new')
42
+ new.write(params[:content].to_s)
43
+ new.close
44
+
45
+ # Read in binary mode and fix encoding afterwards
46
+ patch = IO.popen("diff -u '#{original.path}' '#{new.path}'", 'rb') {|io| io.read }
47
+ patch.force_encoding(Encoding.default_external)
48
+ changes = PatchParser.parse(patch, PatchFormatter.new).html
49
+ flash.now[:changes] = changes.blank? ? %{<div class="flash">#{:no_changes.t}</div>} : changes
50
+ halt render(request.xhr? ? flash.now[:changes] : :edit)
51
+ end
52
+ end
@@ -0,0 +1,56 @@
1
+ description 'reCAPTCHA support to avoid spamming'
2
+ require 'net/http'
3
+
4
+ RECAPTCHA_PUBLIC = Config['recaptcha.public']
5
+ RECAPTCHA_PRIVATE = Config['recaptcha.private']
6
+
7
+ class ::Olelo::Application
8
+ before :edit_buttons do
9
+ %{<br/><label for="recaptcha">#{:captcha.t}</label><br/><div id="recaptcha"></div><br/>} if flash[:show_captcha]
10
+ end
11
+
12
+ hook :script do
13
+ %{<script type="text/javascript" src="http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"/>
14
+ <script type="text/javascript">
15
+ $(function() {
16
+ Recaptcha.create('#{RECAPTCHA_PUBLIC}',
17
+ 'recaptcha', {
18
+ theme: 'clean',
19
+ callback: Recaptcha.focus_response_field
20
+ });
21
+ });
22
+ </script>} if flash[:show_captcha]
23
+ end
24
+
25
+ redefine_method :post_edit do
26
+ if captcha_valid?
27
+ super()
28
+ else
29
+ flash.info! :enter_captcha.t
30
+ flash.now[:show_captcha] = true
31
+ halt render(:edit)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def captcha_valid?
38
+ if Time.now.to_i < session[:olelo_recaptcha_timeout].to_i
39
+ true
40
+ elsif params[:recaptcha_challenge_field] && params[:recaptcha_response_field]
41
+ response = Net::HTTP.post_form(URI.parse('http://api-verify.recaptcha.net/verify'),
42
+ 'privatekey' => RECAPTCHA_PRIVATE,
43
+ 'remoteip' => request.ip,
44
+ 'challenge' => params[:recaptcha_challenge_field],
45
+ 'response' => params[:recaptcha_response_field])
46
+ if response.body.split("\n").first == 'true'
47
+ session[:olelo_recaptcha_timeout] = Time.now.to_i + 3600
48
+ flash.info! :captcha_valid.t
49
+ true
50
+ else
51
+ flash.error! :captcha_invalid.t
52
+ false
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ description 'Creole wiki text filter'
2
+ require 'creole'
3
+
4
+ class OleloCreole < ::Creole::Parser
5
+ include PageHelper
6
+ include Util
7
+
8
+ def make_image(path, title)
9
+ args = title.to_s.split('|')
10
+ image_path = path.dup
11
+ if path !~ %r{^(\w+)://}
12
+ geometry = args.grep(/(\d+x)|(x\d+)|(\d+%)/).first
13
+ image_path += (path.include?('?') ? '&' : '?') + 'aspect=image'
14
+ if geometry
15
+ args.delete(geometry)
16
+ image_path += "&geometry=#{geometry}"
17
+ end
18
+ end
19
+ image_path = escape_html(image_path)
20
+ path = escape_html(path)
21
+ nolink = args.delete('nolink')
22
+ box = args.delete('box')
23
+ alt = escape_html(args[0] ? args[0] : path)
24
+ if nolink
25
+ %{<img src="#{image_path}" alt="#{alt}"/>}
26
+ elsif box
27
+ caption = args[0] ? %{<span class="caption">#{escape_html args[0]}</span>} : ''
28
+ %{<span class="img"><a href="#{path}"><img src="#{image_path}" alt="#{alt}"/>#{caption}</a></span>}
29
+ else
30
+ %{<a href="#{path}" class="img"><img src="#{image_path}" alt="#{alt}"/></a>}
31
+ end
32
+ end
33
+ end
34
+
35
+ Filter.create :creole do |context, content|
36
+ OleloCreole.new(content, :extensions => true, :no_escape => true).to_html
37
+ end
@@ -0,0 +1,9 @@
1
+ description 'Filter which sets Content-Disposition'
2
+
3
+ Filter.create :disposition do |context, content|
4
+ name = context.page.root? ? :root.t : context.page.name.gsub(/[^\w.\-_]/, '_')
5
+ name += '.' + options[:extension] if options[:extension]
6
+ context.header['Content-Disposition'] = %{attachment; filename="#{name}"}
7
+ context.header['Content-Length'] = content.bytesize.to_s
8
+ content
9
+ end
@@ -0,0 +1,67 @@
1
+ description 'Adds links for section editing for headlines'
2
+
3
+ Page.attributes do
4
+ boolean :no_editsection
5
+ end
6
+
7
+ class EditSection < Filters::NestingFilter
8
+ def find_sections_regexp(content, regexp)
9
+ sections, offset = [], 0
10
+ while (offset = content.index(regexp, offset))
11
+ sections.last[1] = offset if sections.last
12
+ # [Section start, section end, Position to insert edit link, section text]
13
+ sections << [offset + $2.size, content.length, offset + $1.size, $3.strip]
14
+ offset += $&.size
15
+ end
16
+ sections
17
+ end
18
+
19
+ # Creole/Mediawiki style headlines (e.g. == Headline ==)
20
+ def find_sections_creole(content)
21
+ find_sections_regexp(content, /((\A|\s*\n)=+(.*?))=*\s*$/)
22
+ end
23
+
24
+ # ATX/Markdown style headlines (e.g. ## Headline)
25
+ def find_sections_atx(content)
26
+ find_sections_regexp(content, /((\A|\s*\n)#+(.*))$/)
27
+ end
28
+
29
+ # Returns section array
30
+ # [[Section start, section end, Position to insert edit link, section text], ...]
31
+ def find_sections(mime, content)
32
+ case mime.to_s
33
+ when %r{^text/x-(creole|mediawiki)$}
34
+ find_sections_creole(content)
35
+ when %r{^text/x-markdown}
36
+ find_sections_atx(content)
37
+ else
38
+ raise "Mime type #{mime} not supported by editsection filter"
39
+ end
40
+ end
41
+
42
+ def filter(context, content)
43
+ page = context.page
44
+ if context[:preview] || !page.head? || page.attributes['no_editsection']
45
+ subfilter(context, content)
46
+ else
47
+ sections = find_sections(context.page.mime, content)
48
+ offset = 0
49
+ prefix = "EDIT#{object_id}X"
50
+ sections.each_with_index do |h,i|
51
+ link = " #{prefix}#{i} "
52
+ content.insert(h[2] + offset, link)
53
+ offset += link.size
54
+ end
55
+ content = subfilter(context, content)
56
+ content.gsub!(/#{prefix}(\d+)/) do
57
+ i = $1.to_i
58
+ len = sections[i][1] - sections[i][0]
59
+ path = build_path(page, :action => :edit, :pos => sections[i][0], :len => len, :comment => :section_edited.t(:section => sections[i][3]))
60
+ %{<a class="editsection" href="#{escape_html path}" title="#{escape_html :edit_section.t(:section => sections[i][3])}">#{escape_html :edit.t}</a>}
61
+ end
62
+ content
63
+ end
64
+ end
65
+ end
66
+
67
+ Filters::Filter.register :editsection, EditSection
@@ -0,0 +1,16 @@
1
+ description 'Add query parameter "aspect" to images'
2
+
3
+ Filter.create :fix_img_tag do |context, content|
4
+ content.gsub!(/(<img[^>+]src=")([^"]+)"/) do |match|
5
+ prefix, path = $1, $2
6
+ if path =~ %r{^w+://} || path.starts_with?(build_path('_')) ||
7
+ (path.starts_with?('/') && !path.starts_with?(build_path(''))) ||
8
+ path.include?('aspect=')
9
+ match
10
+ else
11
+ path = unescape_html(path)
12
+ prefix + escape_html(path + (path.include?('?') ? '&' : '?') + 'aspect=image') + '"'
13
+ end
14
+ end
15
+ content
16
+ end
@@ -0,0 +1,12 @@
1
+ description 'Wraps fragment into html block to make it valid'
2
+
3
+ Filter.create :html_wrapper do |context, content|
4
+ %{<?xml version="1.0" encoding="UTF-8"?>
5
+ <!DOCTYPE html>
6
+ <html xmlns="http://www.w3.org/1999/xhtml">
7
+ <head>
8
+ <title>#{escape_html context.page.title}</title>
9
+ </head>
10
+ <body><div>#{content}</div></body>
11
+ </html>}
12
+ end
@@ -0,0 +1,19 @@
1
+ description 'Handle interwiki links'
2
+
3
+ class Interwiki < Filter
4
+ def configure(options)
5
+ super
6
+ @map = options[:map]
7
+ @regexp = /href="\/?(#{@map.keys.join('|')}):([^"]+)"/
8
+ end
9
+
10
+ def filter(context, content)
11
+ content.gsub!(@regexp) do
12
+ wiki, page = $1, $2
13
+ %{href="#{escape_html @map[$1]}#{$2}"}
14
+ end
15
+ content
16
+ end
17
+ end
18
+
19
+ Filter.register :interwiki, Interwiki
@@ -0,0 +1,26 @@
1
+ description 'Classify links as absent/present/external'
2
+ dependencies 'utils/xml'
3
+
4
+ Filter.create :link_classifier do |context, content|
5
+ doc = XML::Fragment(content)
6
+ doc.css('a[href]').each do |link|
7
+ href = link['href']
8
+ classes = [link['class']].compact
9
+ if href.starts_with?('http://') || href.starts_with?('https://')
10
+ classes << 'external'
11
+ elsif !href.empty? && !href.starts_with?('#')
12
+ path, query = href.split('?')
13
+ if path.starts_with? Config['base_path']
14
+ path = path[Config['base_path'].length..-1]
15
+ elsif !path.starts_with? '/'
16
+ path = context.page.path/'..'/path
17
+ end
18
+ classes << 'internal'
19
+ if !Application.reserved_path?(path)
20
+ classes << (Page.find(path, context.page.tree_version) ? 'present' : 'absent') rescue nil
21
+ end
22
+ end
23
+ link['class'] = classes.join(' ') if !classes.empty?
24
+ end
25
+ doc.to_xhtml
26
+ end
@@ -0,0 +1,15 @@
1
+ en:
2
+ attribute_toc: 'Generate Table of Contents'
3
+ attribute_no_editsection: 'Disable Section Editing'
4
+ edit_section: 'Edit section "#{section}"'
5
+ section_edited: 'Section "#{section}" edited'
6
+ de:
7
+ attribute_toc: 'Inhaltsverzeichnis erzeugen'
8
+ attribute_no_editsection: 'Bearbeiten von Bereichen deaktivieren'
9
+ edit_section: 'Bearbeite Bereich "#{section}"'
10
+ section_edited: 'Bereich "#{section}" bearbeitet'
11
+ cs_CZ:
12
+ attribute_toc: 'Vytvořit obsah'
13
+ attribute_no_editsection: 'Zablokovat editaci sekcí'
14
+ edit_section: 'Edituj sekci "#{section}"'
15
+ section_edited: 'Sekce "#{section}" editována'
@@ -0,0 +1,202 @@
1
+ description 'Filter pipeline aspect'
2
+ dependencies 'aspects'
3
+
4
+ class MandatoryFilterNotFound < NameError; end
5
+
6
+ # Filter base class. Multiple filters can be chained
7
+ # to build a filter aspect.
8
+ # A filter can manipulate the text and the aspect context.
9
+ class Filter
10
+ include PageHelper
11
+ include Templates
12
+ extend Factory
13
+
14
+ attr_accessor :previous
15
+ attr_reader :name, :description, :plugin, :options
16
+
17
+ # Initialize filter
18
+ def initialize(name, options = {})
19
+ @name = name.to_s
20
+ @plugin = options[:plugin] || Plugin.for(self.class)
21
+ @description = options[:description] || @plugin.description
22
+ end
23
+
24
+ # Configure the filter. Takes an option hash
25
+ def configure(options)
26
+ @options = options
27
+ end
28
+
29
+ # Main entry point of a filter
30
+ # Calls previous filter, creates a duplicate from itself
31
+ # and calls filter on it.
32
+ def call(context, content)
33
+ content = previous ? previous.call(context, content) : content
34
+ dup.filter(context, content)
35
+ end
36
+
37
+ # Filter the content. Implement this method!
38
+ def filter(context, content)
39
+ raise NotImplementedError
40
+ end
41
+
42
+ # Print filter definition. For debugging purposes.
43
+ def definition
44
+ previous ? "#{previous.definition} > #{name}" : name
45
+ end
46
+
47
+ # Register a filter class
48
+ def self.register(name, klass, options = {})
49
+ super(name, klass.new(name, options))
50
+ end
51
+
52
+ # Create a filter from a given block.
53
+ def self.create(name, options = {}, &block)
54
+ options[:plugin] ||= Plugin.for(block)
55
+ klass = Class.new(self)
56
+ klass.class_eval { define_method(:filter, &block) }
57
+ register(name, klass, options)
58
+ end
59
+
60
+ # Create regexp filter
61
+ def self.regexp(name, *regexps)
62
+ create(name, :description => 'Regular expression filter') do |context, content|
63
+ regexps.each_slice(2) { |regexp, sub| content.gsub!(regexp, sub) }
64
+ content
65
+ end
66
+ end
67
+
68
+ # Find filter by name
69
+ def self.find(name, options = {})
70
+ filter = self[name].dup
71
+ filter.configure(options.with_indifferent_access)
72
+ filter
73
+ end
74
+ end
75
+
76
+ # Filter which supports subfilters
77
+ class NestingFilter < Filter
78
+ attr_accessor :sub
79
+
80
+ def subfilter(context, content)
81
+ sub ? sub.call(context, content) : content
82
+ end
83
+
84
+ def definition
85
+ sub ? "#{super} (#{sub.definition})" : super
86
+ end
87
+ end
88
+
89
+ class FilterAspect < Aspects::Aspect
90
+ def initialize(name, filter, options)
91
+ super(name, options)
92
+ @filter = filter
93
+ end
94
+
95
+ def call(context, page)
96
+ @filter.call(context, page.content.dup)
97
+ end
98
+
99
+ def definition
100
+ @filter.definition
101
+ end
102
+ end
103
+
104
+ # Filter DSL
105
+ class FilterDSL
106
+ # Build filter class
107
+ class FilterBuilder
108
+ def initialize(name, filter = nil)
109
+ @name, @filter = name, filter
110
+ end
111
+
112
+ # Add optional filter
113
+ def filter(name, options = {}, &block)
114
+ add(name, false, options, &block)
115
+ end
116
+
117
+ # Add mandatory filter
118
+ def filter!(name, options = {}, &block)
119
+ add(name, true, options, &block)
120
+ end
121
+
122
+ # Add filter with method name.
123
+ # Mandatory filters must end with !
124
+ def method_missing(name, options = {}, &block)
125
+ name = name.to_s
126
+ name.ends_with?('!') ? filter!(name[0..-2], options, &block) : filter(name, options, &block)
127
+ end
128
+
129
+ def build(&block)
130
+ instance_eval(&block)
131
+ @filter
132
+ end
133
+
134
+ private
135
+
136
+ def add(name, mandatory, options, &block)
137
+ filter = Filter.find(name, options) rescue nil
138
+ if filter
139
+ filter.previous = @filter
140
+ @filter = filter
141
+ if block
142
+ raise "Filter '#{name}' does not support subfilters" if !(NestingFilter === @filter)
143
+ @filter.sub = FilterBuilder.new(@name).build(&block)
144
+ end
145
+ else
146
+ if mandatory
147
+ raise MandatoryFilterNotFound, "Aspect '#{@name}' not created because mandatory filter '#{name}' is not available"
148
+ else
149
+ Olelo.logger.warn "Aspect '#{@name}' - Optional filter '#{name}' not available"
150
+ end
151
+ @filter = FilterBuilder.new(@name, @filter).build(&block) if block
152
+ end
153
+ self
154
+ end
155
+ end
156
+
157
+ # Build aspect class
158
+ class AspectBuilder
159
+ def initialize(name)
160
+ @name = name
161
+ @options = {}
162
+ end
163
+
164
+ def build(&block)
165
+ instance_eval(&block)
166
+ raise("No filters defined for aspect '#{name}'") if !@filter
167
+ FilterAspect.new(@name, @filter, @options)
168
+ end
169
+
170
+ def filter(&block)
171
+ @filter = FilterBuilder.new(@name).build(&block)
172
+ self
173
+ end
174
+
175
+ def mime(mime); @options[:mime] = mime; self; end
176
+ def accepts(accepts); @options[:accepts] = accepts; self; end
177
+ def needs_layout; @options[:layout] = true; self; end
178
+ def has_priority(prio); @options[:priority] = prio; self; end
179
+ def is_cacheable; @options[:cacheable] = true; self; end
180
+ def is_hidden; @options[:hidden] = true; self; end
181
+ end
182
+
183
+ # Register regexp filter
184
+ def regexp(name, *regexps)
185
+ Filter.regexp(name, *regexps)
186
+ end
187
+
188
+ # Register aspect
189
+ def aspect(name, &block)
190
+ Aspects::Aspect.register(AspectBuilder.new(name).build(&block))
191
+ Olelo.logger.debug "Filter aspect '#{name}' successfully created"
192
+ rescue MandatoryFilterNotFound => ex
193
+ Olelo.logger.warn ex.message
194
+ rescue Exception => ex
195
+ Olelo.logger.error ex
196
+ end
197
+ end
198
+
199
+ setup do
200
+ file = File.join(Config['config_path'], 'aspects.rb')
201
+ FilterDSL.new.instance_eval(File.read(file), file)
202
+ end