simplify auto scroll initialization; fix bug in IE init discovered when using IE7 on pages with wide tables
1 # -*- coding: iso-8859-1 -*-
3 MoinMoin - Theme Package
6 @license: GNU GPL, see COPYING for details.
11 from MoinMoin import log
12 logging = log.getLogger(__name__)
14 from MoinMoin import i18n, wikiutil, config, version, caching
15 from MoinMoin.action import get_available_actions
16 from MoinMoin.Page import Page
17 from MoinMoin.util import pysupport
19 modules = pysupport.getPackageModules(__file__)
21 # Check whether we can emit a RSS feed.
22 # RSS is broken on plain Python 2.4.x, and works only when installing PyXML.
23 # News: A user reported that the RSS is valid when using Python 2.5.1 on Windows.
25 rss_supported = sys.version_info[:3] >= (2, 5, 1) or '_xmlplus' in xml.__file__
29 """ Base class for themes
31 This class supply all the standard template that sub classes can
32 use without rewriting the same code. If you want to change certain
33 elements, override them.
38 # fake _ function to get gettext recognize those texts:
41 # TODO: remove icons that are not used any more.
43 # key alt icon filename w h
44 # ------------------------------------------------------------------
46 'help': ("%(page_help_contents)s", "moin-help.png", 12, 11),
47 'find': ("%(page_find_page)s", "moin-search.png", 12, 12),
48 'diff': (_("Diffs"), "moin-diff.png", 15, 11),
49 'info': (_("Info"), "moin-info.png", 12, 11),
50 'edit': (_("Edit"), "moin-edit.png", 12, 12),
51 'unsubscribe': (_("Unsubscribe"), "moin-unsubscribe.png", 14, 10),
52 'subscribe': (_("Subscribe"), "moin-subscribe.png", 14, 10),
53 'raw': (_("Raw"), "moin-raw.png", 12, 13),
54 'xml': (_("XML"), "moin-xml.png", 20, 13),
55 'print': (_("Print"), "moin-print.png", 16, 14),
56 'view': (_("View"), "moin-show.png", 12, 13),
57 'home': (_("Home"), "moin-home.png", 13, 12),
58 'up': (_("Up"), "moin-parent.png", 15, 13),
60 'attach': ("%(attach_count)s", "moin-attach.png", 7, 15),
61 'attachimg': ("", "attach.png", 32, 32),
63 'rss': (_("[RSS]"), "moin-rss.png", 24, 24),
64 'deleted': (_("[DELETED]"), "moin-deleted.png", 60, 12),
65 'updated': (_("[UPDATED]"), "moin-updated.png", 60, 12),
66 'renamed': (_("[RENAMED]"), "moin-renamed.png", 60, 12),
67 'conflict': (_("[CONFLICT]"), "moin-conflict.png", 60, 12),
68 'new': (_("[NEW]"), "moin-new.png", 31, 12),
69 'diffrc': (_("[DIFF]"), "moin-diff.png", 15, 11),
71 'bottom': (_("[BOTTOM]"), "moin-bottom.png", 14, 10),
72 'top': (_("[TOP]"), "moin-top.png", 14, 10),
73 'www': ("[WWW]", "moin-www.png", 11, 11),
74 'mailto': ("[MAILTO]", "moin-email.png", 14, 10),
75 'news': ("[NEWS]", "moin-news.png", 10, 11),
76 'telnet': ("[TELNET]", "moin-telnet.png", 10, 11),
77 'ftp': ("[FTP]", "moin-ftp.png", 11, 11),
78 'file': ("[FILE]", "moin-ftp.png", 11, 11),
80 'searchbutton': ("[?]", "moin-search.png", 12, 12),
81 'interwiki': ("[%(wikitag)s]", "moin-inter.png", 16, 16),
83 # smileys (this is CONTENT, but good looking smileys depend on looking
84 # adapted to the theme background color and theme style in general)
85 #vvv == vvv this must be the same for GUI editor converter
86 'X-(': ("X-(", 'angry.png', 15, 15),
87 ':D': (":D", 'biggrin.png', 15, 15),
88 '<:(': ("<:(", 'frown.png', 15, 15),
89 ':o': (":o", 'redface.png', 15, 15),
90 ':(': (":(", 'sad.png', 15, 15),
91 ':)': (":)", 'smile.png', 15, 15),
92 'B)': ("B)", 'smile2.png', 15, 15),
93 ':))': (":))", 'smile3.png', 15, 15),
94 ';)': (";)", 'smile4.png', 15, 15),
95 '/!\\': ("/!\\", 'alert.png', 15, 15),
96 '<!>': ("<!>", 'attention.png', 15, 15),
97 '(!)': ("(!)", 'idea.png', 15, 15),
99 # copied 2001-11-16 from http://pikie.darktech.org/cgi/pikie.py?EmotIcon
100 ':-?': (":-?", 'tongue.png', 15, 15),
101 ':\\': (":\\", 'ohwell.png', 15, 15),
102 '>:>': (">:>", 'devil.png', 15, 15),
103 '|)': ("|)", 'tired.png', 15, 15),
105 # some folks use noses in their emoticons
106 ':-(': (":-(", 'sad.png', 15, 15),
107 ':-)': (":-)", 'smile.png', 15, 15),
108 'B-)': ("B-)", 'smile2.png', 15, 15),
109 ':-))': (":-))", 'smile3.png', 15, 15),
110 ';-)': (";-)", 'smile4.png', 15, 15),
111 '|-)': ("|-)", 'tired.png', 15, 15),
114 '(./)': ("(./)", 'checkmark.png', 20, 15),
115 '{OK}': ("{OK}", 'thumbs-up.png', 14, 12),
116 '{X}': ("{X}", 'icon-error.png', 16, 16),
117 '{i}': ("{i}", 'icon-info.png', 16, 16),
118 '{1}': ("{1}", 'prio1.png', 15, 13),
119 '{2}': ("{2}", 'prio2.png', 15, 13),
120 '{3}': ("{3}", 'prio3.png', 15, 13),
122 # version 1.3.4 (stars)
124 '{*}': ("{*}", 'star_on.png', 15, 15),
125 '{o}': ("{o}", 'star_off.png', 15, 15),
129 # Style sheets - usually there is no need to override this in sub
130 # classes. Simply supply the css files in the css directory.
132 # Standard set of style sheets
136 ('screen', 'screen'),
138 ('projection', 'projection'),
142 stylesheets_print = (
148 # Used in slide show mode
149 stylesheets_projection = (
152 ('all', 'projection'),
155 stylesheetsCharset = 'utf-8'
157 def __init__(self, request):
159 Initialize the theme object.
161 @param request: the request object
163 self.request = request
164 self.cfg = request.cfg
165 self._cache = {} # Used to cache elements that may be used several times
167 self._send_title_called = False
169 def img_url(self, img):
170 """ Generate an image href
172 @param img: the image filename
174 @return: the image href
176 return "%s/%s/img/%s" % (self.cfg.url_prefix_static, self.name, img)
178 def emit_custom_html(self, html):
180 generate custom HTML code in `html`
182 @param html: a string or a callable object, in which case
183 it is called and its return value is used
185 @return: string with html
189 html = html(self.request)
193 """ Assemble logo with link to front page
195 The logo contain an image and or text or any html markup the
196 admin inserted in the config file. Everything it enclosed inside
197 a div with id="logo".
203 if self.cfg.logo_string:
204 page = wikiutil.getFrontPage(self.request)
205 logo = page.link_to_raw(self.request, self.cfg.logo_string)
206 html = u'''<div id="logo">%s</div>''' % logo
209 def interwiki(self, d):
210 """ Assemble the interwiki name display, linking to page_front_page
212 @param d: parameter dictionary
214 @return: interwiki html
216 if self.request.cfg.show_interwiki:
217 page = wikiutil.getFrontPage(self.request)
218 text = self.request.cfg.interwikiname or u'Self'
219 link = page.link_to(self.request, text=text, rel='nofollow')
220 html = u'<div id="interwiki"><span>%s</span></div>' % link
226 """ Assemble the title (now using breadcrumbs)
228 @param d: parameter dictionary
232 _ = self.request.getText
234 if d['title_text'] == d['page'].split_title(): # just showing a page, no action
236 segments = d['page_name'].split('/') # was: title_text
237 for s in segments[:-1]:
239 content.append("<li>%s</li>" % Page(self.request, curpage).link_to(self.request, s))
241 link_text = segments[-1]
242 link_title = _('Click to do a full-text search for this title')
244 'action': 'fullsearch',
245 'value': 'linkto:"%s"' % d['page_name'],
248 # we dont use d['title_link'] any more, but make it ourselves:
249 link = d['page'].link_to(self.request, link_text, querystr=link_query, title=link_title, css_class='backlink', rel='nofollow')
250 content.append(('<li>%s</li>') % link)
252 content.append('<li>%s</li>' % wikiutil.escape(d['title_text']))
255 <ul id="pagelocation">
258 ''' % "".join(content)
261 def title_with_separators(self, d):
262 """ Assemble the title using slashes, not <ul>
264 @param d: parameter dictionary
268 _ = self.request.getText
269 if d['title_text'] == d['page'].split_title():
270 # just showing a page, no action
271 segments = d['page_name'].split('/')
272 link_text = segments[-1]
273 link_title = _('Click to do a full-text search for this title')
274 link_query = {'action': 'fullsearch', 'context': '180',
275 'value': 'linkto:"%s"' % d['page_name'], }
276 link = d['page'].link_to(self.request, link_text,
277 querystr=link_query, title=link_title,
278 css_class='backlink', rel='nofollow')
279 if len(segments) <= 1:
284 for s in segments[:-1]:
286 content.append(Page(self.request,
287 curpage).link_to(self.request, s))
289 path_html = u'<span class="sep">/</span>'.join(content)
290 html = u'<span class="pagepath">%s</span><span class="sep">/</span>%s' % (path_html, link)
292 html = wikiutil.escape(d['title_text'])
293 return u'<span id="pagelocation">%s</span>' % html
295 def username(self, d):
296 """ Assemble the username / userprefs link
298 @param d: parameter dictionary
300 @return: username html
302 request = self.request
306 # Add username/homepage link for registered users. We don't care
307 # if it exists, the user can create it.
308 if request.user.valid and request.user.name:
309 interwiki = wikiutil.getInterwikiHomePage(request)
310 name = request.user.name
311 aliasname = request.user.aliasname
314 title = "%s @ %s" % (aliasname, interwiki[0])
315 # link to (interwiki) user homepage
316 homelink = (request.formatter.interwikilink(1, title=title, id="userhome", generated=True, *interwiki) +
317 request.formatter.text(name) +
318 request.formatter.interwikilink(0, title=title, id="userhome", *interwiki))
319 userlinks.append(homelink)
320 # link to userprefs action
321 if 'userprefs' not in self.request.cfg.actions_excluded:
322 userlinks.append(d['page'].link_to(request, text=_('Settings'),
323 querystr={'action': 'userprefs'}, id='userprefs', rel='nofollow'))
325 if request.user.valid:
326 if request.user.auth_method in request.cfg.auth_can_logout:
327 userlinks.append(d['page'].link_to(request, text=_('Logout'),
328 querystr={'action': 'logout', 'logout': 'logout'}, id='logout', rel='nofollow'))
330 query = {'action': 'login'}
331 # special direct-login link if the auth methods want no input
332 if request.cfg.auth_login_inputs == ['special_no_input']:
334 if request.cfg.auth_have_login:
335 userlinks.append(d['page'].link_to(request, text=_("Login"),
336 querystr=query, id='login', rel='nofollow'))
338 userlinks = [u'<li>%s</li>' % link for link in userlinks]
339 html = u'<ul id="username">%s</ul>' % ''.join(userlinks)
342 def splitNavilink(self, text, localize=1):
343 """ Split navibar links into pagename, link to page
345 Admin or user might want to use shorter navibar items by using
346 the [[page|title]] or [[url|title]] syntax. In this case, we don't
347 use localization, and the links goes to page or to the url, not
348 the localized version of page.
353 * wiki:WikiName:PageName
355 * all targets as seen above with title: [[target|title]]
357 @param text: the text used in config or user preferences
359 @return: pagename or url, link to page or url
361 request = self.request
362 fmt = request.formatter
365 # Handle [[pagename|title]] or [[url|title]] formats
366 if text.startswith('[[') and text.endswith(']]'):
369 pagename, title = text.split('|', 1)
370 pagename = pagename.strip()
371 title = title.strip()
373 except (ValueError, TypeError):
374 # Just use the text as is.
375 pagename = text.strip()
379 if wikiutil.is_URL(pagename):
382 link = fmt.url(1, pagename) + fmt.text(title) + fmt.url(0)
383 return pagename, link
385 # remove wiki: url prefix
386 if pagename.startswith("wiki:"):
387 pagename = pagename[5:]
389 # try handling interwiki links
391 interwiki, page = wikiutil.split_interwiki(pagename)
392 thiswiki = request.cfg.interwikiname
393 if interwiki == thiswiki or interwiki == 'Self':
398 link = fmt.interwikilink(True, interwiki, page) + fmt.text(title) + fmt.interwikilink(False, interwiki, page)
399 return pagename, link
403 # Handle regular pagename like "FrontPage"
404 pagename = wikiutil.normalize_pagename(pagename, request.cfg)
406 # Use localized pages for the current user
408 page = wikiutil.getLocalizedPage(request, pagename)
410 page = Page(request, pagename)
412 pagename = page.page_name # can be different, due to i18n
415 title = page.split_title()
416 title = self.shortenPagename(title)
418 link = page.link_to(request, title)
420 return pagename, link
422 def shortenPagename(self, name):
423 """ Shorten page names
425 Shorten very long page names that tend to break the user
426 interface. The short name is usually fine, unless really stupid
427 long names are used (WYGIWYD).
429 If you don't like to do this in your theme, or want to use
430 different algorithm, override this method.
432 @param name: page name, unicode
434 @return: shortened version.
436 maxLength = self.maxPagenameLength()
437 # First use only the sub page name, that might be enough
438 if len(name) > maxLength:
439 name = name.split('/')[-1]
440 # If it's not enough, replace the middle with '...'
441 if len(name) > maxLength:
442 half, left = divmod(maxLength - 3, 2)
443 name = u'%s...%s' % (name[:half + left], name[-half:])
446 def maxPagenameLength(self):
447 """ Return maximum length for shortened page names """
450 def navibar(self, d):
451 """ Assemble the navibar
453 @param d: parameter dictionary
455 @return: navibar html
457 request = self.request
458 found = {} # pages we found. prevent duplicates
459 items = [] # navibar items
460 item = u'<li class="%s">%s</li>'
461 current = d['page_name']
463 # Process config navi_bar
464 if request.cfg.navi_bar:
465 for text in request.cfg.navi_bar:
466 pagename, link = self.splitNavilink(text)
467 if pagename == current:
468 cls = 'wikilink current'
471 items.append(item % (cls, link))
474 # Add user links to wiki links, eliminating duplicates.
475 userlinks = request.user.getQuickLinks()
476 for text in userlinks:
477 # Split text without localization, user knows what he wants
478 pagename, link = self.splitNavilink(text, localize=0)
479 if not pagename in found:
480 if pagename == current:
481 cls = 'userlink current'
484 items.append(item % (cls, link))
487 # Add current page at end of local pages
488 if not current in found:
489 title = d['page'].split_title()
490 title = self.shortenPagename(title)
491 link = d['page'].link_to(request, title)
493 items.append(item % (cls, link))
496 for sistername, sisterurl in request.cfg.sistersites:
497 if sistername == request.cfg.interwikiname: # it is THIS wiki
498 cls = 'sisterwiki current'
499 items.append(item % (cls, sistername))
501 # TODO optimize performance
502 cache = caching.CacheEntry(request, 'sisters', sistername, 'farm', use_pickle=True)
504 data = cache.content()
505 sisterpages = data['sisterpages']
506 if current in sisterpages:
508 url = sisterpages[current]
509 link = request.formatter.url(1, url) + \
510 request.formatter.text(sistername) +\
511 request.formatter.url(0)
512 items.append(item % (cls, link))
515 items = u''.join(items)
523 def get_icon(self, icon):
524 """ Return icon data from self.icons
526 If called from <<Icon(file)>> we have a filename, not a
527 key. Using filenames is deprecated, but for now, we simulate old
530 @param icon: icon name or file name (string)
532 @return: alt (unicode), href (string), width, height (int)
534 if icon in self.icons:
535 alt, icon, w, h = self.icons[icon]
537 # Create filenames to icon data mapping on first call, then
538 # cache in class for next calls.
539 if not getattr(self.__class__, 'iconsByFile', None):
541 for data in self.icons.values():
543 self.__class__.iconsByFile = d
545 # Try to get icon data by file name
546 if icon in self.iconsByFile:
547 alt, icon, w, h = self.iconsByFile[icon]
549 alt, icon, w, h = '', icon, '', ''
551 return alt, self.img_url(icon), w, h
553 def make_icon(self, icon, vars=None, **kw):
555 This is the central routine for making <img> tags for icons!
556 All icons stuff except the top left logo and search field icons are
559 @param icon: icon id (dict key)
562 @return: icon html (img tag)
566 alt, img, w, h = self.get_icon(icon)
568 alt = vars['icon-alt-text'] # if it is possible we take the alt-text from 'page_icons_table'
569 except KeyError, err:
571 alt = alt % vars # if not we just leave the alt-text from 'icons'
572 except KeyError, err:
573 alt = 'KeyError: %s' % str(err)
574 alt = self.request.getText(alt)
575 tag = self.request.formatter.image(src=img, alt=alt, width=w, height=h, **kw)
578 def make_iconlink(self, which, d):
580 Make a link with an icon
582 @param which: icon id (dictionary key)
583 @param d: parameter dictionary
585 @return: html link tag
588 pagekey, querystr, title, icon = self.cfg.page_icons_table[which]
589 qs.update(querystr) # do not modify the querystr dict in the cfg!
590 d['icon-alt-text'] = d['title'] = title % d
591 d['i18ntitle'] = self.request.getText(d['title'])
592 img_src = self.make_icon(icon, d)
594 if rev and which in ['raw', 'print', ]:
596 attrs = {'rel': 'nofollow', 'title': d['i18ntitle'], }
598 if isinstance(page, unicode):
599 # e.g. d['page_parent_page'] is just the unicode pagename
600 # while d['page'] will give a page object
601 page = Page(self.request, page)
602 return page.link_to_raw(self.request, text=img_src, querystr=qs, **attrs)
605 """ Assemble the msg display
607 Display a message with a widget or simple strings with a clear message link.
609 @param d: parameter dictionary
611 @return: msg display html
613 _ = self.request.getText
617 close = d['page'].link_to(self.request, text=_('Clear message'), css_class="clear-link")
618 for msg, msg_class in msgs:
620 result += u'<p>%s</p>' % msg.render()
622 except AttributeError:
623 if msg and msg_class:
624 result += u'<p><div class="%s">%s</div></p>' % (msg_class, msg)
626 result += u'<p>%s</p>\n' % msg
628 html = result + close
629 return u'<div id="message">\n%s\n</div>\n' % html
633 return u'<div id="message">\n%s\n</div>\n' % html
636 """ Assemble page trail
638 @param d: parameter dictionary
642 request = self.request
645 if not user.valid or user.show_page_trail:
646 trail = user.getTrail()
649 for pagename in trail:
651 interwiki, page = wikiutil.split_interwiki(pagename)
652 if interwiki != request.cfg.interwikiname and interwiki != 'Self':
653 link = (self.request.formatter.interwikilink(True, interwiki, page) +
654 self.shortenPagename(page) +
655 self.request.formatter.interwikilink(False, interwiki, page))
656 items.append('<li>%s</li>' % link)
663 page = Page(request, pagename)
664 title = page.split_title()
665 title = self.shortenPagename(title)
666 link = page.link_to(request, title)
667 items.append('<li>%s</li>' % link)
671 </ul>''' % ''.join(items)
674 def _stylesheet_link(self, theme, media, href, title=None):
676 Create a link tag for a stylesheet.
678 @param theme: True: href gives the basename of a theme stylesheet,
679 False: href is a full url of a user/admin defined stylesheet.
680 @param media: 'all', 'screen', 'print', 'projection', ...
681 @param href: see param theme
682 @param title: optional title (for alternate stylesheets), see
683 http://www.w3.org/Style/Examples/007/alternatives
685 @return: stylesheet link html
688 href = '%s/%s/css/%s.css' % (self.cfg.url_prefix_static, self.name, href)
689 attrs = 'type="text/css" charset="%s" media="%s" href="%s"' % (
690 self.stylesheetsCharset, media, href, )
692 return '<link rel="alternate stylesheet" %s title="%s">' % (attrs, title)
694 return '<link rel="stylesheet" %s>' % attrs
696 def html_stylesheets(self, d):
697 """ Assemble html head stylesheet links
699 @param d: parameter dictionary
701 @return: stylesheets links
703 request = self.request
705 if d.get('print_mode'):
706 media = d.get('media', 'print')
707 stylesheets = getattr(self, 'stylesheets_' + media)
709 stylesheets = self.stylesheets
711 theme_css = [self._stylesheet_link(True, *stylesheet) for stylesheet in stylesheets]
712 cfg_css = [self._stylesheet_link(False, *stylesheet) for stylesheet in request.cfg.stylesheets]
715 <!-- css only for MS IE6/IE7 browsers -->
719 """ % self._stylesheet_link(True, 'all', 'msie')
721 # Add user css url (assuming that user css uses same charset)
722 href = request.user.valid and request.user.css_url
723 if href and href.lower() != "none":
724 user_css = self._stylesheet_link(False, 'all', href)
728 return '\n'.join(theme_css + cfg_css + [msie_css, user_css])
730 def shouldShowPageinfo(self, page):
731 """ Should we show page info?
733 Should be implemented by actions. For now, we check here by action
736 @param page: current page
738 @return: true if should show page info
740 if page.exists() and self.request.user.may.read(page.page_name):
741 # These actions show the page content.
742 # TODO: on new action, page info will not show.
743 # A better solution will be if the action itself answer the question: showPageInfo().
744 contentActions = [u'', u'show', u'refresh', u'preview', u'diff',
745 u'subscribe', u'RenamePage', u'CopyPage', u'DeletePage',
746 u'SpellCheck', u'print']
747 return self.request.action in contentActions
750 def pageinfo(self, page):
751 """ Return html fragment with page meta data
753 Since page information uses translated text, it uses the ui
754 language and direction. It looks strange sometimes, but
755 translated text using page direction looks worse.
757 @param page: current page
759 @return: page last edit information
761 _ = self.request.getText
763 if self.shouldShowPageinfo(page):
764 info = page.lastEditInfo()
767 info = _("last edited %(time)s by %(editor)s") % info
769 info = _("last modified %(time)s") % info
770 pagename = page.page_name
771 if self.request.cfg.show_interwiki:
772 pagename = "%s: %s" % (self.request.cfg.interwikiname, pagename)
773 info = "%s (%s)" % (wikiutil.escape(pagename), info)
774 html = '<p id="pageinfo" class="info"%(lang)s>%(info)s</p>\n' % {
775 'lang': self.ui_lang_attr(),
780 def searchform(self, d):
782 assemble HTML code for the search forms
784 @param d: parameter dictionary
786 @return: search form html
788 _ = self.request.getText
789 form = self.request.values
791 'search_label': _('Search:'),
792 'search_value': wikiutil.escape(form.get('value', ''), 1),
793 'search_full_label': _('Text'),
794 'search_title_label': _('Titles'),
795 'url': self.request.href(d['page'].page_name)
800 <form id="searchform" method="get" action="%(url)s">
802 <input type="hidden" name="action" value="fullsearch">
803 <input type="hidden" name="context" value="180">
804 <label for="searchinput">%(search_label)s</label>
805 <input id="searchinput" type="text" name="value" value="%(search_value)s" size="20"
806 onfocus="searchFocus(this)" onblur="searchBlur(this)"
807 onkeyup="searchChange(this)" onchange="searchChange(this)" alt="Search">
808 <input id="titlesearch" name="titlesearch" type="submit"
809 value="%(search_title_label)s" alt="Search Titles">
810 <input id="fullsearch" name="fullsearch" type="submit"
811 value="%(search_full_label)s" alt="Search Full Text">
814 <script type="text/javascript">
815 <!--// Initialize search form
816 var f = document.getElementById('searchform');
817 f.getElementsByTagName('label')[0].style.display = 'none';
818 var e = document.getElementById('searchinput');
826 def showversion(self, d, **keywords):
828 assemble HTML code for copyright and version display
830 @param d: parameter dictionary
832 @return: copyright and version display html
835 if self.cfg.show_version and not keywords.get('print_mode', 0):
836 html = (u'<div id="version">MoinMoin Release %s [Revision %s], '
837 'Copyright by Juergen Hermann et al.</div>') % (version.release, version.revision, )
840 def headscript(self, d):
841 """ Return html head script with common functions
843 @param d: parameter dictionary
845 @return: script for html head
847 # Don't add script for print view
848 if self.request.action == 'print':
851 _ = self.request.getText
853 <script type="text/javascript">
855 var search_hint = "%(search_hint)s";
859 'search_hint': _('Search'),
863 def shouldUseRSS(self, page):
864 """ Return True if RSS feature is available and we are on the
865 RecentChanges page, or False.
867 Currently rss is broken on plain Python, and works only when
868 installing PyXML. Return true if PyXML is installed.
870 if not rss_supported:
872 return page.page_name == u'RecentChanges' or \
873 page.page_name == self.request.getText(u'RecentChanges')
875 def rsshref(self, page):
876 """ Create rss href, used for rss button and head link
881 request = self.request
882 url = page.url(request, querystr={
883 'action': 'rss_rc', 'ddiffs': '1', 'unique': '1', }, escape=0)
886 def rsslink(self, d):
887 """ Create rss link in head, used by FireFox
889 RSS link for FireFox. This shows an rss link in the bottom of
890 the page and let you subscribe to the wiki rss feed.
897 if self.shouldUseRSS(page):
898 link = (u'<link rel="alternate" title="%s Recent Changes" '
899 u'href="%s" type="application/rss+xml">') % (
900 wikiutil.escape(self.cfg.sitename, True),
901 wikiutil.escape(self.rsshref(page), True) )
904 def html_head(self, d):
905 """ Assemble html head
907 @param d: parameter dictionary
912 u'<title>%(title)s - %(sitename)s</title>' % {
913 'title': wikiutil.escape(d['title']),
914 'sitename': wikiutil.escape(d['sitename']),
916 self.externalScript('common'),
917 self.headscript(d), # Should move to separate .js file
918 self.guiEditorScript(d),
919 self.html_stylesheets(d),
921 self.universal_edit_button(d),
923 return '\n'.join(html)
925 def externalScript(self, name):
926 """ Format external script html """
927 src = '%s/common/js/%s.js' % (self.request.cfg.url_prefix_static, name)
928 return '<script type="text/javascript" src="%s"></script>' % src
930 def universal_edit_button(self, d, **keywords):
931 """ Generate HTML for an edit link in the header."""
933 if 'edit' in self.request.cfg.actions_excluded:
935 if not (page.isWritable() and
936 self.request.user.may.write(page.page_name)):
938 _ = self.request.getText
939 querystr = {'action': 'edit'}
941 url = page.url(self.request, querystr=querystr, escape=0)
942 return (u'<link rel="alternate" type="application/wiki" '
943 u'title="%s" href="%s">' % (text, url))
945 def credits(self, d, **keywords):
946 """ Create credits html from credits list """
947 if isinstance(self.cfg.page_credits, (list, tuple)):
948 items = ['<li>%s</li>' % i for i in self.cfg.page_credits]
949 html = '<ul id="credits">\n%s\n</ul>\n' % ''.join(items)
951 # Old config using string, output as is
952 html = self.cfg.page_credits
955 def actionsMenu(self, page):
956 """ Create actions menu list and items data dict
958 The menu will contain the same items always, but items that are
959 not available will be disabled (some broken browsers will let
960 you select disabled options though).
962 The menu should give best user experience for javascript
963 enabled browsers, and acceptable behavior for those who prefer
964 not to use Javascript.
966 TODO: Move actionsMenuInit() into body onload - requires that the theme will render body,
967 it is currently done in wikiutil/page.
969 @param page: current page, Page object
971 @return: actions menu html fragment
973 request = self.request
1001 # action: menu title
1002 '__title__': _("More Actions:"),
1003 # Translation may need longer or shorter separator
1004 '__separator__': _('------------------------'),
1005 'raw': _('Raw Text'),
1006 'print': _('Print View'),
1007 'refresh': _('Delete Cache'),
1008 'SpellCheck': _('Check Spelling'), # rename action!
1009 'RenamePage': _('Rename Page'),
1010 'CopyPage': _('Copy Page'),
1011 'DeletePage': _('Delete Page'),
1012 'LikePages': _('Like Pages'),
1013 'LocalSiteMap': _('Local Site Map'),
1014 'MyPages': _('My Pages'),
1015 'SubscribeUser': _('Subscribe User'),
1016 'Despam': _('Remove Spam'),
1017 'revert': _('Revert to this revision'),
1018 'PackagePages': _('Package Pages'),
1019 'RenderAsDocbook': _('Render as Docbook'),
1020 'SyncPages': _('Sync Pages'),
1024 option = '<option value="%(action)s"%(disabled)s>%(title)s</option>'
1025 # class="disabled" is a workaround for browsers that ignore
1026 # "disabled", e.g IE, Safari
1027 # for XHTML: data['disabled'] = ' disabled="disabled"'
1028 disabled = ' disabled class="disabled"'
1030 # Format standard actions
1031 available = get_available_actions(request.cfg, page, request.user)
1033 data = {'action': action, 'disabled': '', 'title': titles[action]}
1034 # removes excluded actions from the more actions menu
1035 if action in request.cfg.actions_excluded:
1038 # Enable delete cache only if page can use caching
1039 if action == 'refresh':
1040 if not page.canUseCache():
1041 data['action'] = 'show'
1042 data['disabled'] = disabled
1044 # revert action enabled only if user can revert
1045 if action == 'revert' and not request.user.may.revert(page.page_name):
1046 data['action'] = 'show'
1047 data['disabled'] = disabled
1049 # SubscribeUser action enabled only if user has admin rights
1050 if action == 'SubscribeUser' and not request.user.may.admin(page.page_name):
1051 data['action'] = 'show'
1052 data['disabled'] = disabled
1054 # Despam action enabled only for superusers
1055 if action == 'Despam' and not request.user.isSuperUser():
1056 data['action'] = 'show'
1057 data['disabled'] = disabled
1059 # Special menu items. Without javascript, executing will
1060 # just return to the page.
1061 if action.startswith('__'):
1062 data['action'] = 'show'
1064 # Actions which are not available for this wiki, user or page
1065 if (action == '__separator__' or
1066 (action[0].isupper() and not action in available)):
1067 data['disabled'] = disabled
1069 options.append(option % data)
1071 # Add custom actions not in the standard menu, except for
1072 # some actions like AttachFile (we have them on top level)
1073 more = [item for item in available if not item in titles and not item in ('AttachFile', )]
1077 separator = option % {'action': 'show', 'disabled': disabled,
1078 'title': titles['__separator__']}
1079 options.append(separator)
1080 # Add more actions (all enabled)
1082 data = {'action': action, 'disabled': ''}
1083 # Always add spaces: AttachFile -> Attach File
1084 # XXX do not create page just for using split_title -
1085 # creating pages for non-existent does 2 storage lookups
1086 #title = Page(request, action).split_title(force=1)
1088 # Use translated version if available
1089 data['title'] = _(title)
1090 options.append(option % data)
1093 'label': titles['__title__'],
1094 'options': '\n'.join(options),
1095 'rev_field': rev and '<input type="hidden" name="rev" value="%d">' % rev or '',
1096 'do_button': _("Do"),
1097 'url': self.request.href(page.page_name)
1100 <form class="actionsmenu" method="GET" action="%(url)s">
1102 <label>%(label)s</label>
1103 <select name="action"
1104 onchange="if ((this.selectedIndex != 0) &&
1105 (this.options[this.selectedIndex].disabled == false)) {
1108 this.selectedIndex = 0;">
1111 <input type="submit" value="%(do_button)s">
1114 <script type="text/javascript">
1116 actionsMenuInit('%(label)s');
1124 def editbar(self, d):
1125 """ Assemble the page edit bar.
1127 Create html on first call, then return cached html.
1129 @param d: parameter dictionary
1131 @return: iconbar html
1134 if not self.shouldShowEditbar(page):
1137 html = self._cache.get('editbar')
1139 # Remove empty items and format as list. The item for showing inline comments
1140 # is hidden by default. It gets activated through javascript only if inline
1141 # comments exist on the page.
1143 for item in self.editbarItems(page):
1145 if 'nbcomment' in item:
1146 # hiding the complete list item is cosmetically better than just
1147 # hiding the contents (e.g. for sidebar themes).
1148 items.append('<li class="toggleCommentsButton" style="display:none;">%s</li>' % item)
1150 items.append('<li>%s</li>' % item)
1151 html = u'<ul class="editbar">%s</ul>\n' % ''.join(items)
1152 self._cache['editbar'] = html
1156 def shouldShowEditbar(self, page):
1157 """ Should we show the editbar?
1159 Actions should implement this, because only the action knows if
1160 the edit bar makes sense. Until it goes into actions, we do the
1163 @param page: current page
1165 @return: true if editbar should show
1167 # Show editbar only for existing pages, including deleted pages,
1168 # that the user may read. If you may not read, you can't edit,
1169 # so you don't need editbar.
1170 if (page.exists(includeDeleted=1) and
1171 self.request.user.may.read(page.page_name)):
1172 form = self.request.form
1173 action = self.request.action
1174 # Do not show editbar on edit but on save/cancel
1175 return not (action == 'edit' and
1176 not form.has_key('button_save') and
1177 not form.has_key('button_cancel'))
1180 def editbarItems(self, page):
1181 """ Return list of items to show on the editbar
1183 This is separate method to make it easy to customize the
1184 edtibar in sub classes.
1186 _ = self.request.getText
1187 editbar_actions = []
1188 for editbar_item in self.request.cfg.edit_bar:
1189 if (editbar_item == 'Discussion' and
1190 (self.request.getPragma('supplementation-page', self.request.cfg.supplementation_page)
1191 in (True, 1, 'on', '1'))):
1192 editbar_actions.append(self.supplementation_page_nameLink(page))
1193 elif editbar_item == 'Comments':
1194 # we just use <a> to get same style as other links, but we add some dummy
1195 # link target to get correct mouseover pointer appearance. return false
1196 # keeps the browser away from jumping to the link target::
1197 editbar_actions.append('<a href="#" class="nbcomment" onClick="toggleComments();return false;">%s</a>' % _('Comments'))
1198 elif editbar_item == 'Edit':
1199 editbar_actions.append(self.editorLink(page))
1200 elif editbar_item == 'Info':
1201 editbar_actions.append(self.infoLink(page))
1202 elif editbar_item == 'Subscribe':
1203 editbar_actions.append(self.subscribeLink(page))
1204 elif editbar_item == 'Quicklink':
1205 editbar_actions.append(self.quicklinkLink(page))
1206 elif editbar_item == 'Attachments':
1207 editbar_actions.append(self.attachmentsLink(page))
1208 elif editbar_item == 'ActionsMenu':
1209 editbar_actions.append(self.actionsMenu(page))
1210 return editbar_actions
1212 def supplementation_page_nameLink(self, page):
1213 """Return a link to the discussion page
1215 If the discussion page doesn't exist and the user
1216 has no right to create it, show a disabled link.
1218 _ = self.request.getText
1219 suppl_name = self.request.cfg.supplementation_page_name
1220 suppl_name_full = "%s/%s" % (page.page_name, suppl_name)
1222 test = Page(self.request, suppl_name_full)
1223 if not test.exists() and not self.request.user.may.write(suppl_name_full):
1224 return ('<span class="disabled">%s</span>' % _(suppl_name))
1226 return page.link_to(self.request, text=_(suppl_name),
1227 querystr={'action': 'supplementation'}, css_class='nbsupplementation', rel='nofollow')
1229 def guiworks(self, page):
1230 """ Return whether the gui editor / converter can work for that page.
1232 The GUI editor currently only works for wiki format.
1233 For simplicity, we also tell it does not work if the admin forces the text editor.
1235 is_wiki = page.pi['format'] == 'wiki'
1236 gui_disallowed = self.cfg.editor_force and self.cfg.editor_default == 'text'
1237 return is_wiki and not gui_disallowed
1240 def editorLink(self, page):
1241 """ Return a link to the editor
1243 If the user can't edit, return a disabled edit link.
1245 If the user want to show both editors, it will display "Edit
1246 (Text)", otherwise as "Edit".
1248 if 'edit' in self.request.cfg.actions_excluded:
1251 if not (page.isWritable() and
1252 self.request.user.may.write(page.page_name)):
1253 return self.disabledEdit()
1255 _ = self.request.getText
1256 querystr = {'action': 'edit'}
1258 guiworks = self.guiworks(page)
1259 if self.showBothEditLinks() and guiworks:
1260 text = _('Edit (Text)')
1261 querystr['editor'] = 'text'
1262 attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
1266 # 'textonly' will be upgraded dynamically to 'guipossible' by JS
1267 querystr['editor'] = 'textonly'
1268 attrs = {'name': 'editlink', 'rel': 'nofollow', }
1270 querystr['editor'] = 'text'
1271 attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
1273 return page.link_to(self.request, text=text, querystr=querystr, **attrs)
1275 def showBothEditLinks(self):
1276 """ Return True if both edit links should be displayed """
1277 editor = self.request.user.editor_ui
1278 if editor == '<default>':
1279 editor = self.request.cfg.editor_ui
1280 return editor == 'freechoice'
1282 def guiEditorScript(self, d):
1283 """ Return a script that set the gui editor link variables
1285 The link will be created only when javascript is enabled and
1286 the browser is compatible with the editor.
1289 if not (page.isWritable() and
1290 self.request.user.may.write(page.page_name) and
1291 self.showBothEditLinks() and
1292 self.guiworks(page)):
1295 _ = self.request.getText
1297 <script type="text/javascript">
1298 <!-- // GUI edit link and i18n
1299 var gui_editor_link_href = "%(url)s";
1300 var gui_editor_link_text = "%(text)s";
1303 """ % {'url': page.url(self.request, querystr={'action': 'edit', 'editor': 'gui', }),
1304 'text': _('Edit (GUI)'),
1307 def disabledEdit(self):
1308 """ Return a disabled edit link """
1309 _ = self.request.getText
1310 return ('<span class="disabled">%s</span>'
1311 % _('Immutable Page'))
1313 def infoLink(self, page):
1314 """ Return link to page information """
1315 if 'info' in self.request.cfg.actions_excluded:
1318 _ = self.request.getText
1319 return page.link_to(self.request,
1321 querystr={'action': 'info'}, css_class='nbinfo', rel='nofollow')
1323 def subscribeLink(self, page):
1324 """ Return subscribe/unsubscribe link to valid users
1327 @return: subscribe or unsubscribe link
1329 if not ((self.cfg.mail_enabled or self.cfg.jabber_enabled) and self.request.user.valid):
1332 _ = self.request.getText
1333 if self.request.user.isSubscribedTo([page.page_name]):
1334 action, text = 'unsubscribe', _("Unsubscribe")
1336 action, text = 'subscribe', _("Subscribe")
1337 if action in self.request.cfg.actions_excluded:
1339 return page.link_to(self.request, text=text, querystr={'action': action}, css_class='nbsubscribe', rel='nofollow')
1341 def quicklinkLink(self, page):
1342 """ Return add/remove quicklink link
1345 @return: link to add or remove a quicklink
1347 if not self.request.user.valid:
1350 _ = self.request.getText
1351 if self.request.user.isQuickLinkedTo([page.page_name]):
1352 action, text = 'quickunlink', _("Remove Link")
1354 action, text = 'quicklink', _("Add Link")
1355 if action in self.request.cfg.actions_excluded:
1357 return page.link_to(self.request, text=text, querystr={'action': action}, css_class='nbquicklink', rel='nofollow')
1359 def attachmentsLink(self, page):
1360 """ Return link to page attachments """
1361 if 'AttachFile' in self.request.cfg.actions_excluded:
1364 _ = self.request.getText
1365 return page.link_to(self.request,
1366 text=_('Attachments'),
1367 querystr={'action': 'AttachFile'}, css_class='nbattachments', rel='nofollow')
1369 def startPage(self):
1370 """ Start page div with page language and direction
1373 @return: page div with language and direction attribtues
1375 return u'<div id="page"%s>\n' % self.content_lang_attr()
1380 Add an empty page bottom div to prevent floating elements to
1381 float out of the page bottom over the footer.
1383 return '<div id="pagebottom"></div>\n</div>\n'
1385 # Public functions #####################################################
1387 def header(self, d, **kw):
1388 """ Assemble page header
1390 Default behavior is to start a page div. Sub class and add
1393 @param d: parameter dictionary
1395 @return: page header html
1397 return self.startPage()
1399 editorheader = header
1401 def footer(self, d, **keywords):
1402 """ Assemble page footer
1404 Default behavior is to end page div. Sub class and add
1407 @param d: parameter dictionary
1410 @return: page footer html
1412 return self.endPage()
1414 # RecentChanges ######################################################
1416 def recentchanges_entry(self, d):
1418 Assemble a single recentchanges entry (table row)
1420 @param d: parameter dictionary
1422 @return: recentchanges entry html
1424 _ = self.request.getText
1426 html.append('<tr>\n')
1428 html.append('<td class="rcicon1">%(icon_html)s</td>\n' % d)
1430 html.append('<td class="rcpagelink">%(pagelink_html)s</td>\n' % d)
1432 html.append('<td class="rctime">')
1434 html.append("%(time_html)s" % d)
1435 html.append('</td>\n')
1437 html.append('<td class="rcicon2">%(info_html)s</td>\n' % d)
1439 html.append('<td class="rceditor">')
1441 html.append('<br>'.join(d['editors']))
1442 html.append('</td>\n')
1444 html.append('<td class="rccomment">')
1446 if d['changecount'] > 1:
1448 for comment in d['comments']:
1449 html.append('%s<tt>#%02d</tt> %s' % (
1450 notfirst and '<br>' or '', comment[0], comment[1]))
1453 comment = d['comments'][0]
1454 html.append('%s' % comment[1])
1455 html.append('</td>\n')
1457 html.append('</tr>\n')
1459 return ''.join(html)
1461 def recentchanges_daybreak(self, d):
1463 Assemble a rc daybreak indication (table row)
1465 @param d: parameter dictionary
1467 @return: recentchanges daybreak html
1469 if d['bookmark_link_html']:
1470 set_bm = ' %(bookmark_link_html)s' % d
1473 return ('<tr class="rcdaybreak"><td colspan="%d">'
1474 '<strong>%s</strong>'
1476 '</td></tr>\n') % (6, d['date'], set_bm)
1478 def recentchanges_header(self, d):
1480 Assemble the recentchanges header (intro + open table)
1482 @param d: parameter dictionary
1484 @return: recentchanges header html
1486 _ = self.request.getText
1488 # Should use user interface language and direction
1489 html = '<div class="recentchanges"%s>\n' % self.ui_lang_attr()
1492 if self.shouldUseRSS(page):
1494 u'<div class="rcrss">',
1495 self.request.formatter.url(1, self.rsshref(page)),
1496 self.request.formatter.rawHTML(self.make_icon("rss")),
1497 self.request.formatter.url(0),
1500 html += ''.join(link)
1505 for day in d['rc_days']:
1506 if day == d['rc_max_days']:
1507 days.append('<strong>%d</strong>' % day)
1510 wikiutil.link_tag(self.request,
1511 '%s?max_days=%d' % (d['q_page_name'], day),
1513 self.request.formatter, rel='nofollow'))
1514 days = ' | '.join(days)
1515 html += (_("Show %s days.") % (days, ))
1517 if d['rc_update_bookmark']:
1518 html += " %(rc_update_bookmark)s %(rc_curr_bookmark)s" % d
1520 html += '</p>\n</div>\n'
1525 def recentchanges_footer(self, d):
1527 Assemble the recentchanges footer (close table)
1529 @param d: parameter dictionary
1531 @return: recentchanges footer html
1533 _ = self.request.getText
1535 html += '</table>\n'
1537 html += "<br>%(rc_msg)s\n" % d
1541 # Language stuff ####################################################
1543 def ui_lang_attr(self):
1544 """Generate language attributes for user interface elements
1546 User interface elements use the user language (if any), kept in
1550 @return: lang and dir html attributes
1552 lang = self.request.lang
1553 return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
1555 def content_lang_attr(self):
1556 """Generate language attributes for wiki page content
1558 Page content uses the page language or the wiki default language.
1561 @return: lang and dir html attributes
1563 lang = self.request.content_lang
1564 return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
1566 def add_msg(self, msg, msg_class=None):
1567 """ Adds a message to a list which will be used to generate status
1570 @param msg: additional message
1571 @param msg_class: html class for the div of the additional message.
1574 msg_class = 'dialog'
1575 if self._send_title_called:
1577 logging.warning("Calling add_msg() after send_title(): no message can be added.")
1578 logging.info('\n'.join(['Call stack for add_msg():'] + traceback.format_stack()))
1581 self._status.append((msg, msg_class))
1583 # stuff from wikiutil.py
1584 def send_title(self, text, **keywords):
1586 Output the page header (and title).
1588 @param text: the title text
1589 @keyword page: the page instance that called us - using this is more efficient than using pagename..
1590 @keyword pagename: 'PageName'
1591 @keyword print_mode: 1 (or 0)
1592 @keyword editor_mode: 1 (or 0)
1593 @keyword media: css media type, defaults to 'screen'
1594 @keyword allow_doubleclick: 1 (or 0)
1595 @keyword html_head: additional <head> code
1596 @keyword body_attr: additional <body> attributes
1597 @keyword body_onload: additional "onload" JavaScript code
1599 request = self.request
1603 if keywords.has_key('page'):
1604 page = keywords['page']
1605 pagename = page.page_name
1607 pagename = keywords.get('pagename', '')
1608 page = Page(request, pagename)
1609 if keywords.get('msg', ''):
1610 raise DeprecationWarning("Using send_page(msg=) is deprecated! Use theme.add_msg() instead!")
1611 scriptname = request.script_root
1613 # get name of system pages
1614 page_front_page = wikiutil.getFrontPage(request).page_name
1615 page_help_contents = wikiutil.getLocalizedPage(request, 'HelpContents').page_name
1616 page_title_index = wikiutil.getLocalizedPage(request, 'TitleIndex').page_name
1617 page_site_navigation = wikiutil.getLocalizedPage(request, 'SiteNavigation').page_name
1618 page_word_index = wikiutil.getLocalizedPage(request, 'WordIndex').page_name
1619 page_help_formatting = wikiutil.getLocalizedPage(request, 'HelpOnFormatting').page_name
1620 page_find_page = wikiutil.getLocalizedPage(request, 'FindPage').page_name
1621 home_page = wikiutil.getInterwikiHomePage(request) # sorry theme API change!!! Either None or tuple (wikiname,pagename) now.
1622 page_parent_page = getattr(page.getParentPage(), 'page_name', None)
1624 # set content_type, including charset, so web server doesn't touch it:
1625 request.content_type = "text/html; charset=%s" % (config.charset, )
1627 # Prepare the HTML <head> element
1628 user_head = [request.cfg.html_head]
1630 # include charset information - needed for moin_dump or any other case
1631 # when reading the html without a web server
1632 user_head.append('''<meta http-equiv="Content-Type" content="%s;charset=%s">\n''' % (page.output_mimetype, page.output_charset))
1634 meta_keywords = request.getPragma('keywords')
1635 meta_desc = request.getPragma('description')
1637 user_head.append('<meta name="keywords" content="%s">\n' % wikiutil.escape(meta_keywords, 1))
1639 user_head.append('<meta name="description" content="%s">\n' % wikiutil.escape(meta_desc, 1))
1641 # add meta statement if user has doubleclick on edit turned on or it is default
1642 if (pagename and keywords.get('allow_doubleclick', 0) and
1643 not keywords.get('print_mode', 0) and
1644 request.user.edit_on_doubleclick):
1645 if request.user.may.write(pagename): # separating this gains speed
1646 user_head.append('<meta name="edit_on_doubleclick" content="%s">\n' % (request.script_root or '/'))
1648 # search engine precautions / optimization:
1649 # if it is an action or edit/search, send query headers (noindex,nofollow):
1650 if request.query_string:
1651 user_head.append(request.cfg.html_head_queries)
1652 elif request.method == 'POST':
1653 user_head.append(request.cfg.html_head_posts)
1654 # we don't want to have BadContent stuff indexed:
1655 elif pagename in ['BadContent', 'LocalBadContent', ]:
1656 user_head.append(request.cfg.html_head_posts)
1657 # if it is a special page, index it and follow the links - we do it
1658 # for the original, English pages as well as for (the possibly
1659 # modified) frontpage:
1660 elif pagename in [page_front_page, request.cfg.page_front_page,
1661 page_title_index, 'TitleIndex',
1662 page_find_page, 'FindPage',
1663 page_site_navigation, 'SiteNavigation',
1665 user_head.append(request.cfg.html_head_index)
1666 # if it is a normal page, index it, but do not follow the links, because
1667 # there are a lot of illegal links (like actions) or duplicates:
1669 user_head.append(request.cfg.html_head_normal)
1671 if 'pi_refresh' in keywords and keywords['pi_refresh']:
1672 user_head.append('<meta http-equiv="refresh" content="%d;URL=%s">' % keywords['pi_refresh'])
1674 # output buffering increases latency but increases throughput as well
1676 # later: <html xmlns=\"http://www.w3.org/1999/xhtml\">
1678 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1689 'sitename': request.cfg.html_pagetitle or request.cfg.sitename,
1690 'print_mode': keywords.get('print_mode', False),
1691 'media': keywords.get('media', 'screen'),
1693 keywords.get('html_head', ''),
1697 output.append('<link rel="Start" href="%s">\n' % request.href(page_front_page))
1699 output.append('<link rel="Alternate" title="%s" href="%s">\n' % (
1700 _('Wiki Markup'), request.href(pagename, action='raw')))
1701 output.append('<link rel="Alternate" media="print" title="%s" href="%s">\n' % (
1702 _('Print View'), request.href(pagename, action='print')))
1704 # !!! currently disabled due to Mozilla link prefetching, see
1705 # http://www.mozilla.org/projects/netlib/Link_Prefetching_FAQ.html
1706 #~ all_pages = request.getPageList()
1709 #~ pos = all_pages.index(pagename)
1710 #~ except ValueError:
1711 #~ # this shopuld never happend in theory, but let's be sure
1714 #~ request.write('<link rel="First" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[0]))
1716 #~ request.write('<link rel="Previous" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[pos-1])))
1717 #~ if pos+1 < len(all_pages):
1718 #~ request.write('<link rel="Next" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[pos+1])))
1719 #~ request.write('<link rel="Last" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[-1])))
1721 if page_parent_page:
1722 output.append('<link rel="Up" href="%s">\n' % request.href(page_parent_page))
1724 # write buffer because we call AttachFile
1725 request.write(''.join(output))
1728 # XXX maybe this should be removed completely. moin emits all attachments as <link rel="Appendix" ...>
1729 # and it is at least questionable if this fits into the original intent of rel="Appendix".
1730 if pagename and request.user.may.read(pagename):
1731 from MoinMoin.action import AttachFile
1732 AttachFile.send_link_rel(request, pagename)
1735 '<link rel="Search" href="%s">\n' % request.href(page_find_page),
1736 '<link rel="Index" href="%s">\n' % request.href(page_title_index),
1737 '<link rel="Glossary" href="%s">\n' % request.href(page_word_index),
1738 '<link rel="Help" href="%s">\n' % request.href(page_help_formatting),
1741 output.append("</head>\n")
1742 request.write(''.join(output))
1747 if keywords.has_key('body_attr'):
1748 bodyattr.append(' ')
1749 bodyattr.append(keywords['body_attr'])
1751 # Set body to the user interface language and direction
1752 bodyattr.append(' %s' % self.ui_lang_attr())
1754 body_onload = keywords.get('body_onload', '')
1756 bodyattr.append(''' onload="%s"''' % body_onload)
1757 output.append('\n<body%s>\n' % ''.join(bodyattr))
1759 # Output -----------------------------------------------------------
1761 # If in print mode, start page div and emit the title
1762 if keywords.get('print_mode', 0):
1766 'page_name': pagename or '',
1769 request.themedict = d
1770 output.append(self.startPage())
1771 output.append(self.interwiki(d))
1772 output.append(self.title(d))
1774 # In standard mode, emit theme.header
1776 exists = pagename and page.exists(includeDeleted=True)
1777 # prepare dict for theme code:
1780 'script_name': scriptname,
1782 'logo_string': request.cfg.logo_string,
1783 'site_name': request.cfg.sitename,
1786 'pagesize': pagename and page.size() or 0,
1787 # exists checked to avoid creation of empty edit-log for non-existing pages
1788 'last_edit_info': exists and page.lastEditInfo() or '',
1789 'page_name': pagename or '',
1790 'page_find_page': page_find_page,
1791 'page_front_page': page_front_page,
1792 'home_page': home_page,
1793 'page_help_contents': page_help_contents,
1794 'page_help_formatting': page_help_formatting,
1795 'page_parent_page': page_parent_page,
1796 'page_title_index': page_title_index,
1797 'page_word_index': page_word_index,
1798 'user_name': request.user.name,
1799 'user_valid': request.user.valid,
1800 'msg': self._status,
1801 'trail': keywords.get('trail', None),
1802 # Discontinued keys, keep for a while for 3rd party theme developers
1803 'titlesearch': 'use self.searchform(d)',
1804 'textsearch': 'use self.searchform(d)',
1805 'navibar': ['use self.navibar(d)'],
1806 'available_actions': ['use self.request.availableActions(page)'],
1809 # add quoted versions of pagenames
1812 if key.startswith('page_'):
1813 if not d[key] is None:
1814 newdict['q_'+key] = wikiutil.quoteWikinameURL(d[key])
1816 newdict['q_'+key] = None
1818 request.themedict = d
1820 # now call the theming code to do the rendering
1821 if keywords.get('editor_mode', 0):
1822 output.append(self.editorheader(d))
1824 output.append(self.header(d))
1827 request.write(''.join(output))
1829 self._send_title_called = True
1831 def send_footer(self, pagename, **keywords):
1833 Output the page footer.
1835 @param pagename: WikiName of the page
1836 @keyword print_mode: true, when page is displayed in Print mode
1838 request = self.request
1839 d = request.themedict
1841 # Emit end of page in print mode, or complete footer in standard mode
1842 if keywords.get('print_mode', 0):
1843 request.write(self.pageinfo(d['page']))
1844 request.write(self.endPage())
1846 request.write(self.footer(d, **keywords))
1848 # stuff moved from request.py
1849 def send_closing_html(self):
1850 """ generate timing info html and closing html tag,
1851 everyone calling send_title must call this at the end to close
1852 the body and html tags.
1854 request = self.request
1856 # as this is the last chance to emit some html, we stop the clocks:
1857 request.clock.stop('run')
1858 request.clock.stop('total')
1861 if request.cfg.show_timings and request.action != 'print':
1862 request.write('<ul id="timings">\n')
1863 for t in request.clock.dump():
1864 request.write('<li>%s</li>\n' % t)
1865 request.write('</ul>\n')
1866 #request.write('<!-- auth_method == %s -->' % repr(request.user.auth_method))
1867 request.write('</body>\n</html>\n\n')
1869 def sidebar(self, d, **keywords):
1870 """ Display page called SideBar as an additional element on every page
1872 @param d: parameter dictionary
1874 @return: sidebar html
1877 # Check which page to display, return nothing if doesn't exist.
1878 sidebar = self.request.getPragma('sidebar', u'SideBar')
1879 page = Page(self.request, sidebar)
1880 if not page.exists():
1882 # Capture the page's generated HTML in a buffer.
1883 buffer = StringIO.StringIO()
1884 self.request.redirect(buffer)
1886 page.send_page(content_only=1, content_id="sidebar")
1888 self.request.redirect()
1889 return u'<div class="sidebar">%s</div>' % buffer.getvalue()
1892 class ThemeNotFound(Exception):
1893 """ Thrown if the supplied theme could not be found anywhere """
1895 def load_theme(request, theme_name=None):
1896 """ Load a theme for this request.
1898 @param request: moin request
1899 @param theme_name: the name of the theme
1900 @type theme_name: str
1902 @return: a theme initialized for the request
1904 if theme_name is None or theme_name == '<default>':
1905 theme_name = request.cfg.theme_default
1908 Theme = wikiutil.importPlugin(request.cfg, 'theme', theme_name, 'Theme')
1909 except wikiutil.PluginMissingError:
1910 raise ThemeNotFound(theme_name)
1912 return Theme(request)
1914 def load_theme_fallback(request, theme_name=None):
1915 """ Try loading a theme, falling back to defaults on error.
1917 @param request: moin request
1918 @param theme_name: the name of the theme
1919 @type theme_name: str
1921 @return: A statuscode for how successful the loading was
1922 0 - theme was loaded
1923 1 - fallback to default theme
1924 2 - serious fallback to builtin theme
1928 request.theme = load_theme(request, theme_name)
1929 except ThemeNotFound:
1932 request.theme = load_theme(request, request.cfg.theme_default)
1933 except ThemeNotFound:
1935 from MoinMoin.theme.modern import Theme
1936 request.theme = Theme(request)