Attachment 'text_html.py'

Download

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - "text/html+css" Formatter
   4 
   5     @copyright: 2000 - 2004 by Jürgen Hermann <jh@web.de>
   6     @license: GNU GPL, see COPYING for details.
   7 """
   8 
   9 from MoinMoin.formatter.base import FormatterBase
  10 from MoinMoin import wikiutil, i18n, config
  11 from MoinMoin.Page import Page
  12 
  13 class Formatter(FormatterBase):
  14     """
  15         Send HTML data.
  16     """
  17 
  18     hardspace = '&nbsp;'
  19 
  20     def __init__(self, request, **kw):
  21         apply(FormatterBase.__init__, (self, request), kw)
  22 
  23         # inline tags stack. When an inline tag is called, it goes into
  24         # the stack. When a block elemetns starts, all inline tags in
  25         # the stack are closed.
  26         self._inlineStack = []
  27 
  28         self._in_li = 0
  29         self._in_code = 0
  30         self._in_code_area = 0
  31         self._in_code_line = 0
  32         self._code_area_num = 0
  33         self._code_area_state = ['', 0, -1, -1, 0]
  34         self._show_section_numbers = None
  35         self._content_ids = []
  36         self.pagelink_preclosed = False
  37         self._is_included = kw.get('is_included',False)
  38         self.request = request
  39         self.cfg = request.cfg
  40 
  41         if not hasattr(request, '_fmt_hd_counters'):
  42             request._fmt_hd_counters = []
  43 
  44     def langAttr(self, lang=None):
  45         """ Return lang and dir attribute
  46 
  47         Must be used on all block elements - div, p, table, etc.
  48         @param lang: if defined, will return attributes for lang. if not
  49             defined, will return attributes only if the current lang is
  50             differnt from the content lang.
  51         @rtype: dict
  52         @retrun: language attributes
  53         """
  54         if not lang:
  55             lang = self.request.current_lang
  56             # Actions that generate content in user language should change
  57             # the content lang from the default defined in cfg.
  58             if lang == self.request.content_lang:
  59                 # lang is inherited from content div
  60                 return {}
  61 
  62         attr = {'lang': lang, 'dir': i18n.getDirection(lang),}
  63         return attr
  64         
  65 
  66     def _langAttr(self, lang=None):
  67         """ Return lang and dir attribute
  68 
  69         DONT USE, use langAttr instead
  70 
  71         TODO: update all clients that use this to use langAttr and
  72         delete this!
  73 
  74         Must be used on all block elements - div, p, table, etc.
  75         @param lang: if defined, will return attributes for lang. if not
  76             defined, will return attributes only if the current lang is
  77             differnt from the content lang.
  78         @rtype: string
  79         @retrun: language attributes
  80         """
  81         if not lang:
  82             lang = self.request.current_lang
  83             # Actions that generate content in user language should change
  84             # the content lang from the default defined in cfg.
  85             if lang == self.request.content_lang:
  86                 # lang is inherited from content div
  87                 return ''
  88         result = ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
  89         return result
  90 
  91     # Primitive formater functions #####################################
  92 
  93     # all other methods should use these to format tags. This keeps the
  94     # code clean and handle pathological cases like unclosed p and
  95     # inline tages.
  96 
  97     def formatAttributes(self, attr=None):
  98         """ Return formated atributes string
  99 
 100         @param attr: dict containing keys and values
 101         @rtype: string ?
 102         @return: formated attribtues or empty string
 103         """
 104         if attr:
 105             attr = [' %s="%s"' % (k, v) for k, v in attr.items()]           
 106             return ''.join(attr)
 107         return ''
 108     
 109     def openInlineTag(self, tag, attr=None):
 110         """ Open a tag with optional attribues
 111         
 112         @param tag: html tag, string
 113         @parm attr: dict with tag attribues
 114         @rtype: string ?
 115         @return: open tag with attributes
 116         """
 117         # Add to inlineStack
 118         self._inlineStack.append(tag)
 119         # Format
 120         return '<%s%s>' % (tag, self.formatAttributes(attr))
 121 
 122     def closeInlineTag(self, tag):
 123         """ Close inline tag, remove from stack
 124 
 125         @param tag: html tag, string
 126         @rtype: string ?
 127         @return: closing tag
 128         """
 129         # Pull from stack, ignore order, that not our problem. The code
 130         # that calls us should keep correct calling order.
 131         tagindex = self._inlineStack.index(tag)
 132         if tagindex != -1:
 133             del self._inlineStack[tagindex]
 134         return '</%s>' % tag
 135 
 136     def openBlockTag(self, tag, newline=False, attr=None):
 137         """ Open a tag with optional attribues
 138         
 139         @param tag: html tag, string
 140         @param newline: render tag on a separate line
 141         @parm attr: dict with tag attribues
 142         @rtype: string ?
 143         @return: open tag with attributes
 144         """
 145         result = []
 146         # Close p tags, our parser like to wrap everyting in p
 147         if self.in_p:
 148             self.in_p = 0
 149             result.append(self.paragraph(False))
 150 
 151         # Format with attribtues and newliine
 152         if newline:
 153             result.append('\n')
 154         attr = self.formatAttributes(attr)
 155         result.append('<%s%s>' % (tag, attr))
 156         return ''.join(result)
 157 
 158     def closeBlockTag(self, tag, newline=False):
 159         """ Close inline tag, remove from stack
 160 
 161         @param tag: html tag, string
 162         @param newline: render tag on a new line
 163         @rtype: string ?
 164         @return: closing tag
 165         """
 166         # Close all tags in inline stack
 167         # Work on a copy, because closeInlineTag manipulate the stack
 168         result = []
 169         stack = self.inlineStack[:]
 170         stack.reverse()
 171         for tag in stack:
 172             result.append(self.closeInlineTag(tag))
 173         # Format with newline
 174         if newline:
 175             result.append('\n')
 176         result.append('</%s>\n' % (tag))
 177         return ''.join(result)
 178 
 179     # Public methods ###################################################
 180 
 181     def startContent(self, content_id='content', **kwargs):
 182         """ Start content div """        
 183         if content_id!='content':
 184             aid = 'top_%s' % (content_id,)
 185         else:
 186             aid = 'top'
 187         self._content_ids.append(content_id)
 188         return '<div id="%(id)s" %(lang_attr)s>\n%(anchor)s\n' % {
 189             'id': content_id,
 190             'lang_attr': self._langAttr(self.request.content_lang),
 191             'anchor': self.anchordef(aid)
 192             }
 193 
 194     def endContent(self):
 195         try:
 196             cid = self._content_ids.pop()
 197         except:
 198             cid = 'content'
 199         if cid!='content':
 200             aid = 'bottom_%s' % (cid,)
 201         else:
 202             aid = 'bottom'
 203         return '%s\n</div>\n' % self.anchordef(aid)
 204 
 205     def lang(self, on, lang_name):
 206         """ Insert text with specific lang and direction.
 207         
 208             Enclose within span tag if lang_name is different from
 209             the current lang    
 210         """
 211         if lang_name != self.request.current_lang:
 212             dir = i18n.getDirection(lang_name)
 213             return ['<span lang="%(lang_name)s" dir="%(dir)s">' % {
 214                 'lang_name': lang_name, 'dir': dir},
 215                     '</span>'] [not on]
 216         
 217         return ''            
 218                 
 219     def sysmsg(self, on, **kw):
 220         return ['%s<div class="message">' % self.existParagraph(), '</div>\n'][not on]
 221 
 222     
 223     # Links ##############################################################
 224     
 225     def pagelink(self, on, pagename='', **kw):
 226         """ Link to a page.
 227 
 228             See wikiutil.link_tag() for possible keyword parameters.
 229         """
 230         apply(FormatterBase.pagelink, (self, on, pagename), kw)
 231         page = Page(self.request, pagename, formatter=self);
 232         
 233         if self.request.user.show_nonexist_qm and on and not page.exists():
 234             self.pagelink_preclosed = True
 235             return (page.link_to(self.request, on=1, **kw) +
 236                     self.text("?") +
 237                     page.link_to(self.request, on=0, **kw))
 238         elif not on and self.pagelink_preclosed:
 239             self.pagelink_preclosed = False
 240             return ""
 241         else:
 242             return page.link_to(self.request, on=on, **kw)
 243 
 244     def interwikilink(self, on, interwiki='', pagename='', **kw):
 245         if not on: return '</a>'
 246         
 247         wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, '%s:%s' % (interwiki, pagename))
 248         wikiurl = wikiutil.mapURL(self.request, wikiurl)
 249         href = wikiutil.join_wiki(wikiurl, wikitail)
 250 
 251         # return InterWiki hyperlink
 252         if wikitag_bad:
 253             html_class = 'badinterwiki'
 254         else:
 255             html_class = 'interwiki'
 256 
 257         icon = ''
 258         if self.request.user.show_fancy_links:
 259             icon = self.request.theme.make_icon('interwiki', {'wikitag': wikitag}) 
 260         return (self.url(1, href, title=wikitag, unescaped=1,
 261                         pretty_url=kw.get('pretty_url', 0), css = html_class) +
 262                 icon)
 263 
 264     def url(self, on, url=None, css=None, **kw):
 265         """
 266             Keyword params:
 267                 title - title attribute
 268                 ... some more (!!! TODO) 
 269         """
 270         url = wikiutil.mapURL(self.request, url)
 271         pretty = kw.get('pretty_url', 0)
 272         title = kw.get('title', None)
 273 
 274         #if not pretty and wikiutil.isPicture(url):
 275         #    # XXX
 276         #    return '<img src="%s" alt="%s">' % (url,url)
 277 
 278         # create link
 279         if not on:
 280             return '</a>'
 281         str = '<a'
 282         if css: str = '%s class="%s"' % (str, css)
 283         if title: str = '%s title="%s"' % (str, title)
 284         str = '%s href="%s">' % (str, wikiutil.escape(url, 1))
 285 
 286         type = kw.get('type', '')
 287 
 288         if type=='www':
 289             str = '%s%s ' % (str, self.icon("www"))
 290         elif type=='mailto':
 291             str = '%s%s ' % (str, self.icon('mailto'))
 292 
 293         return str
 294 
 295     def anchordef(self, id):
 296         return '<a id="%s"></a>' % (id, )
 297 
 298     def anchorlink(self, on, name='', id = None):
 299         extra = ''
 300         if id:
 301             extra = ' id="%s"' % id
 302         return ['<a href="#%s"%s>' % (name, extra), '</a>'][not on]
 303 
 304     # Text and Text Attributes ###########################################
 305     
 306     def _text(self, text):
 307         if self._in_code:
 308             return wikiutil.escape(text).replace(' ', self.hardspace)
 309         return wikiutil.escape(text)
 310 
 311     # Inline ###########################################################
 312         
 313     def strong(self, on):
 314         tag = 'strong'
 315         if on:
 316             return self.openInlineTag(tag)
 317         return self.closeInlineTag(tag)
 318 
 319     def emphasis(self, on):
 320         tag = 'em'
 321         if on:
 322             return self.openInlineTag(tag)
 323         return self.closeInlineTag(tag)
 324 
 325     def underline(self, on):
 326         tag = 'u'
 327         if on:
 328             return self.openInlineTag(tag)
 329         return self.closeInlineTag(tag)
 330 
 331     def highlight(self, on):
 332         tag = 'strong'
 333         if on:
 334             return self.openInlineTag(tag, attr={'class': 'highlight'})
 335         return self.closeInlineTag(tag)
 336 
 337     def sup(self, on):
 338         tag = 'sup'
 339         if on:
 340             return self.openInlineTag(tag)
 341         return self.closeInlineTag(tag)
 342 
 343     def sub(self, on):
 344         tag = 'sub'
 345         if on:
 346             return self.openInlineTag(tag)
 347         return self.closeInlineTag(tag)
 348 
 349     def code(self, on):
 350         tag = 'tt'
 351 
 352         # Maybe we don't need this, becuse whe have tt will be in inlineStack.
 353         self._in_code = on
 354         
 355         if on:
 356             return self.openInlineTag(tag)
 357         return self.closeInlineTag(tag)
 358         
 359     def small(self, on):
 360         tag = 'small'
 361         if on:
 362             return self.openInlineTag(tag)
 363         return self.closeInlineTag(tag)
 364                                                                                 
 365     def big(self, on):
 366         tag = 'big'
 367         if on:
 368             return self.openInlineTag(tag)
 369         return self.closeInlineTag(tag)
 370 
 371 
 372     # Block elements ###################################################
 373 
 374     def preformatted(self, on):
 375         FormatterBase.preformatted(self, on)
 376         tag = 'pre'
 377         if on:
 378             return self.openBlockTag(tag)
 379         return self.closeBlockTag(tag)
 380                 
 381     def code_area(self, on, code_id, code_type='code', show=0, start=-1, step=-1):
 382         res = ''
 383         ci = self.request.makeUniqueID('CA-%s_%03d' % (code_id, self._code_area_num))
 384         if on:
 385             self._in_code_area = 1
 386             self._in_code_line = 0
 387             self._code_area_state = [ci, show, start, step, start]
 388             if self._code_area_num == 0 and self._code_area_state[1] >= 0:
 389                 res += """<script language='JavaScript'>
 390 function isnumbered(obj){
 391   return obj.childNodes.length && obj.firstChild.childNodes.length && obj.firstChild.firstChild.className == 'LineNumber';
 392 }
 393 function nformat(num,chrs,add){
 394   var nlen = Math.max(0,chrs-(''+num).length), res = '';
 395   while (nlen>0) { res += ' '; nlen-- }
 396   return res+num+add;
 397 }
 398 function addnumber(did,nstart,nstep){
 399   var c = document.getElementById(did), l = c.firstChild, n = 1;
 400   if (!isnumbered(c))
 401     if (typeof nstart == 'undefined') nstart = 1;
 402     if (typeof nstep  == 'undefined') nstep = 1;
 403     n = nstart;
 404     while (l != null){
 405       if (l.tagName == 'SPAN'){
 406         var s = document.createElement('SPAN');
 407         s.className = 'LineNumber'
 408         s.appendChild(document.createTextNode(nformat(n,4,' ')));
 409         n += nstep;
 410         if (l.childNodes.length)
 411           l.insertBefore(s, l.firstChild)
 412         else
 413           l.appendChild(s)
 414       }
 415       l = l.nextSibling;
 416     }
 417   return false;
 418 }
 419 function remnumber(did){
 420   var c = document.getElementById(did), l = c.firstChild;
 421   if (isnumbered(c))
 422     while (l != null){
 423       if (l.tagName == 'SPAN' && l.firstChild.className == 'LineNumber') l.removeChild(l.firstChild);
 424       l = l.nextSibling;
 425     }
 426   return false;
 427 }
 428 function togglenumber(did,nstart,nstep){
 429   var c = document.getElementById(did);
 430   if (isnumbered(c)) {
 431     remnumber(did);
 432   } else {
 433     addnumber(did,nstart,nstep);
 434   }
 435   return false;
 436 }
 437 </script>
 438 """
 439             res += self.openBlockTag('div', attr={'class': 'codearea'})
 440 ##             res += self.existParagraph() + '<div class="codearea">\n'
 441             if self._code_area_state[1] >= 0:
 442                 res += '<script>document.write(\'<a href="#" onClick="return togglenumber(\\\'%s\\\',%d,%d);" class="codenumbers">1,2,3</a>\')</script>' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])
 443             res += '<pre id="%s" class="codearea">' % (self._code_area_state[0], )
 444         else:
 445             res = ''
 446             if self._in_code_line:
 447                 res += self.code_line(0)
 448             res += '</pre>'
 449             if self._code_area_state[1] >= 0:
 450                 res += '<script>document.write(\'<a href="#" onClick="return togglenumber(\\\'%s\\\',%d,%d);" class="codenumbers">1,2,3</a>\')</script>' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])
 451 
 452             # This will close all user left open inline tags
 453             res += self.closeBlockTag('div')
 454 
 455             self._in_code_area = 0
 456             self._code_area_num += 1
 457         return res
 458 
 459     def code_line(self, on):
 460         res = ''
 461         if not on or (on and self._in_code_line):
 462             res += '</span>\n'
 463         if on:
 464             res += '<span class="line">'
 465             if self._code_area_state[1] > 0:
 466                 res += '<span class="LineNumber">%4d </span>' % (self._code_area_state[4], )
 467                 self._code_area_state[4] += self._code_area_state[3]
 468         self._in_code_line = on != 0
 469         return res
 470 
 471     def code_token(self, on, tok_type):
 472         return ['<span class="%s">' % tok_type, '</span>'][not on]
 473 
 474     # Paragraphs, Lines, Rules ###########################################
 475     
 476     def linebreak(self, preformatted=1):
 477         if self._in_code_area:
 478             preformatted = 1
 479         return ['\n', '<br>\n'][not preformatted]
 480         
 481     def paragraph(self, on):
 482         if self._terse:
 483             return ''
 484         FormatterBase.paragraph(self, on)
 485         if self._in_li:
 486             self._in_li = self._in_li + 1
 487         tag = 'p'
 488         if on:
 489             return self.openBlockTag(tag, attr=self.langAttr())
 490         return self.closeBlockTag(tag)
 491             
 492     def rule(self, size=0):
 493         if size:
 494             return self.openBlockTag('hr', attr={'size': size})
 495         return self.openBlockTag('hr')
 496                 
 497     def icon(self, type):
 498         return self.request.theme.make_icon(type)
 499 
 500     def smiley(self, text):
 501         w, h, b, img = config.smileys[text.strip()]
 502         href = img
 503         if not href.startswith('/'):
 504             href = self.request.theme.img_url(img)
 505         return self.image(src=href, alt=text, width=str(w), height=str(h))
 506 
 507     # Lists ##############################################################
 508 
 509     def number_list(self, on, type=None, start=None):
 510         tag = 'ol'
 511         if on:
 512             attr = self.langAttr()
 513             if type is not None:
 514                 attr['type'] = type
 515             if start is not None:
 516                 attr['start'] = start
 517             return self.openBlockTag(tag, attr=attr)
 518         return self.closeBlockTag(tag)
 519     
 520     def bullet_list(self, on):
 521         tag = 'ul'
 522         if on:
 523             attr = self.langAttr()
 524             return self.openBlockTag(tag, attr=attr)
 525         return self.closeBlockTag(tag)
 526            
 527     def listitem(self, on, **kw):
 528         """ List item inherit its lang from the list. """
 529         tag = 'li'
 530         self._in_li = on != 0
 531         if on:
 532             css_class = kw.get('css_class', None)
 533             attrs = ''
 534             if css_class: attrs += ' class="%s"' % (css_class,)
 535             style = kw.get('style', None)
 536             if style:  attrs += ' style="%s"' % style
 537             result = '<li%s>' % (attrs,)
 538         else:
 539             result = '</li>\n'
 540         return result
 541 
 542     def definition_list(self, on):
 543         result = ['%s<dl>' % self.exitsParagraph(), '</dl>\n'][not on]
 544         return result
 545 
 546     def definition_term(self, on):
 547         return ['<dt%s>' % (self._langAttr()), '</dt>\n'][not on]
 548 
 549     def definition_desc(self, on):
 550         return ['<dd%s>' % self._langAttr(), '</dd>\n'][not on]
 551 
 552     def heading(self, on, depth, id = None, **kw):
 553         # remember depth of first heading, and adapt counting depth accordingly
 554         if not self._base_depth:
 555             self._base_depth = depth
 556 
 557         count_depth = max(depth - (self._base_depth - 1), 1)
 558 
 559         # check numbering, possibly changing the default
 560         if self._show_section_numbers is None:
 561             self._show_section_numbers = self.cfg.show_section_numbers
 562             numbering = self.request.getPragma('section-numbers', '').lower()
 563             if numbering in ['0', 'off']:
 564                 self._show_section_numbers = 0
 565             elif numbering in ['1', 'on']:
 566                 self._show_section_numbers = 1
 567             elif numbering in ['2', '3', '4', '5', '6']:
 568                 # explicit base level for section number display
 569                 self._show_section_numbers = int(numbering)
 570 
 571         heading_depth = depth + 1
 572 
 573         # closing tag
 574         if not on:
 575             return '</h%d>\n' % heading_depth
 576 
 577 
 578         # create section number
 579         number = ''
 580         if self._show_section_numbers:
 581             # count headings on all levels
 582             self.request._fmt_hd_counters = self.request._fmt_hd_counters[:count_depth]
 583             while len(self.request._fmt_hd_counters) < count_depth:
 584                 self.request._fmt_hd_counters.append(0)
 585             self.request._fmt_hd_counters[-1] = self.request._fmt_hd_counters[-1] + 1
 586             number = '.'.join(map(str, self.request._fmt_hd_counters[self._show_section_numbers-1:]))
 587             if number: number += ". "
 588 
 589         id_text = ''
 590         if id:
 591           id_text = ' id="%s"' % id
 592 
 593         result = '<h%d%s>' % (heading_depth, id_text)
 594         if self.request.user.show_topbottom:
 595             # TODO change top/bottom refs to content-specific top/bottom refs?
 596             result = ("%s%s%s%s%s%s%s%s" %
 597                       (result,
 598                        kw.get('icons',''),
 599                        self.url(1, "#bottom", unescaped=1),
 600                        self.icon('bottom'),
 601                        self.url(0),
 602                        self.url(1, "#top", unescaped=1),
 603                        self.icon('top'),
 604                        self.url(0)))
 605         return "%s%s%s" % (result, kw.get('icons',''), number)
 606 
 607     
 608     # Tables #############################################################
 609 
 610     # TODO: find better solution for bgcolor, align, valign (deprecated in html4)
 611     # do not remove current code before making working compliant code
 612 
 613     _allowed_table_attrs = {
 614         'table': ['class', 'width', 'bgcolor'],
 615         'row': ['class', 'width', 'align', 'valign', 'bgcolor'],
 616         '': ['colspan', 'rowspan', 'class', 'width', 'align', 'valign', 'bgcolor'],
 617     }
 618 
 619     def _checkTableAttr(self, attrs, prefix):
 620         if not attrs: return ''
 621 
 622         result = ''
 623         for key, val in attrs.items():
 624             if prefix and key[:len(prefix)] != prefix: continue
 625             key = key[len(prefix):]
 626             if key not in self._allowed_table_attrs[prefix]: continue
 627             result = '%s %s=%s' % (result, key, val)
 628         return result
 629 
 630     def table(self, on, attrs={}):
 631         if on:
 632             # Enclose table inside a div to get correct alignment
 633             # when using language macros
 634             attrs = attrs and attrs.copy() or {}
 635             result = '%(endpara)s<div%(lang)s>\n<table%(tableAttr)s>' % {
 636                 'endpara': self.exitsParagraph(),
 637                 'lang': self._langAttr(),
 638                 'tableAttr': self._checkTableAttr(attrs, 'table')
 639             }
 640         else:
 641             result = '</table>\n</div>'
 642         return '%s\n' % result
 643     
 644     def table_row(self, on, attrs={}):
 645         if on:
 646             result = '<tr%s>' % self._checkTableAttr(attrs, 'row')
 647         else:
 648             result = '</tr>'
 649         return '%s\n' % result
 650 
 651     def table_cell(self, on, attrs={}):
 652         if on:
 653             result = '<td%s>' % self._checkTableAttr(attrs, '')
 654         else:
 655             result = '</td>'
 656         return '%s\n' % result
 657 
 658     def escapedText(self, text):
 659         return wikiutil.escape(text)

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] (2007-07-04 15:29:57, 7.7 KB) [[attachment:Slideshow.py]]
  • [get | view] (2004-10-20 05:28:16, 72.8 KB) [[attachment:alum.jpg]]
  • [get | view] (2005-04-14 19:56:30, 68.2 KB) [[attachment:farm.tbz]]
  • [get | view] (2005-05-28 17:05:11, 31.2 KB) [[attachment:me-with-naomi.jpg]]
  • [get | view] (2005-04-14 19:55:22, 8.6 KB) [[attachment:multiconfig.patch]]
  • [get | view] (2005-01-16 05:36:39, 41.4 KB) [[attachment:page-state.patch]]
  • [get | view] (2004-12-28 02:41:46, 36.7 KB) [[attachment:plugin.patch]]
  • [get | view] (2004-10-21 18:49:52, 22.1 KB) [[attachment:text_html.py]]
  • [get | view] (2005-04-18 07:20:14, 1.4 KB) [[attachment:wiki_icons.png]]
 All files | Selected Files: delete move to page copy to page

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