Fossil SCM

Add form elements to the /timeline submenu. Many other related changes and enhancements to the web interface.

drh 2015-02-06 03:30 trunk merge
Commit c0c0bae719eb96e652fecc43cd870a15ca7de803
+55 -4
--- src/cgi.c
+++ src/cgi.c
@@ -52,10 +52,11 @@
5252
*/
5353
#define P(x) cgi_parameter((x),0)
5454
#define PD(x,y) cgi_parameter((x),(y))
5555
#define PT(x) cgi_parameter_trimmed((x),0)
5656
#define PDT(x,y) cgi_parameter_trimmed((x),(y))
57
+#define PB(x) cgi_parameter_boolean(x)
5758
5859
5960
/*
6061
** Destinations for output text.
6162
*/
@@ -436,11 +437,12 @@
436437
static int seqQP = 0; /* Sequence numbers */
437438
static struct QParam { /* One entry for each query parameter or cookie */
438439
const char *zName; /* Parameter or cookie name */
439440
const char *zValue; /* Value of the query parameter or cookie */
440441
int seq; /* Order of insertion */
441
- int isQP; /* True for query parameters */
442
+ char isQP; /* True for query parameters */
443
+ char cTag; /* Tag on query parameters */
442444
} *aParamQP; /* An array of all parameters and cookies */
443445
444446
/*
445447
** Add another query parameter or cookie to the parameter set.
446448
** zName is the name of the query parameter or cookie and zValue
@@ -490,10 +492,21 @@
490492
aParamQP[i].zValue = zValue;
491493
return;
492494
}
493495
}
494496
cgi_set_parameter_nocopy(zName, zValue, 0);
497
+}
498
+void cgi_replace_query_parameter(const char *zName, const char *zValue){
499
+ int i;
500
+ for(i=0; i<nUsedQP; i++){
501
+ if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
502
+ aParamQP[i].zValue = zValue;
503
+ assert( aParamQP[i].isQP );
504
+ return;
505
+ }
506
+ }
507
+ cgi_set_parameter_nocopy(zName, zValue, 1);
495508
}
496509
497510
/*
498511
** Add a query parameter. The zName portion is fixed but a copy
499512
** must be made of zValue.
@@ -1064,10 +1077,20 @@
10641077
zOut = fossil_strdup(zIn);
10651078
for(i=0; zOut[i]; i++){}
10661079
while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
10671080
return zOut;
10681081
}
1082
+
1083
+/*
1084
+** Return true if the CGI parameter zName exists and is not equal to 0,
1085
+** or "no" or "off".
1086
+*/
1087
+int cgi_parameter_boolean(const char *zName){
1088
+ const char *zIn = cgi_parameter(zName, 0);
1089
+ if( zIn==0 ) return 0;
1090
+ return zIn[0]==0 || is_truth(zIn);
1091
+}
10691092
10701093
/*
10711094
** Return the name of the i-th CGI parameter. Return NULL if there
10721095
** are fewer than i registered CGI parameters.
10731096
*/
@@ -1142,23 +1165,51 @@
11421165
cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
11431166
}
11441167
}
11451168
11461169
/*
1147
-** Export all query parameters (but not cookies or environment variables)
1148
-** as hidden values of a form.
1170
+** Export all untagged query parameters (but not cookies or environment
1171
+** variables) as hidden values of a form.
11491172
*/
11501173
void cgi_query_parameters_to_hidden(void){
11511174
int i;
11521175
const char *zN, *zV;
11531176
for(i=0; i<nUsedQP; i++){
1154
- if( aParamQP[i].isQP==0 ) continue;
1177
+ if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
11551178
zN = aParamQP[i].zName;
11561179
zV = aParamQP[i].zValue;
11571180
@ <input type="hidden" name="%h(zN)" value="%h(zV)">
11581181
}
11591182
}
1183
+
1184
+/*
1185
+** Export all untagged query parameters (but not cookies or environment
1186
+** variables) to the HQuery object.
1187
+*/
1188
+void cgi_query_parameters_to_url(HQuery *p){
1189
+ int i;
1190
+ for(i=0; i<nUsedQP; i++){
1191
+ if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1192
+ url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue);
1193
+ }
1194
+}
1195
+
1196
+/*
1197
+** Tag query parameter zName so that it is not exported by
1198
+** cgi_query_parameters_to_hidden(). Or if zName==0, then
1199
+** untag all query parameters.
1200
+*/
1201
+void cgi_tag_query_parameter(const char *zName){
1202
+ int i;
1203
+ if( zName==0 ){
1204
+ for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0;
1205
+ }else{
1206
+ for(i=0; i<nUsedQP; i++){
1207
+ if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1;
1208
+ }
1209
+ }
1210
+}
11601211
11611212
/*
11621213
** This routine works like "printf" except that it has the
11631214
** extra formatting capabilities such as %h and %t.
11641215
*/
11651216
--- src/cgi.c
+++ src/cgi.c
@@ -52,10 +52,11 @@
52 */
53 #define P(x) cgi_parameter((x),0)
54 #define PD(x,y) cgi_parameter((x),(y))
55 #define PT(x) cgi_parameter_trimmed((x),0)
56 #define PDT(x,y) cgi_parameter_trimmed((x),(y))
 
57
58
59 /*
60 ** Destinations for output text.
61 */
@@ -436,11 +437,12 @@
436 static int seqQP = 0; /* Sequence numbers */
437 static struct QParam { /* One entry for each query parameter or cookie */
438 const char *zName; /* Parameter or cookie name */
439 const char *zValue; /* Value of the query parameter or cookie */
440 int seq; /* Order of insertion */
441 int isQP; /* True for query parameters */
 
442 } *aParamQP; /* An array of all parameters and cookies */
443
444 /*
445 ** Add another query parameter or cookie to the parameter set.
446 ** zName is the name of the query parameter or cookie and zValue
@@ -490,10 +492,21 @@
490 aParamQP[i].zValue = zValue;
491 return;
492 }
493 }
494 cgi_set_parameter_nocopy(zName, zValue, 0);
 
 
 
 
 
 
 
 
 
 
 
495 }
496
497 /*
498 ** Add a query parameter. The zName portion is fixed but a copy
499 ** must be made of zValue.
@@ -1064,10 +1077,20 @@
1064 zOut = fossil_strdup(zIn);
1065 for(i=0; zOut[i]; i++){}
1066 while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
1067 return zOut;
1068 }
 
 
 
 
 
 
 
 
 
 
1069
1070 /*
1071 ** Return the name of the i-th CGI parameter. Return NULL if there
1072 ** are fewer than i registered CGI parameters.
1073 */
@@ -1142,23 +1165,51 @@
1142 cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
1143 }
1144 }
1145
1146 /*
1147 ** Export all query parameters (but not cookies or environment variables)
1148 ** as hidden values of a form.
1149 */
1150 void cgi_query_parameters_to_hidden(void){
1151 int i;
1152 const char *zN, *zV;
1153 for(i=0; i<nUsedQP; i++){
1154 if( aParamQP[i].isQP==0 ) continue;
1155 zN = aParamQP[i].zName;
1156 zV = aParamQP[i].zValue;
1157 @ <input type="hidden" name="%h(zN)" value="%h(zV)">
1158 }
1159 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1160
1161 /*
1162 ** This routine works like "printf" except that it has the
1163 ** extra formatting capabilities such as %h and %t.
1164 */
1165
--- src/cgi.c
+++ src/cgi.c
@@ -52,10 +52,11 @@
52 */
53 #define P(x) cgi_parameter((x),0)
54 #define PD(x,y) cgi_parameter((x),(y))
55 #define PT(x) cgi_parameter_trimmed((x),0)
56 #define PDT(x,y) cgi_parameter_trimmed((x),(y))
57 #define PB(x) cgi_parameter_boolean(x)
58
59
60 /*
61 ** Destinations for output text.
62 */
@@ -436,11 +437,12 @@
437 static int seqQP = 0; /* Sequence numbers */
438 static struct QParam { /* One entry for each query parameter or cookie */
439 const char *zName; /* Parameter or cookie name */
440 const char *zValue; /* Value of the query parameter or cookie */
441 int seq; /* Order of insertion */
442 char isQP; /* True for query parameters */
443 char cTag; /* Tag on query parameters */
444 } *aParamQP; /* An array of all parameters and cookies */
445
446 /*
447 ** Add another query parameter or cookie to the parameter set.
448 ** zName is the name of the query parameter or cookie and zValue
@@ -490,10 +492,21 @@
492 aParamQP[i].zValue = zValue;
493 return;
494 }
495 }
496 cgi_set_parameter_nocopy(zName, zValue, 0);
497 }
498 void cgi_replace_query_parameter(const char *zName, const char *zValue){
499 int i;
500 for(i=0; i<nUsedQP; i++){
501 if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
502 aParamQP[i].zValue = zValue;
503 assert( aParamQP[i].isQP );
504 return;
505 }
506 }
507 cgi_set_parameter_nocopy(zName, zValue, 1);
508 }
509
510 /*
511 ** Add a query parameter. The zName portion is fixed but a copy
512 ** must be made of zValue.
@@ -1064,10 +1077,20 @@
1077 zOut = fossil_strdup(zIn);
1078 for(i=0; zOut[i]; i++){}
1079 while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
1080 return zOut;
1081 }
1082
1083 /*
1084 ** Return true if the CGI parameter zName exists and is not equal to 0,
1085 ** or "no" or "off".
1086 */
1087 int cgi_parameter_boolean(const char *zName){
1088 const char *zIn = cgi_parameter(zName, 0);
1089 if( zIn==0 ) return 0;
1090 return zIn[0]==0 || is_truth(zIn);
1091 }
1092
1093 /*
1094 ** Return the name of the i-th CGI parameter. Return NULL if there
1095 ** are fewer than i registered CGI parameters.
1096 */
@@ -1142,23 +1165,51 @@
1165 cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
1166 }
1167 }
1168
1169 /*
1170 ** Export all untagged query parameters (but not cookies or environment
1171 ** variables) as hidden values of a form.
1172 */
1173 void cgi_query_parameters_to_hidden(void){
1174 int i;
1175 const char *zN, *zV;
1176 for(i=0; i<nUsedQP; i++){
1177 if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1178 zN = aParamQP[i].zName;
1179 zV = aParamQP[i].zValue;
1180 @ <input type="hidden" name="%h(zN)" value="%h(zV)">
1181 }
1182 }
1183
1184 /*
1185 ** Export all untagged query parameters (but not cookies or environment
1186 ** variables) to the HQuery object.
1187 */
1188 void cgi_query_parameters_to_url(HQuery *p){
1189 int i;
1190 for(i=0; i<nUsedQP; i++){
1191 if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1192 url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue);
1193 }
1194 }
1195
1196 /*
1197 ** Tag query parameter zName so that it is not exported by
1198 ** cgi_query_parameters_to_hidden(). Or if zName==0, then
1199 ** untag all query parameters.
1200 */
1201 void cgi_tag_query_parameter(const char *zName){
1202 int i;
1203 if( zName==0 ){
1204 for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0;
1205 }else{
1206 for(i=0; i<nUsedQP; i++){
1207 if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1;
1208 }
1209 }
1210 }
1211
1212 /*
1213 ** This routine works like "printf" except that it has the
1214 ** extra formatting capabilities such as %h and %t.
1215 */
1216
+1 -1
--- src/doc.c
+++ src/doc.c
@@ -785,8 +785,8 @@
785785
** Search for documents that match a user-supplied pattern.
786786
*/
787787
void doc_search_page(void){
788788
login_check_credentials();
789789
style_header("Document Search");
790
- search_screen(SRCH_DOC, "docsrch");
790
+ search_screen(SRCH_DOC, 0);
791791
style_footer();
792792
}
793793
--- src/doc.c
+++ src/doc.c
@@ -785,8 +785,8 @@
785 ** Search for documents that match a user-supplied pattern.
786 */
787 void doc_search_page(void){
788 login_check_credentials();
789 style_header("Document Search");
790 search_screen(SRCH_DOC, "docsrch");
791 style_footer();
792 }
793
--- src/doc.c
+++ src/doc.c
@@ -785,8 +785,8 @@
785 ** Search for documents that match a user-supplied pattern.
786 */
787 void doc_search_page(void){
788 login_check_credentials();
789 style_header("Document Search");
790 search_screen(SRCH_DOC, 0);
791 style_footer();
792 }
793
+9 -13
--- src/event.c
+++ src/event.c
@@ -129,36 +129,32 @@
129129
if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
130130
style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
131131
g.zTop, zEventId);
132132
}
133133
zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
134
- style_submenu_element("Context", "Context", "%s/timeline?c=%T",
135
- g.zTop, zETime);
134
+ style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zEventId);
136135
if( g.perm.Hyperlink ){
137136
if( verboseFlag ){
138
- style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s",
139
- g.zTop, zEventId, zUuid);
137
+ style_submenu_element("Plain", 0, "%R/event?name=%.20s&aid=%s",
138
+ zEventId, zUuid);
140139
if( nextRid ){
141140
char *zNext;
142141
zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
143
- style_submenu_element("Next", "Next",
144
- "%s/event?name=%s&aid=%s&v",
145
- g.zTop, zEventId, zNext);
142
+ style_submenu_element("Next", 0,"%R/event?name=%.20s&aid=%s&v",
143
+ zEventId, zNext);
146144
free(zNext);
147145
}
148146
if( prevRid ){
149147
char *zPrev;
150148
zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
151
- style_submenu_element("Prev", "Prev",
152
- "%s/event?name=%s&aid=%s&v",
153
- g.zTop, zEventId, zPrev);
149
+ style_submenu_element("Prev", 0, "%R/event?name=%s&aid=%s&v",
150
+ zEventId, zPrev);
154151
free(zPrev);
155152
}
156153
}else{
157
- style_submenu_element("Detail", "Detail",
158
- "%s/event?name=%s&aid=%s&v",
159
- g.zTop, zEventId, zUuid);
154
+ style_submenu_element("Detail", 0, "%R/event?name=%.20s&aid=%s&v",
155
+ zEventId, zUuid);
160156
}
161157
}
162158
163159
if( verboseFlag && g.perm.Hyperlink ){
164160
int i;
165161
--- src/event.c
+++ src/event.c
@@ -129,36 +129,32 @@
129 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
130 style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
131 g.zTop, zEventId);
132 }
133 zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
134 style_submenu_element("Context", "Context", "%s/timeline?c=%T",
135 g.zTop, zETime);
136 if( g.perm.Hyperlink ){
137 if( verboseFlag ){
138 style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s",
139 g.zTop, zEventId, zUuid);
140 if( nextRid ){
141 char *zNext;
142 zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
143 style_submenu_element("Next", "Next",
144 "%s/event?name=%s&aid=%s&v",
145 g.zTop, zEventId, zNext);
146 free(zNext);
147 }
148 if( prevRid ){
149 char *zPrev;
150 zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
151 style_submenu_element("Prev", "Prev",
152 "%s/event?name=%s&aid=%s&v",
153 g.zTop, zEventId, zPrev);
154 free(zPrev);
155 }
156 }else{
157 style_submenu_element("Detail", "Detail",
158 "%s/event?name=%s&aid=%s&v",
159 g.zTop, zEventId, zUuid);
160 }
161 }
162
163 if( verboseFlag && g.perm.Hyperlink ){
164 int i;
165
--- src/event.c
+++ src/event.c
@@ -129,36 +129,32 @@
129 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
130 style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
131 g.zTop, zEventId);
132 }
133 zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
134 style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zEventId);
 
