- Unified block/inline rendering.

- Split views to separate files.
- Added PNG fallback for SVG displaying.
- Improved SVG compliance.
This commit is contained in:
Alexander Tsvyashchenko 2010-03-27 00:33:50 +02:00
parent 8cfa874772
commit 5a6f81888f
15 changed files with 151 additions and 158 deletions

View File

@ -2,8 +2,13 @@ Redmine Wiki External Filter plugin that allows to filter macro blocks
arguments using external program and display results on wiki. arguments using external program and display results on wiki.
Copyright (C) 2010 Alexander Tsvyashchenko, http://www.ndl.kiev.ua Copyright (C) 2010 Alexander Tsvyashchenko, http://www.ndl.kiev.ua
Based on wiki_latex_plugin by Nils Israel <info@nils-israel.net>
Based on wiki_graphviz_plugin by tckz <at.tckz@gmail.com> * Based on wiki_latex_plugin by Nils Israel <info@nils-israel.net>
* Based on wiki_graphviz_plugin by tckz <at.tckz@gmail.com>
* Includes FlowPlayer flash player, see http://www.flowplayer.org
* Includes CSS Browser Selector, see http://rafael.adm.br/css_browser_selector/
* Thanks to Eike for his "Cross-Browser SVG Issues" article,
http://e.metaclarity.org/52/cross-browser-svg-issues/
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License

View File

@ -13,7 +13,7 @@ class WikiExternalFilterController < ApplicationController
content = read_fragment cache_key content = read_fragment cache_key
if (content) if (content)
send_data content[index], :type => config[macro]['content_type'], :disposition => 'inline', :filename => filename send_data content[index], :type => config[macro]['outputs'][index]['content_type'], :disposition => 'inline', :filename => filename
else else
render_404 render_404
end end

View File

@ -47,7 +47,6 @@ module WikiExternalFilterHelper
if content if content
result[:source] = text result[:source] = text
result[:content] = content result[:content] = content
result[:content_type] = info['content_type']
RAILS_DEFAULT_LOGGER.debug "from cache: #{name}" RAILS_DEFAULT_LOGGER.debug "from cache: #{name}"
else else
result = self.build_forced(text, attachments, info) result = self.build_forced(text, attachments, info)
@ -63,6 +62,8 @@ module WikiExternalFilterHelper
result[:name] = name result[:name] = name
result[:macro] = macro result[:macro] = macro
result[:content_types] = info['outputs'].map { |out| out['content_type'] }
result[:template] = info['template']
return result return result
end end
@ -79,18 +80,16 @@ module WikiExternalFilterHelper
content = [] content = []
errors = "" errors = ""
commands = info['commands']? info['commands'] : [info['command']] info['outputs'].each do |out|
RAILS_DEFAULT_LOGGER.info "executing command: #{out['command']}"
commands.each do |command|
RAILS_DEFAULT_LOGGER.info "executing command: #{command}"
c = nil c = nil
e = nil e = nil
Open4::popen4(command) { |pid, fin, fout, ferr| Open4::popen4(out['command']) { |pid, fin, fout, ferr|
fin.write info[:prolog] if info.key?(:prolog) fin.write out['prolog'] if out.key?('prolog')
fin.write CGI.unescapeHTML(text) fin.write CGI.unescapeHTML(text)
fin.write info[:epilog] if info.key?(:epilog) fin.write out['epilog'] if out.key?('epilog')
fin.close fin.close
c, e = [fout.read, ferr.read] c, e = [fout.read, ferr.read]
} }
@ -103,7 +102,6 @@ module WikiExternalFilterHelper
result[:content] = content result[:content] = content
result[:errors] = errors result[:errors] = errors
result[:content_type] = info['content_type']
result[:source] = text result[:source] = text
result[:status] = $?.exitstatus == 0 result[:status] = $?.exitstatus == 0
@ -111,21 +109,35 @@ module WikiExternalFilterHelper
end end
def render_tag(result) def render_tag(result)
render_to_string :template => 'wiki_external_filter/macro_inline', :layout => false, :locals => result result = result.dup
result[:render_type] = 'inline'
html = render_common(result).chop
html << headers_common(result).chop
html
end end
def render_block(result, wiki_name) def render_block(result, wiki_name)
result = result.dup result = result.dup
result[:render_type] = 'block'
result[:wiki_name] = wiki_name result[:wiki_name] = wiki_name
render_to_string :template => 'wiki_external_filter/macro_block', :layout => false, :locals => result result[:inside] = render_common(result)
html = render_to_string(:template => 'wiki_external_filter/block', :layout => false, :locals => result).chop
html << headers_common(result).chop
html
end
def render_common(result)
render_to_string :template => "wiki_external_filter/macro_#{result[:template]}", :layout => false, :locals => result
end
def headers_common(result)
render_to_string :template => 'wiki_external_filter/headers', :layout => false, :locals => result
end end
class Macro class Macro
def initialize(view, source, attachments, macro, info) def initialize(view, source, attachments, macro, info)
@view = view @view = view
@view.controller.extend(WikiExternalFilterHelper) @view.controller.extend(WikiExternalFilterHelper)
source.gsub!(/<br \/>/, "")
source.gsub!(/<\/?p>/, "")
@result = @view.controller.build(source, attachments, macro, info) @result = @view.controller.build(source, attachments, macro, info)
end end

