Home | History | Annotate | Download | only in resources
      1 <!DOCTYPE HTML>
      2 <html i18n-values="dir:textdirection;">
      3 <head>
      4 <meta charset="utf-8">
      5 <title i18n-content="title"></title>
      6 <link rel="icon" href="../../app/theme/downloads_favicon.png">
      7 <style type="text/css">
      8 div.header {
      9   border-bottom: 1px solid #7289E2;
     10   padding: 8px;
     11   margin: 0;
     12   width: 100%;
     13   left: 0;
     14   top: 0;
     15   height: 32px;
     16   position: absolute;
     17   box-sizing: border-box;
     18   background-image: -webkit-linear-gradient(#D0DAF8, #A6BAF7);
     19   border-bottom-color: #999;
     20   border-bottom-width: 1px;
     21   border-left-color: #999;
     22   border-left-width: 1px;
     23   border-right-color: #999;
     24   border-right-width: 1px;
     25   color: black;
     26 }
     27 
     28 *:-khtml-drag {
     29   background-color: rgba(238,238,238, 0.5);
     30 }
     31 
     32 *[draggable] {
     33   -khtml-user-drag: element;
     34   cursor: move;
     35 }
     36 
     37 ul.downloadlist {
     38   list-style-type: none;
     39   margin: 0;
     40   padding: 0;
     41   position: relative;
     42 }
     43 
     44 .menuicon {
     45   position: absolute;
     46   right: 9px;
     47   top: 11px;
     48   height: 100%;
     49   width: 5px;
     50   margin-left: 0;
     51   background: url('chrome://resources/images/active_downloads_menu.png');
     52   margin-top: 5px;
     53   background-repeat: no-repeat;
     54 }
     55 
     56 .menubutton {
     57   position: absolute;
     58   margin-top: -36px;
     59   right: 0;
     60   height: 35px;
     61   width: 24px;
     62   border-bottom: 1px solid #CCC;
     63   cursor: pointer;
     64 }
     65 
     66 .rowbg {
     67   border-bottom: 1px solid #CCC;
     68   background: -webkit-gradient(linear, left top, left bottom,
     69     from(#f3f3f3), to(#ebebeb), color-stop(0.8, #ededed));
     70 }
     71 
     72 .rowbg:hover {
     73   background: -webkit-gradient(linear, left top, left bottom,
     74     from(#fdfdfd), to(#f1f1f1), color-stop(0.8, #f5f5f5));
     75 }
     76 
     77 .rowbg:active {
     78   background: -webkit-gradient(linear, left top, left bottom,
     79     from(#d2e0f0), to(#dee6f0), color-stop(0.1, #e3e9f0));
     80 }
     81 
     82 .downloadrow {
     83   height: 36px;
     84 }
     85 
     86 .rowbutton {
     87   padding: 5px 5px 0 34px;
     88   position: relative;
     89   right: 24px;
     90   border-right: 1px solid #CCC;
     91   height: 30px;
     92 }
     93 
     94 .rowbutton div.icon {
     95   float: left;
     96   margin-top: 1px;
     97   display: inline
     98   position: relative;
     99   width: 21px;
    100   height: 17px;
    101   background-repeat: no-repeat;
    102 }
    103 
    104 .rowbutton span.title {
    105   position: relative;
    106   text-overflow: ellipsis;
    107   white-space: nowrap;
    108   display: inline-block;
    109   overflow: hidden;
    110   width: 189px;
    111   color: #325282;
    112 }
    113 
    114 .rowbutton span.downloading {
    115   top: -6px;
    116   font-size: .8em;
    117 }
    118 
    119 .rowbutton span.downloaded {
    120   font-size: .8em;
    121 }
    122 
    123 .rowbutton span.error {
    124   font-size: .6em;
    125 }
    126 
    127 .allowdownload {
    128   margin: -10px 5px 5px 30px;
    129   display: block;
    130 }
    131 
    132 .allowdownloadtext {
    133   font-size: .6em;
    134   color: #325282;
    135 }
    136 
    137 .confirm {
    138   font-size: .6em;
    139   text-decoration: underline;
    140   margin-left: 5px;
    141   color: #254f9b;
    142   cursor: pointer;
    143 }
    144 
    145 .progress {
    146   font-size: .6em;
    147   text-align: right;
    148   margin-left: auto;
    149   margin-top: -5px;
    150 }
    151 
    152 div.columnlist {
    153   width: 100%;
    154   top: 0;
    155   left: 0;
    156   bottom: 29px;  /* space for Show All Downloads */
    157   position: absolute;
    158   background: #e8e8e8
    159 }
    160 
    161 span.showalldownloadstext {
    162   color: #254f9b;
    163   cursor: pointer;
    164   text-decoration: underline;
    165   font-size: 12px;
    166   height: 15px;
    167 }
    168 
    169 div.showalldownloads {
    170   width: 100%;
    171   bottom: 0;
    172   height: 29px;
    173   position: absolute;
    174   margin-left: -8px;
    175   text-align: center;
    176   background: #e8e8e8
    177 }
    178 
    179 .menu {
    180   top: 14px;
    181   right: 2px;
    182   -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 3px;
    183   border-bottom-left-radius: 4px 4px;
    184   border-bottom-right-radius: 4px 4px;
    185   border-top-left-radius: 4px 4px;
    186   border-top-right-radius: 0px 0px;
    187   position: absolute;
    188   display: none;
    189   z-index: 999;
    190   background: white;
    191   border-top-left-radius: 4px;
    192   border: 1px solid rgba(0, 0, 0, 0.6);
    193   padding: 5px;
    194 }
    195 
    196 .menuitem {
    197   width: 100%;
    198   height: 20px;
    199   font-size: .8em;
    200   text-align: left;
    201   cursor: pointer;
    202   left: 0;
    203   color: #0D0052;
    204   -webkit-transition: color 1.0s ease-out ;
    205 }
    206 
    207 .menuitem:hover {
    208   text-decoration: underline;
    209   color: #20c;
    210   background: #ebeff9;
    211   -webkit-transition: color 0.0s ease-out ;
    212 }
    213 
    214 div.iconmedia {
    215   background: url('chrome://resources/images/icon_media.png');
    216 }
    217 
    218 div.iconfolder {
    219   background: url('chrome://resources/images/icon_folder.png');
    220 }
    221 
    222 div.iconfile {
    223   background: url('chrome://resources/images/icon_file.png');
    224 }
    225 
    226 div.iconphoto {
    227   background: url('chrome://resources/images/icon_photo.png');
    228 }
    229 
    230 div.iconmusic {
    231   background: url('chrome://resources/images/icon_media.png');
    232 }
    233 
    234 </style>
    235 <script src="shared/js/local_strings.js"></script>
    236 <script src="shared/js/media_common.js"></script>
    237 <script src="shared/js/util.js"></script>
    238 <script>
    239 
    240 var localStrings = null;
    241 var downloadRowList = null;
    242 
    243 function init() {
    244   localStrings = new LocalStrings();
    245   initTestHarness();
    246 
    247   $('header').style.display = 'none';
    248 
    249   $('showalldownloadstext').textContent =
    250       localStrings.getString('showalldownloads');
    251 
    252   downloadRowList = new DownloadRowList();
    253   chrome.send('getDownloads', []);
    254 }
    255 
    256 /**
    257  * Testing. Allow this page to be loaded in a browser.
    258  * Create stubs for localStrings and chrome.send.
    259  */
    260 var testHarnessEnabled = false;
    261 function initTestHarness() {
    262   if (testHarnessEnabled) {
    263     localStrings = {
    264       getString: function(name) {
    265         if (name == 'showalldownloads')
    266           return 'Show All Downloads';
    267         if (name == 'allowdownload')
    268           return 'Allow Download?';
    269         if (name == 'confirmyes')
    270           return 'Yes';
    271         if (name == 'confirmcancel')
    272           return 'Cancel';
    273         return name;
    274       },
    275       getStringF: function(name, path) {
    276         return path + ' - Unknown file type.';
    277       },
    278     };
    279     chrome.send = function(name, ary) {
    280       console.log('chrome.send ' + name + ' ' + ary);
    281       if (name == 'getDownloads' ||
    282           (name == 'openNewFullWindow' &&
    283           ary[0] == 'chrome://downloads'))
    284         sendTestResults();
    285     }
    286   }
    287 }
    288 
    289 /**
    290  * Create a results array with test data and call downloadsList.
    291  */
    292 var id = 1;
    293 var results = [];
    294 function sendTestResults() {
    295   results.push({
    296       state: (id % 2 ? 'DANGEROUS' : 'COMPLETE'),
    297       percent: (id % 2 ? 90 : 100),
    298       id: id,
    299       file_name: ' Test' + id + '.pdf',
    300       file_path: '/home/achuith/Downloads/Test' + id + '.pdf',
    301       progress_status_text : (id % 2 ?
    302           'download progressing nicely' : 'download complete'),
    303     });
    304   id++;
    305   downloadsList(results);
    306 }
    307 
    308 /**
    309  * Current Menu.
    310  */
    311 var menu = {
    312   current_: null,
    313 
    314   /**
    315   * Close the current menu.
    316   */
    317   clear: function() {
    318     var current = this.current_;
    319     if (current) {
    320       current.firstChild.style.display = 'none';
    321       current.style.opacity = '';
    322       this.current_ = null;
    323     }
    324   },
    325 
    326   /**
    327   * If it's a second click on an open menu, close the menu.
    328   * Otherwise, close any other open menu and open the clicked menu.
    329   */
    330   clicked: function(row) {
    331     var menuicon = row.menuicon;
    332     if (this.current_ === menuicon) {
    333       this.clear();
    334       return;
    335     }
    336     this.clear();
    337     if (menuicon.firstChild.style.display != 'block') {
    338       menuicon.firstChild.style.display = 'block';
    339       menuicon.style.opacity = '1';
    340       menuicon.scrollIntoView();
    341       this.current_ = menuicon;
    342     }
    343     window.event.stopPropagation();
    344   },
    345 };
    346 
    347 /**
    348  * C++ api calls.
    349  */
    350 function downloadsList(results) {
    351   downloadRowList.list(results);
    352 }
    353 
    354 function downloadUpdated(result) {
    355   downloadRowList.update(result);
    356 }
    357 
    358 function showAllDownloads() {
    359   chrome.send('openNewFullWindow', ['chrome://downloads']);
    360   dialogClose();
    361 }
    362 
    363 function dialogClose() {
    364   chrome.send('DialogClose', ['']);
    365 }
    366 
    367 /**
    368  * DownloadRow contains all the elements that go into a row of the downloads
    369  * list. It represents a single DownloadItem.
    370  *
    371  * @param {DownloadRowList} list Global DownloadRowList.
    372  * @param {Object} result JSON representation of DownloadItem.
    373  * @constructor
    374  */
    375 function DownloadRow(list, result) {
    376   this.path = result.file_path;
    377   this.name = result.file_name;
    378   this.list = list;
    379   this.id = result.id;
    380 
    381   this.createRow_(list);
    382   this.createRowButton_();
    383   this.createMenu_();
    384 }
    385 
    386 DownloadRow.prototype = {
    387   /**
    388   * Create the row html element and book-keeping for the row.
    389   * @param {DownloadRowList} list global DownloadRowList instance.
    390   * @private
    391   */
    392   createRow_: function(list) {
    393     var row = document.createElement('li');
    394     row.className = 'downloadrow';
    395     row.id = this.path;
    396     row.downloadRow = this;
    397     list.append(row);
    398 
    399     this.element = row;
    400     this.list.downloadList.push(this);
    401   },
    402 
    403   getIconClass_: function() {
    404     if (pathIsImageFile(this.path)) {
    405       return 'icon iconphoto';
    406     } else if (pathIsVideoFile(this.path)) {
    407       return 'icon iconmedia';
    408     } else if (pathIsAudioFile(this.path)) {
    409       return 'icon iconmusic';
    410     }
    411     return 'icon iconfile';
    412   },
    413 
    414   setErrorText_: function(text) {
    415     this.filename.textContent = text;
    416     this.filename.className = 'error title';
    417   },
    418 
    419   supportsPdf_: function() {
    420     return 'application/pdf' in navigator.mimeTypes;
    421   },
    422 
    423   openFilePath_: function() {
    424     chrome.send('openNewFullWindow', ['file://' + this.path]);
    425   },
    426 
    427   /**
    428   * Determine onclick behavior based on filename.
    429   * @private
    430   */
    431   getFunctionForItem_: function() {
    432     var path = this.path;
    433     var self = this;
    434 
    435     if (pathIsAudioFile(path)) {
    436       return function() {
    437         chrome.send('playMediaFile', [path]);
    438       };
    439     }
    440     if (pathIsVideoFile(path)) {
    441       return function() {
    442         chrome.send('playMediaFile', [path]);
    443       };
    444     }
    445     if (pathIsImageFile(path)) {
    446       return function() {
    447         self.openFilePath_();
    448       }
    449     }
    450     if (pathIsHtmlFile(path)) {
    451       return function() {
    452         self.openFilePath_();
    453       }
    454     }
    455     if (pathIsPdfFile(path) && this.supportsPdf_()) {
    456       return function() {
    457         self.openFilePath_();
    458       }
    459     }
    460 
    461     return function() {
    462       self.setErrorText_(localStrings.getStringF('error_unknown_file_type',
    463                           self.name));
    464     };
    465   },
    466 
    467   /**
    468   * Create a child element.
    469   *
    470   * @param {string} type The type - div, span, etc.
    471   * @param {string} className The class name
    472   * @param {HTMLElement} parent Parent to append this child to.
    473   * @param {string} textContent optional text content of child.
    474   * @param {function(*)} onclick onclick function of child.
    475   * @private
    476   */
    477   createChild_: function(type, className, parent, textContent, onclick) {
    478     var elem = document.createElement(type);
    479     elem.className = className;
    480     if (textContent !== undefined)
    481       elem.textContent = textContent;
    482     elem.onclick = onclick;
    483     parent.appendChild(elem);
    484     return elem;
    485   },
    486 
    487   /**
    488     * Create the row button for the left of the row.
    489     * This contains the icon, filename and error elements.
    490     * @private
    491     */
    492   createRowButton_: function () {
    493     this.rowbutton = this.createChild_('div', 'rowbutton rowbg', this.element);
    494 
    495     // Icon.
    496     var icon = this.createChild_('div', this.getIconClass_(), this.rowbutton);
    497 
    498     // Filename.
    499     this.filename = this.createChild_('span', 'downloaded title',
    500         this.rowbutton, this.name);
    501   },
    502 
    503   /**
    504   * Create the menu button on the right of the row.
    505   * This contains the menuicon. The menuicon contains the menu, which
    506   * contains items for Open, Pause/Resume and Cancel.
    507   * @private
    508   */
    509   createMenu_: function() {
    510     var self = this;
    511     this.menubutton = this.createChild_('div', 'menubutton rowbg',
    512         this.element, '',
    513         function() {
    514           menu.clicked(self);
    515         });
    516 
    517     this.menuicon = this.createChild_('div', 'menuicon', this.menubutton);
    518     this.menuicon.align = 'right';
    519 
    520     var menudiv = this.createChild_('div', 'menu', this.menuicon);
    521 
    522     this.open = this.createChild_('div', 'menuitem', menudiv,
    523       localStrings.getString('open'), this.getFunctionForItem_());
    524 
    525     this.pause = this.createChild_('div', 'menuitem', menudiv,
    526       localStrings.getString('pause'), function() {
    527                                          self.pauseToggleDownload_();
    528                                        });
    529 
    530     this.cancel = this.createChild_('div', 'menuitem', menudiv,
    531       localStrings.getString('cancel'), function() {
    532                                           self.cancelDownload_();
    533                                         });
    534 
    535     this.pause.style.display = 'none';
    536     this.cancel.style.display = 'none';
    537   },
    538 
    539   allowDownload_: function() {
    540     chrome.send('allowDownload', ['' + this.id]);
    541   },
    542 
    543   cancelDownload_: function() {
    544     chrome.send('cancelDownload', ['' + this.id]);
    545   },
    546 
    547   pauseToggleDownload_: function() {
    548     this.pause.textContent =
    549       (this.pause.textContent == localStrings.getString('pause')) ?
    550       localStrings.getString('resume') :
    551       localStrings.getString('pause');
    552 
    553     // Convert id to string before send.
    554     chrome.send('pauseToggleDownload', ['' + this.id]);
    555   },
    556 
    557   resetRow_: function() {
    558     this.rowbutton.onclick = '';
    559     this.rowbutton.style.cursor = '';
    560     this.rowbutton.setAttribute('draggable', 'false');
    561   },
    562 
    563   createAllowDownload_: function() {
    564     if (this.allowdownload)
    565       return;
    566 
    567     this.allowdownload = this.createChild_('div', 'allowdownload',
    568         this.rowbutton);
    569 
    570     this.createChild_('span', 'allowdownloadtext', this.allowdownload,
    571         localStrings.getString('allowdownload'));
    572 
    573     var self = this;
    574     this.createChild_('span', 'confirm', this.allowdownload,
    575         localStrings.getString('confirmyes'),
    576         function() {
    577           self.allowDownload_();
    578         });
    579     this.createChild_('span', 'confirm', this.allowdownload,
    580         localStrings.getString('confirmcancel'),
    581         function() {
    582           self.cancelDownload_();
    583         });
    584 
    585     this.resetRow_();
    586     this.menubutton.onclick = '';
    587   },
    588 
    589   removeAllowDownload_: function() {
    590     if (this.allowdownload) {
    591       this.rowbutton.removeChild(this.allowdownload);
    592       this.allowdownload = null;
    593       var self = this;
    594       this.menubutton.onclick = function() {
    595         menu.clicked(self);
    596       };
    597     }
    598   },
    599 
    600   createProgress_: function() {
    601     if (this.progress)
    602       return;
    603 
    604     this.progress = this.createChild_('div', 'progress', this.rowbutton);
    605 
    606     // Menu has Pause/Cancel. Open hidden.
    607     this.open.style.display = 'none';
    608     this.pause.style.display = '';
    609     this.cancel.style.display = '';
    610   },
    611 
    612   removeProgress_: function() {
    613     if (this.progress) {
    614       this.rowbutton.removeChild(this.progress);
    615       this.progress = null;
    616     }
    617   },
    618 
    619   updatePause_: function(result) {
    620     var pause = this.pause;
    621     var pauseStr = localStrings.getString('pause');
    622     var resumeStr = localStrings.getString('resume');
    623 
    624     if (pause &&
    625         result.state == 'PAUSED' &&
    626         pause.textContent != resumeStr) {
    627       pause.textContent = resumeStr;
    628     } else if (pause &&
    629               result.state == 'IN_PROGRESS' &&
    630               pause.textContent != pauseStr) {
    631       pause.textContent = pauseStr;
    632     }
    633   },
    634 
    635   updateProgress_: function(result) {
    636     this.removeAllowDownload_();
    637     this.createProgress_();
    638     this.progress.textContent = result.progress_status_text;
    639     this.updatePause_(result);
    640   },
    641 
    642   /**
    643   * Called when the item has finished downloading. Switch the menu
    644   * and remove the progress bar.
    645   * @private
    646   */
    647   finishedDownloading_: function() {
    648     this.filename.className = 'downloaded title';
    649 
    650     // Menu has Open. Pause/Cancel hidden.
    651     this.open.style.display = '';
    652     this.pause.style.display = 'none';
    653     this.cancel.style.display = 'none';
    654 
    655     // Make rowbutton clickable.
    656     this.rowbutton.onclick = this.getFunctionForItem_();
    657     this.rowbutton.style.cursor = 'pointer';
    658 
    659     // Make rowbutton draggable.
    660     this.rowbutton.setAttribute('draggable', 'true');
    661     var self = this;
    662     this.rowbutton.addEventListener('dragstart', function(e) {
    663       e.dataTransfer.effectAllowed = 'copy';
    664       e.dataTransfer.setData('Text', self.path);
    665       e.dataTransfer.setData('URL', 'file:///' + self.path);
    666     }, false);
    667 
    668     this.removeAllowDownload_();
    669     this.removeProgress_();
    670   },
    671 
    672   /**
    673   * One of the DownloadItem we are observing has updated.
    674   * @param {Object} result JSON representation of DownloadItem.
    675   */
    676   downloadUpdated: function(result) {
    677     this.filename.textContent = result.file_name;
    678     this.filename.className = 'downloading title';
    679 
    680     if (result.state == 'CANCELLED' ||
    681         result.state == 'INTERRUPTED') {
    682       this.list.remove(this);
    683     } else if (result.state == 'DANGEROUS') {
    684       this.createAllowDownload_();
    685     } else if (result.percent < 100) {
    686       this.updateProgress_(result);
    687     } else {
    688       this.finishedDownloading_();
    689     }
    690   },
    691 };
    692 
    693 /**
    694  * DownloadRowList is a container for DownloadRows.
    695  */
    696 function DownloadRowList() {
    697   var downloadpath = localStrings.getString('downloadpath');
    698 
    699   var list = document.createElement('ul');
    700   list.className = 'downloadlist';
    701   list.id = downloadpath;
    702   this.element = list;
    703   this.rows = [];
    704 
    705   document.title = downloadpath.split('/').pop();
    706 
    707   $('main').appendChild(list);
    708 }
    709 
    710 DownloadRowList.prototype = {
    711 
    712   /**
    713   * ROW_HEIGHT is height of each row.
    714   * MAX_ROWS is maximum number of rows displayed (to display a new row
    715   * beyond MAX_ROWS, we delete the oldest row).
    716   * MIN_ROWS is the minimum number of rows displayed.
    717   * numRows is the current number of rows.
    718   * downloadList is the list of DownloadRow elements.
    719   */
    720   ROW_HEIGHT: 36,
    721   MAX_ROWS: 5,
    722   MIN_ROWS: 1,
    723   numRows: 0,
    724   downloadList: [],
    725 
    726   numRowsOutsideRange_: function() {
    727     return this.numRows > this.MIN_ROWS && this.numRows < this.MAX_ROWS;
    728   },
    729 
    730   /**
    731   * Remove a row from the list, as when a download is canceled, or
    732   * the the number of rows has exceeded the max allowed.
    733   *
    734   * @param {DownloadRow} row Row to be removed.
    735   * @private
    736   */
    737   remove: function(row) {
    738     this.downloadList.splice(this.downloadList.indexOf(row), 1);
    739     this.element.removeChild(row.element);
    740     row.element.downloadRow = null;
    741 
    742     this.numRows--;
    743     if (this.numRowsOutsideRange_())
    744       window.resizeBy(0, -this.ROW_HEIGHT);
    745   },
    746 
    747   /**
    748   * Append a new row to the list, removing the last row if we exceed the
    749   * maximum allowed.
    750   * @param {DownloadRow} row Row to be removed.
    751   */
    752   append: function(row) {
    753     if (this.numRowsOutsideRange_())
    754       window.resizeBy(0, this.ROW_HEIGHT);
    755 
    756     this.numRows++;
    757 
    758     var list = this.element;
    759     if (this.numRows > this.MAX_ROWS)
    760       this.remove(list.lastChild.downloadRow);
    761 
    762     if (list.firstChild) {
    763       list.insertBefore(row, list.firstChild);
    764     } else {
    765       list.appendChild(row);
    766     }
    767   },
    768 
    769   /**
    770   * Handle list callback with list of DownloadItems.
    771   * @param {Array} results Array of JSONified DownloadItems.
    772   */
    773   list: function(results) {
    774     var removeList = [];
    775     removeList.pushUnique = function(element) {
    776       if (this.indexOf(element) == -1)
    777         this.push(element);
    778     };
    779 
    780     for (var y = 0; y < this.downloadList.length; y++) {
    781       var found = false;
    782       for (var x = 0; x < results.length; x++) {
    783         var element = $(results[x].file_path);
    784         if (this.downloadList[y].element == element) {
    785           found = true;
    786           break;
    787         }
    788       }
    789       if (!found)
    790         removeList.pushUnique(this.downloadList[y]);
    791     }
    792 
    793     for (var i = 0; i < results.length; i++) {
    794       this.update(results[i]);
    795     }
    796 
    797     for (i = 0; i < removeList.length; i++) {
    798       this.remove(removeList[i]);
    799     }
    800   },
    801 
    802   /**
    803   * Handle update of a DownloadItem we're observing.
    804   * @param {Object} result JSON representation of DownloadItem.
    805   */
    806   update: function(result) {
    807     var element = $(result.file_path);
    808     var row = element && element.downloadRow;
    809 
    810     if (!row &&
    811         result.state != 'CANCELLED' &&
    812         result.state != 'INTERRUPTED') {
    813       row = new DownloadRow(this, result);
    814     }
    815 
    816     row && row.downloadUpdated(result);
    817   },
    818 };
    819 
    820 </script>
    821 <body onload="init();" onclick="menu.clear()" onselectstart="return false"
    822   i18n-values=".style.fontFamily:fontfamily">
    823 <div id="header">
    824   <div id="currenttitle"></div>
    825 </div><br>
    826 <div id="main" class="columnlist"></div>
    827 <div id="showalldownloads" class="showalldownloads">
    828   <span id="showalldownloadstext" class="showalldownloadstext"
    829     onclick="showAllDownloads()"></span>
    830 </div>
    831 </body>
    832 </html>
    833