- Unified block/inline rendering.
- Split views to separate files. - Added PNG fallback for SVG displaying. - Improved SVG compliance.
This commit is contained in:
parent
8cfa874772
commit
5a6f81888f
|
@ -2,8 +2,13 @@ Redmine Wiki External Filter plugin that allows to filter macro blocks
|
|||
arguments using external program and display results on wiki.
|
||||
|
||||
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
|
||||
modify it under the terms of the GNU General Public License
|
||||
|
|
|
@ -13,7 +13,7 @@ class WikiExternalFilterController < ApplicationController
|
|||
content = read_fragment cache_key
|
||||
|
||||
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
|
||||
render_404
|
||||
end
|
||||
|
|
|
@ -47,7 +47,6 @@ module WikiExternalFilterHelper
|
|||
if content
|
||||
result[:source] = text
|
||||
result[:content] = content
|
||||
result[:content_type] = info['content_type']
|
||||
RAILS_DEFAULT_LOGGER.debug "from cache: #{name}"
|
||||
else
|
||||
result = self.build_forced(text, attachments, info)
|
||||
|
@ -63,6 +62,8 @@ module WikiExternalFilterHelper
|
|||
|
||||
result[:name] = name
|
||||
result[:macro] = macro
|
||||
result[:content_types] = info['outputs'].map { |out| out['content_type'] }
|
||||
result[:template] = info['template']
|
||||
|
||||
return result
|
||||
end
|
||||
|
@ -79,18 +80,16 @@ module WikiExternalFilterHelper
|
|||
content = []
|
||||
errors = ""
|
||||
|
||||
commands = info['commands']? info['commands'] : [info['command']]
|
||||
|
||||
commands.each do |command|
|
||||
RAILS_DEFAULT_LOGGER.info "executing command: #{command}"
|
||||
info['outputs'].each do |out|
|
||||
RAILS_DEFAULT_LOGGER.info "executing command: #{out['command']}"
|
||||
|
||||
c = nil
|
||||
e = nil
|
||||
|
||||
Open4::popen4(command) { |pid, fin, fout, ferr|
|
||||
fin.write info[:prolog] if info.key?(:prolog)
|
||||
Open4::popen4(out['command']) { |pid, fin, fout, ferr|
|
||||
fin.write out['prolog'] if out.key?('prolog')
|
||||
fin.write CGI.unescapeHTML(text)
|
||||
fin.write info[:epilog] if info.key?(:epilog)
|
||||
fin.write out['epilog'] if out.key?('epilog')
|
||||
fin.close
|
||||
c, e = [fout.read, ferr.read]
|
||||
}
|
||||
|
@ -103,7 +102,6 @@ module WikiExternalFilterHelper
|
|||
|
||||
result[:content] = content
|
||||
result[:errors] = errors
|
||||
result[:content_type] = info['content_type']
|
||||
result[:source] = text
|
||||
result[:status] = $?.exitstatus == 0
|
||||
|
||||
|
@ -111,21 +109,35 @@ module WikiExternalFilterHelper
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def render_block(result, wiki_name)
|
||||
result = result.dup
|
||||
result[:render_type] = 'block'
|
||||
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
|
||||
|
||||
class Macro
|
||||
def initialize(view, source, attachments, macro, info)
|
||||
@view = view
|
||||
@view.controller.extend(WikiExternalFilterHelper)
|
||||
source.gsub!(/<br \/>/, "")
|
||||
source.gsub!(/<\/?p>/, "")
|
||||
@result = @view.controller.build(source, attachments, macro, info)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<div class='externalfilterblock'>
|
||||
<%= inside %>
|
||||
<br/>
|
||||
<span class='wiki_page'>Goto source: [[<%= wiki_name %>]]</span>
|
||||
</div>
|
|
@ -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
|
||||
%>
|
|
@ -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
|
||||
%>
|
|
@ -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
|
||||
%>
|
|
@ -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 %>" />
|
|
@ -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
|
||||
%>
|
|
@ -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
|
||||
%>
|
|
@ -0,0 +1 @@
|
|||
<span<%= render_type == 'inline' ? " class='externalfilterinline'" : "" %>><%= h content[0] %></span>
|
|
@ -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);
|
|
@ -1,3 +1,4 @@
|
|||
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
|
||||
|
|
|
@ -1,28 +1,54 @@
|
|||
development: &development
|
||||
plantuml:
|
||||
description: "Constructs UML diagram image from its textual description in PlantUML language, see http://plantuml.sourceforge.net"
|
||||
command: "/usr/bin/plantuml -pipe"
|
||||
content_type: "image/png"
|
||||
prolog: "@startuml"
|
||||
epilog: "@enduml"
|
||||
template: image
|
||||
outputs:
|
||||
- command: "/usr/bin/plantuml -pipe"
|
||||
content_type: "image/png"
|
||||
prolog: "@startuml"
|
||||
epilog: "@enduml"
|
||||
graphviz:
|
||||
description: "Constructs graph image from its textual description in DOT language, see http://www.graphviz.org"
|
||||
command: "/usr/bin/dot -Tsvg"
|
||||
content_type: "image/svg+xml"
|
||||
template: svg
|
||||
outputs:
|
||||
- command: "/usr/bin/dot -Tsvg"
|
||||
content_type: "image/svg+xml"
|
||||
- command: "/usr/bin/dot -Tpng"
|
||||
content_type: "image/png"
|
||||
ritex:
|
||||
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"
|
||||
content_type: "image/svg+xml"
|
||||
# For MathML-compliant browsers and when Redmine is fully XML-compliant.
|
||||
# ritex:
|
||||
# description: "Converts WebTeX expression to MathML, see http://ritex.rubyforge.org/"
|
||||
# command: "/usr/bin/ritex"
|
||||
# content_type: "application/xhtml+xml"
|
||||
template: svg
|
||||
outputs:
|
||||
- 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"
|
||||
content_type: "image/svg+xml"
|
||||
video:
|
||||
description: "Converts video file given by its filename to FLV and displays it via Flowplayer"
|
||||
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:
|
||||
description: "Prints a random, hopefully interesting, adage, see http://en.wikipedia.org/wiki/Fortune_(Unix)"
|
||||
command: "/usr/bin/fortune"
|
||||
template: text
|
||||
cache_seconds: 0
|
||||
content_type: "text/plain"
|
||||
outputs:
|
||||
- command: "/usr/bin/fortune"
|
||||
content_type: "text/plain"
|
||||
|
||||
test:
|
||||
type: mock
|
||||
|
|
2
init.rb
2
init.rb
|
@ -19,7 +19,7 @@ Redmine::Plugin.register :wiki_external_filter do
|
|||
info = config[name]
|
||||
desc info['description']
|
||||
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
|
||||
end
|
||||
|
||||
|
|
Reference in New Issue