memodump.py 47.7 KB
Newer Older
dossist's avatar
dossist committed
1 2 3 4 5 6
# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - memodump theme

    Based on modernized theme in MoinMoin

dossist's avatar
dossist committed
7
    Config variables:
dossist's avatar
dossist committed
8
        Following variables and methods in wikiconfig.py will change something in the theme.
dossist's avatar
dossist committed
9 10

        memodump_menuoverride
dossist's avatar
dossist committed
11 12
            Overrides menu elements.

dossist's avatar
dossist committed
13 14
        memodump_menu_def(request)
            Additional data dictionary of menu items.
dossist's avatar
dossist committed
15

dossist's avatar
dossist committed
16
        memodump_hidelocation
dossist's avatar
dossist committed
17
            Overrides list of page names that should have no location area.
dossist's avatar
dossist committed
18 19
            e.g. [page_front_page, u'SideBar', ]

dossist's avatar
dossist committed
20 21 22
    References:
        How to edit menu items:
            https://github.com/dossist/moinmoin-memodump/wiki/EditMenu
dossist's avatar
dossist committed
23
        Tips:
dossist's avatar
dossist committed
24 25
            https://github.com/dossist/moinmoin-memodump/wiki/Tips

dossist's avatar
dossist committed
26
    @copyright: 2014 dossist.
dossist's avatar
dossist committed
27
    @license: GNU GPL, see http://www.gnu.org/licenses/gpl for details.
dossist's avatar
dossist committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
"""

from MoinMoin.theme import ThemeBase
import StringIO, re
from MoinMoin import wikiutil
from MoinMoin.action import get_available_actions
from MoinMoin.Page import Page

class Theme(ThemeBase):

    name = "memodump"

    _ = lambda x: x     # We don't have gettext at this moment, so we fake it
    icons = {
        # key         alt                        icon filename      w   h
        # FileAttach
        'attach':     ("%(attach_count)s",       "moin-attach.png",   16, 16),
        'info':       ("[INFO]",                 "moin-info.png",     16, 16),
        'attachimg':  (_("[ATTACH]"),            "attach.png",        32, 32),
        # RecentChanges
        'rss':        (_("[RSS]"),               "moin-rss.png",      16, 16),
        'deleted':    (_("[DELETED]"),           "moin-deleted.png",  16, 16),
        'updated':    (_("[UPDATED]"),           "moin-updated.png",  16, 16),
        'renamed':    (_("[RENAMED]"),           "moin-renamed.png",  16, 16),
        'conflict':   (_("[CONFLICT]"),          "moin-conflict.png", 16, 16),
        'new':        (_("[NEW]"),               "moin-new.png",      16, 16),
        'diffrc':     (_("[DIFF]"),              "moin-diff.png",     16, 16),
        # General
        'bottom':     (_("[BOTTOM]"),            "moin-bottom.png",   16, 16),
        'top':        (_("[TOP]"),               "moin-top.png",      16, 16),
        'www':        ("[WWW]",                  "moin-www.png",      16, 16),
        'mailto':     ("[MAILTO]",               "moin-email.png",    16, 16),
        'news':       ("[NEWS]",                 "moin-news.png",     16, 16),
        'telnet':     ("[TELNET]",               "moin-telnet.png",   16, 16),
        'ftp':        ("[FTP]",                  "moin-ftp.png",      16, 16),
        'file':       ("[FILE]",                 "moin-ftp.png",      16, 16),
        # search forms
        'searchbutton': ("[?]",                  "moin-search.png",   16, 16),
        'interwiki':  ("[%(wikitag)s]",          "moin-inter.png",    16, 16),

        # smileys (this is CONTENT, but good looking smileys depend on looking
        # adapted to the theme background color and theme style in general)
        #vvv    ==      vvv  this must be the same for GUI editor converter
        'X-(':        ("X-(",                    'angry.png',         16, 16),
        ':D':         (":D",                     'biggrin.png',       16, 16),
        '<:(':        ("<:(",                    'frown.png',         16, 16),
        ':o':         (":o",                     'redface.png',       16, 16),
        ':(':         (":(",                     'sad.png',           16, 16),
        ':)':         (":)",                     'smile.png',         16, 16),
        'B)':         ("B)",                     'smile2.png',        16, 16),
        ':))':        (":))",                    'smile3.png',        16, 16),
        ';)':         (";)",                     'smile4.png',        16, 16),
        '/!\\':       ("/!\\",                   'alert.png',         16, 16),
        '<!>':        ("<!>",                    'attention.png',     16, 16),
        '(!)':        ("(!)",                    'idea.png',          16, 16),
        ':-?':        (":-?",                    'tongue.png',        16, 16),
        ':\\':        (":\\",                    'ohwell.png',        16, 16),
        '>:>':        (">:>",                    'devil.png',         16, 16),
        '|)':         ("|)",                     'tired.png',         16, 16),
        ':-(':        (":-(",                    'sad.png',           16, 16),
        ':-)':        (":-)",                    'smile.png',         16, 16),
        'B-)':        ("B-)",                    'smile2.png',        16, 16),
        ':-))':       (":-))",                   'smile3.png',        16, 16),
        ';-)':        (";-)",                    'smile4.png',        16, 16),
        '|-)':        ("|-)",                    'tired.png',         16, 16),
        '(./)':       ("(./)",                   'checkmark.png',     16, 16),
        '{OK}':       ("{OK}",                   'thumbs-up.png',     16, 16),
        '{X}':        ("{X}",                    'icon-error.png',    16, 16),
        '{i}':        ("{i}",                    'icon-info.png',     16, 16),
        '{1}':        ("{1}",                    'prio1.png',         15, 13),
        '{2}':        ("{2}",                    'prio2.png',         15, 13),
        '{3}':        ("{3}",                    'prio3.png',         15, 13),
        '{*}':        ("{*}",                    'star_on.png',       16, 16),
        '{o}':        ("{o}",                    'star_off.png',      16, 16),
    }
    del _

    stylesheets = (
        # media         basename
        ('all',         'bootstrap.min'),
        ('all',         'bootstrap-theme.min'),
dossist's avatar
dossist committed
109
        ('all',         'memodump'),
dossist's avatar
dossist committed
110
        ('all',         'moinizer'),
dossist's avatar
dossist committed
111
    )
dossist's avatar
dossist committed
112 113 114 115 116 117
    stylesheets_print = (
        ('all',         'bootstrap.min'),
        ('all',         'bootstrap-theme.min'),
        ('all',         'memodump'),
        ('all',         'moinizer'),
        ('all',         'memoprint'),
dossist's avatar
dossist committed
118
    )
119 120 121 122 123 124 125
    stylesheets_projection = (
        ('all',         'bootstrap.min'),
        ('all',         'bootstrap-theme.min'),
        ('all',         'memodump'),
        ('all',         'moinizer'),
        ('all',         'memoslide'),
    )
dossist's avatar
dossist committed
126 127 128 129 130 131 132 133 134 135 136 137

    def header(self, d, **kw):
        """ Assemble wiki header
        header1: supported.
        header2: supported.

        @param d: parameter dictionary
        @rtype: unicode
        @return: page header html
        """

        html = u"""
