TracのWiki編集画面でプレビューを表示するTrac Plugin
TracのWiki編集画面で、「プレビュー」を押して画面遷移するのが面倒じゃないですか?*1
これ。
Wiki全体の変更内容を見るより、自分が変更した部分だけサクッと見たい。
という事で、TracPluginを作ってみました。
参考にしたプラグイン
とても参考にさせて頂きました。
こういう参考になるコードが簡単に見られるっていい時代ですよね。
WikiPreviewOverrayPlugin
入力したテキストを選択して、右クリックを押すことでWiki編集画面から画面遷移せずにWiki変更後のイメージを表示出来ます。*2
こんな感じ。
内部の話ですが、技術的には
辺りを使用しています。
Python初心者+Javascript未経験だったため、良い勉強になりました。
インストール方法
WindowsXP+TracLightning2.4.0での確認しかしていませんので、動かなかったらスミマセン。
まずは、後述しているソースを下記フォルダ構成で配置する。
- フォルダ構成
■WikiPreviewOverrayPlugin setup.py ■wikipreviewoverray __init__.py wikipreviewoverray.py ■htdocs wikipreviewoverray.css ■templates wikipreviewoverray.js
後は、下記のどれかで動かしてみることができます。
- リンクだけ作って確認してみる方法
python setup.py develop
- 普通にinstallする方法
python setup.py install
- eggを作って、Tracのweb画面からinstallする方法
python setup.py bdist_egg
インストール後、もしかしたらサーバの再起動が必要かもしれません。
ソース
外部に置くリポジトリがないため、ひとまずブログ上に公開。
- setup.py
#!/usr/bin/env python # -*- coding: utf-8 -*- from setuptools import find_packages, setup setup( name='WikiPreviewOverrayPlugin', version='0.1', packages=find_packages(exclude=['*.tests*']), package_data={'wikipreviewoverray': [ 'htdocs/*', 'templates/*']}, entry_points = """ [trac.plugins] WikiPreviewOverray = wikipreviewoverray """, url='http://d.hatena.ne.jp/sinsoku/', author = 'sinsoku', author_email = "sinsoku.listy@gmail.com", description = u"Wiki Preview", license ="New BSD", )
- /wikipreviewoverray/__init__.py
# -*- coding: utf-8 -*- from wikipreviewoverray import *
- /wikipreviewoverray/wikipreviewoverray.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import re from trac.core import * from trac.web.chrome import ITemplateProvider, add_stylesheet, add_script from trac.web.api import IRequestFilter, IRequestHandler from trac.util import escape, Markup from trac.perm import IPermissionRequestor from trac.wiki.formatter import wiki_to_html from pkg_resources import resource_filename class WikiPreviewOverrayPlugin(Component): implements(IRequestHandler, ITemplateProvider, IRequestFilter) # ITemplateProvider methods def get_templates_dirs(self): yield resource_filename(__name__, 'templates') def get_htdocs_dirs(self): yield 'wikipreviewoverray', resource_filename(__name__, 'htdocs') # IRequestHandler methods def match_request(self, req): return re.match(r'^/WikiPreviewOverray(?:(.*))', req.path_info) is not None def process_request(self, req): if re.match(r'^/WikiPreviewOverray/wikipreviewoverray.js',req.path_info) : if 'WIKI_CREATE' in req.perm('wiki') or 'WIKI_ADMIN' in req.perm('wiki'): return 'wikipreviewoverray.js',{},'text/plain' # IRequestFilter methods def post_process_request(self, req, template, data, content_type) : if re.match(r'/wiki/', req.path_info) and req.method =='GET' and req.args.get('action') == "edit" : add_script(req, '/WikiPreviewOverray/wikipreviewoverray.js') if not re.match(r'^wikipreviewoverray/wikipreviewoverray.css',req.path_info) : add_stylesheet(req, 'wikipreviewoverray/wikipreviewoverray.css') if re.match(r'^/WikiPreviewOverray/post',req.path_info) : if req.method == 'POST' : text = req.args.get('tohtml') html = unicode(wiki_to_html(text, self.env, req, absurls=1)) req.send_response(200) req.send_header('Content-Type', 'applicatiion/x-www-form-urlencoded') req.end_headers() req.write(html) return template, data, content_type def pre_process_request(self, req, handler): return handler
- /wikipreviewoverray/htdocs/wikipreviewoverray.css
div#WikiPreviewOverray_Container{ position:absolute; padding:5px; background-color: #FFFFFF; z-index: 999; color: #000000; border: 1px solid #000000; -moz-border-radius: 10px; /* for Fx */ -webkit-border-radius: 10px; /* for Safari */ -border-radius:10px; } div#WikiPreviewOverray_Container span#WikiPreviewOverray_Button{ width:5px; height:5px; padding: 0px; display: block; float: right; border: 1px solid #000000; -moz-border-radius: 9px; /* for Fx */ -webkit-border-radius: 9px; /* for Safari */ -border-radius:9px; } div#WikiPreviewOverray_Container span#WikiPreviewOverray_Button:hover{ background-color:#FF0000; }
- /wikipreviewoverray/templates/wikipreviewoverray.js
$( function() { $(document).ready(function(){ $("div#footer").after("<div id='WikiPreviewOverray_Container'/>") $("div#WikiPreviewOverray_Container") .css( "max-width", 380) .css( "max-height", 480) .css( "top" , 0) .css( "left", 0); $("#WikiPreviewOverray_Container").append("<span id='WikiPreviewOverray_Button'></span>") $("span#WikiPreviewOverray_Button").click( function(){ $("#WikiPreviewOverray_Container").hide("slow"); }); $("#WikiPreviewOverray_Container").hide(); $("#WikiPreviewOverray_Container").append("<div id='WikiPreviewOverray_Html'/>") $("div#WikiPreviewOverray_Html") .css( "overflow", "auto") .css( "max-width", 370) .css( "max-height", 470) }); $(document).bind("contextmenu",function(event){ if(document.selection) { // IE var range = document.selection.createRange(); var selected_value = range.text; } else { // IE 以外 var org = document.getElementById("text"); var start = org.selectionStart; var end = org.selectionEnd; var selected_value = org.value.substring(start, end); } if(selected_value.length > 0) { var url = location.pathname.split("/"); var req_url = ""; // /wiki/WikiStartの分をurlから削って、req_urlを構築する for(var i = 1; i < url.length - 2; i++) req_url += "/" + url[i]; req_url += "/WikiPreviewOverray/post"; $.post( req_url, {"tohtml" : selected_value, "__FORM_TOKEN" : $('[name=__FORM_TOKEN]').attr('value')}, function(data, status) { $("div#WikiPreviewOverray_Html").html(data); $("div#WikiPreviewOverray_Container") .css( "top" , event.pageY) .css( "left", event.pageX); $("#WikiPreviewOverray_Container").hide(); $("#WikiPreviewOverray_Container").show("slow"); }, "html" ); selected_value = ""; } // 右クリックメニュー(コンテキストメニュー)を非表示にする。 return false; }); })
Plugin開発者向け
今回のPlugin作成で、POSTを使用する所でハマったので、備忘録を残しておきます。
Tracの本体側ではCSRF対策として、フォームにハッシュのような文字列を付加しています。
本体(/TracLight/python-lib/trac/trac/web/main.py)のソースだと、190行目付近。
- main.py
# Protect against CSRF attacks: we validate the form token for # all POST requests with a content-type corresponding to form # submissions if req.method == 'POST': ctype = req.get_header('Content-Type') if ctype: ctype, options = cgi.parse_header(ctype) if ctype in ('application/x-www-form-urlencoded', 'multipart/form-data') and \ req.args.get('__FORM_TOKEN') != req.form_token: raise HTTPBadRequest('Missing or invalid form token. ' 'Do you have cookies enabled?')
上記を見ていただければわかるように、
- 'Content-Type'が'application/x-www-form-urlencoded'もしくは'multipart/form-data'でない
- '__FORM_TOKEN'がない
と400 Bad Requestが発生してしまいます。
これを回避するためには、Tracのページから'__FORM_TOKEN'を取得し、POSTに含めてやります。
$.post( req_url, {"__FORM_TOKEN" : $('[name=__FORM_TOKEN]').attr('value') }, function(data, status) { //何かの処理 }, "html" );
気づけば簡単に修正出来ますが、中々Web上に情報がなく、苦戦しました。
追記
ソースコード一式をzipにしてDropboxに置きました。
WikiPreviewOverrayPlugin-0.1.zip