View File

@ -0,0 +1,5 @@
<div class='externalfilterblock'>
<%= inside %>
<br/>
<span class='wiki_page'>Goto source: [[<%= wiki_name %>]]</span>
</div>

View File

@ -0,0 +1,10 @@
<%
content_for :header_tags do
if not @stylesheets_included
@stylesheets_included = true
%>
<%= stylesheet_link_tag "wiki_external_filter.css", :plugin => "wiki_external_filter", :media => :all %>
<%
end
end
%>

View File

@ -1,64 +0,0 @@
<div class='externalfilterblock'>
<%
case content_type
when /image\// then
%>
<img src='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name) %>' alt="<%= h source %>" />
<%
when /text\/plain/ then
%>
<pre><%= h content %></pre>
<%
when /\/.*(x|ht)ml/ then
%>
<div><%= content %></div>
<%
when /video\/x\-flv/ then
require 'RMagick'
image = Magick::Image::from_blob(content[0]).first
%>
<a class='flowplayer-video' href='<%= ActionController::Base.relative_url_root + "/wiki_external_filter/video.flv?name=#{name}" %>' alt='<%= h source %>' style='display:block;width:<%= image.columns %>px;height:<%= image.rows + 24 %>px;background-image:url(<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>);background-repeat:no-repeat'><%= image_tag 'play_large.png', :plugin => 'wiki_external_filter', :style => "display:block;position:relative;left:#{image.columns / 2 - 83 / 2}px;top:#{image.rows / 2 - 83 / 2}px" %></a>
<%
@flowplayer_used = true
%>
<%
else
%>
<object class='externalfilterinline' name='<%= name %>' data='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name) %>' type='<%= content_type %>' title="<%= h source %>">
<embed name='<%= name %>-2' src='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name) %>' type='<%= content_type %>' title="<%= h source %>" />
</object>
<%
end
%>
<br/>
<span class='wiki_page'>Goto source: [[<%= wiki_name %>]]</span>
</div>
<%
content_for :header_tags do
%>
<%
if not @stylesheets_included
@stylesheets_included = true
%>
<%= stylesheet_link_tag "wiki_external_filter.css", :plugin => "wiki_external_filter", :media => :all %>
<%
end
%>
<%
if @flowplayer_used
if not @flowplayer_scripts_included
@flowplayer_scripts_included = true
%>
<%= javascript_include_tag 'flowplayer.min.js', :plugin => 'wiki_external_filter' %>
<script language="JavaScript">
//<![CDATA[
window.onload = function () {
flowplayer("a.flowplayer-video", "<%= ActionController::Base.relative_url_root + Engines::RailsExtensions::AssetHelpers.plugin_asset_path('wiki_external_filter', 'javascripts', 'flowplayer.swf') %>", { clip: { bufferLength: 1 } });
};
//]]>
</script>
<%
end
end
end
%>

View File

