Fossil

Check-in [6b645d63]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Perform table sorting using a separate javascript file rather than using in-line javascript.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:6b645d631d44b94d8e95c011c7bf1e5eafd136b57edde78ab64940e0ab83b769
User & Date: drh 2017-12-06 10:01:02
Context
2017-12-06
10:28
Move the in-line javascript associated with the user capability editing into a separate script file. check-in: ef63351d user: drh tags: trunk
10:01
Perform table sorting using a separate javascript file rather than using in-line javascript. check-in: 6b645d63 user: drh tags: trunk
02:02
Move all the in-line javascript from the file tree browser into a separate "tree.js" script. check-in: 2f5c2f65 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/branch.c.

391
392
393
394
395
396
397
398

399
400
401
402
403
404
405
...
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
  style_header("Branches");
  style_adunit_config(ADUNIT_RIGHT_OK);
  style_submenu_checkbox("colors", "Use Branch Colors", 0, 0);
  login_anonymous_available();

  db_prepare(&q, brlistQuery/*works-like:""*/);
  rNow = db_double(0.0, "SELECT julianday('now')");
  @ <div class="brlist"><table id="branchlisttable">

  @ <thead><tr>
  @ <th>Branch Name</th>
  @ <th>Age</th>
  @ <th>Check-ins</th>
  @ <th>Status</th>
  @ <th>Resolution</th>
  @ </tr></thead><tbody>
................................................................................
    }else{
      @ <td></td>
    }
    @ </tr>
  }
  @ </tbody></table></div>
  db_finalize(&q);
  output_table_sorting_javascript("branchlisttable","tkNtt",2);
  style_footer();
}

/*
** WEBPAGE: brlist
** Show a list of branches.  With no query parameters, a sortable table
** is used to show all branches.  If query parameters are present a







|
>







 







|







391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
...
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
  style_header("Branches");
  style_adunit_config(ADUNIT_RIGHT_OK);
  style_submenu_checkbox("colors", "Use Branch Colors", 0, 0);
  login_anonymous_available();

  db_prepare(&q, brlistQuery/*works-like:""*/);
  rNow = db_double(0.0, "SELECT julianday('now')");
  @ <div class="brlist">
  @ <table class='sortable' data-column-types='tkNtt' data-init-sort='2'>
  @ <thead><tr>
  @ <th>Branch Name</th>
  @ <th>Age</th>
  @ <th>Check-ins</th>
  @ <th>Status</th>
  @ <th>Resolution</th>
  @ </tr></thead><tbody>
................................................................................
    }else{
      @ <td></td>
    }
    @ </tr>
  }
  @ </tbody></table></div>
  db_finalize(&q);
  style_table_sorter();
  style_footer();
}

/*
** WEBPAGE: brlist
** Show a list of branches.  With no query parameters, a sortable table
** is used to show all branches.  If query parameters are present a

Changes to src/doc.c.

380
381
382
383
384
385
386
387

388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
void mimetype_list_page(void){
  int i;
  mimetype_verify();
  style_header("Mimetype List");
  @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
  @ suffixes and the following table to guess at the appropriate mimetype
  @ for each document.</p>
  @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'>

  @ <thead>
  @ <tr><th>Suffix<th>Mimetype
  @ </thead>
  @ <tbody>
  for(i=0; i<count(aMime); i++){
    @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr>
  }
  @ </tbody></table>
  output_table_sorting_javascript("mimeTable","tt",1);
  style_footer();
}

/*
** Check to see if the file in the pContent blob is "embedded HTML".  Return
** true if it is, and fill pTitle with the document title.
**







|
>








|







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
void mimetype_list_page(void){
  int i;
  mimetype_verify();
  style_header("Mimetype List");
  @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
  @ suffixes and the following table to guess at the appropriate mimetype
  @ for each document.</p>
  @ <table class='sortable mimetypetable' border=1 cellpadding=0 \
  @ data-column-types='tt' data-init-sort='1'>
  @ <thead>
  @ <tr><th>Suffix<th>Mimetype
  @ </thead>
  @ <tbody>
  for(i=0; i<count(aMime); i++){
    @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr>
  }
  @ </tbody></table>
  style_table_sorter();
  style_footer();
}

/*
** Check to see if the file in the pContent blob is "embedded HTML".  Return
** true if it is, and fill pTitle with the document title.
**

Changes to src/finfo.c.

684
685
686
687
688
689
690

691
692
693
694
695
696
697
698
699
700
701
...
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
...
759
760
761
762
763
764
765

766
767
768

769
770
771
772
773
774
775
...
812
813
814
815
816
817
818
819
820
821
822
       /* 8 */ "  (SELECT name FROM filename WHERE fnid=mlink.pfnid)"
       "  FROM mlink, event"
       " WHERE mlink.fnid=%d"
       "   AND event.objid=mlink.mid"
       " ORDER BY 1 DESC",
       fnid
    );

    @ <h1>MLINK table for file
    @ <a href='%R/finfo?name=%t(zFName)'>%h(zFName)</a></h1>
    @ <div class='brlist'>
    @ <table id='mlinktable'>
    @ <thead><tr>
    @ <th>Date</th>
    @ <th>Check-in</th>
    @ <th>Parent<br>Check-in</th>
    @ <th>Merge?</th>
    @ <th>New</th>
    @ <th>Old</th>
................................................................................
      }
      @ </tr>
    }
    db_finalize(&q);
    @ </tbody>
    @ </table>
    @ </div>
    output_table_sorting_javascript("mlinktable","tttxtttt",1);
  }else{
    int mid = name_to_rid_www("ci");
    db_prepare(&q,
       "SELECT"
       /* 0 */ "  (SELECT name FROM filename WHERE fnid=mlink.fnid),"
       /* 1 */ "  (SELECT uuid FROM blob WHERE rid=mlink.fid),"
       /* 2 */ "  pid,"
................................................................................
       /* 6 */ "  mperm,"
       /* 7 */ "  isaux"
       "  FROM mlink WHERE mid=%d ORDER BY 1",
       mid
    );
    @ <h1>MLINK table for check-in %h(zCI)</h1>
    render_checkin_context(mid, 1);

    @ <hr />
    @ <div class='brlist'>
    @ <table id='mlinktable'>

    @ <thead><tr>
    @ <th>File</th>
    @ <th>Parent<br>Check-in</th>
    @ <th>Merge?</th>
    @ <th>New</th>
    @ <th>Old</th>
    @ <th>Exe<br>Bit?</th>
................................................................................
      }
      @ </tr>
    }
    db_finalize(&q);
    @ </tbody>
    @ </table>
    @ </div>
    output_table_sorting_javascript("mlinktable","ttxtttt",1);
  }
  style_footer();
}







>



|







 







<







 







>


<
>







 







<



684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
...
742
743
744
745
746
747
748

749
750
751
752
753
754
755
...
759
760
761
762
763
764
765
766
767
768

769
770
771
772
773
774
775
776
...
813
814
815
816
817
818
819