135 if( g.perm.Hyperlink ){
136 if( verboseFlag ){
137 style_submenu_element("Plain", 0, "%R/event?name=%.20s&aid=%s",
138 zEventId, zUuid);
139 if( nextRid ){
140 char *zNext;
141 zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
142 style_submenu_element("Next", 0,"%R/event?name=%.20s&aid=%s&v",
143 zEventId, zNext);
 
144 free(zNext);
145 }
146 if( prevRid ){
147 char *zPrev;
148 zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
149 style_submenu_element("Prev", 0, "%R/event?name=%s&aid=%s&v",
150 zEventId, zPrev);
 
151 free(zPrev);
152 }
153 }else{
154 style_submenu_element("Detail", 0, "%R/event?name=%.20s&aid=%s&v",
155 zEventId, zUuid);
 
156 }
157 }
158
159 if( verboseFlag && g.perm.Hyperlink ){
160 int i;
161
+58 -31
--- src/search.c
+++ src/search.c
@@ -567,26 +567,33 @@
567567
/*
568568
** Remove bits from srchFlags which are disallowed by either the
569569
** current server configuration or by user permissions.
570570
*/
571571
unsigned int search_restrict(unsigned int srchFlags){
572
+ static unsigned int knownGood = 0;
573
+ static unsigned int knownBad = 0;
574
+ static const struct { unsigned m; const char *zKey; } aSetng[] = {
575
+ { SRCH_CKIN, "search-ci" },
576
+ { SRCH_DOC, "search-doc" },
577
+ { SRCH_TKT, "search-tkt" },
578
+ { SRCH_WIKI, "search-wiki" },
579
+ };
580
+ int i;
572581
if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC);
573582
if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT);
574583
if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
575
- if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){
576
- srchFlags &= ~SRCH_CKIN;
577
- }
578
- if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){
579
- srchFlags &= ~SRCH_DOC;
580
- }
581
- if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){
582
- srchFlags &= ~SRCH_TKT;
583
- }
584
- if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){
585
- srchFlags &= ~SRCH_WIKI;
586
- }
587
- return srchFlags;
584
+ for(i=0; i<ArraySize(aSetng); i++){
585
+ unsigned int m = aSetng[i].m;
586
+ if( (srchFlags & m)==0 ) continue;
587
+ if( ((knownGood|knownBad) & m)!=0 ) continue;
588
+ if( db_get_boolean(aSetng[i].zKey,0) ){
589
+ knownGood |= m;
590
+ }else{
591
+ knownBad |= m;
592
+ }
593
+ }
594
+ return srchFlags & ~knownBad;
588595
}
589596
590597
/*
591598
** When this routine is called, there already exists a table
592599
**
@@ -875,46 +882,75 @@
875882
/*
876883
** Generate some HTML for doing search. At a minimum include the
877884
** Search-Text entry form. If the "s" query parameter is present, also
878885
** show search results.
879886
**
880
-** The srchFlags parameter is used to customize some of the text of the
881
-** form and the results. srchFlags should be either a single search
882
-** category or all categories. Any srchFlags with two or more bits set
887
+** The srchFlags parameter restricts the set of documents to be searched.
888
+** srchFlags should normally be either a single search category or all
889
+** categories. Any srchFlags with two or more bits set
883890
** is treated like SRCH_ALL for display purposes.
884891
**
885
-** The entry box is shown disabled if srchFlags is 0.
892
+** This routine automatically restricts srchFlag according to user
893
+** permissions and the server configuration. The entry box is shown
894
+** disabled if srchFlags is 0 after these restrictions are applied.
895
+**
896
+** If useYparam is true, then this routine also looks at the y= query
897
+** parameter for further search restrictions.
886898
*/
887
-void search_screen(unsigned srchFlags, const char *zAction){
899
+void search_screen(unsigned srchFlags, int useYparam){
888900
const char *zType = 0;
889901
const char *zClass = 0;
890902
const char *zDisable1;
891903
const char *zDisable2;
892904
const char *zPattern;
905
+ srchFlags = search_restrict(srchFlags);
893906
switch( srchFlags ){
894907
case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
895908
case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
896909
case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
897910
case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break;
898911
}
899
- srchFlags = search_restrict(srchFlags);
900912
if( srchFlags==0 ){
901913
zDisable1 = " disabled";
902914
zDisable2 = " disabled";
903915
zPattern = "";
904916
}else{
905917
zDisable1 = " autofocus";
906918
zDisable2 = "";
907919
zPattern = PD("s","");
908920
}
909
- @ <form method='GET' action='%s(zAction)'>
921
+ @ <form method='GET' action='%R/%t(g.zPath)'>
910922
if( zClass ){
911923
@ <div class='searchForm searchForm%s(zClass)'>
912924
}else{
913925
@ <div class='searchForm'>
914926
}
915927
@ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
928
+ if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){
929
+ static const struct { char *z; char *zNm; unsigned m; } aY[] = {
930
+ { "all", "All", SRCH_ALL },
931
+ { "c", "Check-ins", SRCH_CKIN },
932
+ { "d", "Docs", SRCH_DOC },
933
+ { "t", "Tickets", SRCH_TKT },
934
+ { "w", "Wiki", SRCH_WIKI },
935
+ };
936
+ const char *zY = PD("y","all");
937
+ unsigned newFlags = srchFlags;
938
+ int i;
939
+ @ <select size='1' name='y'>
940
+ for(i=0; i<ArraySize(aY); i++){
941
+ if( (aY[i].m & srchFlags)==0 ) continue;
942
+ cgi_printf("<option value='%s'", aY[i].z);
943
+ if( fossil_strcmp(zY,aY[i].z)==0 ){
944
+ newFlags &= aY[i].m;
945
+ cgi_printf(" selected");
946
+ }
947
+ cgi_printf(">%s</option>\n", aY[i].zNm);
948
+ }
949
+ @ </select>
950
+ srchFlags = newFlags;
951
+ }
916952
@ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
917953
if( srchFlags==0 ){
918954
@ <p class="generalError">Search is disabled</p>
919955
}
920956
@ </div></form>
@@ -937,22 +973,13 @@
937973
**
938974
** Search for check-in comments, documents, tickets, or wiki that
939975
** match a user-supplied pattern.
940976
*/
941977
void search_page(void){
942
- unsigned srchFlags = SRCH_ALL;
943
- const char *zOnly = P("only");
944
-
945978
login_check_credentials();
946
- if( zOnly ){
947
- if( strchr(zOnly,'c') ) srchFlags &= SRCH_CKIN;
948
- if( strchr(zOnly,'d') ) srchFlags &= SRCH_DOC;
949
- if( strchr(zOnly,'t') ) srchFlags &= SRCH_TKT;
950
- if( strchr(zOnly,'w') ) srchFlags &= SRCH_WIKI;
951
- }
952979
style_header("Search");
953
- search_screen(srchFlags, "search");
980
+ search_screen(SRCH_ALL, 1);
954981
style_footer();
955982
}
956983
957984
958985
/*
@@ -1295,11 +1322,11 @@
12951322
);
12961323
db_multi_exec(
12971324
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
12981325
" SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
12991326
" printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime)),"
1300
- " printf('/timeline?y=ci&n=9&c=%%.20s',blob.uuid),"
1327
+ " printf('/timeline?y=ci&c=%%.20s',blob.uuid),"
13011328
" event.mtime"
13021329
" FROM ftsdocs, event, blob"
13031330
" WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed"
13041331
" AND event.objid=ftsdocs.rid"
13051332
" AND blob.rid=ftsdocs.rid"
13061333
--- src/search.c
+++ src/search.c
@@ -567,26 +567,33 @@
567 /*
568 ** Remove bits from srchFlags which are disallowed by either the
569 ** current server configuration or by user permissions.
570 */
571 unsigned int search_restrict(unsigned int srchFlags){
 
 
 
 
 
 
 
 
 
572 if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC);
573 if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT);
574 if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
575 if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){
576 srchFlags &= ~SRCH_CKIN;
577 }
578 if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){
579 srchFlags &= ~SRCH_DOC;
580 }
581 if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){
582 srchFlags &= ~SRCH_TKT;
583 }
584 if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){
585 srchFlags &= ~SRCH_WIKI;
586 }
587 return srchFlags;
588 }
589
590 /*
591 ** When this routine is called, there already exists a table
592 **
@@ -875,46 +882,75 @@
875 /*
876 ** Generate some HTML for doing search. At a minimum include the
877 ** Search-Text entry form. If the "s" query parameter is present, also
878 ** show search results.
879 **
880 ** The srchFlags parameter is used to customize some of the text of the
881 ** form and the results. srchFlags should be either a single search
882 ** category or all categories. Any srchFlags with two or more bits set
883 ** is treated like SRCH_ALL for display purposes.
884 **
885 ** The entry box is shown disabled if srchFlags is 0.
 
 
 
 
 
886 */
887 void search_screen(unsigned srchFlags, const char *zAction){
888 const char *zType = 0;
889 const char *zClass = 0;
890 const char *zDisable1;
891 const char *zDisable2;
892 const char *zPattern;
 
893 switch( srchFlags ){
894 case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
895 case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
896 case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
897 case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break;
898 }
899 srchFlags = search_restrict(srchFlags);
900 if( srchFlags==0 ){
901 zDisable1 = " disabled";
902 zDisable2 = " disabled";
903 zPattern = "";
904 }else{
905 zDisable1 = " autofocus";
906 zDisable2 = "";
907 zPattern = PD("s","");
908 }
909 @ <form method='GET' action='%s(zAction)'>
910 if( zClass ){
911 @ <div class='searchForm searchForm%s(zClass)'>
912 }else{
913 @ <div class='searchForm'>
914 }
915 @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
916 @ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
917 if( srchFlags==0 ){
918 @ <p class="generalError">Search is disabled</p>
919 }
920 @ </div></form>
@@ -937,22 +973,13 @@
937 **
938 ** Search for check-in comments, documents, tickets, or wiki that
939 ** match a user-supplied pattern.
940 */
941 void search_page(void){
942 unsigned srchFlags = SRCH_ALL;
943 const char *zOnly = P("only");
944
945 login_check_credentials();
946 if( zOnly ){
947 if( strchr(zOnly,'c') ) srchFlags &= SRCH_CKIN;
948 if( strchr(zOnly,'d') ) srchFlags &= SRCH_DOC;
949 if( strchr(zOnly,'t') ) srchFlags &= SRCH_TKT;
950 if( strchr(zOnly,'w') ) srchFlags &= SRCH_WIKI;
951 }
952 style_header("Search");
953 search_screen(srchFlags, "search");
954 style_footer();
955 }
956
957
958 /*
@@ -1295,11 +1322,11 @@
1295 );
1296 db_multi_exec(
1297 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1298 " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
1299 " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime)),"
1300 " printf('/timeline?y=ci&n=9&c=%%.20s',blob.uuid),"
1301 " event.mtime"
1302 " FROM ftsdocs, event, blob"
1303 " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed"
1304 " AND event.objid=ftsdocs.rid"
1305 " AND blob.rid=ftsdocs.rid"
1306
--- src/search.c
+++ src/search.c
@@ -567,26 +567,33 @@
567 /*
568 ** Remove bits from srchFlags which are disallowed by either the
569 ** current server configuration or by user permissions.
570 */
571 unsigned int search_restrict(unsigned int srchFlags){
572 static unsigned int knownGood = 0;
573 static unsigned int knownBad = 0;
574 static const struct { unsigned m; const char *zKey; } aSetng[] = {
575 { SRCH_CKIN, "search-ci" },
576 { SRCH_DOC, "search-doc" },
577 { SRCH_TKT, "search-tkt" },
578 { SRCH_WIKI, "search-wiki" },
579 };
580 int i;
581 if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC);
582 if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT);
583 if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
584 for(i=0; i<ArraySize(aSetng); i++){
585 unsigned int m = aSetng[i].m;
586 if( (srchFlags & m)==0 ) continue;
587 if( ((knownGood|knownBad) & m)!=0 ) continue;
588 if( db_get_boolean(aSetng[i].zKey,0) ){
589 knownGood |= m;
590 }else{
591 knownBad |= m;
592 }
593 }
594 return srchFlags & ~knownBad;
 
 
595 }
596
597 /*
598 ** When this routine is called, there already exists a table
599 **
@@ -875,46 +882,75 @@
882 /*
883 ** Generate some HTML for doing search. At a minimum include the
884 ** Search-Text entry form. If the "s" query parameter is present, also
885 ** show search results.
886 **
887 ** The srchFlags parameter restricts the set of documents to be searched.
888 ** srchFlags should normally be either a single search category or all
889 ** categories. Any srchFlags with two or more bits set
890 ** is treated like SRCH_ALL for display purposes.
891 **
892 ** This routine automatically restricts srchFlag according to user
893 ** permissions and the server configuration. The entry box is shown
894 ** disabled if srchFlags is 0 after these restrictions are applied.
895 **
896 ** If useYparam is true, then this routine also looks at the y= query
897 ** parameter for further search restrictions.
898 */
899 void search_screen(unsigned srchFlags, int useYparam){
900 const char *zType = 0;
901 const char *zClass = 0;
902 const char *zDisable1;
903 const char *zDisable2;
904 const char *zPattern;
905 srchFlags = search_restrict(srchFlags);
906 switch( srchFlags ){
907 case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
908 case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
909 case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
910 case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break;
911 }
 
912 if( srchFlags==0 ){
913 zDisable1 = " disabled";
914 zDisable2 = " disabled";
915 zPattern = "";
916 }else{
917 zDisable1 = " autofocus";
918 zDisable2 = "";
919 zPattern = PD("s","");
920 }
921 @ <form method='GET' action='%R/%t(g.zPath)'>
922 if( zClass ){
923 @ <div class='searchForm searchForm%s(zClass)'>
924 }else{
925 @ <div class='searchForm'>
926 }
927 @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
928 if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){
929 static const struct { char *z; char *zNm; unsigned m; } aY[] = {
930 { "all", "All", SRCH_ALL },
931 { "c", "Check-ins", SRCH_CKIN },
932 { "d", "Docs", SRCH_DOC },
933 { "t", "Tickets", SRCH_TKT },
934 { "w", "Wiki", SRCH_WIKI },
935 };
936 const char *zY = PD("y","all");
937 unsigned newFlags = srchFlags;
938 int i;
939 @ <select size='1' name='y'>
940 for(i=0; i<ArraySize(aY); i++){
941 if( (aY[i].m & srchFlags)==0 ) continue;
942 cgi_printf("<option value='%s'", aY[i].z);
943 if( fossil_strcmp(zY,aY[i].z)==0 ){
944 newFlags &= aY[i].m;
945 cgi_printf(" selected");
946 }
947 cgi_printf(">%s</option>\n", aY[i].zNm);
948 }
949 @ </select>
950 srchFlags = newFlags;
951 }
952 @ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
953 if( srchFlags==0 ){
954 @ <p class="generalError">Search is disabled</p>
955 }
956 @ </div></form>
@@ -937,22 +973,13 @@
973 **
974 ** Search for check-in comments, documents, tickets, or wiki that
975 ** match a user-supplied pattern.
976 */
977 void search_page(void){
 
 
 
978 login_check_credentials();
 
 
 
 
 
 
979 style_header("Search");
980 search_screen(SRCH_ALL, 1);
981 style_footer();
982 }
983
984
985 /*
@@ -1295,11 +1322,11 @@
1322 );
1323 db_multi_exec(
1324 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1325 " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
1326 " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime)),"
1327 " printf('/timeline?y=ci&c=%%.20s',blob.uuid),"
1328 " event.mtime"
1329 " FROM ftsdocs, event, blob"
1330 " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed"
1331 " AND event.objid=ftsdocs.rid"
1332 " AND blob.rid=ftsdocs.rid"
1333
+17
--- src/setup.c
+++ src/setup.c
@@ -1263,10 +1263,27 @@
12631263
@ <p><form action="%s(g.zTop)/setup_login_group" method="post"><div>
12641264
login_insert_csrf_secret();
12651265
@ To leave this login group press
12661266
@ <input type="submit" value="Leave Login Group" name="leave">
12671267
@ </form></p>
1268
+ @ <hr><h2>Implementation Details</h2>
1269
+ @ <p>The following are fields from the CONFIG table related to login-groups,
1270
+ @ provided here for instructional and debugging purposes:</p>
1271
+ @ <table border='1' id='configTab'>
1272
+ @ <thead><tr><th>Config.Name<th>Config.Value<th>Config.mtime</tr></thead><tbody>
1273
+ db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config"
1274
+ " WHERE name GLOB 'peer-*'"
1275
+ " OR name GLOB 'project-*'"
1276
+ " ORDER BY name");
1277
+ while( db_step(&q)==SQLITE_ROW ){
1278
+ @ <tr><td>%h(db_column_text(&q,0))</td>
1279
+ @ <td>%h(db_column_text(&q,1))</td>
1280
+ @ <td>%h(db_column_text(&q,2))</td></tr>
1281
+ }
1282
+ db_finalize(&q);
1283
+ @ </tbody></table>
1284
+ output_table_sorting_javascript("configTab","ttt",1);
12681285
}
12691286
style_footer();
12701287
}
12711288
12721289
/*
12731290
--- src/setup.c
+++ src/setup.c
@@ -1263,10 +1263,27 @@
1263 @ <p><form action="%s(g.zTop)/setup_login_group" method="post"><div>
1264 login_insert_csrf_secret();
1265 @ To leave this login group press
1266 @ <input type="submit" value="Leave Login Group" name="leave">
1267 @ </form></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1268 }
1269 style_footer();
1270 }
1271
1272 /*
1273
--- src/setup.c
+++ src/setup.c
@@ -1263,10 +1263,27 @@
1263 @ <p><form action="%s(g.zTop)/setup_login_group" method="post"><div>
1264 login_insert_csrf_secret();
1265 @ To leave this login group press
1266 @ <input type="submit" value="Leave Login Group" name="leave">
1267 @ </form></p>
1268 @ <hr><h2>Implementation Details</h2>
1269 @ <p>The following are fields from the CONFIG table related to login-groups,
1270 @ provided here for instructional and debugging purposes:</p>
1271 @ <table border='1' id='configTab'>
1272 @ <thead><tr><th>Config.Name<th>Config.Value<th>Config.mtime</tr></thead><tbody>
1273 db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config"
1274 " WHERE name GLOB 'peer-*'"
1275 " OR name GLOB 'project-*'"
1276 " ORDER BY name");
1277 while( db_step(&q)==SQLITE_ROW ){
1278 @ <tr><td>%h(db_column_text(&q,0))</td>
1279 @ <td>%h(db_column_text(&q,1))</td>
1280 @ <td>%h(db_column_text(&q,2))</td></tr>
1281 }
1282 db_finalize(&q);
1283 @ </tbody></table>
1284 output_table_sorting_javascript("configTab","ttt",1);
1285 }
1286 style_footer();
1287 }
1288
1289 /*
1290
+1 -1
--- src/sitemap.c
+++ src/sitemap.c
@@ -44,11 +44,11 @@
4444
@ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
4545
@ </ul>
4646
@ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li>
4747
@ <ul>
4848
@ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins</a></li>
49
- @ <li>%z(href("%R/timeline?n=0&namechng"))All checkins with file name
49
+ @ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name
5050
@ changes</a></li>
5151
@ <li>%z(href("%R/reports"))Activity Reports</a></li>
5252
@ </ul>
5353
@ <li>Branches and Tags</a>
5454
@ <ul>
5555
--- src/sitemap.c
+++ src/sitemap.c
@@ -44,11 +44,11 @@
44 @ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
45 @ </ul>
46 @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li>
47 @ <ul>
48 @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins</a></li>
49 @ <li>%z(href("%R/timeline?n=0&namechng"))All checkins with file name
50 @ changes</a></li>
51 @ <li>%z(href("%R/reports"))Activity Reports</a></li>
52 @ </ul>
53 @ <li>Branches and Tags</a>
54 @ <ul>
55
--- src/sitemap.c
+++ src/sitemap.c
@@ -44,11 +44,11 @@
44 @ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
45 @ </ul>
46 @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li>
47 @ <ul>
48 @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins</a></li>
49 @ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name
50 @ changes</a></li>
51 @ <li>%z(href("%R/reports"))Activity Reports</a></li>
52 @ </ul>
53 @ <li>Branches and Tags</a>
54 @ <ul>
55
+165 -19
--- src/style.c
+++ src/style.c
@@ -23,21 +23,42 @@
2323
#include "style.h"
2424
2525
2626
/*
2727
** Elements of the submenu are collected into the following
28
-** structure and displayed below the main menu by style_header().
28
+** structure and displayed below the main menu.
29
+**
30
+** Populate these structure with calls to
31
+**
32
+** style_submenu_element()
33
+** style_submenu_entry()
34
+** style_submenu_checkbox()
35
+** style_submenu_multichoice()
2936
**
30
-** Populate this structure with calls to style_submenu_element()
31
-** prior to calling style_header().
37
+** prior to calling style_footer(). The style_footer() routine
38
+** will generate the appropriate HTML text just below the main
39
+** menu.
3240
*/
3341
static struct Submenu {
34
- const char *zLabel;
42
+ const char *zLabel; /* Button label */
3543
const char *zTitle;
36
- const char *zLink;
44
+ const char *zLink; /* Jump to this link when button is pressed */
3745
} aSubmenu[30];
38
-static int nSubmenu = 0;
46
+static int nSubmenu = 0; /* Number of buttons */
47
+static struct SubmenuCtrl {
48
+ const char *zName; /* Form query parameter */
49
+ const char *zLabel; /* Label. Might be NULL for FF_MULTI */
50
+ int eType; /* FF_ENTRY, FF_CKBOX, FF_MULTI */
51
+ int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */
52
+ const char **azChoice; /* value/display pairs for FF_MULTI */
53
+ const char *zFalse; /* FF_BINARY label when false */
54
+} aSubmenuCtrl[20];
55
+static int nSubmenuCtrl = 0;
56
+#define FF_ENTRY 1
57
+#define FF_CKBOX 2
58
+#define FF_MULTI 3
59
+#define FF_BINARY 4
3960
4061
/*
4162
** Remember that the header has been generated. The footer is omitted
4263
** if an error occurs before the header.
4364
*/
@@ -216,16 +237,63 @@
216237
...
217238
){
218239
va_list ap;
219240
assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
220241
aSubmenu[nSubmenu].zLabel = zLabel;
221
- aSubmenu[nSubmenu].zTitle = zTitle;
242
+ aSubmenu[nSubmenu].zTitle = zTitle ? zTitle : zLabel;
222243
va_start(ap, zLink);
223244
aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
224245
va_end(ap);
225246
nSubmenu++;
226247
}
248
+void style_submenu_entry(
249
+ const char *zName, /* Query parameter name */
250
+ const char *zLabel, /* Label before the entry box */
251
+ int iSize /* Size of the entry box */
252
+){
253
+ assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
254
+ aSubmenuCtrl[nSubmenuCtrl].zName = zName;
255
+ aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
256
+ aSubmenuCtrl[nSubmenuCtrl].iSize = iSize;
257
+ aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY;
258
+ nSubmenuCtrl++;
259
+}
260
+void style_submenu_checkbox(
261
+ const char *zName, /* Query parameter name */
262
+ const char *zLabel /* Label before the checkbox */
263
+){
264
+ assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
265
+ aSubmenuCtrl[nSubmenuCtrl].zName = zName;
266
+ aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
267
+ aSubmenuCtrl[nSubmenuCtrl].eType = FF_CKBOX;
268
+ nSubmenuCtrl++;
269
+}
270
+void style_submenu_binary(
271
+ const char *zName, /* Query parameter name */
272
+ const char *zTrue, /* Label to show when parameter is true */
273
+ const char *zFalse /* Label to show when the parameter is false */
274
+){
275
+ assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
276
+ aSubmenuCtrl[nSubmenuCtrl].zName = zName;
277
+ aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue;
278
+ aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse;
279
+ aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY;
280
+ nSubmenuCtrl++;
281
+}
282
+void style_submenu_multichoice(
283
+ const char *zName, /* Query parameter name */
284
+ int nChoice, /* Number of options */
285
+ const char **azChoice /* value/display pairs. 2*nChoice entries */
286
+){
287
+ assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
288
+ aSubmenuCtrl[nSubmenuCtrl].zName = zName;
289
+ aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice;
290
+ aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice;
291
+ aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
292
+ nSubmenuCtrl++;
293
+}
294
+
227295
228296
/*
229297
** Compare two submenu items for sorting purposes
230298
*/
231299
static int submenuCompare(const void *a, const void *b){
@@ -411,23 +479,102 @@
411479
/* Go back and put the submenu at the top of the page. We delay the
412480
** creation of the submenu until the end so that we can add elements
413481
** to the submenu while generating page text.
414482
*/
415483
cgi_destination(CGI_HEADER);
416
- if( nSubmenu>0 ){
484
+ if( nSubmenu+nSubmenuCtrl>0 ){
417485
int i;
486
+ if( nSubmenuCtrl ){
487
+ cgi_printf("<form id='f01' method='GET' action='%R/%s'>", g.zPath);
488
+ }
418489
@ <div class="submenu">
419
- qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
420
- for(i=0; i<nSubmenu; i++){
421
- struct Submenu *p = &aSubmenu[i];
422
- if( p->zLink==0 ){
423
- @ <span class="label">%h(p->zLabel)</span>
424
- }else{
425
- @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
490
+ if( nSubmenu>0 ){
491
+ qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
492
+ for(i=0; i<nSubmenu; i++){
493
+ struct Submenu *p = &aSubmenu[i];
494
+ if( p->zLink==0 ){
495
+ @ <span class="label">%h(p->zLabel)</span>
496
+ }else{
497
+ @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
498
+ }
499
+ }
500
+ }
501
+ if( nSubmenuCtrl>0 ){
502
+ for(i=0; i<nSubmenuCtrl; i++){
503
+ const char *zQPN = aSubmenuCtrl[i].zName;
504
+ cgi_tag_query_parameter(zQPN);
505
+ switch( aSubmenuCtrl[i].eType ){
506
+ case FF_ENTRY: {
507
+ cgi_printf(
508
+ "<span class='submenuctrl'>"
509
+ "%h:&nbsp;<input type='text' name='%s' size='%d' "
510
+ "value='%h'></span>\n",
511
+ aSubmenuCtrl[i].zLabel,
512
+ zQPN,
513
+ aSubmenuCtrl[i].iSize,
514
+ PD(zQPN,"")
515
+ );
516
+ break;
517
+ }
518
+ case FF_CKBOX: {
519
+ cgi_printf(
520
+ "<span class='submenuctrl'>"
521
+ "%h:&nbsp;<input type='checkbox' name='%s'%s "
522
+ "onchange='gebi(\"f01\").submit();'></span>\n",
523
+ aSubmenuCtrl[i].zLabel,
524
+ zQPN,
525
+ PB(zQPN) ? " checked":""
526
+ );
527
+ break;
528
+ }
529
+ case FF_MULTI: {
530
+ int j;
531
+ const char *zVal = P(zQPN);
532
+ cgi_printf(
533
+ "<select class='submenuctrl' size='1' name='%s' "
534
+ "onchange='gebi(\"f01\").submit();'>\n",
535
+ zQPN
536
+ );
537
+ for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){
538
+ const char *zQPV = aSubmenuCtrl[i].azChoice[j];
539
+ cgi_printf(
540
+ "<option value='%h'%s>%h</option>\n",
541
+ zQPV,
542
+ fossil_strcmp(zVal,zQPV)==0 ? " selected" : "",
543
+ aSubmenuCtrl[i].azChoice[j+1]
544
+ );
545
+ }
546
+ @ </select>
547
+ break;
548
+ }
549
+ case FF_BINARY: {
550
+ int isTrue = PB(zQPN);
551
+ cgi_printf(
552
+ "<select class='submenuctrl' size='1' name='%s' "
553
+ "onchange='gebi(\"f01\").submit();'>\n",
554
+ zQPN
555
+ );
556
+ cgi_printf(
557
+ "<option value='1'%s>%h</option>\n",
558
+ isTrue ? " selected":"", aSubmenuCtrl[i].zLabel
559
+ );
560
+ cgi_printf(
561
+ "<option value='0'%s>%h</option>\n",
562
+ (!isTrue) ? " selected":"", aSubmenuCtrl[i].zFalse
563
+ );
564
+ @ </select>
565
+ break;
566
+ }
567
+ }
426568
}
427569
}
428570
@ </div>
571
+ if( nSubmenuCtrl ){
572
+ cgi_query_parameters_to_hidden();
573
+ cgi_tag_query_parameter(0);
574
+ @ </form>
575
+ }
429576
}
430577
431578
zAd = style_adunit_text(&mAdFlags);
432579
if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){
433580
@ <div class="content adunit_right_container">
@@ -1246,22 +1393,21 @@
12461393
}
12471394
for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
12481395
style_header("Environment Test");
12491396
showAll = atoi(PD("showall","0"));
12501397
if( !showAll ){
1251
- style_submenu_element("Show Cookies", "Show Cookies",
1252
- "%s/test_env?showall=1", g.zTop);
1398
+ style_submenu_element("Show Cookies", 0, "%R/test_env?showall=1");
12531399
}else{
1254
- style_submenu_element("Hide Cookies", "Hide Cookies",
1255
- "%s/test_env", g.zTop);
1400
+ style_submenu_element("Hide Cookies", 0, "%R/test_env");
12561401
}
12571402
#if !defined(_WIN32)
12581403
@ uid=%d(getuid()), gid=%d(getgid())<br />
12591404
#endif
12601405
@ g.zBaseURL = %h(g.zBaseURL)<br />
12611406
@ g.zHttpsURL = %h(g.zHttpsURL)<br />
12621407
@ g.zTop = %h(g.zTop)<br />
1408
+ @ g.zPath = %h(g.zPath)<br />
12631409
for(i=0, c='a'; c<='z'; c++){
12641410
if( login_has_capability(&c, 1) ) zCap[i++] = c;
12651411
}
12661412
zCap[i] = 0;
12671413
@ g.userUid = %d(g.userUid)<br />
12681414
--- src/style.c
+++ src/style.c
@@ -23,21 +23,42 @@
23 #include "style.h"
24
25
26 /*
27 ** Elements of the submenu are collected into the following
28 ** structure and displayed below the main menu by style_header().
 
 
 
 
 
 
 
29 **
30 ** Populate this structure with calls to style_submenu_element()
31 ** prior to calling style_header().
 
32 */
33 static struct Submenu {
34 const char *zLabel;
35 const char *zTitle;
36 const char *zLink;
37 } aSubmenu[30];
38 static int nSubmenu = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
39
40 /*
41 ** Remember that the header has been generated. The footer is omitted
42 ** if an error occurs before the header.
43 */
@@ -216,16 +237,63 @@
216 ...
217 ){
218 va_list ap;
219 assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
220 aSubmenu[nSubmenu].zLabel = zLabel;
221 aSubmenu[nSubmenu].zTitle = zTitle;
222 va_start(ap, zLink);
223 aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
224 va_end(ap);
225 nSubmenu++;
226 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
228 /*
229 ** Compare two submenu items for sorting purposes
230 */
231 static int submenuCompare(const void *a, const void *b){
@@ -411,23 +479,102 @@
411 /* Go back and put the submenu at the top of the page. We delay the
412 ** creation of the submenu until the end so that we can add elements
413 ** to the submenu while generating page text.
414 */
415 cgi_destination(CGI_HEADER);
416 if( nSubmenu>0 ){
417 int i;
 
 
 
418 @ <div class="submenu">
419 qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
420 for(i=0; i<nSubmenu; i++){
421 struct Submenu *p = &aSubmenu[i];
422 if( p->zLink==0 ){
423 @ <span class="label">%h(p->zLabel)</span>
424 }else{
425 @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426 }
427 }
428 @ </div>
 
 
 
 
 
429 }
430
431 zAd = style_adunit_text(&mAdFlags);
432 if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){
433 @ <div class="content adunit_right_container">
@@ -1246,22 +1393,21 @@
1246 }
1247 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1248 style_header("Environment Test");
1249 showAll = atoi(PD("showall","0"));
1250 if( !showAll ){
1251 style_submenu_element("Show Cookies", "Show Cookies",
1252 "%s/test_env?showall=1", g.zTop);
1253 }else{
1254 style_submenu_element("Hide Cookies", "Hide Cookies",
1255 "%s/test_env", g.zTop);
1256 }
1257 #if !defined(_WIN32)
1258 @ uid=%d(getuid()), gid=%d(getgid())<br />
1259 #endif
1260 @ g.zBaseURL = %h(g.zBaseURL)<br />
1261 @ g.zHttpsURL = %h(g.zHttpsURL)<br />
1262 @ g.zTop = %h(g.zTop)<br />
 
1263 for(i=0, c='a'; c<='z'; c++){
1264 if( login_has_capability(&c, 1) ) zCap[i++] = c;
1265 }
1266 zCap[i] = 0;
1267 @ g.userUid = %d(g.userUid)<br />
1268
--- src/style.c
+++ src/style.c
@@ -23,21 +23,42 @@
23 #include "style.h"
24
25
26 /*
27 ** Elements of the submenu are collected into the following
28 ** structure and displayed below the main menu.
29 **
30 ** Populate these structure with calls to
31 **
32 ** style_submenu_element()
33 ** style_submenu_entry()
34 ** style_submenu_checkbox()
35 ** style_submenu_multichoice()
36 **
37 ** prior to calling style_footer(). The style_footer() routine
38 ** will generate the appropriate HTML text just below the main
39 ** menu.
40 */
41 static struct Submenu {
42 const char *zLabel; /* Button label */
43 const char *zTitle;
44 const char *zLink; /* Jump to this link when button is pressed */
45 } aSubmenu[30];
46 static int nSubmenu = 0; /* Number of buttons */
47 static struct SubmenuCtrl {
48 const char *zName; /* Form query parameter */
49 const char *zLabel; /* Label. Might be NULL for FF_MULTI */
50 int eType; /* FF_ENTRY, FF_CKBOX, FF_MULTI */
51 int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */
52 const char **azChoice; /* value/display pairs for FF_MULTI */
53 const char *zFalse; /* FF_BINARY label when false */
54 } aSubmenuCtrl[20];
55 static int nSubmenuCtrl = 0;
56 #define FF_ENTRY 1
57 #define FF_CKBOX 2
58 #define FF_MULTI 3
59 #define FF_BINARY 4
60
61 /*
62 ** Remember that the header has been generated. The footer is omitted
63 ** if an error occurs before the header.
64 */
@@ -216,16 +237,63 @@
237 ...
238 ){
239 va_list ap;
240 assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
241 aSubmenu[nSubmenu].zLabel = zLabel;
242 aSubmenu[nSubmenu].zTitle = zTitle ? zTitle : zLabel;
243 va_start(ap, zLink);
244 aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
245 va_end(ap);
246 nSubmenu++;
247 }
248 void style_submenu_entry(
249 const char *zName, /* Query parameter name */
250 const char *zLabel, /* Label before the entry box */
251 int iSize /* Size of the entry box */
252 ){
253 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
254 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
255 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
256 aSubmenuCtrl[nSubmenuCtrl].iSize = iSize;
257 aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY;
258 nSubmenuCtrl++;
259 }
260 void style_submenu_checkbox(
261 const char *zName, /* Query parameter name */
262 const char *zLabel /* Label before the checkbox */
263 ){
264 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
265 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
266 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
267 aSubmenuCtrl[nSubmenuCtrl].eType = FF_CKBOX;
268 nSubmenuCtrl++;
269 }
270 void style_submenu_binary(
271 const char *zName, /* Query parameter name */
272 const char *zTrue, /* Label to show when parameter is true */
273 const char *zFalse /* Label to show when the parameter is false */
274 ){
275 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
276 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
277 aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue;
278 aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse;
279 aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY;
280 nSubmenuCtrl++;
281 }
282 void style_submenu_multichoice(
283 const char *zName, /* Query parameter name */
284 int nChoice, /* Number of options */
285 const char **azChoice /* value/display pairs. 2*nChoice entries */
286 ){
287 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
288 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
289 aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice;
290 aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice;
291 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
292 nSubmenuCtrl++;
293 }
294
295
296 /*
297 ** Compare two submenu items for sorting purposes
298 */
299 static int submenuCompare(const void *a, const void *b){
@@ -411,23 +479,102 @@
479 /* Go back and put the submenu at the top of the page. We delay the
480 ** creation of the submenu until the end so that we can add elements
481 ** to the submenu while generating page text.
482 */
483 cgi_destination(CGI_HEADER);
484 if( nSubmenu+nSubmenuCtrl>0 ){
485 int i;
486 if( nSubmenuCtrl ){
487 cgi_printf("<form id='f01' method='GET' action='%R/%s'>", g.zPath);
488 }
489 @ <div class="submenu">
490 if( nSubmenu>0 ){
491 qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
492 for(i=0; i<nSubmenu; i++){
493 struct Submenu *p = &aSubmenu[i];
494 if( p->zLink==0 ){
495 @ <span class="label">%h(p->zLabel)</span>
496 }else{
497 @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
498 }
499 }
500 }
501 if( nSubmenuCtrl>0 ){
502 for(i=0; i<nSubmenuCtrl; i++){
503 const char *zQPN = aSubmenuCtrl[i].zName;
504 cgi_tag_query_parameter(zQPN);
505 switch( aSubmenuCtrl[i].eType ){
506 case FF_ENTRY: {
507 cgi_printf(
508 "<span class='submenuctrl'>"
509 "%h:&nbsp;<input type='text' name='%s' size='%d' "
510 "value='%h'></span>\n",
511 aSubmenuCtrl[i].zLabel,
512 zQPN,
513 aSubmenuCtrl[i].iSize,
514 PD(zQPN,"")
515 );
516 break;
517 }
518 case FF_CKBOX: {
519 cgi_printf(
520 "<span class='submenuctrl'>"
521 "%h:&nbsp;<input type='checkbox' name='%s'%s "
522 "onchange='gebi(\"f01\").submit();'></span>\n",
523 aSubmenuCtrl[i].zLabel,
524 zQPN,
525 PB(zQPN) ? " checked":""
526 );
527 break;
528 }
529 case FF_MULTI: {
530 int j;
531 const char *zVal = P(zQPN);
532 cgi_printf(
533 "<select class='submenuctrl' size='1' name='%s' "
534 "onchange='gebi(\"f01\").submit();'>\n",
535 zQPN
536 );
537 for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){
538 const char *zQPV = aSubmenuCtrl[i].azChoice[j];
539 cgi_printf(
540 "<option value='%h'%s>%h</option>\n",
541 zQPV,
542 fossil_strcmp(zVal,zQPV)==0 ? " selected" : "",
543 aSubmenuCtrl[i].azChoice[j+1]
544 );
545 }
546 @ </select>
547 break;
548 }
549 case FF_BINARY: {
550 int isTrue = PB(zQPN);
551 cgi_printf(
552 "<select class='submenuctrl' size='1' name='%s' "
553 "onchange='gebi(\"f01\").submit();'>\n",
554 zQPN
555 );
556 cgi_printf(
557 "<option value='1'%s>%h</option>\n",
558 isTrue ? " selected":"", aSubmenuCtrl[i].zLabel
559 );
560 cgi_printf(
561 "<option value='0'%s>%h</option>\n",
562 (!isTrue) ? " selected":"", aSubmenuCtrl[i].zFalse
563 );
564 @ </select>
565 break;
566 }
567 }
568 }
569 }
570 @ </div>
571 if( nSubmenuCtrl ){
572 cgi_query_parameters_to_hidden();
573 cgi_tag_query_parameter(0);
574 @ </form>
575 }
576 }
577
578 zAd = style_adunit_text(&mAdFlags);
579 if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){
580 @ <div class="content adunit_right_container">
@@ -1246,22 +1393,21 @@
1393 }
1394 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1395 style_header("Environment Test");
1396 showAll = atoi(PD("showall","0"));
1397 if( !showAll ){
1398 style_submenu_element("Show Cookies", 0, "%R/test_env?showall=1");
 
1399 }else{
1400 style_submenu_element("Hide Cookies", 0, "%R/test_env");
 
1401 }
1402 #if !defined(_WIN32)
1403 @ uid=%d(getuid()), gid=%d(getgid())<br />
1404 #endif
1405 @ g.zBaseURL = %h(g.zBaseURL)<br />
1406 @ g.zHttpsURL = %h(g.zHttpsURL)<br />
1407 @ g.zTop = %h(g.zTop)<br />
1408 @ g.zPath = %h(g.zPath)<br />
1409 for(i=0, c='a'; c<='z'; c++){
1410 if( login_has_capability(&c, 1) ) zCap[i++] = c;
1411 }
1412 zCap[i] = 0;
1413 @ g.userUid = %d(g.userUid)<br />
1414
--- src/th_main.c
+++ src/th_main.c
@@ -365,10 +365,69 @@
365365
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
366366
}
367367
Th_SetResultInt(interp, rc);
368368
return TH_OK;
369369
}
370
+
371
+/*
372
+** TH1 command: searchable STRING...
373
+**
374
+** Return true if searching in any of the document classes identified
375
+** by STRING is enabled for the repository and user has the necessary
376
+** capabilities to perform the search.
377
+**
378
+** Document classes:
379
+**
380
+** c Check-in comments
381
+** d Embedded documentation
382
+** t Tickets
383
+** w Wiki
384
+**
385
+** To be clear, only one of the document classes identified by each STRING
386
+** needs to be searchable in order for that argument to be true. But
387
+** all arguments must be true for this routine to return true. Hence, to
388
+** see if ALL document classes are searchable:
389
+**
390
+** if {[searchable c d t w]} {...}
391
+**
392
+** But to see if ANY document class is searchable:
393
+**
394
+** if {[searchable cdtw]} {...}
395
+**
396
+** This command is useful for enabling or disabling a "Search" entry
397
+** on the menu bar.
398
+*/
399
+static int searchableCmd(
400
+ Th_Interp *interp,
401
+ void *p,
402
+ int argc,
403
+ const char **argv,
404
+ int *argl
405
+){
406
+ int rc = 1, i, j;
407
+ unsigned int searchCap = search_restrict(SRCH_ALL);
408
+ if( argc<2 ){
409
+ return Th_WrongNumArgs(interp, "hascap STRING ...");
410
+ }
411
+ for(i=1; i<argc && rc; i++){
412
+ int match = 0;
413
+ for(j=0; j<argl[i]; j++){
414
+ switch( argv[i][j] ){
415
+ case 'c': match |= searchCap & SRCH_CKIN; break;
416
+ case 'd': match |= searchCap & SRCH_DOC; break;
417
+ case 't': match |= searchCap & SRCH_TKT; break;
418
+ case 'w': match |= searchCap & SRCH_WIKI; break;
419
+ }
420
+ }
421
+ if( !match ) rc = 0;
422
+ }
423
+ if( g.thTrace ){
424
+ Th_Trace("[searchable %#h] => %d<br />\n", argl[1], argv[1], rc);
425
+ }
426
+ Th_SetResultInt(interp, rc);
427
+ return TH_OK;
428
+}
370429
371430
/*
372431
** TH1 command: hasfeature STRING
373432
**
374433
** Return true if the fossil binary has the given compile-time feature
@@ -1419,10 +1478,11 @@
14191478
{"randhex", randhexCmd, 0},
14201479
{"regexp", regexpCmd, 0},
14211480
{"reinitialize", reinitializeCmd, 0},
14221481
{"render", renderCmd, 0},
14231482
{"repository", repositoryCmd, 0},
1483
+ {"searchable", searchableCmd, 0},
14241484
{"setParameter", setParameterCmd, 0},
14251485
{"setting", settingCmd, 0},
14261486
{"styleHeader", styleHeaderCmd, 0},
14271487
{"styleFooter", styleFooterCmd, 0},
14281488
{"tclReady", tclReadyCmd, 0},
14291489
--- src/th_main.c
+++ src/th_main.c
@@ -365,10 +365,69 @@
365 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
366 }
367 Th_SetResultInt(interp, rc);
368 return TH_OK;
369 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
371 /*
372 ** TH1 command: hasfeature STRING
373 **
374 ** Return true if the fossil binary has the given compile-time feature
@@ -1419,10 +1478,11 @@
1419 {"randhex", randhexCmd, 0},
1420 {"regexp", regexpCmd, 0},
1421 {"reinitialize", reinitializeCmd, 0},
1422 {"render", renderCmd, 0},
1423 {"repository", repositoryCmd, 0},
 
1424 {"setParameter", setParameterCmd, 0},
1425 {"setting", settingCmd, 0},
1426 {"styleHeader", styleHeaderCmd, 0},
1427 {"styleFooter", styleFooterCmd, 0},
1428 {"tclReady", tclReadyCmd, 0},
1429
--- src/th_main.c
+++ src/th_main.c
@@ -365,10 +365,69 @@
365 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
366 }
367 Th_SetResultInt(interp, rc);
368 return TH_OK;
369 }
370
371 /*
372 ** TH1 command: searchable STRING...
373 **
374 ** Return true if searching in any of the document classes identified
375 ** by STRING is enabled for the repository and user has the necessary
376 ** capabilities to perform the search.
377 **
378 ** Document classes:
379 **
380 ** c Check-in comments
381 ** d Embedded documentation
382 ** t Tickets
383 ** w Wiki
384 **
385 ** To be clear, only one of the document classes identified by each STRING
386 ** needs to be searchable in order for that argument to be true. But
387 ** all arguments must be true for this routine to return true. Hence, to
388 ** see if ALL document classes are searchable:
389 **
390 ** if {[searchable c d t w]} {...}
391 **
392 ** But to see if ANY document class is searchable:
393 **
394 ** if {[searchable cdtw]} {...}
395 **
396 ** This command is useful for enabling or disabling a "Search" entry
397 ** on the menu bar.
398 */
399 static int searchableCmd(
400 Th_Interp *interp,
401 void *p,
402 int argc,
403 const char **argv,
404 int *argl
405 ){
406 int rc = 1, i, j;
407 unsigned int searchCap = search_restrict(SRCH_ALL);
408 if( argc<2 ){
409 return Th_WrongNumArgs(interp, "hascap STRING ...");
410 }
411 for(i=1; i<argc && rc; i++){
412 int match = 0;
413 for(j=0; j<argl[i]; j++){
414 switch( argv[i][j] ){
415 case 'c': match |= searchCap & SRCH_CKIN; break;
416 case 'd': match |= searchCap & SRCH_DOC; break;
417 case 't': match |= searchCap & SRCH_TKT; break;
418 case 'w': match |= searchCap & SRCH_WIKI; break;
419 }
420 }
421 if( !match ) rc = 0;
422 }
423 if( g.thTrace ){
424 Th_Trace("[searchable %#h] => %d<br />\n", argl[1], argv[1], rc);
425 }
426 Th_SetResultInt(interp, rc);
427 return TH_OK;
428 }
429
430 /*
431 ** TH1 command: hasfeature STRING
432 **
433 ** Return true if the fossil binary has the given compile-time feature
@@ -1419,10 +1478,11 @@
1478 {"randhex", randhexCmd, 0},
1479 {"regexp", regexpCmd, 0},
1480 {"reinitialize", reinitializeCmd, 0},
1481 {"render", renderCmd, 0},
1482 {"repository", repositoryCmd, 0},
1483 {"searchable", searchableCmd, 0},
1484 {"setParameter", setParameterCmd, 0},
1485 {"setting", settingCmd, 0},
1486 {"styleHeader", styleHeaderCmd, 0},
1487 {"styleFooter", styleFooterCmd, 0},
1488 {"tclReady", tclReadyCmd, 0},
1489
+117 -108
--- src/timeline.c
+++ src/timeline.c
@@ -494,10 +494,11 @@
494494
" (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm"
495495
" FROM mlink"
496496
" WHERE mid=:mid AND (pid!=fid OR pfnid>0)"
497497
" AND (fid>0 OR"
498498
" fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))"
499
+ " AND NOT mlink.isaux"
499500
" ORDER BY 3 /*sort*/"
500501
);
501502
fchngQueryInit = 1;
502503
}
503504
db_bind_int(&fchngQuery, ":mid", rid);
@@ -666,16 +667,16 @@
666667
if( cSep=='[' ) cgi_printf("[");
667668
cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n");
668669
}
669670
cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
670671
graph_free(pGraph);
671
- @ var canvasDiv = gebi("canvas");
672
- @ var canvasStyle = window.getComputedStyle && window.getComputedStyle(canvasDiv,null);
673
- @ var lineColor = (canvasStyle && canvasStyle.getPropertyValue('color')) || 'black';
674
- @ var bgColor = (canvasStyle && canvasStyle.getPropertyValue('background-color')) || 'white';
675
- @ if( bgColor=='transparent' ) bgColor = 'white';
676
- @ var boxColor = lineColor;
672
+ @ var cDiv = gebi("canvas");
673
+ @ var csty = window.getComputedStyle && window.getComputedStyle(cDiv,null);
674
+ @ var lineClr = (csty && csty.getPropertyValue('color')) || 'black';
675
+ @ var bgClr = (csty && csty.getPropertyValue('background-color')) ||'white';
676
+ @ if( bgClr=='transparent' ) bgClr = 'white';
677
+ @ var boxColor = lineClr;
677678
@ function drawBox(color,x0,y0,x1,y1){
678679
@ var n = document.createElement("div");
679680
@ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
680681
@ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
681682
@ var w = x1-x0+1;
@@ -685,11 +686,11 @@
685686
@ n.style.left = x0+"px";
686687
@ n.style.top = y0+"px";
687688
@ n.style.width = w+"px";
688689
@ n.style.height = h+"px";
689690
@ n.style.backgroundColor = color;
690
- @ canvasDiv.appendChild(n);
691
+ @ cDiv.appendChild(n);
691692
@ return n;
692693
@ }
693694
@ function absoluteY(id){
694695
@ var obj = gebi(id);
695696
@ if( !obj ) return;
@@ -711,39 +712,39 @@
711712
@ }while( obj = obj.offsetParent );
712713
@ }
713714
@ return left;
714715
@ }
715716
@ function drawUpArrow(x,y0,y1){
716
- @ drawBox(lineColor,x,y0,x+1,y1);
717
+ @ drawBox(lineClr,x,y0,x+1,y1);
717718
@ if( y0+10>=y1 ){
718
- @ drawBox(lineColor,x-1,y0+1,x+2,y0+2);
719
- @ drawBox(lineColor,x-2,y0+3,x+3,y0+4);
719
+ @ drawBox(lineClr,x-1,y0+1,x+2,y0+2);
720
+ @ drawBox(lineClr,x-2,y0+3,x+3,y0+4);
720721
@ }else{
721
- @ drawBox(lineColor,x-1,y0+2,x+2,y0+4);
722
- @ drawBox(lineColor,x-2,y0+5,x+3,y0+7);
722
+ @ drawBox(lineClr,x-1,y0+2,x+2,y0+4);
723
+ @ drawBox(lineClr,x-2,y0+5,x+3,y0+7);
723724
@ }
724725
@ }
725726
@ function drawThinArrow(y,xFrom,xTo){
726727
@ if( xFrom<xTo ){
727
- @ drawBox(lineColor,xFrom,y,xTo,y);
728
- @ drawBox(lineColor,xTo-3,y-1,xTo-2,y+1);
729
- @ drawBox(lineColor,xTo-4,y-2,xTo-4,y+2);
728
+ @ drawBox(lineClr,xFrom,y,xTo,y);
729
+ @ drawBox(lineClr,xTo-3,y-1,xTo-2,y+1);
730
+ @ drawBox(lineClr,xTo-4,y-2,xTo-4,y+2);
730731
@ }else{
731
- @ drawBox(lineColor,xTo,y,xFrom,y);
732
- @ drawBox(lineColor,xTo+2,y-1,xTo+3,y+1);
733
- @ drawBox(lineColor,xTo+4,y-2,xTo+4,y+2);
732
+ @ drawBox(lineClr,xTo,y,xFrom,y);
733
+ @ drawBox(lineClr,xTo+2,y-1,xTo+3,y+1);
734
+ @ drawBox(lineClr,xTo+4,y-2,xTo+4,y+2);
734735
@ }
735736
@ }
736737
@ function drawThinLine(x0,y0,x1,y1){
737
- @ drawBox(lineColor,x0,y0,x1,y1);
738
+ @ drawBox(lineClr,x0,y0,x1,y1);
738739
@ }
739740
@ function drawNodeBox(color,x0,y0,x1,y1){
740741
@ drawBox(color,x0,y0,x1,y1).style.cursor = "pointer";
741742
@ }
742743
@ function drawNode(p, left, btm){
743744
@ drawNodeBox(boxColor,p.x-5,p.y-5,p.x+6,p.y+6);
744
- @ drawNodeBox(p.bg||bgColor,p.x-4,p.y-4,p.x+5,p.y+5);
745
+ @ drawNodeBox(p.bg||bgClr,p.x-4,p.y-4,p.x+5,p.y+5);
745746
@ if( p.u>0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5);
746747
@ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2);
747748
if( !omitDescenders ){
748749
@ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5);
749750
@ if( p.d ) drawUpArrow(p.x, p.y+6, btm);
@@ -765,11 +766,11 @@
765766
@ for(var i=0; i<n; i+=2){
766767
@ var x1 = p.au[i]*railPitch + left;
767768
@ var x0 = x1>p.x ? p.x+7 : p.x-6;
768769
@ var u = rowinfo[p.au[i+1]-1];
769770
@ if(u.id<p.id){
770
- @ drawBox(lineColor,x0,p.y,x1,p.y+1);
771
+ @ drawBox(lineClr,x0,p.y,x1,p.y+1);
771772
@ drawUpArrow(x1, u.y+6, p.y);
772773
@ }else{
773774
@ drawBox("#600000",x0,p.y,x1,p.y+1);
774775
@ drawBox("#600000",x1-1,p.y,x1,u.y+1);
775776
@ drawBox("#600000",x1,u.y,u.x-6,u.y+1);
@@ -952,13 +953,22 @@
952953
if( z==0 ) return -1.0;
953954
if( fossil_isdate(z) ){
954955
mtime = db_double(0.0, "SELECT julianday(%Q,'utc')", z);
955956
if( mtime>0.0 ) return mtime;
956957
}
957
- rid = symbolic_name_to_rid(z, "ci");
958
- if( rid==0 ) return -1.0;
959
- mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
958
+ rid = symbolic_name_to_rid(z, "*");
959
+ if( rid ){
960
+ mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
961
+ }else{
962
+ mtime = db_double(-1.0,
963
+ "SELECT max(event.mtime) FROM event, tag, tagxref"
964
+ " WHERE tag.tagname GLOB 'event-%q*'"
965
+ " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
966
+ " AND event.objid=tagxref.rid",
967
+ z
968
+ );
969
+ }
960970
return mtime;
961971
}
962972
963973
/*
964974
** The value of one second in julianday notation
@@ -1011,10 +1021,47 @@
10111021
}
10121022
db_finalize(&q);
10131023
return blob_str(&out);
10141024
}
10151025
1026
+
1027
+/*
1028
+** Add the select/option box to the timeline submenu that is used to
1029
+** set the y= parameter that determines which elements to display
1030
+** on the timeline.
1031
+*/
1032
+static void timeline_y_submenu(void){
1033
+ static int i = 0;
1034
+ static const char *az[12];
1035
+ if( i==0 ){
1036
+ az[0] = "all";
1037
+ az[1] = "All Types";
1038
+ i = 2;
1039
+ if( g.perm.Read ){
1040
+ az[i++] = "ci";
1041
+ az[i++] = "Check-ins";
1042
+ az[i++] = "g";
1043
+ az[i++] = "Tags";
1044
+ }
1045
+ if( g.perm.RdWiki ){
1046
+ az[i++] = "e";
1047
+ az[i++] = "Tech Notes";
1048
+ }
1049
+ if( g.perm.RdTkt ){
1050
+ az[i++] = "t";
1051
+ az[i++] = "Tickets";
1052
+ }
1053
+ if( g.perm.RdWiki ){
1054
+ az[i++] = "w";
1055
+ az[i++] = "Wiki";
1056
+ }
1057
+ assert( i<=ArraySize(az) );
1058
+ }
1059
+ if( i>2 ){
1060
+ style_submenu_multichoice("y", i/2, az);
1061
+ }
1062
+}
10161063
10171064
/*
10181065
** WEBPAGE: timeline
10191066
**
10201067
** Query parameters:
@@ -1055,11 +1102,11 @@
10551102
*/
10561103
void page_timeline(void){
10571104
Stmt q; /* Query used to generate the timeline */
10581105
Blob sql; /* text of SQL used to generate timeline */
10591106
Blob desc; /* Description of the timeline */
1060
- int nEntry = atoi(PD("n","20")); /* Max number of entries on timeline */
1107
+ int nEntry; /* Max number of entries on timeline */
10611108
int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */
10621109
int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */
10631110
int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */
10641111
const char *zUser = P("u"); /* All entries by this user if not NULL */
10651112
const char *zType = PD("y","all"); /* Type of events. All if NULL */
@@ -1069,11 +1116,11 @@
10691116
const char *zTagName = P("t"); /* Show events with this tag */
10701117
const char *zBrName = P("r"); /* Show events related to this tag */
10711118
const char *zSearch = P("s"); /* Search string */
10721119
const char *zUses = P("uf"); /* Only show checkins hold this file */
10731120
const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
1074
- const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (week-of-year)*/
1121
+ const char *zYearWeek = P("yw"); /* Checkins for YYYY-WW (week-of-year) */
10751122
int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
10761123
int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
10771124
int tagid; /* Tag ID */
10781125
int tmFlags = 0; /* Timeline flags */
10791126
const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1084,10 +1131,32 @@
10841131
int noMerge = P("shortest")==0; /* Follow merge links if shorter */
10851132
int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
10861133
int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
10871134
int pd_rid;
10881135
double rBefore, rAfter, rCirca; /* Boundary times */
1136
+ const char *z;
1137
+ char *zOlderButton = 0; /* URL for Older button at the bottom */
1138
+
1139
+ /* Set number of rows to display */
1140
+ z = P("n");
1141
+ if( z ){
1142
+ if( fossil_strcmp(z,"all")==0 ){
1143
+ nEntry = 0;
1144
+ }else{
1145
+ nEntry = atoi(z);
1146
+ if( nEntry<=0 ){
1147
+ cgi_replace_query_parameter("n","10");
1148
+ nEntry = 10;
1149
+ }
1150
+ }
1151
+ }else if( zCirca ){
1152
+ cgi_replace_query_parameter("n","11");
1153
+ nEntry = 11;
1154
+ }else{
1155
+ cgi_replace_query_parameter("n","50");
1156
+ nEntry = 50;
1157
+ }
10891158
10901159
/* To view the timeline, must have permission to read project data.
10911160
*/
10921161
pd_rid = name_to_typed_rid(P("dp"),"ci");
10931162
if( pd_rid ){
@@ -1097,10 +1166,11 @@
10971166
if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
10981167
login_needed();
10991168
return;
11001169
}
11011170
url_initialize(&url, "timeline");
1171
+ cgi_query_parameters_to_url(&url);
11021172
if( zTagName && g.perm.Read ){
11031173
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
11041174
zThisTag = zTagName;
11051175
}else if( zBrName && g.perm.Read ){
11061176
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
@@ -1117,32 +1187,26 @@
11171187
if( zType[0]=='a' ){
11181188
tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
11191189
}else{
11201190
tmFlags |= TIMELINE_GRAPH;
11211191
}
1122
- if( nEntry>0 ) url_add_parameter(&url, "n", mprintf("%d", nEntry));
1123
- if( P("ng")!=0 || zSearch!=0 ){
1192
+ if( PB("ng") || zSearch!=0 ){
11241193
tmFlags &= ~TIMELINE_GRAPH;
1125
- url_add_parameter(&url, "ng", 0);
11261194
}
1127
- if( P("brbg")!=0 ){
1195
+ if( PB("brbg") ){
11281196
tmFlags |= TIMELINE_BRCOLOR;
1129
- url_add_parameter(&url, "brbg", 0);
11301197
}
1131
- if( P("unhide")!=0 ){
1198
+ if( PB("unhide") ){
11321199
tmFlags |= TIMELINE_UNHIDE;
1133
- url_add_parameter(&url, "unhide", 0);
11341200
}
1135
- if( P("ubg")!=0 ){
1201
+ if( PB("ubg") ){
11361202
tmFlags |= TIMELINE_UCOLOR;
1137
- url_add_parameter(&url, "ubg", 0);
11381203
}
11391204
if( zUses!=0 ){
11401205
int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
11411206
if( ufid ){
11421207
zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1143
- url_add_parameter(&url, "uf", zUses);
11441208
db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
11451209
compute_uses_file("usesfile", ufid, 0);
11461210
zType = "ci";
11471211
}else{
11481212
zUses = 0;
@@ -1161,22 +1225,20 @@
11611225
timeline_temp_table();
11621226
blob_zero(&sql);
11631227
blob_zero(&desc);
11641228
blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
11651229
blob_append(&sql, timeline_query_for_www(), -1);
1166
- if( P("fc")!=0 || P("v")!=0 || P("detail")!=0 ){
1230
+ if( PB("fc") || PB("v") || PB("detail") ){
11671231
tmFlags |= TIMELINE_FCHANGES;
1168
- url_add_parameter(&url, "v", 0);
11691232
}
11701233
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
11711234
blob_append_sql(&sql,
11721235
" AND NOT EXISTS(SELECT 1 FROM tagxref"
11731236
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
11741237
TAG_HIDDEN
11751238
);
11761239
}
1177
- if( !useDividers ) url_add_parameter(&url, "nd", 0);
11781240
if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
11791241
/* If from= and to= are present, display all nodes on a path connecting
11801242
** the two */
11811243
PathNode *p = 0;
11821244
const char *zFrom = 0;
@@ -1241,34 +1303,19 @@
12411303
}
12421304
if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
12431305
}
12441306
blob_appendf(&desc, " of %z[%S]</a>",
12451307
href("%R/info/%s", zUuid), zUuid);
1246
- if( p_rid ){
1247
- url_add_parameter(&url, "p", zUuid);
1248
- }
12491308
if( d_rid ){
12501309
if( p_rid ){
12511310
/* If both p= and d= are set, we don't have the uuid of d yet. */
12521311
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
12531312
}
1254
- url_add_parameter(&url, "d", zUuid);
1255
- }
1256
- if( nEntry>20 ){
1257
- timeline_submenu(&url, "20 Entries", "n", "20", 0);
1258
- }
1259
- if( nEntry<200 && nEntry>0 ){
1260
- timeline_submenu(&url, "200 Entries", "n", "200", 0);
1261
- }
1262
- if( tmFlags & TIMELINE_FCHANGES ){
1263
- timeline_submenu(&url, "Hide Files", "v", 0, 0);
1264
- }else{
1265
- timeline_submenu(&url, "Show Files", "v", "", 0);
1266
- }
1267
- if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1268
- timeline_submenu(&url, "Unhide", "unhide", "", 0);
1269
- }
1313
+ }
1314
+ style_submenu_binary("v","With Files","Without Files");
1315
+ style_submenu_entry("n","Lines",1);
1316
+ timeline_y_submenu();
12701317
}else if( f_rid && g.perm.Read ){
12711318
/* If f= is present, ignore all other parameters other than n= */
12721319
char *zUuid;
12731320
db_multi_exec(
12741321
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1282,16 +1329,11 @@
12821329
if( useDividers ) timeline_add_dividers(0, f_rid);
12831330
blob_appendf(&desc, "Parents and children of check-in ");
12841331
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
12851332
blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
12861333
tmFlags |= TIMELINE_DISJOINT;
1287
- url_add_parameter(&url, "f", zUuid);
1288
- if( tmFlags & TIMELINE_FCHANGES ){
1289
- timeline_submenu(&url, "Hide Files", "v", 0, 0);
1290
- }else{
1291
- timeline_submenu(&url, "Show Files", "v", "", 0);
1292
- }
1334
+ style_submenu_binary("v","With Files","Without Files");
12931335
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
12941336
timeline_submenu(&url, "Unhide", "unhide", "", 0);
12951337
}
12961338
}else{
12971339
/* Otherwise, a timeline based on a span of time */
@@ -1316,11 +1358,10 @@
13161358
blob_append_sql(&sql,
13171359
"AND (EXISTS(SELECT 1 FROM tagxref"
13181360
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
13191361
13201362
if( zBrName ){
1321
- url_add_parameter(&url, "r", zBrName);
13221363
/* The next two blob_appendf() calls add SQL that causes checkins that
13231364
** are not part of the branch which are parents or children of the
13241365
** branch to be included in the report. This related check-ins are
13251366
** useful in helping to visualize what has happened on a quiescent
13261367
** branch that is infrequently merged with a much more activate branch.
@@ -1348,15 +1389,11 @@
13481389
" AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
13491390
" WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
13501391
TAG_HIDDEN
13511392
);
13521393
}
1353
- }else{
1354
- url_add_parameter(&url, "mionly", "1");
13551394
}
1356
- }else{
1357
- url_add_parameter(&url, "t", zTagName);
13581395
}
13591396
blob_append_sql(&sql, ")");
13601397
}
13611398
if( (zType[0]=='w' && !g.perm.RdWiki)
13621399
|| (zType[0]=='t' && !g.perm.RdTkt)
@@ -1384,11 +1421,10 @@
13841421
}
13851422
blob_append_sql(&sql, ")");
13861423
}
13871424
}else{ /* zType!="all" */
13881425
blob_append_sql(&sql, " AND event.type=%Q", zType);
1389
- url_add_parameter(&url, "y", zType);
13901426
if( zType[0]=='c' ){
13911427
zEType = "checkin";
13921428
}else if( zType[0]=='w' ){
13931429
zEType = "wiki edit";
13941430
}else if( zType[0]=='t' ){
@@ -1406,41 +1442,35 @@
14061442
zCirca = zBefore = zAfter = 0;
14071443
nEntry = -1;
14081444
}
14091445
blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)",
14101446
zUser, zUser);
1411
- url_add_parameter(&url, "u", zUser);
14121447
zThisUser = zUser;
14131448
}
14141449
if( zSearch ){
14151450
blob_append_sql(&sql,
14161451
" AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
14171452
zSearch, zSearch);
1418
- url_add_parameter(&url, "s", zSearch);
14191453
}
14201454
rBefore = symbolic_name_to_mtime(zBefore);
14211455
rAfter = symbolic_name_to_mtime(zAfter);
14221456
rCirca = symbolic_name_to_mtime(zCirca);
14231457
if( rAfter>0.0 ){
14241458
if( rBefore>0.0 ){
14251459
blob_append_sql(&sql,
14261460
" AND event.mtime>=%.17g AND event.mtime<=%.17g"
14271461
" ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
1428
- url_add_parameter(&url, "a", zAfter);
1429
- url_add_parameter(&url, "b", zBefore);
14301462
nEntry = -1;
14311463
}else{
14321464
blob_append_sql(&sql,
14331465
" AND event.mtime>=%.17g ORDER BY event.mtime ASC",
14341466
rAfter-ONE_SECOND);
1435
- url_add_parameter(&url, "a", zAfter);
14361467
}
14371468
}else if( rBefore>0.0 ){
14381469
blob_append_sql(&sql,
14391470
" AND event.mtime<=%.17g ORDER BY event.mtime DESC",
14401471
rBefore+ONE_SECOND);
1441
- url_add_parameter(&url, "b", zBefore);
14421472
}else if( rCirca>0.0 ){
14431473
Blob sql2;
14441474
blob_init(&sql2, blob_sql_text(&sql), -1);
14451475
blob_append_sql(&sql2,
14461476
" AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1452,11 +1482,10 @@
14521482
" AND event.mtime>=%f ORDER BY event.mtime ASC",
14531483
rCirca
14541484
);
14551485
nEntry -= (nEntry+1)/2;
14561486
if( useDividers ) timeline_add_dividers(rCirca, 0);
1457
- url_add_parameter(&url, "c", zCirca);
14581487
}else{
14591488
blob_append_sql(&sql, " ORDER BY event.mtime DESC");
14601489
}
14611490
if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
14621491
db_multi_exec("%s", blob_sql_text(&sql));
@@ -1509,64 +1538,44 @@
15091538
}
15101539
if( g.perm.Hyperlink ){
15111540
if( zAfter || n==nEntry ){
15121541
zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
15131542
timeline_submenu(&url, "Older", "b", zDate, "a");
1543
+ zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "a", 0));
15141544
free(zDate);
15151545
}
15161546
if( zBefore || (zAfter && n==nEntry) ){
15171547
zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
15181548
timeline_submenu(&url, "Newer", "a", zDate, "b");
15191549
free(zDate);
1520
- }else if( tagid==0 && zUses==0 ){
1521
- if( zType[0]!='a' ){
1522
- timeline_submenu(&url, "All Types", "y", "all", 0);
1523
- }
1524
- if( zType[0]!='w' && g.perm.RdWiki ){
1525
- timeline_submenu(&url, "Wiki Only", "y", "w", 0);
1526
- }
1527
- if( zType[0]!='c' && g.perm.Read ){
1528
- timeline_submenu(&url, "Checkins Only", "y", "ci", 0);
1529
- }
1530
- if( zType[0]!='t' && g.perm.RdTkt ){
1531
- timeline_submenu(&url, "Tickets Only", "y", "t", 0);
1532
- }
1533
- if( zType[0]!='e' && g.perm.RdWiki ){
1534
- timeline_submenu(&url, "Events Only", "y", "e", 0);
1535
- }
1536
- if( zType[0]!='g' && g.perm.Read ){
1537
- timeline_submenu(&url, "Tags Only", "y", "g", 0);
1538
- }
1539
- }
1540
- if( nEntry>20 ){
1541
- timeline_submenu(&url, "20 Entries", "n", "20", 0);
1542
- }
1543
- if( nEntry<200 && nEntry>0 ){
1544
- timeline_submenu(&url, "200 Entries", "n", "200", 0);
15451550
}
15461551
if( zType[0]=='a' || zType[0]=='c' ){
1547
- if( tmFlags & TIMELINE_FCHANGES ){
1548
- timeline_submenu(&url, "Hide Files", "v", 0, 0);
1549
- }else{
1550
- timeline_submenu(&url, "Show Files", "v", "", 0);
1551
- }
15521552
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
15531553
timeline_submenu(&url, "Unhide", "unhide", "", 0);
15541554
}
15551555
}
1556
+ style_submenu_binary("v","With Files","Without Files");
1557
+ if( zUses==0 ) timeline_y_submenu();
1558
+ style_submenu_entry("n","Lines",1);
15561559
}
15571560
}
15581561
if( P("showsql") ){
15591562
@ <blockquote>%h(blob_sql_text(&sql))</blockquote>
15601563
}
1564
+ if( search_restrict(SRCH_CKIN)!=0 ){
1565
+ style_submenu_element("Search", 0, "%R/search?y=c");
1566
+ }
15611567
if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
15621568
blob_zero(&sql);
15631569
db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
15641570
@ <h2>%b(&desc)</h2>
15651571
blob_reset(&desc);
15661572
www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0);
15671573
db_finalize(&q);
1574
+ if( zOlderButton ){
1575
+ @ %z(xhref("class='button'","%z",zOlderButton))Older</a>
1576
+ }
15681577
style_footer();
15691578
}
15701579
15711580
/*
15721581
** The input query q selects various records. Print a human-readable
@@ -2041,10 +2050,10 @@
20412050
" AND blob.rid=c.cid"
20422051
);
20432052
while( db_step(&q)==SQLITE_ROW ){
20442053
const char *zUuid = db_column_text(&q, 0);
20452054
@ <li>
2046
- @ <a href="%s(g.zTop)/timeline?p=%s(zUuid)&amp;d=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2055
+ @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&amp;unhide">%S(zUuid)</a>
20472056
}
20482057
db_finalize(&q);
20492058
style_footer();
20502059
}
20512060
--- src/timeline.c
+++ src/timeline.c
@@ -494,10 +494,11 @@
494 " (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm"
495 " FROM mlink"
496 " WHERE mid=:mid AND (pid!=fid OR pfnid>0)"
497 " AND (fid>0 OR"
498 " fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))"
 
499 " ORDER BY 3 /*sort*/"
500 );
501 fchngQueryInit = 1;
502 }
503 db_bind_int(&fchngQuery, ":mid", rid);
@@ -666,16 +667,16 @@
666 if( cSep=='[' ) cgi_printf("[");
667 cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n");
668 }
669 cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
670 graph_free(pGraph);
671 @ var canvasDiv = gebi("canvas");
672 @ var canvasStyle = window.getComputedStyle && window.getComputedStyle(canvasDiv,null);
673 @ var lineColor = (canvasStyle && canvasStyle.getPropertyValue('color')) || 'black';
674 @ var bgColor = (canvasStyle && canvasStyle.getPropertyValue('background-color')) || 'white';
675 @ if( bgColor=='transparent' ) bgColor = 'white';
676 @ var boxColor = lineColor;
677 @ function drawBox(color,x0,y0,x1,y1){
678 @ var n = document.createElement("div");
679 @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
680 @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
681 @ var w = x1-x0+1;
@@ -685,11 +686,11 @@
685 @ n.style.left = x0+"px";
686 @ n.style.top = y0+"px";
687 @ n.style.width = w+"px";
688 @ n.style.height = h+"px";
689 @ n.style.backgroundColor = color;
690 @ canvasDiv.appendChild(n);
691 @ return n;
692 @ }
693 @ function absoluteY(id){
694 @ var obj = gebi(id);
695 @ if( !obj ) return;
@@ -711,39 +712,39 @@
711 @ }while( obj = obj.offsetParent );
712 @ }
713 @ return left;
714 @ }
715 @ function drawUpArrow(x,y0,y1){
716 @ drawBox(lineColor,x,y0,x+1,y1);
717 @ if( y0+10>=y1 ){
718 @ drawBox(lineColor,x-1,y0+1,x+2,y0+2);
719 @ drawBox(lineColor,x-2,y0+3,x+3,y0+4);
720 @ }else{
721 @ drawBox(lineColor,x-1,y0+2,x+2,y0+4);
722 @ drawBox(lineColor,x-2,y0+5,x+3,y0+7);
723 @ }
724 @ }
725 @ function drawThinArrow(y,xFrom,xTo){
726 @ if( xFrom<xTo ){
727 @ drawBox(lineColor,xFrom,y,xTo,y);
728 @ drawBox(lineColor,xTo-3,y-1,xTo-2,y+1);
729 @ drawBox(lineColor,xTo-4,y-2,xTo-4,y+2);
730 @ }else{
731 @ drawBox(lineColor,xTo,y,xFrom,y);
732 @ drawBox(lineColor,xTo+2,y-1,xTo+3,y+1);
733 @ drawBox(lineColor,xTo+4,y-2,xTo+4,y+2);
734 @ }
735 @ }
736 @ function drawThinLine(x0,y0,x1,y1){
737 @ drawBox(lineColor,x0,y0,x1,y1);
738 @ }
739 @ function drawNodeBox(color,x0,y0,x1,y1){
740 @ drawBox(color,x0,y0,x1,y1).style.cursor = "pointer";
741 @ }
742 @ function drawNode(p, left, btm){
743 @ drawNodeBox(boxColor,p.x-5,p.y-5,p.x+6,p.y+6);
744 @ drawNodeBox(p.bg||bgColor,p.x-4,p.y-4,p.x+5,p.y+5);
745 @ if( p.u>0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5);
746 @ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2);
747 if( !omitDescenders ){
748 @ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5);
749 @ if( p.d ) drawUpArrow(p.x, p.y+6, btm);
@@ -765,11 +766,11 @@
765 @ for(var i=0; i<n; i+=2){
766 @ var x1 = p.au[i]*railPitch + left;
767 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
768 @ var u = rowinfo[p.au[i+1]-1];
769 @ if(u.id<p.id){
770 @ drawBox(lineColor,x0,p.y,x1,p.y+1);
771 @ drawUpArrow(x1, u.y+6, p.y);
772 @ }else{
773 @ drawBox("#600000",x0,p.y,x1,p.y+1);
774 @ drawBox("#600000",x1-1,p.y,x1,u.y+1);
775 @ drawBox("#600000",x1,u.y,u.x-6,u.y+1);
@@ -952,13 +953,22 @@
952 if( z==0 ) return -1.0;
953 if( fossil_isdate(z) ){
954 mtime = db_double(0.0, "SELECT julianday(%Q,'utc')", z);
955 if( mtime>0.0 ) return mtime;
956 }
957 rid = symbolic_name_to_rid(z, "ci");
958 if( rid==0 ) return -1.0;
959 mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
 
 
 
 
 
 
 
 
 
960 return mtime;
961 }
962
963 /*
964 ** The value of one second in julianday notation
@@ -1011,10 +1021,47 @@
1011 }
1012 db_finalize(&q);
1013 return blob_str(&out);
1014 }
1015
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1016
1017 /*
1018 ** WEBPAGE: timeline
1019 **
1020 ** Query parameters:
@@ -1055,11 +1102,11 @@
1055 */
1056 void page_timeline(void){
1057 Stmt q; /* Query used to generate the timeline */
1058 Blob sql; /* text of SQL used to generate timeline */
1059 Blob desc; /* Description of the timeline */
1060 int nEntry = atoi(PD("n","20")); /* Max number of entries on timeline */
1061 int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */
1062 int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */
1063 int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */
1064 const char *zUser = P("u"); /* All entries by this user if not NULL */
1065 const char *zType = PD("y","all"); /* Type of events. All if NULL */
@@ -1069,11 +1116,11 @@
1069 const char *zTagName = P("t"); /* Show events with this tag */
1070 const char *zBrName = P("r"); /* Show events related to this tag */
1071 const char *zSearch = P("s"); /* Search string */
1072 const char *zUses = P("uf"); /* Only show checkins hold this file */
1073 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
1074 const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (week-of-year)*/
1075 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1076 int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
1077 int tagid; /* Tag ID */
1078 int tmFlags = 0; /* Timeline flags */
1079 const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1084,10 +1131,32 @@
1084 int noMerge = P("shortest")==0; /* Follow merge links if shorter */
1085 int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
1086 int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
1087 int pd_rid;
1088 double rBefore, rAfter, rCirca; /* Boundary times */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1089
1090 /* To view the timeline, must have permission to read project data.
1091 */
1092 pd_rid = name_to_typed_rid(P("dp"),"ci");
1093 if( pd_rid ){
@@ -1097,10 +1166,11 @@
1097 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1098 login_needed();
1099 return;
1100 }
1101 url_initialize(&url, "timeline");
 
1102 if( zTagName && g.perm.Read ){
1103 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
1104 zThisTag = zTagName;
1105 }else if( zBrName && g.perm.Read ){
1106 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
@@ -1117,32 +1187,26 @@
1117 if( zType[0]=='a' ){
1118 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
1119 }else{
1120 tmFlags |= TIMELINE_GRAPH;
1121 }
1122 if( nEntry>0 ) url_add_parameter(&url, "n", mprintf("%d", nEntry));
1123 if( P("ng")!=0 || zSearch!=0 ){
1124 tmFlags &= ~TIMELINE_GRAPH;
1125 url_add_parameter(&url, "ng", 0);
1126 }
1127 if( P("brbg")!=0 ){
1128 tmFlags |= TIMELINE_BRCOLOR;
1129 url_add_parameter(&url, "brbg", 0);
1130 }
1131 if( P("unhide")!=0 ){
1132 tmFlags |= TIMELINE_UNHIDE;
1133 url_add_parameter(&url, "unhide", 0);
1134 }
1135 if( P("ubg")!=0 ){
1136 tmFlags |= TIMELINE_UCOLOR;
1137 url_add_parameter(&url, "ubg", 0);
1138 }
1139 if( zUses!=0 ){
1140 int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
1141 if( ufid ){
1142 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1143 url_add_parameter(&url, "uf", zUses);
1144 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
1145 compute_uses_file("usesfile", ufid, 0);
1146 zType = "ci";
1147 }else{
1148 zUses = 0;
@@ -1161,22 +1225,20 @@
1161 timeline_temp_table();
1162 blob_zero(&sql);
1163 blob_zero(&desc);
1164 blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
1165 blob_append(&sql, timeline_query_for_www(), -1);
1166 if( P("fc")!=0 || P("v")!=0 || P("detail")!=0 ){
1167 tmFlags |= TIMELINE_FCHANGES;
1168 url_add_parameter(&url, "v", 0);
1169 }
1170 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1171 blob_append_sql(&sql,
1172 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1173 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
1174 TAG_HIDDEN
1175 );
1176 }
1177 if( !useDividers ) url_add_parameter(&url, "nd", 0);
1178 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
1179 /* If from= and to= are present, display all nodes on a path connecting
1180 ** the two */
1181 PathNode *p = 0;
1182 const char *zFrom = 0;
@@ -1241,34 +1303,19 @@
1241 }
1242 if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
1243 }
1244 blob_appendf(&desc, " of %z[%S]</a>",
1245 href("%R/info/%s", zUuid), zUuid);
1246 if( p_rid ){
1247 url_add_parameter(&url, "p", zUuid);
1248 }
1249 if( d_rid ){
1250 if( p_rid ){
1251 /* If both p= and d= are set, we don't have the uuid of d yet. */
1252 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
1253 }
1254 url_add_parameter(&url, "d", zUuid);
1255 }
1256 if( nEntry>20 ){
1257 timeline_submenu(&url, "20 Entries", "n", "20", 0);
1258 }
1259 if( nEntry<200 && nEntry>0 ){
1260 timeline_submenu(&url, "200 Entries", "n", "200", 0);
1261 }
1262 if( tmFlags & TIMELINE_FCHANGES ){
1263 timeline_submenu(&url, "Hide Files", "v", 0, 0);
1264 }else{
1265 timeline_submenu(&url, "Show Files", "v", "", 0);
1266 }
1267 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1268 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1269 }
1270 }else if( f_rid && g.perm.Read ){
1271 /* If f= is present, ignore all other parameters other than n= */
1272 char *zUuid;
1273 db_multi_exec(
1274 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1282,16 +1329,11 @@
1282 if( useDividers ) timeline_add_dividers(0, f_rid);
1283 blob_appendf(&desc, "Parents and children of check-in ");
1284 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1285 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
1286 tmFlags |= TIMELINE_DISJOINT;
1287 url_add_parameter(&url, "f", zUuid);
1288 if( tmFlags & TIMELINE_FCHANGES ){
1289 timeline_submenu(&url, "Hide Files", "v", 0, 0);
1290 }else{
1291 timeline_submenu(&url, "Show Files", "v", "", 0);
1292 }
1293 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1294 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1295 }
1296 }else{
1297 /* Otherwise, a timeline based on a span of time */
@@ -1316,11 +1358,10 @@
1316 blob_append_sql(&sql,
1317 "AND (EXISTS(SELECT 1 FROM tagxref"
1318 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1319
1320 if( zBrName ){
1321 url_add_parameter(&url, "r", zBrName);
1322 /* The next two blob_appendf() calls add SQL that causes checkins that
1323 ** are not part of the branch which are parents or children of the
1324 ** branch to be included in the report. This related check-ins are
1325 ** useful in helping to visualize what has happened on a quiescent
1326 ** branch that is infrequently merged with a much more activate branch.
@@ -1348,15 +1389,11 @@
1348 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1349 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1350 TAG_HIDDEN
1351 );
1352 }
1353 }else{
1354 url_add_parameter(&url, "mionly", "1");
1355 }
1356 }else{
1357 url_add_parameter(&url, "t", zTagName);
1358 }
1359 blob_append_sql(&sql, ")");
1360 }
1361 if( (zType[0]=='w' && !g.perm.RdWiki)
1362 || (zType[0]=='t' && !g.perm.RdTkt)
@@ -1384,11 +1421,10 @@
1384 }
1385 blob_append_sql(&sql, ")");
1386 }
1387 }else{ /* zType!="all" */
1388 blob_append_sql(&sql, " AND event.type=%Q", zType);
1389 url_add_parameter(&url, "y", zType);
1390 if( zType[0]=='c' ){
1391 zEType = "checkin";
1392 }else if( zType[0]=='w' ){
1393 zEType = "wiki edit";
1394 }else if( zType[0]=='t' ){
@@ -1406,41 +1442,35 @@
1406 zCirca = zBefore = zAfter = 0;
1407 nEntry = -1;
1408 }
1409 blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)",
1410 zUser, zUser);
1411 url_add_parameter(&url, "u", zUser);
1412 zThisUser = zUser;
1413 }
1414 if( zSearch ){
1415 blob_append_sql(&sql,
1416 " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
1417 zSearch, zSearch);
1418 url_add_parameter(&url, "s", zSearch);
1419 }
1420 rBefore = symbolic_name_to_mtime(zBefore);
1421 rAfter = symbolic_name_to_mtime(zAfter);
1422 rCirca = symbolic_name_to_mtime(zCirca);
1423 if( rAfter>0.0 ){
1424 if( rBefore>0.0 ){
1425 blob_append_sql(&sql,
1426 " AND event.mtime>=%.17g AND event.mtime<=%.17g"
1427 " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
1428 url_add_parameter(&url, "a", zAfter);
1429 url_add_parameter(&url, "b", zBefore);
1430 nEntry = -1;
1431 }else{
1432 blob_append_sql(&sql,
1433 " AND event.mtime>=%.17g ORDER BY event.mtime ASC",
1434 rAfter-ONE_SECOND);
1435 url_add_parameter(&url, "a", zAfter);
1436 }
1437 }else if( rBefore>0.0 ){
1438 blob_append_sql(&sql,
1439 " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
1440 rBefore+ONE_SECOND);
1441 url_add_parameter(&url, "b", zBefore);
1442 }else if( rCirca>0.0 ){
1443 Blob sql2;
1444 blob_init(&sql2, blob_sql_text(&sql), -1);
1445 blob_append_sql(&sql2,
1446 " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1452,11 +1482,10 @@
1452 " AND event.mtime>=%f ORDER BY event.mtime ASC",
1453 rCirca
1454 );
1455 nEntry -= (nEntry+1)/2;
1456 if( useDividers ) timeline_add_dividers(rCirca, 0);
1457 url_add_parameter(&url, "c", zCirca);
1458 }else{
1459 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
1460 }
1461 if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
1462 db_multi_exec("%s", blob_sql_text(&sql));
@@ -1509,64 +1538,44 @@
1509 }
1510 if( g.perm.Hyperlink ){
1511 if( zAfter || n==nEntry ){
1512 zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
1513 timeline_submenu(&url, "Older", "b", zDate, "a");
 
1514 free(zDate);
1515 }
1516 if( zBefore || (zAfter && n==nEntry) ){
1517 zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
1518 timeline_submenu(&url, "Newer", "a", zDate, "b");
1519 free(zDate);
1520 }else if( tagid==0 && zUses==0 ){
1521 if( zType[0]!='a' ){
1522 timeline_submenu(&url, "All Types", "y", "all", 0);
1523 }
1524 if( zType[0]!='w' && g.perm.RdWiki ){
1525 timeline_submenu(&url, "Wiki Only", "y", "w", 0);
1526 }
1527 if( zType[0]!='c' && g.perm.Read ){
1528 timeline_submenu(&url, "Checkins Only", "y", "ci", 0);
1529 }
1530 if( zType[0]!='t' && g.perm.RdTkt ){
1531 timeline_submenu(&url, "Tickets Only", "y", "t", 0);
1532 }
1533 if( zType[0]!='e' && g.perm.RdWiki ){
1534 timeline_submenu(&url, "Events Only", "y", "e", 0);
1535 }
1536 if( zType[0]!='g' && g.perm.Read ){
1537 timeline_submenu(&url, "Tags Only", "y", "g", 0);
1538 }
1539 }
1540 if( nEntry>20 ){
1541 timeline_submenu(&url, "20 Entries", "n", "20", 0);
1542 }
1543 if( nEntry<200 && nEntry>0 ){
1544 timeline_submenu(&url, "200 Entries", "n", "200", 0);
1545 }
1546 if( zType[0]=='a' || zType[0]=='c' ){
1547 if( tmFlags & TIMELINE_FCHANGES ){
1548 timeline_submenu(&url, "Hide Files", "v", 0, 0);
1549 }else{
1550 timeline_submenu(&url, "Show Files", "v", "", 0);
1551 }
1552 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1553 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1554 }
1555 }
 
 
 