@ -0,0 +1,22 @@
<%
require 'RMagick'
image = Magick::Image::from_blob(content[0]).first
%>
<a class='flowplayer-video<%= render_type == 'inline' ? " externalfilterinline" : "" %>' href='<%= ActionController::Base.relative_url_root + "/wiki_external_filter/#{macro}.flv?name=#{name}" %>' title='<%= h source %>' style='display:block;width:<%= image.columns %>px;height:<%= image.rows + 24 %>px;background-image:url(<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>);background-repeat:no-repeat'><%= image_tag 'play_large.png', :plugin => 'wiki_external_filter', :style => "display:block;position:relative;left:#{image.columns / 2 - 83 / 2}px;top:#{image.rows / 2 - 83 / 2}px" %></a>
<%
content_for :header_tags do
if not @flowplayer_scripts_included
@flowplayer_scripts_included = true
%>
<%= javascript_include_tag 'flowplayer.min.js', :plugin => 'wiki_external_filter' %>
<script language="JavaScript" type="text/javascript">
//<![CDATA[
window.onload = function () {
flowplayer("a.flowplayer-video", "<%= ActionController::Base.relative_url_root + Engines::RailsExtensions::AssetHelpers.plugin_asset_path('wiki_external_filter', 'javascripts', 'flowplayer.swf') %>", { clip: { bufferLength: 1 } });
};
//]]>
</script>
<%
end
end
%>

View File

@ -0,0 +1 @@
<img <%= render_type == 'inline' ? "class='externalfilterinline'" : "" %> src='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>' alt="<%= h source %>" />

View File

@ -1,61 +0,0 @@
<%
case content_type
when /image\// then
%>
<img class='externalfilterinline' src='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>' alt="<%= h source %>" />
<%
when /text\/plain/ then
%>
<pre class='externalfilterinline'><%= h content[0] %></pre>
<%
when /\/.*(x|ht)ml/ then
%>
<div class='externalfilterinline'><%= content[0] %></div>
<%
when /video\/x\-flv/ then
require 'RMagick'
image = Magick::Image::from_blob(content[0]).first
%>
<a class='flowplayer-video externalfilterinline' href='<%= ActionController::Base.relative_url_root + "/wiki_external_filter/video.flv?name=#{name}" %>' alt='<%= h source %>' style='display:block;width:<%= image.columns %>px;height:<%= image.rows + 24 %>px;background-image:url(<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>);background-repeat:no-repeat'><%= image_tag 'play_large.png', :plugin => 'wiki_external_filter', :style => "display:block;position:relative;left:#{image.columns / 2 - 83 / 2}px;top:#{image.rows / 2 - 83 / 2}px" %></a>
<%
@flowplayer_used = true
%>
<%
else
%>
<object class='externalfilterinline' name='<%= name %>' data='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>' type='<%= content_type %>' title="<%= h source %>">
<embed name='<%= name %>-2' src='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>' type='<%= content_type %>' title="<%= h source %>" />
</object>
<%
end
%>
<%
content_for :header_tags do
%>
<%
if not @stylesheets_included
@stylesheets_included = true
%>
<%= stylesheet_link_tag "wiki_external_filter.css", :plugin => "wiki_external_filter", :media => :all %>
<%
end
%>
<%
if @flowplayer_used
if not @flowplayer_scripts_included
@flowplayer_scripts_included = true
%>
<%= javascript_include_tag 'flowplayer.min.js', :plugin => 'wiki_external_filter' %>
<script language="JavaScript">
//<![CDATA[
window.onload = function () {
flowplayer("a.flowplayer-video", "<%= ActionController::Base.relative_url_root + Engines::RailsExtensions::AssetHelpers.plugin_asset_path('wiki_external_filter', 'javascripts', 'flowplayer.swf') %>", { clip: { bufferLength: 1 } });
};
//]]>
</script>
<%
end
end
end
%>

View File