820
821
822
       /* 8 */ "  (SELECT name FROM filename WHERE fnid=mlink.pfnid)"
       "  FROM mlink, event"
       " WHERE mlink.fnid=%d"
       "   AND event.objid=mlink.mid"
       " ORDER BY 1 DESC",
       fnid
    );
    style_table_sorter();
    @ <h1>MLINK table for file
    @ <a href='%R/finfo?name=%t(zFName)'>%h(zFName)</a></h1>
    @ <div class='brlist'>
    @ <table class='sortable' data-column-types='tttxtttt' data-init-sort='1'>
    @ <thead><tr>
    @ <th>Date</th>
    @ <th>Check-in</th>
    @ <th>Parent<br>Check-in</th>
    @ <th>Merge?</th>
    @ <th>New</th>
    @ <th>Old</th>
................................................................................
      }
      @ </tr>
    }
    db_finalize(&q);
    @ </tbody>
    @ </table>
    @ </div>

  }else{
    int mid = name_to_rid_www("ci");
    db_prepare(&q,
       "SELECT"
       /* 0 */ "  (SELECT name FROM filename WHERE fnid=mlink.fnid),"
       /* 1 */ "  (SELECT uuid FROM blob WHERE rid=mlink.fid),"
       /* 2 */ "  pid,"
................................................................................
       /* 6 */ "  mperm,"
       /* 7 */ "  isaux"
       "  FROM mlink WHERE mid=%d ORDER BY 1",
       mid
    );
    @ <h1>MLINK table for check-in %h(zCI)</h1>
    render_checkin_context(mid, 1);
    style_table_sorter();
    @ <hr />
    @ <div class='brlist'>

    @ <table class='sortable' data-column-types='ttxtttt' data-init-sort='1'>
    @ <thead><tr>
    @ <th>File</th>
    @ <th>Parent<br>Check-in</th>
    @ <th>Merge?</th>
    @ <th>New</th>
    @ <th>Old</th>
    @ <th>Exe<br>Bit?</th>
................................................................................
      }
      @ </tr>
    }
    db_finalize(&q);
    @ </tbody>
    @ </table>
    @ </div>

  }
  style_footer();
}

Changes to src/main.mk.

197
198
199
200
201
202
203

204
205
206
207
208
209
210
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \

  $(SRCDIR)/tree.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \







>







197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \

Changes to src/name.c.

1113
1114
1115
1116
1117
1118
1119
1120

1121
1122
1123
1124
1125
1126
1127
....
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
    "SELECT description.rid, description.uuid, description.summary,"
    "       length(blob.content), coalesce(delta.srcid,''),"
    "       datetime(description.ctime)"
    "  FROM description, blob LEFT JOIN delta ON delta.rid=blob.rid"
    " WHERE description.rid=blob.rid"
    " ORDER BY length(content) DESC"
  );
  @ <table cellpadding="2" cellspacing="0" border="1" id="bigblobtab">

  @ <thead><tr><th align="right">Size<th align="right">RID
  @ <th align="right">Delta From<th>Hash<th>Description<th>Date</tr></thead>
  @ <tbody>
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q,0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDesc = db_column_text(&q, 2);
................................................................................
    @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
    @ <td align="left">%h(zDesc)</td>
    @ <td align="left">%z(href("%R/timeline?c=%T",zDate))%s(zDate)</a></td>
    @ </tr>
  }
  @ </tbody></table>
  db_finalize(&q);
  output_table_sorting_javascript("bigblobtab", "NnnttT", -1);
  style_footer();
}

