1 # -*- coding: iso-8859-1 -*-
3 MoinMoin - Theme Package
5 @copyright: 2003-2008 MoinMoin:ThomasWaldmann
6 @license: GNU GPL, see COPYING for details.
9 from MoinMoin import i18n, wikiutil, config, version, caching
10 from MoinMoin.Page import Page
11 from MoinMoin.util import pysupport
13 modules = pysupport.getPackageModules(__file__)
15 # Check whether we can emit a RSS feed.
16 # RSS is broken on plain Python 2.3.x/2.4.x, and works only when installing PyXML.
17 # News: A user reported that the RSS is valid when using Python 2.5.1 on Windows.
19 rss_supported = sys.version_info[:3] >= (2, 5, 1) or '_xmlplus' in xml.__file__
23 """ Base class for themes
25 This class supply all the standard template that sub classes can
26 use without rewriting the same code. If you want to change certain
27 elements, override them.
32 # fake _ function to get gettext recognize those texts:
35 # TODO: remove icons that are not used any more.
37 # key alt icon filename w h
38 # ------------------------------------------------------------------
40 'help': ("%(page_help_contents)s", "moin-help.png", 12, 11),
41 'find': ("%(page_find_page)s", "moin-search.png", 12, 12),
42 'diff': (_("Diffs"), "moin-diff.png", 15, 11),
43 'info': (_("Info"), "moin-info.png", 12, 11),
44 'edit': (_("Edit"), "moin-edit.png", 12, 12),
45 'unsubscribe': (_("Unsubscribe"), "moin-unsubscribe.png", 14, 10),
46 'subscribe': (_("Subscribe"), "moin-subscribe.png", 14, 10),
47 'raw': (_("Raw"), "moin-raw.png", 12, 13),
48 'xml': (_("XML"), "moin-xml.png", 20, 13),
49 'print': (_("Print"), "moin-print.png", 16, 14),
50 'view': (_("View"), "moin-show.png", 12, 13),
51 'home': (_("Home"), "moin-home.png", 13, 12),
52 'up': (_("Up"), "moin-parent.png", 15, 13),
54 'attach': ("%(attach_count)s", "moin-attach.png", 7, 15),
55 'attachimg': ("", "attach.png", 32, 32),
57 'rss': (_("[RSS]"), "moin-rss.png", 24, 24),
58 'deleted': (_("[DELETED]"), "moin-deleted.png", 60, 12),
59 'updated': (_("[UPDATED]"), "moin-updated.png", 60, 12),
60 'renamed': (_("[RENAMED]"), "moin-renamed.png", 60, 12),
61 'conflict': (_("[CONFLICT]"), "moin-conflict.png", 60, 12),
62 'new': (_("[NEW]"), "moin-new.png", 31, 12),
63 'diffrc': (_("[DIFF]"), "moin-diff.png", 15, 11),
65 'bottom': (_("[BOTTOM]"), "moin-bottom.png", 14, 10),
66 'top': (_("[TOP]"), "moin-top.png", 14, 10),
67 'www': ("[WWW]", "moin-www.png", 11, 11),
68 'mailto': ("[MAILTO]", "moin-email.png", 14, 10),
69 'news': ("[NEWS]", "moin-news.png", 10, 11),
70 'telnet': ("[TELNET]", "moin-telnet.png", 10, 11),
71 'ftp': ("[FTP]", "moin-ftp.png", 11, 11),
72 'file': ("[FILE]", "moin-ftp.png", 11, 11),
74 'searchbutton': ("[?]", "moin-search.png", 12, 12),
75 'interwiki': ("[%(wikitag)s]", "moin-inter.png", 16, 16),
77 # smileys (this is CONTENT, but good looking smileys depend on looking
78 # adapted to the theme background color and theme style in general)
79 #vvv == vvv this must be the same for GUI editor converter
80 'X-(': ("X-(", 'angry.png', 15, 15),
81 ':D': (":D", 'biggrin.png', 15, 15),
82 '<:(': ("<:(", 'frown.png', 15, 15),
83 ':o': (":o", 'redface.png', 15, 15),
84 ':(': (":(", 'sad.png', 15, 15),
85 ':)': (":)", 'smile.png', 15, 15),
86 'B)': ("B)", 'smile2.png', 15, 15),
87 ':))': (":))", 'smile3.png', 15, 15),
88 ';)': (";)", 'smile4.png', 15, 15),
89 '/!\\': ("/!\\", 'alert.png', 15, 15),
90 '<!>': ("<!>", 'attention.png', 15, 15),
91 '(!)': ("(!)", 'idea.png', 15, 15),
93 # copied 2001-11-16 from http://pikie.darktech.org/cgi/pikie.py?EmotIcon
94 ':-?': (":-?", 'tongue.png', 15, 15),
95 ':\\': (":\\", 'ohwell.png', 15, 15),
96 '>:>': (">:>", 'devil.png', 15, 15),
97 '|)': ("|)", 'tired.png', 15, 15),
99 # some folks use noses in their emoticons
100 ':-(': (":-(", 'sad.png', 15, 15),
101 ':-)': (":-)", 'smile.png', 15, 15),
102 'B-)': ("B-)", 'smile2.png', 15, 15),
103 ':-))': (":-))", 'smile3.png', 15, 15),
104 ';-)': (";-)", 'smile4.png', 15, 15),
105 '|-)': ("|-)", 'tired.png', 15, 15),
108 '(./)': ("(./)", 'checkmark.png', 20, 15),
109 '{OK}': ("{OK}", 'thumbs-up.png', 14, 12),
110 '{X}': ("{X}", 'icon-error.png', 16, 16),
111 '{i}': ("{i}", 'icon-info.png', 16, 16),
112 '{1}': ("{1}", 'prio1.png', 15, 13),
113 '{2}': ("{2}", 'prio2.png', 15, 13),
114 '{3}': ("{3}", 'prio3.png', 15, 13),
116 # version 1.3.4 (stars)
118 '{*}': ("{*}", 'star_on.png', 15, 15),
119 '{o}': ("{o}", 'star_off.png', 15, 15),
123 # Style sheets - usually there is no need to override this in sub
124 # classes. Simply supply the css files in the css directory.
126 # Standard set of style sheets
130 ('screen', 'screen'),
132 ('projection', 'projection'),
136 stylesheets_print = (
142 # Used in slide show mode
143 stylesheets_projection = (
146 ('all', 'projection'),
149 stylesheetsCharset = 'utf-8'
151 def __init__(self, request):
153 Initialize the theme object.
155 @param request: the request object
157 self.request = request
158 self.cfg = request.cfg
159 self._cache = {} # Used to cache elements that may be used several times
161 self._send_title_called = False
163 def img_url(self, img):
164 """ Generate an image href
166 @param img: the image filename
168 @return: the image href
170 return "%s/%s/img/%s" % (self.cfg.url_prefix_static, self.name, img)
172 def emit_custom_html(self, html):
174 generate custom HTML code in `html`
176 @param html: a string or a callable object, in which case
177 it is called and its return value is used
179 @return: string with html
183 html = html(self.request)
187 """ Assemble logo with link to front page
189 The logo contain an image and or text or any html markup the
190 admin inserted in the config file. Everything it enclosed inside
191 a div with id="logo".
197 if self.cfg.logo_string:
198 page = wikiutil.getFrontPage(self.request)
199 logo = page.link_to_raw(self.request, self.cfg.logo_string)
200 html = u'''<div id="logo">%s</div>''' % logo
203 def interwiki(self, d):
204 """ Assemble the interwiki name display, linking to page_front_page
206 @param d: parameter dictionary
208 @return: interwiki html
210 if self.request.cfg.show_interwiki:
211 page = wikiutil.getFrontPage(self.request)
212 text = self.request.cfg.interwikiname or 'Self'
213 link = page.link_to(self.request, text=text, rel='nofollow')
214 html = u'<div id="interwiki"><span>%s</span></div>' % link
220 """ Assemble the title (now using breadcrumbs)
222 @param d: parameter dictionary
226 _ = self.request.getText
228 if d['title_text'] == d['page'].split_title(): # just showing a page, no action
230 segments = d['page_name'].split('/') # was: title_text
231 for s in segments[:-1]:
233 content.append("<li>%s</li>" % Page(self.request, curpage).link_to(self.request, s))
235 link_text = segments[-1]
236 link_title = _('Click to do a full-text search for this title')
238 'action': 'fullsearch',
239 'value': 'linkto:"%s"' % d['page_name'],
242 # we dont use d['title_link'] any more, but make it ourselves:
243 link = d['page'].link_to(self.request, link_text, querystr=link_query, title=link_title, css_class='backlink', rel='nofollow')
244 content.append(('<li>%s</li>') % link)
246 content.append('<li>%s</li>' % wikiutil.escape(d['title_text']))
249 <ul id="pagelocation">
252 ''' % "".join(content)
255 def username(self, d):
256 """ Assemble the username / userprefs link
258 @param d: parameter dictionary
260 @return: username html
262 request = self.request
266 # Add username/homepage link for registered users. We don't care
267 # if it exists, the user can create it.
268 if request.user.valid and request.user.name:
269 interwiki = wikiutil.getInterwikiHomePage(request)
270 name = request.user.name
271 aliasname = request.user.aliasname
274 title = "%s @ %s" % (aliasname, interwiki[0])
275 # link to (interwiki) user homepage
276 homelink = (request.formatter.interwikilink(1, title=title, id="userhome", generated=True, *interwiki) +
277 request.formatter.text(name) +
278 request.formatter.interwikilink(0, title=title, id="userhome", *interwiki))
279 userlinks.append(homelink)
280 # link to userprefs action
281 if 'userprefs' not in self.request.cfg.actions_excluded:
282 userlinks.append(d['page'].link_to(request, text=_('Settings'),
283 querystr={'action': 'userprefs'}, id='userprefs', rel='nofollow'))
285 if request.user.valid:
286 if request.user.auth_method in request.cfg.auth_can_logout:
287 userlinks.append(d['page'].link_to(request, text=_('Logout'),
288 querystr={'action': 'logout', 'logout': 'logout'}, id='logout', rel='nofollow'))
290 query = {'action': 'login'}
291 # special direct-login link if the auth methods want no input
292 if request.cfg.auth_login_inputs == ['special_no_input']:
294 if request.cfg.auth_have_login:
295 userlinks.append(d['page'].link_to(request, text=_("Login"),
296 querystr=query, id='login', rel='nofollow'))
298 userlinks = [u'<li>%s</li>' % link for link in userlinks]
299 html = u'<ul id="username">%s</ul>' % ''.join(userlinks)
302 def splitNavilink(self, text, localize=1):
303 """ Split navibar links into pagename, link to page
305 Admin or user might want to use shorter navibar items by using
306 the [[page|title]] or [[url|title]] syntax. In this case, we don't
307 use localization, and the links goes to page or to the url, not
308 the localized version of page.
313 * wiki:WikiName:PageName
315 * all targets as seen above with title: [[target|title]]
317 @param text: the text used in config or user preferences
319 @return: pagename or url, link to page or url
321 request = self.request
322 fmt = request.formatter
325 # Handle [[pagename|title]] or [[url|title]] formats
326 if text.startswith('[[') and text.endswith(']]'):
329 pagename, title = text.split('|', 1)
330 pagename = pagename.strip()
331 title = title.strip()
333 except (ValueError, TypeError):
334 # Just use the text as is.
335 pagename = text.strip()
339 if wikiutil.is_URL(pagename):
342 link = fmt.url(1, pagename) + fmt.text(title) + fmt.url(0)
343 return pagename, link
345 # remove wiki: url prefix
346 if pagename.startswith("wiki:"):
347 pagename = pagename[5:]
349 # try handling interwiki links
351 interwiki, page = wikiutil.split_interwiki(pagename)
352 thiswiki = request.cfg.interwikiname
353 if interwiki == thiswiki or interwiki == 'Self':
358 link = fmt.interwikilink(True, interwiki, page) + fmt.text(title) + fmt.interwikilink(False, interwiki, page)
359 return pagename, link
363 # Handle regular pagename like "FrontPage"
364 pagename = request.normalizePagename(pagename)
366 # Use localized pages for the current user
368 page = wikiutil.getLocalizedPage(request, pagename)
370 page = Page(request, pagename)
372 pagename = page.page_name # can be different, due to i18n
375 title = page.split_title()
376 title = self.shortenPagename(title)
378 link = page.link_to(request, title)
380 return pagename, link
382 def shortenPagename(self, name):
383 """ Shorten page names
385 Shorten very long page names that tend to break the user
386 interface. The short name is usually fine, unless really stupid
387 long names are used (WYGIWYD).
389 If you don't like to do this in your theme, or want to use
390 different algorithm, override this method.
392 @param name: page name, unicode
394 @return: shortened version.
396 maxLength = self.maxPagenameLength()
397 # First use only the sub page name, that might be enough
398 if len(name) > maxLength:
399 name = name.split('/')[-1]
400 # If it's not enough, replace the middle with '...'
401 if len(name) > maxLength:
402 half, left = divmod(maxLength - 3, 2)
403 name = u'%s...%s' % (name[:half + left], name[-half:])
406 def maxPagenameLength(self):
407 """ Return maximum length for shortened page names """
410 def navibar(self, d):
411 """ Assemble the navibar
413 @param d: parameter dictionary
415 @return: navibar html
417 request = self.request
418 found = {} # pages we found. prevent duplicates
419 items = [] # navibar items
420 item = u'<li class="%s">%s</li>'
421 current = d['page_name']
423 # Process config navi_bar
424 if request.cfg.navi_bar:
425 for text in request.cfg.navi_bar:
426 pagename, link = self.splitNavilink(text)
427 if pagename == current:
428 cls = 'wikilink current'
431 items.append(item % (cls, link))
434 # Add user links to wiki links, eliminating duplicates.
435 userlinks = request.user.getQuickLinks()
436 for text in userlinks:
437 # Split text without localization, user knows what he wants
438 pagename, link = self.splitNavilink(text, localize=0)
439 if not pagename in found:
440 if pagename == current:
441 cls = 'userlink current'
444 items.append(item % (cls, link))
447 # Add current page at end of local pages
448 if not current in found:
449 title = d['page'].split_title()
450 title = self.shortenPagename(title)
451 link = d['page'].link_to(request, title)
453 items.append(item % (cls, link))
456 for sistername, sisterurl in request.cfg.sistersites:
457 if sistername == request.cfg.interwikiname: # it is THIS wiki
458 cls = 'sisterwiki current'
459 items.append(item % (cls, sistername))
461 # TODO optimize performance
462 cache = caching.CacheEntry(request, 'sisters', sistername, 'farm', use_pickle=True)
464 data = cache.content()
465 sisterpages = data['sisterpages']
466 if current in sisterpages:
468 url = sisterpages[current]
469 link = request.formatter.url(1, url) + \
470 request.formatter.text(sistername) +\
471 request.formatter.url(0)
472 items.append(item % (cls, link))
475 items = u''.join(items)
483 def get_icon(self, icon):
484 """ Return icon data from self.icons
486 If called from <<Icon(file)>> we have a filename, not a
487 key. Using filenames is deprecated, but for now, we simulate old
490 @param icon: icon name or file name (string)
492 @return: alt (unicode), href (string), width, height (int)
494 if icon in self.icons:
495 alt, icon, w, h = self.icons[icon]
497 # Create filenames to icon data mapping on first call, then
498 # cache in class for next calls.
499 if not getattr(self.__class__, 'iconsByFile', None):
501 for data in self.icons.values():
503 self.__class__.iconsByFile = d
505 # Try to get icon data by file name
506 if icon in self.iconsByFile:
507 alt, icon, w, h = self.iconsByFile[icon]
509 alt, icon, w, h = '', icon, '', ''
511 return alt, self.img_url(icon), w, h
513 def make_icon(self, icon, vars=None, **kw):
515 This is the central routine for making <img> tags for icons!
516 All icons stuff except the top left logo and search field icons are
519 @param icon: icon id (dict key)
522 @return: icon html (img tag)
526 alt, img, w, h = self.get_icon(icon)
528 alt = vars['icon-alt-text'] # if it is possible we take the alt-text from 'page_icons_table'
529 except KeyError, err:
531 alt = alt % vars # if not we just leave the alt-text from 'icons'
532 except KeyError, err:
533 alt = 'KeyError: %s' % str(err)
534 alt = self.request.getText(alt)
535 tag = self.request.formatter.image(src=img, alt=alt, width=w, height=h, **kw)
538 def make_iconlink(self, which, d):
540 Make a link with an icon
542 @param which: icon id (dictionary key)
543 @param d: parameter dictionary
545 @return: html link tag
548 pagekey, querystr, title, icon = self.cfg.page_icons_table[which]
549 qs.update(querystr) # do not modify the querystr dict in the cfg!
550 d['icon-alt-text'] = d['title'] = title % d
551 d['i18ntitle'] = self.request.getText(d['title'])
552 img_src = self.make_icon(icon, d)
554 if rev and which in ['raw', 'print', ]:
556 attrs = {'rel': 'nofollow', 'title': d['i18ntitle'], }
558 if isinstance(page, unicode):
559 # e.g. d['page_parent_page'] is just the unicode pagename
560 # while d['page'] will give a page object
561 page = Page(self.request, page)
562 return page.link_to_raw(self.request, text=img_src, querystr=qs, **attrs)
565 """ Assemble the msg display
567 Display a message with a widget or simple strings with a clear message link.
569 @param d: parameter dictionary
571 @return: msg display html
573 _ = self.request.getText
577 close = d['page'].link_to(self.request, text=_('Clear message'), css_class="clear-link")
578 for msg, msg_class in msgs:
580 result += u'<p>%s</p>' % msg.render()
582 except AttributeError:
583 if msg and msg_class:
584 result += u'<p><div class="%s">%s</div></p>' % (msg_class, msg)
586 result += u'<p>%s</p>\n' % msg
588 html = result + close
589 return u'<div id="message">\n%s\n</div>\n' % html
593 return u'<div id="message">\n%s\n</div>\n' % html
596 """ Assemble page trail
598 @param d: parameter dictionary
602 request = self.request
605 if not user.valid or user.show_page_trail:
606 trail = user.getTrail()
609 for pagename in trail:
611 interwiki, page = wikiutil.split_interwiki(pagename)
612 if interwiki != request.cfg.interwikiname and interwiki != 'Self':
613 link = (self.request.formatter.interwikilink(True, interwiki, page) +
614 self.shortenPagename(page) +
615 self.request.formatter.interwikilink(False, interwiki, page))
616 items.append('<li>%s</li>' % link)
623 page = Page(request, pagename)
624 title = page.split_title()
625 title = self.shortenPagename(title)
626 link = page.link_to(request, title)
627 items.append('<li>%s</li>' % link)
631 </ul>''' % ''.join(items)
634 def html_stylesheets(self, d):
635 """ Assemble html head stylesheet links
637 @param d: parameter dictionary
639 @return: stylesheets links
641 link = '<link rel="stylesheet" type="text/css" charset="%s" media="%s" href="%s">'
644 if d.get('print_mode'):
645 media = d.get('media', 'print')
646 stylesheets = getattr(self, 'stylesheets_' + media)
648 stylesheets = self.stylesheets
649 usercss = self.request.user.valid and self.request.user.css_url
651 # Create stylesheets links
653 prefix = self.cfg.url_prefix_static
654 csshref = '%s/%s/css' % (prefix, self.name)
655 for media, basename in stylesheets:
656 href = '%s/%s.css' % (csshref, basename)
657 html.append(link % (self.stylesheetsCharset, media, href))
659 # Don't add user css url if it matches one of ours
660 if usercss and usercss == href:
663 # admin configurable additional css (farm or wiki level)
664 for media, csshref in self.request.cfg.stylesheets:
665 html.append(link % (self.stylesheetsCharset, media, csshref))
667 csshref = '%s/%s/css/msie.css' % (prefix, self.name)
669 <!-- css only for MSIE browsers -->
673 """ % link % (self.stylesheetsCharset, 'all', csshref))
675 # Add user css url (assuming that user css uses same charset)
676 if usercss and usercss.lower() != "none":
677 html.append(link % (self.stylesheetsCharset, 'all', usercss))
679 return '\n'.join(html)
681 def shouldShowPageinfo(self, page):
682 """ Should we show page info?
684 Should be implemented by actions. For now, we check here by action
687 @param page: current page
689 @return: true if should show page info
691 if page.exists() and self.request.user.may.read(page.page_name):
692 # These actions show the page content.
693 # TODO: on new action, page info will not show.
694 # A better solution will be if the action itself answer the question: showPageInfo().
695 contentActions = [u'', u'show', u'refresh', u'preview', u'diff',
696 u'subscribe', u'RenamePage', u'CopyPage', u'DeletePage',
697 u'SpellCheck', u'print']
698 return self.request.action in contentActions
701 def pageinfo(self, page):
702 """ Return html fragment with page meta data
704 Since page information uses translated text, it uses the ui
705 language and direction. It looks strange sometimes, but
706 translated text using page direction looks worse.
708 @param page: current page
710 @return: page last edit information
712 _ = self.request.getText
714 if self.shouldShowPageinfo(page):
715 info = page.lastEditInfo()
718 info = _("last edited %(time)s by %(editor)s") % info
720 info = _("last modified %(time)s") % info
721 pagename = page.page_name
722 if self.request.cfg.show_interwiki:
723 pagename = "%s: %s" % (self.request.cfg.interwikiname, pagename)
724 info = "%s (%s)" % (wikiutil.escape(pagename), info)
725 html = '<p id="pageinfo" class="info"%(lang)s>%(info)s</p>\n' % {
726 'lang': self.ui_lang_attr(),
731 def searchform(self, d):
733 assemble HTML code for the search forms
735 @param d: parameter dictionary
737 @return: search form html
739 _ = self.request.getText
740 form = self.request.form
742 'search_label': _('Search:'),
743 'search_value': wikiutil.escape(form.get('value', [''])[0], 1),
744 'search_full_label': _('Text'),
745 'search_title_label': _('Titles'),
746 'baseurl': self.request.getScriptname(),
747 'pagename_quoted': wikiutil.quoteWikinameURL(d['page'].page_name),
752 <form id="searchform" method="get" action="%(baseurl)s/%(pagename_quoted)s">
754 <input type="hidden" name="action" value="fullsearch">
755 <input type="hidden" name="context" value="180">
756 <label for="searchinput">%(search_label)s</label>
757 <input id="searchinput" type="text" name="value" value="%(search_value)s" size="20"
758 onfocus="searchFocus(this)" onblur="searchBlur(this)"
759 onkeyup="searchChange(this)" onchange="searchChange(this)" alt="Search">
760 <input id="titlesearch" name="titlesearch" type="submit"
761 value="%(search_title_label)s" alt="Search Titles">
762 <input id="fullsearch" name="fullsearch" type="submit"
763 value="%(search_full_label)s" alt="Search Full Text">
766 <script type="text/javascript">
767 <!--// Initialize search form
768 var f = document.getElementById('searchform');
769 f.getElementsByTagName('label')[0].style.display = 'none';
770 var e = document.getElementById('searchinput');
778 def showversion(self, d, **keywords):
780 assemble HTML code for copyright and version display
782 @param d: parameter dictionary
784 @return: copyright and version display html
787 if self.cfg.show_version and not keywords.get('print_mode', 0):
788 html = (u'<div id="version">MoinMoin Release %s [Revision %s], '
789 'Copyright by Juergen Hermann et al.</div>') % (version.release, version.revision, )
792 def headscript(self, d):
793 """ Return html head script with common functions
795 @param d: parameter dictionary
797 @return: script for html head
799 # Don't add script for print view
800 if self.request.action == 'print':
803 _ = self.request.getText
805 <script type="text/javascript">
807 var search_hint = "%(search_hint)s";
811 'search_hint': _('Search Wiki'),
815 def shouldUseRSS(self, page):
816 """ Return True if RSS feature is available and we are on the
817 RecentChanges page, or False.
819 Currently rss is broken on plain Python, and works only when
820 installing PyXML. Return true if PyXML is installed.
822 if not rss_supported:
824 return page.page_name == u'RecentChanges' or \
825 page.page_name == self.request.getText(u'RecentChanges')
827 def rsshref(self, page):
828 """ Create rss href, used for rss button and head link
833 request = self.request
834 url = page.url(request, querystr={
835 'action': 'rss_rc', 'ddiffs': '1', 'unique': '1', }, escape=0)
838 def rsslink(self, d):
839 """ Create rss link in head, used by FireFox
841 RSS link for FireFox. This shows an rss link in the bottom of
842 the page and let you subscribe to the wiki rss feed.
849 if self.shouldUseRSS(page):
850 link = (u'<link rel="alternate" title="%s Recent Changes" '
851 u'href="%s" type="application/rss+xml">') % (
852 wikiutil.escape(self.cfg.sitename, True),
853 wikiutil.escape(self.rsshref(page), True) )
856 def html_head(self, d):
857 """ Assemble html head
859 @param d: parameter dictionary
864 u'<title>%(title)s - %(sitename)s</title>' % {
865 'title': wikiutil.escape(d['title']),
866 'sitename': wikiutil.escape(d['sitename']),
868 self.externalScript('common'),
869 self.headscript(d), # Should move to separate .js file
870 self.guiEditorScript(d),
871 self.html_stylesheets(d),
873 self.universal_edit_button(d),
875 return '\n'.join(html)
877 def externalScript(self, name):
878 """ Format external script html """
879 src = '%s/common/js/%s.js' % (self.request.cfg.url_prefix_static, name)
880 return '<script type="text/javascript" src="%s"></script>' % src
882 def universal_edit_button(self, d, **keywords):
883 """ Generate HTML for an edit link in the header."""
885 if 'edit' in self.request.cfg.actions_excluded:
887 if not (page.isWritable() and
888 self.request.user.may.write(page.page_name)):
890 _ = self.request.getText
891 querystr = {'action': 'edit'}
893 url = page.url(self.request, querystr=querystr, escape=0)
894 return (u'<link rel="alternate" type="application/wiki" '
895 u'title="%s" href="%s" />' % (text, url))
897 def credits(self, d, **keywords):
898 """ Create credits html from credits list """
899 if isinstance(self.cfg.page_credits, (list, tuple)):
900 items = ['<li>%s</li>' % i for i in self.cfg.page_credits]
901 html = '<ul id="credits">\n%s\n</ul>\n' % ''.join(items)
903 # Old config using string, output as is
904 html = self.cfg.page_credits
907 def actionsMenu(self, page):
908 """ Create actions menu list and items data dict
910 The menu will contain the same items always, but items that are
911 not available will be disabled (some broken browsers will let
912 you select disabled options though).
914 The menu should give best user experience for javascript
915 enabled browsers, and acceptable behavior for those who prefer
916 not to use Javascript.
918 TODO: Move actionsMenuInit() into body onload - requires that the theme will render body,
919 it is currently done in wikiutil/page.
921 @param page: current page, Page object
923 @return: actions menu html fragment
925 request = self.request
954 '__title__': _("More Actions:"),
955 # Translation may need longer or shorter separator
956 '__separator__': _('------------------------'),
957 'raw': _('Raw Text'),
958 'print': _('Print View'),
959 'refresh': _('Delete Cache'),
960 'SpellCheck': _('Check Spelling'), # rename action!
961 'RenamePage': _('Rename Page'),
962 'CopyPage': _('Copy Page'),
963 'DeletePage': _('Delete Page'),
964 'LikePages': _('Like Pages'),
965 'LocalSiteMap': _('Local Site Map'),
966 'MyPages': _('My Pages'),
967 'SubscribeUser': _('Subscribe User'),
968 'Despam': _('Remove Spam'),
969 'revert': _('Revert to this revision'),
970 'PackagePages': _('Package Pages'),
971 'RenderAsDocbook': _('Render as Docbook'),
972 'SyncPages': _('Sync Pages'),
976 option = '<option value="%(action)s"%(disabled)s>%(title)s</option>'
977 # class="disabled" is a workaround for browsers that ignore
978 # "disabled", e.g IE, Safari
979 # for XHTML: data['disabled'] = ' disabled="disabled"'
980 disabled = ' disabled class="disabled"'
982 # Format standard actions
983 available = request.getAvailableActions(page)
985 data = {'action': action, 'disabled': '', 'title': titles[action]}
986 # removes excluded actions from the more actions menu
987 if action in request.cfg.actions_excluded:
990 # Enable delete cache only if page can use caching
991 if action == 'refresh':
992 if not page.canUseCache():
993 data['action'] = 'show'
994 data['disabled'] = disabled
996 # revert action enabled only if user can revert
997 if action == 'revert' and not request.user.may.revert(page.page_name):
998 data['action'] = 'show'
999 data['disabled'] = disabled
1001 # SubscribeUser action enabled only if user has admin rights
1002 if action == 'SubscribeUser' and not request.user.may.admin(page.page_name):
1003 data['action'] = 'show'
1004 data['disabled'] = disabled
1006 # PackagePages action only if user has write rights
1007 if action == 'PackagePages' and not request.user.may.write(page.page_name):
1008 data['action'] = 'show'
1009 data['disabled'] = disabled
1011 # Despam action enabled only for superusers
1012 if action == 'Despam' and not request.user.isSuperUser():
1013 data['action'] = 'show'
1014 data['disabled'] = disabled
1016 # Special menu items. Without javascript, executing will
1017 # just return to the page.
1018 if action.startswith('__'):
1019 data['action'] = 'show'
1021 # Actions which are not available for this wiki, user or page
1022 if (action == '__separator__' or
1023 (action[0].isupper() and not action in available)):
1024 data['disabled'] = disabled
1026 options.append(option % data)
1028 # Add custom actions not in the standard menu, except for
1029 # some actions like AttachFile (we have them on top level)
1030 more = [item for item in available if not item in titles and not item in ('AttachFile', )]
1034 separator = option % {'action': 'show', 'disabled': disabled,
1035 'title': titles['__separator__']}
1036 options.append(separator)
1037 # Add more actions (all enabled)
1039 data = {'action': action, 'disabled': ''}
1040 # Always add spaces: AttachFile -> Attach File
1041 # XXX do not create page just for using split_title -
1042 # creating pages for non-existent does 2 storage lookups
1043 #title = Page(request, action).split_title(force=1)
1045 # Use translated version if available
1046 data['title'] = _(title)
1047 options.append(option % data)
1050 'label': titles['__title__'],
1051 'options': '\n'.join(options),
1052 'rev_field': rev and '<input type="hidden" name="rev" value="%d">' % rev or '',
1053 'do_button': _("Do"),
1054 'baseurl': self.request.getScriptname(),
1055 'pagename_quoted': wikiutil.quoteWikinameURL(page.page_name),
1058 <form class="actionsmenu" method="GET" action="%(baseurl)s/%(pagename_quoted)s">
1060 <label>%(label)s</label>
1061 <select name="action"
1062 onchange="if ((this.selectedIndex != 0) &&
1063 (this.options[this.selectedIndex].disabled == false)) {
1066 this.selectedIndex = 0;">
1069 <input type="submit" value="%(do_button)s">
1072 <script type="text/javascript">
1074 actionsMenuInit('%(label)s');
1082 def editbar(self, d):
1083 """ Assemble the page edit bar.
1085 Create html on first call, then return cached html.
1087 @param d: parameter dictionary
1089 @return: iconbar html
1092 if not self.shouldShowEditbar(page):
1095 html = self._cache.get('editbar')
1097 # Remove empty items and format as list
1098 items = ''.join(['<li>%s</li>' % item
1099 for item in self.editbarItems(page) if item])
1100 html = u'<ul class="editbar">%s</ul>\n' % items
1101 self._cache['editbar'] = html
1105 def shouldShowEditbar(self, page):
1106 """ Should we show the editbar?
1108 Actions should implement this, because only the action knows if
1109 the edit bar makes sense. Until it goes into actions, we do the
1112 @param page: current page
1114 @return: true if editbar should show
1116 # Show editbar only for existing pages, including deleted pages,
1117 # that the user may read. If you may not read, you can't edit,
1118 # so you don't need editbar.
1119 if (page.exists(includeDeleted=1) and
1120 self.request.user.may.read(page.page_name)):
1121 form = self.request.form
1122 action = self.request.action
1123 # Do not show editbar on edit but on save/cancel
1124 return not (action == 'edit' and
1125 not form.has_key('button_save') and
1126 not form.has_key('button_cancel'))
1129 def editbarItems(self, page):
1130 """ Return list of items to show on the editbar
1132 This is separate method to make it easy to customize the
1133 edtibar in sub classes.
1135 _ = self.request.getText
1136 editbar_actions = []
1137 for editbar_item in self.request.cfg.edit_bar:
1138 if editbar_item == 'Discussion':
1139 if not self.request.cfg.supplementation_page and self.request.getPragma('supplementation-page', 1) in ('on', '1'):
1140 editbar_actions.append(self.supplementation_page_nameLink(page))
1141 elif self.request.cfg.supplementation_page and not self.request.getPragma('supplementation-page', 1) in ('off', '0'):
1142 editbar_actions.append(self.supplementation_page_nameLink(page))
1143 elif editbar_item == 'Comments':
1144 # we just use <a> to get same style as other links, but we add some dummy
1145 # link target to get correct mouseover pointer appearance. return false
1146 # keeps the browser away from jumping to the link target::
1147 editbar_actions.append('<a href="#" class="toggleCommentsButton" style="display:none;" onClick="toggleComments();return false;">%s</a>' % _('Comments'))
1148 elif editbar_item == 'Edit':
1149 editbar_actions.append(self.editorLink(page))
1150 elif editbar_item == 'Info':
1151 editbar_actions.append(self.infoLink(page))
1152 elif editbar_item == 'Subscribe':
1153 editbar_actions.append(self.subscribeLink(page))
1154 elif editbar_item == 'Quicklink':
1155 editbar_actions.append(self.quicklinkLink(page))
1156 elif editbar_item == 'Attachments':
1157 editbar_actions.append(self.attachmentsLink(page))
1158 elif editbar_item == 'ActionsMenu':
1159 editbar_actions.append(self.actionsMenu(page))
1160 return editbar_actions
1162 def supplementation_page_nameLink(self, page):
1163 """Return a link to the discussion page
1165 If the discussion page doesn't exist and the user
1166 has no right to create it, show a disabled link.
1168 _ = self.request.getText
1169 suppl_name = self.request.cfg.supplementation_page_name
1170 suppl_name_full = "%s/%s" % (page.page_name, suppl_name)
1172 test = Page(self.request, suppl_name_full)
1173 if not test.exists() and not self.request.user.may.write(suppl_name_full):
1174 return ('<span class="disabled">%s</span>' % _(suppl_name))
1176 return page.link_to(self.request, text=_(suppl_name),
1177 querystr={'action': 'supplementation'}, css_class='nbsupplementation', rel='nofollow')
1179 def guiworks(self, page):
1180 """ Return whether the gui editor / converter can work for that page.
1182 The GUI editor currently only works for wiki format.
1183 For simplicity, we also tell it does not work if the admin forces the text editor.
1185 is_wiki = page.pi['format'] == 'wiki'
1186 gui_disallowed = self.cfg.editor_force and self.cfg.editor_default == 'text'
1187 return is_wiki and not gui_disallowed
1190 def editorLink(self, page):
1191 """ Return a link to the editor
1193 If the user can't edit, return a disabled edit link.
1195 If the user want to show both editors, it will display "Edit
1196 (Text)", otherwise as "Edit".
1198 if 'edit' in self.request.cfg.actions_excluded:
1201 if not (page.isWritable() and
1202 self.request.user.may.write(page.page_name)):
1203 return self.disabledEdit()
1205 _ = self.request.getText
1206 querystr = {'action': 'edit'}
1208 guiworks = self.guiworks(page)
1209 if self.showBothEditLinks() and guiworks:
1210 text = _('Edit (Text)')
1211 querystr['editor'] = 'text'
1212 attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
1216 # 'textonly' will be upgraded dynamically to 'guipossible' by JS
1217 querystr['editor'] = 'textonly'
1218 attrs = {'name': 'editlink', 'rel': 'nofollow', }
1220 querystr['editor'] = 'text'
1221 attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
1223 return page.link_to(self.request, text=text, querystr=querystr, **attrs)
1225 def showBothEditLinks(self):
1226 """ Return True if both edit links should be displayed """
1227 editor = self.request.user.editor_ui
1228 if editor == '<default>':
1229 editor = self.request.cfg.editor_ui
1230 return editor == 'freechoice'
1232 def guiEditorScript(self, d):
1233 """ Return a script that set the gui editor link variables
1235 The link will be created only when javascript is enabled and
1236 the browser is compatible with the editor.
1239 if not (page.isWritable() and
1240 self.request.user.may.write(page.page_name) and
1241 self.showBothEditLinks() and
1242 self.guiworks(page)):
1245 _ = self.request.getText
1247 <script type="text/javascript">
1248 <!-- // GUI edit link and i18n
1249 var gui_editor_link_href = "%(url)s";
1250 var gui_editor_link_text = "%(text)s";
1253 """ % {'url': page.url(self.request, querystr={'action': 'edit', 'editor': 'gui', }),
1254 'text': _('Edit (GUI)'),
1257 def disabledEdit(self):
1258 """ Return a disabled edit link """
1259 _ = self.request.getText
1260 return ('<span class="disabled">%s</span>'
1261 % _('Immutable Page'))
1263 def infoLink(self, page):
1264 """ Return link to page information """
1265 if 'info' in self.request.cfg.actions_excluded:
1268 _ = self.request.getText
1269 return page.link_to(self.request,
1271 querystr={'action': 'info'}, css_class='nbinfo', rel='nofollow')
1273 def subscribeLink(self, page):
1274 """ Return subscribe/unsubscribe link to valid users
1277 @return: subscribe or unsubscribe link
1279 if not ((self.cfg.mail_enabled or self.cfg.jabber_enabled) and self.request.user.valid):
1282 _ = self.request.getText
1283 if self.request.user.isSubscribedTo([page.page_name]):
1284 action, text = 'unsubscribe', _("Unsubscribe")
1286 action, text = 'subscribe', _("Subscribe")
1287 if action in self.request.cfg.actions_excluded:
1289 return page.link_to(self.request, text=text, querystr={'action': action}, css_class='nbsubscribe', rel='nofollow')
1291 def quicklinkLink(self, page):
1292 """ Return add/remove quicklink link
1295 @return: link to add or remove a quicklink
1297 if not self.request.user.valid:
1300 _ = self.request.getText
1301 if self.request.user.isQuickLinkedTo([page.page_name]):
1302 action, text = 'quickunlink', _("Remove Link")
1304 action, text = 'quicklink', _("Add Link")
1305 if action in self.request.cfg.actions_excluded:
1307 return page.link_to(self.request, text=text, querystr={'action': action}, css_class='nbquicklink', rel='nofollow')
1309 def attachmentsLink(self, page):
1310 """ Return link to page attachments """
1311 if 'AttachFile' in self.request.cfg.actions_excluded:
1314 _ = self.request.getText
1315 return page.link_to(self.request,
1316 text=_('Attachments'),
1317 querystr={'action': 'AttachFile'}, css_class='nbattachments', rel='nofollow')
1319 def startPage(self):
1320 """ Start page div with page language and direction
1323 @return: page div with language and direction attribtues
1325 return u'<div id="page"%s>\n' % self.content_lang_attr()
1330 Add an empty page bottom div to prevent floating elements to
1331 float out of the page bottom over the footer.
1333 return '<div id="pagebottom"></div>\n</div>\n'
1335 # Public functions #####################################################
1337 def header(self, d, **kw):
1338 """ Assemble page header
1340 Default behavior is to start a page div. Sub class and add
1343 @param d: parameter dictionary
1345 @return: page header html
1347 return self.startPage()
1349 editorheader = header
1351 def footer(self, d, **keywords):
1352 """ Assemble page footer
1354 Default behavior is to end page div. Sub class and add
1357 @param d: parameter dictionary
1360 @return: page footer html
1362 return self.endPage()
1364 # RecentChanges ######################################################
1366 def recentchanges_entry(self, d):
1368 Assemble a single recentchanges entry (table row)
1370 @param d: parameter dictionary
1372 @return: recentchanges entry html
1374 _ = self.request.getText
1376 html.append('<tr>\n')
1378 html.append('<td class="rcicon1">%(icon_html)s</td>\n' % d)
1380 html.append('<td class="rcpagelink">%(pagelink_html)s</td>\n' % d)
1382 html.append('<td class="rctime">')
1384 html.append("%(time_html)s" % d)
1385 html.append('</td>\n')
1387 html.append('<td class="rcicon2">%(info_html)s</td>\n' % d)
1389 html.append('<td class="rceditor">')
1391 html.append('<br>'.join(d['editors']))
1392 html.append('</td>\n')
1394 html.append('<td class="rccomment">')
1396 if d['changecount'] > 1:
1398 for comment in d['comments']:
1399 html.append('%s<tt>#%02d</tt> %s' % (
1400 notfirst and '<br>' or '', comment[0], comment[1]))
1403 comment = d['comments'][0]
1404 html.append('%s' % comment[1])
1405 html.append('</td>\n')
1407 html.append('</tr>\n')
1409 return ''.join(html)
1411 def recentchanges_daybreak(self, d):
1413 Assemble a rc daybreak indication (table row)
1415 @param d: parameter dictionary
1417 @return: recentchanges daybreak html
1419 if d['bookmark_link_html']:
1420 set_bm = ' %(bookmark_link_html)s' % d
1423 return ('<tr class="rcdaybreak"><td colspan="%d">'
1424 '<strong>%s</strong>'
1426 '</td></tr>\n') % (6, d['date'], set_bm)
1428 def recentchanges_header(self, d):
1430 Assemble the recentchanges header (intro + open table)
1432 @param d: parameter dictionary
1434 @return: recentchanges header html
1436 _ = self.request.getText
1438 # Should use user interface language and direction
1439 html = '<div class="recentchanges"%s>\n' % self.ui_lang_attr()
1442 if self.shouldUseRSS(page):
1444 u'<div class="rcrss">',
1445 self.request.formatter.url(1, self.rsshref(page)),
1446 self.request.formatter.rawHTML(self.make_icon("rss")),
1447 self.request.formatter.url(0),
1450 html += ''.join(link)
1455 for day in d['rc_days']:
1456 if day == d['rc_max_days']:
1457 days.append('<strong>%d</strong>' % day)
1460 wikiutil.link_tag(self.request,
1461 '%s?max_days=%d' % (d['q_page_name'], day),
1463 self.request.formatter, rel='nofollow'))
1464 days = ' | '.join(days)
1465 html += (_("Show %s days.") % (days, ))
1467 if d['rc_update_bookmark']:
1468 html += " %(rc_update_bookmark)s %(rc_curr_bookmark)s" % d
1470 html += '</p>\n</div>\n'
1475 def recentchanges_footer(self, d):
1477 Assemble the recentchanges footer (close table)
1479 @param d: parameter dictionary
1481 @return: recentchanges footer html
1483 _ = self.request.getText
1485 html += '</table>\n'
1487 html += "<br>%(rc_msg)s\n" % d
1491 # Language stuff ####################################################
1493 def ui_lang_attr(self):
1494 """Generate language attributes for user interface elements
1496 User interface elements use the user language (if any), kept in
1500 @return: lang and dir html attributes
1502 lang = self.request.lang
1503 return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
1505 def content_lang_attr(self):
1506 """Generate language attributes for wiki page content
1508 Page content uses the page language or the wiki default language.
1511 @return: lang and dir html attributes
1513 lang = self.request.content_lang
1514 return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
1516 def add_msg(self, msg, msg_class=None):
1517 """ Adds a message to a list which will be used to generate status
1520 @param msg: additional message
1521 @param msg_class: html class for the div of the additional message.
1524 msg_class = 'dialog'
1525 if self._send_title_called:
1526 raise Exception("You cannot call add_msg() after send_title()")
1527 self._status.append((msg, msg_class))
1529 # stuff from wikiutil.py
1530 def send_title(self, text, **keywords):
1532 Output the page header (and title).
1534 @param text: the title text
1535 @keyword page: the page instance that called us - using this is more efficient than using pagename..
1536 @keyword pagename: 'PageName'
1537 @keyword print_mode: 1 (or 0)
1538 @keyword editor_mode: 1 (or 0)
1539 @keyword media: css media type, defaults to 'screen'
1540 @keyword allow_doubleclick: 1 (or 0)
1541 @keyword html_head: additional <head> code
1542 @keyword body_attr: additional <body> attributes
1543 @keyword body_onload: additional "onload" JavaScript code
1545 request = self.request
1549 if keywords.has_key('page'):
1550 page = keywords['page']
1551 pagename = page.page_name
1553 pagename = keywords.get('pagename', '')
1554 page = Page(request, pagename)
1555 if keywords.get('msg', ''):
1556 raise DeprecationWarning("Using send_page(msg=) is deprecated! Use theme.add_msg() instead!")
1557 scriptname = request.getScriptname()
1558 pagename_quoted = wikiutil.quoteWikinameURL(pagename)
1560 # get name of system pages
1561 page_front_page = wikiutil.getFrontPage(request).page_name
1562 page_help_contents = wikiutil.getLocalizedPage(request, 'HelpContents').page_name
1563 page_title_index = wikiutil.getLocalizedPage(request, 'TitleIndex').page_name
1564 page_site_navigation = wikiutil.getLocalizedPage(request, 'SiteNavigation').page_name
1565 page_word_index = wikiutil.getLocalizedPage(request, 'WordIndex').page_name
1566 page_help_formatting = wikiutil.getLocalizedPage(request, 'HelpOnFormatting').page_name
1567 page_find_page = wikiutil.getLocalizedPage(request, 'FindPage').page_name
1568 home_page = wikiutil.getInterwikiHomePage(request) # sorry theme API change!!! Either None or tuple (wikiname,pagename) now.
1569 page_parent_page = getattr(page.getParentPage(), 'page_name', None)
1571 # Prepare the HTML <head> element
1572 user_head = [request.cfg.html_head]
1574 # include charset information - needed for moin_dump or any other case
1575 # when reading the html without a web server
1576 user_head.append('''<meta http-equiv="Content-Type" content="%s;charset=%s">\n''' % (page.output_mimetype, page.output_charset))
1578 meta_keywords = request.getPragma('keywords')
1579 meta_desc = request.getPragma('description')
1581 user_head.append('<meta name="keywords" content="%s">\n' % wikiutil.escape(meta_keywords, 1))
1583 user_head.append('<meta name="description" content="%s">\n' % wikiutil.escape(meta_desc, 1))
1585 # search engine precautions / optimization:
1586 # if it is an action or edit/search, send query headers (noindex,nofollow):
1587 if request.query_string:
1588 user_head.append(request.cfg.html_head_queries)
1589 elif request.request_method == 'POST':
1590 user_head.append(request.cfg.html_head_posts)
1591 # we don't want to have BadContent stuff indexed:
1592 elif pagename in ['BadContent', 'LocalBadContent', ]:
1593 user_head.append(request.cfg.html_head_posts)
1594 # if it is a special page, index it and follow the links - we do it
1595 # for the original, English pages as well as for (the possibly
1596 # modified) frontpage:
1597 elif pagename in [page_front_page, request.cfg.page_front_page,
1598 page_title_index, 'TitleIndex',
1599 page_find_page, 'FindPage',
1600 page_site_navigation, 'SiteNavigation',
1602 user_head.append(request.cfg.html_head_index)
1603 # if it is a normal page, index it, but do not follow the links, because
1604 # there are a lot of illegal links (like actions) or duplicates:
1606 user_head.append(request.cfg.html_head_normal)
1608 if 'pi_refresh' in keywords and keywords['pi_refresh']:
1609 user_head.append('<meta http-equiv="refresh" content="%d;URL=%s">' % keywords['pi_refresh'])
1611 # output buffering increases latency but increases throughput as well
1613 # later: <html xmlns=\"http://www.w3.org/1999/xhtml\">
1615 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1626 'sitename': request.cfg.html_pagetitle or request.cfg.sitename,
1627 'print_mode': keywords.get('print_mode', False),
1628 'media': keywords.get('media', 'screen'),
1630 keywords.get('html_head', ''),
1634 output.append('<link rel="Start" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_front_page)))
1636 output.append('<link rel="Alternate" title="%s" href="%s/%s?action=raw">\n' % (
1637 _('Wiki Markup'), scriptname, pagename_quoted, ))
1638 output.append('<link rel="Alternate" media="print" title="%s" href="%s/%s?action=print">\n' % (
1639 _('Print View'), scriptname, pagename_quoted, ))
1641 # !!! currently disabled due to Mozilla link prefetching, see
1642 # http://www.mozilla.org/projects/netlib/Link_Prefetching_FAQ.html
1643 #~ all_pages = request.getPageList()
1646 #~ pos = all_pages.index(pagename)
1647 #~ except ValueError:
1648 #~ # this shopuld never happend in theory, but let's be sure
1651 #~ request.write('<link rel="First" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[0]))
1653 #~ request.write('<link rel="Previous" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos-1])))
1654 #~ if pos+1 < len(all_pages):
1655 #~ request.write('<link rel="Next" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos+1])))
1656 #~ request.write('<link rel="Last" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[-1])))
1658 if page_parent_page:
1659 output.append('<link rel="Up" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_parent_page)))
1661 # write buffer because we call AttachFile
1662 request.write(''.join(output))
1665 # XXX maybe this should be removed completely. moin emits all attachments as <link rel="Appendix" ...>
1666 # and it is at least questionable if this fits into the original intent of rel="Appendix".
1667 if pagename and request.user.may.read(pagename):
1668 from MoinMoin.action import AttachFile
1669 AttachFile.send_link_rel(request, pagename)
1672 '<link rel="Search" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_find_page)),
1673 '<link rel="Index" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_title_index)),
1674 '<link rel="Glossary" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_word_index)),
1675 '<link rel="Help" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_help_formatting)),
1678 output.append("</head>\n")
1679 request.write(''.join(output))
1685 if keywords.has_key('body_attr'):
1686 bodyattr.append(' ')
1687 bodyattr.append(keywords['body_attr'])
1689 # Add doubleclick edit action
1690 if (pagename and keywords.get('allow_doubleclick', 0) and
1691 not keywords.get('print_mode', 0) and
1692 request.user.edit_on_doubleclick):
1693 if request.user.may.write(pagename): # separating this gains speed
1694 url = page.url(request, {'action': 'edit'})
1695 bodyattr.append(''' ondblclick="location.href='%s'" ''' % wikiutil.escape(url, True))
1697 # Set body to the user interface language and direction
1698 bodyattr.append(' %s' % self.ui_lang_attr())
1700 body_onload = keywords.get('body_onload', '')
1702 bodyattr.append(''' onload="%s"''' % body_onload)
1703 output.append('\n<body%s>\n' % ''.join(bodyattr))
1705 # Output -----------------------------------------------------------
1707 # If in print mode, start page div and emit the title
1708 if keywords.get('print_mode', 0):
1712 'page_name': pagename or '',
1715 request.themedict = d
1716 output.append(self.startPage())
1717 output.append(self.interwiki(d))
1718 output.append(self.title(d))
1720 # In standard mode, emit theme.header
1722 exists = pagename and page.exists(includeDeleted=True)
1723 # prepare dict for theme code:
1726 'script_name': scriptname,
1728 'logo_string': request.cfg.logo_string,
1729 'site_name': request.cfg.sitename,
1732 'pagesize': pagename and page.size() or 0,
1733 # exists checked to avoid creation of empty edit-log for non-existing pages
1734 'last_edit_info': exists and page.lastEditInfo() or '',
1735 'page_name': pagename or '',
1736 'page_find_page': page_find_page,
1737 'page_front_page': page_front_page,
1738 'home_page': home_page,
1739 'page_help_contents': page_help_contents,
1740 'page_help_formatting': page_help_formatting,
1741 'page_parent_page': page_parent_page,
1742 'page_title_index': page_title_index,
1743 'page_word_index': page_word_index,
1744 'user_name': request.user.name,
1745 'user_valid': request.user.valid,
1746 'msg': self._status,
1747 'trail': keywords.get('trail', None),
1748 # Discontinued keys, keep for a while for 3rd party theme developers
1749 'titlesearch': 'use self.searchform(d)',
1750 'textsearch': 'use self.searchform(d)',
1751 'navibar': ['use self.navibar(d)'],
1752 'available_actions': ['use self.request.availableActions(page)'],
1755 # add quoted versions of pagenames
1758 if key.startswith('page_'):
1759 if not d[key] is None:
1760 newdict['q_'+key] = wikiutil.quoteWikinameURL(d[key])
1762 newdict['q_'+key] = None
1764 request.themedict = d
1766 # now call the theming code to do the rendering
1767 if keywords.get('editor_mode', 0):
1768 output.append(self.editorheader(d))
1770 output.append(self.header(d))
1773 request.write(''.join(output))
1776 self._send_title_called = True
1778 def send_footer(self, pagename, **keywords):
1780 Output the page footer.
1782 @param pagename: WikiName of the page
1783 @keyword print_mode: true, when page is displayed in Print mode
1785 request = self.request
1786 d = request.themedict
1788 # Emit end of page in print mode, or complete footer in standard mode
1789 if keywords.get('print_mode', 0):
1790 request.write(self.pageinfo(d['page']))
1791 request.write(self.endPage())
1793 request.write(self.footer(d, **keywords))
1795 # stuff moved from request.py
1796 def send_closing_html(self):
1797 """ generate timing info html and closing html tag,
1798 everyone calling send_title must call this at the end to close
1799 the body and html tags.
1801 request = self.request
1803 # as this is the last chance to emit some html, we stop the clocks:
1804 request.clock.stop('run')
1805 request.clock.stop('total')
1808 if request.cfg.show_timings and request.action != 'print':
1809 request.write('<ul id="timings">\n')
1810 for t in request.clock.dump():
1811 request.write('<li>%s</li>\n' % t)
1812 request.write('</ul>\n')
1813 #request.write('<!-- auth_method == %s -->' % repr(request.user.auth_method))
1814 request.write('</body>\n</html>\n\n')