1556 }
1557 }
1558 if( P("showsql") ){
1559 @ <blockquote>%h(blob_sql_text(&sql))</blockquote>
1560 }
 
 
 
1561 if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
1562 blob_zero(&sql);
1563 db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
1564 @ <h2>%b(&desc)</h2>
1565 blob_reset(&desc);
1566 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0);
1567 db_finalize(&q);
 
 
 
1568 style_footer();
1569 }
1570
1571 /*
1572 ** The input query q selects various records. Print a human-readable
@@ -2041,10 +2050,10 @@
2041 " AND blob.rid=c.cid"
2042 );
2043 while( db_step(&q)==SQLITE_ROW ){
2044 const char *zUuid = db_column_text(&q, 0);
2045 @ <li>
2046 @ <a href="%s(g.zTop)/timeline?p=%s(zUuid)&amp;d=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2047 }
2048 db_finalize(&q);
2049 style_footer();
2050 }
2051
--- src/timeline.c
+++ src/timeline.c
@@ -494,10 +494,11 @@
494 " (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm"
495 " FROM mlink"
496 " WHERE mid=:mid AND (pid!=fid OR pfnid>0)"
497 " AND (fid>0 OR"
498 " fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))"
499 " AND NOT mlink.isaux"
500 " ORDER BY 3 /*sort*/"
501 );
502 fchngQueryInit = 1;
503 }
504 db_bind_int(&fchngQuery, ":mid", rid);
@@ -666,16 +667,16 @@
667 if( cSep=='[' ) cgi_printf("[");
668 cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n");
669 }
670 cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
671 graph_free(pGraph);
672 @ var cDiv = gebi("canvas");
673 @ var csty = window.getComputedStyle && window.getComputedStyle(cDiv,null);
674 @ var lineClr = (csty && csty.getPropertyValue('color')) || 'black';
675 @ var bgClr = (csty && csty.getPropertyValue('background-color')) ||'white';
676 @ if( bgClr=='transparent' ) bgClr = 'white';
677 @ var boxColor = lineClr;
678 @ function drawBox(color,x0,y0,x1,y1){
679 @ var n = document.createElement("div");
680 @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
681 @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
682 @ var w = x1-x0+1;
@@ -685,11 +686,11 @@
686 @ n.style.left = x0+"px";
687 @ n.style.top = y0+"px";
688 @ n.style.width = w+"px";
689 @ n.style.height = h+"px";
690 @ n.style.backgroundColor = color;
691 @ cDiv.appendChild(n);
692 @ return n;
693 @ }
694 @ function absoluteY(id){
695 @ var obj = gebi(id);
696 @ if( !obj ) return;
@@ -711,39 +712,39 @@
712 @ }while( obj = obj.offsetParent );
713 @ }
714 @ return left;
715 @ }
716 @ function drawUpArrow(x,y0,y1){
717 @ drawBox(lineClr,x,y0,x+1,y1);
718 @ if( y0+10>=y1 ){
719 @ drawBox(lineClr,x-1,y0+1,x+2,y0+2);
720 @ drawBox(lineClr,x-2,y0+3,x+3,y0+4);
721 @ }else{
722 @ drawBox(lineClr,x-1,y0+2,x+2,y0+4);
723 @ drawBox(lineClr,x-2,y0+5,x+3,y0+7);
724 @ }
725 @ }
726 @ function drawThinArrow(y,xFrom,xTo){
727 @ if( xFrom<xTo ){
728 @ drawBox(lineClr,xFrom,y,xTo,y);
729 @ drawBox(lineClr,xTo-3,y-1,xTo-2,y+1);
730 @ drawBox(lineClr,xTo-4,y-2,xTo-4,y+2);
731 @ }else{
732 @ drawBox(lineClr,xTo,y,xFrom,y);
733 @ drawBox(lineClr,xTo+2,y-1,xTo+3,y+1);
734 @ drawBox(lineClr,xTo+4,y-2,xTo+4,y+2);
735 @ }
736 @ }
737 @ function drawThinLine(x0,y0,x1,y1){
738 @ drawBox(lineClr,x0,y0,x1,y1);
739 @ }
740 @ function drawNodeBox(color,x0,y0,x1,y1){
741 @ drawBox(color,x0,y0,x1,y1).style.cursor = "pointer";
742 @ }
743 @ function drawNode(p, left, btm){
744 @ drawNodeBox(boxColor,p.x-5,p.y-5,p.x+6,p.y+6);
745 @ drawNodeBox(p.bg||bgClr,p.x-4,p.y-4,p.x+5,p.y+5);
746 @ if( p.u>0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5);
747 @ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2);
748 if( !omitDescenders ){
749 @ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5);
750 @ if( p.d ) drawUpArrow(p.x, p.y+6, btm);
@@ -765,11 +766,11 @@
766 @ for(var i=0; i<n; i+=2){
767 @ var x1 = p.au[i]*railPitch + left;
768 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
769 @ var u = rowinfo[p.au[i+1]-1];
770 @ if(u.id<p.id){
771 @ drawBox(lineClr,x0,p.y,x1,p.y+1);
772 @ drawUpArrow(x1, u.y+6, p.y);
773 @ }else{
774 @ drawBox("#600000",x0,p.y,x1,p.y+1);
775 @ drawBox("#600000",x1-1,p.y,x1,u.y+1);
776 @ drawBox("#600000",x1,u.y,u.x-6,u.y+1);
@@ -952,13 +953,22 @@
953 if( z==0 ) return -1.0;
954 if( fossil_isdate(z) ){
955 mtime = db_double(0.0, "SELECT julianday(%Q,'utc')", z);
956 if( mtime>0.0 ) return mtime;
957 }
958 rid = symbolic_name_to_rid(z, "*");
959 if( rid ){
960 mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
961 }else{
962 mtime = db_double(-1.0,
963 "SELECT max(event.mtime) FROM event, tag, tagxref"
964 " WHERE tag.tagname GLOB 'event-%q*'"
965 " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
966 " AND event.objid=tagxref.rid",
967 z
968 );
969 }
970 return mtime;
971 }
972
973 /*
974 ** The value of one second in julianday notation
@@ -1011,10 +1021,47 @@
1021 }
1022 db_finalize(&q);
1023 return blob_str(&out);
1024 }
1025
1026
1027 /*
1028 ** Add the select/option box to the timeline submenu that is used to
1029 ** set the y= parameter that determines which elements to display
1030 ** on the timeline.
1031 */
1032 static void timeline_y_submenu(void){
1033 static int i = 0;
1034 static const char *az[12];
1035 if( i==0 ){
1036 az[0] = "all";
1037 az[1] = "All Types";
1038 i = 2;
1039 if( g.perm.Read ){
1040 az[i++] = "ci";
1041 az[i++] = "Check-ins";
1042 az[i++] = "g";
1043 az[i++] = "Tags";
1044 }
1045 if( g.perm.RdWiki ){
1046 az[i++] = "e";
1047 az[i++] = "Tech Notes";
1048 }
1049 if( g.perm.RdTkt ){
1050 az[i++] = "t";
1051 az[i++] = "Tickets";
1052 }
1053 if( g.perm.RdWiki ){
1054 az[i++] = "w";
1055 az[i++] = "Wiki";
1056 }
1057 assert( i<=ArraySize(az) );
1058 }
1059 if( i>2 ){
1060 style_submenu_multichoice("y", i/2, az);
1061 }
1062 }
1063
1064 /*
1065 ** WEBPAGE: timeline
1066 **
1067 ** Query parameters:
@@ -1055,11 +1102,11 @@
1102 */
1103 void page_timeline(void){
1104 Stmt q; /* Query used to generate the timeline */
1105 Blob sql; /* text of SQL used to generate timeline */
1106 Blob desc; /* Description of the timeline */
1107 int nEntry; /* Max number of entries on timeline */
1108 int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */
1109 int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */
1110 int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */
1111 const char *zUser = P("u"); /* All entries by this user if not NULL */
1112 const char *zType = PD("y","all"); /* Type of events. All if NULL */
@@ -1069,11 +1116,11 @@
1116 const char *zTagName = P("t"); /* Show events with this tag */
1117 const char *zBrName = P("r"); /* Show events related to this tag */
1118 const char *zSearch = P("s"); /* Search string */
1119 const char *zUses = P("uf"); /* Only show checkins hold this file */
1120 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
1121 const char *zYearWeek = P("yw"); /* Checkins for YYYY-WW (week-of-year) */
1122 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1123 int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
1124 int tagid; /* Tag ID */
1125 int tmFlags = 0; /* Timeline flags */
1126 const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1084,10 +1131,32 @@
1131 int noMerge = P("shortest")==0; /* Follow merge links if shorter */
1132 int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
1133 int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
1134 int pd_rid;
1135 double rBefore, rAfter, rCirca; /* Boundary times */
1136 const char *z;
1137 char *zOlderButton = 0; /* URL for Older button at the bottom */
1138
1139 /* Set number of rows to display */
1140 z = P("n");
1141 if( z ){
1142 if( fossil_strcmp(z,"all")==0 ){
1143 nEntry = 0;
1144 }else{
1145 nEntry = atoi(z);
1146 if( nEntry<=0 ){
1147 cgi_replace_query_parameter("n","10");
1148 nEntry = 10;
1149 }
1150 }
1151 }else if( zCirca ){
1152 cgi_replace_query_parameter("n","11");
1153 nEntry = 11;
1154 }else{
1155 cgi_replace_query_parameter("n","50");
1156 nEntry = 50;
1157 }
1158
1159 /* To view the timeline, must have permission to read project data.
1160 */
1161 pd_rid = name_to_typed_rid(P("dp"),"ci");
1162 if( pd_rid ){
@@ -1097,10 +1166,11 @@
1166 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1167 login_needed();
1168 return;
1169 }
1170 url_initialize(&url, "timeline");
1171 cgi_query_parameters_to_url(&url);
1172 if( zTagName && g.perm.Read ){
1173 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
1174 zThisTag = zTagName;
1175 }else if( zBrName && g.perm.Read ){
1176 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
@@ -1117,32 +1187,26 @@
1187 if( zType[0]=='a' ){
1188 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
1189 }else{
1190 tmFlags |= TIMELINE_GRAPH;
1191 }
1192 if( PB("ng") || zSearch!=0 ){
 
1193 tmFlags &= ~TIMELINE_GRAPH;
 
1194 }
1195 if( PB("brbg") ){
1196 tmFlags |= TIMELINE_BRCOLOR;
 
1197 }
1198 if( PB("unhide") ){
1199 tmFlags |= TIMELINE_UNHIDE;
 
1200 }
1201 if( PB("ubg") ){
1202 tmFlags |= TIMELINE_UCOLOR;
 
1203 }
1204 if( zUses!=0 ){
1205 int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
1206 if( ufid ){
1207 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
 
1208 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
1209 compute_uses_file("usesfile", ufid, 0);
1210 zType = "ci";
1211 }else{
1212 zUses = 0;
@@ -1161,22 +1225,20 @@
1225 timeline_temp_table();
1226 blob_zero(&sql);
1227 blob_zero(&desc);
1228 blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
1229 blob_append(&sql, timeline_query_for_www(), -1);
1230 if( PB("fc") || PB("v") || PB("detail") ){
1231 tmFlags |= TIMELINE_FCHANGES;
 
1232 }
1233 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1234 blob_append_sql(&sql,
1235 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1236 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
1237 TAG_HIDDEN
1238 );
1239 }
 
1240 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
1241 /* If from= and to= are present, display all nodes on a path connecting
1242 ** the two */
1243 PathNode *p = 0;
1244 const char *zFrom = 0;
@@ -1241,34 +1303,19 @@
1303 }
1304 if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
1305 }
1306 blob_appendf(&desc, " of %z[%S]</a>",
1307 href("%R/info/%s", zUuid), zUuid);
 
 
 
1308 if( d_rid ){
1309 if( p_rid ){
1310 /* If both p= and d= are set, we don't have the uuid of d yet. */
1311 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
1312 }
1313 }
1314 style_submenu_binary("v","With Files","Without Files");
1315 style_submenu_entry("n","Lines",1);
1316 timeline_y_submenu();
 
 
 
 
 
 
 
 
 
 
 
 
1317 }else if( f_rid && g.perm.Read ){
1318 /* If f= is present, ignore all other parameters other than n= */
1319 char *zUuid;
1320 db_multi_exec(
1321 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1282,16 +1329,11 @@
1329 if( useDividers ) timeline_add_dividers(0, f_rid);
1330 blob_appendf(&desc, "Parents and children of check-in ");
1331 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1332 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
1333 tmFlags |= TIMELINE_DISJOINT;
1334 style_submenu_binary("v","With Files","Without Files");
 
 
 
 
 
1335 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1336 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1337 }
1338 }else{
1339 /* Otherwise, a timeline based on a span of time */
@@ -1316,11 +1358,10 @@
1358 blob_append_sql(&sql,
1359 "AND (EXISTS(SELECT 1 FROM tagxref"
1360 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1361
1362 if( zBrName ){
 
1363 /* The next two blob_appendf() calls add SQL that causes checkins that
1364 ** are not part of the branch which are parents or children of the
1365 ** branch to be included in the report. This related check-ins are
1366 ** useful in helping to visualize what has happened on a quiescent
1367 ** branch that is infrequently merged with a much more activate branch.
@@ -1348,15 +1389,11 @@
1389 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1390 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1391 TAG_HIDDEN
1392 );
1393 }
 
 
1394 }
 
 
1395 }
1396 blob_append_sql(&sql, ")");
1397 }
1398 if( (zType[0]=='w' && !g.perm.RdWiki)
1399 || (zType[0]=='t' && !g.perm.RdTkt)
@@ -1384,11 +1421,10 @@
1421 }
1422 blob_append_sql(&sql, ")");
1423 }
1424 }else{ /* zType!="all" */
1425 blob_append_sql(&sql, " AND event.type=%Q", zType);
 
1426 if( zType[0]=='c' ){
1427 zEType = "checkin";
1428 }else if( zType[0]=='w' ){
1429 zEType = "wiki edit";
1430 }else if( zType[0]=='t' ){
@@ -1406,41 +1442,35 @@
1442 zCirca = zBefore = zAfter = 0;
1443 nEntry = -1;
1444 }
1445 blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)",
1446 zUser, zUser);
 
1447 zThisUser = zUser;
1448 }
1449 if( zSearch ){
1450 blob_append_sql(&sql,
1451 " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
1452 zSearch, zSearch);
 
1453 }
1454 rBefore = symbolic_name_to_mtime(zBefore);
1455 rAfter = symbolic_name_to_mtime(zAfter);
1456 rCirca = symbolic_name_to_mtime(zCirca);
1457 if( rAfter>0.0 ){
1458 if( rBefore>0.0 ){
1459 blob_append_sql(&sql,
1460 " AND event.mtime>=%.17g AND event.mtime<=%.17g"
1461 " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
 
 
1462 nEntry = -1;
1463 }else{
1464 blob_append_sql(&sql,
1465 " AND event.mtime>=%.17g ORDER BY event.mtime ASC",
1466 rAfter-ONE_SECOND);
 
1467 }
1468 }else if( rBefore>0.0 ){
1469 blob_append_sql(&sql,
1470 " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
1471 rBefore+ONE_SECOND);
 
1472 }else if( rCirca>0.0 ){
1473 Blob sql2;
1474 blob_init(&sql2, blob_sql_text(&sql), -1);
1475 blob_append_sql(&sql2,
1476 " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1452,11 +1482,10 @@
1482 " AND event.mtime>=%f ORDER BY event.mtime ASC",
1483 rCirca
1484 );
1485 nEntry -= (nEntry+1)/2;
1486 if( useDividers ) timeline_add_dividers(rCirca, 0);
 
1487 }else{
1488 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
1489 }
1490 if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
1491 db_multi_exec("%s", blob_sql_text(&sql));
@@ -1509,64 +1538,44 @@
1538 }
1539 if( g.perm.Hyperlink ){
1540 if( zAfter || n==nEntry ){
1541 zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
1542 timeline_submenu(&url, "Older", "b", zDate, "a");
1543 zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "a", 0));
1544 free(zDate);
1545 }
1546 if( zBefore || (zAfter && n==nEntry) ){
1547 zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
1548 timeline_submenu(&url, "Newer", "a", zDate, "b");
1549 free(zDate);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1550 }
1551 if( zType[0]=='a' || zType[0]=='c' ){
 
 
 
 
 
1552 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1553 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1554 }
1555 }
1556 style_submenu_binary("v","With Files","Without Files");
1557 if( zUses==0 ) timeline_y_submenu();
1558 style_submenu_entry("n","Lines",1);
1559 }
1560 }
1561 if( P("showsql") ){
1562 @ <blockquote>%h(blob_sql_text(&sql))</blockquote>
1563 }
1564 if( search_restrict(SRCH_CKIN)!=0 ){
1565 style_submenu_element("Search", 0, "%R/search?y=c");
1566 }
1567 if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
1568 blob_zero(&sql);
1569 db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
1570 @ <h2>%b(&desc)</h2>
1571 blob_reset(&desc);
1572 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0);
1573 db_finalize(&q);
1574 if( zOlderButton ){
1575 @ %z(xhref("class='button'","%z",zOlderButton))Older</a>
1576 }
1577 style_footer();
1578 }
1579
1580 /*
1581 ** The input query q selects various records. Print a human-readable
@@ -2041,10 +2050,10 @@
2050 " AND blob.rid=c.cid"
2051 );
2052 while( db_step(&q)==SQLITE_ROW ){
2053 const char *zUuid = db_column_text(&q, 0);
2054 @ <li>
2055 @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2056 }
2057 db_finalize(&q);
2058 style_footer();
2059 }
2060
+1 -1
--- src/tkt.c
+++ src/tkt.c
@@ -1434,8 +1434,8 @@
14341434
*/
14351435
void tkt_srchpage(void){
14361436
login_check_credentials();
14371437
style_header("Ticket Search");
14381438
ticket_standard_submenu(T_ALL_BUT(T_SRCH));
1439
- search_screen(SRCH_TKT, "tktsrch");
1439
+ search_screen(SRCH_TKT, 0);
14401440
style_footer();
14411441
}
14421442
--- src/tkt.c
+++ src/tkt.c
@@ -1434,8 +1434,8 @@
1434 */
1435 void tkt_srchpage(void){
1436 login_check_credentials();
1437 style_header("Ticket Search");
1438 ticket_standard_submenu(T_ALL_BUT(T_SRCH));
1439 search_screen(SRCH_TKT, "tktsrch");
1440 style_footer();
1441 }
1442
--- src/tkt.c
+++ src/tkt.c
@@ -1434,8 +1434,8 @@
1434 */
1435 void tkt_srchpage(void){
1436 login_check_credentials();
1437 style_header("Ticket Search");
1438 ticket_standard_submenu(T_ALL_BUT(T_SRCH));
1439 search_screen(SRCH_TKT, 0);
1440 style_footer();
1441 }
1442
+25 -8
--- src/url.c
+++ src/url.c
@@ -432,47 +432,64 @@
432432
** An instance of this object is used to build a URL with query parameters.
433433
*/
434434
struct HQuery {
435435
Blob url; /* The URL */
436436
const char *zBase; /* The base URL */
437
- int nParam; /* Number of parameters. Max 10 */
438
- const char *azName[15]; /* Parameter names */
439
- const char *azValue[15]; /* Parameter values */
437
+ int nParam; /* Number of parameters. */
438
+ int nAlloc; /* Number of allocated slots */
439
+ const char **azName; /* Parameter names */
440
+ const char **azValue; /* Parameter values */
440441
};
441442
#endif
442443
443444
/*
444445
** Initialize the URL object.
445446
*/
446447
void url_initialize(HQuery *p, const char *zBase){
448
+ memset(p, 0, sizeof(*p));
447449
blob_zero(&p->url);
448450
p->zBase = zBase;
449
- p->nParam = 0;
450451
}
451452
452453
/*
453454
** Resets the given URL object, deallocating any memory
454455
** it uses.
455456
*/
456457
void url_reset(HQuery *p){
457458
blob_reset(&p->url);
459
+ fossil_free(p->azName);
460
+ fossil_free(p->azValue);
458461
url_initialize(p, p->zBase);
459462
}
460463
461464
/*
462465
** Add a fixed parameter to an HQuery.
463466
*/
464467
void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
465
- assert( p->nParam < count(p->azName) );
466
- assert( p->nParam < count(p->azValue) );
467
- p->azName[p->nParam] = zName;
468
- p->azValue[p->nParam] = zValue;
468
+ int i;
469
+ for(i=0; i<p->nParam; i++){
470
+ if( fossil_strcmp(p->azName[i],zName)==0 ){
471
+ p->azValue[i] = zValue;
472
+ return;
473
+ }
474
+ }
475
+ assert( i==p->nParam );
476
+ if( i>=p->nAlloc ){
477
+ p->nAlloc = p->nAlloc*2 + 10;
478
+ p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc);
479
+ p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc);
480
+ }
481
+ p->azName[i] = zName;
482
+ p->azValue[i] = zValue;
469483
p->nParam++;
470484
}
471485
472486
/*
473487
** Render the URL with a parameter override.
488
+**
489
+** Returned memory is transient and is overwritten on the next call to this
490
+** routine for the same HQuery, or until the HQuery object is destroyed.
474491
*/
475492
char *url_render(
476493
HQuery *p, /* Base URL */
477494
const char *zName1, /* First override */
478495
const char *zValue1, /* First override value */
479496
--- src/url.c
+++ src/url.c
@@ -432,47 +432,64 @@
432 ** An instance of this object is used to build a URL with query parameters.
433 */
434 struct HQuery {
435 Blob url; /* The URL */
436 const char *zBase; /* The base URL */
437 int nParam; /* Number of parameters. Max 10 */
438 const char *azName[15]; /* Parameter names */
439 const char *azValue[15]; /* Parameter values */
 
440 };
441 #endif
442
443 /*
444 ** Initialize the URL object.
445 */
446 void url_initialize(HQuery *p, const char *zBase){
 
447 blob_zero(&p->url);
448 p->zBase = zBase;
449 p->nParam = 0;
450 }
451
452 /*
453 ** Resets the given URL object, deallocating any memory
454 ** it uses.
455 */
456 void url_reset(HQuery *p){
457 blob_reset(&p->url);
 
 
458 url_initialize(p, p->zBase);
459 }
460
461 /*
462 ** Add a fixed parameter to an HQuery.
463 */
464 void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
465 assert( p->nParam < count(p->azName) );
466 assert( p->nParam < count(p->azValue) );
467 p->azName[p->nParam] = zName;
468 p->azValue[p->nParam] = zValue;
 
 
 
 
 
 
 
 
 
 
 
469 p->nParam++;
470 }
471
472 /*
473 ** Render the URL with a parameter override.
 
 
 
474 */
475 char *url_render(
476 HQuery *p, /* Base URL */
477 const char *zName1, /* First override */
478 const char *zValue1, /* First override value */
479
--- src/url.c
+++ src/url.c
@@ -432,47 +432,64 @@
432 ** An instance of this object is used to build a URL with query parameters.
433 */
434 struct HQuery {
435 Blob url; /* The URL */
436 const char *zBase; /* The base URL */
437 int nParam; /* Number of parameters. */
438 int nAlloc; /* Number of allocated slots */
439 const char **azName; /* Parameter names */
440 const char **azValue; /* Parameter values */
441 };
442 #endif
443
444 /*
445 ** Initialize the URL object.
446 */
447 void url_initialize(HQuery *p, const char *zBase){
448 memset(p, 0, sizeof(*p));
449 blob_zero(&p->url);
450 p->zBase = zBase;
 
451 }
452
453 /*
454 ** Resets the given URL object, deallocating any memory
455 ** it uses.
456 */
457 void url_reset(HQuery *p){
458 blob_reset(&p->url);
459 fossil_free(p->azName);
460 fossil_free(p->azValue);
461 url_initialize(p, p->zBase);
462 }
463
464 /*
465 ** Add a fixed parameter to an HQuery.
466 */
467 void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
468 int i;
469 for(i=0; i<p->nParam; i++){
470 if( fossil_strcmp(p->azName[i],zName)==0 ){
471 p->azValue[i] = zValue;
472 return;
473 }
474 }
475 assert( i==p->nParam );
476 if( i>=p->nAlloc ){
477 p->nAlloc = p->nAlloc*2 + 10;
478 p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc);
479 p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc);
480 }
481 p->azName[i] = zName;
482 p->azValue[i] = zValue;
483 p->nParam++;
484 }
485
486 /*
487 ** Render the URL with a parameter override.
488 **
489 ** Returned memory is transient and is overwritten on the next call to this
490 ** routine for the same HQuery, or until the HQuery object is destroyed.
491 */
492 char *url_render(
493 HQuery *p, /* Base URL */
494 const char *zName1, /* First override */
495 const char *zValue1, /* First override value */
496
+1 -1
--- src/wiki.c
+++ src/wiki.c
@@ -291,11 +291,11 @@
291291
*/
292292
void wiki_srchpage(void){
293293
login_check_credentials();
294294
style_header("Wiki Search");
295295
wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
296
- search_screen(SRCH_WIKI, "wikisrch");
296
+ search_screen(SRCH_WIKI, 0);
297297
style_footer();
298298
}
299299
300300
/*
301301
** WEBPAGE: wiki
302302
--- src/wiki.c
+++ src/wiki.c
@@ -291,11 +291,11 @@
291 */
292 void wiki_srchpage(void){
293 login_check_credentials();
294 style_header("Wiki Search");
295 wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
296 search_screen(SRCH_WIKI, "wikisrch");
297 style_footer();
298 }
299
300 /*
301 ** WEBPAGE: wiki
302
--- src/wiki.c
+++ src/wiki.c
@@ -291,11 +291,11 @@
291 */
292 void wiki_srchpage(void){
293 login_check_credentials();
294 style_header("Wiki Search");
295 wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
296 search_screen(SRCH_WIKI, 0);
297 style_footer();
298 }
299
300 /*
301 ** WEBPAGE: wiki
302

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button