/*
** COMMAND: test-unsent
**
** Usage: %fossil test-unsent







|
>







 







|







1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
....
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
    "SELECT description.rid, description.uuid, description.summary,"
    "       length(blob.content), coalesce(delta.srcid,''),"
    "       datetime(description.ctime)"
    "  FROM description, blob LEFT JOIN delta ON delta.rid=blob.rid"
    " WHERE description.rid=blob.rid"
    " ORDER BY length(content) DESC"
  );
  @ <table cellpadding="2" cellspacing="0" border="1" \
  @  class='sortable' data-column-types='NnnttT' data-init-sort='0'>
  @ <thead><tr><th align="right">Size<th align="right">RID
  @ <th align="right">Delta From<th>Hash<th>Description<th>Date</tr></thead>
  @ <tbody>
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q,0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDesc = db_column_text(&q, 2);
................................................................................
    @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
    @ <td align="left">%h(zDesc)</td>
    @ <td align="left">%z(href("%R/timeline?c=%T",zDate))%s(zDate)</a></td>
    @ </tr>
  }
  @ </tbody></table>
  db_finalize(&q);
  style_table_sorter();
  style_footer();
}

/*
** COMMAND: test-unsent
**
** Usage: %fossil test-unsent

Changes to src/report.c.

943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
....
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
      break;
    }
  }
  rc = sqlite3_finalize(pStmt);
  fossil_free((void *)azVals);
  return rc;
}

/*
** Output Javascript code that will enables sorting of the table with
** the id zTableId by clicking.
**
** The javascript was originally derived from:
**
**     http://www.webtoolkit.info/sortable-html-table.html
**
** But there have been extensive modifications.
**
** This variation allows column types to be expressed using the second
** argument.  Each character of the second argument represent a column.
**
**       t      Sort by text
**       n      Sort numerically
**       k      Sort by the data-sortkey property
**       x      This column is not sortable
**
** Capital letters mean sort in reverse order.
** If there are fewer characters in zColumnTypes[] than their are columns,
** then all extra columns assume type "t" (text).
**
** The third parameter is the column that was initially sorted (using 1-based
** column numbers, like SQL).  Make this value 0 if none of the columns are
** initially sorted.  Make the value negative if the column is initially sorted
** in reverse order.
**
** Clicking on the same column header twice in a row inverts the sort.
*/
void output_table_sorting_javascript(
  const char *zTableId,      /* ID of table to sort */
  const char *zColumnTypes,  /* String for column types */
  int iInitSort              /* Initially sorted column. Leftmost is 1. 0 for NONE */
){
  @ <script>
  @ function SortableTable(tableEl,columnTypes,initSort){
  @   this.tbody = tableEl.getElementsByTagName('tbody');
  @   this.columnTypes = columnTypes;
  @   var ncols = tableEl.rows[0].cells.length;
  @   for(var i = columnTypes.length; i<=ncols; i++){this.columnTypes += 't';}
  @   this.sort = function (cell) {
  @     var column = cell.cellIndex;
  @     var sortFn;
  @     switch( cell.sortType ){
  if( strchr(zColumnTypes,'n') ){
    @       case "n": sortFn = this.sortNumeric;  break;
  }
  if( strchr(zColumnTypes,'N') ){
    @       case "N": sortFn = this.sortReverseNumeric;  break;
  }
  @       case "t": sortFn = this.sortText;  break;
  if( strchr(zColumnTypes,'T') ){
    @       case "T": sortFn = this.sortReverseText;  break;
  }
  if( strchr(zColumnTypes,'k') ){
    @       case "k": sortFn = this.sortKey;  break;
  }
  if( strchr(zColumnTypes,'K') ){
    @       case "K": sortFn = this.sortReverseKey;  break;
  }
  @       default:  return;
  @     }
  @     this.sortIndex = column;
  @     var newRows = new Array();
  @     for (j = 0; j < this.tbody[0].rows.length; j++) {
  @        newRows[j] = this.tbody[0].rows[j];
  @     }
  @     if( this.sortIndex==Math.abs(this.prevColumn)-1 ){
  @       newRows.reverse();
  @       this.prevColumn = -this.prevColumn;
  @     }else{
  @       newRows.sort(sortFn);
  @       this.prevColumn = this.sortIndex+1;
  @     }
  @     for (i=0;i<newRows.length;i++) {
  @       this.tbody[0].appendChild(newRows[i]);
  @     }
  @     this.setHdrIcons();
  @   }
  @   this.setHdrIcons = function() {
  @     for (var i=0; i<this.hdrRow.cells.length; i++) {
  @       if( this.columnTypes[i]=='x' ) continue;
  @       var sortType;
  @       if( this.prevColumn==i+1 ){
  @         sortType = 'asc';
  @       }else if( this.prevColumn==(-1-i) ){
  @         sortType = 'desc'
  @       }else{
  @         sortType = 'none';
  @       }
  @       var hdrCell = this.hdrRow.cells[i];
  @       var clsName = hdrCell.className.replace(/\s*\bsort\s*\w+/, '');
  @       clsName += ' sort ' + sortType;
  @       hdrCell.className = clsName;
  @     }
  @   }
  @   this.sortText = function(a,b) {
  @     var i = thisObject.sortIndex;
  @     aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  @     bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  @     if(aa<bb) return -1;
  @     if(aa==bb) return a.rowIndex-b.rowIndex;
  @     return 1;
  @   }
  if( strchr(zColumnTypes,'T') ){
    @   this.sortReverseText = function(a,b) {
    @     var i = thisObject.sortIndex;
    @     aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    @     bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    @     if(aa<bb) return +1;
    @     if(aa==bb) return a.rowIndex-b.rowIndex;
    @     return -1;
    @   }
  }
  if( strchr(zColumnTypes,'n') ){
    @   this.sortNumeric = function(a,b) {
    @     var i = thisObject.sortIndex;
    @     aa = parseFloat(a.cells[i].textContent);
    @     if (isNaN(aa)) aa = 0;
    @     bb = parseFloat(b.cells[i].textContent);
    @     if (isNaN(bb)) bb = 0;
    @     if(aa==bb) return a.rowIndex-b.rowIndex;
    @     return aa-bb;
    @   }
  }
  if( strchr(zColumnTypes,'N') ){
    @   this.sortReverseNumeric = function(a,b) {
    @     var i = thisObject.sortIndex;
    @     aa = parseFloat(a.cells[i].textContent);
    @     if (isNaN(aa)) aa = 0;
    @     bb = parseFloat(b.cells[i].textContent);
    @     if (isNaN(bb)) bb = 0;
    @     if(aa==bb) return a.rowIndex-b.rowIndex;
    @     return bb-aa;
    @   }
  }
  if( strchr(zColumnTypes,'k') ){
    @   this.sortKey = function(a,b) {
    @     var i = thisObject.sortIndex;
    @     aa = a.cells[i].getAttribute("data-sortkey");
    @     bb = b.cells[i].getAttribute("data-sortkey");
    @     if(aa<bb) return -1;
    @     if(aa==bb) return a.rowIndex-b.rowIndex;
    @     return 1;
    @   }
  }
  if( strchr(zColumnTypes,'K') ){
    @   this.sortReverseKey = function(a,b) {
    @     var i = thisObject.sortIndex;
    @     aa = a.cells[i].getAttribute("data-sortkey");
    @     bb = b.cells[i].getAttribute("data-sortkey");
    @     if(aa<bb) return +1;
    @     if(aa==bb) return a.rowIndex-b.rowIndex;
    @     return -1;
    @   }
  }
  @   var x = tableEl.getElementsByTagName('thead');
  @   if(!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length>0)){
  @     return;
  @   }
  @   if(x && x[0].rows && x[0].rows.length > 0) {
  @     this.hdrRow = x[0].rows[0];
  @   } else {
  @     return;
  @   }
  @   var thisObject = this;
  @   this.prevColumn = initSort;
  @   for (var i=0; i<this.hdrRow.cells.length; i++) {
  @     if( columnTypes[i]=='x' ) continue;
  @     var hdrcell = this.hdrRow.cells[i];
  @     hdrcell.sTable = this;
  @     hdrcell.style.cursor = "pointer";
  @     hdrcell.sortType = columnTypes[i] || 't';
  @     hdrcell.onclick = function () {
  @       this.sTable.sort(this);
  @       return false;
  @     }
  @   }
  @   this.setHdrIcons()
  @ }
  @ var t = new SortableTable(gebi("%s(zTableId)"),"%s(zColumnTypes)",%d(iInitSort));
  @ </script>
}


/*
** WEBPAGE: rptview
**
** Generate a report.  The rn query parameter is the report number
** corresponding to REPORTFMT.RN.  If the tablist query parameter exists,
** then the output consists of lines of tab-separated fields instead of
................................................................................
    }
    if( g.perm.NewTkt ){
      style_submenu_element("New Ticket", "%s/tktnew", g.zTop);
    }
    style_header("%s", zTitle);
    output_color_key(zClrKey, 1,
        "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\"");
    @ <table border="1" cellpadding="2" cellspacing="0" class="report"
    @  id="reportTable">
    sState.rn = rn;
    sState.nCount = 0;
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2);
    report_unrestrict_sql();
    @ </tbody></table>
    if( zErr1 ){
      @ <p class="reportError">Error: %h(zErr1)</p>
    }else if( zErr2 ){
      @ <p class="reportError">Error: %h(zErr2)</p>
    }
    output_table_sorting_javascript("reportTable","",0);
    style_footer();
  }else{
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2);
    report_unrestrict_sql();
    cgi_set_content_type("text/plain");
  }







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
|











|







943
944
945
946
947
948
949

























































































































































































950
951
952
953
954
955
956
....
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
      break;
    }
  }
  rc = sqlite3_finalize(pStmt);
  fossil_free((void *)azVals);
  return rc;
}


























































































































































































/*
** WEBPAGE: rptview
**
** Generate a report.  The rn query parameter is the report number
** corresponding to REPORTFMT.RN.  If the tablist query parameter exists,
** then the output consists of lines of tab-separated fields instead of
................................................................................
    }
    if( g.perm.NewTkt ){
      style_submenu_element("New Ticket", "%s/tktnew", g.zTop);
    }
    style_header("%s", zTitle);
    output_color_key(zClrKey, 1,
        "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\"");
    @ <table border="1" cellpadding="2" cellspacing="0" class="report sortable"
    @  data-column-types='' data-init-sort='0'>
    sState.rn = rn;
    sState.nCount = 0;
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2);
    report_unrestrict_sql();
    @ </tbody></table>
    if( zErr1 ){
      @ <p class="reportError">Error: %h(zErr1)</p>
    }else if( zErr2 ){
      @ <p class="reportError">Error: %h(zErr2)</p>
    }
    style_table_sorter();
    style_footer();
  }else{
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2);
    report_unrestrict_sql();
    cgi_set_content_type("text/plain");
  }

Changes to src/setup.c.

206
207
208
209
210
211
212
213

214
215
216
217
218
219
220
...
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
....
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
....
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
....
2240
2241
2242
2243
2244
2245
2246
2247
2248

2249
2250
2251
2252
2253
2254
2255
....
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
    }
    db_finalize(&s);
  }else{
    style_header("Users With Capabilities \"%h\"", zWith);
  }
  @ </tbody></table>
  @ <div class='section'>Users</div>
  @ <table border=1 cellpadding=2 cellspacing=0 class='userTable' id='userlist'>

  @ <thead><tr>
  @ <th>ID<th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
  @ <tbody>
  db_multi_exec(
     "CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL) WITHOUT ROWID;"
  );
  if( db_table_exists("repository","accesslog") ){
................................................................................
    @ <td>%h(zExp?zExp:"")
    @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
    @ </tr>
    fossil_free(zAge);
  }
  @ </tbody></table>
  db_finalize(&s);
  output_table_sorting_javascript("userlist","nktxTTK",2);
  style_footer();
}

/*
** Render the user-capability table
*/
static void setup_usercap_table(void){
................................................................................
    @ </form></p>
    @ <br />For best results, use the same number of <a href="setup_access#ipt">
    @ IP octets</a> in the login cookie across all repositories in the
    @ same Login Group.
    @ <hr /><h2>Implementation Details</h2>
    @ <p>The following are fields from the CONFIG table related to login-groups,
    @ provided here for instructional and debugging purposes:</p>
    @ <table border='1' id='configTab'>
    @ <thead><tr>
    @ <th>Config.Name<th>Config.Value<th>Config.mtime</tr>
    @ </thead><tbody>
    db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config"
                   " WHERE name GLOB 'peer-*'"
                   "    OR name GLOB 'project-*'"
                   "    OR name GLOB 'login-group-*'"
................................................................................
    while( db_step(&q)==SQLITE_ROW ){
      @ <tr><td>%h(db_column_text(&q,0))</td>
      @ <td>%h(db_column_text(&q,1))</td>
      @ <td>%h(db_column_text(&q,2))</td></tr>
    }
    db_finalize(&q);
    @ </tbody></table>
    output_table_sorting_javascript("configTab","ttt",1);
  }
  style_footer();
}

