Commits

Thomas Waldmann committed 6938dad Merge

merged sharky's other repo

  • Participants
  • Parent commits b942154, 5e59575

Comments (0)

Files changed (30)

MoinMoin/apps/frontend/views.py

 from babel import Locale
 
 from whoosh.query import Term, Prefix, And, Or, DateRange, Every
+from whoosh.analysis import StandardAnalyzer
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 @frontend.route('/+search', defaults=dict(item_name=u''), methods=['GET', 'POST'])
 def search(item_name):
     search_form = SearchForm.from_flat(request.values)
+    ajax = True if request.args.get('boolajax') else False
     valid = search_form.validate()
-    query = search_form['q'].value
-    if valid:
+    if ajax:
+        query = request.args.get('q')
+        history = request.args.get('history')
+    else:
+        query = search_form['q'].value
         history = bool(request.values.get('history'))
+    if valid or ajax:
+        #most fields in the schema use a StandardAnalyzer, it omits fairly frequently used words
+        #this finds such words and reports to the user
+        analyzer = StandardAnalyzer()
+        omitted_words = [token.text for token in analyzer(query, removestops=False) if token.stopped]
+
         idx_name = ALL_REVS if history else LATEST_REVS
-        qp = flaskg.storage.query_parser([NAME_EXACT, NAME, SUMMARY, CONTENT], idx_name=idx_name)
+        qp = flaskg.storage.query_parser([NAME_EXACT, NAME, SUMMARY, CONTENT, CONTENTNGRAM], idx_name=idx_name)
         q = qp.parse(query)
 
         _filter = None
             _filter = Or(terms)
 
         with flaskg.storage.indexer.ix[idx_name].searcher() as searcher:
+            #terms is set to retrieve list of terms which matched, in the searchtemplate, for highlight.
             flaskg.clock.start('search')
-            results = searcher.search(q, filter=_filter, limit=100)
+            results = searcher.search(q, filter=_filter, limit=100, terms=True)
             flaskg.clock.stop('search')
             flaskg.clock.start('search suggestions')
             name_suggestions = [word for word, score in results.key_terms(NAME, docs=20, numterms=10)]
             content_suggestions = [word for word, score in results.key_terms(CONTENT, docs=20, numterms=10)]
             flaskg.clock.stop('search suggestions')
             flaskg.clock.start('search render')
