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>&nbsp;%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 = '&nbsp; %(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.
  • [get | view] (2006-12-29 15:02:33, 14.7 KB) [[attachment:AttachTable.py]]
  • [get | view] (2009-08-11 07:04:46, 53.6 KB) [[attachment:TaskPlanner-1.8.4.py]]
  • [get | view] (2010-01-23 14:17:02, 52.7 KB) [[attachment:TaskPlanner-1.9.1.py]]
  • [get | view] (2007-04-14 15:35:39, 49.1 KB) [[attachment:TaskPlanner.py]]
  • [get | view] (2007-02-01 07:00:31, 243.0 KB) [[attachment:TaskPlanner.zip]]
  • [get | view] (2009-08-11 07:05:54, 56.5 KB) [[attachment:TaskPlanner1.8.4plusEmail.py]]
  • [get | view] (2006-12-29 19:46:46, 146.4 KB) [[attachment:TaskPlannerAttachTableIcons.zip]]
  • [get | view] (2007-08-24 20:11:02, 244.6 KB) [[attachment:TaskPlanner_LlubNek.zip]]
  • [get | view] (2007-04-02 14:42:44, 15.3 KB) [[attachment:TaskTable.py]]
  • [get | view] (2006-12-29 19:45:58, 66.9 KB) [[attachment:__init__.py]]
  • [get | view] (2006-12-29 19:45:29, 5.0 KB) [[attachment:_init_.diff]]
  • [get | view] (2006-12-26 16:49:56, 63.8 KB) [[attachment:addtask.jpg]]
  • [get | view] (2007-01-01 22:27:54, 3.8 KB) [[attachment:classic_theme.jpg]]
  • [get | view] (2008-05-07 16:42:25, 201.5 KB) [[attachment:clickingerror.jpg]]
  • [get | view] (2006-12-26 16:55:30, 9.7 KB) [[attachment:common.css]]
  • [get | view] (2006-12-26 16:55:03, 1.1 KB) [[attachment:common.diff]]
  • [get | view] (2007-01-01 22:27:36, 13.8 KB) [[attachment:common_icons.jpg]]
  • [get | view] (2006-12-26 16:53:07, 3.1 KB) [[attachment:de.AttachTable.po]]
  • [get | view] (2006-12-26 16:52:44, 7.4 KB) [[attachment:de.TaskPlanner.po]]
  • [get | view] (2006-12-26 16:50:48, 72.5 KB) [[attachment:imagepreview.jpg]]
  • [get | view] (2006-12-26 16:50:21, 78.2 KB) [[attachment:tasktable.jpg]]
  • [get | view] (2008-05-07 16:42:29, 8.4 KB) [[attachment:traceback_taskplanner.html]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.