/*
** WEBPAGE: setup_timeline
**
................................................................................
    if( prevx<0 ) prevx = 0;
    @ <p><a href="admin_log?n=%d(limit)&x=%d(prevx)">[Newer]</a></p>
  }
  db_prepare(&stLog,
    "SELECT datetime(time,'unixepoch'), who, page, what "
    "FROM admin_log "
    "ORDER BY time DESC");

  @ <table id="adminLogTable" class="adminLogTable" width="100%%">

  @ <thead>
  @ <th>Time</th>
  @ <th>User</th>
  @ <th>Page</th>
  @ <th width="60%%">Message</th>
  @ </thead><tbody>
  while( SQLITE_ROW == db_step(&stLog) ){
................................................................................
    @ <td>%h(zMessage)</td>
    @ </tr>
  }
  @ </tbody></table>
  if( counter>ofst+limit ){
    @ <p><a href="admin_log?n=%d(limit)&x=%d(limit+ofst)">[Older]</a></p>
  }

  output_table_sorting_javascript("adminLogTable", "Tttx", 1);
  style_footer();
}

/*
** WEBPAGE: srchsetup
**
** Configure the search engine.  Requires Admin privilege.







|
>







 







|







 







|







 







|







 







|
|
>







 







<
<







206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
....
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
....
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
....
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
....
2269
2270
2271
2272
2273
2274
2275


2276
2277
2278
2279
2280
2281
2282
    }
    db_finalize(&s);
  }else{
    style_header("Users With Capabilities \"%h\"", zWith);
  }
  @ </tbody></table>
  @ <div class='section'>Users</div>
  @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
  @  data-column-types='nktxTTK' data-init-sort='2'>
  @ <thead><tr>
  @ <th>ID<th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
  @ <tbody>
  db_multi_exec(
     "CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL) WITHOUT ROWID;"
  );
  if( db_table_exists("repository","accesslog") ){
................................................................................
    @ <td>%h(zExp?zExp:"")
    @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
    @ </tr>
    fossil_free(zAge);
  }
  @ </tbody></table>
  db_finalize(&s);
  style_table_sorter();
  style_footer();
}

/*
** Render the user-capability table
*/
static void setup_usercap_table(void){
................................................................................
    @ </form></p>
    @ <br />For best results, use the same number of <a href="setup_access#ipt">
    @ IP octets</a> in the login cookie across all repositories in the
    @ same Login Group.
    @ <hr /><h2>Implementation Details</h2>
    @ <p>The following are fields from the CONFIG table related to login-groups,
    @ provided here for instructional and debugging purposes:</p>
    @ <table border='1' class='sortable' data-column-types='ttt' data-init-sort='1'>
    @ <thead><tr>
    @ <th>Config.Name<th>Config.Value<th>Config.mtime</tr>
    @ </thead><tbody>
    db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config"
                   " WHERE name GLOB 'peer-*'"
                   "    OR name GLOB 'project-*'"
                   "    OR name GLOB 'login-group-*'"
................................................................................
    while( db_step(&q)==SQLITE_ROW ){
      @ <tr><td>%h(db_column_text(&q,0))</td>
      @ <td>%h(db_column_text(&q,1))</td>
      @ <td>%h(db_column_text(&q,2))</td></tr>
    }
    db_finalize(&q);
    @ </tbody></table>
    style_table_sorter();
  }
  style_footer();
}

