changeset 353:cbf97d9067f3

Merging with main repo
author Michael Mayorov <marchael@kb.csu.ru>
date Mon, 04 Jul 2011 12:35:19 +0000
parents 7ad7f9472ae0
children ae63dc7cf921
files MoinMoin/app.py MoinMoin/apps/__init__.py MoinMoin/apps/admin/__init__.py MoinMoin/apps/admin/views.py MoinMoin/apps/feed/__init__.py MoinMoin/apps/frontend/__init__.py MoinMoin/apps/frontend/views.py MoinMoin/apps/misc/__init__.py MoinMoin/apps/serve/__init__.py MoinMoin/config/default.py MoinMoin/converter/audio_video_in.py MoinMoin/converter/everything.py MoinMoin/converter/image_in.py MoinMoin/converter/link.py MoinMoin/converter/nonexistent_in.py MoinMoin/items/__init__.py MoinMoin/script/maint/build_indexes.py MoinMoin/storage/backends/sqla.py MoinMoin/templates/base.html MoinMoin/templates/global_index.html MoinMoin/templates/history.html MoinMoin/templates/index.html MoinMoin/templates/index2.html MoinMoin/templates/itemviews.html MoinMoin/templates/modify_svg-edit.html MoinMoin/templates/modify_twikidraw.html MoinMoin/templates/snippets.html MoinMoin/util/mimetype.py MoinMoin/wikiutil.py quickinstall quickinstall.bat setup.py wikiconfig.py
diffstat 33 files changed, 216 insertions(+), 408 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/app.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/app.py	Mon Jul 04 12:35:19 2011 +0000
@@ -103,21 +103,21 @@
     from MoinMoin.apps.frontend import frontend
     frontend.before_request(before_wiki)
     frontend.after_request(after_wiki)
-    app.register_module(frontend)
+    app.register_blueprint(frontend)
     from MoinMoin.apps.admin import admin
     admin.before_request(before_wiki)
     admin.after_request(after_wiki)
-    app.register_module(admin, url_prefix='/+admin')
+    app.register_blueprint(admin, url_prefix='/+admin')
     from MoinMoin.apps.feed import feed
     feed.before_request(before_wiki)
     feed.after_request(after_wiki)
-    app.register_module(feed, url_prefix='/+feed')
+    app.register_blueprint(feed, url_prefix='/+feed')
     from MoinMoin.apps.misc import misc
     misc.before_request(before_wiki)
     misc.after_request(after_wiki)
-    app.register_module(misc, url_prefix='/+misc')
+    app.register_blueprint(misc, url_prefix='/+misc')
     from MoinMoin.apps.serve import serve
-    app.register_module(serve, url_prefix='/+serve')
+    app.register_blueprint(serve, url_prefix='/+serve')
     clock.stop('create_app register')
     clock.start('create_app flask-cache')
     cache = Cache()
@@ -146,7 +146,7 @@
             FileSystemLoader(app.cfg.template_dirs),
             app.jinja_env.loader,
         ])
-    app.error_handlers[403] = themed_error
+    app.register_error_handler(403, themed_error)
     clock.stop('create_app flask-themes')
     clock.stop('create_app total')
     del clock
@@ -269,11 +269,7 @@
     try:
         flaskg.unprotected_storage = app.unprotected_storage
 
-        try:
-            flaskg.user = setup_user()
-        except HTTPException as e:
-            # this makes stuff like abort(redirect(...)) work
-            return app.handle_http_exception(e)
+        flaskg.user = setup_user()
 
         flaskg.dicts = app.cfg.dicts()
         flaskg.groups = app.cfg.groups()
--- a/MoinMoin/apps/__init__.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/__init__.py	Mon Jul 04 12:35:19 2011 +0000
@@ -0,0 +1,13 @@
+# Copyright: 2011 MoinMoin:ThomasWaldmann
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - flask modules for better modularization
+
+This package contains some Flask Modules:
+
+- frontend has all usual wiki user interface code
+- feed Module for all feed-like stuff
+- admin Module for special stuff for wiki admins
+- serve Module for static file serving
+"""
--- a/MoinMoin/apps/admin/__init__.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/admin/__init__.py	Mon Jul 04 12:35:19 2011 +0000
@@ -8,7 +8,7 @@
 """
 
 
-from flask import Module
-admin = Module(__name__)
+from flask import Blueprint
+admin = Blueprint('admin', __name__, template_folder='templates')
 import MoinMoin.apps.admin.views
 
--- a/MoinMoin/apps/admin/views.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/admin/views.py	Mon Jul 04 12:35:19 2011 +0000
@@ -83,7 +83,7 @@
             flash('%s.%s: %s -> %s' % (user_name, key, unicode(oldval), unicode(val), ), "info")
         else:
             flash('modifying %s.%s failed' % (user_name, key, ), "error")
-    return redirect(url_for('admin.userbrowser'))
+    return redirect(url_for('.userbrowser'))
 
 
 @admin.route('/mail_recovery_token', methods=['GET', 'POST', ])
@@ -92,7 +92,7 @@
     Send user an email so he can reset his password.
     """
     flash("mail recovery token not implemented yet")
-    return redirect(url_for('admin.userbrowser'))
+    return redirect(url_for('.userbrowser'))
 
 
 @admin.route('/sysitems_upgrade', methods=['GET', 'POST', ])
@@ -113,7 +113,7 @@
             flash(_('System items upgrade failed due to the following error: %(error)s.', error=e), 'error')
         else:
             flash(_('System items have been upgraded successfully!'))
-        return redirect(url_for('admin.index'))
+        return redirect(url_for('.index'))
 
 
 from MoinMoin.config import default as defaultconfig
--- a/MoinMoin/apps/feed/__init__.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/feed/__init__.py	Mon Jul 04 12:35:19 2011 +0000
@@ -9,7 +9,7 @@
 """
 
 
-from flask import Module
-feed = Module(__name__)
+from flask import Blueprint
+feed = Blueprint('feed', __name__)
 import MoinMoin.apps.feed.views
 
--- a/MoinMoin/apps/frontend/__init__.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/frontend/__init__.py	Mon Jul 04 12:35:19 2011 +0000
@@ -9,7 +9,7 @@
 """
 
 
-from flask import Module
-frontend = Module(__name__)
+from flask import Blueprint
+frontend = Blueprint('frontend', __name__)
 import MoinMoin.apps.frontend.views
 
--- a/MoinMoin/apps/frontend/views.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/frontend/views.py	Mon Jul 04 12:35:19 2011 +0000
@@ -59,7 +59,7 @@
 @frontend.route('/')
 def show_root():
     item_name = app.cfg.item_root
-    location = url_for('frontend.show_item', item_name=item_name)
+    location = url_for('.show_item', item_name=item_name)
     return redirect(location)
 
 @frontend.route('/robots.txt')
@@ -69,6 +69,7 @@
 Crawl-delay: 20
 Disallow: /+convert/
 Disallow: /+dom/
+Disallow: /+download/
 Disallow: /+modify/
 Disallow: /+copy/
 Disallow: /+delete/
@@ -177,7 +178,7 @@
 
 @frontend.route('/+show/<itemname:item_name>')
 def redirect_show_item(item_name):
-    return redirect(url_for('frontend.show_item', item_name=item_name))
+    return redirect(url_for('.show_item', item_name=item_name))
 
 
 @frontend.route('/+dom/<int:rev>/<itemname:item_name>')
@@ -248,6 +249,15 @@
         abort(403)
     return item.do_get()
 
+@frontend.route('/+download/<int:rev>/<itemname:item_name>')
+@frontend.route('/+download/<itemname:item_name>', defaults=dict(rev=-1))
+def download_item(item_name, rev):
+    try:
+        item = Item.create(item_name, rev_no=rev)
+    except AccessDeniedError:
+        abort(403)
+    return item.do_get(force_attachment=True)
+
 @frontend.route('/+convert/<itemname:item_name>')
 def convert_item(item_name):
     """
