Attachment '__init__.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - Theme Package
4
5 @copyright: 2003-2006 by MoinMoin:ThomasWaldmann
6 @license: GNU GPL, see COPYING for details.
7 """
8
9 from MoinMoin import i18n, wikiutil, config, version
10 from MoinMoin.Page import Page
11 from MoinMoin.util import pysupport
12
13 modules = pysupport.getPackageModules(__file__)
14
15
16 class ThemeBase:
17 """ Base class for themes
18
19 This class supply all the standard template that sub classes can
20 use without rewriting the same code. If you want to change certain
21 elements, override them.
22 """
23
24 name = 'base'
25
26 # fake _ function to get gettext recognize those texts:
27 _ = lambda x: x
28
29 # TODO: remove icons that are not used any more.
30 icons = {
31 # key alt icon filename w h
32 # ------------------------------------------------------------------
33 # navibar
34 'help': ("%(page_help_contents)s", "moin-help.png", 12, 11),
35 'find': ("%(page_find_page)s", "moin-search.png", 12, 12),
36 'diff': (_("Diffs"), "moin-diff.png", 15, 11),
37 'info': (_("Info"), "moin-info.png", 12, 11),
38 'edit': (_("Edit"), "moin-edit.png", 12, 12),
39 'unsubscribe':(_("Unsubscribe"), "moin-unsubscribe.png", 14, 10),
40 'subscribe': (_("Subscribe"), "moin-subscribe.png",14, 10),
41 'raw': (_("Raw"), "moin-raw.png", 12, 13),
42 'xml': (_("XML"), "moin-xml.png", 20, 13),
43 'print': (_("Print"), "moin-print.png", 16, 14),
44 'view': (_("View"), "moin-show.png", 12, 13),
45 'home': (_("Home"), "moin-home.png", 13, 12),
46 'up': (_("Up"), "moin-parent.png", 15, 13),
47 # FileAttach
48 'attach': ("%(attach_count)s", "moin-attach.png", 7, 15),
49 # RecentChanges
50 'rss': (_("[RSS]"), "moin-rss.png", 36, 14),
51 'deleted': (_("[DELETED]"), "moin-deleted.png",60, 12),
52 'updated': (_("[UPDATED]"), "moin-updated.png",60, 12),
53 'new': (_("[NEW]"), "moin-new.png", 31, 12),
54 'diffrc': (_("[DIFF]"), "moin-diff.png", 15, 11),
55 # General
56 'bottom': (_("[BOTTOM]"), "moin-bottom.png", 14, 10),
57 'top': (_("[TOP]"), "moin-top.png", 14, 10),
58 'www': ("[WWW]", "moin-www.png", 11, 11),
59 'mailto': ("[MAILTO]", "moin-email.png", 14, 10),
60 'news': ("[NEWS]", "moin-news.png", 10, 11),
61 'telnet': ("[TELNET]", "moin-telnet.png", 10, 11),
62 'ftp': ("[FTP]", "moin-ftp.png", 11, 11),
63 'file': ("[FILE]", "moin-ftp.png", 11, 11),
64 # search forms
65 'searchbutton': ("[?]", "moin-search.png", 12, 12),
66 'interwiki': ("[%(wikitag)s]", "moin-inter.png", 16, 16),
67 # AttachTable and TaskTable toggle controls
68 'table-null': ("", "table-null.png", 16, 16),
69 'table-close': (_("Click to collapse table"), "table-close.png", 16, 16),
70 'table-open': (_("Click to expand table"), "table-open.png", 16, 16),
71 # TaskTable icons
72 'task--todo': (_("To Do"), "task-no-todo.png", 48, 48),
73 'task--inprogress': (_("In Progress"), "task-no-inprogress.png", 48, 48),
74 'task--pending': (_("Pending"), "task-no-pending.png", 48, 48),
75 'task--done': (_("Done"), "task-no-done.png", 48, 48),
76 'task--failed': (_("Failed"), "task-no-failed.png", 48, 48),
77 'task-low-todo': (_("Low - To Do"), "task-low-todo.png", 48, 48),
78 'task-low-inprogress': (_("Low - In Progress"), "task-low-inprogress.png", 48, 48),
79 'task-low-pending': (_("Low - Pending"), "task-low-pending.png", 48, 48),
80 'task-low-done': (_("Low - Done"), "task-low-done.png", 48, 48),
81 'task-low-failed': (_("Low - Failed"), "task-low-failed.png", 48, 48),
82 'task-medium-todo': (_("Medium - To Do"), "task-medium-todo.png", 48, 48),
83 'task-medium-inprogress': (_("Medium - In Progress"), "task-medium-inprogress.png", 48, 48),
84 'task-medium-pending': (_("Medium - Pending"), "task-medium-pending.png", 48, 48),
85 'task-medium-done': (_("Medium - Done"), "task-medium-done.png", 48, 48),
86 'task-medium-failed': (_("Medium - Failed"), "task-medium-failed.png", 48, 48),
87 'task-high-todo': (_("High - To Do"), "task-high-todo.png", 48, 48),
88 'task-high-inprogress': (_("High - In Progress"), "task-high-inprogress.png", 48, 48),
89 'task-high-pending': (_("High - Pending"), "task-high-pending.png", 48, 48),
90 'task-high-done': (_("High - Done"), "task-high-done.png", 48, 48),
91 'task-high-failed': (_("High - Failed"), "task-high-failed.png", 48, 48),
92 'task-critical-todo': (_("Critical - To Do"), "task-critical-todo.png", 48, 48),
93 'task-critical-inprogress': (_("Critical - In Progress"), "task-critical-inprogress.png", 48, 48),
94 'task-critical-pending': (_("Critical - Pending"), "task-critical-pending.png", 48, 48),
95 'task-critical-done': (_("Critical - Done"), "task-critical-done.png", 48, 48),
96 'task-critical-failed': (_("Critical - Failed"), "task-critical-failed.png", 48, 48),
97 'task-closed': (_("Closed"), "task-closed.png", 48, 48),
98 'task-removeme': (_("Remove Me"), "task-removeme.png", 48, 48),
99 # AttachTable mimetypes
100 'unknown': (_("Unknown filetype"), "filetype-unknown.png", 48, 48),
101 'generic-x-archive': (_("Archive file"), "generic-x-archive.png", 48, 48),
102 'generic-x-audio': (_("Audio file"), "generic-x-audio.png", 48, 48),
103 'generic-x-image': (_("Image file"), "generic-x-image.png", 48, 48),
104 'generic-text-x-source': (_("Source code file"), "generic-text-x-source.png", 48, 48),
105 'generic-x-video': (_("Video file"), "generic-x-video.png", 48, 48),
106 '.doc': (_("MS Word file"), "mime-msword.png", 48, 48),
107 '.html': (_("HTML file"), "mime-text-html.png", 48, 48),
108 '.mdb': (_("MS Access file"), "mime-msaccess.png", 48, 48),
109 '.pdf': (_("PDF file"), "mime-pdf.png", 48, 48),
110 '.ppt': (_("MS PowerPoint file"), "mime-mspowerpoint.png", 48, 48),
111 '.txt': (_("Text file"), "generic-x-text.png", 48, 48),
112 '.xls': (_("MS Excel file"), "mime-msexcel.png", 48, 48),
113
114 # smileys (this is CONTENT, but good looking smileys depend on looking
115 # adapted to the theme background color and theme style in general)
116 #vvv == vvv this must be the same for GUI editor converter
117 'X-(': ("X-(", 'angry.png', 15, 15),
118 ':D': (":D", 'biggrin.png', 15, 15),
119 '<:(': ("<:(", 'frown.png', 15, 15),
120 ':o': (":o", 'redface.png', 15, 15),
121 ':(': (":(", 'sad.png', 15, 15),
122 ':)': (":)", 'smile.png', 15, 15),
123 'B)': ("B)", 'smile2.png', 15, 15),
124 ':))': (":))", 'smile3.png', 15, 15),
125 ';)': (";)", 'smile4.png', 15, 15),
126 '/!\\': ("/!\\", 'alert.png', 15, 15),
127 '<!>': ("<!>", 'attention.png', 15, 15),
128 '(!)': ("(!)", 'idea.png', 15, 15),
129
130 # copied 2001-11-16 from http://pikie.darktech.org/cgi/pikie.py?EmotIcon
131 ':-?': (":-?", 'tongue.png', 15, 15),
132 ':\\': (":\\", 'ohwell.png', 15, 15),
133 '>:>': (">:>", 'devil.png', 15, 15),
134 '|)': ("|)", 'tired.png', 15, 15),
135
136 # some folks use noses in their emoticons
137 ':-(': (":-(", 'sad.png', 15, 15),
138 ':-)': (":-)", 'smile.png', 15, 15),
139 'B-)': ("B-)", 'smile2.png', 15, 15),
140 ':-))': (":-))", 'smile3.png', 15, 15),
141 ';-)': (";-)", 'smile4.png', 15, 15),
142 '|-)': ("|-)", 'tired.png', 15, 15),
143
144 # version 1.0
145 '(./)': ("(./)", 'checkmark.png', 20, 15),
146 '{OK}': ("{OK}", 'thumbs-up.png', 14, 12),
147 '{X}': ("{X}", 'icon-error.png', 16, 16),
148 '{i}': ("{i}", 'icon-info.png', 16, 16),
149 '{1}': ("{1}", 'prio1.png', 15, 13),
150 '{2}': ("{2}", 'prio2.png', 15, 13),
151 '{3}': ("{3}", 'prio3.png', 15, 13),
152
153 # version 1.3.4 (stars)
154 # try {*}{*}{o}
155 '{*}': ("{*}", 'star_on.png', 15, 15),
156 '{o}': ("{o}", 'star_off.png', 15, 15),
157 }
158 del _
159
160 # Style sheets - usually there is no need to override this in sub
161 # classes. Simply supply the css files in the css directory.
162
163 # Standard set of style sheets
164 stylesheets = (
165 # media basename
166 ('all', 'common'),
167 ('screen', 'screen'),
168 ('print', 'print'),
169 ('projection', 'projection'),
170 )
171
172 # Used in print mode
173 stylesheets_print = (
174 # media basename
175 ('all', 'common'),
176 ('all', 'print'),
177 )
178
179 # Used in slide show mode
180 stylesheets_projection = (
181 # media basename
182 ('all', 'common'),
183 ('all', 'projection'),
184 )
185
186 stylesheetsCharset = 'utf-8'
187
188 def __init__(self, request):
189 """
190 Initialize the theme object.
191
192 @param request: the request object
193 """
194 self.request = request
195 self.cfg = request.cfg
196 self._cache = {} # Used to cache elements that may be used several times
197
198 def img_url(self, img):
199 """ Generate an image href
200
201 @param img: the image filename
202 @rtype: string
203 @return: the image href
204 """
205 return "%s/%s/img/%s" % (self.cfg.url_prefix_static, self.name, img)
206
207 def emit_custom_html(self, html):
208 """
209 generate custom HTML code in `html`
210
211 @param html: a string or a callable object, in which case
212 it is called and its return value is used
213 @rtype: string
214 @return: string with html
215 """
216 if html:
217 if callable(html): html = html(self.request)
218 return html
219
220 def logo(self):
221 """ Assemble logo with link to front page
222
223 The logo contain an image and or text or any html markup the
224 admin inserted in the config file. Everything it enclosed inside
225 a div with id="logo".
226
227 @rtype: unicode
228 @return: logo html
229 """
230 html = u''
231 if self.cfg.logo_string:
232 page = wikiutil.getFrontPage(self.request)
233 logo = page.link_to_raw(self.request, self.cfg.logo_string)
234 html = u'''<div id="logo">%s</div>''' % logo
235 return html
236
237 def interwiki(self, d):
238 """ Assemble the interwiki name display, linking to page_front_page
239
240 @param d: parameter dictionary
241 @rtype: string
242 @return: interwiki html
243 """
244 if self.request.cfg.show_interwiki:
245 page = wikiutil.getFrontPage(self.request)
246 text = self.request.cfg.interwikiname or 'Self'
247 link = page.link_to(self.request, text=text, rel='nofollow')
248 html = u'<div id="interwiki"><span>%s</span></div>' % link
249 else:
250 html = u''
251 return html
252
253 def title(self, d):
254 """ Assemble the title (now using breadcrumbs)
255
256 @param d: parameter dictionary
257 @rtype: string
258 @return: title html
259 """
260 _ = self.request.getText
261 content = []
262 if d['title_text'] == d['page_name']: # just showing a page, no action
263 curpage = ''
264 segments = d['page_name'].split('/') # was: title_text
265 for s in segments[:-1]:
266 curpage += s
267 content.append("<li>%s</li>" % Page(self.request, curpage).link_to(self.request, s))
268 curpage += '/'
269 link_text = segments[-1]
270 link_title = _('Click to do a full-text search for this title')
271 link_query = {
272 'action': 'fullsearch',
273 'value': 'linkto:"%s"' % d['page_name'],
274 'context': '180',
275 }
276 # we dont use d['title_link'] any more, but make it ourselves:
277 link = d['page'].link_to(self.request, link_text, querystr=link_query, title=link_title, css_class='backlink', rel='nofollow')
278 content.append(('<li>%s</li>') % link)
279 else:
280 content.append('<li>%s</li>' % wikiutil.escape(d['title_text']))
281
282 html = '''
283 <ul id="pagelocation">
284 %s
285 </ul>
286 ''' % "".join(content)
287 return html
288
289 def username(self, d):
290 """ Assemble the username / userprefs link
291
292 @param d: parameter dictionary
293 @rtype: unicode
294 @return: username html
295 """
296 request = self.request
297 _ = request.getText
298
299 userlinks = []
300 # Add username/homepage link for registered users. We don't care
301 # if it exists, the user can create it.
302 if request.user.valid and request.user.name:
303 interwiki = wikiutil.getInterwikiHomePage(request)
304 name = request.user.name
305 aliasname = request.user.aliasname
306 if not aliasname:
307 aliasname = name
308 title = "%s @ %s" % (aliasname, interwiki[0])
309 # link to (interwiki) user homepage
310 homelink = (request.formatter.interwikilink(1, title=title, id="userhome", generated=True, *interwiki) +
311 request.formatter.text(name) +
312 request.formatter.interwikilink(0, title=title, id="userhome", *interwiki))
313 userlinks.append(homelink)
314 # link to userprefs action
315 userlinks.append(d['page'].link_to(request, text=_('Preferences'),
316 querystr={'action': 'userprefs'}, id='userprefs', rel='nofollow'))
317
318 if request.cfg.show_login:
319 if request.user.valid:
320 userlinks.append(d['page'].link_to(request, text=_('Logout', formatted=False),
321 querystr={'action': 'logout', 'logout': 'logout'}, id='logout', rel='nofollow'))
322 else:
323 userlinks.append(d['page'].link_to(request, text=_("Login", formatted=False),
324 querystr={'action': 'login'}, id='login', rel='nofollow'))
325
326 userlinks = [u'<li>%s</li>' % link for link in userlinks]
327 html = u'<ul id="username">%s</ul>' % ''.join(userlinks)
328 return html
329
330 # Schemas supported in toolbar links, using [url label] format
331 linkSchemas = [r'http://', r'https://', r'ftp://', 'mailto:', r'irc://', r'ircs://', ] + \
332 [x + ':' for x in config.url_schemas]
333
334 def splitNavilink(self, text, localize=1):
335 """ Split navibar links into pagename, link to page
336
337 Admin or user might want to use shorter navibar items by using
338 the [page title] or [url title] syntax. In this case, we don't
339 use localization, and the links goes to page or to the url, not
340 the localized version of page.
341
342 TODO: refactor/cleanup!
343 * the ItemCache is seeing 3 requests for a normal navi_bar entry
344 * for user quicklinks stuff, ItemCache is seeing a 1st request for
345 WikiName:PageName, then a 2nd (correct) one for PageName
346 * page object gets recreated at the end, although we already have
347 one for some cases
348
349 @param text: the text used in config or user preferences
350 @rtype: tuple
351 @return: pagename or url, link to page or url
352 """
353 request = self.request
354
355 # Handle [pagename title] or [url title] formats
356 if text.startswith('[') and text.endswith(']'):
357 try:
358 pagename, title = text[1:-1].strip().split(' ', 1)
359 title = title.strip()
360 localize = 0
361 except (ValueError, TypeError):
362 # Just use the text as is.
363 pagename = title = text
364
365 # Handle regular pagename like "FrontPage"
366 else:
367 # Use localized pages for the current user
368 if localize:
369 page = wikiutil.getSysPage(request, text)
370 else:
371 page = Page(request, text)
372 pagename = page.page_name
373 title = page.split_title(request)
374 title = self.shortenPagename(title)
375 link = page.link_to(request, title)
376
377
378 from MoinMoin import config
379 for scheme in self.linkSchemas:
380 if pagename.startswith(scheme):
381 title = wikiutil.escape(title)
382 link = self.request.formatter.url(1, pagename) + \
383 self.request.formatter.text(title) +\
384 self.request.formatter.url(0)
385 return pagename, link
386
387 # remove wiki: url prefix
388 if pagename.startswith("wiki:"):
389 pagename = pagename[5:]
390
391 # try handling interwiki links
392 try:
393 interwiki, page = pagename.split(':', 1)
394 thiswiki = request.cfg.interwikiname
395 if interwiki == thiswiki:
396 pagename = page
397 title = page
398 else:
399 return (pagename,
400 self.request.formatter.interwikilink(True, interwiki, page) +
401 page +
402 self.request.formatter.interwikilink(False, interwiki, page)
403 )
404
405 except ValueError:
406 pass
407
408 pagename = request.normalizePagename(pagename)
409 link = Page(request, pagename).link_to(request, title)
410
411 return pagename, link
412
413 def shortenPagename(self, name):
414 """ Shorten page names
415
416 Shorten very long page names that tend to break the user
417 interface. The short name is usually fine, unless really stupid
418 long names are used (WYGIWYD).
419
420 If you don't like to do this in your theme, or want to use
421 different algorithm, override this method.
422
423 @param name: page name, unicode
424 @rtype: unicode
425 @return: shortened version.
426 """
427 maxLength = self.maxPagenameLength()
428 # First use only the sub page name, that might be enough
429 if len(name) > maxLength:
430 name = name.split('/')[-1]
431 # If it's not enough, replace the middle with '...'
432 if len(name) > maxLength:
433 half, left = divmod(maxLength - 3, 2)
434 name = u'%s...%s' % (name[:half + left], name[-half:])
435 return name
436
437 def maxPagenameLength(self):
438 """ Return maximum length for shortened page names """
439 return 25
440
441 def navibar(self, d):
442 """ Assemble the navibar
443
444 @param d: parameter dictionary
445 @rtype: unicode
446 @return: navibar html
447 """
448 request = self.request
449 found = {} # pages we found. prevent duplicates
450 items = [] # navibar items
451 item = u'<li class="%s">%s</li>'
452 current = d['page_name']
453
454 # Process config navi_bar
455 if request.cfg.navi_bar:
456 for text in request.cfg.navi_bar:
457 pagename, link = self.splitNavilink(text)
458 if pagename == current:
459 cls = 'wikilink current'
460 else:
461 cls = 'wikilink'
462 items.append(item % (cls, link))
463 found[pagename] = 1
464
465 # Add user links to wiki links, eliminating duplicates.
466 userlinks = request.user.getQuickLinks()
467 for text in userlinks:
468 # Split text without localization, user knows what he wants
469 pagename, link = self.splitNavilink(text, localize=0)
470 if not pagename in found:
471 if pagename == current:
472 cls = 'userlink current'
473 else:
474 cls = 'userlink'
475 items.append(item % (cls, link))
476 found[pagename] = 1
477
478 # Add current page at end
479 if not current in found:
480 title = d['page'].split_title(request)
481 title = self.shortenPagename(title)
482 link = d['page'].link_to(request, title)
483 cls = 'current'
484 items.append(item % (cls, link))
485
486 # Assemble html
487 items = u''.join(items)
488 html = u'''
489 <ul id="navibar">
490 %s
491 </ul>
492 ''' % items
493 return html
494
495 def get_icon(self, icon):
496 """ Return icon data from self.icons
497
498 If called from [[Icon(file)]] we have a filename, not a
499 key. Using filenames is deprecated, but for now, we simulate old
500 behavior.
501
502 @param icon: icon name or file name (string)
503 @rtype: tuple
504 @return: alt (unicode), href (string), width, height (int)
505 """
506 if icon in self.icons:
507 alt, icon, w, h = self.icons[icon]
508 else:
509 # Create filenames to icon data mapping on first call, then
510 # cache in class for next calls.
511 if not getattr(self.__class__, 'iconsByFile', None):
512 d = {}
513 for data in self.icons.values():
514 d[data[1]] = data
515 self.__class__.iconsByFile = d
516
517 # Try to get icon data by file name
518 if icon in self.iconsByFile:
519 alt, icon, w, h = self.iconsByFile[icon]
520 else:
521 alt, icon, w, h = '', icon, '', ''
522
523 return alt, self.img_url(icon), w, h
524
525 def make_icon(self, icon, vars=None):
526 """
527 This is the central routine for making <img> tags for icons!
528 All icons stuff except the top left logo and search field icons are
529 handled here.
530
531 @param icon: icon id (dict key)
532 @param vars: ...
533 @rtype: string
534 @return: icon html (img tag)
535 """
536 if vars is None:
537 vars = {}
538 alt, img, w, h = self.get_icon(icon)
539 try:
540 alt = alt % vars
541 except KeyError, err:
542 alt = 'KeyError: %s' % str(err)
543 if self.request:
544 alt = self.request.getText(alt, formatted=False)
545 try:
546 tag = self.request.formatter.image(src=img, alt=alt, width=w, height=h)
547 except AttributeError: # XXX FIXME if we have no formatter or no request
548 tag = '<img src="%s" alt="%s" width="%s" height="%s">' % (
549 img, alt, w, h)
550 import warnings
551 warnings.warn("calling themes without correct request", DeprecationWarning)
552 return tag
553
554 def make_iconlink(self, which, d):
555 """
556 Make a link with an icon
557
558 @param which: icon id (dictionary key)
559 @param d: parameter dictionary
560 @rtype: string
561 @return: html link tag
562 """
563 querystr, title, icon = self.cfg.page_icons_table[which]
564 d['title'] = title % d
565 d['i18ntitle'] = self.request.getText(d['title'], formatted=False)
566 img_src = self.make_icon(icon, d)
567 rev = d['rev']
568 if rev and which in ['raw', 'print', ]:
569 querystr['rev'] = str(rev)
570 attrs = {'rel': 'nofollow', 'title': d['i18ntitle'], }
571 page = d['page']
572 return page.link_to_raw(self.request, text=img_src, querystr=querystr, **attrs)
573
574 def msg(self, d):
575 """ Assemble the msg display
576
577 Display a message with a widget or simple strings with a clear message link.
578
579 @param d: parameter dictionary
580 @rtype: unicode
581 @return: msg display html
582 """
583 _ = self.request.getText
584 msg = d['msg']
585 if not msg:
586 return u''
587
588 if isinstance(msg, (str, unicode)):
589 # Render simple strings with a close link
590 close = d['page'].link_to(self.request, text=_('Clear message'))
591 html = u'<p>%s</p>\n<div class="buttons">%s</div>\n' % (msg, close)
592 else:
593 # msg is a widget
594 html = msg.render()
595
596 return u'<div id="message">\n%s\n</div>\n' % html
597
598 def trail(self, d):
599 """ Assemble page trail
600
601 @param d: parameter dictionary
602 @rtype: unicode
603 @return: trail html
604 """
605 request = self.request
606 user = request.user
607 html = ''
608 if user.valid and user.show_page_trail:
609 trail = user.getTrail()
610 if trail:
611 items = []
612 for pagename in trail:
613 try:
614 interwiki, page = pagename.split(":", 1)
615 if request.cfg.interwikiname != interwiki:
616 link = (self.request.formatter.interwikilink(True, interwiki, page) +
617 self.shortenPagename(page) +
618 self.request.formatter.interwikilink(False, interwiki, page))
619 items.append('<li>%s</li>' % link)
620 continue
621 else:
622 pagename = page
623
624 except ValueError:
625 pass
626 page = Page(request, pagename)
627 title = page.split_title(request)
628 title = self.shortenPagename(title)
629 link = page.link_to(request, title)
630 items.append('<li>%s</li>' % link)
631 html = '''
632 <ul id="pagetrail">
633 %s
634 </ul>''' % ''.join(items)
635 return html
636
637 def html_stylesheets(self, d):
638 """ Assemble html head stylesheet links
639
640 @param d: parameter dictionary
641 @rtype: string
642 @return: stylesheets links
643 """
644 link = '<link rel="stylesheet" type="text/css" charset="%s" media="%s" href="%s">'
645
646 # Check mode
647 if d.get('print_mode'):
648 media = d.get('media', 'print')
649 stylesheets = getattr(self, 'stylesheets_' + media)
650 else:
651 stylesheets = self.stylesheets
652 usercss = self.request.user.valid and self.request.user.css_url
653
654 # Create stylesheets links
655 html = []
656 prefix = self.cfg.url_prefix_static
657 csshref = '%s/%s/css' % (prefix, self.name)
658 for media, basename in stylesheets:
659 href = '%s/%s.css' % (csshref, basename)
660 html.append(link % (self.stylesheetsCharset, media, href))
661
662 # Don't add user css url if it matches one of ours
663 if usercss and usercss == href:
664 usercss = None
665
666 # admin configurable additional css (farm or wiki level)
667 for media, csshref in self.request.cfg.stylesheets:
668 html.append(link % (self.stylesheetsCharset, media, csshref))
669
670
671 # tribute to the most sucking browser: MS IE6
672 if self.cfg.hacks.get('ie7', False) and self.request.action != 'edit':
673 # using FCKeditor and IE7 at the same time makes nices crashes in IE
674 html.append("""
675 <!-- compliance patch for microsoft browsers -->
676 <!--[if lt IE 7]>
677 <script src="%s/common/ie7/ie7-standard-p.js" type="text/javascript"></script>
678 <![endif]-->
679 """ % prefix)
680
681 csshref = '%s/%s/css/msie.css' % (prefix, self.name)
682 html.append("""
683 <!-- css only for MSIE browsers -->
684 <!--[if IE]>
685 %s
686 <![endif]-->
687 """ % link % (self.stylesheetsCharset, 'all', csshref))
688
689 # Add user css url (assuming that user css uses same charset)
690 if usercss and usercss.lower() != "none":
691 html.append(link % (self.stylesheetsCharset, 'all', usercss))
692
693 return '\n'.join(html)
694
695 def shouldShowPageinfo(self, page):
696 """ Should we show page info?
697
698 Should be implemented by actions. For now, we check here by action
699 name and page.
700
701 @param page: current page
702 @rtype: bool
703 @return: true if should show page info
704 """
705 if page.exists() and self.request.user.may.read(page.page_name):
706 # These actions show the page content.
707 # TODO: on new action, page info will not show. A better
708 # solution will be if the action itself answer the question:
709 # showPageInfo().
710 contentActions = [u'', u'show', u'refresh', u'preview', u'diff',
711 u'subscribe', u'RenamePage', u'DeletePage',
712 u'SpellCheck', u'print']
713 return self.request.action in contentActions
714 return False
715
716 def pageinfo(self, page):
717 """ Return html fragment with page meta data
718
719 Since page information uses translated text, it uses the ui
720 language and direction. It looks strange sometimes, but
721 translated text using page direction looks worse.
722
723 @param page: current page
724 @rtype: unicode
725 @return: page last edit information
726 """
727 _ = self.request.getText
728 html = ''
729 if self.shouldShowPageinfo(page):
730 info = page.lastEditInfo()
731 if info:
732 if info['editor']:
733 info = _("last edited %(time)s by %(editor)s") % info
734 else:
735 info = _("last modified %(time)s") % info
736 pagename = page.page_name
737 if self.request.cfg.show_interwiki:
738 pagename = "%s: %s" % (self.request.cfg.interwikiname, pagename)
739 info = "%s (%s)" % (pagename, info)
740 html = '<p id="pageinfo" class="info"%(lang)s>%(info)s</p>\n' % {
741 'lang': self.ui_lang_attr(),
742 'info': info
743 }
744 return html
745
746 def searchform(self, d):
747 """
748 assemble HTML code for the search forms
749
750 @param d: parameter dictionary
751 @rtype: unicode
752 @return: search form html
753 """
754 _ = self.request.getText
755 form = self.request.form
756 updates = {
757 'search_label': _('Search:'),
758 'search_value': wikiutil.escape(form.get('value', [''])[0], 1),
759 'search_full_label': _('Text', formatted=False),
760 'search_title_label': _('Titles', formatted=False),
761 }
762 d.update(updates)
763
764 html = u'''
765 <form id="searchform" method="get" action="">
766 <div>
767 <input type="hidden" name="action" value="fullsearch">
768 <input type="hidden" name="context" value="180">
769 <label for="searchinput">%(search_label)s</label>
770 <input id="searchinput" type="text" name="value" value="%(search_value)s" size="20"
771 onfocus="searchFocus(this)" onblur="searchBlur(this)"
772 onkeyup="searchChange(this)" onchange="searchChange(this)" alt="Search">
773 <input id="titlesearch" name="titlesearch" type="submit"
774 value="%(search_title_label)s" alt="Search Titles">
775 <input id="fullsearch" name="fullsearch" type="submit"
776 value="%(search_full_label)s" alt="Search Full Text">
777 </div>
778 </form>
779 <script type="text/javascript">
780 <!--// Initialize search form
781 var f = document.getElementById('searchform');
782 f.getElementsByTagName('label')[0].style.display = 'none';
783 var e = document.getElementById('searchinput');
784 searchChange(e);
785 searchBlur(e);
786 //-->
787 </script>
788 ''' % d
789 return html
790
791 def showversion(self, d, **keywords):
792 """
793 assemble HTML code for copyright and version display
794
795 @param d: parameter dictionary
796 @rtype: string
797 @return: copyright and version display html
798 """
799 html = ''
800 if self.cfg.show_version and not keywords.get('print_mode', 0):
801 html = (u'<div id="version">MoinMoin Release %s [Revision %s], '
802 'Copyright 2000-2006 by Juergen Hermann</div>') % (version.release, version.revision, )
803 return html
804
805 def headscript(self, d):
806 """ Return html head script with common functions
807
808 @param d: parameter dictionary
809 @rtype: unicode
810 @return: script for html head
811 """
812 # Don't add script for print view
813 if self.request.action == 'print':
814 return u''
815
816 _ = self.request.getText
817 script = u"""
818 <script type="text/javascript">
819 <!--
820 var search_hint = "%(search_hint)s";
821 //-->
822 </script>
823 """ % {
824 'search_hint': _('Search', formatted=False),
825 }
826 return script
827
828 def shouldUseRSS(self):
829 """ Return True if rss feature is available, or False
830
831 Currently rss is broken on plain Python, and works only when
832 installing PyXML. Return true if PyXML is installed.
833 """
834 # Stolen from wikitest.py
835 try:
836 import xml
837 return '_xmlplus' in xml.__file__
838 except ImportError:
839 # This error reported on Python 2.2
840 return False
841
842 def rsshref(self):
843 """ Create rss href, used for rss button and head link
844
845 @rtype: unicode
846 @return: rss href
847 """
848 request = self.request
849 url = Page(request, 'RecentChanges').url(request, querystr={
850 'action':'rss_rc', 'ddiffs': '1', 'unique': '1', }, escape=0, relative=False)
851 return url
852
853 def rsslink(self):
854 """ Create rss link in head, used by FireFox
855
856 RSS link for FireFox. This shows an rss link in the bottom of
857 the page and let you subscribe to the wiki rss feed.
858
859 @rtype: unicode
860 @return: html head
861 """
862 link = u''
863 if self.shouldUseRSS():
864 link = (u'<link rel="alternate" title="%s Recent Changes" '
865 u'href="%s" type="application/rss+xml">') % (
866 self.cfg.sitename,
867 self.rsshref() )
868 return link
869
870 def html_head(self, d):
871 """ Assemble html head
872
873 @param d: parameter dictionary
874 @rtype: unicode
875 @return: html head
876 """
877 html = [
878 u'<title>%(title)s - %(sitename)s</title>' % d,
879 self.externalScript('common'),
880 self.headscript(d), # Should move to separate .js file
881 self.guiEditorScript(d),
882 self.html_stylesheets(d),
883 self.rsslink(),
884 ]
885 return '\n'.join(html)
886
887 def externalScript(self, name):
888 """ Format external script html """
889 src = '%s/common/js/%s.js' % (self.request.cfg.url_prefix_static, name)
890 return '<script type="text/javascript" src="%s"></script>' % src
891
892 def credits(self, d, **keywords):
893 """ Create credits html from credits list """
894 if isinstance(self.cfg.page_credits, (list, tuple)):
895 items = ['<li>%s</li>' % i for i in self.cfg.page_credits]
896 html = '<ul id="credits">\n%s\n</ul>\n' % ''.join(items)
897 else:
898 # Old config using string, output as is
899 html = self.cfg.page_credits
900 return html
901
902 def actionsMenu(self, page):
903 """ Create actions menu list and items data dict
904
905 The menu will contain the same items always, but items that are
906 not available will be disabled (some broken browsers will let
907 you select disabled options though).
908
909 The menu should give best user experience for javascript
910 enabled browsers, and acceptable behavior for those who prefer
911 not to use Javascript.
912
913 TODO: Move actionsMenuInit() into body onload. This require
914 that the theme will render body, its currently done on
915 wikiutil/page.
916
917 @param page: current page, Page object
918 @rtype: unicode
919 @return: actions menu html fragment
920 """
921 request = self.request
922 _ = request.getText
923 rev = request.rev
924
925 menu = [
926 'raw',
927 'print',
928 'RenderAsDocbook',
929 'refresh',
930 '__separator__',
931 'SpellCheck',
932 'LikePages',
933 'LocalSiteMap',
934 '__separator__',
935 'RenamePage',
936 'DeletePage',
937 '__separator__',
938 'MyPages',
939 'SubscribeUser',
940 '__separator__',
941 'Despam',
942 'revert',
943 'PackagePages',
944 ]
945
946 titles = {
947 # action: menu title
948 '__title__': _("More Actions:", formatted=False),
949 # Translation may need longer or shorter separator
950 '__separator__': _('------------', formatted=False),
951 'raw': _('Raw Text', formatted=False),
952 'print': _('Print View', formatted=False),
953 'refresh': _('Delete Cache', formatted=False),
954 'SpellCheck': _('Check Spelling', formatted=False), # rename action!
955 'RenamePage': _('Rename Page', formatted=False),
956 'DeletePage': _('Delete Page', formatted=False),
957 'LikePages': _('Like Pages', formatted=False),
958 'LocalSiteMap': _('Local Site Map', formatted=False),
959 'MyPages': _('My Pages', formatted=False),
960 'SubscribeUser': _('Subscribe User', formatted=False),
961 'Despam': _('Remove Spam', formatted=False),
962 'revert': _('Revert to this revision', formatted=False),
963 'PackagePages': _('Package Pages', formatted=False),
964 'RenderAsDocbook': _('Render as Docbook', formatted=False),
965 }
966
967 options = []
968 option = '<option value="%(action)s"%(disabled)s>%(title)s</option>'
969 # class="disabled" is a workaround for browsers that ignore
970 # "disabled", e.g IE, Safari
971 # for XHTML: data['disabled'] = ' disabled="disabled"'
972 disabled = ' disabled class="disabled"'
973
974 # Format standard actions
975 available = request.getAvailableActions(page)
976 for action in menu:
977 data = {'action': action, 'disabled': '', 'title': titles[action]}
978
979 # Enable delete cache only if page can use caching
980 if action == 'refresh':
981 if not page.canUseCache():
982 data['action'] = 'show'
983 data['disabled'] = disabled
984
985 # Special menu items. Without javascript, executing will
986 # just return to the page.
987 elif action.startswith('__'):
988 data['action'] = 'show'
989
990 # Actions which are not available for this wiki, user or page
991 if (action == '__separator__' or
992 (action[0].isupper() and not action in available)):
993 data['disabled'] = disabled
994
995 options.append(option % data)
996
997 # Add custom actions not in the standard menu, except for
998 # some actions like AttachFile (we have them on top level)
999 more = [item for item in available if not item in titles and not item in ('AttachFile',)]
1000 more.sort()
1001 if more:
1002 # Add separator
1003 separator = option % {'action': 'show', 'disabled': disabled,
1004 'title': titles['__separator__']}
1005 options.append(separator)
1006 # Add more actions (all enabled)
1007 for action in more:
1008 data = {'action': action, 'disabled': ''}
1009 # Always add spaces: AttachFile -> Attach File
1010 # XXX TODO do not create page just for using split_title
1011 title = Page(request, action).split_title(request, force=1)
1012 # Use translated version if available
1013 data['title'] = _(title, formatted=False)
1014 options.append(option % data)
1015
1016 data = {
1017 'label': titles['__title__'],
1018 'options': '\n'.join(options),
1019 'rev_field': rev and '<input type="hidden" name="rev" value="%d">' % rev or '',
1020 'do_button': _("Do")
1021 }
1022 html = '''
1023 <form class="actionsmenu" method="get" action="">
1024 <div>
1025 <label>%(label)s</label>
1026 <select name="action"
1027 onchange="if ((this.selectedIndex != 0) &&
1028 (this.options[this.selectedIndex].disabled == false)) {
1029 this.form.submit();
1030 }
1031 this.selectedIndex = 0;">
1032 %(options)s
1033 </select>
1034 <input type="submit" value="%(do_button)s">
1035 %(rev_field)s
1036 </div>
1037 <script type="text/javascript">
1038 <!--// Init menu
1039 actionsMenuInit('%(label)s');
1040 //-->
1041 </script>
1042 </form>
1043 ''' % data
1044
1045 return html
1046
1047 def editbar(self, d):
1048 """ Assemble the page edit bar.
1049
1050 Create html on first call, then return cached html.
1051
1052 @param d: parameter dictionary
1053 @rtype: unicode
1054 @return: iconbar html
1055 """
1056 page = d['page']
1057 if not self.shouldShowEditbar(page):
1058 return ''
1059
1060 html = self._cache.get('editbar')
1061 if html is None:
1062 # Remove empty items and format as list
1063 items = ''.join(['<li>%s</li>' % item
1064 for item in self.editbarItems(page) if item])
1065 html = u'<ul class="editbar">%s</ul>\n' % items
1066 self._cache['editbar'] = html
1067
1068 return html
1069
1070 def shouldShowEditbar(self, page):
1071 """ Should we show the editbar?
1072
1073 Actions should implement this, because only the action knows if
1074 the edit bar makes sense. Until it goes into actions, we do the
1075 checking here.
1076
1077 @param page: current page
1078 @rtype: bool
1079 @return: true if editbar should show
1080 """
1081 # Show editbar only for existing pages, including deleted pages,
1082 # that the user may read. If you may not read, you can't edit,
1083 # so you don't need editbar.
1084 if (page.exists(includeDeleted=1) and
1085 self.request.user.may.read(page.page_name)):
1086 form = self.request.form
1087 action = self.request.action
1088 # Do not show editbar on edit but on save/cancel
1089 return not (action == 'edit' and
1090 not form.has_key('button_save') and
1091 not form.has_key('button_cancel'))
1092 return False
1093
1094 def editbarItems(self, page):
1095 """ Return list of items to show on the editbar
1096
1097 This is separate method to make it easy to customize the
1098 edtibar in sub classes.
1099 """
1100 return [self.editorLink(page),
1101 self.infoLink(page),
1102 self.subscribeLink(page),
1103 self.quicklinkLink(page),
1104 self.attachmentsLink(page),
1105 self.actionsMenu(page),
1106 ]
1107
1108 def guiworks(self, page):
1109 """ Return whether the gui editor / converter can work for that page.
1110
1111 The GUI editor currently only works for wiki format.
1112 """
1113 return page.pi_format == 'wiki'
1114
1115 def editorLink(self, page):
1116 """ Return a link to the editor
1117
1118 If the user can't edit, return a disabled edit link.
1119
1120 If the user want to show both editors, it will display "Edit
1121 (Text)", otherwise as "Edit".
1122 """
1123 if not (page.isWritable() and
1124 self.request.user.may.write(page.page_name)):
1125 return self.disabledEdit()
1126
1127 _ = self.request.getText
1128 querystr = {'action': 'edit'}
1129
1130 guiworks = self.guiworks(page)
1131 if self.showBothEditLinks() and guiworks:
1132 text = _('Edit (Text)', formatted=False)
1133 querystr['editor'] = 'text'
1134 attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
1135 else:
1136 text = _('Edit', formatted=False)
1137 if guiworks:
1138 # 'textonly' will be upgraded dynamically to 'guipossible' by JS
1139 querystr['editor'] = 'textonly'
1140 attrs = {'name': 'editlink', 'rel': 'nofollow', }
1141 else:
1142 querystr['editor'] = 'text'
1143 attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
1144
1145 return page.link_to(self.request, text=text, querystr=querystr, **attrs)
1146
1147 def showBothEditLinks(self):
1148 """ Return True if both edit links should be displayed """
1149 editor = self.request.user.editor_ui
1150 if editor == '<default>':
1151 editor = self.request.cfg.editor_ui
1152 return editor == 'freechoice'
1153
1154 def guiEditorScript(self, d):
1155 """ Return a script that set the gui editor link variables
1156
1157 The link will be created only when javascript is enabled and
1158 the browser is compatible with the editor.
1159 """
1160 page = d['page']
1161 if not (page.isWritable() and
1162 self.request.user.may.write(page.page_name) and
1163 self.showBothEditLinks() and
1164 self.guiworks(page)):
1165 return ''
1166
1167 _ = self.request.getText
1168 return """\
1169 <script type="text/javascript">
1170 <!-- // GUI edit link and i18n
1171 var gui_editor_link_href = "%(url)s";
1172 var gui_editor_link_text = "%(text)s";
1173 //-->
1174 </script>
1175 """ % {'url': page.url(self.request, querystr={'action': 'edit', 'editor': 'gui', }, escape=0),
1176 'text': _('Edit (GUI)', formatted=False),
1177 }
1178
1179 def disabledEdit(self):
1180 """ Return a disabled edit link """
1181 _ = self.request.getText
1182 return ('<span class="disabled">%s</span>'
1183 % _('Immutable Page', formatted=False))
1184
1185 def infoLink(self, page):
1186 """ Return link to page information """
1187 _ = self.request.getText
1188 return page.link_to(self.request,
1189 text=_('Info', formatted=False),
1190 querystr={'action': 'info'}, id='info', rel='nofollow')
1191
1192 def subscribeLink(self, page):
1193 """ Return subscribe/unsubscribe link to valid users
1194
1195 @rtype: unicode
1196 @return: subscribe or unsubscribe link
1197 """
1198 if not (self.cfg.mail_enabled and self.request.user.valid):
1199 return ''
1200
1201 _ = self.request.getText
1202 if self.request.user.isSubscribedTo([page.page_name]):
1203 text = _("Unsubscribe", formatted=False)
1204 else:
1205 text = _("Subscribe", formatted=False)
1206 return page.link_to(self.request, text=text, querystr={'action': 'subscribe'}, id='subscribe', rel='nofollow')
1207
1208 def quicklinkLink(self, page):
1209 """ Return add/remove quicklink link
1210
1211 @rtype: unicode
1212 @return: link to add or remove a quicklink
1213 """
1214 if not self.request.user.valid:
1215 return ''
1216
1217 _ = self.request.getText
1218 if self.request.user.isQuickLinkedTo([page.page_name]):
1219 text = _("Remove Link", formatted=False)
1220 else:
1221 text = _("Add Link", formatted=False)
1222 return page.link_to(self.request, text=text, querystr={'action': 'quicklink'}, id='quicklink', rel='nofollow')
1223
1224 def attachmentsLink(self, page):
1225 """ Return link to page attachments """
1226 _ = self.request.getText
1227 return page.link_to(self.request,
1228 text=_('Attachments', formatted=False),
1229 querystr={'action': 'AttachFile'}, id='attachments', rel='nofollow')
1230
1231 def startPage(self):
1232 """ Start page div with page language and direction
1233
1234 @rtype: unicode
1235 @return: page div with language and direction attribtues
1236 """
1237 return u'<div id="page"%s>\n' % self.content_lang_attr()
1238
1239 def endPage(self):
1240 """ End page div
1241
1242 Add an empty page bottom div to prevent floating elements to
1243 float out of the page bottom over the footer.
1244 """
1245 return '<div id="pagebottom"></div>\n</div>\n'
1246
1247 # Public functions #####################################################
1248
1249 def header(self, d, **kw):
1250 """ Assemble page header
1251
1252 Default behavior is to start a page div. Sub class and add
1253 footer items.
1254
1255 @param d: parameter dictionary
1256 @rtype: string
1257 @return: page header html
1258 """
1259 return self.startPage()
1260
1261 editorheader = header
1262
1263 def footer(self, d, **keywords):
1264 """ Assemble page footer
1265
1266 Default behavior is to end page div. Sub class and add
1267 footer items.
1268
1269 @param d: parameter dictionary
1270 @keyword ...:...
1271 @rtype: string
1272 @return: page footer html
1273 """
1274 return self.endPage()
1275
1276 # RecentChanges ######################################################
1277
1278 def recentchanges_entry(self, d):
1279 """
1280 Assemble a single recentchanges entry (table row)
1281
1282 @param d: parameter dictionary
1283 @rtype: string
1284 @return: recentchanges entry html
1285 """
1286 _ = self.request.getText
1287 html = []
1288 html.append('<tr>\n')
1289
1290 html.append('<td class="rcicon1">%(icon_html)s</td>\n' % d)
1291
1292 html.append('<td class="rcpagelink">%(pagelink_html)s</td>\n' % d)
1293
1294 html.append('<td class="rctime">')
1295 if d['time_html']:
1296 html.append("%(time_html)s" % d)
1297 html.append('</td>\n')
1298
1299 html.append('<td class="rcicon2">%(info_html)s</td>\n' % d)
1300
1301 html.append('<td class="rceditor">')
1302 if d['editors']:
1303 html.append('<br>'.join(d['editors']))
1304 html.append('</td>\n')
1305
1306 html.append('<td class="rccomment">')
1307 if d['comments']:
1308 if d['changecount'] > 1:
1309 notfirst = 0
1310 for comment in d['comments']:
1311 html.append('%s<tt>#%02d</tt> %s' % (
1312 notfirst and '<br>' or '', comment[0], comment[1]))
1313 notfirst = 1
1314 else:
1315 comment = d['comments'][0]
1316 html.append('%s' % comment[1])
1317 html.append('</td>\n')
1318
1319 html.append('</tr>\n')
1320
1321 return ''.join(html)
1322
1323 def recentchanges_daybreak(self, d):
1324 """
1325 Assemble a rc daybreak indication (table row)
1326
1327 @param d: parameter dictionary
1328 @rtype: string
1329 @return: recentchanges daybreak html
1330 """
1331 if d['bookmark_link_html']:
1332 set_bm = ' %(bookmark_link_html)s' % d
1333 else:
1334 set_bm = ''
1335 return ('<tr class="rcdaybreak"><td colspan="%d">'
1336 '<strong>%s</strong>'
1337 '%s'
1338 '</td></tr>\n') % (6, d['date'], set_bm)
1339
1340 def recentchanges_header(self, d):
1341 """
1342 Assemble the recentchanges header (intro + open table)
1343
1344 @param d: parameter dictionary
1345 @rtype: string
1346 @return: recentchanges header html
1347 """
1348 _ = self.request.getText
1349
1350 # Should use user interface language and direction
1351 html = '<div class="recentchanges"%s>\n' % self.ui_lang_attr()
1352 html += '<div>\n'
1353 if self.shouldUseRSS():
1354 link = [
1355 u'<div class="rcrss">',
1356 self.request.formatter.url(1, self.rsshref()),
1357 self.request.formatter.rawHTML(self.make_icon("rss")),
1358 self.request.formatter.url(0),
1359 u'</div>',
1360 ]
1361 html += ''.join(link)
1362 html += '<p>'
1363 # Add day selector
1364 if d['rc_days']:
1365 days = []
1366 for day in d['rc_days']:
1367 if day == d['rc_max_days']:
1368 days.append('<strong>%d</strong>' % day)
1369 else:
1370 days.append(
1371 wikiutil.link_tag(self.request,
1372 '%s?max_days=%d' % (d['q_page_name'], day),
1373 str(day),
1374 self.request.formatter, rel='nofollow'))
1375 days = ' | '.join(days)
1376 html += (_("Show %s days.") % (days,))
1377
1378 if d['rc_update_bookmark']:
1379 html += " %(rc_update_bookmark)s %(rc_curr_bookmark)s" % d
1380
1381 html += '</p>\n</div>\n'
1382
1383 html += '<table>\n'
1384 return html
1385
1386 def recentchanges_footer(self, d):
1387 """
1388 Assemble the recentchanges footer (close table)
1389
1390 @param d: parameter dictionary
1391 @rtype: string
1392 @return: recentchanges footer html
1393 """
1394 _ = self.request.getText
1395 html = ''
1396 html += '</table>\n'
1397 if d['rc_msg']:
1398 html += "<br>%(rc_msg)s\n" % d
1399 html += '</div>\n'
1400 return html
1401
1402 # Language stuff ####################################################
1403
1404 def ui_lang_attr(self):
1405 """Generate language attributes for user interface elements
1406
1407 User interface elements use the user language (if any), kept in
1408 request.lang.
1409
1410 @rtype: string
1411 @return: lang and dir html attributes
1412 """
1413 lang = self.request.lang
1414 return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
1415
1416 def content_lang_attr(self):
1417 """Generate language attributes for wiki page content
1418
1419 Page content uses the page language or the wiki default language.
1420
1421 @rtype: string
1422 @return: lang and dir html attributes
1423 """
1424 lang = self.request.content_lang
1425 return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
1426
1427 # stuff from wikiutil.py
1428 def send_title(self, text, **keywords):
1429 """
1430 Output the page header (and title).
1431
1432 TODO: check all code that call us and add page keyword for the
1433 current page being rendered.
1434
1435 @param text: the title text
1436 @keyword msg: additional message (after saving)
1437 @keyword pagename: 'PageName'
1438 @keyword page: the page instance that called us.
1439 @keyword print_mode: 1 (or 0)
1440 @keyword editor_mode: 1 (or 0)
1441 @keyword media: css media type, defaults to 'screen'
1442 @keyword allow_doubleclick: 1 (or 0)
1443 @keyword html_head: additional <head> code
1444 @keyword body_attr: additional <body> attributes
1445 @keyword body_onload: additional "onload" JavaScript code
1446 """
1447 request = self.request
1448 _ = request.getText
1449 rev = request.rev
1450
1451 if keywords.has_key('page'):
1452 page = keywords['page']
1453 pagename = page.page_name
1454 else:
1455 pagename = keywords.get('pagename', '')
1456 page = Page(request, pagename)
1457
1458 scriptname = request.getScriptname()
1459 pagename_quoted = wikiutil.quoteWikinameURL(pagename)
1460
1461 # get name of system pages
1462 page_front_page = wikiutil.getFrontPage(request).page_name
1463 page_help_contents = wikiutil.getSysPage(request, 'HelpContents').page_name
1464 page_title_index = wikiutil.getSysPage(request, 'TitleIndex').page_name
1465 page_site_navigation = wikiutil.getSysPage(request, 'SiteNavigation').page_name
1466 page_word_index = wikiutil.getSysPage(request, 'WordIndex').page_name
1467 page_user_prefs = wikiutil.getSysPage(request, 'UserPreferences').page_name
1468 page_help_formatting = wikiutil.getSysPage(request, 'HelpOnFormatting').page_name
1469 page_find_page = wikiutil.getSysPage(request, 'FindPage').page_name
1470 home_page = wikiutil.getInterwikiHomePage(request) # XXX sorry theme API change!!! Either None or tuple (wikiname,pagename) now.
1471 page_parent_page = getattr(page.getParentPage(), 'page_name', None)
1472
1473 # Prepare the HTML <head> element
1474 user_head = [request.cfg.html_head]
1475
1476 # include charset information - needed for moin_dump or any other case
1477 # when reading the html without a web server
1478 user_head.append('''<meta http-equiv="Content-Type" content="%s;charset=%s">\n''' % (page.output_mimetype, page.output_charset))
1479
1480 meta_keywords = request.getPragma('keywords')
1481 meta_desc = request.getPragma('description')
1482 if meta_keywords:
1483 user_head.append('<meta name="keywords" content="%s">\n' % escape(meta_keywords, 1))
1484 if meta_desc:
1485 user_head.append('<meta name="description" content="%s">\n' % escape(meta_desc, 1))
1486
1487 # search engine precautions / optimization:
1488 # if it is an action or edit/search, send query headers (noindex,nofollow):
1489 if request.query_string:
1490 user_head.append(request.cfg.html_head_queries)
1491 elif request.request_method == 'POST':
1492 user_head.append(request.cfg.html_head_posts)
1493 # we don't want to have BadContent stuff indexed:
1494 elif pagename in ['BadContent', 'LocalBadContent', ]:
1495 user_head.append(request.cfg.html_head_posts)
1496 # if it is a special page, index it and follow the links - we do it
1497 # for the original, English pages as well as for (the possibly
1498 # modified) frontpage:
1499 elif pagename in [page_front_page, request.cfg.page_front_page,
1500 page_title_index, 'TitleIndex',
1501 page_find_page, 'FindPage',
1502 page_site_navigation, 'SiteNavigation',
1503 'RecentChanges', ]:
1504 user_head.append(request.cfg.html_head_index)
1505 # if it is a normal page, index it, but do not follow the links, because
1506 # there are a lot of illegal links (like actions) or duplicates:
1507 else:
1508 user_head.append(request.cfg.html_head_normal)
1509
1510 if keywords.has_key('pi_refresh') and keywords['pi_refresh']:
1511 user_head.append('<meta http-equiv="refresh" content="%(delay)d;URL=%(url)s">' % keywords['pi_refresh'])
1512
1513 # output buffering increases latency but increases throughput as well
1514 output = []
1515 # later: <html xmlns=\"http://www.w3.org/1999/xhtml\">
1516 output.append("""\
1517 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
1518 <html>
1519 <head>
1520 %s
1521 %s
1522 %s
1523 """ % (
1524 ''.join(user_head),
1525 self.html_head({
1526 'page': page,
1527 'title': wikiutil.escape(text),
1528 'sitename': wikiutil.escape(request.cfg.html_pagetitle or request.cfg.sitename),
1529 'print_mode': keywords.get('print_mode', False),
1530 'media': keywords.get('media', 'screen'),
1531 }),
1532 keywords.get('html_head', ''),
1533 ))
1534
1535 # Links
1536 output.append('<link rel="Start" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_front_page)))
1537 if pagename:
1538 output.append('<link rel="Alternate" title="%s" href="%s/%s?action=raw">\n' % (
1539 _('Wiki Markup'), scriptname, pagename_quoted,))
1540 output.append('<link rel="Alternate" media="print" title="%s" href="%s/%s?action=print">\n' % (
1541 _('Print View'), scriptname, pagename_quoted,))
1542
1543 # !!! currently disabled due to Mozilla link prefetching, see
1544 # http://www.mozilla.org/projects/netlib/Link_Prefetching_FAQ.html
1545 #~ all_pages = request.getPageList()
1546 #~ if all_pages:
1547 #~ try:
1548 #~ pos = all_pages.index(pagename)
1549 #~ except ValueError:
1550 #~ # this shopuld never happend in theory, but let's be sure
1551 #~ pass
1552 #~ else:
1553 #~ request.write('<link rel="First" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[0]))
1554 #~ if pos > 0:
1555 #~ request.write('<link rel="Previous" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos-1])))
1556 #~ if pos+1 < len(all_pages):
1557 #~ request.write('<link rel="Next" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos+1])))
1558 #~ request.write('<link rel="Last" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[-1])))
1559
1560 if page_parent_page:
1561 output.append('<link rel="Up" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_parent_page)))
1562
1563 # write buffer because we call AttachFile
1564 request.write(''.join(output))
1565 output = []
1566
1567 if pagename:
1568 from MoinMoin.action import AttachFile
1569 AttachFile.send_link_rel(request, pagename)
1570
1571 output.extend([
1572 '<link rel="Search" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_find_page)),
1573 '<link rel="Index" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_title_index)),
1574 '<link rel="Glossary" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_word_index)),
1575 '<link rel="Help" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_help_formatting)),
1576 ])
1577
1578 output.append("</head>\n")
1579 request.write(''.join(output))
1580 output = []
1581 request.flush()
1582
1583 # start the <body>
1584 bodyattr = []
1585 if keywords.has_key('body_attr'):
1586 bodyattr.append(' ')
1587 bodyattr.append(keywords['body_attr'])
1588
1589 # Add doubleclick edit action
1590 if (pagename and keywords.get('allow_doubleclick', 0) and
1591 not keywords.get('print_mode', 0) and
1592 request.user.edit_on_doubleclick):
1593 if request.user.may.write(pagename): # separating this gains speed
1594 querystr = wikiutil.escape(wikiutil.makeQueryString({'action': 'edit'}))
1595 # TODO: remove escape=0 in 2.0
1596 url = page.url(request, querystr, escape=0, relative=False)
1597 bodyattr.append(''' ondblclick="location.href='%s'" ''' % url)
1598
1599 # Set body to the user interface language and direction
1600 bodyattr.append(' %s' % self.ui_lang_attr())
1601
1602 body_onload = keywords.get('body_onload', '')
1603 if body_onload:
1604 bodyattr.append(''' onload="%s"''' % body_onload)
1605 output.append('\n<body%s>\n' % ''.join(bodyattr))
1606
1607 # Output -----------------------------------------------------------
1608
1609 # If in print mode, start page div and emit the title
1610 if keywords.get('print_mode', 0):
1611 d = {
1612 'title_text': text,
1613 'page': page,
1614 'page_name': pagename or '',
1615 'rev': rev,
1616 }
1617 request.themedict = d
1618 output.append(self.startPage())
1619 output.append(self.interwiki(d))
1620 output.append(self.title(d))
1621
1622 # In standard mode, emit theme.header
1623 else:
1624 # prepare dict for theme code:
1625 d = {
1626 'theme': self.name,
1627 'script_name': scriptname,
1628 'title_text': text,
1629 'logo_string': request.cfg.logo_string,
1630 'site_name': request.cfg.sitename,
1631 'page': page,
1632 'rev': rev,
1633 'pagesize': pagename and page.size() or 0,
1634 'last_edit_info': pagename and page.lastEditInfo() or '',
1635 'page_name': pagename or '',
1636 'page_find_page': page_find_page,
1637 'page_front_page': page_front_page,
1638 'home_page': home_page,
1639 'page_help_contents': page_help_contents,
1640 'page_help_formatting': page_help_formatting,
1641 'page_parent_page': page_parent_page,
1642 'page_title_index': page_title_index,
1643 'page_word_index': page_word_index,
1644 'page_user_prefs': page_user_prefs,
1645 'user_name': request.user.name,
1646 'user_valid': request.user.valid,
1647 'user_prefs': (page_user_prefs, request.user.name)[request.user.valid],
1648 'msg': keywords.get('msg', ''),
1649 'trail': keywords.get('trail', None),
1650 # Discontinued keys, keep for a while for 3rd party theme developers
1651 'titlesearch': 'use self.searchform(d)',
1652 'textsearch': 'use self.searchform(d)',
1653 'navibar': ['use self.navibar(d)'],
1654 'available_actions': ['use self.request.availableActions(page)'],
1655 }
1656
1657 # add quoted versions of pagenames
1658 newdict = {}
1659 for key in d:
1660 if key.startswith('page_'):
1661 if not d[key] is None:
1662 newdict['q_'+key] = wikiutil.quoteWikinameURL(d[key])
1663 else:
1664 newdict['q_'+key] = None
1665 d.update(newdict)
1666 request.themedict = d
1667
1668 # now call the theming code to do the rendering
1669 if keywords.get('editor_mode', 0):
1670 output.append(self.editorheader(d))
1671 else:
1672 output.append(self.header(d))
1673
1674 # emit it
1675 request.write(''.join(output))
1676 output = []
1677 request.flush()
1678
1679 def send_footer(self, pagename, **keywords):
1680 """
1681 Output the page footer.
1682
1683 @param pagename: WikiName of the page
1684 @keyword print_mode: true, when page is displayed in Print mode
1685 """
1686 request = self.request
1687 d = request.themedict
1688
1689 # Emit end of page in print mode, or complete footer in standard mode
1690 if keywords.get('print_mode', 0):
1691 request.write(self.pageinfo(d['page']))
1692 request.write(self.endPage())
1693 else:
1694 request.write(self.footer(d, **keywords))
1695
1696 # stuff moved from request.py
1697 def send_closing_html(self):
1698 """ generate timing info html and closing html tag,
1699 everyone calling send_title must call this at the end to close
1700 the body and html tags.
1701 """
1702 request = self.request
1703
1704 # as this is the last chance to emit some html, we stop the clocks:
1705 request.clock.stop('run')
1706 request.clock.stop('total')
1707
1708 # Close html code
1709 if request.cfg.show_timings and request.action != 'print':
1710 request.write('<ul id="timings">\n')
1711 for t in request.clock.dump():
1712 request.write('<li>%s</li>\n' % t)
1713 request.write('</ul>\n')
1714 #request.write('<!-- auth_method == %s -->' % repr(request.user.auth_method))
1715 request.write('</body>\n</html>\n\n')
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.