/*
** WEBPAGE: setup_timeline
**
................................................................................
    if( prevx<0 ) prevx = 0;
    @ <p><a href="admin_log?n=%d(limit)&x=%d(prevx)">[Newer]</a></p>
  }
  db_prepare(&stLog,
    "SELECT datetime(time,'unixepoch'), who, page, what "
    "FROM admin_log "
    "ORDER BY time DESC");
  style_table_sorter();
  @ <table class="sortable adminLogTable" width="100%%" \
  @  data-column-types='Tttx' data-init-sort='1'>
  @ <thead>
  @ <th>Time</th>
  @ <th>User</th>
  @ <th>Page</th>
  @ <th width="60%%">Message</th>
  @ </thead><tbody>
  while( SQLITE_ROW == db_step(&stLog) ){
................................................................................
    @ <td>%h(zMessage)</td>
    @ </tr>
  }
  @ </tbody></table>
  if( counter>ofst+limit ){
    @ <p><a href="admin_log?n=%d(limit)&x=%d(limit+ofst)">[Older]</a></p>
  }


  style_footer();
}

/*
** WEBPAGE: srchsetup
**
** Configure the search engine.  Requires Admin privilege.

Added src/sorttable.js.















































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/* Javascript code that will enables sorting of the table.  This code is
** derived from
**
**     http://www.webtoolkit.info/sortable-html-table.html
**
** but with extensive modifications.
**
** All tables with class "sortable" are registered with the SortableTable()
** function.  Example:
**
**   <table class='sortable' data-column-types='tnkx' data-init-sort='2'>
**
** Column data types are determined by the data-column-types attribute of
** the table.  The value of data-column-types is a string where each 
** character of the string represents a datatype for one column in the
** table.
**
**       t      Sort by text
**       n      Sort numerically
**       k      Sort by the data-sortkey property
**       x      This column is not sortable
**
** Capital letters mean sort in reverse order.
** If there are fewer characters in zColumnTypes[] than their are columns,
** then all extra columns assume type "t" (text).
**
** If a column of the table is initially sorted, then the data-init-sort
** attribute should be set to the 1-based index of that column.  (The
** left-most column is 1, the next column to the right is 2, and so forth.)
** A value of 0 in the data-init-sort attribute indicates that no columns
** where initially sorted.
**
** Clicking on the same column header twice in a row inverts the sort.
*/
function SortableTable(tableEl){
  var columnTypes = tableEl.getAttribute("data-column-types");
  var initSort = tableEl.getAttribute("data-init-sort");
  this.tbody = tableEl.getElementsByTagName('tbody');
  this.columnTypes = columnTypes;
  var ncols = tableEl.rows[0].cells.length;
  for(var i = columnTypes.length; i<=ncols; i++){this.columnTypes += 't';}
  this.sort = function (cell) {
    var column = cell.cellIndex;
    var sortFn;
    switch( cell.sortType ){
      case "n": sortFn = this.sortNumeric;  break;
      case "N": sortFn = this.sortReverseNumeric;  break;
      case "t": sortFn = this.sortText;  break;
      case "T": sortFn = this.sortReverseText;  break;
      case "k": sortFn = this.sortKey;  break;
      case "K": sortFn = this.sortReverseKey;  break;
      default:  return;
    }
    this.sortIndex = column;
    var newRows = new Array();
    for (j = 0; j < this.tbody[0].rows.length; j++) {
       newRows[j] = this.tbody[0].rows[j];
    }
    if( this.sortIndex==Math.abs(this.prevColumn)-1 ){
      newRows.reverse();
      this.prevColumn = -this.prevColumn;
    }else{
      newRows.sort(sortFn);
      this.prevColumn = this.sortIndex+1;
    }
    for (i=0;i<newRows.length;i++) {
      this.tbody[0].appendChild(newRows[i]);
    }
    this.setHdrIcons();
  }
  this.setHdrIcons = function() {
    for (var i=0; i<this.hdrRow.cells.length; i++) {
      if( this.columnTypes[i]=='x' ) continue;
      var sortType;
      if( this.prevColumn==i+1 ){
        sortType = 'asc';
      }else if( this.prevColumn==(-1-i) ){
        sortType = 'desc'
      }else{
        sortType = 'none';
      }
      var hdrCell = this.hdrRow.cells[i];
      var clsName = hdrCell.className.replace(/\s*\bsort\s*\w+/, '');
      clsName += ' sort ' + sortType;
      hdrCell.className = clsName;
    }
  }
  this.sortText = function(a,b) {
    var i = thisObject.sortIndex;
    aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    if(aa<bb) return -1;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return 1;
  }
  this.sortReverseText = function(a,b) {
    var i = thisObject.sortIndex;
    aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    if(aa<bb) return +1;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return -1;
  }
  this.sortNumeric = function(a,b) {
    var i = thisObject.sortIndex;
    aa = parseFloat(a.cells[i].textContent);
    if (isNaN(aa)) aa = 0;
    bb = parseFloat(b.cells[i].textContent);
    if (isNaN(bb)) bb = 0;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return aa-bb;
  }
  this.sortReverseNumeric = function(a,b) {
    var i = thisObject.sortIndex;
    aa = parseFloat(a.cells[i].textContent);
    if (isNaN(aa)) aa = 0;
    bb = parseFloat(b.cells[i].textContent);
    if (isNaN(bb)) bb = 0;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return bb-aa;
  }
  this.sortKey = function(a,b) {
    var i = thisObject.sortIndex;
    aa = a.cells[i].getAttribute("data-sortkey");
    bb = b.cells[i].getAttribute("data-sortkey");
    if(aa<bb) return -1;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return 1;
  }
  this.sortReverseKey = function(a,b) {
    var i = thisObject.sortIndex;
    aa = a.cells[i].getAttribute("data-sortkey");
    bb = b.cells[i].getAttribute("data-sortkey");
    if(aa<bb) return +1;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return -1;
  }
  var x = tableEl.getElementsByTagName('thead');
  if(!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length>0)){
    return;
  }
  if(x && x[0].rows && x[0].rows.length > 0) {
    this.hdrRow = x[0].rows[0];
  } else {
    return;
  }
  var thisObject = this;
  this.prevColumn = initSort;
  for (var i=0; i<this.hdrRow.cells.length; i++) {
    if( columnTypes[i]=='x' ) continue;
    var hdrcell = this.hdrRow.cells[i];
    hdrcell.sTable = this;
    hdrcell.style.cursor = "pointer";
    hdrcell.sortType = columnTypes[i] || 't';
    hdrcell.onclick = function () {
      this.sTable.sort(this);
      return false;
    }
  }
  this.setHdrIcons()
}
(function(){
  var x = document.getElementsByClassName("sortable");
  for(var i=0; i<x.length; i++){
    SortableTable(x[i]);
  }
}())

Changes to src/statrep.c.

200
201
202
203
204
205
206






207
208

209
210
211
212
213
214
215
...
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
...
342
343
344
345
346
347
348

349
350
351
352
353
354
355
356
357
...
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
...
419
420
421
422
423
424
425

426
427
428
429
430
431
432
433
434
...
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
...
503
504
505
506
507
508
509

510
511
512
513
514
515
516
517
518
...
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
...
584
585
586
587
588
589
590

591
592
593

594
595
596
597
598
599
600
...
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654

655
656
657
658
659
660
661
662
663
664
665
...
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
             includeMonth ? 7 : 4, zUserName);
  @ <h1>Timeline Events (%s(stats_report_label_for_type()))
  @ by year%s(includeMonth ? "/month" : "")
  if( zUserName ){
    @ for user %h(zUserName)
  }
  @ </h1>






  @ <table class='statistics-report-table-events' border='0' cellpadding='2'
  @  cellspacing='0' id='statsTable'>

  @ <thead>
  @ <th>%s(zTimeLabel)</th>
  @ <th>Events</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </thead><tbody>
  /*
     Run the query twice. The first time we calculate the maximum
................................................................................
  if(nEventTotal){
    const char *zAvgLabel = includeMonth ? "month" : "year";
    int nAvg = iterations ? (nEventTotal/iterations) : 0;
    @ <br /><div>Total events: %d(nEventTotal)
    @ <br />Average per active %s(zAvgLabel): %d(nAvg)
    @ </div>
  }
  if( !includeMonth ){
    output_table_sorting_javascript("statsTable","tnx",-1);
  }
}

/*
** Implements the "byuser" view for /reports.
*/
static void stats_report_by_user(){
  Stmt query = empty_Stmt;
................................................................................
                         " GROUP BY ifnull(euser,user) ORDER BY count(*) DESC;"
  );
  if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){
    @ <center><svg width=700 height=400>
    piechart_render(700, 400, PIE_OTHER|PIE_PERCENT);
    @ </svg></centre><hr />
  }

  @ <table class='statistics-report-table-events' border='0'
  @ cellpadding='2' cellspacing='0' id='statsTable'>
  @ <thead><tr>
  @ <th>User</th>
  @ <th>Events</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </tr></thead><tbody>
  db_prepare(&query,
               "SELECT ifnull(euser,user), "
................................................................................
    /*
      Potential improvement: calculate the min/max event counts and
      use percent-based graph bars.
    */
  }
  @ </tbody></table>
  db_finalize(&query);
  output_table_sorting_javascript("statsTable","tkx",2);
}