dossist's avatar
dossist committed
138
  <div id="outbox" class="sidebar-toggle">
dossist's avatar
dossist committed
139
    <!-- Bootstrap navbar -->
140
    <div class="navbar navbar-inverse navbar-fixed-top navbar-mobile-toggle" role="navigation">
dossist's avatar
dossist committed
141
      <div class="container">
dossist's avatar
dossist committed
142 143 144

        <!-- Navbar header -->
        <div class="navbar-header">
dossist's avatar
dossist committed
145
          <!-- Sidebar toggler -->
dossist's avatar
dossist committed
146
          <button type="button" class="btn navbar-btn sidebar-toggler" data-toggle="toggle" data-target=".sidebar-toggle">
dossist's avatar
dossist committed
147
            <span class="sr-only">Toggle sidebar</span>
dossist's avatar
dossist committed
148
            <span class="menu-btn-sidebar-toggler sidebar-toggle"></span>
dossist's avatar
dossist committed
149
          </button>
dossist's avatar
dossist committed
150 151 152 153 154 155 156 157
          <!-- Button to show navbar controls when collapsed -->
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <!-- Sitename -->
dossist's avatar
dossist committed
158
%(sitename)s
dossist's avatar
dossist committed
159 160 161 162 163
        </div> <!-- /.navbar-header -->

        <!-- Body of navbar -->
        <div class="collapse navbar-collapse">

dossist's avatar
dossist committed
164
          <!-- Navbar elements -->
dossist's avatar
dossist committed
165
          <ul class="nav navbar-nav navbar-right">
dossist's avatar
dossist committed
166 167 168 169 170 171 172

            <!-- Comment toggle button -->
%(commentbutton)s
            <!-- Edit button -->
%(edit)s
            <!-- Search form -->
%(search)s
dossist's avatar
dossist committed
173
            <!-- Menu -->
dossist's avatar
dossist committed
174
%(menu)s
dossist's avatar
dossist committed
175
            <!-- Login user -->
dossist's avatar
dossist committed
176 177
%(usermenu)s

dossist's avatar
dossist committed
178
          </ul> <!-- /.navbar-right -->
dossist's avatar
dossist committed
179

dossist's avatar
dossist committed
180
        </div> <!-- /.collapse -->
dossist's avatar
dossist committed
181 182 183 184
      </div> <!-- /.container -->
    </div> <!-- /.navbar -->
    <!-- End of navbar -->

185
    <div class="container no-padding" id="pagebox">
dossist's avatar
dossist committed
186 187
%(custom_pre)s

188
      <!-- Sidebar -->
dossist's avatar
dossist committed
189 190 191
      <div class="sidebar-toggle" id="sidebar-curtain">
        <div class="sidebar-toggle" id="sidebar-mover">
          <div class="sidebar-toggle" id="sidebar" role="navigation">
dossist's avatar
dossist committed
192 193 194
<!-- SideBar contents -->
%(sidebar)s
<!-- End of SideBar contents -->
195
%(navilinks)s
dossist's avatar
dossist committed
196
%(trail)s
197 198 199
          </div> <!-- /#sidebar -->
        </div> <!-- /#sidebar-mover -->
      </div><div id="contentbox"> <!-- End of Sidebar and Beginning of content -->
dossist's avatar
dossist committed
200 201 202

%(custom_post)s
%(msg)s
dossist's avatar
dossist committed
203 204
%(location)s

dossist's avatar
dossist committed
205
<!-- Page contents -->
206 207 208 209 210 211 212 213 214 215 216 217 218 219
""" % {'sitename': self.logo(),
       'location': self.location(d),
       'menu': self.menu(d),
       'usermenu': self.username(d),
       'search': self.searchform(d),
       'edit': self.editbutton(d),
       'commentbutton': self.commentbutton(),
       'sidebar': self.sidebar(d),
       'trail': self.trail(d),
       #'quicklinks': self.quicklinks(d),
       'navilinks': self.navibar(d),
       'msg': self.msg(d),
       'custom_pre': self.emit_custom_html(self.cfg.page_header1), # custom html just below the navbar, not recommended!
       'custom_post': self.emit_custom_html(self.cfg.page_header2), # custom html just before the contents, not recommended!
dossist's avatar
dossist committed
220 221 222 223
      }

        return html

224 225 226 227 228 229
    def editorheader(self, d, **kw):
        """
        header() for edit mode. Just set edit mode flag and call self.header().
        """
        d['edit_mode'] = 1
        return self.header(d, **kw)
dossist's avatar
dossist committed
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

    def footer(self, d, **keywords):
        """ Assemble wiki footer
        footer1: supported.
        footer2: supported.

        @param d: parameter dictionary
        @keyword ...:...
        @rtype: unicode
        @return: page footer html
        """
        page = d['page']

        html = u"""
<!-- End of page contents -->
245
        <div class="clearfix"></div>
dossist's avatar
dossist committed
246

247 248 249
      </div> <!-- /#contentbox -->
      <!-- End of content body -->
    </div> <!-- /.container, #pagebox -->
250
  </div> <!-- /#outbox -->
dossist's avatar
dossist committed
251

252 253 254 255
  <!-- pageinfo -->
  <div id="pageinfo-container">
    <div class="container">
      %(pageinfo)s
dossist's avatar
dossist committed
256
    </div>
257 258
  </div>
  <!-- End of pageinfo -->
dossist's avatar
dossist committed
259 260 261

%(custom_pre)s

262 263
  <!-- Footer -->
  <div id="footer">
264 265 266
    <div class="container text-right text-muted">
      %(credits)s
      %(version)s
dossist's avatar
dossist committed
267
%(custom_post)s
268
    </div> <!-- /.container -->
269 270 271 272 273 274 275 276
  </div> <!-- /#footer -->
  <!-- End of footer -->

  <!-- Bootstrap core JavaScript -->
  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="%(prefix)s/%(theme)s/js/jquery.min.js"></script>
  <!-- Include all compiled plugins (below), or include individual files as needed -->
  <script src="%(prefix)s/%(theme)s/js/bootstrap.min.js"></script>
277 278
  <!-- toggle.js by dossist -->
  <script src="%(prefix)s/%(theme)s/js/toggle.js"></script>
279 280
  <!-- Custom script -->