@ -0,0 +1,27 @@
<img class='svg <%= render_type == 'inline' ? "externalfilterinline" : "" %>' src='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>' alt='<%= h source %>' />
<object class='svgobject <%= render_type == 'inline' ? "externalfilterinline" : "" %>' name='<%= name %>' data='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 0) %>' type='<%= content_types[0] %>' title="<%= h source %>">
<%
if content.size > 1
%>
<img<%= render_type == 'inline' ? " class='externalfilterinline'" : "" %> src='<%= url_for(:controller => 'wiki_external_filter', :action => 'filter', :macro => macro, :name => name, :index => 1) %>' alt='<%= h source %>' />
<%
end
%>
</object>
<%
content_for :header_tags do
if not @svg_scripts_included
@svg_scripts_included = true
%>
<%= javascript_include_tag 'css_browser_selector.js', :plugin => 'wiki_external_filter' %>
<style type="text/css">
.svgobject { display: none }
.ie .svgobject { display: inline }
.ie img.svg { display: none }
.gecko .svgobject { display: inline }
.gecko img.svg { display: none }
</style>
<%
end
end
%>

View File

@ -0,0 +1 @@
<span<%= render_type == 'inline' ? " class='externalfilterinline'" : "" %>><%= h content[0] %></span>

View File

@ -0,0 +1,8 @@
/*
CSS Browser Selector v0.3.5 (Feb 05, 2010)
Rafael Lima (http://rafael.adm.br)
http://rafael.adm.br/css_browser_selector
License: http://creativecommons.org/licenses/by/2.5/
Contributors: http://rafael.adm.br/css_browser_selector#contributors
*/
function css_browser_selector(u){var ua = u.toLowerCase(),is=function(t){return ua.indexOf(t)>-1;},g='gecko',w='webkit',s='safari',o='opera',h=document.documentElement,b=[(!(/opera|webtv/i.test(ua))&&/msie\s(\d)/.test(ua))?('ie ie'+RegExp.$1):is('firefox/2')?g+' ff2':is('firefox/3.5')?g+' ff3 ff3_5':is('firefox/3')?g+' ff3':is('gecko/')?g:is('opera')?o+(/version\/(\d+)/.test(ua)?' '+o+RegExp.$1:(/opera(\s|\/)(\d+)/.test(ua)?' '+o+RegExp.$2:'')):is('konqueror')?'konqueror':is('chrome')?w+' chrome':is('iron')?w+' iron':is('applewebkit/')?w+' '+s+(/version\/(\d+)/.test(ua)?' '+s+RegExp.$1:''):is('mozilla/')?g:'',is('j2me')?'mobile':is('iphone')?'iphone':is('ipod')?'ipod':is('mac')?'mac':is('darwin')?'mac':is('webtv')?'webtv':is('win')?'win':is('freebsd')?'freebsd':(is('x11')||is('linux'))?'linux':'','js']; c = b.join(' '); h.className += ' '+c; return c;}; css_browser_selector(navigator.userAgent);

View File

@ -1,3 +1,4 @@
ActionController::Routing::Routes.draw do |map| ActionController::Routing::Routes.draw do |map|
map.connect 'wiki_external_filter/:filename', :controller => 'wiki_external_filter', :action => 'filter', :macro => 'flowplayer', :index => '1', :requirements => { :filename => /\S+\.flv/ } map.connect 'wiki_external_filter/:filename', :controller => 'wiki_external_filter', :action => 'filter', :macro => 'video', :index => '1', :requirements => { :filename => /video\.flv/ }
map.connect 'wiki_external_filter/:filename', :controller => 'wiki_external_filter', :action => 'filter', :macro => 'video_url', :index => '1', :requirements => { :filename => /video_url\.flv/ }
end end

View File