/*
** Implements the "byfile" view for /reports. If zUserName is not NULL then the
** report is restricted to events created by the named user account.
*/
static void stats_report_by_file(const char *zUserName){
................................................................................
  );
  mxEvent = db_int(1, "SELECT max(cnt) FROM statrep");
  @ <h1>Check-ins Per File
  if( zUserName ){
    @ for user %h(zUserName)
  }
  @ </h1>

  @ <table class='statistics-report-table-events' border='0'
  @ cellpadding='2' cellspacing='0' id='statsTable'>
  @ <thead><tr>
  @ <th>File</th>
  @ <th>Check-ins</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </tr></thead><tbody>
  while( SQLITE_ROW == db_step(&query) ){
    const char *zFile = db_column_text(&query, 0);
................................................................................
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(sz)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
  }
  @ </tbody></table>
  db_finalize(&query);
  output_table_sorting_javascript("statsTable","tNx",2);
}

/*
** Implements the "byweekday" view for /reports. If zUserName is not NULL then
** the report is restricted to events created by the named user account.
*/
static void stats_report_day_of_week(const char *zUserName){
................................................................................
    , zUserName
  );
  if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){
    @ <center><svg width=700 height=400>
    piechart_render(700, 400, PIE_OTHER|PIE_PERCENT);
    @ </svg></centre><hr />
  }

  @ <table class='statistics-report-table-events' border='0'
  @ cellpadding='2' cellspacing='0' id='statsTable'>
  @ <thead><tr>
  @ <th>DoW</th>
  @ <th>Day</th>
  @ <th>Events</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </tr></thead><tbody>
  while( SQLITE_ROW == db_step(&query) ){
................................................................................
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
  }
  @ </tbody></table>
  db_finalize(&query);
  output_table_sorting_javascript("statsTable","ntnx",1);
}


/*
** Helper for stats_report_by_month_year(), which generates a list of
** week numbers. zTimeframe should be either a timeframe in the form YYYY
** or YYYY-MM. If zUserName is not NULL then the report is restricted to events
................................................................................
             " GROUP BY wk ORDER BY wk DESC", zYear, zUserName);
  @ <h1>Timeline events (%h(stats_report_label_for_type()))
  @ for the calendar weeks of %h(zYear)
  if( zUserName ){
    @  for user %h(zUserName)
  }
  @ </h1>

  cgi_printf("<table class='statistics-report-table-events' "
              "border='0' cellpadding='2' width='100%%' "
             "cellspacing='0' id='statsTable'>");

  cgi_printf("<thead><tr>"
             "<th>Week</th>"
             "<th>Events</th>"
             "<th width='90%%'><!-- relative commits graph --></th>"
             "</tr></thead>"
             "<tbody>");
  while( SQLITE_ROW == db_step(&q) ){
................................................................................
  cgi_printf("</tbody></table>");
  if(total){
    int nAvg = iterations ? (total/iterations) : 0;
    cgi_printf("<br /><div>Total events: %d<br />"
               "Average per active week: %d</div>",
               total, nAvg);
  }
  output_table_sorting_javascript("statsTable","tnx",-1);
}


/*
** Generate a report that shows the most recent change for each user.
*/
static void stats_report_last_change(void){
  Stmt s;
  double rNow;
  char *zBaseUrl;

  stats_report_init_view();

  @ <h1>Event Summary
  @ (%s(stats_report_label_for_type())) by User</h1>
  @ <table border=1  class='statistics-report-table-events'
  @ cellpadding=2 cellspacing=0 id='lastchng'>
  @ <thead><tr>
  @ <th>User<th>Total Changes<th>Last Change</tr></thead>
  @ <tbody>
  zBaseUrl = mprintf("%R/timeline?y=%t&u=", PD("type","ci"));
  db_prepare(&s,
    "SELECT coalesce(euser,user),"
    "       count(*),"
................................................................................
    @ <td>%d(cnt)
    @ <td data-sortkey='%f(rMTime)' style='white-space:nowrap'>%s(zAge?zAge:"")
    @ </tr>
    fossil_free(zAge);
  }
  @ </tbody></table>
  db_finalize(&s);
  output_table_sorting_javascript("lastchng","tNK",3);
}


/* Report types
*/
#define RPT_BYFILE    1
#define RPT_BYMONTH   2







>
>
>
>
>
>
|
<
>







 







<
<
<







 







>
|
|







 







<







 







>
|
|







 







|







 







>
|
|







 







<







 







>
|

<
>







 







<












>


|
|







 







<







200
201
202
203
204
205
206
207
208
209
210
211
212
213

214
215
216
217
218
219
220
221
...
319
320
321
322
323
324
325



326
327
328
329
330
331
332
...
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
...
392
393
394
395
396
397
398

399
400
401
402
403
404
405
...
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
...
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
...
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
...
545
546
547
548
549
550
551

552
553
554
555
556
557
558
...
588
589
590
591
592
593
594
595
596
597

598
599
600
601
602
603
604
605
...
640
641
642
643
644
645
646

647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
...
684
685
686
687
688
689
690

691
692
693
694
695
696
697
             includeMonth ? 7 : 4, zUserName);
  @ <h1>Timeline Events (%s(stats_report_label_for_type()))
  @ by year%s(includeMonth ? "/month" : "")
  if( zUserName ){
    @ for user %h(zUserName)
  }
  @ </h1>
  @ <table border='0' cellpadding='2' cellspacing='0' \
  if( !includeMonth ){
    @ class='statistics-report-table-events sortable' \
    @ data-column-types='tnx' data-init-sort='0'>
    style_table_sorter();
  }else{
    @ class='statistics-report-table-events'>

  }
  @ <thead>
  @ <th>%s(zTimeLabel)</th>
  @ <th>Events</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </thead><tbody>
  /*
     Run the query twice. The first time we calculate the maximum
................................................................................
  if(nEventTotal){
    const char *zAvgLabel = includeMonth ? "month" : "year";
    int nAvg = iterations ? (nEventTotal/iterations) : 0;
    @ <br /><div>Total events: %d(nEventTotal)
    @ <br />Average per active %s(zAvgLabel): %d(nAvg)
    @ </div>
  }



}

/*
** Implements the "byuser" view for /reports.
*/
static void stats_report_by_user(){
  Stmt query = empty_Stmt;
................................................................................
                         " GROUP BY ifnull(euser,user) ORDER BY count(*) DESC;"
  );
  if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){
    @ <center><svg width=700 height=400>
    piechart_render(700, 400, PIE_OTHER|PIE_PERCENT);
    @ </svg></centre><hr />
  }
  style_table_sorter();
  @ <table class='statistics-report-table-events sortable' border='0' \
  @ cellpadding='2' cellspacing='0' data-column-types='tkx' data-init-sort='2'>
  @ <thead><tr>
  @ <th>User</th>
  @ <th>Events</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </tr></thead><tbody>
  db_prepare(&query,
               "SELECT ifnull(euser,user), "
................................................................................
    /*
      Potential improvement: calculate the min/max event counts and
      use percent-based graph bars.
    */
  }
  @ </tbody></table>
  db_finalize(&query);

}

/*
** Implements the "byfile" view for /reports. If zUserName is not NULL then the
** report is restricted to events created by the named user account.
*/
static void stats_report_by_file(const char *zUserName){
................................................................................
  );
  mxEvent = db_int(1, "SELECT max(cnt) FROM statrep");
  @ <h1>Check-ins Per File
  if( zUserName ){
    @ for user %h(zUserName)
  }
  @ </h1>
  style_table_sorter();
  @ <table class='statistics-report-table-events sortable' border='0' \
  @ cellpadding='2' cellspacing='0' data-column-types='tNx' data-init-sort='2'>
  @ <thead><tr>
  @ <th>File</th>
  @ <th>Check-ins</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </tr></thead><tbody>
  while( SQLITE_ROW == db_step(&query) ){
    const char *zFile = db_column_text(&query, 0);
................................................................................
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(sz)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
  }
  @ </tbody></table>
  db_finalize(&query);

}

