Edgewall Software

Ticket #7228: 7228-download-source-r9975.patch

File 7228-download-source-r9975.patch, 32.8 KB (added by rblank, 22 months ago)

Works with IE7, download link next to size.

  • trac/attachment.py

    diff --git a/trac/attachment.py b/trac/attachment.py
    a b  
    809809                                 title=get_resource_name(self.env, attachment)) 
    810810                href = get_resource_url(self.env, attachment, formatter.href) 
    811811                title = get_resource_name(self.env, attachment) 
    812                 img = tag.img(src=formatter.href.chrome('common/download.png'), 
    813                               alt=_("Download")) 
    814812                return tag(tag.a(label, class_='attachment', title=title, 
    815813                                 href=href + params), 
    816                            tag.span(" ", 
    817                                     tag.a(img, class_='trac-rawlink', 
    818                                           href=raw_href + params, 
    819                                           title=_("Download")), 
    820                                     class_="noprint")) 
     814                           tag.a(u'\u200b', class_='trac-rawlink', 
     815                                 href=raw_href + params, title=_("Download"))) 
    821816            except ResourceNotFound: 
    822817                pass 
    823818            # FIXME: should be either: 
  • trac/htdocs/css/browser.css

    diff --git a/trac/htdocs/css/browser.css b/trac/htdocs/css/browser.css
    a b  
    6363 vertical-align: middle; 
    6464 font-size: 70%; 
    6565} 
     66table.dirlist td.size a.trac-rawlink { 
     67 margin-left: .3em; 
     68} 
    6669table.dirlist td.age { 
    6770 border-width: 0 2px 0 0; 
    6871 border-style: solid; 
    6972 font-size: 85%; 
    7073} 
    71 table.dirlist td.name { width: 100% } 
    72 table.dirlist td.name a, table.dirlist td.name span { 
    73  background-position: 0% 50%; 
    74  background-repeat: no-repeat; 
    75  padding-left: 20px; 
     74table.dirlist td.name { width: 100%; white-space: nowrap } 
     75table.dirlist td.name a.parent { 
     76  background: url(../parent.png) 0 50% no-repeat; 
     77  padding-left: 20px; 
    7678} 
    77 table.dirlist td.name a.parent { background-image: url(../parent.png) } 
    78 table.dirlist td.name div { white-space: pre } 
    7979table.dirlist tr span.expander {  
    80   background-image: url(../expander_normal.png);  
     80  background: url(../expander_normal.png) 0 50% no-repeat;  
    8181  cursor: pointer;  
    8282  padding-left: 8px;  
    8383  margin-left: 4px;  
    8484} 
    8585table.dirlist tr span.expander:hover {  
    86   background-image: url(../expander_normal_hover.png);  
     86  background: url(../expander_normal_hover.png) 0 50% no-repeat; 
    8787} 
    8888table.dirlist tr.expanded span.expander {  
    89   background-image: url(../expander_open.png);  
     89  background: url(../expander_open.png) 0 50% no-repeat;  
    9090  padding-left: 12px;  
    9191  margin-left: 0;  
    9292} 
    9393table.dirlist tr.expanded span.expander:hover {  
    94   background-image: url(../expander_open_hover.png);  
     94  background: url(../expander_open_hover.png) 0 50% no-repeat;  
    9595} 
    96 table.dirlist td.name a.dir { background-image: url(../folder.png) } 
    97 table.dirlist td.name a.file { background-image: url(../file.png); display: block } 
     96table.dirlist td.name a.dir { 
     97  background: url(../folder.png) 0 50% no-repeat; 
     98  padding-left: 20px; 
     99} 
     100table.dirlist td.name a.file { 
     101  background: url(../file.png) 0 50% no-repeat; 
     102  padding-left: 20px; 
     103} 
    98104table.dirlist td.name a, table.dirlist td.rev a { border-bottom: none } 
    99105table.dirlist td.author, table.dirlist td.change { font-size: 85% } 
    100106table.dirlist td.rev a.chgset {  
    101   background-repeat: no-repeat; 
    102   background-image: url(../changeset.png);  
    103   background-position: 100% 50%; 
     107  background: url(../changeset.png) 100% 50% no-repeat; 
    104108  padding: 0 0 0 5px;  
    105109  margin: 0 5px 0 0;  
    106110} 
    107111table.dirlist td.description { padding-left: 2em } 
    108112table.dirlist td.description > :first-child { margin-top: 0 } 
    109113table.dirlist td.description > :last-child { margin-bottom: 0 } 
    110  
    111114table.dirlist td span.loading {  
    112   background-image: url(../loading.gif);  
     115  background: url(../loading.gif) 0 50% no-repeat; 
     116  padding-left: 20px; 
    113117  font-style: italic  
    114118} 
    115119 
  • trac/htdocs/css/trac.css

    diff --git a/trac/htdocs/css/trac.css b/trac/htdocs/css/trac.css
    a b  
    6060  background: url(../envelope.png) center center no-repeat; 
    6161  padding-left: 14px; 
    6262 } 
     63 a.trac-rawlink { 
     64  background: url(../download.png) center center no-repeat; 
     65  padding-left: 21px; 
     66 } 
    6367} 
    6468 
    6569/* Forms */ 
     
    616620@media print { 
    617621 #header, #altlinks, #footer, #help { display: none } 
    618622 .nav, form, .buttons form, form .buttons, form .inlinebuttons, 
    619  .noprint, .trac-rawlink, .trac-nav, .trac-topnav { 
     623 .noprint, .trac-nav, .trac-topnav { 
    620624   display: none; 
    621625 } 
    622626 form.printableform { display: block } 
  • trac/htdocs/js/expand_dir.js

    diff --git a/trac/htdocs/js/expand_dir.js b/trac/htdocs/js/expand_dir.js
    a b  
    4141        var expander = $('<span class="expander">&nbsp;</span>') 
    4242          .attr("title", _("Expand sub-directory in place")) 
    4343          .click(function() { toggleDir($(this), qargs); }) 
    44         a.wrap('<div></div>').before(expander); 
     44        a.before(expander); 
    4545        if (autoexpand && a.text() == autoexpand[0]) 
    4646          autoexpand_expander = expander; 
    4747      } 
  • trac/templates/list_of_attachments.html

    diff --git a/trac/templates/list_of_attachments.html b/trac/templates/list_of_attachments.html
    a b  
    1616               foldable = value_of('foldable', False)" py:strip=""> 
    1717  <py:def function="show_one_attachment(attachment)"> 
    1818    <i18n:msg params="file, size, author, date"> 
    19       <a href="${url_of(attachment.resource)}" title="View attachment">$attachment.filename</a> 
    20       <a href="${url_of(attachment.resource, format='raw')}" class="trac-rawlink" 
    21          title="Download"><img src="${chrome.htdocs_location}download.png" alt="Download"/></a> 
     19      <a href="${url_of(attachment.resource)}" title="View attachment">${attachment.filename 
     20        }</a><a href="${url_of(attachment.resource, format='raw')}" class="trac-rawlink" title="Download">&#8203;</a> 
    2221       (<span title="${_('%(size)s bytes', size=attachment.size)}">${pretty_size(attachment.size)}</span>) - 
    2322      added by <em>${authorinfo(attachment.author)}</em> ${dateinfo(attachment.date)} ago. 
    2423    </i18n:msg> 
  • trac/tests/wikisyntax.py

    diff --git a/trac/tests/wikisyntax.py b/trac/tests/wikisyntax.py
    a b  
    4040------------------------------ 
    4141""" 
    4242 
    43 ATTACHMENT_TEST_CASES = """ 
     43ATTACHMENT_TEST_CASES = u""" 
    4444============================== attachment: link resolver (deprecated) 
    4545attachment:wiki:WikiStart:file.txt (deprecated) 
    4646attachment:ticket:123:file.txt (deprecated) 
     
    4848[attachment:ticket:123:file.txt] (deprecated) 
    4949------------------------------ 
    5050<p> 
    51 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">attachment:wiki:WikiStart:file.txt</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> (deprecated) 
    52 <a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">attachment:ticket:123:file.txt</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> (deprecated) 
    53 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">file.txt</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> (deprecated) 
    54 <a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">ticket:123:file.txt</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> (deprecated) 
     51<a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">attachment:wiki:WikiStart:file.txt</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download">\u200b</a> (deprecated) 
     52<a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">attachment:ticket:123:file.txt</a><a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download">\u200b</a> (deprecated) 
     53<a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">file.txt</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download">\u200b</a> (deprecated) 
     54<a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">ticket:123:file.txt</a><a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download">\u200b</a> (deprecated) 
    5555</p> 
    5656------------------------------ 
    5757============================== attachment: "foreign" links 
     
    6262attachment:foo.txt:wiki:SomePage/SubPage 
    6363------------------------------ 
    6464<p> 
    65 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">attachment:file.txt:wiki:WikiStart</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
    66 <a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">attachment:file.txt:ticket:123</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
    67 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">file.txt</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
    68 <a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">file.txt:ticket:123</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
    69 <a class="attachment" href="/attachment/wiki/SomePage/SubPage/foo.txt" title="Attachment 'foo.txt' in SomePage/SubPage">attachment:foo.txt:wiki:SomePage/SubPage</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/SomePage/SubPage/foo.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
     65<a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">attachment:file.txt:wiki:WikiStart</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download">\u200b</a> 
     66<a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">attachment:file.txt:ticket:123</a><a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download">\u200b</a> 
     67<a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">file.txt</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download">\u200b</a> 
     68<a class="attachment" href="/attachment/ticket/123/file.txt" title="Attachment 'file.txt' in Ticket #123">file.txt:ticket:123</a><a class="trac-rawlink" href="/raw-attachment/ticket/123/file.txt" title="Download">\u200b</a> 
     69<a class="attachment" href="/attachment/wiki/SomePage/SubPage/foo.txt" title="Attachment 'foo.txt' in SomePage/SubPage">attachment:foo.txt:wiki:SomePage/SubPage</a><a class="trac-rawlink" href="/raw-attachment/wiki/SomePage/SubPage/foo.txt" title="Download">\u200b</a> 
    7070</p> 
    7171------------------------------ 
    7272============================== attachment: "local" links 
     
    7474[attachment:file.txt that file] 
    7575------------------------------ 
    7676<p> 
    77 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">attachment:file.txt</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
    78 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">that file</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
     77<a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">attachment:file.txt</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download">\u200b</a> 
     78<a class="attachment" href="/attachment/wiki/WikiStart/file.txt" title="Attachment 'file.txt' in WikiStart">that file</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt" title="Download">\u200b</a> 
    7979</p> 
    8080------------------------------ 
    8181============================== attachment: "missing" links 
     
    101101[attachment:file.txt?format=raw that file] 
    102102------------------------------ 
    103103<p> 
    104 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt?format=raw" title="Attachment 'file.txt' in WikiStart">attachment:file.txt?format=raw</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt?format=raw" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
    105 <a class="attachment" href="/attachment/wiki/WikiStart/file.txt?format=raw" title="Attachment 'file.txt' in WikiStart">that file</a><span class="noprint"> <a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt?format=raw" title="Download"><img src="/chrome/common/download.png" alt="Download"/></a></span> 
     104<a class="attachment" href="/attachment/wiki/WikiStart/file.txt?format=raw" title="Attachment 'file.txt' in WikiStart">attachment:file.txt?format=raw</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt?format=raw" title="Download">\u200b</a> 
     105<a class="attachment" href="/attachment/wiki/WikiStart/file.txt?format=raw" title="Attachment 'file.txt' in WikiStart">that file</a><a class="trac-rawlink" href="/raw-attachment/wiki/WikiStart/file.txt?format=raw" title="Download">\u200b</a> 
    106106</p> 
    107107------------------------------ 
    108108""" # " 
  • trac/ticket/templates/ticket_change.html

    diff --git a/trac/ticket/templates/ticket_change.html b/trac/ticket/templates/ticket_change.html
    a b  
    1717      <strong>${field.label}</strong> 
    1818      <py:choose> 
    1919        <py:when test="field_name == 'attachment'"><i18n:msg params="name"> 
    20           <a href="${href.attachment('ticket', ticket.id, field.new)}"><em>${field.new}</em></a> 
    21           <a href="${href('raw-attachment', 'ticket', ticket.id, field.new)}" 
    22              title="Download" class="trac-rawlink"><img src="${chrome.htdocs_location}download.png" alt="Download"/></a> 
     20          <a href="${href.attachment('ticket', ticket.id, field.new)}"><em>${field.new 
     21            }</em></a><a href="${href('raw-attachment', 'ticket', ticket.id, field.new)}" 
     22                         title="Download" class="trac-rawlink">&#8203;</a> 
    2323          added 
    2424        </i18n:msg></py:when> 
    2525        <py:when test="'rendered' in field">${field.rendered}</py:when> 
  • trac/versioncontrol/templates/dir_entries.html

    diff --git a/trac/versioncontrol/templates/dir_entries.html b/trac/versioncontrol/templates/dir_entries.html
    a b  
    1313                                  order=(order != 'name' and order or None), desc=desc)}">$entry.name</a> 
    1414        </td> 
    1515        <td class="size"> 
    16           <span title="${_('%(size)s bytes', size=entry.content_length)}">${pretty_size(entry.content_length)}</span> 
     16          <span title="${_('%(size)s bytes', size=entry.content_length)}">${pretty_size(entry.content_length) 
     17            }</span><a py:if="entry.raw_href" class="trac-rawlink" href="$entry.raw_href" 
     18                       title="${entry.kind == 'dir' and _('Download as Zip archive') or _('Download')}">&#8203;</a> 
    1719        </td> 
    1820        <td class="rev"> 
    1921          <a title="View Revision Log" href="${href.log(reponame, entry.path, rev=rev)}">${display_rev(entry.rev)}</a> 
  • trac/versioncontrol/templates/repository_index.html

    diff --git a/trac/versioncontrol/templates/repository_index.html b/trac/versioncontrol/templates/repository_index.html
    a b  
    55  <table class="listing dirlist" id="${repoindex or None}"> 
    66    <xi:include href="dirlist_thead.html" /> 
    77    <tbody> 
    8       <py:for each="idx, (reponame, repoinfo, repos, change, err) in enumerate(repo.repositories)" 
     8      <py:for each="idx, (reponame, repoinfo, repos, change, err, raw_href) in enumerate(repo.repositories)" 
    99              py:with="chgset_context = change and context('changeset', change.rev, parent=repos.resource); 
    1010                       chgset_view = change and change.can_view(perm)"> 
    1111        <tr class="${idx % 2 and 'even' or 'odd'}"> 
     
    1313            <em py:strip="not err"> 
    1414              <b py:strip="repoinfo.alias != ''"> 
    1515                <a class="dir" title="View Root Directory" 
    16                   href="${href.browser(repos.reponame, order=(order != 'name' and order or None), desc=desc)}">$reponame</a> 
     16                   href="${href.browser(repos.reponame, order=(order != 'name' and order or None), desc=desc)}">$reponame</a> 
    1717              </b> 
    1818            </em> 
    1919          </td> 
    20           <td class="size" /> 
     20          <td class="size"> 
     21            <a py:if="raw_href" class="trac-rawlink" href="$raw_href" title="Download as Zip archive">&#8203;</a> 
     22          </td> 
    2123          <td class="rev"> 
    2224            <py:if test="not err"> 
    2325              <a title="View Revision Log" href="${href.log(repos.reponame)}">${repos.display_rev(change.rev)}</a> 
  • trac/versioncontrol/web_ui/browser.py

    diff --git a/trac/versioncontrol/web_ui/browser.py b/trac/versioncontrol/web_ui/browser.py
    a b  
    330330 
    331331        path = req.args.get('path', '/') 
    332332        rev = req.args.get('rev', '') 
    333         if rev in ('', 'HEAD'): 
     333        if rev.lower() in ('', 'head'): 
    334334            rev = None 
    335335        order = req.args.get('order', 'name').lower() 
    336336        desc = req.args.has_key('desc') 
     
    499499                            timerange = TimeRange(youngest.date) 
    500500                        else: 
    501501                            timerange.insert(youngest.date) 
    502                     entry = (reponame, repoinfo, repos, youngest, None) 
     502                    raw_href = self._get_download_href(context.href, repos, 
     503                                                       None, None) 
     504                    entry = (reponame, repoinfo, repos, youngest, None, 
     505                             raw_href) 
    503506                else: 
    504                     entry = (reponame, repoinfo, None, None, "XXX") 
     507                    entry = (reponame, repoinfo, None, None, "XXX", None) 
    505508            except TracError, err: 
    506509                entry = (reponame, repoinfo, None, None, 
    507                          exception_to_unicode(err)) 
     510                         exception_to_unicode(err), None) 
    508511            repositories.append(entry) 
    509512 
    510513        # Ordering of repositories 
    511514        if order == 'date': 
    512             def repo_order((reponame, repoinfo, repos, youngest, err)): 
     515            def repo_order((reponame, repoinfo, repos, youngest, err, href)): 
    513516                return youngest and youngest.date 
    514517        else: 
    515             def repo_order((reponame, repoinfo, repos, youngest, err)): 
     518            def repo_order((reponame, repoinfo, repos, youngest, err, href)): 
    516519                return embedded_numbers(reponame.lower()) 
    517520 
    518521        repositories = sorted(repositories, key=repo_order, reverse=desc) 
     
    522525 
    523526    def _render_dir(self, req, repos, node, rev, order, desc): 
    524527        req.perm(node.resource).require('BROWSER_VIEW') 
     528        download_href = self._get_download_href 
    525529 
    526530        # Entries metadata 
    527531        class entry(object): 
    528             __slots__ = 'name rev kind isdir path content_length'.split() 
     532            _copy = 'name rev kind isdir path content_length'.split() 
     533            __slots__ = _copy + ['raw_href'] 
    529534            def __init__(self, node): 
    530                 for f in entry.__slots__: 
     535                for f in entry._copy: 
    531536                    setattr(self, f, getattr(node, f)) 
     537                self.raw_href = download_href(req.href, repos, node, rev) 
    532538                 
    533539        entries = [entry(n) for n in node.get_entries() 
    534540                   if n.can_view(req.perm)] 
     
    574580        entries = sorted(entries, key=browse_order, reverse=desc) 
    575581 
    576582        # ''Zip Archive'' alternate link 
    577         path = node.path.strip('/') 
    578         if repos.reponame: 
    579             path = repos.reponame + '/' + path 
    580         if any(fnmatchcase(path, p.strip('/')) 
    581                for p in self.downloadable_paths): 
    582             zip_href = req.href.changeset(rev or repos.youngest_rev,  
    583                                           repos.reponame or None, node.path, 
    584                                           old=rev, 
    585                                           old_path=repos.reponame or '/', 
    586                                           format='zip') 
     583        zip_href = self._get_download_href(req.href, repos, node, rev) 
     584        if zip_href: 
    587585            add_link(req, 'alternate', zip_href, _('Zip Archive'), 
    588586                     'application/zip', 'zip') 
    589587 
     
    675673                'annotate': force_source, 
    676674                } 
    677675 
     676    def _get_download_href(self, href, repos, node, rev): 
     677        """Return the URL for downloading a file, or a directory as a ZIP.""" 
     678        if node is not None and node.isfile: 
     679            return href.export(rev or 'HEAD', repos.reponame or None, 
     680                               node.path) 
     681        path = npath = node is not None and node.path.strip('/') or '' 
     682        if repos.reponame: 
     683            path = (repos.reponame + '/' + npath).rstrip('/') 
     684        if any(fnmatchcase(path, p.strip('/')) 
     685               for p in self.downloadable_paths): 
     686            return href.changeset(rev or repos.youngest_rev,  
     687                                  repos.reponame or None, npath, 
     688                                  old=rev, old_path=repos.reponame or '/', 
     689                                  format='zip') 
     690 
    678691    # public methods 
    679692     
    680693    def render_properties(self, mode, context, props): 
     
    739752        elif '@' in export: 
    740753            path, rev = export.split('@', 1) 
    741754        else: 
    742             rev, path = 'HEAD', export 
    743         return tag.a(label, class_='export', 
    744                      href=formatter.href.export(rev, path) + fragment) 
     755            rev, path = None, export 
     756        node, raw_href, title = self._get_link_info(path, rev, formatter.href, 
     757                                                    formatter.perm) 
     758        if raw_href: 
     759            return tag.a(label, class_='export', href=raw_href + fragment, 
     760                         title=title) 
     761        return tag.a(label, class_='missing export') 
    745762 
    746763    def _format_browser_link(self, formatter, ns, path, label): 
    747764        path, query, fragment = formatter.split_link(path) 
     
    749766        match = self.PATH_LINK_RE.match(path) 
    750767        if match: 
    751768            path, rev, marks = match.groups() 
    752         return tag.a(label, class_='source', 
    753                      href=(formatter.href.browser(path, rev=rev, marks=marks) + 
    754                            query + fragment)) 
     769        href = formatter.href 
     770        src_href = href.browser(path, rev=rev, marks=marks) + query + fragment 
     771        node, raw_href, title = self._get_link_info(path, rev, formatter.href, 
     772                                                    formatter.perm) 
     773        if not node: 
     774            return tag.a(label, class_='missing source') 
     775        link = tag.a(label, class_='source', href=src_href) 
     776        if raw_href: 
     777            link = tag(link, tag.a(u'\u200b', href=raw_href + fragment, 
     778                                   title=title, class_='trac-rawlink')) 
     779        return link 
    755780 
    756781    PATH_LINK_RE = re.compile(r"([^@#:]*)"     # path 
    757782                              r"[@:]([^#:]+)?" # rev 
    758783                              r"(?::(\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*))?" # marks 
    759784                              ) 
    760785 
     786    def _get_link_info(self, path, rev, href, perm): 
     787        rm = RepositoryManager(self.env) 
     788        reponame, repos, npath = rm.get_repository_by_path(path) 
     789        node = get_allowed_node(repos, npath, rev, perm) 
     790        if node is not None: 
     791            raw_href = self._get_download_href(href, repos, node, rev) 
     792            title = node.isfile and _("Download") \ 
     793                    or _("Download as Zip archive") 
     794            return (node, raw_href, title) 
     795        return (None, None, None) 
     796         
    761797    # IHTMLPreviewAnnotator methods 
    762798 
    763799    def get_annotation_type(self): 
  • trac/versioncontrol/web_ui/tests/wikisyntax.py

    diff --git a/trac/versioncontrol/web_ui/tests/wikisyntax.py b/trac/versioncontrol/web_ui/tests/wikisyntax.py
    a b  
    33import unittest 
    44 
    55from trac.test import Mock 
    6 from trac.versioncontrol import NoSuchChangeset 
     6from trac.versioncontrol import NoSuchChangeset, NoSuchNode 
    77from trac.versioncontrol.api import * 
    88from trac.versioncontrol.web_ui import * 
    99from trac.wiki.tests import formatter 
     
    2323            return '200' 
    2424        else: 
    2525            raise NoSuchChangeset(rev) 
    26      
     26 
     27def _get_node(path, rev=None): 
     28    if path == 'foo': 
     29        return Mock(path=path, rev=rev, isfile=False, 
     30                    can_view=lambda resource: True) 
     31    elif path == 'missing/file': 
     32        raise NoSuchNode(path, rev) 
     33    else: 
     34        return Mock(path=path, rev=rev, isfile=True, 
     35                    can_view=lambda resource: True) 
     36 
    2737def _get_repository(reponame): 
    2838    return Mock(reponame=reponame, youngest_rev='200', 
    2939                get_changeset=_get_changeset, 
    30                 normalize_rev=_normalize_rev) 
     40                normalize_rev=_normalize_rev, 
     41                get_node=_get_node) 
    3142 
    3243def repository_setup(tc): 
    3344    setattr(tc.env, 'get_repository', _get_repository) 
     
    252263""" 
    253264 
    254265 
    255 SOURCE_TEST_CASES = """ 
     266SOURCE_TEST_CASES = u""" 
    256267============================== source: link resolver 
    257268source:/foo/bar 
    258269source:/foo/bar#42   # no long works as rev spec 
     
    264275source:/foo/bar@42#L20 
    265276source:/foo/bar@head#L20 
    266277source:/foo/bar@#L20 
     278source:/missing/file 
    267279------------------------------ 
    268280<p> 
    269 <a class="source" href="/browser/foo/bar">source:/foo/bar</a> 
    270 <a class="source" href="/browser/foo/bar#42">source:/foo/bar#42</a>   # no long works as rev spec 
    271 <a class="source" href="/browser/foo/bar#head">source:/foo/bar#head</a> # 
    272 <a class="source" href="/browser/foo/bar?rev=42">source:/foo/bar@42</a> 
    273 <a class="source" href="/browser/foo/bar?rev=head">source:/foo/bar@head</a> 
    274 <a class="source" href="/browser/foo%2520bar/baz%252Bquux">source:/foo%20bar/baz%2Bquux</a> 
    275 <a class="source" href="/browser/?rev=42">source:@42</a> 
    276 <a class="source" href="/browser/foo/bar?rev=42#L20">source:/foo/bar@42#L20</a> 
    277 <a class="source" href="/browser/foo/bar?rev=head#L20">source:/foo/bar@head#L20</a> 
    278 <a class="source" href="/browser/foo/bar#L20">source:/foo/bar@#L20</a> 
     281<a class="source" href="/browser/foo/bar">source:/foo/bar</a><a class="trac-rawlink" href="/export/HEAD/foo/bar" title="Download">\u200b</a> 
     282<a class="source" href="/browser/foo/bar#42">source:/foo/bar#42</a><a class="trac-rawlink" href="/export/HEAD/foo/bar#42" title="Download">\u200b</a>   # no long works as rev spec 
     283<a class="source" href="/browser/foo/bar#head">source:/foo/bar#head</a><a class="trac-rawlink" href="/export/HEAD/foo/bar#head" title="Download">\u200b</a> # 
     284<a class="source" href="/browser/foo/bar?rev=42">source:/foo/bar@42</a><a class="trac-rawlink" href="/export/42/foo/bar" title="Download">\u200b</a> 
     285<a class="source" href="/browser/foo/bar?rev=head">source:/foo/bar@head</a><a class="trac-rawlink" href="/export/head/foo/bar" title="Download">\u200b</a> 
     286<a class="source" href="/browser/foo%2520bar/baz%252Bquux">source:/foo%20bar/baz%2Bquux</a><a class="trac-rawlink" href="/export/HEAD/foo%2520bar/baz%252Bquux" title="Download">\u200b</a> 
     287<a class="source" href="/browser/?rev=42">source:@42</a><a class="trac-rawlink" href="/export/42/" title="Download">\u200b</a> 
     288<a class="source" href="/browser/foo/bar?rev=42#L20">source:/foo/bar@42#L20</a><a class="trac-rawlink" href="/export/42/foo/bar#L20" title="Download">\u200b</a> 
     289<a class="source" href="/browser/foo/bar?rev=head#L20">source:/foo/bar@head#L20</a><a class="trac-rawlink" href="/export/head/foo/bar#L20" title="Download">\u200b</a> 
     290<a class="source" href="/browser/foo/bar#L20">source:/foo/bar@#L20</a><a class="trac-rawlink" href="/export/HEAD/foo/bar#L20" title="Download">\u200b</a> 
     291<a class="missing source">source:/missing/file</a> 
    279292</p> 
    280293------------------------------ 
    281294============================== source: link resolver + query  
     
    284297------------------------------ 
    285298<p> 
    286299<a class="source" href="/browser/foo?order=size&amp;desc=1">source:/foo?order=size&amp;desc=1</a> 
    287 <a class="source" href="/browser/foo/bar?format=raw">source:/foo/bar?format=raw</a> 
     300<a class="source" href="/browser/foo/bar?format=raw">source:/foo/bar?format=raw</a><a class="trac-rawlink" href="/export/HEAD/foo/bar" title="Download">\u200b</a> 
    288301</p> 
    289302------------------------------ 
    290303============================== source: provider, with quoting 
     
    294307[source:"even with whitespaces" Path with spaces] 
    295308------------------------------ 
    296309<p> 
    297 <a class="source" href="/browser/even%20with%20whitespaces">source:'even with whitespaces'</a> 
    298 <a class="source" href="/browser/even%20with%20whitespaces">source:"even with whitespaces"</a> 
    299 <a class="source" href="/browser/even%20with%20whitespaces">Path with spaces</a> 
    300 <a class="source" href="/browser/even%20with%20whitespaces">Path with spaces</a> 
     310<a class="source" href="/browser/even%20with%20whitespaces">source:'even with whitespaces'</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download">\u200b</a> 
     311<a class="source" href="/browser/even%20with%20whitespaces">source:"even with whitespaces"</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download">\u200b</a> 
     312<a class="source" href="/browser/even%20with%20whitespaces">Path with spaces</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download">\u200b</a> 
     313<a class="source" href="/browser/even%20with%20whitespaces">Path with spaces</a><a class="trac-rawlink" href="/export/HEAD/even%20with%20whitespaces" title="Download">\u200b</a> 
    301314</p> 
    302315------------------------------ 
    303316============================== export: link resolver 
     
    306319export:/foo/pict.gif@123 
    307320------------------------------ 
    308321<p> 
    309 <a class="export" href="/export/HEAD/foo/bar.html">export:/foo/bar.html</a> 
    310 <a class="export" href="/export/123/foo/pict.gif">export:123:/foo/pict.gif</a> 
    311 <a class="export" href="/export/123/foo/pict.gif">export:/foo/pict.gif@123</a> 
     322<a class="export" href="/export/HEAD/foo/bar.html" title="Download">export:/foo/bar.html</a> 
     323<a class="export" href="/export/123/foo/pict.gif" title="Download">export:123:/foo/pict.gif</a> 
     324<a class="export" href="/export/123/foo/pict.gif" title="Download">export:/foo/pict.gif@123</a> 
    312325</p> 
    313326------------------------------ 
    314327============================== export: link resolver + fragment 
    315328export:/foo/bar.html#header 
    316329------------------------------ 
    317330<p> 
    318 <a class="export" href="/export/HEAD/foo/bar.html#header">export:/foo/bar.html#header</a> 
     331<a class="export" href="/export/HEAD/foo/bar.html#header" title="Download">export:/foo/bar.html#header</a> 
    319332</p> 
    320333------------------------------ 
    321334""" # " (be Emacs friendly...) 
  • trac/versioncontrol/web_ui/util.py

    diff --git a/trac/versioncontrol/web_ui/util.py b/trac/versioncontrol/web_ui/util.py
    a b  
    1919from genshi.builder import tag 
    2020 
    2121from trac.resource import ResourceNotFound  
    22 from trac.util.datefmt import pretty_timedelta 
    23 from trac.util.text import shorten_line 
    2422from trac.util.translation import tag_, _ 
    2523from trac.versioncontrol.api import NoSuchNode, NoSuchChangeset 
    2624 
    27 __all__ = ['get_changes', 'get_path_links', 'get_existing_node'] 
     25__all__ = ['get_changes', 'get_path_links', 'get_existing_node', 
     26           'get_allowed_node'] 
    2827 
    2928def get_changes(repos, revs): 
    3029    changes = {} 
     
    6968            tag.p(tag_("You can %(search)s in the repository history to see " 
    7069                       "if that path existed but was later removed", 
    7170                       search=search_a)))) 
     71 
     72def get_allowed_node(repos, path, rev, perm): 
     73    if repos is not None: 
     74        try: 
     75            node = repos.get_node(path, rev) 
     76        except NoSuchNode: 
     77            return None 
     78        if node.can_view(perm): 
     79            return node