-            html = render_template('search.html',
-                                   results=results,
-                                   name_suggestions=u', '.join(name_suggestions),
-                                   content_suggestions=u', '.join(content_suggestions),
-                                   query=query,
-                                   medium_search_form=search_form,
-                                   item_name=item_name,
-                                   history=history,
-            )
+
+            lastword = query.split(' ')[-1]
+            word_suggestions = []
+            if len(lastword) > 2:
+                corrector = searcher.corrector(CONTENT)
+                word_suggestions = corrector.suggest(lastword, limit=3)
+            if ajax:
+                html = render_template('ajaxsearch.html',
+                                       results=results,
+                                       word_suggestions=u', '.join(word_suggestions),
+                                       name_suggestions=u', '.join(name_suggestions),
+                                       content_suggestions=u', '.join(content_suggestions),
+                                       omitted_words=u', '.join(omitted_words),
+                                       history=history,
+                )
+            else:
+                html = render_template('search.html',
+                                       results=results,
+                                       name_suggestions=u', '.join(name_suggestions),
+                                       content_suggestions=u', '.join(content_suggestions),
+                                       query=query,
+                                       medium_search_form=search_form,
+                                       item_name=item_name,
+                                       omitted_words=u', '.join(omitted_words),
+                                       history=history,
+                )
             flaskg.clock.stop('search render')
     else:
         html = render_template('search.html',

MoinMoin/constants/keys.py

 ITEMLINKS = u"itemlinks"
 ITEMTRANSCLUSIONS = u"itemtransclusions"
 TAGS = u"tags"
+CONTENTNGRAM = u"contentngram"
 
 ACTION = u"action"
 ADDRESS = u"address"

MoinMoin/converter/_args_wiki.py

 # see parse() docstring for example
 _parse_rules = r'''
 (?:
-    ([-\w]+)
+    ([-&\w]+)
     =
 )?
 (?:

MoinMoin/converter/_tests/test_image.py

 
 
 class TestImg(object):
+
+    def serialize(self, elem, **options):
+        from StringIO import StringIO
+        buffer = StringIO()
+        elem.write(buffer.write, **options)
+        return buffer.getvalue()
+
     def setup_class(self):
         self.converter = ConverterPage()
 
     def testImage(self):
+        """Tests if a set of imagetypes, result inside an img tag"""
         tree_xml = ('<ns0:page ns0:page-href="wiki:///Home" xmlns:ns0="http://moinmo.in/namespaces/page" '
                     'xmlns:ns1="http://www.w3.org/2001/XInclude" xmlns:ns2="http://www.w3.org/1999/xhtml" '
                     'xmlns:ns3="http://www.w3.org/1999/xlink"><ns0:body><ns0:p ns2:data-lineno="1">'
                     '<ns0:page ns2:class="moin-transclusion" ns0:page-href="wiki:///imagetest" ns2:data-href="/imagetest">'
                     '<ns0:body><ns0:object ns3:href="/+get/+2882c905b2ab409fbf79cd05637a112d/imagetest" ns0:type="{0}" />'
                     '</ns0:body></ns0:page></ns0:p></ns0:body></ns0:page>')
+
         tests = [
-            ('image/jpeg', 'img'),
-            ('image/svg+xml', 'img'),
-            ('image/png', 'img'),
-            ('image/gif', 'img'),
+            'image/jpeg',
+            'image/svg+xml',
+            'image/png',
+            'image/gif',
         ]
 
-        for imagetype, tag_expected in tests:
-            self.runTest(tree_xml.format(imagetype), tag_expected)
+        output = ('<div xmlns="http://www.w3.org/1999/xhtml"><p data-lineno="1"><span class="moin-transclusion" '
+                  'data-href="/imagetest"><img src="/+get/+2882c905b2ab409fbf79cd05637a112d/imagetest">'
+                  '</span></p></div>')
 
-    def runTest(self, tree_xml, tag_expected):
+        for imagetype in tests:
+            self.runTest(tree_xml.format(imagetype), output)
+
+    def test_resize(self):
+        """Tests if resize attributes convert to respective html tag resize attributes"""
+        image_resize = ('<ns0:page xmlns:ns0="http://moinmo.in/namespaces/page" '
+                        'xmlns:ns2="http://www.w3.org/1999/xhtml" xmlns:ns3="http://www.w3.org/1999/xlink">'
+                        '<ns0:body><ns0:p><ns0:page>'
+                        '<ns0:body><ns0:object ns3:href="/+get/+2882c905b2ab409fbf79cd05637a112d/imagetest" '
+                        'ns2:height="10" ns2:width="10" ns0:type="image/jpeg" />'
+                        '</ns0:body></ns0:page></ns0:p></ns0:body></ns0:page>')
+
+        image_resize_out = ('<div xmlns="http://www.w3.org/1999/xhtml"><p><div><img height="10" '
+                            'src="/+get/+2882c905b2ab409fbf79cd05637a112d/imagetest" width="10">'
+                            '</div></p></div>')
+
+        self.runTest(image_resize, image_resize_out)
+
+    def runTest(self, tree_xml, output):
         tree = ET.XML(tree_xml)
         tree = self.converter(tree)
-        assert len(tree) and len(tree[0]) and len(tree[0][0]) == 1
-        assert tree[0][0][0].tag.name == tag_expected
+        assert self.serialize(tree, method="html") == output

MoinMoin/converter/_tests/test_moinwiki_in.py

 
 import re
 
-from MoinMoin.util.tree import moin_page, xlink
+from MoinMoin.util.tree import moin_page, xlink, html, xinclude
 
 from MoinMoin.converter.moinwiki_in import Converter
 
     namespaces = {
         moin_page: '',
         xlink: 'xlink',
+        html: 'xhtml',
+        xinclude: 'xinclude',
     }
 
     output_re = re.compile(r'\s+xmlns(:\S+)?="[^"]+"')
                 '<page><body><p><a xlink:href="http://moinmo.in/">MoinMoin</a></p></body></page>'),
             (u'[[MoinMoin]]',
                 '<page><body><p><a xlink:href="wiki.local:MoinMoin">MoinMoin</a></p></body></page>'),
+            (u'{{somelocalimage||width=10, height=10}}',
+                '<page><body><p><xinclude:include xhtml:height="10" xhtml:width="10" xinclude:href="wiki.local:somelocalimage?" /></p></body></page>'),
+            (u'{{somelocalimage||width=10, &h=10}}',
+                '<page><body><p><xinclude:include xhtml:width="10" xinclude:href="wiki.local:somelocalimage?h=10" /></p></body></page>'),
+            (u'{{http://moinmo.in/|test|width=10, height=10}}',
+                '<page><body><p><object alt="test" xhtml:height="10" xhtml:width="10" xlink:href="http://moinmo.in/">test</object></p></body></page>'),
             (u'{{http://moinmo.in/}}',
                 '<page><body><p><object xlink:href="http://moinmo.in/" /></p></body></page>', None, 'unknown'),
             (u'{{http://moinmo.in/|MoinMoin}}',

MoinMoin/converter/_tests/test_rst_in.py

     namespaces = {
         moin_page.namespace: '',
         xlink.namespace: 'xlink',
+        html: 'xhtml',
     }
 
     output_re = re.compile(r'\s+xmlns(:\S+)?="[^"]+"')
 
     def test_image(self):
         data = [
-            (u'.. image:: images/biohazard.png', '<page><body><object xlink:href="images/biohazard.png" /></body></page>'),
+            (u'.. image:: images/biohazard.png', '<page><body><object xlink:href="wiki.local:images/biohazard.png?do=get" /></body></page>'),
             (u""".. image:: images/biohazard.png
    :height: 100
    :width: 200
    :scale: 50
-   :alt: alternate text""", '<page><body><object alt="images/biohazard.png" height="100" scale="50" width="200" xlink:href="images/biohazard.png" /></body></page>'),
-            (u'abc |a| cba\n\n.. |a| image:: test.png', '<page><body><p>abc <object alt="test.png" xlink:href="test.png" /> cba</p></body></page>'),
+   :alt: alternate text""", '<page><body><object xhtml:alt="alternate text" xhtml:height="50" xhtml:width="100" xlink:href="wiki.local:images/biohazard.png?do=get" /></body></page>'),
+            (u'abc |test| cba\n\n.. |test| image:: test.png', '<page><body><p>abc <object xhtml:alt="test" xlink:href="wiki.local:test.png?do=get" /> cba</p></body></page>'),
         ]
         for i in data:
             yield (self.do, ) + i

MoinMoin/converter/html_out.py

         # TODO: maybe IE8 would display transcluded external pages if we could do <object... type="text/html" ...>
         href = elem.get(xlink.href, None)
         attrib = {}
+        whitelist = ['width', 'height']
+        for key in elem.attrib:
+            if key.name in whitelist:
+                attrib[key] = elem.attrib[key]
         mimetype = Type(_type=elem.get(moin_page.type_, CONTENTTYPE_NONEXISTENT))
         # Get the object type
         obj_type = self.eval_object_type(mimetype, href)

MoinMoin/converter/image_in.py

 from emeraldtree import ElementTree as ET
 
 from MoinMoin.util.iri import Iri
-from MoinMoin.util.tree import moin_page, xlink
+from MoinMoin.util.tree import moin_page, xlink, xinclude, html
+from werkzeug import url_encode, url_decode
+
+from MoinMoin.constants.contenttypes import CHARSET
 
 
 class Converter(object):
 
     def __call__(self, rev, contenttype=None, arguments=None):
         item_name = rev.item.name
-        attrib = {
+        query_keys = {'do': 'get', 'rev': rev.revid}
+        attrib = {}
+        if arguments:
+            query = arguments.keyword.get(xinclude.href).query
+            query_keys.update(url_decode(query))
+            attrib = arguments.keyword
+
+        query = url_encode(query_keys, charset=CHARSET, encode_keys=True)
+
+        attrib.update({
             moin_page.type_: unicode(self.input_type),
             xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name,
-                            query='do=get&rev={0}'.format(rev.revid)),
-        }
+                            query=query),
+        })
+
         obj = moin_page.object_(attrib=attrib, children=[item_name, ])
         body = moin_page.body(children=(obj, ))
         return moin_page.page(children=(body, ))

MoinMoin/converter/include.py

 
 from MoinMoin.converter.html_out import mark_item_as_transclusion, Attributes
 
+from ._args import Arguments
+
 # elements generated by moin wiki markup that cannot have block children
 NO_BLOCK_CHILDREN = [
     'p',
                         elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, ))
                         included_elements.append(elem_h)
 
-                    page_doc = page.content.internal_representation()
+                    page_doc = page.content.internal_representation(attributes=Arguments(keyword=elem.attrib))
                     # page_doc.tag = self.tag_div # XXX why did we have this?
 
                     self.recurse(page_doc, page_href)

MoinMoin/converter/moinwiki_in.py

 from MoinMoin.constants.contenttypes import CHARSET
 from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
-from MoinMoin.util.tree import moin_page, xlink, xinclude
+from MoinMoin.util.tree import moin_page, xlink, xinclude, html
 from MoinMoin.util.interwiki import is_known_wiki
 from MoinMoin.i18n import _
 
             args = parse_arguments(object_args).keyword  # XXX needs different parsing
         else:
             args = {}
+
+        query_keys = {}
+        attrib = {}
+        whitelist = ['width', 'height']
+        for attr, value in args.iteritems():
+            if attr.startswith('&'):
+                query_keys[attr[1:]] = value
+            elif attr in whitelist:
+                attrib[html(attr)] = value
+
         if object_item is not None:
-            query = url_encode(args, charset=CHARSET, encode_keys=True)
+            query = url_encode(query_keys, charset=CHARSET, encode_keys=True)
             att = 'attachment:'  # moin 1.9 needed this for an attached file
             if object_item.startswith(att):
                 object_item = '/' + object_item[len(att):]  # now we have a subitem
             target = Iri(scheme='wiki.local', path=object_item, query=query, fragment=None)
             text = object_item
 
-            attrib = {xinclude.href: target}
+            attrib[xinclude.href] = target
+
             element = xinclude.include(attrib=attrib)
             stack.top_append(element)
         else:
             target = Iri(object_url)
             text = object_url
 
-            attrib = {xlink.href: target}
+            attrib[xlink.href] = target
+
             if object_text is not None:
                 attrib[moin_page.alt] = object_text
 

MoinMoin/converter/rst_in.py

 import re
 import pytest
 
+from werkzeug import url_encode, url_decode
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
 from MoinMoin import config
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import html, moin_page, xlink
+from MoinMoin.constants.contenttypes import CHARSET
 
 from ._util import allowed_uri_scheme, decode_data, normalize_split_text
 
         pass
 
     def visit_image(self, node):
-        new_node = moin_page.object(attrib={xlink.href: node['uri']})
-        # TODO: rewrite this more compact
-        alt = node.get('alt', None)
-        if alt:
-            new_node.set(moin_page.alt, node['uri'])
-        arg = node.get('width', u'')
-        if arg:
-            new_node.set(moin_page.width, arg)
-        arg = node.get('height', u'')
-        if arg:
-            new_node.set(moin_page.height, arg)
+        whitelist = ['width', 'height', 'align', 'alt', ]
+        attr = {}
+        for key in whitelist:
+            if node.get(key):
+                attr[html(key)] = node.get(key)
 
-        # TODO: there is no 'scale' attribute in moinwiki
-        arg = node.get('scale', u'')
-        if arg:
-            new_node.set(moin_page.scale, arg)
+        #there is no 'scale' attribute, hence absent from whitelist, handled separately
+        #TODO: Error reporting in case of bad input, currently prints on terminal
+        #TODO: mark_item_as_transclusion in html_out conflicts 'align' by adding item-wrapper
+
+        if node.get('scale'):
+            scaling_factor = int(node.get('scale')) / 100.0
+            for key in ('width', 'height'):
+                if html(key) in attr:
+                    attr[html(key)] = int(int(attr[html(key)]) * scaling_factor)
+
+        new_node = moin_page.object(attr)
+        url = Iri(node['uri'])
+        if url.scheme is None:
+            url.scheme = u'wiki.local'
+            query_keys = url_decode(url.query or '')
+            query_keys['do'] = 'get'
+            url.query = url_encode(query_keys, charset=CHARSET, encode_keys=True)
+        new_node.set(xlink.href, url)
 
         self.open_moin_page_node(new_node)
 

MoinMoin/items/content.py

     data = property(fget=get_data)
 
     @timed('conv_in_dom')
-    def internal_representation(self, converters=['smiley']):
+    def internal_representation(self, converters=['smiley'], attributes=None):
         """
         Return the internal representation of a document using a DOM Tree
         """
 
             # We can process the conversion
             links = Iri(scheme='wiki', authority='', path='/' + self.name)
-            doc = input_conv(self.rev, self.contenttype)
+            doc = input_conv(self.rev, self.contenttype, arguments=attributes)
             # XXX is the following assuming that the top element of the doc tree
             # is a moin_page.page element? if yes, this is the wrong place to do that
             # as not every doc will have that element (e.g. for images, we just get

MoinMoin/static/js/common.js

+//
+// MoinMoin2 commonly used JavaScript functions
+//
+/*jslint browser: true, nomen: true, todo: true*/
+/*global $:true, _:true*/
+
+function MoinMoin() {
+    "use strict";
+}
+// Utility function to add a message to moin flash area.
+MoinMoin.prototype.MOINFLASHINFO = "moin-flash moin-flash-info";
+MoinMoin.prototype.MOINFLASHWARNING = "moin-flash moin-flash-warning";
+MoinMoin.prototype.moinFlashMessage = function (classes, message) {
+    "use strict";
+    var pTag = '<P class="' + classes + '">' + message + '</p>';
+    $(pTag).appendTo($('#moin-flash'));
+};
+
+
+// Highlight currently selected link in side panel. Executed on page load
+MoinMoin.prototype.selected_link = function () {
+    "use strict";
+    var selected = window.location.pathname,
+        list = $('.panel'),
+        i,
+        j,
+        nav_links,
+        link;
+    for (j = 0; j < list.length; j += 1) {
+        nav_links = list[j].getElementsByTagName('a');
+
+        for (i = 0; i < nav_links.length; i += 1) {
+            link = nav_links[i].attributes.href.value;
+
+            if (link === selected) {
+                nav_links[i].setAttribute('class', 'current-link');
+                break;
+            }
+        }
+    }
+};
+
+
+// toggleComments is executed when user clicks a Comments button and conditionally on dom ready.
+MoinMoin.prototype.toggleComments = function () {
+    "use strict";
+    // Toggle visibility of every tag with class "comment"
+    var pageComments = $('.comment'),   // will hold list of elements with class "comment"
+        buttons = $('.moin-toggle-comments-button > a');
+    if (pageComments.is(':hidden')) {
+        pageComments.show();
+        buttons.attr('title', _("Hide comments"));
+    } else {
+        pageComments.hide();
+        buttons.attr('title', _("Show comments"));
+    }
+};
+
+// Comments initialization is executed once after document ready.
+MoinMoin.prototype.initToggleComments = function () {
+    "use strict";
+    var pageComments = $('.comment');
+    if (pageComments.length > 0) {
+        // There are comments, so show itemview Comments button
+        $('.moin-toggle-comments-button').css('display', '');
+        $('.moin-toggle-comments-button').click(toggleComments);
+        // comments are visible; per user option, hide comments if there is not a <br id="moin-show-comments" />
+        if (!document.getElementById('moin-show-comments')) {
+            this.toggleComments();
+        }
+        else {
+            var commentButton = $('.moin-toggle-comments-button');
+            commentButton.button('toggle');
+            {{ "commentButton.attr('title', '%s');" % _("Hide comments") }}
+        }
+    }
+    $('.moin-toggle-comments-button').click(this.toggleComments);
+};
+
+
+
+// toggleTransclusionOverlays is executed when user clicks a Transclusions button on the Show item page.
+MoinMoin.prototype.toggleTransclusionOverlays = function () {
+    "use strict";
+    var overlays = $('.moin-item-overlay-ul, .moin-item-overlay-lr'),
+        buttons;
+    if (overlays.length > 0) {
+        buttons = $('.moin-transclusions-button > a, .moin-transclusions-button');
+        if (overlays.is(':visible')) {
+            overlays.hide();
+            buttons.attr('title', _("Show transclusions"));
+        } else {
+            overlays.show();
+            buttons.attr('title', _("Hide transclusions"));
+        }
+    }
+};
+
+// Transclusion initialization is executed once after document ready.
+MoinMoin.prototype.initTransclusionOverlays = function () {
+    "use strict";
+    var elem, overlayUL, overlayLR, wrapper, wrappers, transclusions,
+        rightArrow = '\u2192';
+    // get list of elements to be wrapped; must work in reverse order in case there are nested transclusions
+    transclusions = $($('.moin-transclusion').get().reverse());
+    transclusions.each(function (index) {
+        elem = transclusions[index];
+        // if this is the transcluded item page, do not wrap (avoid creating useless overlay links to same page)
+        if (location.pathname !== elem.getAttribute('data-href')) {
+            if (elem.tagName === 'DIV') {
+                wrapper = $('<div class="moin-item-wrapper"></div>');
+            } else {
+                wrapper = $('<span class="moin-item-wrapper"></span>');
+            }
+            overlayUL = $('<a class="moin-item-overlay-ul"></a>');
+            $(overlayUL).attr('href', elem.getAttribute('data-href'));
+            $(overlayUL).append(rightArrow);
+            overlayLR = $(overlayUL).clone(true);
+            $(overlayLR).attr('class', 'moin-item-overlay-lr');
+            // if the parent of this element is an A, then wrap parent (avoid A's within A's)
+            if ($(elem).parent()[0].tagName === 'A') {
+                elem = $(elem).parent()[0];
+            }
+            // insert wrapper after elem, append (move) elem, append overlays
+            $(elem).after(wrapper);
+            $(wrapper).append(elem);
+            $(wrapper).append(overlayUL);
+            $(wrapper).append(overlayLR);
+        }
+    });
+    // if an element was wrapped above, then make the Transclusions buttons visible
+    wrappers = $('.moin-item-wrapper');
+    if (wrappers.length > 0) {
+        $('.moin-transclusions-button').css('display', '');
+        $('.moin-transclusions-button').click(toggleTransclusionOverlays);
+    }
+    $('.moin-transclusions-button').click(this.toggleTransclusionOverlays);
+};
+
+
+
+// Executed on page load.  If logged in user has less than 6 quicklinks,  do nothing.
+// Else, show the first five links, hide the others, and append a >>> button to show hidden quicklinks on mouseover.
+MoinMoin.prototype.QuicklinksExpander = function () {
+    "use strict";
+    var QUICKLINKS_EXPAND = ">>>",
+        QUICKLINKS_COLLAPSE = "<<<",
+        QUICKLINKS_MAX = 5,
+        newThis;
+    // 8 helper functions
+    function getLinks() {
+        return $(".userlink:not(.moin-navibar-icon)");
+    }
+    function createIcon(txt) {
+        var li = document.createElement("li"),
+            arrows = document.createTextNode(txt);
+        li.setAttribute("class", "moin-userlink moin-navibar-icon");
+        li.appendChild(arrows);
+        return li;
+    }
+    function appendIcon(txt) {
+        var elem = createIcon(txt);
+        document.getElementById("moin-navibar").appendChild(elem);
+        return elem;
+    }
+    function shouldHide(links) {
+        // links should be hidden only if user has created so many that it impacts nice page layout
+        return (links.length > QUICKLINKS_MAX);
+    }
+    function getHideableLinks() {
+        return getLinks().slice(QUICKLINKS_MAX);
+    }
+    function hideShowHideableLinks(action) {
+        getHideableLinks().each(function () {
+            if (action === "hide") {
+                $(this).hide();
+            } else {
+                $(this).show();
+            }
+        });
+    }
+    function hideLinks() {
+        hideShowHideableLinks("hide");
+    }
+    function showLinks() {
+        hideShowHideableLinks("show");
+    }
+
+    this.getLinks = getLinks;
+    this.appendIcon = appendIcon;
+    this.shouldHide = shouldHide;
+    this.getHideableLinks = getHideableLinks;
+    this.hideLinks = hideLinks;
+    this.showLinks = showLinks;
+    this.navibar = $("#moin-header");
+    this.link = this.getLinks();
+    this.hideable = this.getHideableLinks();
+
+    if (this.shouldHide(this.link)) {
+        this.expandIcon = $(this.appendIcon(QUICKLINKS_EXPAND));
+        this.closeIcon = $(this.appendIcon(QUICKLINKS_COLLAPSE));
+        this.closeIcon.hide();
+        // Hide everything after the first QUICKLINKS_MAX links
+        this.hideLinks();
+        newThis = this;
+        // When the user mouses over the icon link,
+        // Show the hidden links
+        this.expandIcon.mouseenter(function () {
+            newThis.showLinks();
+            newThis.expandIcon.hide();
+            newThis.closeIcon.show();
+        });
+        this.closeIcon.mouseenter(function () {
+            newThis.hideLinks();
+            newThis.expandIcon.show();
+            newThis.closeIcon.hide();
+        });
+    }
+};
+
+
+
+// When a page has subitems, this toggles the subtrees in the Subitems sidebar.
+MoinMoin.prototype.toggleSubtree = function (item) {
+    "use strict";
+    var subtree = $(item).siblings("ul");
+    subtree.toggle(200);
+};
+
+
+
+// OnMouseOver show the fqname of the item else only show the value/id.
+function togglefqname(){
+    "use strict";
+    var fullname, value;
+    $(".moin-fqname").hover(function () {
+        fullname = $(this).attr('data-fqname');
+        value = $(this).html();
+        $(this).html(fullname);
+    },function () {
+        $(this).html(value);
+    });
+}
+$(document).ready(togglefqname);
+
+
+// Executed when user clicks insert-name button defined in modify.html.
+// When a page with subitems is modified, a subitems sidebar is present. User may
+// position caret in textarea and click button to insert name into textarea.
+MoinMoin.prototype.InsertName = function (fullname) {
+    "use strict";
+    var textArea, endPos, startPos;
+    textArea = document.getElementById('f_content_form_data_text');
+    startPos = textArea.selectionStart;
+    endPos = textArea.selectionEnd;
+    textArea.value = textArea.value.substring(0, startPos) + fullname + textArea.value.substring(endPos, textArea.value.length);
+    textArea.focus();
+    textArea.setSelectionRange(startPos + fullname.length, startPos + fullname.length);
+};
+
+
+// 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.
+MoinMoin.prototype.enhanceUserSettings = function () {
+    "use strict";
+    // do nothing if this is not a User Settings page
+    if ($('#moin-usersettings').length === 0) { return; }
+
+    // create a UL that will be displayed as row of tabs or column of buttons
+    var tabs = $('#moin-usersettings'),
+        titles = $('<ul class="moin-tab-titles">'),
+        hashTag = window.location.hash,
+        tab;
+    // for each form on page, create a corresponding LI
+    $('.moin-tab-body').each(function () {
+        var li = $(document.createElement('li')),
+        // 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;
+            window.location.hash = tab;
+            $('.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;
+        });
+    });
+    // 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);
+
+    // check for the hashtag and switch tab
+    if (hashTag !== '') {
+        tab = $('.moin-tab-titles li a[href="' + hashTag + '"]');
+        if (tab.length !== 0) {
+            $(tab)[0].click();
+        }
+    } else {
+        // click a tab to show first form and hide all other forms
+        $(titles.children('li').children('a')[0]).click();
+    }
+
+    // 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());
+    });
+
+    // add/remove "*" indicator if user changes/saves form
+    function changeHandler(ev) {
+        var form = $(ev.currentTarget),
+            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)
+            $('.moin-change-indicator', title).remove();
+        } else {
+            // the values differ
+            if (!$('.moin-change-indicator', title).length) {
+                // only add a change indicator if there none
+                title.append($('<span class="moin-change-indicator">*</span>'));
+            }
+        }
+    }
+    // attach above function to all forms as a change handler
+    $('#moin-usersettings form').change(changeHandler);
+
+    // executed when user clicks submit button on a user settings form
+    function submitHandler(ev) {
+        var form = $(ev.target),
+            button = $('button', form),
+            buttonBaseText = button.html(),
+            buttonDotList = [' .&nbsp;&nbsp;', ' &nbsp;.&nbsp;', ' &nbsp;&nbsp;.'],
+            buttonDotIndex = 0,
+            buttonDotAnimation;
+
+        // disable the button
+        button.attr('disabled', true);
+
+        // remove change indicators from the current tab as we are now saving it
+        $('.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
+        function buttonRunAnimation() {
+            button.html(buttonBaseText + buttonDotList[buttonDotIndex % buttonDotList.length]);
+            buttonDotIndex += 1;
+        }
+        buttonDotAnimation = setInterval(buttonRunAnimation, 500);
+        buttonRunAnimation();
+
+        // send the form to the server
+        $.post(form.attr('action'), form.serialize(), function (data) {
+            var i, f, newform;
+            clearInterval(buttonDotAnimation);
+            // if the response indicates a redirect, set the new location
+            if (data.redirect) {
+                location.href = data.redirect;
+                return;
+            }
+            // remove all flash messages previously added via javascript
+            $('#moin-flash .moin-flash-javascript').remove();
+            // add new flash messages from the response
+            for (i = 0; i < data.flash.length; i += 1) {
+                f = $(document.createElement('p'));
+                f.html(data.flash[i][0]);
+                f.addClass('moin-flash');
+                f.addClass('moin-flash-javascript');
+                f.addClass('moin-flash-' + data.flash[i][1]);
+                $('#moin-flash').append(f);
+            }
+            // get the new form element from the response
+            newform = $(data.form);
+            // set event handlers on the new form
+            newform.submit(submitHandler);
+            newform.change(changeHandler);
+            // store the forms initial data
+            newform.data('initialForm', newform.serialize());
+            // replace the old form with the new one
+            form.replaceWith(newform);
+        }, 'json');
+        return false;
+    }
+    // attach above function as a submit handler to each user setting form
+    $('#moin-usersettings form').submit(submitHandler);
+
+    // 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
+MoinMoin.prototype.enhanceEdit = 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+).
+    //       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,
+        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
+        this.moinFlashMessage(this.MOINFLASHWARNING, MESSAGEOLD);
+    }
+
+    // called after +modify page loads -- scrolls the textarea after a doubleclick
+    function scrollTextarea(jumpLine) {
+        // jumpLine is textarea scroll-to line
+        var textArea = document.getElementById('f_content_form_data_text'),
+            textLines,
+            scrolledText,
+            scrollAmount,
+            textAreaClone;
+
+        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();
+            scrolledText = textLines.split("\n"); // all browsers yield \n rather than \r\n or \r
+            scrolledText = scrolledText.slice(0, jumpLine);
+            if (navigator.userAgent && navigator.userAgent.substring(0, OPERA.length) === OPERA) {
+                scrolledText = scrolledText.join('\r\n') + '\r\n';
+            } else {
+                scrolledText = scrolledText.join('\n') + '\n';
+            }
+            // clone textarea, paste in truncated textArea data, measure height, delete clone
+            textAreaClone = $(textArea).clone(true);
+            textAreaClone = textAreaClone[0];
+            textAreaClone.id = "moin-textAreaClone";
+            textArea.parentNode.appendChild(textAreaClone);
+            $("#moin-textAreaClone").val(scrolledText);
+            textAreaClone.rows = 1;
+            scrollAmount = textAreaClone.scrollHeight - 100; // get total height of clone - 100 pixels
+            textAreaClone.parentNode.removeChild(textAreaClone);
+            // position the caret
+            textArea.focus();
+            if (scrollAmount > 0) { textArea.scrollTop = scrollAmount; }
+            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);
+            }
+        }
+    }
+
+    // called after a "show" page loads, scroll page to textarea caret position
+    function scrollPage(lineno) {
+        var elem = document.getElementById(TOPID),
+            notFound = true,
+            RADIX = 10,
+            saveColor;
+
+        lineno = parseInt(lineno, RADIX);
+        // find a starting point at bottom of moin-content
+        while (elem.lastChild) { elem = elem.lastChild; }
+        // walk DOM backward looking for a lineno attr equal or less than lineno
+        while (notFound && elem.id !== TOPID) {
+            if (elem.hasAttribute && elem.hasAttribute(LINENOATTR) && parseInt(elem.getAttribute(LINENOATTR), RADIX) <= lineno) {
+                notFound = false;
+            }
+            if (notFound) {
+                if (elem.previousSibling) {
+                    elem = elem.previousSibling;
+                    while (elem.lastChild) { elem = elem.lastChild; }
+                } else {
+                    elem = elem.parentNode;
+                }
+            }
+        }
+        // scroll element into view and then back off 100 pixels
+        // TODO: does not scroll when user setting for show comments is off; user toggles show comments on; user doubleclicks and updates comments; (elem has display:none)
+        elem.scrollIntoView();
+        window.scrollTo(window.pageXOffset, window.pageYOffset - 100);
+        // highlight background of selected element for a second or so
+        saveColor = elem.style.backgroundColor;
+        elem.style.backgroundColor = 'yellow';
+        setTimeout(function () { elem.style.backgroundColor = saveColor; }, 1500);
+    }
+
+    // called after user doubleclicks, return a line number close to doubleclick point
+    function findLineNo(elem) {
+        var lineno;
+        // first try easy way via jquery checking event node and all parent nodes
+        lineno = $(elem).closest("[" + LINENOATTR + "]");
+        if (lineno.length) { return $(lineno).attr(LINENOATTR); }
+        // walk DOM backward looking for a lineno attr among siblings, cousins, uncles...
+        while (elem.id !== TOPID) {
+            if (elem.hasAttribute && elem.hasAttribute(LINENOATTR)) {
+                // not perfect, a lineno prior to target
+                return elem.getAttribute(LINENOATTR);
+            }
+            if (elem.previousSibling) {
+                elem = elem.previousSibling;
+                while (elem.lastChild) { elem = elem.lastChild; }
+            } else {
+                elem = elem.parentNode;
+            }
+        }
+        // user double-clicked on dead zone so we walked back to #moin-content
+        return 0;
+    }
+
+    // called after user clicks OK button to save edit changes
+    function getCaretLineno(textArea) {
+        // return the line number of the textarea caret
+        var caretPoint,
+            textLines;
+        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;
+        }
+        // get textarea text, split at caret, return number of lines before caret
+        if (navigator.userAgent && navigator.userAgent.substring(0, OPERA.length) === OPERA) {
+            textLines = textArea.value;
+        } else {
+            textLines = $(textArea).val();
+        }
+        textLines = textLines.substring(0, caretPoint);
+        return textLines.split("\n").length;
+    }
+
+    // doubleclick processing starts here
+    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
+            modifyButton = $('.moin-modify-button')[0];
+            if (modifyButton) {
+                // add doubleclick event handler when user doubleclicks within the content area
+                $('#moin-content').dblclick(function (e) {
+                    // get clicked line number, save, and go to +modify page
+                    lineno = findLineNo(e.target);
+                    sessionStorage.moinDoubleLineNo = lineno;
+                    document.location = modifyButton.href;
+                });
+            }
+            if (sessionStorage.moinCaretLineNo) {
+                // we have just edited this page; scroll "show" page to last position of caret in edit textarea
+                scrollPage(sessionStorage.moinCaretLineNo);
+                sessionStorage.removeItem('moinCaretLineNo');
+            }
+        }
+
+        // Start of processing for "modify" pages
+        if (sessionStorage.moinDoubleLineNo) {
+            // this is a +modify page, scroll the textarea to the doubleclicked line
+            lineno = sessionStorage.moinDoubleLineNo;
+            sessionStorage.removeItem('moinDoubleLineNo');
+            if (lineno === '0') {
+                // give user a hint because the double-click was a miss
+                this.moinFlashMessage(this.MOINFLASHINFO, MESSAGEMISSED);
+                lineno = 1;
+            }
+            scrollTextarea(lineno - 1);
+            // is option to scroll page after edit set?
+            if (document.getElementById('moin-scroll-page-after-edit')) {
+                // add click handler to OK (save) button to capture position of caret in textarea
+                $("#moin-save-text-button").click(function () {
+                    caretLineno = getCaretLineno(document.getElementById('f_content_form_data_text'));
+                    // save lineno for use in "show" page load
+                    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')) {
+            modifyButton = $('.moin-modify-button')[0];
+            if (modifyButton) {
+                // this is a "show" page, add doubleclick event handler to content node
+                $('#moin-content').dblclick(function () {
+                    document.location = modifyButton.href;
+                });
+            }
+        } else {
+            modifyForm = $('#moin-modify')[0];
+            if (modifyForm) {
+                // user is editing with obsolete browser, give warning about missing features
+                this.moinFlashMessage(this.MOINFLASHWARNING, MESSAGEOBSOLETE);
+            }
+        }
+    }
+};
+
+$(document).ready(function () {
+    "use strict";
+    var moin = new MoinMoin();
+
+    moin.selected_link();
+    moin.initToggleComments();
+    moin.initTransclusionOverlays();
+    moin.QuicklinksExpander();
+
+    $('.moin-insertname-action').click(function () {
+        var fullname = $(this).data('name');
+        moin.InsertName(fullname);
+    });
+
+    $('.expander').click(function() {
+        moin.toggleSubtree(this);
+    });
+
+    moin.enhanceUserSettings();
+    moin.enhanceEdit();
+});

MoinMoin/static/js/index_action.js

+/*
+ * Click and submit handlers for form elements on the global index page.
+ * Copyright 2011, AkashSinha<akash2607@gmail.com>
+ * License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+ */
+
+/*jslint browser: true, nomen: true*/
+/*global $:true, _:true */
+
+// This anonymous function is executed once after a global index page loads.
+$("document").ready(function () {
+    "use strict";
+
+    var POPUP_FADE_TIME = 200, // fade in, fade out times for selected popups
+        IFRAME_CREATE_DELAY = 200, // delay between start of multiple downloads
+        IFRAME_REMOVE_DELAY = 3000, // life expectancy of iframe used for file downloads
+        MESSAGE_VIEW_TIME = 4000, // life expectancy of delete/destroy status messages
+        // delete and destroy process started and completed messages
+        ACTION_LOADING = {'delete': _("Deleting.."), 'destroy': _("Destroying..")},
+        ACTION_DONE = {'delete': _("Items deleted: "), 'destroy': _("Items destroyed: ")},
+        ACTION_FAILED = {'delete': _(", Items not deleted: "), 'destroy': _(", Items not destroyed: ")};
+
+    // called by click handlers New Item, Delete item, and Destroy item within Actions dropdown menu
+    function showpop(action) {
+        // hide Actions popup and show either New Item or Comment popup
+        $(".popup-container").css("display", "none");
+        if (action === "newitem") {
+            $("#popup-for-newitem").css("display", "block");
+            $("#file_upload").appendTo("#popup-for-newitem .popup-body");
+            $(".upload-form").css("display", "block");
+        } else {
+            $("#popup-for-action").css("display", "block");
+            $(".popup-comment").removeClass("blank");
+            $(".popup-comment").val("");
+            $(".popup-action").val(action);
+        }
+        $("#popup").fadeIn();
+        $("#lightbox").css("display", "block");
+    }
+
+    // called by click handlers within "Create new item" and "Please provide comment" popups
+    function hidepop() {
+        // hide popup
+        $("#popup").css("display", "none");
+        $("#lightbox").css("display", "none");
+    }
+
+    // called by Actions Download click handler
+    function startFileDownload(elem) {
+        // create a hidden iframe to start a file download, then remove it after 3 seconds
+        var frame = $('<iframe style="display: none;"></iframe>');
+        frame.attr('src', $(elem).attr('href'));
+        $(elem).after(frame);
+        setTimeout(function () { frame.remove(); }, IFRAME_REMOVE_DELAY);
+    }
+
+    // called by do_action when an item is successfully deleted or destroyed
+    function hide(item_link) {
+        // remove a deleted or destroyed item from current display
+        item_link.parent().remove();
+    }
+
+    // called by do_action when item cannot be deleted or destroyed
+    function show_conflict(item_link) {
+        // mark an item as having failed a delete or destroy operation
+        item_link.removeClass().addClass("moin-conflict");
+        item_link.parent().removeClass();
+    }
+
+    // executed via the "provide comment" popup triggered by an Actions Delete or Destroy selection
+    function do_action(comment, action) {
+        // create an array of selected item names
+        var links = [],
+            itemnames,
+            actionTrigger,
+            url;
+        $(".selected-item").children("a.moin-item").each(function () {
+            var itemname = $(this).attr("title");
+            links.push(itemname);
+        });
+        // hide comment popup, display "deleting..." or "destroying..."
+        $("#popup").css("display", "none");
+        $(".moin-index-message span").text(ACTION_LOADING[action]);
+        $(".moin-index-message").css("display", "block");
+        // create a transaction to delete or destroy selected items
+        itemnames = JSON.stringify(links);
+        actionTrigger = "moin-" + action + "-trigger";
+        url = $("#" + actionTrigger).attr("data-actionurl");
+        $.post(url, {
+            itemnames: itemnames,
+            comment: comment
+        }, function (data) {
+            // process post results
+            var itemnames = data.itemnames,
+                action_status = data.status,
+                success_item = 0,
+                left_item = 0,
+                message;
+            $.each(itemnames, function (itemindex, itemname) {
+                // hide (remove) deleted/destroyed items, or show conflict (ACL rules, or ?)
+                if (action_status[itemindex]) {
+                    hide($('.selected-item').children('a.moin-item[title="' + itemname + '"]'));
+                    success_item += 1;
+                } else {
+                    show_conflict($('.selected-item').children('a.moin-item[title="' + itemname + '"]'));
+                    left_item += 1;
+                }
+            });
+            // show a message summarizing delete/destroy results for 4 seconds
+            message = ACTION_DONE[action] + success_item;
+            if (left_item) {
+                message += ACTION_FAILED[action] + left_item + ".";
+            }
+            $(".moin-index-message span").text(message);
+            setTimeout(function () {
+                $(".moin-index-message").fadeOut();
+            }, MESSAGE_VIEW_TIME);
+        }, "json");
+    }
+
+    // -- Select All handlers start here
+
+    // add click handler to "Select All" tab to select/deselect all items
+    $(".moin-select-allitem").click(function () {
+        // toggle classes
+        if ($(this).hasClass("allitem-toselect")) {
+            $(".moin-item-index div").removeClass().addClass("selected-item");
+            $(this).removeClass("allitem-toselect").addClass("allitem-selected");
+        } else {
+            $(this).removeClass("allitem-selected").addClass("allitem-toselect");
+            $(".moin-item-index div").removeClass();
+        }
+    });
+
+    // -- Actions handlers start here
+
+    // add click handler to "Actions" drop down list
+    // also executed via .click call when user clicks on an action (new, download, delete, destroy)
+    $(".show-action").click(function () {
+        // show/hide actions drop down list
+        var actionsDiv = $(this).parent().parent();
+        if (actionsDiv.find("ul:first").is(":visible")) {
+            actionsDiv.find("ul:first").fadeOut(POPUP_FADE_TIME);
+            actionsDiv.removeClass("action-visible");
+        } else {
+            actionsDiv.find("ul:first").fadeIn(POPUP_FADE_TIME);
+            actionsDiv.addClass("action-visible");
+        }
+    });
+
+    // add click handler to "New Item" action tab entry
+    $("#moin-create-newitem").click(function () {
+        // show new item popup and hide actions dropdown
+        showpop("newitem");
+        $(".show-action").trigger("click");
+    });
+
+    // add click handler to close button "X" on new item popup
+    $(".popup-cancel").click(function () {
+        // if files are selected for upload, add to drag and drop area; hide popup
+        if ($("#popup-for-newitem:visible").length) {
+            $("#file_upload").appendTo("#moin-upload-cont");
+            $(".upload-form").css("display", "none");
+        }
+        hidepop();
+    });
+
+    // add submit handler to "Create" button on new item popup
+    // This is a workaround for browsers that do not support "required" attribute (ie9, safari 5.1)
+    // note: The creation of a new item is performed via action=... attribute on form
+    $("#popup-for-newitem").find("form:first").submit(function () {
+        // if no item name was provided show hint and stop form action
+        var itembox = $(this).children("input[name='newitem']"),
+            itemname = itembox.val();
+        if ($.trim(itemname) === "") {
+            itembox.addClass("blank");
+            itembox.focus();
+            return false;
+        }
+    });
+
+    // add click handler to "Download" button of Actions dropdown
+    $("#moin-download-trigger").click(function () {
+        if (!($("div.selected-item").length)) {
+            // no items selected, show message for 4 seconds
+            $('.moin-index-message span').text(_("Nothing was selected."));
+            $(".moin-index-message").fadeIn();
+            setTimeout(function () {
+                $(".moin-index-message").fadeOut();
+            }, MESSAGE_VIEW_TIME);
+        } else {
+            // download selected files (add small delay to start of multiple downloads for IE9)
+            $(".selected-item").children(".moin-download-link").each(function (index, element) {
+                // at 0 ms IE9 skipped 41 of 42 downloads, at 100 ms IE9 skipped 14 of 42, success at 200 ms
+                var wait = index * IFRAME_CREATE_DELAY;
+                setTimeout(function () { startFileDownload(element); }, wait);
+            });
+        }
+        // hide the list of actions
+        $(".show-action").trigger("click");
+    });
+
+    // add click handler to "Delete" and "Destroy" buttons of Actions dropdown
+    $(".moin-action-tab").click(function () {
+        // Show error msg if nothing selected, else show comment popup. Hide actions dropdown.
+        if (!($("div.selected-item").length)) {
+            $('.moin-index-message span').text(_("Nothing was selected."));
+            $(".moin-index-message").fadeIn();
+            setTimeout(function () {
+                $(".moin-index-message").fadeOut();
+            }, MESSAGE_VIEW_TIME);
+        } else {
+            if (this.id === "moin-delete-trigger") {
+                showpop("delete");
+            } else {
+                showpop("destroy");
+            }
+        }
+        $(".show-action").trigger("click");
+    });
+
+    // add click handler to "Submit" button on "Please provide comment..." popup
+    $(".popup-submit").click(function () {
+        // process delete or destroy action
+        var comment = $(".popup-comment").val(),
+            action = $(".popup-action").val();
+        comment = $.trim(comment);
+        do_action(comment, action);
+        hidepop();
+    });
+
+    // -- Filter by content type handlers start here
+
+    // add click handler to "Filter by content type" button
+    $(".moin-contenttypes-wrapper").children("div").click(function () {
+        // show/hide content type dropdown
+        var wrapper = $(this).parent();
+        if (wrapper.find("form:visible").length) {
+            $(".moin-contenttypes-wrapper").find("form").fadeOut(POPUP_FADE_TIME);
+            $(this).removeClass().addClass("ct-hide");
+        } else {
+            $(".moin-contenttypes-wrapper").find("form").fadeIn(POPUP_FADE_TIME);
+            $(this).removeClass().addClass("ct-shown");
+        }
+    });
+
+    // add click handler to "Toggle" button on "Filter by content type" dropdown
+    $(".filter-toggle").click(function () {
+        // reverse checked/unchecked for each content type
+        $(".moin-contenttypes-wrapper form").find("input[type='checkbox']").each(function () {
+            if ($(this).attr("checked")) {
+                $(this).removeAttr("checked");
+            } else {
+                $(this).attr("checked", "checked");
+            }
+        });
+        return false;
+    });
+
+    // add click handler to "More" button on "Filter by content type" dropdown
+    $(".filter-more").click(function () {
+        // show/hide help text describing each content type
+        var helper_texts = $(".moin-contenttypes-wrapper form").find(".helper-text:visible");
+        if (helper_texts.length) {
+            helper_texts.fadeOut();
+        } else {
+            $(".moin-contenttypes-wrapper form").find(".helper-text").css("display", "block");
+        }
+
+        return false;
+    });
+
+    // -- individual item handlers start here
+
+    // add click handlers to all items shown on global index page
+    $(".moin-select-item").click(function () {
+        // toggle selection classes
+        if ($(this).parent().hasClass("selected-item")) {
+            $(this).parent().removeClass("selected-item");
+            if ($(".moin-select-allitem").hasClass("allitem-selected")) {
+                $(".moin-select-allitem").removeClass("allitem-selected").addClass("allitem-toselect");
+            }
+        } else {
+            $(this).parent().addClass("selected-item");
+        }
+    });
+});

MoinMoin/static/js/jquery.i18n.min.js

+/*
+ * jQuery i18n plugin
+ * @requires jQuery v1.1 or later
+ *
+ * See http://recursive-design.com/projects/jquery-i18n/
+ *
+ * Licensed under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ * Version: 1.0.0 (201210141329)
+ */
+(function(f){f.i18n={dict:null,setDictionary:function(a){this.dict=a},_:function(a,d){var e=a;if(this.dict&&this.dict[a])e=this.dict[a];return this.printf(e,d)},printf:function(a,d){if(!d)return a;for(var e="",c=/%(\d+)\$s/g,b=c.exec(a);b;){var g=parseInt(b[1],10)-1;a=a.replace("%"+b[1]+"$s",d[g]);b=c.exec(a)}c=a.split("%s");if(c.length>1)for(b=0;b<d.length;b++){if(c[b].length>0&&c[b].lastIndexOf("%")==c[b].length-1)c[b]+="s"+c.splice(b+1,1)[0];e+=c[b]+d[b]}return e+c[c.length-1]}};f.fn._t=function(a,
+d){return f(this).text(f.i18n._(a,d))}})(jQuery);

MoinMoin/static/js/search.js

+$(document).ready(function(){
+	// kill form action on pressing Enter
+	$('#moin-long-searchform').submit(function(e){
+		e.preventDefault();
+		return false;
+	});
+
+	// hide form submit button
+	$('#moin-long-searchform .button').hide();
+
+	function ajaxify(query, allrevs) {
+		$.ajax({
+  			type: "GET",
+  			url: "/+search",
+  			data: { q: query, history: allrevs, boolajax: true }
+		}).done(function( html ) {
+			$('#finalresults').html(html)
+		});
+	}
+	$('#moin-search-query').keyup(function() {
+		var allrev = false
+		if($('[name="history"]').prop('checked')){
+ 			allrev = true;
+		}		
+  		ajaxify($(this).val(), allrev);
+	});
+});

MoinMoin/storage/middleware/indexing.py

 from flask import g as flaskg
 from flask import current_app as app
 
-from whoosh.fields import Schema, TEXT, ID, IDLIST, NUMERIC, DATETIME, KEYWORD, BOOLEAN
+from whoosh.fields import Schema, TEXT, ID, IDLIST, NUMERIC, DATETIME, KEYWORD, BOOLEAN, NGRAMWORDS
 from whoosh.writing import AsyncWriter
 from whoosh.qparser import QueryParser, MultifieldParser, RegexPlugin, PseudoFieldPlugin
 from whoosh.qparser import WordNode
     doc[WIKINAME] = wikiname
     doc[CONTENT] = content
     doc[BACKENDNAME] = backend_name
+    if CONTENTNGRAM in schema:
+        doc[CONTENTNGRAM] = content
     return doc
 
 
             # TRASH from metadata
             TRASH: BOOLEAN(stored=True),
             # data (content), converted to text/plain and tokenized
-            CONTENT: TEXT(stored=True),
+            CONTENT: TEXT(stored=True, spelling=True),
         }
 
         latest_revs_fields = {
             ITEMTRANSCLUSIONS: ID(stored=True),
             # tokenized ACL from metadata
             ACL: TEXT(analyzer=AclTokenizer(acl_rights_contents), multitoken_query="and", stored=True),
+            # ngram words, index ngrams of words from main content
+            CONTENTNGRAM: NGRAMWORDS(minsize=3, maxsize=6),
         }
         latest_revs_fields.update(**common_fields)
 

MoinMoin/templates/ajaxsearch.html

+<br/>
+{% if omitted_words %}
+  <p>{{ _("common words in query: %(termlist)s", termlist=omitted_words) }}</p>
+{% endif %}
+<br/> 
+{% if results is defined %}
+    <p class="searchstats">
+        {% if results %}
+        {{ _("%(result_len)d results found (%(runtime).3f secs).",
+              result_len=results|length, runtime=results.runtime
+            )
+        }}
+        {% else %}
+        {{ _("No results found (%(runtime).3f secs).", runtime=results.runtime) }}
+        {% endif %}
+    </p>
+    {% endif %}
+
+    {% if results is defined %}
+        {% if word_suggestions %}
+            <p>{{ _("input suggestions: %(termlist)s", termlist=word_suggestions) }}</p>
+        {% endif %}
+        {% if name_suggestions or content_suggestions %}
+            <p>{{ _("name term suggestions: %(termlist)s", termlist=name_suggestions) }}</p>
+            <p>{{ _("content term suggestions: %(termlist)s", termlist=content_suggestions) }}</p>
+        {% endif %}
+        {% if results %}
+            <div class="searchresults">
+                <table>
+                    {% for result in results %}
+                        {% if result['wikiname'] == cfg.interwikiname %}
+                            <tr>
+                                <td class="moin-wordbreak">{{ result.pos + 1 }}
+                                    {% if history %}
+                                        <a href="{{ url_for_item(item_name=result['revid'], wiki_name='Self', namespace=result['namespace'], field='revid') }}"><b>{{ result['name'] | join(' | ')}}</b></a>
+                                    {% else %}
+                                        <a href="{{ url_for_item(item_name=result['name'][0], wiki_name='Self', namespace=result['namespace']) }}"><b>{{ result['name'] | join(' | ')}}</b></a>
+                                    {% endif %}
+                                </td>
+                            </tr>
+                            {% if result['summary'] %}
+                                <tr>
+                                    <td>
+                                        <p class="info searchhitinfobar">{{ _("Summary: %(summary)s", summary=result['summary']) }}</p>
+                                    </td>
+                                </tr>
+                            {% endif %}
+                            <tr>
+                                <td>
+                                    <p class="info searchhitinfobar">{{ _("Revision: %(revid)s Last Change: %(mtime)s", revid=result['revid']|shorten_id, mtime=result['mtime']|datetimeformat) }}</p>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>
+                                    {% if user.may.read(result['name']) %}
+                                        <p class="info foundtext">{{ result.highlights('content')|safe }}</p>
+                                    {% else %}
+                                        <p class="info foundtext">{{ _("You don't have read permission for this item.") }}</p>
+                                    {% endif %}
+                                </td>
+                            </tr>
+                        {% else %}
+                            <tr>
+                                <td class="moin-wordbreak">{{ result.pos + 1 }}
+                                <a class="moin-interwiki" href="{{ url_for_item(item_name=result['name'], wiki_name=result['wikiname'], rev=result['revid']) }}"><b>{{ "%s:%s" % (result['wikiname'], result['name']) }}</b></a>
+                                </td>
+                            </tr>
+                        {% endif %}
+                    {% endfor %}
+                </table>
+            </div>
+        {% endif %}
+{% endif %}

MoinMoin/templates/base.html

 
 {% block body_scripts %} {# js before </body> reduces IE8 js errors related to svgweb #}
     <script src="{{ url_for('serve.files', name='jquery', filename='jquery.min.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/jquery.i18n.min.js') }}"></script>
+    <script src="{{ url_for('frontend.template', filename='dictionary.js') }}"></script>
     <script src="{{ url_for('serve.files', name='bootstrap', filename='js/bootstrap.min.js') }}"></script>
-    <script src="{{ url_for('frontend.template', filename='common.js') }}"></script>
     <script src="{{ url_for('serve.files', name='autosize', filename='jquery.autosize-min.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/common.js') }}"></script>
     {{ scripts }}
     <!--[if lt IE 9]>
         {# TODO: use a local copy later #}

MoinMoin/templates/common.js

-//
-// MoinMoin2 commonly used JavaScript functions
-//
-/*jslint browser: true, */
-/*global $:false */
-
-// This file is a Jinja2 template and is not jslint friendly in its raw state.
-// To run jslint, use your browser debugging tools to view, copy and paste this file to jslint.
-
-
-// Utility function to add a message to moin flash area.
-var MOINFLASHHINT = "moin-flash moin-flash-hint",
-    MOINFLASHINFO = "moin-flash moin-flash-info",
-    MOINFLASHWARNING = "moin-flash moin-flash-warning",
-    MOINFLASHERROR = "moin-flash moin-flash-error";
-function moinFlashMessage(classes, message) {
-    "use strict";
-    var pTag = '<P class="' + classes + '">' + message + '</p>';
-    $(pTag).appendTo($('#moin-flash'));
-}
-
-
-// Highlight currently selected link in side panel. Executed on page load
-function selected_link() {
-    "use strict";
-    var selected = window.location.pathname,
-        list = $('.panel'),
-        i,
-        j,
-        nav_links,
-        link;
-    for (j = 0; j < list.length; j += 1) {
-        nav_links = list[j].getElementsByTagName('a');
-
-        for (i = 0; i < nav_links.length; i += 1) {
-            link = nav_links[i].attributes.href.value;
-
-            if (link === selected) {
-                nav_links[i].setAttribute('class', 'current-link');
-                break;
-            }
-        }
-    }
-}
-$(document).ready(selected_link);
-
-
-// toggleComments is executed when user clicks a Comments button and conditionally on dom ready.
-var pageComments = null; // will hold list of elements with class "comment"
-function toggleComments() {
-    "use strict";
-    // Toggle visibility of every tag with class "comment"
-    var buttons = $('.moin-toggle-comments-button > a, .moin-toggle-comments-button');
-    if (pageComments.is(':hidden')) {
-        pageComments.show();
-        {{ "buttons.attr('title', '%s');" % _("Hide comments") }}
-    } else {
-        pageComments.hide();
-        {{ "buttons.attr('title', '%s');" % _("Show comments") }}
-    }
-}
-
-// Comments initialization is executed once after document ready.
-function initToggleComments() {
-    "use strict";
-    var titles;
-    pageComments = $('.comment');
-    if (pageComments.length > 0) {
-        // There are comments, so show itemview Comments button
-        $('.moin-toggle-comments-button').css('display', '');
-        $('.moin-toggle-comments-button').click(toggleComments);
-        // comments are visible; per user option, hide comments if there is not a <br id="moin-show-comments" />
-        if (!document.getElementById('moin-show-comments')) {
-            toggleComments();
-        }
-        else {
-            var commentButton = $('.moin-toggle-comments-button');
-            commentButton.button('toggle');
-            {{ "commentButton.attr('title', '%s');" % _("Hide comments") }}
-        }
-    }
-}
-$(document).ready(initToggleComments);
-
-
-
-// toggleTransclusionOverlays is executed when user clicks a Transclusions button on the Show item page.
-function toggleTransclusionOverlays() {
-    "use strict";
-    var overlays = $('.moin-item-overlay-ul, .moin-item-overlay-lr'),
-        buttons;
-    if (overlays.length > 0) {
-        buttons = $('.moin-transclusions-button > a, .moin-transclusions-button');
-        if (overlays.is(':visible')) {
-            overlays.hide();
-            {{ "buttons.attr('title', '%s');" % _("Show transclusions") }}
-        } else {
-            overlays.show();
-            {{ "buttons.attr('title', '%s');" % _("Hide transclusions") }}
-        }
-    }
-}
-
-// Transclusion initialization is executed once after document ready.
-function initTransclusionOverlays() {
-    "use strict";
-    var elem, overlayUL, overlayLR, wrapper, wrappers, transclusions, titles,
-        rightArrow = '\u2192';
-    // get list of elements to be wrapped; must work in reverse order in case there are nested transclusions
-    transclusions = $($('.moin-transclusion').get().reverse());
-    transclusions.each(function (index) {
-        elem = transclusions[index];
-        // if this is the transcluded item page, do not wrap (avoid creating useless overlay links to same page)
-        if (location.pathname !== elem.getAttribute('data-href')) {
-            if (elem.tagName === 'DIV') {
-                wrapper = $('<div class="moin-item-wrapper"></div>');
-            } else {
-                wrapper = $('<span class="moin-item-wrapper"></span>');
-            }
-            overlayUL = $('<a class="moin-item-overlay-ul"></a>');
-            $(overlayUL).attr('href', elem.getAttribute('data-href'));
-            $(overlayUL).append(rightArrow);
-            overlayLR = $(overlayUL).clone(true);
-            $(overlayLR).attr('class', 'moin-item-overlay-lr');
-            // if the parent of this element is an A, then wrap parent (avoid A's within A's)
-            if ($(elem).parent()[0].tagName === 'A') {
-                elem = $(elem).parent()[0];
-            }
-            // insert wrapper after elem, append (move) elem, append overlays
-            $(elem).after(wrapper);
-            $(wrapper).append(elem);
-            $(wrapper).append(overlayUL);
-            $(wrapper).append(overlayLR);
-        }
-    });
-    // if an element was wrapped above, then make the Transclusions buttons visible
-    wrappers = $('.moin-item-wrapper');
-    if (wrappers.length > 0) {
-        $('.moin-transclusions-button').css('display', '');
-        $('.moin-transclusions-button').click(toggleTransclusionOverlays);
-    }
-}
-$(document).ready(initTransclusionOverlays);
-
-
-
-// Executed on page load.  If logged in user has less than 6 quicklinks,  do nothing.
-// Else, show the first five links, hide the others, and append a >>> button to show hidden quicklinks on mouseover.
-function QuicklinksExpander() {
-    "use strict";
-    var QUICKLINKS_EXPAND = ">>>",
-        QUICKLINKS_COLLAPSE = "<<<",
-        QUICKLINKS_MAX = 5,
-        newThis;
-    // 8 helper functions
-    function getLinks() {
-        return $(".userlink:not(.moin-navibar-icon)");
-    }
-    function createIcon(txt) {
-        var li = document.createElement("li"),
-            arrows = document.createTextNode(txt);
-        li.setAttribute("class", "moin-userlink moin-navibar-icon");
-        li.appendChild(arrows);
-        return li;
-    }
-    function appendIcon(txt) {
-        var elem = createIcon(txt);
-        document.getElementById("moin-navibar").appendChild(elem);
-        return elem;
-    }
-    function shouldHide(links) {
-        // links should be hidden only if user has created so many that it impacts nice page layout
-        return (links.length > QUICKLINKS_MAX);
-    }
-    function getHideableLinks() {
-        return getLinks().slice(QUICKLINKS_MAX);
-    }
-    function hideShowHideableLinks(action) {
-        getHideableLinks().each(function (i) {
-            if (action === "hide") {
-                $(this).hide();
-            } else {
-                $(this).show();
-            }
-        });
-    }
-    function hideLinks() {
-        hideShowHideableLinks("hide");
-    }
-    function showLinks() {
-        hideShowHideableLinks("show");
-    }
-
-    this.getLinks = getLinks;
-    this.appendIcon = appendIcon;
-    this.shouldHide = shouldHide;
-    this.getHideableLinks = getHideableLinks;
-    this.hideLinks = hideLinks;
-    this.showLinks = showLinks;
-    this.navibar = $("#moin-header");
-    this.link = this.getLinks();
-    this.hideable = this.getHideableLinks();
-
-    if (this.shouldHide(this.link)) {
-        this.expandIcon = $(this.appendIcon(QUICKLINKS_EXPAND));
-        this.closeIcon = $(this.appendIcon(QUICKLINKS_COLLAPSE));
-        this.closeIcon.hide();
-        // Hide everything after the first QUICKLINKS_MAX links
-        this.hideLinks();
-        newThis = this;
-        // When the user mouses over the icon link,
-        // Show the hidden links
-        this.expandIcon.mouseenter(function (e) {
-            newThis.showLinks();
-            newThis.expandIcon.hide();
-            newThis.closeIcon.show();
-        });
-        this.closeIcon.mouseenter(function (e) {
-            newThis.hideLinks();
-            newThis.expandIcon.show();
-            newThis.closeIcon.hide();
-        });
-    }
-}
-$(document).ready(QuicklinksExpander);
-
-
-
-// When a page has subitems, this toggles the subtrees in the Subitems sidebar.
-function toggleSubtree(item) {
-    "use strict";
-    var subtree = $(item).siblings("ul");
-    subtree.toggle(200);
-}
-
-
-
-// OnMouseOver show the fqname of the item else only show the value/id.
-function togglefqname(){
-    "use strict";
-    var fullname, value;
-    $(".moin-fqname").hover(function () {
-        fullname = $(this).attr('data-fqname');
-        value = $(this).html();
-        $(this).html(fullname);
-    },function () {
-        $(this).html(value);
-    });
-}
-$(document).ready(togglefqname);
-
-
-// Executed when user clicks insert-name button defined in modify.html.
-// When a page with subitems is modified, a subitems sidebar is present. User may
-// position caret in textarea and click button to insert name into textarea.
-function InsertName(fullname) {
-    "use strict";
-    var textArea, scrollTop, endPos, startPos;
-    textArea = document.getElementById('f_content_form_data_text');
-    startPos = textArea.selectionStart;
-    endPos = textArea.selectionEnd;
-    textArea.value = textArea.value.substring(0, startPos) + fullname + textArea.value.substring(endPos, textArea.value.length);
-    textArea.focus();
-    textArea.setSelectionRange(startPos+fullname.length,startPos+fullname.length);
-}
-
-
-// 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; }
-
-    // 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">'),
-            hashTag = window.location.hash;
-        // for each form on page, create a corresponding LI
-        $('.moin-tab-body').each(function () {
-            var li = $(document.createElement('li')),
-                // 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;
-                window.location.hash = tab;
-                $('.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;
-            });
-        });
-        // 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);
-
-        // check for the hashtag and switch tab
-        if (hashTag !== '') {
-            var tab = $('.moin-tab-titles li a[href="'+ hashTag +'"]');
-            if (tab.length !== 0) {
-                $(tab)[0].click();
-            }
-        } else {
-            // click a tab to show first form and hide all other forms
-            $(titles.children('li').children('a')[0]).click();
-        }
-
-        // 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());
-        });
-    });
-
-    // add/remove "*" indicator if user changes/saves form
-    function changeHandler(ev) {
-        var form = $(ev.currentTarget),
-            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)
-            $('.moin-change-indicator', title).remove();
-        } else {
-            // the values differ
-            if (!$('.moin-change-indicator', title).length) {
-                // only add a change indicator if there none
-                title.append($('<span class="moin-change-indicator">*</span>'));
-            }
-        }
-    }
-    // attach above function to all forms as a change handler
-    $('#moin-usersettings form').change(changeHandler);
-
-    // executed when user clicks submit button on a user settings form
-    function submitHandler(ev) {
-        var form = $(ev.target),
-            button = $('button', form),
-            buttonBaseText = button.html(),
-            buttonDotList = [' .&nbsp;&nbsp;', ' &nbsp;.&nbsp;', ' &nbsp;&nbsp;.'],
-            buttonDotIndex = 0,
-            buttonDotAnimation;
-
-        // disable the button
-        button.attr('disabled', true);
-
-        // remove change indicators from the current tab as we are now saving it
-        $('.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
-        function buttonRunAnimation() {
-            button.html(buttonBaseText + buttonDotList[buttonDotIndex % buttonDotList.length]);
-            buttonDotIndex += 1;
-        }
-        buttonDotAnimation = setInterval(buttonRunAnimation, 500);
-        buttonRunAnimation();
-
-        // send the form to the server
-        $.post(form.attr('action'), form.serialize(), function (data) {
-            var i, f, newform;
-            clearInterval(buttonDotAnimation);
-            // if the response indicates a redirect, set the new location
-            if (data.redirect) {
-                location.href = data.redirect;
-                return;
-            }
-            // remove all flash messages previously added via javascript
-            $('#moin-flash .moin-flash-javascript').remove();
-            // add new flash messages from the response
-            for (i = 0; i < data.flash.length; i += 1) {
-                f = $(document.createElement('p'));
-                f.html(data.flash[i][0]);
-                f.addClass('moin-flash');
-                f.addClass('moin-flash-javascript');
-                f.addClass('moin-flash-' + data.flash[i][1]);
-                $('#moin-flash').append(f);
-            }
-            // get the new form element from the response
-            newform = $(data.form);
-            // set event handlers on the new form
-            newform.submit(submitHandler);
-            newform.change(changeHandler);
-            // store the forms initial data
-            newform.data('initialForm', newform.serialize());
-            // replace the old form with the new one
-            form.replaceWith(newform);
-        }, 'json');
-        return false;
-    }
-    // attach above function as a submit handler to each user setting form
-    $('#moin-usersettings form').submit(submitHandler);
-
-    // 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+).
-    //       IE8 does not scroll page after edit (cannot determine caret position within textarea).
-    "use strict";
-