/*
** Implements the "byweekday" view for /reports. If zUserName is not NULL then
** the report is restricted to events created by the named user account.
*/
static void stats_report_day_of_week(const char *zUserName){
................................................................................
    , zUserName
  );
  if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){
    @ <center><svg width=700 height=400>
    piechart_render(700, 400, PIE_OTHER|PIE_PERCENT);
    @ </svg></centre><hr />
  }
  style_table_sorter();
  @ <table class='statistics-report-table-events sortable' border='0' \
  @ cellpadding='2' cellspacing='0' data-column-types='ntnx' data-init-sort='1'>
  @ <thead><tr>
  @ <th>DoW</th>
  @ <th>Day</th>
  @ <th>Events</th>
  @ <th width='90%%'><!-- relative commits graph --></th>
  @ </tr></thead><tbody>
  while( SQLITE_ROW == db_step(&query) ){
................................................................................
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
  }
  @ </tbody></table>
  db_finalize(&query);

}


/*
** Helper for stats_report_by_month_year(), which generates a list of
** week numbers. zTimeframe should be either a timeframe in the form YYYY
** or YYYY-MM. If zUserName is not NULL then the report is restricted to events
................................................................................
             " GROUP BY wk ORDER BY wk DESC", zYear, zUserName);
  @ <h1>Timeline events (%h(stats_report_label_for_type()))
  @ for the calendar weeks of %h(zYear)
  if( zUserName ){
    @  for user %h(zUserName)
  }
  @ </h1>
  style_table_sorter();
  cgi_printf("<table class='statistics-report-table-events sortable' "
              "border='0' cellpadding='2' width='100%%' "

             "cellspacing='0' data-column-types='tnx' data-init-sort='0'>");
  cgi_printf("<thead><tr>"
             "<th>Week</th>"
             "<th>Events</th>"
             "<th width='90%%'><!-- relative commits graph --></th>"
             "</tr></thead>"
             "<tbody>");
  while( SQLITE_ROW == db_step(&q) ){
................................................................................
  cgi_printf("</tbody></table>");
  if(total){
    int nAvg = iterations ? (total/iterations) : 0;
    cgi_printf("<br /><div>Total events: %d<br />"
               "Average per active week: %d</div>",
               total, nAvg);
  }

}


/*
** Generate a report that shows the most recent change for each user.
*/
static void stats_report_last_change(void){
  Stmt s;
  double rNow;
  char *zBaseUrl;

  stats_report_init_view();
  style_table_sorter();
  @ <h1>Event Summary
  @ (%s(stats_report_label_for_type())) by User</h1>
  @ <table border=1  class='statistics-report-table-events sortable' \
  @ cellpadding=2 cellspacing=0 data-column-types='tNK' data-init-sort='3'>
  @ <thead><tr>
  @ <th>User<th>Total Changes<th>Last Change</tr></thead>
  @ <tbody>
  zBaseUrl = mprintf("%R/timeline?y=%t&u=", PD("type","ci"));
  db_prepare(&s,
    "SELECT coalesce(euser,user),"
    "       count(*),"
................................................................................
    @ <td>%d(cnt)
    @ <td data-sortkey='%f(rMTime)' style='white-space:nowrap'>%s(zAge?zAge:"")
    @ </tr>
    fossil_free(zAge);
  }
  @ </tbody></table>
  db_finalize(&s);

}


/* Report types
*/
#define RPT_BYFILE    1
#define RPT_BYMONTH   2

Changes to src/style.c.

79
80
81
82
83
84
85
86
87
88
89

90
91
92
93
94
95
96
...
495
496
497
498
499
500
501







502
503
504
505
506
507
508
...
517
518
519
520
521
522
523



524
525
526
527
528
529
530

/*
** Ad-unit styles.
*/
static unsigned adUnitFlags = 0;

/*
** True if the "href.js" javascript file is required.
*/
static int needHrefJs = 0;



/*
** Generate and return a anchor tag like this:
**
**        <a href="URL">
**  or    <a id="ID">
**
................................................................................
    *pAdFlag = ADUNIT_RIGHT_OK;
    return zAd;
  }else if( !fossil_all_whitespace(zAd = db_get("adunit",0)) ){
    return zAd;
  }
  return 0;
}








/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
  @ <script src='%R/builtin/%s(zFile)?id=%S(MANIFEST_UUID)'></script>
}
................................................................................
    /* Load up the page data */
    bMouseover = (!g.isHuman || db_get_boolean("auto-hyperlink-ishuman",0))
                 && db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
    style_load_one_js_file("href.js");
  }



}

/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){
  const char *zFooter;







|

|
<
>







 







>
>
>
>
>
>
>







 







>
>
>







79
80
81
82
83
84
85
86
87
88

89
90
91
92
93
94
95
96
...
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
...
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540

/*
** Ad-unit styles.
*/
static unsigned adUnitFlags = 0;

/*
** Flags for various javascript files needed prior to </body>
*/
static int needHrefJs = 0;   /* href.js */

static int needSortJs = 0;   /* sorttable.js */

/*
** Generate and return a anchor tag like this:
**
**        <a href="URL">
**  or    <a id="ID">
**
................................................................................
    *pAdFlag = ADUNIT_RIGHT_OK;
    return zAd;
  }else if( !fossil_all_whitespace(zAd = db_get("adunit",0)) ){
    return zAd;
  }
  return 0;
}

/*
** Indicate that the table-sorting javascript is needed.
*/
void style_table_sorter(void){
  needSortJs = 1;
}

/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
  @ <script src='%R/builtin/%s(zFile)?id=%S(MANIFEST_UUID)'></script>
}
................................................................................
    /* Load up the page data */
    bMouseover = (!g.isHuman || db_get_boolean("auto-hyperlink-ishuman",0))
                 && db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
    style_load_one_js_file("href.js");
  }
  if( needSortJs ){
    style_load_one_js_file("sorttable.js");
  }
}

/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){
  const char *zFooter;

Changes to src/timeline.c.

2513
2514
2515
2516
2517
2518
2519

2520

2521
2522
2523
2524
2525
2526
2527
....
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
    const char *zCkin = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zStatus = db_column_int(&q,2) ? "Open"
                                 : "Resolved by editing date";
    const char *zUser = db_column_text(&q, 3);
    char *zHref = href("%R/timeline?c=%S", zCkin);
    if( cnt==0 ){

      @ <div class="brlist"><table id="timewarptable">

      @ <thead><tr>
      @ <th>Check-in</th>
      @ <th>Date</th>
      @ <th>User</th>
      @ <th>Status</th>
      @ </tr></thead><tbody>
    }
................................................................................
    cnt++;
  }
  db_finalize(&q);
  if( cnt==0 ){
    @ <p>No timewarps in this repository</p>
  }else{
    @ </tbody></table></div>
    output_table_sorting_javascript("timewarptable","tttt",2);
  }
  style_footer();
}







>
|
>







 







<



2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
....
2537
2538
2539
2540
2541
2542
2543

