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,13 @@
|
|
|
1
|
+
description 'Tag to embed github gist'
|
|
2
|
+
export_scripts 'gist-embed.css'
|
|
3
|
+
require 'open-uri'
|
|
4
|
+
|
|
5
|
+
Tag.define :gist, :requires => :id do |context, attrs|
|
|
6
|
+
if attrs['id'] =~ /^\d+$/
|
|
7
|
+
body = open("https://gist.github.com/#{attrs['id']}.json").read
|
|
8
|
+
gist = JSON.parse(body)
|
|
9
|
+
gist['div'].gsub(' ', ' ')
|
|
10
|
+
else
|
|
11
|
+
raise ArgumentError, 'Invalid gist id'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
description 'Safe html tags'
|
|
2
|
+
|
|
3
|
+
HTML_TAGS = [
|
|
4
|
+
[:a, {:optional => %w(href title)}],
|
|
5
|
+
[:img, {:autoclose => true, :optional => %w(src alt title)}],
|
|
6
|
+
[:br, {:autoclose => true}],
|
|
7
|
+
[:i],
|
|
8
|
+
[:u],
|
|
9
|
+
[:b],
|
|
10
|
+
[:pre],
|
|
11
|
+
[:kbd],
|
|
12
|
+
# provided by syntax highlighter
|
|
13
|
+
# [:code, :optional => %w(lang)]
|
|
14
|
+
[:cite],
|
|
15
|
+
[:strong],
|
|
16
|
+
[:em],
|
|
17
|
+
[:ins],
|
|
18
|
+
[:sup],
|
|
19
|
+
[:sub],
|
|
20
|
+
[:del],
|
|
21
|
+
[:table],
|
|
22
|
+
[:tr],
|
|
23
|
+
[:td, {:optional => %w(colspan rowspan)}],
|
|
24
|
+
[:th],
|
|
25
|
+
[:ol, {:optional => %w(start)}],
|
|
26
|
+
[:ul],
|
|
27
|
+
[:li],
|
|
28
|
+
[:p],
|
|
29
|
+
[:h1],
|
|
30
|
+
[:h2],
|
|
31
|
+
[:h3],
|
|
32
|
+
[:h4],
|
|
33
|
+
[:h5],
|
|
34
|
+
[:h6],
|
|
35
|
+
[:blockquote, {:optional => %w(cite)}],
|
|
36
|
+
[:div, {:optional => %w(style)}],
|
|
37
|
+
[:span, {:optional => %w(style)}],
|
|
38
|
+
[:video, {:optional => %w(autoplay controls height width loop preload src poster)}],
|
|
39
|
+
[:audio, {:optional => %w(autoplay controls loop preload src)}]
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
HTML_TAGS.each do |name, options|
|
|
43
|
+
options ||= {}
|
|
44
|
+
if options.delete(:autoclose)
|
|
45
|
+
Tag.define name, options do |context, attrs|
|
|
46
|
+
attrs = attrs.map {|(k,v)| %{#{k}="#{escape_html v}"} }.join
|
|
47
|
+
"<#{name}#{attrs.blank? ? '' : ' '+attrs}/>"
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
Tag.define name, options do |context, attrs, content|
|
|
51
|
+
attrs = attrs.map {|(k,v)| %{#{k}="#{escape_html v}"} }.join
|
|
52
|
+
content = subfilter(context.subcontext, content)
|
|
53
|
+
content.gsub!(/(\A<p[^>]*>)|(<\/p>\Z)/, '')
|
|
54
|
+
"<#{name}#{attrs.blank? ? '' : ' '+attrs}>#{content}</#{name}>"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
description 'Include tags'
|
|
2
|
+
|
|
3
|
+
Tag.define :include, :optional => '*', :requires => :page, :limit => 10, :description => 'Include page' do |context, attrs|
|
|
4
|
+
path = attrs['page']
|
|
5
|
+
path = context.page.path/'..'/path if !path.starts_with? '/'
|
|
6
|
+
if page = Page.find(path, context.page.tree_version)
|
|
7
|
+
Aspects::Aspect.find!(page, :name => attrs['aspect'], :layout => true).
|
|
8
|
+
call(context.subcontext(:params => attrs.merge(:included => true), :page => page), page)
|
|
9
|
+
else
|
|
10
|
+
%{<a href="#{escape_html build_path(path, :action => :new)}">#{escape_html :create_page.t(:page => path)}</a>}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Tag.define :includeonly, :immediate => true, :description => 'Text which is shown only if included' do |context, attrs, content|
|
|
15
|
+
nested_tags(context.subcontext, content) if context.params[:included]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Tag.define :noinclude, :immediate => true, :description => 'Text which is not included' do |context, attrs, content|
|
|
19
|
+
nested_tags(context.subcontext, content) if !context.params[:included]
|
|
20
|
+
end
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
description 'Extends wiki text with custom xml tags'
|
|
2
|
+
dependencies 'filters'
|
|
3
|
+
|
|
4
|
+
# Simple XML tag parser based on regular expressions
|
|
5
|
+
class TagSoupParser
|
|
6
|
+
include Util
|
|
7
|
+
|
|
8
|
+
NAME = /[\-\w]+(?:\:[\-\w]+)?/
|
|
9
|
+
QUOTED_VALUE = /"[^"]*"|'[^']*'/
|
|
10
|
+
UNQUOTED_VALUE = /(?:[^\s'"\/>]|\/+[^'"\/>])+/
|
|
11
|
+
QUOTED_ATTR = /(#{NAME})=(#{QUOTED_VALUE})/
|
|
12
|
+
UNQUOTED_ATTR = /(#{NAME})=(#{UNQUOTED_VALUE})/
|
|
13
|
+
BOOL_ATTR = /(#{NAME})/
|
|
14
|
+
ATTRIBUTE = /\A\s*(#{QUOTED_ATTR}|#{UNQUOTED_ATTR}|#{BOOL_ATTR})/
|
|
15
|
+
|
|
16
|
+
# enabled_tags must be a hash of tag names
|
|
17
|
+
# that will be recognized by the parser.
|
|
18
|
+
# Hash values must be true for autoclosing tags and false otherwise.
|
|
19
|
+
# Other tags are ignored.
|
|
20
|
+
def initialize(enabled_tags, content)
|
|
21
|
+
@enabled_tags, @content = enabled_tags, content
|
|
22
|
+
@output = ''
|
|
23
|
+
@parsed = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Parse the content and call the block
|
|
27
|
+
# for every recognized tag.
|
|
28
|
+
# The block gets two arguments,
|
|
29
|
+
# the attribute hash and the content of the tag.
|
|
30
|
+
# Another instance of the parser has to parse the content to support nested tags.
|
|
31
|
+
def parse(&block)
|
|
32
|
+
while @content =~ /<(#{NAME})/
|
|
33
|
+
@output << $`
|
|
34
|
+
@content = $'
|
|
35
|
+
name = $1.downcase
|
|
36
|
+
if @enabled_tags.include?(name)
|
|
37
|
+
@name = name
|
|
38
|
+
@parsed = $&
|
|
39
|
+
parse_tag(&block)
|
|
40
|
+
else
|
|
41
|
+
# unknown tag, continue parsing after it
|
|
42
|
+
@output << $&
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
@output << @content
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Parse the attribute list
|
|
51
|
+
# Allowed attribute formats
|
|
52
|
+
# name="value"
|
|
53
|
+
# name='value'
|
|
54
|
+
# name=value (no space, ' or " allowed in value)
|
|
55
|
+
# name (for boolean values)
|
|
56
|
+
def parse_attributes
|
|
57
|
+
@attrs = Hash.with_indifferent_access
|
|
58
|
+
while @content =~ ATTRIBUTE
|
|
59
|
+
@content = $'
|
|
60
|
+
@parsed << $&
|
|
61
|
+
match = $&
|
|
62
|
+
case match
|
|
63
|
+
when QUOTED_ATTR
|
|
64
|
+
@attrs[$1] = unescape_html($2[1...-1])
|
|
65
|
+
when UNQUOTED_ATTR
|
|
66
|
+
@attrs[$1] = unescape_html($2)
|
|
67
|
+
when BOOL_ATTR
|
|
68
|
+
@attrs[$1] = $1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Parse a tag after the beginning "<@name"
|
|
74
|
+
def parse_tag
|
|
75
|
+
parse_attributes
|
|
76
|
+
|
|
77
|
+
case @content
|
|
78
|
+
when /\A\s*(\/?)>/
|
|
79
|
+
@content = $'
|
|
80
|
+
@parsed << $&
|
|
81
|
+
@output << yield(@name, @attrs, !$1.blank? || @enabled_tags[@name] ? '' : get_content)
|
|
82
|
+
else
|
|
83
|
+
# Tag which begins with <name but has no >.
|
|
84
|
+
# Ignore this and continue parsing after it.
|
|
85
|
+
@output << @parsed
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Collect the inner content of the tag
|
|
90
|
+
def get_content
|
|
91
|
+
stack = [@name]
|
|
92
|
+
text = ''
|
|
93
|
+
while !stack.empty?
|
|
94
|
+
case @content
|
|
95
|
+
# Tag begins
|
|
96
|
+
when /\A<(#{NAME})/
|
|
97
|
+
@content = $'
|
|
98
|
+
text << $&
|
|
99
|
+
stack << $1
|
|
100
|
+
# Tag ends
|
|
101
|
+
when /\A<\/(#{NAME})>/
|
|
102
|
+
@content = $'
|
|
103
|
+
if i = stack.rindex($1.downcase)
|
|
104
|
+
stack = stack[0...i]
|
|
105
|
+
text << $& if !stack.empty?
|
|
106
|
+
else
|
|
107
|
+
text << $&
|
|
108
|
+
end
|
|
109
|
+
# Text till the next tag beginning
|
|
110
|
+
when /\A[^<]+/
|
|
111
|
+
text << $&
|
|
112
|
+
@content = $'
|
|
113
|
+
# Suprious <
|
|
114
|
+
when /\A</
|
|
115
|
+
text << '<'
|
|
116
|
+
@content = $'
|
|
117
|
+
# No closing tag
|
|
118
|
+
else
|
|
119
|
+
text << @content
|
|
120
|
+
@content = ''
|
|
121
|
+
break
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
text
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class Tag < Filters::NestingFilter
|
|
129
|
+
@@tags = {}
|
|
130
|
+
|
|
131
|
+
def self.tags
|
|
132
|
+
@@tags
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Define a tag which is executed by the tag filter
|
|
136
|
+
#
|
|
137
|
+
# Supported options:
|
|
138
|
+
# * :limit - Limit tag count
|
|
139
|
+
# * :optional - List of optional attributes (* to allow arbitrary)
|
|
140
|
+
# * :requires - List of required attributes
|
|
141
|
+
# * :dynamic - Dynamic tags are uncached, the content is generated
|
|
142
|
+
# on the fly everytime the page is rendered.
|
|
143
|
+
# Warning: Dynamic tags introduce a large overhead!
|
|
144
|
+
# * :immediate - Replace tag immediately with generated content.
|
|
145
|
+
# This means BEFORE the execution of the subfilter.
|
|
146
|
+
# Immediate tags can generate wiki text which is then parsed by the subfilter.
|
|
147
|
+
# The default behaviour is that tags are not immediate.
|
|
148
|
+
# The content is not parsed by the subfilter, this is useful for html generation.
|
|
149
|
+
# * :description - Tag description, by default the plugin description
|
|
150
|
+
# * :namespace - Namespace of the tag, by default the plugin name
|
|
151
|
+
#
|
|
152
|
+
# Tags are added as methods to this filter. This means every method
|
|
153
|
+
# of this class can be called from the tag block.
|
|
154
|
+
# Dynamic tags are an exception. They are executed later from the layout hook.
|
|
155
|
+
def self.define(name, options = {}, &block)
|
|
156
|
+
raise 'Dynamic tag cannot be immediate' if options[:dynamic] && options[:immediate]
|
|
157
|
+
raise 'Tag must take two or three arguments' if block.arity != 2 && block.arity != 3
|
|
158
|
+
# Find the plugin which provided this tag.
|
|
159
|
+
plugin = Plugin.for(block)
|
|
160
|
+
options.merge!(:name => name.to_s, :plugin => plugin, :autoclose => block.arity == 2,
|
|
161
|
+
:optional => Set.new([*options[:optional]].compact.flatten.map(&:to_s)),
|
|
162
|
+
:requires => Set.new([*options[:requires]].compact.flatten.map(&:to_s)))
|
|
163
|
+
options[:description] ||= plugin.description
|
|
164
|
+
options[:namespace] ||= plugin.path.split('/').last
|
|
165
|
+
tag = TagInfo.new(options)
|
|
166
|
+
@@tags[tag.full_name] = @@tags[tag.name] = tag
|
|
167
|
+
if tag.dynamic
|
|
168
|
+
klass = Class.new
|
|
169
|
+
klass.class_eval do
|
|
170
|
+
include PageHelper
|
|
171
|
+
include Templates
|
|
172
|
+
define_method(:call, &block)
|
|
173
|
+
end
|
|
174
|
+
tag.dynamic = klass
|
|
175
|
+
else
|
|
176
|
+
define_method("TAG #{tag.full_name}", &block)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Configure the tag filter
|
|
181
|
+
# Options:
|
|
182
|
+
# * :enable - Whitelist of tags to enable
|
|
183
|
+
# * :disable - Blacklist of tags to disable
|
|
184
|
+
# * :static - Execute dynamic tags only once
|
|
185
|
+
#
|
|
186
|
+
# Examples:
|
|
187
|
+
# :enable => %w(html:* include) Enables all tags in the html namespace and the include tag.
|
|
188
|
+
def configure(options)
|
|
189
|
+
super
|
|
190
|
+
list = @options[:enable] ? tag_list(*@options[:enable]) : @@tags.keys
|
|
191
|
+
list -= tag_list(*@options[:disable]) if @options[:disable]
|
|
192
|
+
@enabled_tags = list.inject({}) do |hash, name|
|
|
193
|
+
hash[name] = @@tags[name].autoclose
|
|
194
|
+
hash
|
|
195
|
+
end
|
|
196
|
+
@static = options[:static]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Parse nested tags. This method can be called from tag blocks.
|
|
200
|
+
def nested_tags(context, content)
|
|
201
|
+
context[:tag_level] ||= 0
|
|
202
|
+
context[:tag_level] += 1
|
|
203
|
+
return 'Maximum tag nesting exceeded' if context[:tag_level] > MAX_RECURSION
|
|
204
|
+
result = TagSoupParser.new(@enabled_tags, content).parse do |name, attrs, text|
|
|
205
|
+
process_tag(name, attrs, text, context)
|
|
206
|
+
end
|
|
207
|
+
context[:tag_level] -= 1
|
|
208
|
+
result
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Execute the subfilter on content. Tags are also evaluated.
|
|
212
|
+
def subfilter(context, content)
|
|
213
|
+
super(context, nested_tags(context, content))
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Main filter method
|
|
217
|
+
def filter(context, content)
|
|
218
|
+
@protected_tags = []
|
|
219
|
+
@protection_prefix = "TAG#{object_id}X"
|
|
220
|
+
@protection_suffix = 'X'
|
|
221
|
+
replace_protected_tags(subfilter(context, content))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private
|
|
225
|
+
|
|
226
|
+
def tag_list(*list)
|
|
227
|
+
@@tags.select do |name, tag|
|
|
228
|
+
list.include?(tag.name) ||
|
|
229
|
+
list.include?(tag.full_name) ||
|
|
230
|
+
list.include?("#{tag.namespace}:*")
|
|
231
|
+
end.map(&:last)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
MAX_RECURSION = 100
|
|
235
|
+
BLOCK_ELEMENTS = %w(style script address blockquote div h1 h2 h3 h4 h5 h6 ul p ol pre table hr br)
|
|
236
|
+
BLOCK_ELEMENT_REGEX = /<(#{BLOCK_ELEMENTS.join('|')})/
|
|
237
|
+
|
|
238
|
+
class TagInfo
|
|
239
|
+
attr_accessor :name, :namespace, :limit, :optional, :requires,
|
|
240
|
+
:autoclose, :immediate, :dynamic, :description, :plugin
|
|
241
|
+
|
|
242
|
+
def full_name
|
|
243
|
+
"#{namespace}:#{name}"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def initialize(options)
|
|
247
|
+
options.each_pair {|k,v| send("#{k}=", v) }
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def process_tag(name, attrs, content, context)
|
|
252
|
+
tag = @@tags[name]
|
|
253
|
+
name = tag.full_name
|
|
254
|
+
|
|
255
|
+
tag_counter = context[:tag_counter] ||= {}
|
|
256
|
+
tag_counter[name] ||= 0
|
|
257
|
+
tag_counter[name] += 1
|
|
258
|
+
|
|
259
|
+
raise 'Tag limit exceeded' if tag.limit && tag_counter[name] > tag.limit
|
|
260
|
+
|
|
261
|
+
required = tag.requires.find {|a| !attrs.include?(a) }
|
|
262
|
+
raise %{Attribute "#{required}" is required} if required
|
|
263
|
+
|
|
264
|
+
if !tag.optional.include?('*')
|
|
265
|
+
invalid = attrs.keys.find {|a| !tag.requires.include?(a) && !tag.optional.include?(a) }
|
|
266
|
+
raise %{Attribute "#{invalid}" is invalid} if invalid
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
args = [context, attrs, content]
|
|
270
|
+
args.pop if tag.autoclose
|
|
271
|
+
|
|
272
|
+
content =
|
|
273
|
+
if tag.dynamic
|
|
274
|
+
if @static
|
|
275
|
+
tag.dynamic.new.call(*args).to_s
|
|
276
|
+
else
|
|
277
|
+
args[0] = name
|
|
278
|
+
%{DYNTAG(#{encode64 Marshal.dump(args)})}
|
|
279
|
+
end
|
|
280
|
+
else
|
|
281
|
+
send("TAG #{name}", *args).to_s
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
if tag.immediate
|
|
285
|
+
content
|
|
286
|
+
else
|
|
287
|
+
@protected_tags << content
|
|
288
|
+
"#{@protection_prefix}#{@protected_tags.length-1}#{@protection_suffix}"
|
|
289
|
+
end
|
|
290
|
+
rescue Exception => ex
|
|
291
|
+
Olelo.logger.error ex.message
|
|
292
|
+
"#{name} - #{ex.message}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def replace_protected_tags(content)
|
|
296
|
+
# Protected elements can be nested into each other
|
|
297
|
+
MAX_RECURSION.times do
|
|
298
|
+
break if !content.gsub!(/#{@protection_prefix}(\d+)#{@protection_suffix}/) do
|
|
299
|
+
element = @protected_tags[$1.to_i]
|
|
300
|
+
|
|
301
|
+
# Remove unwanted <p>-tags around block-level-elements
|
|
302
|
+
prefix = $`
|
|
303
|
+
if element =~ BLOCK_ELEMENT_REGEX
|
|
304
|
+
count = prefix.scan('<p>').size - prefix.scan('</p>').size
|
|
305
|
+
count > 0 ? '</p>' + element + '<p>' : element
|
|
306
|
+
else
|
|
307
|
+
element
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
content.gsub!(%r{<p>\s*</p>}, '')
|
|
311
|
+
end
|
|
312
|
+
content
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Evaluate and replace all dynamic tags on the page
|
|
317
|
+
Application.hook :render, 2000 do |name, xml, layout|
|
|
318
|
+
no_cache = false
|
|
319
|
+
xml.gsub!(/DYNTAG\(([^\)]+)\)/) do
|
|
320
|
+
no_cache = true
|
|
321
|
+
begin
|
|
322
|
+
name, attrs, content = Marshal.load(decode64($1))
|
|
323
|
+
raise 'Invalid dynamic tag' unless Hash === attrs && Tag.tags[name] && Tag.tags[name].dynamic
|
|
324
|
+
begin
|
|
325
|
+
context = Aspects::Context.new(:page => page, :params => params, :request => request, :response => response)
|
|
326
|
+
if content
|
|
327
|
+
raise 'Invalid dynamic tag' unless String === content
|
|
328
|
+
Tag.tags[name].dynamic.new.call(context, attrs, content).to_s
|
|
329
|
+
else
|
|
330
|
+
Tag.tags[name].dynamic.new.call(context, attrs).to_s
|
|
331
|
+
end
|
|
332
|
+
rescue Exception => ex
|
|
333
|
+
Olelo.logger.debug ex
|
|
334
|
+
"#{name} - #{escape_html ex.message}"
|
|
335
|
+
end
|
|
336
|
+
rescue Exception => ex
|
|
337
|
+
Olelo.logger.error ex
|
|
338
|
+
''
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
cache_control(:no_cache => true) if no_cache
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
Filters::Filter.register :tag, Tag, :description => 'Process extension tags'
|
|
345
|
+
|
|
346
|
+
Tag.define :nowiki, :description => 'Disable tag and wikitext filtering' do |context, attrs, content|
|
|
347
|
+
escape_html(content)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
Tag.define :notags, :description => 'Disable tag processing', :immediate => true do |context, attrs, content|
|
|
351
|
+
content
|
|
352
|
+
end
|
|
353
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
description 'Math tag for LaTeX rendering'
|
|
2
|
+
dependencies 'utils/image_magick'
|
|
3
|
+
|
|
4
|
+
class MathRenderer
|
|
5
|
+
include Util
|
|
6
|
+
extend Factory
|
|
7
|
+
|
|
8
|
+
def self.instance
|
|
9
|
+
@instance ||= create(Config['math_renderer']) || create('latex')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.create(name)
|
|
13
|
+
registry[name].new
|
|
14
|
+
rescue Exception => ex
|
|
15
|
+
Olelo.logger.warn "Failed to initialize math renderer #{name}: #{ex.message}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class RitexRenderer < MathRenderer
|
|
20
|
+
def initialize
|
|
21
|
+
require 'ritex'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render(code, display)
|
|
25
|
+
Ritex::Parser.new.parse(code)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
register 'ritex', RitexRenderer
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ItexRenderer < MathRenderer
|
|
32
|
+
def initialize
|
|
33
|
+
`itex2MML --version`
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def render(code, display)
|
|
37
|
+
Shell.itex2MML(display == 'block' ? '--display' : '--inline').run(code.strip)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
register 'itex', ItexRenderer
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class BlahtexMLRenderer < MathRenderer
|
|
44
|
+
def initialize
|
|
45
|
+
`blahtex`
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def render(code, display)
|
|
49
|
+
content = Shell.blahtex('--mathml').run(code.strip)
|
|
50
|
+
content =~ %r{<mathml>(.*)</mathml>}m
|
|
51
|
+
'<mathml xmlns="http://www.w3.org/1998/Math/MathML" display="' + display + '">' + $1.to_s + '</mathml>'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
register 'blahtexml', BlahtexMLRenderer
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class BlahtexImageRenderer < MathRenderer
|
|
58
|
+
include PageHelper
|
|
59
|
+
|
|
60
|
+
def initialize
|
|
61
|
+
`blahtex`
|
|
62
|
+
FileUtils.mkpath(Config['blahtex_directory'])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def render(code, display)
|
|
66
|
+
content = Shell.blahtex('--png', '--png-directory', Config['blahtex_directory']).run(code.strip)
|
|
67
|
+
if content =~ %r{<md5>(.*)</md5>}m
|
|
68
|
+
path = build_path "_/blahtex/#{$1}.png"
|
|
69
|
+
%{<img src="#{escape_html path}" alt="#{escape_html code}" class="math #{display}"/>}
|
|
70
|
+
elsif content.include?('error') && content =~ %r{<message>(.*)</message>}
|
|
71
|
+
raise $1
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
register 'blahteximage', BlahtexImageRenderer
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class GoogleRenderer < MathRenderer
|
|
79
|
+
def render(code, display)
|
|
80
|
+
%{<img src="http://chart.apis.google.com/chart?cht=tx&chl=#{escape code}" alt="#{escape_html code}" class="math #{display}"/>}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
register 'google', GoogleRenderer
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class LaTeXRenderer < MathRenderer
|
|
87
|
+
def render(code, display)
|
|
88
|
+
display == 'inline' ? "\\(#{escape_html(code)}\\)" : "\\[#{escape_html(code)}\\]"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
register 'latex', LaTeXRenderer
|
|
92
|
+
register 'mathjax', LaTeXRenderer
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
Tag.define :math, :optional => :display do |context, attrs, code|
|
|
96
|
+
raise('Limits exceeded') if code.size > 10240
|
|
97
|
+
MathRenderer.instance.render(code, attrs['display'] == 'block' ? 'block' : 'inline')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class ::Olelo::Application
|
|
101
|
+
hook :script do
|
|
102
|
+
if page && Config['math_renderer'] == 'mathjax'
|
|
103
|
+
%{<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"/>}
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
get '/_/blahtex/:name', :name => /[\w\.]+/ do
|
|
108
|
+
begin
|
|
109
|
+
response['Content-Type'] = 'image/png'
|
|
110
|
+
file = File.join(Config['blahtex_directory'], params[:name])
|
|
111
|
+
response['Content-Length'] ||= File.stat(file).size.to_s
|
|
112
|
+
halt BlockFile.open(file, 'rb')
|
|
113
|
+
rescue => ex
|
|
114
|
+
ImageMagick.label(ex.message)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|