@ -1,28 +1,54 @@
development: &development development: &development
plantuml: plantuml:
description: "Constructs UML diagram image from its textual description in PlantUML language, see http://plantuml.sourceforge.net" description: "Constructs UML diagram image from its textual description in PlantUML language, see http://plantuml.sourceforge.net"
command: "/usr/bin/plantuml -pipe" template: image
content_type: "image/png" outputs:
prolog: "@startuml" - command: "/usr/bin/plantuml -pipe"
epilog: "@enduml" content_type: "image/png"
prolog: "@startuml"
epilog: "@enduml"
graphviz: graphviz:
description: "Constructs graph image from its textual description in DOT language, see http://www.graphviz.org" description: "Constructs graph image from its textual description in DOT language, see http://www.graphviz.org"
command: "/usr/bin/dot -Tsvg" template: svg
content_type: "image/svg+xml" outputs:
- command: "/usr/bin/dot -Tsvg"
content_type: "image/svg+xml"
- command: "/usr/bin/dot -Tpng"
content_type: "image/png"
ritex: ritex:
description: "Converts WebTeX expression to MathML, see http://ritex.rubyforge.org/" description: "Converts WebTeX expression to MathML, see http://ritex.rubyforge.org/"
command: "(echo '<!DOCTYPE math PUBLIC \"-//W3C//DTD MathML 2.0//EN\" \"http://www.w3.org/Math/DTD/mathml2/mathml2.dtd\">'; /usr/bin/ritex) | xmllint --noent --nonet --catalogs --loaddtd - | sed 's/^<!DOCTYPE.\\+$//' | /usr/bin/math2svg" template: svg
content_type: "image/svg+xml" outputs:
# For MathML-compliant browsers and when Redmine is fully XML-compliant. - command: "(echo '<!DOCTYPE math PUBLIC \"-//W3C//DTD MathML 2.0//EN\" \"http://www.w3.org/Math/DTD/mathml2/mathml2.dtd\">'; /usr/bin/ritex) | xmllint --noent --nonet --catalogs --loaddtd - | sed 's/^<!DOCTYPE.\\+$//' | /usr/bin/math2svg"
# ritex: content_type: "image/svg+xml"
# description: "Converts WebTeX expression to MathML, see http://ritex.rubyforge.org/" video:
# command: "/usr/bin/ritex" description: "Converts video file given by its filename to FLV and displays it via Flowplayer"
# content_type: "application/xhtml+xml" template: flash-video
replace_attachments: true
outputs:
- command: "xargs -I{} ffmpeg -i {} -vframes 1 -f mjpeg -"
content_type: "image/jpeg"
# We have to use temporary file as otherwise ffmpeg is unable to inject
# metadata.
- command: "FN=`cat`; if [ `echo ${FN##*.} | tr [:upper:] [:lower:]` != 'flv' ]; then TMPVIDEO=`tempfile --directory=/var/tmp`; ffmpeg -y -i $FN -sameq -ar 44100 -f flv $TMPVIDEO; STATUS=$?; cat $TMPVIDEO; rm $TMPVIDEO; exit $STATUS; else cat $FN; fi"
content_type: "video/x-flv"
video_url:
description: "Converts video file given by its URL to FLV and displays it via Flowplayer"
template: flash-video
outputs:
- command: "wget -i - -O - | ffmpeg -i - -vframes 1 -f mjpeg -"
content_type: "image/jpeg"
# We have to use temporary file as otherwise ffmpeg is unable to inject
# metadata.
- command: "FN=`cat`; wget $FN -O - | (if [ `echo ${FN##*.} | tr [:upper:] [:lower:]` != 'flv' ]; then TMPVIDEO=`tempfile --directory=/var/tmp`; ffmpeg -y -i - -sameq -ar 44100 -f flv $TMPVIDEO; STATUS=$?; cat $TMPVIDEO; rm $TMPVIDEO; exit $STATUS; else cat; fi)"
content_type: "video/x-flv"
fortune: fortune:
description: "Prints a random, hopefully interesting, adage, see http://en.wikipedia.org/wiki/Fortune_(Unix)" description: "Prints a random, hopefully interesting, adage, see http://en.wikipedia.org/wiki/Fortune_(Unix)"
command: "/usr/bin/fortune" template: text
cache_seconds: 0 cache_seconds: 0
content_type: "text/plain" outputs:
- command: "/usr/bin/fortune"
content_type: "text/plain"
test: test:
type: mock type: mock

View File

@ -19,7 +19,7 @@ Redmine::Plugin.register :wiki_external_filter do
info = config[name] info = config[name]
desc info['description'] desc info['description']
macro name do |obj, args| macro name do |obj, args|
m = WikiExternalFilterHelper::Macro.new(self, args.to_s, obj.page.attachments, name, info) m = WikiExternalFilterHelper::Macro.new(self, args.to_s, obj ? obj.page.attachments : nil, name, info)
m.render m.render
end end