Commits

Thomas Waldmann committed 5036a72 Merge

merged default into gae branch

  • Participants
  • Parent commits 350376d, 269cd70
  • Branches gae

Comments (0)

Files changed (36)

File MoinMoin/__init__.py

 
 import sys
 if sys.hexversion < 0x2060000:
-    sys.exit("{0} requires Python 2.6 or greater.\n".format(project))
+    sys.exit("%s requires Python 2.6 or greater.\n" % project)
 
 
 from MoinMoin.util.version import Version

File MoinMoin/apps/frontend/views.py

 from MoinMoin.items.content import content_registry
 from MoinMoin import config, user, util
 from MoinMoin.constants.keys import *
-from MoinMoin.constants.itemtypes import ITEMTYPES
 from MoinMoin.util import crypto
 from MoinMoin.util.interwiki import url_for_item
 from MoinMoin.search import SearchForm, ValidSearch
 
 
 class UserSettingsOptionsForm(Form):
-    # TODO: if the checkbox in the form is checked, we get key: u'1' in the
-    # form data and all is fine. if it is not checked, the key is not present
-    # in the form data and flatland assigns None to the attribute (not False).
-    # If moin detects the None, it thinks this has not been set and uses its
-    # builtin defaults (for some True, for some others False). Makes
-    # edit_on_doubleclick malfunctioning (because its default is True).
     name = 'usersettings_options'
     mailto_author = Checkbox.using(label=L_('Publish my email (not my wiki homepage) in author info'))
     edit_on_doubleclick = Checkbox.using(label=L_('Open editor on double click'))

File MoinMoin/conftest.py

 from MoinMoin.app import create_app_ext, destroy_app, before_wiki, teardown_wiki
 from MoinMoin._tests import wikiconfig
 from MoinMoin.storage import create_simple_mapping
-from flask import g as flaskg
 
 
 def init_test_app(given_config):

File MoinMoin/constants/itemtypes.py

 MoinMoin - itemtype related constants
 """
 
-
-from __future__ import absolute_import, division
-
-from collections import namedtuple
-
-from MoinMoin.i18n import L_
-
-
-ItemtypeSpec = namedtuple('ItemtypeSpec', 'itemtype display_name description')
-
-ITEMTYPE_DEFAULT = u'default'
+ITEMTYPE_NONEXISTENT = u'nonexistent'
+ITEMTYPE_USERPROFILE = u'userprofile'
+ITEMTYPE_DEFAULT = u'default'  # == wiki-like
 ITEMTYPE_TICKET = u'ticket'
 ITEMTYPE_BLOG = u'blog'
 ITEMTYPE_BLOGENTRY = u'blogentry'
-
-# TODO Perhaps construct this list from the item_registry instead of having it
-# as a constant, which is more extensible (we can have itemtype plugins in
-# future and plugged-in itemtypes will be included too). Two more fields (ie.
-# display name and description) are needed in the registry then to support the
-# automatic construction.
-ITEMTYPES = [
-    ItemtypeSpec(ITEMTYPE_DEFAULT, L_('Default'), L_('Wiki item')),
-    ItemtypeSpec(ITEMTYPE_TICKET, L_('Ticket'), L_('Ticket item')),
-    ItemtypeSpec(ITEMTYPE_BLOG, L_('Blog'), L_('Blog item')),
-    ItemtypeSpec(ITEMTYPE_BLOGENTRY, L_('Blog entry'), L_('Blog entry item')),
-]

File MoinMoin/constants/keys.py

 USEROBJ_ATTRS = [
     # User objects proxy these attributes of the UserProfile objects:
     NAME, DISABLED, ITEMID, ALIASNAME, ENC_PASSWORD, EMAIL, OPENID,
-    MAILTO_AUTHOR, SHOW_COMMENTS, RESULTS_PER_PAGE, EDIT_ON_DOUBLECLICK,
+    MAILTO_AUTHOR, SHOW_COMMENTS, RESULTS_PER_PAGE, EDIT_ON_DOUBLECLICK, SCROLL_PAGE_AFTER_EDIT,
     EDIT_ROWS, THEME_NAME, LOCALE, TIMEZONE, SUBSCRIBED_ITEMS, QUICKLINKS,
     CSS_URL,
 ]

File MoinMoin/converter/__init__.py

 
         def __lt__(self, other):
             if isinstance(other, self.__class__):
-                if self.priority != other.priority:
-                    return self.priority < other.priority
                 if self.type_output != other.type_output:
                     return other.type_output.issupertype(self.type_output)
                 if self.type_input != other.type_input:
                     return other.type_input.issupertype(self.type_input)
+                if self.priority != other.priority:
+                    return self.priority < other.priority
                 return False
             return NotImplemented
 

File MoinMoin/converter/_tests/test_include.py

 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}')
         rendered = Item.create(u'page1').content._render_data()
-        assert '<img alt="logo" class="moin-transclusion"' in rendered
+        assert '<div class="moin-transclusion" data-href="/logo"><img alt="logo"' in rendered
 
         # <p /> is not valid html5; should be <p></p>. to be valid.  Even better, there should be no empty p's.
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}{{logo}}')

File MoinMoin/converter/everything.py

 
 from . import default_registry
 from MoinMoin.util.mime import Type, type_moin_document
-default_registry.register(Converter._factory, Type('application/octet-stream'), type_moin_document,
-                          default_registry.PRIORITY_MIDDLE + 3)
-default_registry.register(Converter._factory, Type(type=None, subtype=None), type_moin_document,
-                          default_registry.PRIORITY_MIDDLE + 3)
+default_registry.register(Converter._factory, Type('application/octet-stream'), type_moin_document)
+default_registry.register(Converter._factory, Type(type=None, subtype=None), type_moin_document)

File MoinMoin/converter/html_out.py

     visit_style = Attribute('style')
     visit_title = Attribute('title')
     visit_id = Attribute('id')
+    visit_type = Attribute('type') # IE8 needs <object... type="image/svg+xml" ...> to display svg images
 
     def __init__(self, element):
         self.element = element
             return "object"
 
     def visit_moinpage_object(self, elem):
+        # TODO: maybe IE8 would display transcluded external pages if we could do <object... type="text/html" ...>
         href = elem.get(xlink.href, None)
         attrib = {}
         mimetype = Type(_type=elem.get(moin_page.type_, 'application/x-nonexistent'))
                 attrib[html.controls] = 'controls'
             new_elem = self.new_copy(getattr(html, obj_type), elem, attrib)
 
-        return mark_item_as_transclusion(new_elem, href)
+        if obj_type == "object" and href.scheme:
+            # items similar to {{http://moinmo.in}} are marked here, other objects are marked in include.py
+            return mark_item_as_transclusion(new_elem, href)
+        return new_elem
 
     def visit_moinpage_p(self, elem):
         return self.new_copy(html.p, elem)

File MoinMoin/converter/include.py

File contents unchanged.

File MoinMoin/converter/pygments_in.py

 
     from . import default_registry
     from MoinMoin.util.mime import Type, type_moin_document
-    # Pygments type detection is rather expensive, therefore we want to register
-    # after all normal parsers but before the compatibility parsers and wildcard
-    default_registry.register(Converter._factory, Type(type='text'), type_moin_document,
-                              default_registry.PRIORITY_MIDDLE + 1)
-    default_registry.register(Converter._factory, Type('x-moin/format'), type_moin_document,
-                              default_registry.PRIORITY_MIDDLE + 1)
+    default_registry.register(Converter._factory, Type(type='text'), type_moin_document)
+    default_registry.register(Converter._factory, Type('x-moin/format'), type_moin_document)
 
 else:
     # we have no Pygments, minimal Converter replacement, so highlight view does not crash

File MoinMoin/converter/text_in.py

 
 from . import default_registry
 from MoinMoin.util.mime import Type, type_moin_document
+# Assign a lower priority (= bigger number) so that it is tried after pygments_in
 default_registry.register(Converter._factory, Type(type='text'), type_moin_document,
-                          default_registry.PRIORITY_MIDDLE + 2)
+                          default_registry.PRIORITY_MIDDLE + 1)

File MoinMoin/items/__init__.py

 import json
 from StringIO import StringIO
 from collections import namedtuple
+from operator import attrgetter
 
 from flask import current_app as app
 from flask import g as flaskg
     HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
     )
 from MoinMoin.constants.contenttypes import charset
-from MoinMoin.constants.itemtypes import ITEMTYPES
 
 from .content import content_registry, Content, NonExistentContent, Draw
 
 
 
 class RegistryItem(RegistryBase):
-    class Entry(namedtuple('Entry', 'factory itemtype priority')):
+    class Entry(namedtuple('Entry', 'factory itemtype display_name description order')):
         def __call__(self, itemtype, *args, **kw):
             if self.itemtype == itemtype:
                 return self.factory(*args, **kw)
 
         def __lt__(self, other):
             if isinstance(other, self.__class__):
-                if self.priority != other.priority:
-                    return self.priority < other.priority
                 return self.itemtype < other.itemtype
             return NotImplemented
 
-    def register(self, factory, itemtype, priority=RegistryBase.PRIORITY_MIDDLE):
+    def __init__(self):
+        super(RegistryItem, self).__init__()
+        self.shown_entries = []
+
+    def register(self, e, shown):
         """
         Register a factory
 
         :param factory: Factory to register. Callable, must return an object.
         """
-        return self._register(self.Entry(factory, itemtype, priority))
+        if shown:
+            self.shown_entries.append(e)
+            self.shown_entries.sort(key=attrgetter('order'))
+        return self._register(e)
 
 
 item_registry = RegistryItem()
 
 def register(cls):
-    item_registry.register(cls._factory, cls.itemtype)
+    item_registry.register(RegistryItem.Entry(cls._factory, cls.itemtype, cls.display_name, cls.description, cls.order), cls.shown)
     return cls
 
 
 
 class Item(object):
     """ Highlevel (not storage) Item, wraps around a storage Revision"""
+    # placeholder values for registry entry properties
+    itemtype = ''
+    display_name = u''
+    description = u''
+    shown = True
+    order = 0
+
     @classmethod
     def _factory(cls, *args, **kw):
         return cls(*args, **kw)
     A "conventional" wiki item.
     """
     itemtype = u'default'
