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.
- data/.gitignore +10 -0
- data/.gitmodules +3 -0
- data/Gemfile +3 -0
- data/README.markdown +104 -0
- data/Rakefile +92 -0
- data/bin/olelo +5 -0
- data/config.ru +86 -0
- data/config/aspects.rb +256 -0
- data/config/config.yml.default +154 -0
- data/config/initializers/00-mime_types.rb +29 -0
- data/config/initializers/01-slim.rb +2 -0
- data/config/interwiki.yml +11 -0
- data/doc/AUTHORS +7 -0
- data/doc/LICENSE +22 -0
- data/lib/olelo.rb +39 -0
- data/lib/olelo/application.rb +294 -0
- data/lib/olelo/attributes.rb +285 -0
- data/lib/olelo/config.rb +88 -0
- data/lib/olelo/extensions.rb +252 -0
- data/lib/olelo/helper.rb +290 -0
- data/lib/olelo/hooks.rb +142 -0
- data/lib/olelo/html_safe.rb +29 -0
- data/lib/olelo/initializer.rb +76 -0
- data/lib/olelo/locale.rb +63 -0
- data/lib/olelo/locale.yml +284 -0
- data/lib/olelo/menu.rb +101 -0
- data/lib/olelo/middleware/blacklist.rb +25 -0
- data/lib/olelo/middleware/degrade_mime_type.rb +19 -0
- data/lib/olelo/middleware/flash.rb +97 -0
- data/lib/olelo/middleware/force_encoding.rb +41 -0
- data/lib/olelo/page.rb +266 -0
- data/lib/olelo/patch.rb +311 -0
- data/lib/olelo/plugin.rb +188 -0
- data/lib/olelo/repository.rb +225 -0
- data/lib/olelo/routing.rb +223 -0
- data/lib/olelo/templates.rb +30 -0
- data/lib/olelo/user.rb +132 -0
- data/lib/olelo/util.rb +233 -0
- data/lib/olelo/version.rb +3 -0
- data/lib/olelo/virtualfs.rb +161 -0
- data/lib/rack/olelo_patches.rb +33 -0
- data/lib/rack/relative_redirect.rb +44 -0
- data/lib/rack/static_cache.rb +93 -0
- data/lib/yard/addons.rb +1 -0
- data/lib/yard/addons/hook_handler.rb +25 -0
- data/lib/yard/addons/override_tag.rb +14 -0
- data/lib/yard/addons/route_handler.rb +33 -0
- data/lib/yard/addons/sanitize_anchor.rb +16 -0
- data/olelo.gemspec +31 -0
- data/plugins/aspects/changelog.rb +45 -0
- data/plugins/aspects/documentbrowser.rb +57 -0
- data/plugins/aspects/download.rb +11 -0
- data/plugins/aspects/highlight.rb +8 -0
- data/plugins/aspects/image.rb +41 -0
- data/plugins/aspects/imageinfo.rb +64 -0
- data/plugins/aspects/locale.yml +60 -0
- data/plugins/aspects/main.rb +199 -0
- data/plugins/aspects/pageinfo.rb +37 -0
- data/plugins/aspects/source.rb +6 -0
- data/plugins/aspects/subpages.rb +44 -0
- data/plugins/aspects/text.rb +9 -0
- data/plugins/blog/blog.css +1 -0
- data/plugins/blog/blog.scss +37 -0
- data/plugins/blog/locale.yml +12 -0
- data/plugins/blog/main.rb +85 -0
- data/plugins/editor/locale.yml +18 -0
- data/plugins/editor/markup/main.rb +3 -0
- data/plugins/editor/markup/script.js +10 -0
- data/plugins/editor/markup/script/00-jquery.textselection.js +267 -0
- data/plugins/editor/markup/script/01-olelo.markupeditor.js +116 -0
- data/plugins/editor/markup/script/init.js +10 -0
- data/plugins/editor/preview.rb +52 -0
- data/plugins/editor/recaptcha.rb +56 -0
- data/plugins/filters/creole.rb +37 -0
- data/plugins/filters/disposition.rb +9 -0
- data/plugins/filters/editsection.rb +67 -0
- data/plugins/filters/fix_img_tag.rb +16 -0
- data/plugins/filters/html_wrapper.rb +12 -0
- data/plugins/filters/interwiki.rb +19 -0
- data/plugins/filters/link_classifier.rb +26 -0
- data/plugins/filters/locale.yml +15 -0
- data/plugins/filters/main.rb +202 -0
- data/plugins/filters/markdown_nowiki.rb +15 -0
- data/plugins/filters/numbering.xsl +93 -0
- data/plugins/filters/orgmode.rb +6 -0
- data/plugins/filters/rubypants.rb +6 -0
- data/plugins/filters/s5/main.rb +32 -0
- data/plugins/filters/s5/s5.xsl +118 -0
- data/plugins/filters/tilt.rb +17 -0
- data/plugins/filters/toc.rb +50 -0
- data/plugins/filters/xhtml2latex.xsl +232 -0
- data/plugins/filters/xslt.rb +22 -0
- data/plugins/gallery/gallery.css +1 -0
- data/plugins/gallery/gallery.scss +28 -0
- data/plugins/gallery/main.rb +34 -0
- data/plugins/misc/fancybox/images/blank.gif +0 -0
- data/plugins/misc/fancybox/images/fancy_close.png +0 -0
- data/plugins/misc/fancybox/images/fancy_loading.png +0 -0
- data/plugins/misc/fancybox/images/fancy_nav_left.png +0 -0
- data/plugins/misc/fancybox/images/fancy_nav_right.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_e.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_n.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_ne.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_nw.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_s.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_se.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_sw.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_w.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_left.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_main.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_over.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_right.png +0 -0
- data/plugins/misc/fancybox/images/fancybox-x.png +0 -0
- data/plugins/misc/fancybox/images/fancybox-y.png +0 -0
- data/plugins/misc/fancybox/images/fancybox.png +0 -0
- data/plugins/misc/fancybox/jquery.fancybox.css +1 -0
- data/plugins/misc/fancybox/jquery.fancybox.scss +323 -0
- data/plugins/misc/fancybox/main.rb +4 -0
- data/plugins/misc/fancybox/script.js +37 -0
- data/plugins/misc/fancybox/script/00-jquery.mousewheel.js +84 -0
- data/plugins/misc/fancybox/script/01-jquery.easing.js +205 -0
- data/plugins/misc/fancybox/script/02-jquery.fancybox.js +1156 -0
- data/plugins/misc/fancybox/script/init.js +18 -0
- data/plugins/misc/system.rb +192 -0
- data/plugins/misc/variables.rb +29 -0
- data/plugins/misc/webdav.rb +45 -0
- data/plugins/repositories/git_grep.rb +69 -0
- data/plugins/repositories/gitrb_repository.rb +204 -0
- data/plugins/repositories/locale.yml +12 -0
- data/plugins/repositories/rugged_repository.rb +454 -0
- data/plugins/security/acl.rb +57 -0
- data/plugins/security/basic_auth.rb +21 -0
- data/plugins/security/locale.yml +21 -0
- data/plugins/security/persistent_login.rb +32 -0
- data/plugins/security/portal.rb +28 -0
- data/plugins/security/private_wiki.rb +24 -0
- data/plugins/security/readonly_wiki.rb +25 -0
- data/plugins/security/stack.rb +20 -0
- data/plugins/security/yamlfile.rb +66 -0
- data/plugins/tags/code.rb +6 -0
- data/plugins/tags/footnotes.rb +35 -0
- data/plugins/tags/gist-embed.css +123 -0
- data/plugins/tags/gist.rb +13 -0
- data/plugins/tags/html.rb +57 -0
- data/plugins/tags/include.rb +20 -0
- data/plugins/tags/main.rb +353 -0
- data/plugins/tags/math.rb +117 -0
- data/plugins/tags/scripting.rb +64 -0
- data/plugins/tags/sort.rb +7 -0
- data/plugins/tags/tabs.rb +20 -0
- data/plugins/treeview/images/collapsed.png +0 -0
- data/plugins/treeview/images/expanded.png +0 -0
- data/plugins/treeview/images/menu.png +0 -0
- data/plugins/treeview/images/tree.png +0 -0
- data/plugins/treeview/images/wait.gif +0 -0
- data/plugins/treeview/main.rb +16 -0
- data/plugins/treeview/script.js +5 -0
- data/plugins/treeview/script/00-jquery.treeview.js +164 -0
- data/plugins/treeview/script/init.js +25 -0
- data/plugins/treeview/treeview.css +1 -0
- data/plugins/treeview/treeview.scss +113 -0
- data/plugins/utils/assets.rb +74 -0
- data/plugins/utils/cache.rb +53 -0
- data/plugins/utils/image_magick.rb +34 -0
- data/plugins/utils/pygments.css +1 -0
- data/plugins/utils/pygments.rb +50 -0
- data/plugins/utils/pygments.scss +83 -0
- data/plugins/utils/semaphore.rb +50 -0
- data/plugins/utils/shell.rb +45 -0
- data/plugins/utils/store.rb +315 -0
- data/plugins/utils/worker.rb +36 -0
- data/plugins/utils/xml.rb +29 -0
- data/static/images/favicon.png +0 -0
- data/static/script.js +267 -0
- data/static/script/00-json2.js +486 -0
- data/static/script/01-jstorage.js +217 -0
- data/static/script/02-jquery.js +9440 -0
- data/static/script/03-jquery.ui.core.js +337 -0
- data/static/script/04-jquery.ui.widget.js +502 -0
- data/static/script/05-jquery.ui.position.js +517 -0
- data/static/script/06-jquery.ui.menu.js +609 -0
- data/static/script/07-jquery.ui.autocomplete.js +601 -0
- data/static/script/08-olelo.i18n.js +37 -0
- data/static/script/09-olelo.unsaved.js +68 -0
- data/static/script/10-olelo.historytable.js +40 -0
- data/static/script/11-olelo.pagination.js +18 -0
- data/static/script/13-olelo.tabwidget.js +57 -0
- data/static/script/14-olelo.timeago.js +70 -0
- data/static/script/15-olelo.underliner.js +31 -0
- data/static/script/16-olelo.ui.combobox.js +32 -0
- data/static/script/init.js +48 -0
- data/static/themes/atlantis/constants.scss +15 -0
- data/static/themes/atlantis/iehacks.scss +38 -0
- data/static/themes/atlantis/images/actions/delete.png +0 -0
- data/static/themes/atlantis/images/actions/edit.png +0 -0
- data/static/themes/atlantis/images/actions/history.png +0 -0
- data/static/themes/atlantis/images/actions/home.png +0 -0
- data/static/themes/atlantis/images/actions/move.png +0 -0
- data/static/themes/atlantis/images/actions/new.png +0 -0
- data/static/themes/atlantis/images/actions/page.png +0 -0
- data/static/themes/atlantis/images/bg/button.png +0 -0
- data/static/themes/atlantis/images/bg/container.png +0 -0
- data/static/themes/atlantis/images/bg/content.png +0 -0
- data/static/themes/atlantis/images/bg/footer.png +0 -0
- data/static/themes/atlantis/images/bg/header.jpg +0 -0
- data/static/themes/atlantis/images/bg/header.orig.jpg +0 -0
- data/static/themes/atlantis/images/bg/header_gray.jpg +0 -0
- data/static/themes/atlantis/images/bug.png +0 -0
- data/static/themes/atlantis/images/filetypes/7z.png +0 -0
- data/static/themes/atlantis/images/filetypes/_archive.png +0 -0
- data/static/themes/atlantis/images/filetypes/_audio.png +0 -0
- data/static/themes/atlantis/images/filetypes/_code.png +0 -0
- data/static/themes/atlantis/images/filetypes/_linux.png +0 -0
- data/static/themes/atlantis/images/filetypes/_picture.png +0 -0
- data/static/themes/atlantis/images/filetypes/_video.png +0 -0
- data/static/themes/atlantis/images/filetypes/bz2.png +0 -0
- data/static/themes/atlantis/images/filetypes/doc.png +0 -0
- data/static/themes/atlantis/images/filetypes/flac.png +0 -0
- data/static/themes/atlantis/images/filetypes/gz.png +0 -0
- data/static/themes/atlantis/images/filetypes/html.png +0 -0
- data/static/themes/atlantis/images/filetypes/java.png +0 -0
- data/static/themes/atlantis/images/filetypes/jpg.png +0 -0
- data/static/themes/atlantis/images/filetypes/midi.png +0 -0
- data/static/themes/atlantis/images/filetypes/mp3.png +0 -0
- data/static/themes/atlantis/images/filetypes/ogg.png +0 -0
- data/static/themes/atlantis/images/filetypes/pdf.png +0 -0
- data/static/themes/atlantis/images/filetypes/php.png +0 -0
- data/static/themes/atlantis/images/filetypes/png.png +0 -0
- data/static/themes/atlantis/images/filetypes/ppt.png +0 -0
- data/static/themes/atlantis/images/filetypes/psd.png +0 -0
- data/static/themes/atlantis/images/filetypes/rar.png +0 -0
- data/static/themes/atlantis/images/filetypes/rb.png +0 -0
- data/static/themes/atlantis/images/filetypes/sh.png +0 -0
- data/static/themes/atlantis/images/filetypes/tar.png +0 -0
- data/static/themes/atlantis/images/filetypes/txt.png +0 -0
- data/static/themes/atlantis/images/filetypes/wma.png +0 -0
- data/static/themes/atlantis/images/filetypes/xls.png +0 -0
- data/static/themes/atlantis/images/filetypes/zip.png +0 -0
- data/static/themes/atlantis/images/folder.png +0 -0
- data/static/themes/atlantis/images/folder_open.png +0 -0
- data/static/themes/atlantis/images/loading.gif +0 -0
- data/static/themes/atlantis/images/loading.xcf +0 -0
- data/static/themes/atlantis/images/not_found.png +0 -0
- data/static/themes/atlantis/images/page.png +0 -0
- data/static/themes/atlantis/images/search.png +0 -0
- data/static/themes/atlantis/layout.scss +115 -0
- data/static/themes/atlantis/menu.scss +99 -0
- data/static/themes/atlantis/print.scss +129 -0
- data/static/themes/atlantis/screen.scss +495 -0
- data/static/themes/atlantis/style.css +3 -0
- data/static/themes/lib/autocomplete.scss +39 -0
- data/static/themes/lib/headlines.scss +10 -0
- data/static/themes/lib/horizontal-list.scss +31 -0
- data/static/themes/lib/patch.scss +88 -0
- data/static/themes/lib/reset.scss +114 -0
- data/static/themes/lib/rounded.scss +46 -0
- data/static/themes/lib/shadow.scss +14 -0
- data/test/config_test.rb +28 -0
- data/test/factory_test.rb +29 -0
- data/test/hash_extensions_test.rb +16 -0
- data/test/helper.rb +38 -0
- data/test/hooks_test.rb +85 -0
- data/test/object_extensions_test.rb +20 -0
- data/test/page_test.rb +168 -0
- data/test/request_test.rb +166 -0
- data/test/string_extensions_test.rb +32 -0
- data/test/templates_test.rb +39 -0
- data/test/util_test.rb +71 -0
- data/views/changes.slim +22 -0
- data/views/compare.slim +8 -0
- data/views/delete.slim +9 -0
- data/views/deleted.slim +2 -0
- data/views/edit.slim +65 -0
- data/views/error.slim +6 -0
- data/views/history.slim +20 -0
- data/views/layout.slim +38 -0
- data/views/login.slim +37 -0
- data/views/move.slim +10 -0
- data/views/not_found.slim +6 -0
- data/views/profile.slim +26 -0
- data/views/show.slim +9 -0
- 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
|