@@ -331,7 +341,7 @@
         TextCha(form).amend_form()
         if form.validate():
             item.revert()
-            return redirect(url_for('frontend.show_item', item_name=item_name))
+            return redirect(url_for('.show_item', item_name=item_name))
     return render_template(item.revert_template,
                            item=item, item_name=item_name,
                            rev_no=rev,
@@ -356,7 +366,7 @@
             target = form['target'].value
             comment = form['comment'].value
             item.copy(target, comment)
-            return redirect(url_for('frontend.show_item', item_name=target))
+            return redirect(url_for('.show_item', item_name=target))
     return render_template(item.copy_template,
                            item=item, item_name=item_name,
                            form=form,
@@ -380,7 +390,7 @@
             target = form['target'].value
             comment = form['comment'].value
             item.rename(target, comment)
-            return redirect(url_for('frontend.show_item', item_name=target))
+            return redirect(url_for('.show_item', item_name=target))
     return render_template(item.rename_template,
                            item=item, item_name=item_name,
                            form=form,
@@ -402,7 +412,7 @@
         if form.validate():
             comment = form['comment'].value
             item.delete(comment)
-            return redirect(url_for('frontend.show_item', item_name=item_name))
+            return redirect(url_for('.show_item', item_name=item_name))
     return render_template(item.delete_template,
                            item=item, item_name=item_name,
                            form=form,
@@ -432,7 +442,7 @@
         if form.validate():
             comment = form['comment'].value
             item.destroy(comment=comment, destroy_item=destroy_item)
-            return redirect(url_for('frontend.show_item', item_name=item_name))
+            return redirect(url_for('.show_item', item_name=item_name))
     return render_template(item.destroy_template,
                            item=item, item_name=item_name,
                            rev_no=rev,
@@ -443,7 +453,7 @@
 # XXX this has some functional redundancy with "index", solve that later
 @frontend.route('/+index2/<itemname:item_name>', methods=['GET'])
 def index2(item_name):
-    # flat index using jquery.file-upload (see also jfu_server)
+    # flat index using jquery-file-upload (see also jfu_server)
     return render_template('index2.html',
                            item_name=item_name,
                           )
@@ -459,8 +469,8 @@
             abort(403)
         files = []
         for full_name, rel_name, mimetype in item.flat_index():
-            url = url_for('show_item', item_name=full_name)
-            url_download = url_for('get_item', item_name=full_name)
+            url = url_for('.show_item', item_name=full_name)
+            url_download = url_for('.download_item', item_name=full_name)
             files.append(dict(name=rel_name, url=url, url_download=url_download, size=0))
         return jsonify(files=files)
     if request.method == 'POST':
@@ -474,8 +484,8 @@
                                item_name=item_name)
             return jsonify(name=subitem_name,
                            size=size,
-                           url=url_for('show_item', item_name=item_name, rev=revno),
-                           url_download=url_for('get_item', item_name=item_name, rev=revno),
+                           url=url_for('.show_item', item_name=item_name, rev=revno),
+                           url_download=url_for('.download_item', item_name=item_name, rev=revno),
                           )
         except AccessDeniedError:
             abort(403)
@@ -668,7 +678,7 @@
             msg = _('Your quicklink to this page could not be removed.'), "error"
     if msg:
         flash(*msg)
-    return redirect(url_for('frontend.show_item', item_name=item_name))
+    return redirect(url_for('.show_item', item_name=item_name))
 
 
 @frontend.route('/+subscribe/<itemname:item_name>')
@@ -692,7 +702,7 @@
             msg = _('You could not get subscribed to this item.'), "error"
     if msg:
         flash(*msg)
-    return redirect(url_for('frontend.show_item', item_name=item_name))
+    return redirect(url_for('.show_item', item_name=item_name))
 
 
 class ValidRegistration(Validator):
@@ -798,7 +808,7 @@
                     flash(msg, "error")
                 else:
                     flash(_('Account created, please log in now.'), "info")
-                    return redirect(url_for('frontend.show_root'))
+                    return redirect(url_for('.show_root'))
 
     else:
         # not openid registration and no MoinAuth
@@ -824,7 +834,7 @@
                     flash(msg, "error")
                 else:
                     flash(_('Account created, please log in now.'), "info")
-                    return redirect(url_for('frontend.show_root'))
+                    return redirect(url_for('.show_root'))
 
     return render_template(template,
                            item_name=item_name,
@@ -882,7 +892,7 @@
                 if not is_ok:
                     flash(msg, "error")
             flash(_("If this account exists, you will be notified."), "info")
-            return redirect(url_for('frontend.show_root'))
+            return redirect(url_for('.show_root'))
     return render_template('lostpass.html',
                            item_name=item_name,
                            form=form,
@@ -937,7 +947,7 @@
                 flash(_("Your password has been changed, you can log in now."), "info")
             else:
                 flash(_('Your token is invalid!'), "error")
-            return redirect(url_for('frontend.show_root'))
+            return redirect(url_for('.show_root'))
     return render_template('recoverpass.html',
                            item_name=item_name,
                            form=form,
@@ -1005,7 +1015,7 @@
         form = LoginForm.from_flat(request.form)
         if form.validate():
             # we have a logged-in, valid user
-            return redirect(url_for('frontend.show_root'))
+            return redirect(url_for('.show_root'))
         # flash the error messages (if any)
         for msg in flaskg._login_messages:
             flash(msg, "error")
@@ -1022,7 +1032,7 @@
     for key in ['user.id', 'user.auth_method', 'user.auth_attribs', ]:
         if key in session:
             del session[key]
-    return redirect(url_for('frontend.show_root'))
+    return redirect(url_for('.show_root'))
 
 
 class ValidChangePass(Validator):
@@ -1163,7 +1173,7 @@
                 if success:
                     form.update_object(flaskg.user, omit=['submit']) # don't save submit button value :)
                     flaskg.user.save()
-                    return redirect(url_for('frontend.usersettings'))
+                    return redirect(url_for('.usersettings'))
                 else:
                     # reset to valid values
                     form = FormClass.from_object(flaskg.user)
@@ -1197,7 +1207,7 @@
             flaskg.user.setBookmark(tm)
     else:
         flash(_("You must log in to use bookmarks."), "error")
-    return redirect(url_for('frontend.global_history'))
+    return redirect(url_for('.global_history'))
 
 
 @frontend.route('/+diffraw/<path:item_name>')
--- a/MoinMoin/apps/misc/__init__.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/misc/__init__.py	Mon Jul 04 12:35:19 2011 +0000
@@ -8,7 +8,7 @@
 """
 
 
-from flask import Module
-misc = Module(__name__)
+from flask import Blueprint
+misc = Blueprint('misc', __name__, template_folder='templates')
 import MoinMoin.apps.misc.views
 
--- a/MoinMoin/apps/serve/__init__.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/apps/serve/__init__.py	Mon Jul 04 12:35:19 2011 +0000
@@ -10,7 +10,7 @@
 """
 
 
-from flask import Module
-serve = Module(__name__)
+from flask import Blueprint
+serve = Blueprint('serve', __name__)
 import MoinMoin.apps.serve.views
 
--- a/MoinMoin/config/default.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/config/default.py	Mon Jul 04 12:35:19 2011 +0000
@@ -352,7 +352,7 @@
     ('item_views', [
         # (endpointname, label, check_item_exists
         ('frontend.show_item', L_('Show'), L_('Show'), False, ),
-        ('frontend.get_item', L_('Download'), L_('Download'), True, ),
+        ('frontend.download_item', L_('Download'), L_('Download'), True, ),
         ('frontend.history', L_('History'), L_('Revision History'), True, ),
         # note: when rendering a non-existing item, you'll be offered to
         # create it (in the content area), so we do not offer "Modify":
--- a/MoinMoin/converter/audio_video_in.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/converter/audio_video_in.py	Mon Jul 04 12:35:19 2011 +0000
@@ -26,11 +26,11 @@
     def __init__(self, input_type):
         self.input_type = input_type
 
-    def __call__(self, content):
-        item_name = content # we just give the name of the item in the content
+    def __call__(self, rev):
+        item_name = rev.item.name
         attrib = {
             moin_page.type_: unicode(self.input_type),
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get'),
+            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get&rev=%d' % rev.revno),
         }
         return moin_page.object_(attrib=attrib, children=[u'Your Browser does not support HTML5 audio/video element.', ])
 
--- a/MoinMoin/converter/everything.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/converter/everything.py	Mon Jul 04 12:35:19 2011 +0000
@@ -21,10 +21,10 @@
     def _factory(cls, input, output, **kw):
         return cls()
 
-    def __call__(self, content):
-        item_name = content # we just give the name of the item in the content
+    def __call__(self, rev):
+        item_name = rev.item.name
         attrib = {
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get'),
+            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get&rev=%d' % rev.revno),
         }
         return moin_page.a(attrib=attrib, children=["Download %s." % item_name])
 
--- a/MoinMoin/converter/image_in.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/converter/image_in.py	Mon Jul 04 12:35:19 2011 +0000
@@ -24,11 +24,11 @@
     def __init__(self, input_type):
         self.input_type = input_type
 
-    def __call__(self, content):
-        item_name = content # we just give the name of the item in the content
+    def __call__(self, rev):
+        item_name = rev.item.name
         attrib = {
             moin_page.type_: unicode(self.input_type),
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get'),
+            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get&rev=%d' % rev.revno),
         }
         return moin_page.object_(attrib=attrib, children=[item_name, ])
 
--- a/MoinMoin/converter/link.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/converter/link.py	Mon Jul 04 12:35:19 2011 +0000
@@ -105,14 +105,15 @@
         if links == 'extern':
             return cls(url_root=url_root)
 
-    def _get_do(self, query):
+    def _get_do_rev(self, query):
         """
-        get 'do' value from query string and remove do=value from querystring
+        get 'do' and 'rev' values from query string and remove them from querystring
 
         Note: we can't use url_decode/url_encode from e.g. werkzeug because
               url_encode quotes the qs values (and Iri code will quote them again)
         """
         do = None
+        revno = None
         separator = '&'
         result = []
         if query:
@@ -126,15 +127,20 @@
                 if k == 'do':
                     do = v
                     continue # we remove do=xxx from qs
+                if k == 'rev':
+                    revno = v
+                    continue # we remove rev=n from qs
                 result.append(u'%s=%s' % (k, v))
         if result:
             query = separator.join(result)
         else:
             query = None
-        return do, query
+        if revno is not None:
+            revno = int(revno)
+        return do, revno, query
 
     def handle_wiki_links(self, elem, input):
-        do, query = self._get_do(input.query)
+        do, revno, query = self._get_do_rev(input.query)
         link = Iri(query=query, fragment=input.fragment)
 
         if input.authority and input.authority.host:
@@ -156,16 +162,18 @@
 
         if not input.authority or err:
             # local wiki link
+            path = input.path[1:]
+            if revno is not None:
+                path = IriPath('%d/' % revno) + path
             if do is not None:
-                link.path = IriPath('+' + do + '/') + input.path[1:]
-            else:
-                link.path = input.path[1:]
+                path = IriPath('+%s/' % do) + path
+            link.path = path
             base = self.url_root
 
         elem.set(self._tag_xlink_href, base + link)
 
     def handle_wikilocal_links(self, elem, input, page):
-        do, query = self._get_do(input.query)
+        do, revno, query = self._get_do_rev(input.query)
         link = Iri(query=query, fragment=input.fragment)
 
         if input.path:
@@ -177,10 +185,11 @@
         else:
             path = page.path[1:]
 
+        if revno is not None:
+            path = IriPath('%d/' % revno) + path
         if do is not None:
-            link.path = IriPath('+' + do + '/') + path
-        else:
-            link.path = path
+            path = IriPath('+%s/') + path
+        link.path = path
         output = self.url_root + link
 
         elem.set(self._tag_xlink_href, output)
--- a/MoinMoin/converter/nonexistent_in.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/converter/nonexistent_in.py	Mon Jul 04 12:35:19 2011 +0000
@@ -22,8 +22,8 @@
     def _factory(cls, input, output, **kw):
         return cls()
 
-    def __call__(self, content):
-        item_name = content # we just give the name of the item in the content
+    def __call__(self, rev):
+        item_name = rev.item.name
         attrib = {
             xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=modify'),
         }
--- a/MoinMoin/items/__init__.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/items/__init__.py	Mon Jul 04 12:35:19 2011 +0000
@@ -1,5 +1,5 @@
 # Copyright: 2009 MoinMoin:ThomasWaldmann
-# Copyright: 2009 MoinMoin:ReimarBauer
+# Copyright: 2009-2011 MoinMoin:ReimarBauer
 # Copyright: 2009 MoinMoin:ChristopherDenter
 # Copyright: 2008,2009 MoinMoin:BastianBlank
 # Copyright: 2010 MoinMoin:ValentinJaniaut
@@ -219,7 +219,7 @@
         return ''
 
     def feed_input_conv(self):
-        return self.name
+        return self.rev
 
     def internal_representation(self, converters=['smiley']):
         """
@@ -633,7 +633,7 @@
         ]),
     ]
 
-    def do_get(self):
+    def do_get(self, force_attachment=False):
         abort(404)
 
     def _convert(self):
@@ -743,45 +743,39 @@
         return _("Impossible to convert the data to the contenttype: %(contenttype)s",
                  contenttype=request.values.get('contenttype'))
 
-    def do_get(self):
+    def do_get(self, force_attachment=False):
         hash = self.rev.get(HASH_ALGORITHM)
         if is_resource_modified(request.environ, hash): # use hash as etag
-            return self._do_get_modified(hash)
+            return self._do_get_modified(hash, force_attachment=force_attachment)
         else:
             return Response(status=304)
 
-    def _do_get_modified(self, hash):
+    def _do_get_modified(self, hash, force_attachment=False):
         member = request.values.get('member')
-        return self._do_get(hash, member)
+        return self._do_get(hash, member, force_attachment=force_attachment)
 
-    def _do_get(self, hash, member=None):
-        filename = None
+    def _do_get(self, hash, member=None, force_attachment=False):
         if member: # content = file contained within a archive item revision
             path, filename = os.path.split(member)
             mt = MimeType(filename=filename)
-            content_disposition = mt.content_disposition(app.cfg)
-            content_type = mt.content_type()
             content_length = None
             file_to_send = self.get_member(member)
         else: # content = item revision
             rev = self.rev
+            filename = rev.item.name
             try:
                 mimestr = rev[CONTENTTYPE]
             except KeyError:
-                mt = MimeType(filename=rev.item.name)
+                mt = MimeType(filename=filename)
             else:
                 mt = MimeType(mimestr=mimestr)
-            content_disposition = mt.content_disposition(app.cfg)
-            content_type = mt.content_type()
             content_length = rev[SIZE]
             file_to_send = rev
-
-        # TODO: handle content_disposition is not None
-        # Important: empty filename keeps flask from trying to autodetect filename,
-        # as this would not work for us, because our file's are not necessarily fs files.
+        content_type = mt.content_type()
+        as_attachment = force_attachment or mt.as_attachment(app.cfg)
         return send_file(file=file_to_send,
                          mimetype=content_type,
-                         as_attachment=False, attachment_filename=filename,
+                         as_attachment=as_attachment, attachment_filename=filename,
                          cache_timeout=10, # wiki data can change rapidly
                          add_etags=True, etag=hash, conditional=True)
 
@@ -865,8 +859,9 @@
 
 
 class ApplicationXTar(TarMixin, Application):
-    def feed_input_conv(self):
-        return self.rev
+    """
+    Tar items
+    """
 
 item_registry.register(ApplicationXTar._factory, Type('application/x-tar'))
 item_registry.register(ApplicationXTar._factory, Type('application/x-gtar'))
@@ -900,8 +895,9 @@
 
 
 class ApplicationZip(ZipMixin, Application):
-    def feed_input_conv(self):
-        return self.rev
+    """
+    Zip items
+    """
 
 item_registry.register(ApplicationZip._factory, Type('application/zip'))
 
@@ -999,7 +995,7 @@
         outfile.close()
         return content_type, data
 
-    def _do_get_modified(self, hash):
+    def _do_get_modified(self, hash, force_attachment=False):
         try:
             width = int(request.values.get('w'))
         except (TypeError, ValueError):
@@ -1033,7 +1029,7 @@
                 headers, data = c
             return Response(data, headers=headers)
         else:
-            return self._do_get(hash)
+            return self._do_get(hash, force_attachment=force_attachment)
 
     def _render_data_diff(self, oldrev, newrev):
         if PIL is None:
@@ -1051,7 +1047,7 @@
         c = app.cache.get(cid)
         if c is None:
             if PIL is None:
-                abort(404)
+                abort(404) # TODO render user friendly error image
 
             content_type = newrev[CONTENTTYPE]
             if content_type == 'image/jpeg':
@@ -1063,17 +1059,21 @@
             else:
                 raise ValueError("content_type %r not supported" % content_type)
 
-            oldimage = PILImage.open(oldrev)
-            newimage = PILImage.open(newrev)
-            oldimage.load()
-            newimage.load()
-            diffimage = PILdiff(newimage, oldimage)
-            outfile = StringIO()
-            diffimage.save(outfile, output_type)
-            data = outfile.getvalue()
-            outfile.close()
-            headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
-            app.cache.set(cid, (headers, data))
+            try:
+                oldimage = PILImage.open(oldrev)
+                newimage = PILImage.open(newrev)
+                oldimage.load()
+                newimage.load()
+                diffimage = PILdiff(newimage, oldimage)
+                outfile = StringIO()
+                diffimage.save(outfile, output_type)
+                data = outfile.getvalue()
+                outfile.close()
+                headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
+                app.cache.set(cid, (headers, data))
+            except (IOError, ValueError) as err:
+                logging.exception("error during PILdiff: %s", err.message)
+                abort(404) # TODO render user friendly error image
         else:
             # XXX TODO check ACL behaviour
             headers, data = c
@@ -1293,8 +1293,8 @@
 
         # We determine the different parameters for the reply
         mt = MimeType(mimestr='application/docbook+xml;charset=utf-8')
-        content_disposition = mt.content_disposition(app.cfg)
         content_type = mt.content_type()
+        as_attachment = mt.as_attachment(app.cfg)
         # After creation of the StringIO, we are at the end of the file
         # so position is the size the file.
         # and then we should move it back at the beginning of the file
@@ -1304,7 +1304,7 @@
         # as this would not work for us, because our file's are not necessarily fs files.
         return send_file(file=file_to_send,
                          mimetype=content_type,
-                         as_attachment=False, attachment_filename=None,
+                         as_attachment=as_attachment, attachment_filename=None,
                          cache_timeout=10, # wiki data can change rapidly
                          add_etags=False, etag=None, conditional=True)
 
@@ -1510,16 +1510,16 @@
 
     def modify(self):
         # called from modify UI/POST
-        file_upload = request.values.get('data')
+        png_upload = request.values.get('png_data')
+        svg_upload = request.values.get('filepath')
         filename = request.form['filename']
-        filecontent = file_upload.decode('base_64')
-        basepath, basename = os.path.split(filename)
-        basename, ext = os.path.splitext(basename)
+        png_content = png_upload.decode('base_64')
+        png_content = base64.urlsafe_b64decode(png_content.split(',')[1])
+        svg_content = svg_upload.decode('base_64')
         content_length = None
-
-        if ext == '.png':
-            filecontent = base64.urlsafe_b64decode(filecontent.split(',')[1])
-        self.put_member(filename, filecontent, content_length,
+        self.put_member("drawing.svg", svg_content, content_length,
+                        expected_members=set(['drawing.svg', 'drawing.png']))
+        self.put_member("drawing.png", png_content, content_length,
                         expected_members=set(['drawing.svg', 'drawing.png']))
 
     def do_modify(self, contenttype, template_name):
--- a/MoinMoin/script/maint/build_indexes.py	Fri Jul 01 22:31:53 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +0,0 @@
-1# Copyright: 2011 MoinMoin:MichaelMayorov
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-MoinMoin - Build indexes
-"""
-
-import os, shutil, datetime
-
-from flask import current_app as app
-from flask import g as flaskg
-from flaskext.script import Command, Option
-from whoosh.filedb.multiproc import MultiSegmentWriter
-from whoosh.index import create_in, exists_in
-
-from MoinMoin.search.indexing import WhooshIndex
-from MoinMoin.config import MTIME, NAME
-from MoinMoin.error import FatalError
-from MoinMoin.script.maint.update_indexes import UpdateIndexes
-from MoinMoin.storage.error import NoSuchItemError
-
-# Information about index and schema for latest and all revisions
-latest_indexname_schema = ("latest_revisions_index", "latest_revisions_schema")
-all_indexname_schema = ("all_revisions_index", "all_revisions_schema")
-both_indexnames_schemas = (latest_indexname_schema, all_indexname_schema)
-
-class RebuildIndexes(Command):
-    description = 'Build indexes'
-
-    option_list = (
-        Option('--for', required=True, dest='indexname', type=str, choices=("all-revs", "latest-revs", "both"), 
-            help='For what type of indexes we will use action'),
-        Option('--action', required=True, dest='action', type=str, choices=("build", "update", "clean", "move"), 
-            help='Action for given indexes:\n\
-                  build -- Build in index_dir_tmp\n\
-                  update -- Update in index_dir\n\
-                  clean -- Clean index_dir\n\
-                  move  -- Move index files from index_dir to index_dir_tmp'
-               ),
-        Option('--procs', '-p', required=False, dest='procs', type=int, default=None,
-            help='Number of processors the writer.'),
-        Option('--limitmb', '-l', required=False, dest='limitmb', type=int, default=10,
-            help='Maximum memory (in megabytes) each index-writer will use for the indexing pool.'),
-                  )
-
-    # We use 3 separated functions because want to avoid checks and increase speed
-    def run(self, indexname, action, procs, limitmb):
-        # Building in app.cfg.index_dir_tmp
-        def build_index(indexnames_schemas, path, clean):
-            if clean:
-                clean_indexes(indexnames_schemas, path, clean)
-            indexnames = [indexname for indexname, schema in indexnames_schemas]
-            with all_rev_index.writer() as all_rev_writer:
-                with latest_rev_index.writer() as latest_rev_writer:
-                    for item in backend.iter_items_noindex():
-                        for rev_no in item.list_revisions():
-                            if "all_revisions_index" in indexnames:
-                                revision = item.get_revision(rev_no)
-                                metadata = dict([(str(key), value)
-                                                 for key, value in revision.items()
-                                                 if key in all_rev_field_names])
-                                metadata[MTIME] = datetime.datetime.fromtimestamp(metadata[MTIME])
-                                metadata["rev_no"] = rev_no
-                                all_rev_writer.add_document(**metadata)
-                        # revision is now the latest revision of this item
-                        if "latest_revisions_index" in indexnames:
-                            revision = item.get_revision(rev_no)
-                            metadata = dict([(str(key), value)
-                                              for key, value in revision.items()
-                                              if key in latest_rev_field_names])
-                            metadata[MTIME] = datetime.datetime.fromtimestamp(metadata[MTIME])
-                            metadata["rev_no"] = rev_no
-                            latest_rev_writer.add_document(**metadata)
-
-        # Updating index in app.cfg.index_dir
-        def update_index(indexnames_schemas):
-            indexnames = [indexname for indexname, schema in indexnames_schemas]
-            create_documents = []
-            delete_documents = []
-            latest_documents = []
-            for item in backend.iter_items_noindex():
-                name = item.get_revision(0)[NAME]
-                index_rev_list = item_index_revs(all_rev_searcher, name)
-                backend_rev_list = item.list_revisions()
-                add_rev_nos = set(backend_rev_list) - set(index_rev_list)
-                if add_rev_nos:
-                    if "all_revisions_index" in indexnames:
-                        create_documents.append((name, add_rev_nos))
-                    if "latest_revisions_index" in indexnames:
-                        latest_documents.append((name, max(add_rev_nos))) # Add latest revision 
-                remove_rev_nos = set(index_rev_list) - set(backend_rev_list)
-                if "all_revisions_index" in indexnames and remove_rev_nos:
-                    delete_documents.append((name, remove_rev_nos))
-
-            if latest_documents:
-                with latest_rev_index.writer() as latest_rev_writer:
-                    for name, rev_no in latest_documents:
-                        try:
-                            storage_rev = backend.get_item(name).get_revision(rev_no)
-                            converted_rev = backend_to_index(storage_rev, rev_no, latest_rev_field_names)
-                            found = latest_rev_searcher.document(name=name.lower())
-                            if not found:
-                                latest_rev_writer.add_document(**converted_rev)
-                            # Checking what last revision is the latest
-                            elif found["rev_no"] < converted_rev["rev_no"]:
-                                doc_number = latest_rev_searcher.document_number(name=name.lower())
-                                latest_rev_writer.delete_document(doc_number)
-                                latest_rev_writer.add_document(**converted_rev)
-                        except NoSuchItemError:
-                            # Item has been trashed, removing all stuff
-                            doc_number = latest_rev_searcher.document_number(name=name.lower())
-                            if doc_number:
-                                latest_rev_writer.delete_document(doc_number)
-
-            if delete_documents:
-                with all_rev_index.writer() as all_rev_writer:
-                    for name, rev_nos in delete_documents:
-                        # If document with this name wasn't found in indexes
-                        # then we just ignore it
-                        if not rev_nos:
-                            continue
-                        for rev_no in rev_nos:
-                            doc_number = all_rev_searcher.document_number(rev_no=rev_no,
-                                                                          name=name.lower()
-                                                                         )
-                            if doc_number:
-                                all_rev_writer.delete_document(doc_number)
-
-            if create_documents:
-                with all_rev_index.writer() as all_rev_writer:
-                    for name, rev_nos in create_documents:
-                        try:
-                            for rev_no in rev_nos:
-                                    storage_rev = backend.get_item(name).get_revision(rev_no)
-                                    converted_rev = backend_to_index(storage_rev, rev_no, all_rev_field_names)
-                                    all_rev_writer.add_document(**converted_rev)
-                        except NoSuchItemError:
-                            # Item has been trashed, removing all stuff
-                            rev_nos = item_index_revs(all_rev_searcher, name.lower())
-                            for rev_no in rev_nos:
-                                doc_number = all_rev_searcher.document_number(rev_no=rev_no,
-                                                                              name=name.lower()
-                                                                             )
-                                if doc_number:
-                                    all_rev_writer.delete_document(doc_number)
-
-        def clean_index(indexnames_schemas, path):
-            for indexname, schema in indexnames_schemas:
-                index_object.create_index(indexdir=path,
-                                          indexname=indexname,
-                                          schema=schema
-                                         )
-
-        def item_index_revs(searcher, name):
-            name = name.lower()
-            revs_found = searcher.documents(name=name)
-            return [rev["rev_no"] for rev in revs_found]
-
-        # Convert fields from backend format to whoosh schema
-        def backend_to_index(backend_rev, rev_no, schema_fields):
-            metadata = dict([(str(key), value)
-                              for key, value in backend_rev.items()
-                              if key in schema_fields])
-            metadata[MTIME] = datetime.datetime.fromtimestamp(metadata[MTIME])
-            metadata["rev_no"] = rev_no
-            return metadata
-
-        def do_action(action, indexnames_schemas):
-            if action == "build":
-                build_index(indexnames_schemas, app.cfg.index_dir_tmp, clean=False)
-            elif action == "update":
-                update_index(indexnames_schemas)
-            elif action == "clean":
-                clean_index(indexnames_schemas, app.cfg.index_dir)
-            elif action == "move":
-                for indexname, schema in indexnames_schemas:
-                    if not exists_in(app.cfg.index_dir_tmp, indexname=indexname):
-                        raise FatalError(u"Can't find %s in %s" % (indexname, app.cfg.index_dir_tmp))
-                    for filename in latest_rev_index.storage.list():
-                        if indexname in filename:
-                            try:
-                                os.remove(os.path.join(app.cfg.index_dir, filename))
-                            except:
-                                pass
-                            shutil.move(os.path.join(app.cfg.index_dir_tmp, filename), app.cfg.index_dir)
-
-        backend = flaskg.unprotected_storage = app.unprotected_storage
-        index_object = WhooshIndex(index_dir=app.cfg.index_dir_tmp)
-        if os.path.samefile(app.cfg.index_dir_tmp, app.cfg.index_dir):
-            raise FatalError(u"app.cfg.index_dir and app.cfg.tmp_index_dir are equal")
-
-        latest_rev_index = index_object.latest_revisions_index
-        all_rev_index = index_object.all_revisions_index
-
-        latest_rev_field_names = latest_rev_index.schema.names()
-        all_rev_field_names = all_rev_index.schema.names()
-
-        latest_rev_searcher = latest_rev_index.searcher()
-        all_rev_searcher = all_rev_index.searcher()
-
-        if indexname == "both":
-            do_action(action, both_indexnames_schemas)
-        elif indexname == "all-revs":
-            do_action(action, (all_indexname_schema,))
-        elif indexname == "latest-revs":
-            do_action(action, (latest_indexname_schema,))
--- a/MoinMoin/storage/backends/sqla.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/storage/backends/sqla.py	Mon Jul 04 12:35:19 2011 +0000
@@ -600,6 +600,7 @@
         self._revno = revno
         self.setup(item._backend)
         self._item = item
+        self._item_id = item.id
 
     def __del__(self):
         # XXX XXX XXX DO NOT RELY ON THIS
--- a/MoinMoin/templates/base.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/base.html	Mon Jul 04 12:35:19 2011 +0000
@@ -30,7 +30,7 @@
     {% endblock %}
 
     {% block head_links %}
-    <link rel="shortcut icon" href="{{ url_for('.static', filename='logos/favicon.ico') }}" />
+    <link rel="shortcut icon" href="{{ url_for('static', filename='logos/favicon.ico') }}" />
     <link rel="archives" href="{{ url_for('frontend.history', item_name=item_name) }}" />
     {% set parent_item = theme_supp.parent_item(item_name) %}
     {%- if parent_item -%}
@@ -49,7 +49,16 @@
     {% block head_scripts %}
     <script src="{{ url_for('serve.files', name='jquery', filename='jquery.min.js') }}"></script>
     <script src="{{ url_for('serve.files', name='svgweb', filename='svg.js') }}"></script>
-    <script src="{{ url_for('.static', filename='js/common.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/common.js') }}"></script>
+    <script type="text/x-mathjax-config">
+  MathJax.Hub.Config({
+    extensions: ["tex2jax.js"],
+    jax: ["input/TeX","output/HTML-CSS"],
+    tex2jax: {inlineMath: [["$","$"],["\\(","\\)"]]}
+  });
+</script>
+<script src="{{ url_for('serve.files', name='mathjax', filename='MathJax.js') }}"> </script>
+ 
     {{ scripts }}
     <!--[if lt IE 9]>
         {# TODO: use a local copy later #}
--- a/MoinMoin/templates/global_index.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/global_index.html	Mon Jul 04 12:35:19 2011 +0000
@@ -6,7 +6,7 @@
         <ul>
         {% for fullname, relname, contenttype in index %}
             <li>
-                <a href="{{ url_for('show_item', item_name=fullname) }}"
+                <a href="{{ url_for('.show_item', item_name=fullname) }}"
                    class="{{ contenttype|contenttype_to_class }}">
                     {{ relname }}
                 </a>
--- a/MoinMoin/templates/history.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/history.html	Mon Jul 04 12:35:19 2011 +0000
@@ -34,7 +34,7 @@
                 <td class="moin-wordbreak">{{ rev.comment }}</td>
                 <td><a href="{{ url_for('frontend.show_item', item_name=rev.item.name, rev=rev.revno) }}">{{ _('show') }}</a></td>
                 <td><a href="{{ url_for('frontend.show_item_meta', item_name=rev.item.name, rev=rev.revno) }}">{{ _('meta') }}</a></td>
-                <td><a href="{{ url_for('frontend.get_item', item_name=rev.item.name, rev=rev.revno) }}">{{ _('download') }}</a></td>
+                <td><a href="{{ url_for('frontend.download_item', item_name=rev.item.name, rev=rev.revno) }}">{{ _('download') }}</a></td>
                 <td><a href="{{ url_for('frontend.highlight_item', item_name=rev.item.name, rev=rev.revno) }}">{{ _('highlight') }}</a></td>
                 <td><a href="{{ url_for('frontend.revert_item', item_name=rev.item.name, rev=rev.revno) }}">{{ _('revert') }}</a></td>
                 <td><a href="{{ url_for('frontend.destroy_item', item_name=rev.item.name, rev=rev.revno) }}">{{ _('destroy') }}</a></td>
--- a/MoinMoin/templates/index.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/index.html	Mon Jul 04 12:35:19 2011 +0000
@@ -6,7 +6,7 @@
         <ul>
         {% for fullname, relname, contenttype in index %}
             <li>
-                <a href="{{ url_for('show_item', item_name=fullname) }}"
+                <a href="{{ url_for('.show_item', item_name=fullname) }}"
                    class="{{ contenttype|contenttype_to_class }}">
                     {{ relname }}
                 </a>
--- a/MoinMoin/templates/index2.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/index2.html	Mon Jul 04 12:35:19 2011 +0000
@@ -2,13 +2,13 @@
 
 {% block theme_stylesheets %}
 {{ super() }}
-<link rel="stylesheet" href="{{ url_for('serve.files', name='fileupload', filename='jquery.fileupload-ui.css') }}">
+<link rel="stylesheet" href="{{ url_for('serve.files', name='jquery_file_upload', filename='jquery.fileupload-ui.css') }}">
 {% endblock %}
 
 {% block content %}
     <h1>{{ _("Index of subitems of '%(item_name)s'", item_name=item_name) }}</h1>
 <div id="file_upload">
-    <form action="{{ url_for('jfu_server', item_name=item_name) }}" method="POST" enctype="multipart/form-data">
+    <form action="{{ url_for('.jfu_server', item_name=item_name) }}" method="POST" enctype="multipart/form-data">
         <input type="file" name="data_file" multiple>
         <button type="submit">Upload</button>
         <div class="file_upload_label">Upload files</div>
@@ -31,7 +31,7 @@
         </tr>
     </table>
 </div>
-<script src="{{ url_for('serve.files', name='fileupload', filename='jquery.fileupload.js') }}"></script>
-<script src="{{ url_for('serve.files', name='fileupload', filename='jquery.fileupload-ui.js') }}"></script>
-<script src="{{ url_for('.static', filename='js/jfu.js') }}"></script>
+<script src="{{ url_for('serve.files', name='jquery_file_upload', filename='jquery.fileupload.js') }}"></script>
+<script src="{{ url_for('serve.files', name='jquery_file_upload', filename='jquery.fileupload-ui.js') }}"></script>
+<script src="{{ url_for('static', filename='js/jfu.js') }}"></script>
 {% endblock %}
--- a/MoinMoin/templates/itemviews.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/itemviews.html	Mon Jul 04 12:35:19 2011 +0000
@@ -3,8 +3,8 @@
     {% for endpoint, label, title, check_exists in cfg.item_views if not endpoint in cfg.endpoints_excluded %}
         {% if (not check_exists or check_exists and exists) and endpoint in [
                'frontend.show_item', 'frontend.index', 'frontend.index2',
-               'frontend.highlight_item', 'frontend.show_item_meta', 'frontend.get_item',
-               'frontend.get_item', 'frontend.history', 'frontend.backrefs', 'frontend.sitemap',
+               'frontend.highlight_item', 'frontend.show_item_meta', 'frontend.download_item',
+               'frontend.history', 'frontend.backrefs', 'frontend.sitemap',
                'frontend.similar_names',
                'frontend.modify_item',
                'frontend.copy_item', 'frontend.rename_item', 'frontend.delete_item', 'frontend.destroy_item',
--- a/MoinMoin/templates/modify_svg-edit.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/modify_svg-edit.html	Mon Jul 04 12:35:19 2011 +0000
@@ -1,7 +1,7 @@
 {% extends "modify_applet.html" %}
 {% block data_editor %}
 <p>
-<object data="{{ url_for('serve.files', name='svgedit', filename='svg-editor.html') }}?paramurl={{ url_for('frontend.get_item', item_name=item_name, member='drawing.svg') }}&amp;savepath={{ url_for('frontend.modify_item', item_name=item_name) }}&amp;viewpath={{ url_for('frontend.show_item', item_name=item_name) }}" width="100%" height="600">
+<object data="{{ url_for('serve.files', name='svgedit', filename='editor/svg-editor.html') }}?paramurl={{ url_for('frontend.get_item', item_name=item_name, member='drawing.svg') }}" width="100%" height="600">
 </object>
 </p>
 <br />
--- a/MoinMoin/templates/modify_twikidraw.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/modify_twikidraw.html	Mon Jul 04 12:35:19 2011 +0000
@@ -2,7 +2,7 @@
 {% block data_editor %}
 <p>
 <applet code="CH.ifa.draw.twiki.TWikiDraw.class"
-        archive="{{ url_for('serve.files', name='twikidraw', filename='twikidraw.jar') }}"
+        archive="{{ url_for('serve.files', name='twikidraw_moin', filename='twikidraw_moin.jar') }}"
         width="800" height="620">
         <param name="drawpath" value="{{ url_for('frontend.get_item', item_name=item_name, member='drawing.draw') }}" />
         <param name="pngpath"  value="{{ url_for('frontend.get_item', item_name=item_name, member='drawing.png') }}" />
--- a/MoinMoin/templates/snippets.html	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/templates/snippets.html	Mon Jul 04 12:35:19 2011 +0000
@@ -2,7 +2,7 @@
 
 {# Logo in the header #}
 {% macro logo() -%}
-<img src="{{ url_for('.static', filename='logos/moinmoin.png') }}" id="moin-img-logo" alt="Logo">
+<img src="{{ url_for('static', filename='logos/moinmoin.png') }}" id="moin-img-logo" alt="Logo">
 {%- endmacro %}
 
 {# Additional HTML tags inside <head> #}
@@ -37,10 +37,10 @@
 {# Image links in the footer #}
 {% macro creditlogos(start='<ul id="moin-creditlogos"><li>'|safe, end='</li></ul>'|safe, sep='</li><li>'|safe) %}
 {{ start }}
-{{ creditlogo('http://moinmo.in/', url_for('.static', filename='logos/moinmoin_powered.png'),
+{{ creditlogo('http://moinmo.in/', url_for('static', filename='logos/moinmoin_powered.png'),
    'MoinMoin powered', 'This site uses the MoinMoin Wiki software.') }}
 {{ sep }}
-{{ creditlogo('http://moinmo.in/Python', url_for('.static', filename='logos/python_powered.png'),
+{{ creditlogo('http://moinmo.in/Python', url_for('static', filename='logos/python_powered.png'),
    'Python powered', 'MoinMoin is written in Python.') }}
 {{ end }}
 {% endmacro %}
--- a/MoinMoin/util/mimetype.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/util/mimetype.py	Mon Jul 04 12:35:19 2011 +0000
@@ -163,21 +163,12 @@
         """ return a string major/minor only, no params """
         return "%s/%s" % (self.major, self.minor)
 
-    def content_disposition(self, cfg):
+    def as_attachment(self, cfg):
         # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
         # we just let the user store them to disk ('attachment').
         # For safe files, we directly show them inline (this also works better for IE).
         mime_type = self.mime_type()
-        dangerous = mime_type in cfg.mimetypes_xss_protect
-        content_disposition = dangerous and 'attachment' or 'inline'
-        filename = self.filename
-        if filename is not None:
-            # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
-            # There is no solution that is compatible to IE except stripping non-ascii chars
-            if isinstance(filename, unicode):
-                filename = filename.encode(config.charset)
-            content_disposition += '; filename="%s"' % filename
-        return content_disposition
+        return mime_type in cfg.mimetypes_xss_protect
 
     def module_name(self):
         """ convert this mimetype to a string useable as python module name,
--- a/MoinMoin/wikiutil.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/MoinMoin/wikiutil.py	Mon Jul 04 12:35:19 2011 +0000
@@ -320,15 +320,12 @@
             pass
 
 
-def file_headers(filename=None,
-                 content_type=None, content_length=None, content_disposition=None):
+def file_headers(filename=None, content_type=None, content_length=None):
         """
         Compute http headers for sending a file
 
-        :param filename: filename for content-disposition header and for autodetecting
-                         content_type (unicode, default: None)
+        :param filename: filename for autodetecting content_type (unicode, default: None)
         :param content_type: content-type header value (str, default: autodetect from filename)
-        :param content_disposition: type for content-disposition header (str, default: None)
         :param content_length: for content-length header (int, default:None)
         """
         if filename:
@@ -349,9 +346,5 @@
         headers = [('Content-Type', content_type)]
         if content_length is not None:
             headers.append(('Content-Length', str(content_length)))
-        if content_disposition is None and mt is not None:
-            content_disposition = mt.content_disposition(app.cfg)
-        if content_disposition:
-            headers.append(('Content-Disposition', content_disposition))
         return headers
 
--- a/quickinstall	Fri Jul 01 22:31:53 2011 +0000
+++ b/quickinstall	Mon Jul 04 12:35:19 2011 +0000
@@ -26,36 +26,16 @@
 # wikiconfig.py expects them. should be replaced by packaging.
 # we do this FIRST, so that breakage with pip install is better visible.
 
-download $DIR/ckeditor.tgz http://download.cksource.com/CKEditor/CKEditor/CKEditor%203.5/ckeditor_3.5.tar.gz
-tar xz -C $DIR/ -f $DIR/ckeditor.tgz
-
-download $DIR/twd.tgz http://static.moinmo.in/files/packages/TWikiDrawPlugin-moin.tar.gz
-tar xz -C $DIR/ -f $DIR/twd.tgz
-
 download $DIR/svgedit.tgz http://static.moinmo.in/files/packages/svg-edit.tar.gz
 tar xz -C $DIR/ -f $DIR/svgedit.tgz
 
-mkdir $DIR/jquery.fu
-download $DIR/jquery.fu/jquery.fileupload.js https://www.github.com/blueimp/jQuery-File-Upload/raw/master/jquery.fileupload.js
-download $DIR/jquery.fu/jquery.fileupload-ui.js https://www.github.com/blueimp/jQuery-File-Upload/raw/master/jquery.fileupload-ui.js
-download $DIR/jquery.fu/jquery.fileupload-ui.css https://www.github.com/blueimp/jQuery-File-Upload/raw/master/jquery.fileupload-ui.css
-download $DIR/jquery.fu/pbar-ani.gif https://www.github.com/blueimp/jQuery-File-Upload/raw/master/pbar-ani.gif
-
-mkdir $DIR/jquery
-download $DIR/jquery/jquery.min.js http://code.jquery.com/jquery-1.4.4.min.js
-
-download $DIR/svgweb.zip http://svgweb.googlecode.com/files/svgweb-2010-08-10-Owlephant-1.zip
-unzip -q -o -d $DIR/ $DIR/svgweb.zip
-
-AWDDIR='AnyWikiDraw 0.14'
-download $DIR/awd.zip 'http://downloads.sourceforge.net/project/anywikidraw/anywikidraw/anywikidraw-0.14/anywikidraw-0.14.zip?use_mirror=ignum'
-unzip -q -o -d $DIR/ $DIR/awd.zip
-cd $DIR/
-ln -s "$AWDDIR" AnyWikiDraw
-cd ..
+download $DIR/mathjax.tgz http://static.moinmo.in/files/packages/mathjax.tar.gz
+tar xz -C $DIR/ -f $DIR/mathjax.tgz
 
 # first install babel, moin's setup.py will emit a warning if it is not there
 pip install babel
+# first install XStatic, XStatic-jQuery's setup.py will fail if it is not there
+pip install XStatic
 
 # "install" moin2 from repo to the env, this will also install required python
 # packages from pypi. we do this LAST, so that breakage is better visible.
--- a/quickinstall.bat	Fri Jul 01 22:31:53 2011 +0000
+++ b/quickinstall.bat	Mon Jul 04 12:35:19 2011 +0000
@@ -16,33 +16,19 @@
 echo Getting some 3rd party stuff and unpack them into env/, where the default
 echo wikiconfig.py expects them (should be replaced by packaging) ...
 
-wget -nc "http://download.cksource.com/CKEditor/CKEditor/CKEditor 3.5/ckeditor_3.5.tar.gz" -Penv/
-7za x env/ckeditor_3.5.tar.gz -y -oenv\
-7za x env/ckeditor_3.5.tar -y -oenv\
-
-wget -nc http://static.moinmo.in/files/packages/TWikiDrawPlugin-moin.tar.gz -Penv/
-7za x env/TWikiDrawPlugin-moin.tar.gz -y -oenv\
-7za x env/TWikiDrawPlugin-moin.tar -y -oenv\
-
 wget -nc http://static.moinmo.in/files/packages/svg-edit.tar.gz -Penv/
 7za x env/svg-edit.tar.gz -y -oenv\
 7za x env/svg-edit.tar -y -oenv\
 
-mkdir env\jquery
-wget -nc http://code.jquery.com/jquery-1.4.4.min.js -Oenv/jquery/jquery.min.js
-
-wget -nc http://svgweb.googlecode.com/files/svgweb-2010-08-10-Owlephant-1.zip -Penv/
-7za x env/svgweb-2010-08-10-Owlephant-1.zip -y -oenv\
-
-wget -nc http://downloads.sourceforge.net/project/anywikidraw/anywikidraw/anywikidraw-0.14/anywikidraw-0.14.zip?use_mirror=ignum -Penv/
-7za x env/anywikidraw-0.14.zip -y -oenv\
-xcopy "env\AnyWikiDraw 0.14" env\AnyWikiDraw\ /Y /E /H
 
 del /q env\*.tar
 
 echo Installing babel first ...
 pip install babel
 
+echo Installing XStatic first ...
+pip install XStatic
+
 echo Installing all required python packages from pypi ...
 pip install -e .
 
--- a/setup.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/setup.py	Mon Jul 04 12:35:19 2011 +0000
@@ -76,7 +76,7 @@
     install_requires=[
         'blinker>=1.1', # event signalling (e.g. for change notification trigger)
         'docutils>=0.6', # reST markup processing
-        'Flask>=0.6', # micro framework
+        'Flask>=0.7.1', # micro framework
         'Flask-Babel>=0.6', # i18n support
         'Flask-Cache>=0.3.2', # caching support
         'Flask-Script>=0.3', # scripting support
@@ -90,7 +90,12 @@
         'Werkzeug==0.6.2', # use this if 0.7dev fails
         'py==1.3.4', # py.test 1.3.4 is needed by unit tests
         'sphinx', # needed to build the docs
-        'whoosh>=1.8.3', # needed for indexed search
+        'XStatic-CKEditor',
+        'XStatic-jQuery',
+        'XStatic-jQuery-File-Upload',
+        'XStatic-svgweb',
+        'XStatic-TWikiDraw-moin',
+        'XStatic-AnyWikiDraw',
     ],
     # optional features and their list of requirements
     extras_require = {
--- a/wikiconfig.py	Fri Jul 01 22:31:53 2011 +0000
+++ b/wikiconfig.py	Mon Jul 04 12:35:19 2011 +0000
@@ -56,15 +56,36 @@
     serve_files = dict(
         docs = os.path.join(wikiconfig_dir, 'docs', '_build', 'html'),
         # see "quickinstall" script about how to get those files there
-        ckeditor = os.path.join(wikiconfig_dir, env_dir, 'ckeditor'),
-        jquery = os.path.join(wikiconfig_dir, env_dir, 'jquery'),
-        svgweb = os.path.join(wikiconfig_dir, env_dir, 'svgweb', 'src'),
-        anywikidraw = os.path.join(wikiconfig_dir, env_dir, 'AnyWikiDraw', 'anywikidraw', 'moinmoin'),
-        twikidraw = os.path.join(wikiconfig_dir, env_dir, 'TWikiDrawPlugin'),
         svgedit = os.path.join(wikiconfig_dir, env_dir, 'svg-edit'),
-        fileupload = os.path.join(wikiconfig_dir, env_dir, 'jquery.fu'),
+        mathjax = os.path.join(wikiconfig_dir, env_dir, 'MathJax'),
     )
 
+    # we slowly migrate all stuff from above (old) method, to xstatic (new) method,
+    # see https://bitbucket.org/thomaswaldmann/xstatic for details:
+    from xstatic.pkg.jquery import JQuery
+    j = JQuery(root_url='/static', provider='local', protocol='http')
+    serve_files.update([(j.name, j.get_mapping()[1])])
+
+    from xstatic.pkg.jquery_file_upload import JQueryFileUpload
+    jfu = JQueryFileUpload(root_url='/static', provider='local', protocol='http')
+    serve_files.update([(jfu.name, jfu.get_mapping()[1])])
+
+    from xstatic.pkg.svgweb import SVGWeb
+    sw = SVGWeb(root_url='/static', provider='local', protocol='http')
+    serve_files.update([(sw.name, sw.get_mapping()[1])])
+
+    from xstatic.pkg.ckeditor import CKEditor
+    cke = CKEditor(root_url='/static', provider='local', protocol='http')
+    serve_files.update([(cke.name, cke.get_mapping()[1])])
+
+    from xstatic.pkg.twikidraw_moin import TWikiDraw
+    twd = TWikiDraw(root_url='/static', provider='local', protocol='http')
+    serve_files.update([(twd.name, twd.get_mapping()[1])])
+
+    from xstatic.pkg.anywikidraw import AnyWikiDraw
+    awd = AnyWikiDraw(root_url='/static', provider='local', protocol='http')
+    serve_files.update([(awd.name, awd.get_mapping()[1])])
+
     # ^^^ DON'T TOUCH THIS EXCEPT IF YOU KNOW WHAT YOU DO ^^^
 
     #item_root = u'Home' # change to some better value