+    display_name = L_('Default')
+    description = L_('Wiki item')
+    order = -10
 
     def _do_modify_show_templates(self):
         # call this if the item is still empty
     itemtype implementation of userprofile.
     """
     itemtype = u'userprofile'
+    display_name = L_('User profile')
+    description = L_('User profile item (not implemented yet!)')
 
 
 @register
     undetermined itemtype)
     """
     itemtype = u'nonexistent'
+    shown = False
 
     def _convert(self, doc):
         abort(404)
     def _select_itemtype(self):
         return render_template('modify_select_itemtype.html',
                                item_name=self.name,
-                               itemtypes=ITEMTYPES,
+                               itemtypes=item_registry.shown_entries,
                               )
 
 

File MoinMoin/items/blog.py

 from flask import current_app as app
 
 from whoosh.query import Term, And, Prefix, DateRange
+from whoosh.sorting import FunctionFacet
 
 from MoinMoin.i18n import L_
 from MoinMoin.themes import render_template
 from MoinMoin.forms import OptionalText, Tags, DateTime
 from MoinMoin.storage.middleware.protecting import AccessDenied
-from MoinMoin.constants.keys import NAME, NAME_EXACT, WIKINAME, ITEMTYPE, PTIME, TAGS
+from MoinMoin.constants.keys import NAME, NAME_EXACT, WIKINAME, ITEMTYPE, MTIME, PTIME, TAGS
 from MoinMoin.items import Item, Default, register, BaseMetaForm
 
 
 @register
 class Blog(Default):
     itemtype = ITEMTYPE_BLOG
+    display_name = L_('Blog')
+    description = L_('Blog item')
+    order = 0
 
     class _ModifyForm(Default._ModifyForm):
         meta_form = BlogMetaForm
                  Term(ITEMTYPE, ITEMTYPE_BLOG_ENTRY),
                  # Only sub items of this item
                  Prefix(NAME_EXACT, prefix),
-                 # Filter out those items that do not have a PTIME meta or PTIME is in the future.
-                 DateRange(PTIME, start=None, end=datetime.utcfromtimestamp(current_timestamp)),
                 ]
         if tag:
             terms.append(Term(TAGS, tag))
         query = And(terms)
-        revs = flaskg.storage.search(query, sortedby=[PTIME], reverse=True, limit=None)
+
+        def ptime_sort_key(searcher, docnum):
+            """
+            Compute the publication time key for blog entries sorting.
+
+            If PTIME is not defined, we use MTIME.
+            """
+            fields = searcher.stored_fields(docnum)
+            ptime = fields.get(PTIME, fields[MTIME])
+            return ptime
+        ptime_sort_facet = FunctionFacet(ptime_sort_key)
+
+        revs = flaskg.storage.search(query, sortedby=ptime_sort_facet, reverse=True, limit=None)
         blog_entry_items = [Item.create(rev.meta[NAME], rev_id=rev.revid) for rev in revs]
         return render_template('blog/main.html',
                                item_name=self.name,
 @register
 class BlogEntry(Default):
     itemtype = ITEMTYPE_BLOG_ENTRY
+    display_name = L_('Blog entry')
+    description = L_('Blog entry item')
+    order = 0
 
     class _ModifyForm(Default._ModifyForm):
         meta_form = BlogEntryMetaForm

File MoinMoin/items/content.py

 
         def __lt__(self, other):
             if isinstance(other, self.__class__):
+                if self.content_type != other.content_type:
+                    return other.content_type.issupertype(self.content_type)
                 if self.priority != other.priority:
                     return self.priority < other.priority
-                if self.content_type != other.content_type:
-                    return other.content_type.issupertype(self.content_type)
                 return False
             return NotImplemented
 
         self.group_names = group_names
         self.groups = dict([(g, []) for g in group_names])
 
-    def register(self, factory, contenttype, default_contenttype_params, display_name, group, ingroup_order, priority=RegistryBase.PRIORITY_MIDDLE):
+    def register(self, e, group):
         """
-        Register a factory
-
-        :param factory: Factory to register. Callable, must return an object.
+        Register a contenttype entry and optionally add it to a specific group.
         """
-        e = self.Entry(factory, contenttype, default_contenttype_params, display_name, ingroup_order, priority)
         # If group is specified and contenttype is not a wildcard one
-        if group and contenttype.type and contenttype.subtype:
+        if group and e.content_type.type and e.content_type.subtype:
             if group not in self.groups:
                 raise ValueError('Unknown group name: {0}'.format(group))
             self.groups[group].append(e)
 ])
 
 def register(cls):
-    content_registry.register(cls._factory, Type(cls.contenttype), cls.default_contenttype_params, cls.display_name, cls.group, cls.ingroup_order)
+    content_registry.register(RegistryContent.Entry(cls._factory, Type(cls.contenttype), cls.default_contenttype_params, cls.display_name, cls.ingroup_order, RegistryContent.PRIORITY_MIDDLE), cls.group)
     return cls
 
 

File MoinMoin/items/ticket.py

 @register
 class Ticket(Contentful):
     itemtype = ITEMTYPE_TICKET
+    display_name = L_('Ticket')
+    description = L_('Ticket item')
     modify_template = 'ticket.html'
 
     def do_show(self, revid):

File MoinMoin/templates/base.html

     {%- endif %}
     {% endblock %}
 