%(script)s
281
  <!-- End of JavaScript -->
282 283 284 285 286 287 288
""" % {'pageinfo': self.pageinfo(page),
       'custom_pre': self.emit_custom_html(self.cfg.page_footer1), # Pre footer custom html (not recommended!)
       'credits': self.credits(d),
       'version': self.showversion(d, **keywords),
       'custom_post': self.emit_custom_html(self.cfg.page_footer2), # In-footer custom html (not recommended!)
       'prefix': self.cfg.url_prefix_static,
       'theme': self.name,
289
       'script': self.script(),
dossist's avatar
dossist committed
290 291 292 293
      }

        return html

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
    def script(self):
        """
        Append in-html script at the bottom of the page body.
        """
        
        return ur"""
  <script>
    +function ($) {
      // Toggle minified navbar under mobile landscape view
      $('.navbar-collapse').on('show.bs.collapse', function () {
        $('.navbar-mobile-toggle').togglejs('show');
      });
      $('.navbar-collapse').on('hidden.bs.collapse', function () {
        $('.navbar-mobile-toggle').togglejs('hide');
      });
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
      
      //Scroll position fix for hash anchors
      var mdAnchorFix = {
        escapeRe: /[ !"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g,
        escape: function (str) {
          return str.replace(mdAnchorFix.escapeRe, '\\$&');
        },
        rgbRe: /^rgba\(([ \t]*\d{1,3},){3}([ \t]*\d{1,3})\)$/i,
        isTransparent: function (rgbstr) {
          if (rgbstr === 'transparent') {
            return true;
          }
          rgbMatch = rgbstr.match(mdAnchorFix.rgbRe);
          if (rgbMatch) {
            return (Number(rgbMatch[2]) ? false : true);
          }
          return false;
        },
        navbarHeight: function () {
          var height = 0;
          var $navbar = $('.navbar');
          if ( !mdAnchorFix.isTransparent($navbar.css('background-color'))
               && ($navbar.css('display') !== 'none')
               && ($navbar.css('visibility') !== 'hidden') ) {
            height = $navbar.height();
          }
          return height;
        },
        jump: function () {
          origin = $('#' + mdAnchorFix.escape(location.hash.substr(1))).offset().top;
          offset = mdAnchorFix.navbarHeight() + 15;
          setTimeout(function () { window.scrollTo(0, origin - offset); }, 1);
        },
        clickWrapper: function () {
          if ( ($(this).attr('href') === location.hash)
               || !('onhashchange' in window.document.body) ) {
            setTimeout(function () { $(window).trigger("hashchange"); }, 1);
          }
        },
      };
      $('#pagebox a[href^="#"]:not([href="#"])').on("click", mdAnchorFix.clickWrapper);
      $(window).on("hashchange", mdAnchorFix.jump);
      if (location.hash) setTimeout(function () { mdAnchorFix.jump(); }, 100);
352 353 354 355
    }(jQuery);
  </script>
"""
    
dossist's avatar
dossist committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369
    def logo(self):
        """ Assemble logo with link to front page
        Using <a> tag only instead of wrapping with div

        The logo may contain an image and or text or any html markup.
        Just note that everything is enclosed in <a> tag.

        @rtype: unicode
        @return: logo html
        """
        html = u''
        if self.cfg.logo_string:
            page = wikiutil.getFrontPage(self.request)
            html = page.link_to_raw(self.request, self.cfg.logo_string, css_class="navbar-brand")
dossist's avatar
dossist committed
370 371 372 373 374
            html = u'''
          <div class="navbar-brand-wrapper">
            %s
          </div>
          ''' % html
dossist's avatar
dossist committed
375 376
        return html

dossist's avatar
dossist committed
377 378 379 380
    def location(self, d):
        """ Assemble location area on top of the page content.
        Certain pages shouldn't have location area as it feels redundant.
        Location area is excluded in FrontPage by default.
dossist's avatar
dossist committed
381
        Config variable memodump_hidelocation will override the list of pages to have no location area.
dossist's avatar
dossist committed
382
        """
dossist's avatar
dossist committed
383 384
        html = u''
        page = d['page']
385
        pages_hide = [self.request.cfg.page_front_page, ]
dossist's avatar
dossist committed
386
        try:
dossist's avatar
dossist committed
387
            pages_hide = self.request.cfg.memodump_hidelocation
dossist's avatar
dossist committed
388 389 390 391 392
        except AttributeError:
            pass
        if not page.page_name in pages_hide:
            html = u'''
        <div id="location">
393
%(interwiki)s
dossist's avatar
dossist committed
394 395 396
%(pagename)s
          %(lastupdate)s
        </div>
397
''' % {'interwiki': self.interwiki(d), 'pagename': self.title(d), 'lastupdate': self.lastupdate(d), }
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
        return html

    def interwiki(self, d):
        """ Assemble the interwiki name display, linking to page_front_page

        @param d: parameter dictionary
        @rtype: string
        @return: interwiki html
        """
        if self.request.cfg.show_interwiki:
            page = wikiutil.getFrontPage(self.request)
            text = self.request.cfg.interwikiname or 'Self'
            link = page.link_to(self.request, text=text, rel='nofollow')
            html = u'<span id="interwiki">%s<span class="sep">:</span></span>' % link
        else:
            html = u''
dossist's avatar
dossist committed
414 415 416 417
        return html

    def lastupdate(self, d):
        """ Return html for last update in location area, if conditions are met. """
dossist's avatar
dossist committed
418
        _ = self.request.getText
dossist's avatar
dossist committed
419 420 421 422 423 424 425
        page = d['page']
        html = u''
        if self.shouldShowPageinfo(page):
            info = page.lastEditInfo()
            if info:
                html = u'<span class="lastupdate">%s %s</span>' % (_('Last updated at'), info['time'])
        return html
dossist's avatar
dossist committed
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443

    def searchform(self, d):
        """
        assemble HTML code for the search form

        @param d: parameter dictionary
        @rtype: unicode
        @return: search form html
        """
        _ = self.request.getText
        form = self.request.values
        updates = {
            'search_label': _('Search:'),
            'search_hint': _('Search'),
            'search_value': wikiutil.escape(form.get('value', ''), 1),
            'search_full_label': _('Text'),
            'search_title_label': _('Titles'),
            'url': self.request.href(d['page'].page_name)
dossist's avatar
dossist committed
444
        }
dossist's avatar
dossist committed
445 446 447
        d.update(updates)

        html = u'''
dossist's avatar
dossist committed
448 449 450 451 452 453 454 455 456 457 458 459
          <li>
            <div class="navbar-form">
              <form class="form-search" role="search" id="searchform" method="get" action="%(url)s">
                <input type="hidden" name="action" value="fullsearch">
                <input type="hidden" name="context" value="180">
                <div class="form-group">
                  <label class="sr-only" for="searchinput">%(search_label)s</label>
                  <input id="searchinput" type="text" class="form-control form-search" placeholder="%(search_hint)s" name="value" value="%(search_value)s">
                </div>
              </form>
            </div>
          </li>
dossist's avatar
dossist committed
460 461 462 463 464 465 466 467 468
''' % d
        return html

    def editbutton(self, d):
        """ Return an edit button html fragment.

        If the user can't edit, return a disabled edit button.
        """
        page = d['page']
469
        edit_mode = d.get('edit_mode', 0)
dossist's avatar
dossist committed
470 471 472 473 474 475 476 477 478 479 480 481 482 483

        if 'edit' in self.request.cfg.actions_excluded:
            return u""

        button = u''
        li_attr = u''

        if not (page.isWritable() and
                self.request.user.may.write(page.page_name)):
            button = self.disabledEdit()
            li_attr = u' class="disabled"'
        else:
            _ = self.request.getText
            querystr = {'action': 'edit'}
484
            text = u'<span class="hidden-sm">%s</span>' % _('Edit')
dossist's avatar
dossist committed
485
            attrs = {'name': 'editlink', 'rel': 'nofollow', 'css_class': 'menu-nav-edit'}
dossist's avatar
dossist committed
486
            button = page.link_to_raw(self.request, text=text, querystr=querystr, **attrs)
487 488
            if edit_mode:
                li_attr = u' class="active"'
dossist's avatar
dossist committed
489 490

        html = u'''
dossist's avatar
dossist committed
491 492 493
            <li%s>
              %s
            </li>
dossist's avatar
dossist committed
494 495 496 497 498 499 500
''' % (li_attr, button)

        return html

    def disabledEdit(self):
        """ Return a disabled edit link """
        _ = self.request.getText
501 502 503 504 505
        html = u'%s<span class="hidden-sm">%s</span>%s' % (
                   self.request.formatter.url(1, css="menu-nav-edit"),
                   _('Immutable Page'),
                   self.request.formatter.url(0)
               )
dossist's avatar
dossist committed
506 507
        return html

dossist's avatar
dossist committed
508 509 510 511 512 513 514 515 516
    def commentbutton(self):
        """
        Return a comment toggle button html.
        Don't check if 'Comment' is present in self.request.cfg.edit_bar
        The button is display:none; (i.e. disappeared) by default, but will automatically appear
        when default javascript notices there is a comment in the source.
        """
        _ = self.request.getText
        html = u'''
dossist's avatar
dossist committed
517 518
            <li class="toggleCommentsButton navbar-comment-toggle" style="display:none;">
              <a href="#" class="menu-nav-comment nbcomment navbar-comment-toggle" rel="nofollow" onClick="toggleComments();return false;" data-toggle="toggle" data-target=".navbar-comment-toggle">
519 520
                <span class="hidden-sm">%s</span>
              </a>
dossist's avatar
dossist committed
521
            </li>
dossist's avatar
dossist committed
522 523 524
''' % _('Comments')
        return html

dossist's avatar
dossist committed
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
    def username(self, d):
        """ Assemble the username / userprefs link as dropdown menu
        Assemble a login link instead in case of no login user.

        @param d: parameter dictionary
        @rtype: unicode
        @return: username html
        """
        request = self.request
        _ = request.getText

        userlinks = []
        userbutton = u''
        loginbutton = u''

        # Add username/homepage link for registered users. We don't care
        # if it exists, the user can create it.
        if request.user.valid and request.user.name:
            interwiki = wikiutil.getInterwikiHomePage(request)
            name = request.user.name
            aliasname = request.user.aliasname
            if not aliasname:
                aliasname = name
            title = "%s @ %s" % (aliasname, interwiki[0])
            # make user button
            userbutton = u'%s<span class="nav-maxwidth-100">%s</span><span class="padding"></span><span class="caret"></span>%s' % (
                request.formatter.url(1, url="#", css="menu-nav-user dropdown-toggle", **{"data-toggle": "dropdown", "rel": "nofollow"}),
                request.formatter.text(name),
                request.formatter.url(0),
                )
            # link to (interwiki) user homepage
            wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_interwiki(self.request, *interwiki)
            wikiurl = wikiutil.mapURL(self.request, wikiurl)
            href = wikiutil.join_wiki(wikiurl, wikitail)
            homelink = (request.formatter.url(1, href, title=title, css='menu-dd-userhome', rel="nofollow") +
                       request.formatter.text(name) +
                       request.formatter.url(0))
            userlinks.append(homelink)

            # link to userprefs action
            if 'userprefs' not in self.request.cfg.actions_excluded:
                userlinks.append(d['page'].link_to_raw(request, text=_('Settings'), css_class='menu-dd-userprefs',
                                                       querystr={'action': 'userprefs'}, rel='nofollow'))
            # logout link
            if request.user.auth_method in request.cfg.auth_can_logout:
                userlinks.append(d['page'].link_to_raw(request, text=_('Logout'), css_class='menu-dd-logout',
                                                       querystr={'action': 'logout', 'logout': 'logout'}, rel='nofollow'))
        else:
            query = {'action': 'login'}
            # special direct-login link if the auth methods want no input
            if request.cfg.auth_login_inputs == ['special_no_input']:
                query['login'] = '1'
            if request.cfg.auth_have_login:
dossist's avatar
dossist committed
578
                loginbutton = (d['page'].link_to_raw(request, text=_("Login"),
dossist's avatar
dossist committed
579
                                                     querystr=query, css_class='menu-nav-login', rel='nofollow'))
dossist's avatar
dossist committed
580 581

        if userbutton:
dossist's avatar
dossist committed
582
            userlinks_html = u'</li>\n                <li>'.join(userlinks)
dossist's avatar
dossist committed
583
            html = u'''
dossist's avatar
dossist committed
584 585 586 587 588 589
            <li class="dropdown">
              %s
              <ul class="dropdown-menu">
                <li>%s</li>
              </ul>
            </li> <!-- /dropdown -->
dossist's avatar
dossist committed
590 591 592 593
''' % (userbutton, userlinks_html)

        elif loginbutton:
            html = u'''
dossist's avatar
dossist committed
594 595 596
            <li>
              %s
            </li>
dossist's avatar
dossist committed
597 598 599 600 601 602 603 604 605 606 607 608 609
''' % loginbutton

        else:
            html = u''

        return html

    def menu(self, d):
        """
        Build dropdown menu html. Incompatible with original actionsMenu() method.

        Menu can be customized by adding a config variable 'memodump_menuoverride'.
        The variable will override the default menu set.
dossist's avatar
dossist committed
610
        Additional menu definitions are given via config method 'memodump_menu_def(request)'.
dossist's avatar
dossist committed
611
        See the code below or project wiki for details.
dossist's avatar
dossist committed
612 613 614 615 616 617 618 619 620 621

        @param d: parameter dictionary
        @rtype: string
        @return: menu html
        """
        request = self.request
        _ = request.getText
        rev = request.rev
        page = d['page']

dossist's avatar
dossist committed
622 623 624 625 626 627 628 629 630 631 632 633
        page_recent_changes = wikiutil.getLocalizedPage(request, u'RecentChanges')
        page_find_page = wikiutil.getLocalizedPage(request, u'FindPage')
        page_help_contents = wikiutil.getLocalizedPage(request, u'HelpContents')
        page_help_formatting = wikiutil.getLocalizedPage(request, u'HelpOnFormatting')
        page_help_wikisyntax = wikiutil.getLocalizedPage(request, u'HelpOnMoinWikiSyntax')
        page_title_index = wikiutil.getLocalizedPage(request, u'TitleIndex')
        page_word_index = wikiutil.getLocalizedPage(request, u'WordIndex')
        page_front_page = wikiutil.getFrontPage(request)
        page_sidebar = Page(request, request.getPragma('sidebar', u'SideBar'))
        quicklink = self.menuQuickLink(page)
        subscribe = self.menuSubscribe(page)

dossist's avatar
dossist committed
634 635 636
        try:
            menu = request.cfg.memodump_menuoverride
        except AttributeError:
dossist's avatar
dossist committed
637 638 639
            # default list of items in dropdown menu.
            # menu items are assembled in this order.
            # see wiki for detailed info on customization.
dossist's avatar
dossist committed
640
            menu = [
dossist's avatar
dossist committed
641
                '===== Navigation =====',
dossist's avatar
dossist committed
642 643 644 645
                'RecentChanges',
                'FindPage',
                'LocalSiteMap',
                '__separator__',
dossist's avatar
dossist committed
646
                '===== Help =====',
dossist's avatar
dossist committed
647 648 649
                'HelpContents',
                'HelpOnMoinWikiSyntax',
                '__separator__',
dossist's avatar
dossist committed
650 651 652 653 654 655
                '===== Display =====',
                'AttachFile',
                'info',
                'raw',
                'print',
                '__separator__',
dossist's avatar
dossist committed
656
                '===== Edit =====',
dossist's avatar
dossist committed
657 658
                'RenamePage',
                'DeletePage',
dossist's avatar
dossist committed
659
                'revert',
dossist's avatar
dossist committed
660 661 662
                'CopyPage',
                'Load',
                'Save',
dossist's avatar
dossist committed
663 664 665
                'Despam',
                'editSideBar',
                '__separator__',
dossist's avatar
dossist committed
666
                '===== User =====',
dossist's avatar
dossist committed
667
                'quicklink',
dossist's avatar
dossist committed
668
                'subscribe',
dossist's avatar
dossist committed
669
            ]
dossist's avatar
dossist committed
670 671 672

        # menu element definitions
        menu_def = {
dossist's avatar
dossist committed
673 674 675 676 677 678 679 680 681 682 683
            'raw': {
                # Title for this menu entry
                'title': _('Raw Text'),
                # href and args are for normal entries ('special': False), otherwise ignored.
                # 'href': Nonexistent or empty for current page
                'href': '',
                # 'args': {'query1': 'value1', 'query2': 'value2', }
                # Optionally specify this for <a href="href?query1=value1&query2=value2">
                # If href and args are both nonexistent or empty, key is automatically interpreted to be an action name
                # and href and args are automatically set.
                'args': '',
dossist's avatar
dossist committed
684
                # 'special' can be:
dossist's avatar
dossist committed
685 686
                #   'disabled', 'removed', 'separator' or 'header' for whatever they say,
                #    False, None or nonexistent for normal menu display.
dossist's avatar
dossist committed
687
                # 'separator' and 'header' are automatically removed when there are no entries to show among them.
dossist's avatar
dossist committed
688
                'special': False,
dossist's avatar
dossist committed
689
            },
690
            'print': {'title': _('Print View'), },
dossist's avatar
dossist committed
691 692
            'refresh': {
                'title': _('Delete Cache'),
dossist's avatar
dossist committed
693
                'special': not (self.memodumpIsAvailableAction(page, 'refresh') and page.canUseCache()) and 'removed',
dossist's avatar
dossist committed
694
            },
695 696 697 698 699 700 701
            'SpellCheck': {'title': _('Check Spelling'), },
            'RenamePage': {'title': _('Rename Page'), },
            'CopyPage':   {'title': _('Copy Page'), },
            'DeletePage': {'title': _('Delete Page'), },
            'LikePages':  {'title': _('Like Pages'), },
            'LocalSiteMap': {'title': _('Local Site Map'), },
            'MyPages':    {'title': _('My Pages'), },
dossist's avatar
dossist committed
702 703
            'SubscribeUser': {
                'title': _('Subscribe User'),
dossist's avatar
dossist committed
704
                'special': not (self.memodumpIsAvailableAction(page, 'SubscribeUser')
dossist's avatar
dossist committed
705
                                and request.user.may.admin(page.page_name)) and 'removed',
dossist's avatar
dossist committed
706
            },
dossist's avatar
dossist committed
707 708
            'Despam': {
                'title': _('Remove Spam'),
dossist's avatar
dossist committed
709
                'special': not (self.memodumpIsAvailableAction(page, 'Despam') and request.user.isSuperUser()) and 'removed',
dossist's avatar
dossist committed
710
            },
dossist's avatar
dossist committed
711 712
            'revert': {
                'title': _('Revert to this revision'),
dossist's avatar
dossist committed
713
                'special': not (self.memodumpIsAvailableAction(page, 'revert')
dossist's avatar
dossist committed
714
                                and rev
dossist's avatar
dossist committed
715
                                and request.user.may.revert(page.page_name)) and 'removed',
dossist's avatar
dossist committed
716
            },
717 718 719 720
            'PackagePages': {'title': _('Package Pages'), },
            'RenderAsDocbook': {'title': _('Render as Docbook'), },
            'SyncPages': {'title': _('Sync Pages'), },
            'AttachFile': {'title': _('Attachments'), },
dossist's avatar
dossist committed
721 722 723
            'quicklink': {
                'title': quicklink[1], 'args': dict(action=quicklink[0], rev=rev),
                'special': not quicklink[0] and 'removed',
dossist's avatar
dossist committed
724
            },
dossist's avatar
dossist committed
725 726 727
            'subscribe': {
                'title': subscribe[1], 'args': dict(action=subscribe[0], rev=rev),
                'special': not subscribe[0] and 'removed',
dossist's avatar
dossist committed
728
            },
729
            'info': {'title': _('Info'), },
dossist's avatar
dossist committed
730
# menu items not in menu_def will be assumed to be action names,
dossist's avatar
dossist committed
731
# and receive appropriate title, href, and args automatically.
732 733
#           'Load': {'title': _('Load'), },
#           'Save': {'title': _('Save'), },
dossist's avatar
dossist committed
734 735
            # menu decorations
            '__separator__':   {'title': _('------------------------'), 'special': 'separator', },
dossist's avatar
dossist committed
736
            '----':            {'title': _('------------------------'), 'special': 'separator', },
dossist's avatar
dossist committed
737 738 739 740 741 742 743
            '-----':           {'title': _('------------------------'), 'special': 'separator', },
            '------':          {'title': _('------------------------'), 'special': 'separator', },
            '-------':         {'title': _('------------------------'), 'special': 'separator', },
            '--------':        {'title': _('------------------------'), 'special': 'separator', },
            '---------':       {'title': _('------------------------'), 'special': 'separator', },
            '----------':      {'title': _('------------------------'), 'special': 'separator', },
            # header example
dossist's avatar
dossist committed
744 745 746 747 748 749 750 751 752 753 754
            '__title_navigation__': {'title': _('Navigation'), 'special': 'header', },
            # useful pages
            'RecentChanges':   {'title': page_recent_changes.page_name, 'href': page_recent_changes.url(request)},
            'FindPage':        {'title': page_find_page.page_name, 'href': page_find_page.url(request)},
            'HelpContents':    {'title': page_help_contents.page_name, 'href': page_help_contents.url(request)},
            'HelpOnFormatting': {'title': page_help_formatting.page_name, 'href': page_help_formatting.url(request)},
            'HelpOnMoinWikiSyntax': {'title': page_help_wikisyntax.page_name, 'href': page_help_wikisyntax.url(request)},
            'TitleIndex':      {'title': page_title_index.page_name, 'href': page_title_index.url(request)},
            'WordIndex':       {'title': page_word_index.page_name, 'href': page_word_index.url(request)},
            'FrontPage':       {'title': page_front_page.page_name, 'href': page_front_page.url(request)},
            'SideBar':         {'title': page_sidebar.page_name, 'href': page_sidebar.url(request)},
dossist's avatar
dossist committed
755 756 757
            'editSideBar': {
                'title': _('Edit SideBar'), 'href': page_sidebar.url(request),
                'args': dict(action='edit'),
dossist's avatar
dossist committed
758
                'special': not self.memodumpIsEditablePage(page_sidebar) and 'removed'
dossist's avatar
dossist committed
759 760
            },
        }
dossist's avatar
dossist committed
761

762
        # register state determining functions on request for use in config
dossist's avatar
dossist committed
763 764
        request.memodumpIsAvailableAction = self.memodumpIsAvailableAction
        request.memodumpIsEditablePage = self.memodumpIsEditablePage
765

dossist's avatar
dossist committed
766
        try:
dossist's avatar
dossist committed
767
            menu_def.update(request.cfg.memodump_menu_def(request))
dossist's avatar
dossist committed
768 769 770 771 772 773
        except AttributeError:
            pass

        compiled = self.menuCompile(d, menu, menu_def)
        menubody = self.menuRender(compiled)

dossist's avatar
dossist committed
774 775
        if menubody:
            html = u'''
dossist's avatar
dossist committed
776 777 778 779 780 781 782
            <li class="dropdown">
              <!-- Menu button -->
              <a href="#" class="menu-nav-menu dropdown-toggle" data-toggle="dropdown">
                %s<span class="padding"></span><span class="caret"></span>
              </a>
              <!-- Dropdown contents -->
              <ul class="dropdown-menu">
dossist's avatar
dossist committed
783
%s
dossist's avatar
dossist committed
784 785
              </ul>
            </li> <!-- /dropdown -->
dossist's avatar
dossist committed
786 787 788 789 790 791
''' % (_('Menu'), menubody)
        else:
            html = u''

        return html

dossist's avatar
dossist committed
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
    def menuGetQueryString(self, args):
        """
        Return a URL query string generated from arguments dictionary.
        {'q1': 'v1', 'q2': 'v2'} will turn into u'?q1=val&q2=val'
        """
        parts = []
        for key, value in args.iteritems():
            if value:
                parts.append(u'%s=%s' % (key, value))
        output = u'&'.join(parts)
        if output:
            output = u'?' + output
        return output

    def menuCompile(self, d, menu, menu_def):
        """
        Return a compiled list of menu data ready to input to renderer.
        """
        # subroutines to generate compiled data
        def generateAction(action, title=''):
            query = self.menuGetQueryString({'action': action, 'rev': rev})
            if not title:
                title = _(action)
            return (action, title, u'%s%s' % (page.url(request), query), False)
        def generateHeader(key, title):
            return (key, _(title), '', 'header')
        def generateSpecial(key, data):
            return (key, data.get('title', _(key)), data.get('href', u''), data.get('special', False))
        def generateNormal(key, data):
dossist's avatar
dossist committed
821 822 823
            if not data.get('href'):
                data['href'] = page.url(request)
            if data.get('args'):
dossist's avatar
dossist committed
824 825
                data['href'] = u'%s%s' % (data['href'], self.menuGetQueryString(data['args']))
            return (key, data.get('title', _(key)), data.get('href'), False)
dossist's avatar
dossist committed
826

dossist's avatar
dossist committed
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844
        request = self.request
        _ = request.getText
        rev = request.rev
        page = d['page']
        header_re = re.compile(r'^(\=+)\s+(.+?)\s+\1$') # '= title ='

        compiled = [] # [('key', 'title', 'href', 'special'), ]
        for key in menu:
            # check if key is in the definitions list
            data = menu_def.get(key)
            if data:
                # 'removed', 'disabled', 'separator' or 'header'
                if data.get('special'):
                    compiled.append(generateSpecial(key, data))
                # normal display
                else:
                    # recognizes key as action if href and args are not provided
                    if not (data.get('href') or data.get('args')):
dossist's avatar
dossist committed
845
                        if self.memodumpIsAvailableAction(page, key):
dossist's avatar
dossist committed
846 847 848 849 850 851 852 853 854 855 856 857 858
                            compiled.append(generateAction(key, title=data.get('title', '')))
                        else:
                            continue
                    # otherwise compile as a normal menu entry
                    else:
                        compiled.append(generateNormal(key, data))
            else:
                # check if key is header string
                header_match = header_re.search(key)
                # header
                if header_match:
                    compiled.append(generateHeader(key, header_match.group(2)))
                # action not in menu_def
dossist's avatar
dossist committed
859
                elif self.memodumpIsAvailableAction(page, key):
dossist's avatar
dossist committed
860 861 862 863 864 865 866 867 868 869 870 871 872
                    compiled.append(generateAction(key))

        return self.menuThinCompiled(compiled)

    def menuThinCompiled(self, compiled):
        """
        Remove unnecessary rules and headers as well as 'removed' items from compiled menu data.
        """
        how_nice = {
            False: 0,
            'header': 2,
            'separator': 1,
            'removed': 1000,
dossist's avatar
dossist committed
873
        }
dossist's avatar
dossist committed
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
        thinned = []
        atmosphere = how_nice['separator']

        for record in reversed(compiled):
            nice = how_nice.get(record[3], 0)
            if nice < atmosphere:
                thinned.append(record)
                atmosphere = nice
            if not nice:
                atmosphere = 1000

        thinned.reverse()
        return thinned

    def menuRender(self, compiled):
        templates = {
dossist's avatar
dossist committed
890 891 892 893
            False:       u'                <li><a href="%(href)s" class="menu-dd-%(key)s" rel="nofollow">%(title)s</a></li>',
            'disabled':  u'                <li class="disabled"><a href="#" class="menu-dd-%(key)s" rel="nofollow">%(title)s</a></li>',
            'separator': u'                <li class="divider"></li>',
            'header':    u'                <li class="dropdown-header">%(title)s</li>',
dossist's avatar
dossist committed
894
            'removed':   u'',
dossist's avatar
dossist committed
895
        }
896

dossist's avatar
dossist committed
897
        lines = []
dossist's avatar
dossist committed
898 899 900 901
        for record in compiled:
            special = record[3]
            dictionary = dict(key=record[0], title=record[1], href=record[2])
            lines.append(templates[special] % dictionary)
dossist's avatar
dossist committed
902
        return u'\n'.join(lines)
dossist's avatar
dossist committed
903

dossist's avatar
dossist committed
904
    def memodumpIsAvailableAction(self, page, action):
dossist's avatar
dossist committed
905
        """
dossist's avatar
dossist committed
906 907
        Return if action is available or not.
        If action starts with lowercase, return True without actually check if action exists.
dossist's avatar
dossist committed
908 909 910 911
        """
        request = self.request
        excluded = request.cfg.actions_excluded
        available = get_available_actions(request.cfg, page, request.user)
dossist's avatar
dossist committed
912
        return not (action in excluded or (action[0].isupper() and not action in available))
dossist's avatar
dossist committed
913

dossist's avatar
dossist committed
914
    def memodumpIsEditablePage(self, page):
dossist's avatar
dossist committed
915
        """
dossist's avatar
dossist committed
916
        Return True if page is editable for current user, False if not.
dossist's avatar
dossist committed
917 918 919 920

        @param page: page object
        """
        return page.isWritable() and self.request.user.may.write(page.page_name)
dossist's avatar
dossist committed
921 922 923 924 925

    def menuQuickLink(self, page):
        """
        Return quicklink action name and localized text according to status of page

dossist's avatar
dossist committed
926
        @param page: page object
dossist's avatar
dossist committed
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962
        @rtype: unicode
        @return (action, text)
        """
        if not self.request.user.valid:
            return (u'', u'')

        _ = self.request.getText
        if self.request.user.isQuickLinkedTo([page.page_name]):
            action, text = u'quickunlink', _("Remove Link")
        else:
            action, text = u'quicklink', _("Add Link")
        if action in self.request.cfg.actions_excluded:
            return (u'', u'')

        return (action, text)

    def menuSubscribe(self, page):
        """
        Return subscribe action name and localized text according to status of page

        @rtype: unicode
        @return (action, text)
        """
        if not ((self.cfg.mail_enabled or self.cfg.jabber_enabled) and self.request.user.valid):
            return (u'', u'')

        _ = self.request.getText
        if self.request.user.isSubscribedTo([page.page_name]):
            action, text = 'unsubscribe', _("Unsubscribe")
        else:
            action, text = 'subscribe', _("Subscribe")
        if action in self.request.cfg.actions_excluded:
            return (u'', u'')

        return (action, text)

dossist's avatar
dossist committed
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
    def sidebar(self, d, **keywords):
        """ Display page called SideBar as an additional element on every page
        content_id has been changed from the original

        @param d: parameter dictionary
        @rtype: string
        @return: sidebar html
        """

        # Check which page to display, return nothing if doesn't exist.
        sidebar = self.request.getPragma('sidebar', u'SideBar')
        page = Page(self.request, sidebar)
        if not page.exists():
            return u""
        # Capture the page's generated HTML in a buffer.
        buffer = StringIO.StringIO()
        self.request.redirect(buffer)
        try:
            page.send_page(content_only=1, content_id="sidebar-content")
        finally:
            self.request.redirect()
984
        return u'<div class="sidebar clearfix">%s</div>' % buffer.getvalue()
dossist's avatar
dossist committed
985

dossist's avatar
dossist committed
986 987 988 989 990 991 992 993 994 995 996
    def trail(self, d):
        """ Assemble page trail

        @param d: parameter dictionary
        @rtype: unicode
        @return: trail html
        """
        _ = self.request.getText
        request = self.request
        user = request.user
        html = u''
997
        li = u'                <li>%s</li>'
dossist's avatar
dossist committed
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023

        if not user.valid or user.show_page_trail:
            trail = user.getTrail()
            if trail:
                items = []
                for pagename in trail:
                    try:
                        interwiki, page = wikiutil.split_interwiki(pagename)
                        if interwiki != request.cfg.interwikiname and interwiki != 'Self':
                            link = (self.request.formatter.interwikilink(True, interwiki, page) +
                                    self.shortenPagename(page) +
                                    self.request.formatter.interwikilink(False, interwiki, page))
                            items.append(li % link)
                            continue
                        else:
                            pagename = page

                    except ValueError:
                        pass
                    page = Page(request, pagename)
                    title = page.split_title()
                    title = self.shortenPagename(title)
                    link = page.link_to(request, title)
                    items.append(li % link)

                html = u'''
1024 1025 1026
            <div id="pagetrail">
              <h4>%s</h4>
              <ul>
dossist's avatar
dossist committed
1027
%s
1028 1029
              </ul>
            </div>
dossist's avatar
dossist committed
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
''' % (_('Trail'), u'\n'.join(items))

        return html

    def quicklinks(self, d):
        """ Assemble quicklinks

        @param d: parameter dictionary
        @rtype: unicode
        @return: quicklinks html
        """
        _ = self.request.getText
        html = u''
1043
        li = u'                <li class="%s">%s</li>'
dossist's avatar
dossist committed
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
        found = {}
        items = []
        current = d['page_name']

        userlinks = self.request.user.getQuickLinks()
        for text in userlinks:
            # non-localized anchor and texts
            pagename, link = self.splitNavilink(text, localize=0)
            if not pagename in found:
                if pagename == current:
                    cls = 'userlink active'
                    link = u'<a>%s</a>' % pagename
                else:
                    cls = 'userlink'
                items.append(li % (cls, link))
                found[pagename] = 1

        if items:
            html = u'''
1063 1064 1065
            <div id="quicklinks">
              <h4>%s</h4>
              <ul>
dossist's avatar
dossist committed
1066
%s
1067 1068
              </ul>
            </div>
dossist's avatar
dossist committed
1069 1070 1071 1072
''' % (_('Quick links'), u'\n'.join(items))

        return html

1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
    def navibar(self, d):
        """ Assemble the navibar (which moved to sidebar as one of sections)
        NavIbar, not the navbar at the page top!

        @param d: parameter dictionary
        @rtype: unicode
        @return: navibar html
        """
        request = self.request
        _ = request.getText
        found = {} # pages we found. prevent duplicates
        items = [] # navibar items
1085
        item = u'                <li class="%s">%s</li>'
1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143
        current = d['page_name']

        # Process config navi_bar
        if request.cfg.navi_bar:
            for text in request.cfg.navi_bar:
                pagename, link = self.splitNavilink(text)
                if pagename == current:
                    cls = 'wikilink active'
                else:
                    cls = 'wikilink'
                items.append(item % (cls, link))
                found[pagename] = 1

        # Add user links to wiki links, eliminating duplicates.
        userlinks = request.user.getQuickLinks()
        for text in userlinks:
            # Split text without localization, user knows what he wants
            pagename, link = self.splitNavilink(text, localize=0)
            if not pagename in found:
                if pagename == current:
                    cls = 'userlink active'
                else:
                    cls = 'userlink'
                items.append(item % (cls, link))
                found[pagename] = 1

        # Add current page at end of local pages
#       if not current in found:
#           title = d['page'].split_title()
#           title = self.shortenPagename(title)
#           link = d['page'].link_to(request, title)
#           cls = 'active'
#           items.append(item % (cls, link))

        # Add sister pages.
        for sistername, sisterurl in request.cfg.sistersites:
            if sistername == request.cfg.interwikiname: # it is THIS wiki
                cls = 'sisterwiki active'
                items.append(item % (cls, sistername))
            else:
                # TODO optimize performance
                cache = caching.CacheEntry(request, 'sisters', sistername, 'farm', use_pickle=True)
                if cache.exists():
                    data = cache.content()
                    sisterpages = data['sisterpages']
                    if current in sisterpages:
                        cls = 'sisterwiki'
                        url = sisterpages[current]
                        link = request.formatter.url(1, url) + \
                               request.formatter.text(sistername) +\
                               request.formatter.url(0)
                        items.append(item % (cls, link))

        # Assemble html
        items = u''.join(items)
        html = u''
        if items:
            html = u'''
1144 1145 1146
            <div>
              <h4>%s</h4>
              <ul id='navibar'>
1147
%s
1148 1149
              </ul>
            </div>
1150 1151 1152 1153
''' % (_('Navigation'), items)

        return html

dossist's avatar
dossist committed
1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
    def msg(self, d):
        """ Assemble the msg display

        Display a message in an alert box with an optional close button.

        @param d: parameter dictionary
        @rtype: unicode
        @return: msg display html
        """
        _ = self.request.getText
        msgs = d['msg']

        msg_conv = {
            'hint': 'alert-success',
            'info': 'alert-info',
            'warning': 'alert-warning',
            'error': 'alert-danger',
dossist's avatar
dossist committed
1171
        }
dossist's avatar
dossist committed
1172 1173 1174

        result = []
        template = u'''
1175 1176 1177 1178
        <div class="alert %(dismiss)s%(color)s">
          %(close)s
          %(msg)s
        </div>
dossist's avatar
dossist committed
1179 1180 1181
'''
        param = {
            'close': u'<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>',
dossist's avatar
dossist committed
1182
            'dismiss': 'alert-dismissible ',
dossist's avatar
dossist committed
1183 1184
            'msg': '',
            'color': '',
dossist's avatar
dossist committed
1185
        }
dossist's avatar
dossist committed
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209

        for msg, msg_class in msgs:
            param['color'] = msg_conv['info']
            try:
                param['msg'] = msg.render()
                param['close'] = ''
                param['dismiss'] = ''
            except AttributeError:
                if msg and msg_class:
                    param['msg'] = msg
                    if msg_class in msg_conv:
                        param['color'] = msg_conv[msg_class]
                elif msg:
                    param['msg'] = msg
            finally:
                if msg:
                    result.append(template % param)
        if result:
            return u'\n'.join(result)
        else:
            return u''

    def send_title(self, text, **keywords):
        """ Capture original send_title() and rewrite DOCTYPE for html5 """
dossist's avatar
dossist committed
1210 1211 1212

        # for mobile
        additional_head = u'<meta name="viewport" content="width=device-width,initial-scale=1.0">\n'
dossist's avatar
dossist committed
1213 1214 1215 1216 1217 1218
        try:
            if not self.request.cfg.memodump_additional_head:
                raise AttributeError
        except AttributeError:
            self.request.cfg.html_head = u'%s%s' % (additional_head, self.request.cfg.html_head)
            self.request.cfg.memodump_additional_head = True
dossist's avatar
dossist committed
1219

dossist's avatar
dossist committed
1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
        buffer = StringIO.StringIO()
        self.request.redirect(buffer)
        try:
            ThemeBase.send_title(self, text, **keywords)
        finally:
            self.request.redirect()
        html = re.sub(ur'^<!DOCTYPE HTML .*?>\n', ur'<!DOCTYPE html>\n', buffer.getvalue())
        self.request.write(html)

    def guiEditorScript(self, d):
dossist's avatar
dossist committed
1230
        """ Disable default skin javascript to prevent gui edit button from automatically appearing """
dossist's avatar
dossist committed
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251
        return u''

    def _stylesheet_link(self, theme, media, href, title=None):
        """ Removed charset attribute to satisfy html5 requirements """
        if theme:
            href = '%s/%s/css/%s.css' % (self.cfg.url_prefix_static, self.name, href)
        attrs = 'type="text/css" media="%s" href="%s"' % (
                media, wikiutil.escape(href, True), )
        if title:
            return '<link rel="alternate stylesheet" %s title="%s">' % (attrs, title)
        else:
            return '<link rel="stylesheet" %s>' % attrs

def execute(request):
    """
    Generate and return a theme object

    @param request: the request object
    @rtype: MoinTheme
    @return: Theme object
    """
1252
    return Theme(request)