2544
2545
2546
    const char *zCkin = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zStatus = db_column_int(&q,2) ? "Open"
                                 : "Resolved by editing date";
    const char *zUser = db_column_text(&q, 3);
    char *zHref = href("%R/timeline?c=%S", zCkin);
    if( cnt==0 ){
      style_table_sorter();
      @ <div class="brlist">
      @ <table class='sortable' data-column-types='tttt' data-init-sort='2'>
      @ <thead><tr>
      @ <th>Check-in</th>
      @ <th>Date</th>
      @ <th>User</th>
      @ <th>Status</th>
      @ </tr></thead><tbody>
    }
................................................................................
    cnt++;
  }
  db_finalize(&q);
  if( cnt==0 ){
    @ <p>No timewarps in this repository</p>
  }else{
    @ </tbody></table></div>

  }
  style_footer();
}

Changes to src/unversioned.c.

506
507
508
509
510
511
512

513
514

515
516
517
518
519
520
521
...
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
    int isDeleted = zHash==0;
    int fullSize = db_column_int(&q, 3);
    char *zAge = human_readable_age((iNow - mtime)/86400.0);
    const char *zLogin = db_column_text(&q, 4);
    int rcvid = db_column_int(&q,5);
    if( zLogin==0 ) zLogin = "";
    if( (n++)==0 ){

      @ <div class="uvlist">
      @ <table cellpadding="2" cellspacing="0" border="1" id="uvtab">

      @ <thead><tr>
      @   <th> Name
      @   <th> Age
      @   <th> Size
      @   <th> User
      @   <th> SHA1
      if( g.perm.Admin ){
................................................................................
    @ <tfoot><tr><td><b>Total over %d(cnt) files</b><td><td>%s(zSzName)
    @ <td><td>
    if( g.perm.Admin ){
      @ <td>
    }
    @ </tfoot>
    @ </table></div>
    output_table_sorting_javascript("uvtab","tkKttN",1);
  }else{
    @ No unversioned files on this server.
  }
  style_footer();
}

/*







>

|
>







 







<







506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
...
559
560
561
562
563
564
565

566
567
568
569
570
571
572
    int isDeleted = zHash==0;
    int fullSize = db_column_int(&q, 3);
    char *zAge = human_readable_age((iNow - mtime)/86400.0);
    const char *zLogin = db_column_text(&q, 4);
    int rcvid = db_column_int(&q,5);
    if( zLogin==0 ) zLogin = "";
    if( (n++)==0 ){
      style_table_sorter();
      @ <div class="uvlist">
      @ <table cellpadding="2" cellspacing="0" border="1" class='sortable' \
      @  data-column-types='tkKttn' data-init-sort='1'>
      @ <thead><tr>
      @   <th> Name
      @   <th> Age
      @   <th> Size
      @   <th> User
      @   <th> SHA1
      if( g.perm.Admin ){
................................................................................
    @ <tfoot><tr><td><b>Total over %d(cnt) files</b><td><td>%s(zSzName)
    @ <td><td>
    if( g.perm.Admin ){
      @ <td>
    }
    @ </tfoot>
    @ </table></div>

  }else{
    @ No unversioned files on this server.
  }
  style_footer();
}

/*

Changes to src/user.c.

685
686
687
688
689
690
691
692

693
694
695
696
697
698
699
...
732
733
734
735
736
737
738
739
740
741
    style_submenu_element("Newer", "%s/access_log?o=%d&n=%d&y=%d",
              g.zTop, skip>=n ? skip-n : 0, n, y);
  }
  rc = db_prepare_ignore_error(&q, "%s", blob_sql_text(&sql));
  fLogEnabled = db_get_boolean("access-log", 0);
  @ <div align="center">Access logging is %s(fLogEnabled?"on":"off").
  @ (Change this on the <a href="setup_settings">settings</a> page.)</div>
  @ <table border="1" cellpadding="5" id="logtable" align="center">

  @ <thead><tr><th width="33%%">Date</th><th width="34%%">User</th>
  @ <th width="33%%">IP Address</th></tr></thead><tbody>
  while( rc==SQLITE_OK && db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zIP = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    int bSuccess = db_column_int(&q, 3);
................................................................................
  @ <input type="submit" name="delfailbtn" value="Delete"></input>
  @ </form>
  @ <form method="post" action="%s(g.zTop)/access_log">
  @ <label><input type="checkbox" name="delall">
  @ Delete all entries</input></label>
  @ <input type="submit" name="delallbtn" value="Delete"></input>
  @ </form>
  output_table_sorting_javascript("logtable", "Ttt", 1);
  style_footer();
}







|
>







 







|


685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
...
733
734
735
736
737
738
739
740
741
742
    style_submenu_element("Newer", "%s/access_log?o=%d&n=%d&y=%d",
              g.zTop, skip>=n ? skip-n : 0, n, y);
  }
  rc = db_prepare_ignore_error(&q, "%s", blob_sql_text(&sql));
  fLogEnabled = db_get_boolean("access-log", 0);
  @ <div align="center">Access logging is %s(fLogEnabled?"on":"off").
  @ (Change this on the <a href="setup_settings">settings</a> page.)</div>
  @ <table border="1" cellpadding="5" class="sortable" align="center" \
  @  data-column-types='Ttt' data-init-sort='1'>
  @ <thead><tr><th width="33%%">Date</th><th width="34%%">User</th>
  @ <th width="33%%">IP Address</th></tr></thead><tbody>
  while( rc==SQLITE_OK && db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zIP = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    int bSuccess = db_column_int(&q, 3);
................................................................................
  @ <input type="submit" name="delfailbtn" value="Delete"></input>
  @ </form>
  @ <form method="post" action="%s(g.zTop)/access_log">
  @ <label><input type="checkbox" name="delall">
  @ Delete all entries</input></label>
  @ <input type="submit" name="delallbtn" value="Delete"></input>
  @ </form>
  style_table_sorter();
  style_footer();
}

Changes to win/Makefile.mingw.

606
607
608
609
610
611
612

613
614
615
616
617
618
619
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \

  $(SRCDIR)/tree.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \







>







606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \

Changes to win/Makefile.msc.

532
533
534
535
536
537
538

539
540
541
542
543
544
545
        $(SRCDIR)\../skins/xekri/header.txt \
        $(SRCDIR)\diff.tcl \
        $(SRCDIR)\graph.js \
        $(SRCDIR)\href.js \
        $(SRCDIR)\markdown.md \
        $(SRCDIR)\menu.js \
        $(SRCDIR)\sbsdiff.js \

        $(SRCDIR)\tree.js \
        $(SRCDIR)\wiki.wiki

OBJ   = $(OX)\add$O \
        $(OX)\allrepo$O \
        $(OX)\attach$O \
        $(OX)\bag$O \







>







532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
        $(SRCDIR)\../skins/xekri/header.txt \
        $(SRCDIR)\diff.tcl \
        $(SRCDIR)\graph.js \
        $(SRCDIR)\href.js \
        $(SRCDIR)\markdown.md \
        $(SRCDIR)\menu.js \
        $(SRCDIR)\sbsdiff.js \
        $(SRCDIR)\sorttable.js \
        $(SRCDIR)\tree.js \
        $(SRCDIR)\wiki.wiki

OBJ   = $(OX)\add$O \
        $(OX)\allrepo$O \
        $(OX)\attach$O \
        $(OX)\bag$O \