-    {% 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('frontend.template', filename='common.js') }}"></script>
-    {{ scripts }}
-    <!--[if lt IE 9]>
-        {# TODO: use a local copy later #}
-        <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
-    <![endif]-->
-    {% endblock %}
+
 {% endblock %}
 </head>
 <body{% if body_onload %} onload="{{ body_onload }}"{% endif %} lang="{{ theme_supp.ui_lang }}" dir="{{ theme_supp.ui_dir }}">
 {% endblock %}
 </div>
 
+{% block body_scripts %} {# js before </body> reduces IE8 js errors related to svgweb #}
+    <!--[if IE 8]>
+        <script src="{{ url_for('serve.files', name='svgweb', filename='svg.js') }}"></script>
+    <![endif]-->
+    <script src="{{ url_for('serve.files', name='jquery', filename='jquery.min.js') }}"></script>
+    <script src="{{ url_for('frontend.template', filename='common.js') }}"></script>
+    {{ scripts }}
+    <!--[if lt IE 9]>
+        {# TODO: use a local copy later #}
+        <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+    <![endif]-->
+    <!--[if lt IE 8]>
+        {# required to save user settings with IE7 and earlier #}
+        <script src="{{ url_for('serve.files', name='json_js', filename='json2.js') }}"></script>
+    <![endif]-->
+{% endblock %}
+
 </body>
 </html>

File MoinMoin/templates/blog/utils.html

         </div>
     {% endif %}
     <div class="moin-blog-entry-info">
-        {% set publish_time = entry_item.meta['ptime'] %}
-        {% if publish_time %}
-            {{ _("Published on") }} {{ entry_item.meta['ptime']|datetimeformat }}
+        {% set publication_time = entry_item.meta['ptime'] or entry_item.meta['mtime'] %}
+        {% if publication_time %}
+            {{ _("Published on") }} {{ publication_time|datetimeformat }}
             {{ _("by") }} {{ utils.editor_info(entry_item.meta) }}
         {% else %}
             {{ _("Not published yet.") }}

File MoinMoin/templates/common.js

 function selected_link() {
     "use strict";
     var selected = window.location.pathname,
-        list = document.getElementsByClassName('panel'),
+        list = $('.panel'),
         i,
         j,
         nav_links,
             if ($(elem).parent()[0].tagName === 'A') {
                 elem = $(elem).parent()[0];
             }
-            // wrap element, add UL and LR overlay siblings, and replace old elem with wrapped elem
-            $(wrapper).append($(elem).clone(true));
+            // insert wrapper after elem, append (move) elem, append overlays
+            $(elem).after(wrapper);
+            $(wrapper).append(elem);
             $(wrapper).append(overlayUL);
             $(wrapper).append(overlayLR);
-            $(elem).replaceWith(wrapper);
         }
     });
     // if an element was wrapped above, then make the Transclusions buttons visible
 }
 
 
+// User Settings page enhancements - make long multi-form page appear as a shorter page
+// with a row of tabs at the top or side that may be clicked to select a form.
+$(function () {
+    "use strict";
+    // do nothing if this is not a User Settings page
+    if ($('#moin-usersettings').length === 0) { return; }
 
-// Executed on page load.  If this is the user "Settings" page, make the long 6-form page
-// appear as a shorter page with a row of tabs near the top.  User clicks a tab to select target form.
-function initMoinTabs() {
-    "use strict";
-    // find all .moin-tabs elements and initialize them
-    $('.moin-tabs').each(function () {
-        var tabs = $(this),
-            titles = $(document.createElement('ul')),
-            lastLocationHash;
-        titles.addClass('moin-tab-titles');
-
-        // switching between tabs based on the current location hash
-        function updateFromLocationHash() {
-            if (location.hash !== undefined && location.hash !== '' && tabs.children(location.hash).length) {
-                if (location.hash !== lastLocationHash) {
-                    lastLocationHash = location.hash;
-                    tabs.children('.moin-tab-body').hide();
-                    tabs.children(location.hash).show();
-                    titles.children('li').children('a').removeClass('current');
-                    titles.children('li').children('a[href="' + location.hash + '"]').addClass('current');
-                }
-            } else {
-                $(titles.children('li').children('a')[0]).click();
-            }
-        }
-
-        // move all tab titles to an <ul> at the beginning of .moin-tabs
-        tabs.children('.moin-tab-title').each(function () {
+    // create a UL that will be displayed as row of tabs or column of buttons
+    $(function () {
+        var tabs = $('#moin-usersettings'),
+            titles = $('<ul class="moin-tab-titles">');
+        // for each form on page, create a corresponding LI
+        $('.moin-tab-body').each(function () {
             var li = $(document.createElement('li')),
-                a = $(this).children('a');
-            a.click(function () {
-                location.hash = this.hash;
-                updateFromLocationHash();
+                // copy a-tag defined in heading
+                aTagClone = $(this).find('a').clone();
+            li.append(aTagClone);
+            titles.append(li);
+            // add click handler to show this form and hide all others
+            aTagClone.click(function (ev) {
+                var tab = this.hash;
+                $('.moin-current-tab').removeClass('moin-current-tab');
+                $(ev.target).addClass('moin-current-tab');
+                tabs.children('.moin-tab-body').hide().removeClass('moin-current-form');
+                tabs.children(tab).show().addClass('moin-current-form');
                 return false;
             });
-            li.append(a);
-            titles.append(li);
-            $(this).remove();
         });
-        tabs.prepend(titles);
+        // if this is foobar (or similar sidebar theme) remove buttons that work when javascript is disabled
+        $('.moin-tabs ul').remove();
+        // add tabs/buttons with click handlers to top/side per theme template
+        $('.moin-tabs').prepend(titles);
+        // click a tab to show first form and hide all other forms
+        $(titles.children('li').children('a')[0]).click();
 
-        updateFromLocationHash();
-        setInterval(updateFromLocationHash, 40); // there is no event for that
-    });
-}
-$(document).ready(initMoinTabs);
-
-
-
-// Executed on page load.  Useful only if this is the user "Settings" page.
-// Saves initial values of user "Settings" forms on client side.
-// Detects unsaved changes and sets visual indicator.
-// Processes form Submit and displays status messages and updated data.
-function initMoinUsersettings() {
-    "use strict";
-    // save initial values of each form
-    $('#moin-usersettings form').each(function () {
-        $(this).data('initialForm', $(this).serialize());
+        // save initial values of each form; used in changeHandler to detect changes to a form
+        $('#moin-usersettings form').each(function () {
+            $(this).data('initialForm', $(this).serialize());
+        });
     });
 
-    // check if any changes were made, add indicator if user changes tabs without saving form
+    // add/remove "*" indicator if user changes/saves form
     function changeHandler(ev) {
         var form = $(ev.currentTarget),
-            title = $('.moin-tab-titles a.current', form.parentsUntil('.moin-tabs').parent()),
-            e;
+            title = $('.moin-tab-titles a.moin-current-tab', form.parentsUntil('.moin-tabs').parent());
         if (form.data('initialForm') === form.serialize()) {
             // current values are identicaly to initial ones, remove all change indicators (if any)
-            $('.change-indicator', title).remove();
+            $('.moin-change-indicator', title).remove();
         } else {
             // the values differ
-            if (!$('.change-indicator', title).length) {
+            if (!$('.moin-change-indicator', title).length) {
                 // only add a change indicator if there none
-                e = $(document.createElement('span'));
-                e.addClass('change-indicator');
-                e.text('*');
-                title.append(e);
+                title.append($('<span class="moin-change-indicator">*</span>'));
             }
         }
     }
-    // attach above function to all forms on page
+    // attach above function to all forms as a change handler
     $('#moin-usersettings form').change(changeHandler);
 
-    // executed when user clicks submit button on one of the tabbed forms
+    // executed when user clicks submit button on a user settings form
     function submitHandler(ev) {
         var form = $(ev.target),
             button = $('button', form),
         button.attr('disabled', true);
 
         // remove change indicators from the current tab as we are now saving it
-        $('.moin-tab-titles a.current .change-indicator',
+        $('.moin-tab-titles a.moin-current-tab .moin-change-indicator',
                 form.parentsUntil('.moin-tabs').parent()).remove();
 
         // animate the submit button to indicating a running request
         }, 'json');
         return false;
     }
-    // attach above function to all form submit buttons
+    // attach above function as a submit handler to each user setting form
     $('#moin-usersettings form').submit(submitHandler);
-}
-$(document).ready(initMoinUsersettings);
+
+    // warn user if he tries to leave page when there are unsaved changes (Opera 12.10 does not support onbeforeunload)
+    window.onbeforeunload = function () {
+        var discardMessage = ' {{ _("Your changes will be discarded if you leave this page without saving.") }} ';
+        if ($('.moin-change-indicator').length > 0) {
+            return discardMessage;
+        }
+    };
+});  // end of User Settings page enhancements
 
 
 // This anonymous function supports doubleclick to edit, auto-scroll the edit textarea and page after edit
 $(function () {
     // NOTE: if browser does not support sessionStorage, then auto-scroll is not available
-    //       sessionStorage is supported by FF3.5+, Chrome4+, Safari4+, Opera10.5+, and IE8+
+    //       (sessionStorage is supported by FF3.5+, Chrome4+, Safari4+, Opera10.5+, and IE8+).
+    //       IE8 does not scroll page after edit (cannot determine caret position within textarea).
     "use strict";
 
     var TOPID = 'moin-content',
         LINENOATTR = "data-lineno",
         MESSAGEMISSED = ' {{ _("You missed! Double-click on text or to the right of text to auto-scroll text editor.") }} ',
         MESSAGEOBSOLETE = ' {{ _("Your browser is obsolete. Upgrade to gain auto-scroll text editor feature.") }} ',
+        MESSAGEOLD = ' {{ _("Your browser is old. Upgrade to gain auto-scroll page after edit feature.") }} ',
         OPERA = 'Opera', // special handling required because textareas have \r\n line endings
         modifyButton,
+        modifyForm,
         lineno,
         message,
         caretLineno;
 
+    // IE8 workaround for missing setSelectionRange
+    function setSelection(textArea, charStart) {
+        // scroll IE8 textarea, target line will be near bottom of textarea
+        var range = textArea.createTextRange();
+        range.collapse(true);
+        range.moveEnd('character', charStart);
+        range.moveStart('character', charStart);
+        range.select();
+        //warn user that features are missing with IE8
+        moinFlashMessage(MOINFLASHWARNING, MESSAGEOLD);
+    }
+
     // called after +modify page loads -- scrolls the textarea after a doubleclick
     function scrollTextarea(jumpLine) {
         // jumpLine is textarea scroll-to line
             scrollAmount,
             textAreaClone;
 
-        if (textArea && textArea.setSelectionRange) {
+        if (textArea && (textArea.setSelectionRange || textArea.createTextRange)) {
             window.scrollTo(0, 0);
             // get data from textarea, split into array of lines, truncate based on jumpLine, make into a string
             textLines = $(textArea).val();
             textAreaClone.rows = 1;
             scrollAmount = textAreaClone.scrollHeight - 100; // get total height of clone - 100 pixels
             textAreaClone.parentNode.removeChild(textAreaClone);
-            // position the caret, highlight the position of the caret for a second or so
+            // position the caret
             textArea.focus();
             if (scrollAmount > 0) { textArea.scrollTop = scrollAmount; }
-            textArea.setSelectionRange(scrolledText.length, scrolledText.length + 8);
-            setTimeout(function () {textArea.setSelectionRange(scrolledText.length, scrolledText.length + 4); }, 1000);
-            setTimeout(function () {textArea.setSelectionRange(scrolledText.length, scrolledText.length); }, 1500);
+            if (textArea.setSelectionRange) {
+                // html5 compliant browsers, highlight the position of the caret for a second or so
+                textArea.setSelectionRange(scrolledText.length, scrolledText.length + 8);
+                setTimeout(function () {textArea.setSelectionRange(scrolledText.length, scrolledText.length + 4); }, 1000);
+                setTimeout(function () {textArea.setSelectionRange(scrolledText.length, scrolledText.length); }, 1500);
+            } else {
+                // IE8 workaround to position the caret and scroll textarea
+                setSelection(textArea, scrolledText.length);
+            }
         }
     }
 
         if (textArea.selectionStart) {
             caretPoint = textArea.selectionStart;
         } else {
+            // IE7, IE8 or
             // IE9 - user has clicked ouside of textarea and textarea focus and caret position has been lost
             return 0;
         }
     }
 
     // doubleclick processing starts here
-    if (Storage !== "undefined") {
+    if (window.sessionStorage) {
         // Start of processing for "show" pages
         if (document.getElementById('moin-edit-on-doubleclick')) {
             // this is a "show" page and the edit on doubleclick option is set for this user
                 $("#f_submit").click(function () {
                     caretLineno = getCaretLineno(document.getElementById('f_content_form_data_text'));
                     // save lineno for use in "show" page load
-                    sessionStorage.moinCaretLineNo = caretLineno;
+                    if (caretLineno > 0) { sessionStorage.moinCaretLineNo = caretLineno; }
                 });
             }
         }
     } else {
         // provide reduced functionality for obsolete browsers that do not support local storage: IE6, IE7, etc.
         if (document.getElementById('moin-edit-on-doubleclick')) {
-            moinFlashMessage(MOINFLASHWARNING, MESSAGEOBSOLETE);
             modifyButton = $('.moin-modify-button')[0];
             if (modifyButton) {
-                // add doubleclick event handler when user doubleclicks within the content area
+                // this is a "show" page, add doubleclick event handler to content node
                 $('#moin-content').dblclick(function (e) {
                     document.location = modifyButton.href;
                 });
             }
+        } else {
+            modifyForm = $('#moin-modify')[0];
+            if (modifyForm) {
+                // user is editing with obsolete browser, give warning about missing features
+                moinFlashMessage(MOINFLASHWARNING, MESSAGEOBSOLETE);
+            }
         }
     }
 });

File MoinMoin/templates/destroy.html

 {% import "forms.html" as forms %}
 {% extends theme("show.html") %}
 
-{% set title = _("DESTROY COMPLETE item '%(item_name)s'", item_name=item.name) %}
+{% set title = _("DESTROY COMPLETE ITEM '%(item_name)s'", item_name=item.name) %}
 
 {% block content %}
 {% if rev_id == None %}

File MoinMoin/templates/forms.html

 {% endmacro %}
 
 {% macro render_button(text) %}
-  <button>{{ text }}</button>
+  <button type="submit">{{ text }}</button>
 {% endmacro %}
 
 {% macro render_textcha(gen, form) %}
   </dd>
 {% endmacro %}
 
-{% macro render_file_uploader(submit_url) %}
+{% macro render_file_uploader(submit_url) %} {# pages that use this macro must also use file_uploader_scripts macro below #}
     <div id="file_upload">
         <div class="upload-form">
         <form action="{{ submit_url }}" method="POST" enctype="multipart/form-data" class="upload_file">
             </tr>
         </table>
     </div>
+{% endmacro %}
+{% macro file_uploader_scripts() %}
     <link rel="stylesheet" href="{{ url_for('serve.files', name='jquery_file_upload', filename='jquery.fileupload-ui.css') }}">
     <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>

File MoinMoin/templates/index.html

     {% set title = _("Global Index") %}
 {% endif %}
 
-{% block head_scripts %}
-{{ super() }}
-    <script src="{{ url_for('frontend.template', filename='index_action.js') }}"></script>
-{% endblock %}
-
 {% block action_bars_inner %}
     <ul>
-        {% if index %}
+        {% if dirs or files %}
         <li class="action-bar">
         <div class="moin-select-all">
             <span class="moin-select-allitem allitem-toselect" title="{{ _("Select All") }}">{{ _("Select All") }}</span>
             </div>
             <ul>
                 <li id="moin-create-newitem">{{ _("New item") }}</li>
-                {% if index %}
+                {% if dirs or files %}
                 <li id="moin-download-trigger">{{ _("Download") }}</li>
                 <li class="moin-action-tab"
                     id="moin-delete-trigger"
     </div>
 {% endblock %}
 
-{% macro render_entry(e) %}
-    {% set maxchars = 20 %}
+{% macro entry_anchor(type, e) -%}
+    index-{{ type }}-{{ e.meta['itemid'] }}
+{%- endmacro %}
+
+{% set maxchars = 20 %}
+
+{% macro render_dir_entry(e) %}
+    <div>
+        <a href="{{ url_for('.index', item_name=e.meta['name']) }}"
+           name="{{ entry_anchor('dir', e) }}"
+           {# TODO .moin-dir-item class is not styled yet #}
+           class="moin-dir-item"
+           title="{{ e.relname }}">
+           {{ e.relname|truncate(maxchars, true, '..') }}
+        </a>
+        {% if e in files %}
+            <a href="#{{ entry_anchor('file', e) }}"
+               title="{{ _("This item itself also matches your filter.") }}">↓
+            </a>
+        {% endif %}
+    </div>
+{% endmacro %}
+
+{% macro render_file_entry(e) %}
     <div>
         <span class="moin-select-item">&nbsp;</span>
         {% set mimetype = "application/x.moin.download" %}
         <a href="{{ url_for('.download_item', item_name=e.meta['name'], mimetype=mimetype) }}" class="moin-download-link">
         </a>
         <a href="{{ url_for('.show_item', item_name=e.meta['name']) }}"
+           name="{{ entry_anchor('file', e) }}"
            {# TODO .moin-itemtype-* classes are not styled yet #}
            class="{{ e.meta['contenttype']|contenttype_to_class }} moin-itemtype-{{ e.meta['itemtype'] }} moin-item"
            title="{{ e.relname }}">
            {{ e.relname|truncate(maxchars, true, '..') }}
         </a>
-        {% if e.hassubitems %}
-            <a href="{{ url_for('frontend.index', item_name=e.meta['name']) }}"
-               title="{{ _("More") }}"
-               class="moin-more-index">&nbsp;
-        </a>
+        {% if e in dirs %}
+            <a href="#{{ entry_anchor('dir', e) }}"
+               title="{{ _("This item also has subitems that match your filter.") }}">↑
+            </a>
         {% endif %}
     </div>
 {% endmacro %}
         <div class="moin-item-index">
             <p>{{ _("These items have subitems that match your filter:") }}</p>
             {% for e in dirs %}
-                {{ render_entry(e) }}
+                {{ render_dir_entry(e) }}
             {% endfor %}
         </div>
         <div class="moin-clr"></div>
     {% if files %}
         <div class="moin-item-index">
             {% for e in files %}
-                {{ render_entry(e) }}
+                {{ render_file_entry(e) }}
             {% endfor %}
         </div>
         <div class="moin-clr"></div>
     {{ forms.render_file_uploader(submit_url) }}
     </div>
     <span class="moin-drag">{{ _("(Drag and drop multiple files to this white area to upload them.)") }}</span>
-    <script src="{{ url_for('static', filename='js/jfu.js') }}"></script>
     <div id="lightbox">&nbsp;</div>
 {% endblock %}
+
+{% block body_scripts %}
+{{ super() }}
+    <script src="{{ url_for('frontend.template', filename='index_action.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/jfu.js') }}"></script>
+    {{ forms.file_uploader_scripts() }}
+{% endblock %}

File MoinMoin/templates/modify.html

 {% endblock %}
 
 {% block options_for_javascript %}
-{%- if user.edit_on_doubleclick -%}
+{%- if user.scroll_page_after_edit -%}
     <br id="moin-scroll-page-after-edit" />
 {%- endif %}
 {% endblock %}

File MoinMoin/templates/modify_select_itemtype.html

 {{ _("Item '%(name)s' does not exist (yet), but you can try creating it now. Please select the type of the item you want to create.", name=item_name) }}
 </p>
 <table class="zebra">
-    {% for it in itemtypes %}
+    {% for e in itemtypes %}
     <tr>
         <td>
-            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=it.itemtype) }}">{{ it.display_name }}</a> - {{ it.description }}
+            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=e.itemtype) }}">{{ e.display_name }}</a> - {{ e.description }}
         </td>
     </tr>
     {% endfor %}

File MoinMoin/templates/usersettings.html

 
 {% block content %}
 <h1>{{ _("User Settings") }}</h1>
-
-<div id="moin-usersettings" class="moin-tabs">
-    <h2 class="moin-tab-title"><a href="#personal">{{ _("Personal Settings") }}</a></h2>
-    <div id="personal" class="moin-tab-body moin-form">
-        {{ user_forms.personal(form_objs.personal) }}
-    </div>
-
-    <h2 class="moin-tab-title"><a href="#password">{{ _("Change Password") }}</a></h2>
-    <div id="password" class="moin-tab-body moin-form">
-        {{ user_forms.password(form_objs.password) }}
-    </div>
-
-    <h2 class="moin-tab-title"><a href="#notification">{{ _("Notification Settings") }}</a></h2>
-    <div id="notification" class="moin-tab-body moin-form">
-        {{ user_forms.notification(form_objs.notification) }}
-    </div>
-
-    <h2 class="moin-tab-title"><a href="#ui">{{ _("Wiki Appearance Settings") }}</a></h2>
-    <div id="ui" class="moin-tab-body moin-form">
-        {{ user_forms.ui(form_objs.ui) }}
-    </div>
-
-    <h2 class="moin-tab-title"><a href="#navigation">{{ _("Navigation Settings") }}</a></h2>
-    <div id="navigation" class="moin-tab-body moin-form">
-        {{ user_forms.navigation(form_objs.navigation) }}
-    </div>
-
-    <h2 class="moin-tab-title"><a href="#options">{{ _("Options") }}</a></h2>
-    <div id="options" class="moin-tab-body moin-form">
-        {{ user_forms.options(form_objs.options) }}
-    </div>
-</div>
-
+<div class="moin-tabs"></div> {# placeholder - javascript will append UL with clickable tabs here #}
+{{ user_forms.all_usersettings_forms(form_objs) }}
 {% endblock %}

File MoinMoin/templates/usersettings_forms.html

 {{ forms.render_button(_("Save")) }}
 {{ gen.form.close() }}
 {% endmacro %}
+
+{# javascript functions within common.js are dependent upon the structure, classes and ids defined here #}
+{% macro all_usersettings_forms(form_objs) %}
+<div id="moin-usersettings">
+    <div id="personal" class="moin-tab-body moin-form">
+        <h2 class="moin-settings-head"><a href="#personal">{{ _("Personal Settings") }}</a></h2>
+        {{ personal(form_objs.personal) }}
+    </div>
+    <div id="password" class="moin-tab-body moin-form">
+        <h2 class="moin-settings-head"><a href="#password">{{ _("Change Password") }}</a></h2>
+        {{ password(form_objs.password) }}
+    </div>
+    <div id="notification" class="moin-tab-body moin-form">
+        <h2 class="moin-settings-head"><a href="#notification">{{ _("Notification Settings") }}</a></h2>
+        {{ notification(form_objs.notification) }}
+    </div>
+    <div id="ui" class="moin-tab-body moin-form">
+        <h2 class="moin-settings-head"><a href="#ui">{{ _("Wiki Appearance Settings") }}</a></h2>
+        {{ ui(form_objs.ui) }}
+    </div>
+    <div id="navigation" class="moin-tab-body moin-form">
+        <h2 class="moin-settings-head"><a href="#navigation">{{ _("Navigation Settings") }}</a></h2>
+        {{ navigation(form_objs.navigation) }}
+    </div>
+    <div id="options" class="moin-tab-body moin-form">
+        <h2 class="moin-settings-head"><a href="#options">{{ _("Options") }}</a></h2>
+        {{ options(form_objs.options) }}
+    </div>
+</div>
+{% endmacro %}

File MoinMoin/themes/foobar/static/css/common.css

 .moin-form dd input{width:70%}
 .moin-form dt{clear:both;float:left;width:30%;text-align:right;margin-top:.3em;padding-right:1%;}
 .moin-form dt label.required:after{content:'*';color:#000}
-.moin-form button,.moin-form input[type="submit"]{margin-left:48%}
+.moin-form button,.moin-form input[type="submit"]{clear:both;display:table;margin:auto}
 #moin-modify dd,form[name="delete_item"] dd,form[name="rename_item"] dd,form[name="destroy_item"] dd{margin-left:.9em;width:100%;float:none;}
 #moin-modify dd input,form[name="delete_item"] dd input,form[name="rename_item"] dd input,form[name="destroy_item"] dd input{width:96%}
 #moin-modify dt,form[name="delete_item"] dt,form[name="rename_item"] dt,form[name="destroy_item"] dt{float:none;text-align:left;width:auto}
 a.moin-conflict:before{content:url("../img/moin-conflict.png");margin:0 .2em}
 .moin-tab-titles{margin:-.5em;list-style:none;}
 .moin-tab-titles span{display:none}
+.moin-tab-titles a.moin-current-tab{color:#1e90ff}
 #moin-usersetting-title li a{font-size:.84em;color:#696969}
 #moin-global-history,#moin-page-history{font-size:.75em}
 #moin-page-history table{border:1px solid #ccc;border-radius:5px;}
 h4{font-size:1em;margin:1em 0;border-bottom:2px solid #ccc;padding-bottom:2px}
 h5{font-size:.8333333333333334em;margin:1.2em 0;border-bottom:1px solid #ccc;padding-bottom:1px}
 h6{font-size:.6944444444444444em;margin:1.44em 0;border-bottom:1px solid #ccc;padding-bottom:1px}
-a.permalink{display:none;cursor:pointer;margin-left:.1em;color:#616161;}
-a.permalink:hover{color:#000}
-h1:hover .permalink,h2:hover .permalink,h3:hover .permalink,h4:hover .permalink,h5:hover .permalink,h6:hover .permalink{display:inline;text-decoration:none}
+a.moin-permalink{display:none;cursor:pointer;margin-left:.1em;color:#808080;}
+a.moin-permalink:hover.moin-permalink{color:#ff2727}
+h1:hover .moin-permalink,h2:hover .moin-permalink,h3:hover .moin-permalink,h4:hover .moin-permalink,h5:hover .moin-permalink,h6:hover .moin-permalink{display:inline;text-decoration:none}
 hr{background-color:#000;border:0;margin:.9em 0;height:1px;}
 hr.moin-hr1{height:2px}
 hr.moin-hr2{height:3px}

File MoinMoin/themes/foobar/static/css/stylus/main.styl

                 color font_color
     button,
     input[type="submit"]
-        margin-left 48%
+        clear both
+        display table
+        margin auto
 
 #moin-modify, form[name="delete_item"], form[name="rename_item"], form[name="destroy_item"]
     dd
     span
         display none
 
+.moin-tab-titles a.moin-current-tab
+    color link_color
+
 #moin-usersetting-title
     li a
         font-size .84em
         border-bottom heading_underline_thickness solid border_color
         padding-bottom heading_underline_thickness
 
-// special style for heading with mouseover permalinks
-a.permalink
+// headings with mouseover permalinks
+a.moin-permalink
     display none
     cursor pointer
     margin-left 0.1em
-    color footer_color
-    &:hover
-        color font_color
+    color nonexistent_link_color
+    &:hover.moin-permalink
+        color nonexistent_hover_color
 
-h1:hover .permalink,
-h2:hover .permalink,
-h3:hover .permalink,
-h4:hover .permalink,
-h5:hover .permalink,
-h6:hover .permalink
+h1:hover .moin-permalink,
+h2:hover .moin-permalink,
+h3:hover .moin-permalink,
+h4:hover .moin-permalink,
+h5:hover .moin-permalink,
+h6:hover .moin-permalink
     display inline
     text-decoration none
 

File MoinMoin/themes/foobar/templates/usersettings.html

 {% import "usersettings_forms.html" as user_forms %}
 
 {% block local_panel %}
-<ul id="moin-usersetting-title">
-<li class="moin-tab-title"><a href="#moin-personal">{{ _("Personal Settings") }}</a></li>
-<li class="moin-tab-title"><a href="#moin-password">{{ _("Change Password") }}</a></li>
-<li class="moin-tab-title"><a href="#moin-notification">{{ _("Notification Settings") }}</a></li>
-<li class="moin-tab-title"><a href="#moin-ui">{{ _("Wiki Appearance Settings") }}</a></li>
-<li class="moin-tab-title"><a href="#moin-navigation">{{ _("Navigation Settings") }}</a></li>
-<li class="moin-tab-title"><a href="#moin-options">{{ _("Options") }}</a></li>
-</ul>
+<div class="moin-tabs">
+    <ul id="moin-usersetting-title">
+        <li class="moin-tab-title"><a href="#moin-personal">{{ _("Personal Settings") }}</a></li>
+        <li class="moin-tab-title"><a href="#moin-password">{{ _("Change Password") }}</a></li>
+        <li class="moin-tab-title"><a href="#moin-notification">{{ _("Notification Settings") }}</a></li>
+        <li class="moin-tab-title"><a href="#moin-ui">{{ _("Wiki Appearance Settings") }}</a></li>
+        <li class="moin-tab-title"><a href="#moin-navigation">{{ _("Navigation Settings") }}</a></li>
+        <li class="moin-tab-title"><a href="#moin-options">{{ _("Options") }}</a></li>
+    </ul>
+</div>
 {% endblock %}
 
 {% block content %}
-<div id="moin-usersettings" class="moin-tabs">
-    <div class="moin-tab-title"><a href="#moin-personal"></a></div>
-    <div id="moin-personal" class="moin-tab-body moin-form">
-        <h2>{{ _("Personal Settings") }}</h2>
-        {{ user_forms.personal(form_objs.personal) }}
-    </div>
-
-
-    <div id="moin-password" class="moin-tab-body moin-form">
-        <h2>{{ _("Change Password") }}</h2>
-        {{ user_forms.password(form_objs.password) }}
-    </div>
-
-
-    <div id="moin-notification" class="moin-tab-body moin-form">
-        <h2>{{ _("Notification Settings") }}</h2>
-        {{ user_forms.notification(form_objs.notification) }}
-    </div>
-
-
-    <div id="moin-ui" class="moin-tab-body moin-form">
-        <h2>{{ _("Wiki Appearance Settings") }}</h2>
-        {{ user_forms.ui(form_objs.ui) }}
-    </div>
-
-
-    <div id="moin-navigation" class="moin-tab-body moin-form">
-        <h2>{{ _("Navigation Settings") }}</h2>
-        {{ user_forms.navigation(form_objs.navigation) }}
-    </div>
-
-
-    <div id="moin-options" class="moin-tab-body moin-form">
-        <h2>{{ _("Options") }}</h2>
-        {{ user_forms.options(form_objs.options) }}
-    </div>
-</div>
-
+{{ user_forms.all_usersettings_forms(form_objs) }}
 {% endblock %}

File MoinMoin/themes/modernized/static/css/common.css

 .moin-form dd input{width:75%}
 .moin-form dt{clear:both;float:left;width:25%;text-align:right;margin-top:.3em;padding-right:1%;}
 .moin-form dt label.required:after{content:'*';color:#000}
-.moin-form button,.moin-form input[type="submit"]{margin-left:48%}
+.moin-form button,.moin-form input[type="submit"]{clear:both;display:table;margin:auto}
 form[name="delete_item"] dd input,form[name="rename_item"] dd input,form[name="destroy_item"] dd input{width:100%}
 form[name="delete_item"] dt,form[name="rename_item"] dt,form[name="destroy_item"] dt{width:20%}
 #moin-modify dd{width:100%;}
 .moin-textcha{padding-top:.2em}
 #options dd{float:none;width:38%;display:table-cell;padding:1.2em}
 #options dt{margin-left:10%;text-align:left;width:61%;margin-top:1.3em}
-#moin-usersettings a{color:#00008b}
+.moin-tabs .moin-tab-titles a{color:#00008b}
 .moin-tab-titles{margin:0;padding:-10px 0 0;list-style:none;border-bottom:3px solid #4e7da9}
-.moin-tab-titles li{display:inline-block;margin:10px 0 -3px;padding:0 5px;border-bottom:3px solid #4e7da9}
+.moin-tab-titles li{display:inline-block;*display:inline;zoom:1;margin:10px 0 -3px;padding:0 5px;border-bottom:3px solid #4e7da9}
 .moin-tab-titles a{display:inline-block;padding:4px;background-color:#eef1f6;border-width:1px 1px 0;border-style:solid;border-color:#4e7da9;color:#00008b;border-top-left-radius:7px;border-top-right-radius:7px}
 .moin-tab-titles a:hover{background-color:#d8dfe9;text-decoration:none}
-.moin-tab-titles a.current{background:#d8dfe9;padding-top:8px;margin-top:-4px}
-.moin-tab-titles .change-indicator{font-weight:bold;color:#1f9ae0}
-.moin-tab-title a{color:#000;text-decoration:none}
+.moin-tab-titles a.moin-current-tab{background:#d8dfe9;padding-top:8px;margin-top:-4px}
+.moin-tab-titles .moin-change-indicator{font-weight:bold;color:#1f9ae0}
+.moin-current-form h2{display:none}
+.moin-settings-head a{color:#000;text-decoration:none}
 .searchresults dt{margin-top:1em;font-weight:normal}
 .searchresults dd,.searchresults p{font-size:.85em}
 .searchresults td{border-width:0}
 .moin-item-index div.selected-item .moin-select-item{background-image:url("../img/moin-checkbox-on.png")}
 .moin-align-right{text-align:right;float:right}
 .moin-align-right ul{margin:0;padding:0}
-.moin-align-right ul li.action-bar{list-style-type:none;display:inline-block}
+.moin-align-right ul li.action-bar{list-style-type:none;display:inline-block;*display:inline;zoom:1}
 .moin-contenttypes-wrapper{position:relative;margin:0;color:#00008b;background:#eef1f6;border:1px solid #4e7da9;min-width:12em;text-align:left}
 .moin-contenttypes-wrapper div{cursor:pointer;margin:.1em;padding:.5em 1.2em .5em .5em;height:1em}
 div.ct-shown{background:#d8dfe9 url("../img/moin-movedown.png") no-repeat center right;color:#000}
 #moin-navibar li:hover{background:#d8dfe9}
 #moin-pageline{clear:both;margin:4px 10px;padding:0;width:auto;height:1px;border-bottom:1px solid #fff;border-top:1px solid #fff;background:#fff}
 .moin-itemviews{clear:both;display:block;margin:0 0 -6px 0;padding:2px 8px;background:#708090;font-size:.8em;border-radius:9px;color:#fff}
-.moin-itemviews li{display:inline-block;padding:0;margin:4px 6px;line-height:1em}
+.moin-itemviews li{display:inline-block;*display:inline;zoom:1;padding:0;margin:4px 6px;line-height:1em}
 .moin-itemviews a,.moin-itemviews a:visited{color:#cfcfcf;}
 .moin-itemviews a:hover,.moin-itemviews a:visited:hover{color:#fff}
 .moin-itemviews a:hover{text-decoration:underline}
 #moin-pageinfo,#moin-wikilicense,#moin-credits,#moin-version,#moin-timings{margin:10px 20px;text-align:left;font-size:.7em;color:#737373;}
 #moin-pageinfo a,#moin-wikilicense a,#moin-credits a,#moin-version a,#moin-timings a{color:#737373}
 #moin-pageinfo{margin-top:20px}
-#moin-timings li{display:inline-block;margin:0 20px 0 0}
+#moin-timings li{display:inline-block;*display:inline;zoom:1;margin:0 20px 0 0}
 #moin-credits span{display:inline-block;margin:0 5px}
 #moin-creditlogos{float:right;list-style:none;margin:5px 10px}
-#moin-creditlogos li{display:inline-block;margin:10px 0 10px 10px}
+#moin-creditlogos li{display:inline-block;*display:inline;zoom:1;margin:10px 0 10px 10px}
 .moin-item-wrapper{position:relative;display:inline-block}
 div.moin-item-wrapper{width:100%}
 .moin-item-wrapper > a:hover{color:#1f9ae0;text-decoration:none}
 .moin-item-overlay-ul:hover,.moin-item-overlay-lr:hover{opacity:.8;filter:alpha;background-color:#eef1f6;color:#1f9ae0}
 div.moin-item-wrapper,div.moin-item-wrapper > div,div.moin-item-wrapper > div > p:first-child,div.moin-item-wrapper > div > p:last-child{margin:0}
 div.moin-item-wrapper > div >  p:first-child ~ p:last-child{margin-top:1em}
-.moin-permalink{display:none;cursor:pointer;font-size:80%;margin-left:3px}
-a.moin-permalink{color:#939393}
-a:hover.moin-permalink{color:#1f62ad}
+a.moin-permalink{display:none;cursor:pointer;margin-left:.1em;color:#939393;}
+a.moin-permalink:hover.moin-permalink{color:#1f62ad}
 h1:hover .moin-permalink,h2:hover .moin-permalink,h3:hover .moin-permalink,h4:hover .moin-permalink,h5:hover .moin-permalink,h6:hover .moin-permalink{display:inline;text-decoration:none}
 @media print{html{font-family:serif;font-size:12pt;width:100%}
 body,#moin-page,#moin-page,#moin-content-data{margin:0;padding:0}

File MoinMoin/themes/modernized/static/css/stylus/main.styl

                 color font_color
     button,
     input[type="submit"]
-        margin-left 48%
+        clear both
+        display table
+        margin auto
 
 form[name="delete_item"], form[name="rename_item"], form[name="destroy_item"]
     dd
         width 61%
         margin-top 1.3em
 
-#moin-usersettings
+.moin-tabs .moin-tab-titles
     a
         color link_color
 
 
 .moin-tab-titles li
     display inline-block
+    *display: inline; // IE7 hack to display title tabs in a row ...
+    zoom: 1; // IE7 hack ... rather than a column
     margin 10px 0 -3px
     padding 0 5px
     border-bottom 3px solid border_color
     background-color table_row_color
     text-decoration none
 
-.moin-tab-titles a.current
+.moin-tab-titles a.moin-current-tab
     background table_row_color
     padding-top 8px
     margin-top -4px
 
-.moin-tab-titles .change-indicator
+.moin-tab-titles .moin-change-indicator
     font-weight bold
     color hover_color
 
-.moin-tab-title a
+.moin-current-form h2
+    display none // bold tab at top of user setting page makes h2 redundant
+
+.moin-settings-head a
     color font_color
     text-decoration none
 
 .moin-align-right ul li.action-bar
     list-style-type none
     display inline-block
+    *display: inline; // IE7 hack to display index action bar in a row ...
+    zoom: 1; // IE7 hack ... rather than a column
 
 .moin-contenttypes-wrapper
     position relative
 
 .moin-itemviews li
     display inline-block
+    *display: inline; // IE7 hack to display itemviews in a row ...
+    zoom: 1; // IE7 hack ... rather than a column
     padding 0
     margin 4px 6px
     line-height 1em
 
 #moin-timings li
     display inline-block
+    *display: inline; // IE7 hack to display timings in a row ...
+    zoom: 1; // IE7 hack ... rather than a column
     margin 0 20px 0 0
 
 #moin-credits span
 
 #moin-creditlogos li
     display inline-block
+    *display: inline; // IE7 hack to display credit logos in a row ...
+    zoom: 1; // IE7 hack ... rather than a column
     margin 10px 0 10px 10px
 
 .moin-item-wrapper
 div.moin-item-wrapper > div >  p:first-child ~ p:last-child
     margin-top 1em
 
-.moin-permalink
+// headings with mouseover permalinks
+a.moin-permalink
     display none
     cursor pointer
-    font-size 80%
-    margin-left 3px
-
-a.moin-permalink
+    margin-left .1em
     color nonexistent_link_color
-
-a:hover.moin-permalink
-    color nonexistent_hover_color
+    &:hover.moin-permalink
+        color nonexistent_hover_color
 
 h1:hover .moin-permalink,
 h2:hover .moin-permalink,

File _ui_tests/utils.py

 except ImportError:
     pytest.skip('selenium needs to be installed for this test')
 
+import config
+import urllib
+
+try:
+    f = urllib.urlopen(config.BASE_URL)
+except IOError:
+    pytest.skip('The UI tests need a wiki server running on %s' % config.BASE_URL)
+
 import driver_register
 
 

File docs/devel/development.rst

 Development
 ===========
 
-Project Organisation
-====================
-We mainly use IRC and the wiki for communication, documentation and planning.
+Useful Resources
+================
 
-IRC channels on chat.freenode.net:
+IRC channels on chat.freenode.net (quick communication and discussion):
 
-* #moin-dev (core development topics)
-* #moin (user support, extensions)
+* #moin-dev  (core development topics)
+* #moin  (user support, extensions)
 
 Wikis:
 
-* http://moinmo.in/
+* http://moinmo.in/  (production wiki, using moin 1.9)
+* http://test.moinmo.in/  (test wiki, using moin 2)
 
-Documentation:
+Documentation (installation, configuration, user docs, api reference):
 
 * http://readthedocs.org/docs/moin-20/en/latest/
 
-Issue tracker:
+Issue tracker (bugs, proposals, todo):
 
 * http://bitbucket.org/thomaswaldmann/moin-2.0/issues
 
-Code Repositories:
+Code Repositories (using Mercurial DVCS):
 
-* http://hg.moinmo.in/moin/2.0 - main repository
-* http://bitbucket.org/thomaswaldmann/moin-2.0 - bitbucket mirror for your
-  convenience, simplifying forking and contributing
+* http://hg.moinmo.in/moin/2.0  (main repository)
+* http://bitbucket.org/thomaswaldmann/moin-2.0  (bitbucket mirror for your
+  convenience, simplifying forking and contributing)
 
-We use Mercurial DVCS for distributed version control.
+Code review (get feedback about code changes):
 
-If you are not using Mercurial, you can still submit patches.
-In that case, open an issue in the issue tracker and attach the patch there.
+* http://codereview.appspot.com/
 
-Code review:
+Pastebin (temporary stuff - do not use for code reviews, do not use from issue
+tracker or for any long-term needed stuff):
 
-Please use http://codereview.appspot.com/ for getting feedback on moin-related
-code, especially if you want to contribute or publish that code.
+* http://rn0.ru/
 
-If you are using a local mercurial repository/workdir, you can very easily
-upload your uncommitted workdir state to codereview using their "upload.py".
 
-Then just ask on the IRC channel for review and provide the codereview URL.
+Typical development workflow
+============================
+
+This is the typical workflow for non-trivial changes and developers that likely
+want to contribute more than one change:
+
+* create your own development environment (only once):
+
+  - create a google account (if you don't have one already, it's free), so you
+    can use codereview.appspot.com
+  - create a bitbucket account (if you don't have one already, it's free)
+  - clone ("fork") the main repository on bitbucket, so you have your own bb
+    repo to publish your work
+  - clone your own bb repo to your local development machine
+  - do a development install from your local repo - read the moin2 install docs
+    for detailled instructions.
+  - join #moin-dev IRC channel and stay there whenever possible
+
+* find some stuff to work on:
+
+  - look at the issue tracker to find some stuff you can solve
+  - in case you want to work on some (non-trivial) new issue or idea that is
+    not on the issue tracker yet, first create an issue there with a detailled
+    description of it
+  - discuss with / get feedback from other developers on the #moin-dev IRC
+    channel
+
+* work on the stuff:
+
+  - to avoid double work, add a comment on the issue tracker that you are
+    working on that issue
+  - work in your local repo on your local development machine (make sure you
+    work in the right branch)
+  - concentrate on one issue / one topic, create a clean set of changes (that
+    means not doing more than needed to fix the issue, but also it means fixing
+    the issue completely and everywhere)
+  - write good, clean, easy-to-understand code.
+  - obey PEP-8
+  - do not fix or change unrelated stuff while working, but rather create new
+    issues on the tracker, so it's not forgotten
+  - regularly run the unit tests ("make test"), the amount of failing tests
+    shall not increase due to your changes
+  - if you fix something that had no test, first try to write a (correct, but
+    still failing) test for it, then fix the code and see the test not failing
+    any more
+  - if you implement new functionality, write tests for it first, then
+    implement it
+  - do an own review of your changes. Use hg diff, hg status - read everything
+    you changed - slowly, looking for stuff that can be improved. Fix
+    everything you find that way before requesting feedback from others.
+  - get feedback from other developers about your changes:
+   
+    + put them on codereview (just run python upload.py in your local repo -
+      if it is not first upload, reuse the same ID to update the already
+      existing codereview)
+    + post the codereview URL to #moin-dev IRC channel asking for review
+    + if you want to get feedback on non-code stuff, either use the issue
+      tracker or a pastebin (only use pastebins for temporary stuff)
+  - repeat until everybody is happy with it
+  - do some final testing - practically and using the unit tests
+  - commit your changes to your local repo, use a meaningful commit comment
+
+* publish your stuff and request it being merged:
+
+  - push the changeset to your public bitbucket repo
+  - create a pull request to request that your changes get pulled into the
+    main repository
+  - optionally, tell about it on the IRC channel
+  - if you fixed an issue from the issue tracker, make sure the issue gets
+    closed after your fix has been merged.
+
+
+Alternate contribution workflows
+================================
+If the above workflow looks like overkill (e.g. for simple changes) or you
+can't work with the tools we usually use, you can also do it like this:
+
+* find an existing issue on the issue tracker about the issue you were fixing
+  (or create a new one), make sure to give (or update) all the details, like:
+
+  - precise version number / changeset hashes of the original code your patch
+    is based on
+  - precise description of the issue, how to reproduce it, tracebacks, ...
+  - why your fix is correct / how you tested it
+* create a patch using the diff tool, attach patch.txt to the issue:
+
+    diff -urN originalcodetree/ modifiedcodetree/ > patch.txt
+
+* if you fixed an issue from the issue tracker, make sure the issue gets
+  closed after your fix has been committed to the main repo.
+
+For trivial fixes (like typos), you can also try just grabbing a developer
+on IRC, telling filename, line number and get it fixed by him.
+
+Note: if developers find that the required changes are not that simple or are
+potentially causing other issues, codereview or other parts of the full
+workflow might be needed.
+
 
 MoinMoin architecture
 =====================

File docs/user/markdown.rst

+===============
+Markdown Markup
+===============
+
         'Jinja2>=2.6', # template engine
         'pygments>=1.4', # src code / text file highlighting
         'Werkzeug>=0.8.1', # wsgi toolkit
-        'pytest>=2.1', # pytest is needed by unit tests
-        'pytest-pep8', # coding style checker
+        'pytest>=2.1, <2.3', # pytest is needed by unit tests
+                             # note: currently 2.3.x is not compatible with our test code,
+                             # likely due to the fixtures changes.
+        'pytest-pep8<1.0.3', # coding style checker
+                             # note: pytest-pep8 1.0.3 needs pytest 2.3
         'whoosh>=2.5.0', # needed for indexed search
         'sphinx>=1.1', # needed to build the docs
         'pdfminer', # pdf -> text/plain conversion
         'XStatic>=0.0.2', # support for static file pypi packages
         'XStatic-CKEditor>=3.6.1.2',
-        'XStatic-jQuery>=1.6.1.4',
+        'XStatic-jQuery>=1.8.2',
         'XStatic-jQuery-File-Upload>=4.4.2',
+        'XStatic-JSON-js',
         'XStatic-svgweb>=2011.2.3.2',
         'XStatic-TWikiDraw-moin>=2004.10.23.2',
         'XStatic-AnyWikiDraw>=0.14.2',
-        'XStatic-svg-edit-moin>=2011.07.07.2',
+        'XStatic-svg-edit-moin>=2012.11.15.1',
     ],
     # optional features and their list of requirements
     extras_require={

File wikiconfig.py

     )
     # see https://bitbucket.org/thomaswaldmann/xstatic for infos about xstatic:
     from xstatic.main import XStatic
+    # names below must be package names
     mod_names = ['jquery', 'jquery_file_upload',
+                 'json_js',
                  'ckeditor',
                  'svgweb',
                  'svgedit_moin', 'twikidraw_moin', 'anywikidraw',