Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -52,10 +52,11 @@ */ #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) +#define PB(x) cgi_parameter_boolean(x) /* ** Destinations for output text. */ @@ -436,11 +437,12 @@ static int seqQP = 0; /* Sequence numbers */ static struct QParam { /* One entry for each query parameter or cookie */ const char *zName; /* Parameter or cookie name */ const char *zValue; /* Value of the query parameter or cookie */ int seq; /* Order of insertion */ - int isQP; /* True for query parameters */ + char isQP; /* True for query parameters */ + char cTag; /* Tag on query parameters */ } *aParamQP; /* An array of all parameters and cookies */ /* ** Add another query parameter or cookie to the parameter set. ** zName is the name of the query parameter or cookie and zValue @@ -490,10 +492,21 @@ aParamQP[i].zValue = zValue; return; } } cgi_set_parameter_nocopy(zName, zValue, 0); +} +void cgi_replace_query_parameter(const char *zName, const char *zValue){ + int i; + for(i=0; i0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0; return zOut; } + +/* +** Return true if the CGI parameter zName exists and is not equal to 0, +** or "no" or "off". +*/ +int cgi_parameter_boolean(const char *zName){ + const char *zIn = cgi_parameter(zName, 0); + if( zIn==0 ) return 0; + return zIn[0]==0 || is_truth(zIn); +} /* ** Return the name of the i-th CGI parameter. Return NULL if there ** are fewer than i registered CGI parameters. */ @@ -1142,23 +1165,51 @@ cgi_printf("%h = %h
\n", zName, aParamQP[i].zValue); } } /* -** Export all query parameters (but not cookies or environment variables) -** as hidden values of a form. +** Export all untagged query parameters (but not cookies or environment +** variables) as hidden values of a form. */ void cgi_query_parameters_to_hidden(void){ int i; const char *zN, *zV; for(i=0; i } } + +/* +** Export all untagged query parameters (but not cookies or environment +** variables) to the HQuery object. +*/ +void cgi_query_parameters_to_url(HQuery *p){ + int i; + for(i=0; irEventDate); - style_submenu_element("Context", "Context", "%s/timeline?c=%T", - g.zTop, zETime); + style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zEventId); if( g.perm.Hyperlink ){ if( verboseFlag ){ - style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", - g.zTop, zEventId, zUuid); + style_submenu_element("Plain", 0, "%R/event?name=%.20s&aid=%s", + zEventId, zUuid); if( nextRid ){ char *zNext; zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); - style_submenu_element("Next", "Next", - "%s/event?name=%s&aid=%s&v", - g.zTop, zEventId, zNext); + style_submenu_element("Next", 0,"%R/event?name=%.20s&aid=%s&v", + zEventId, zNext); free(zNext); } if( prevRid ){ char *zPrev; zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); - style_submenu_element("Prev", "Prev", - "%s/event?name=%s&aid=%s&v", - g.zTop, zEventId, zPrev); + style_submenu_element("Prev", 0, "%R/event?name=%s&aid=%s&v", + zEventId, zPrev); free(zPrev); } }else{ - style_submenu_element("Detail", "Detail", - "%s/event?name=%s&aid=%s&v", - g.zTop, zEventId, zUuid); + style_submenu_element("Detail", 0, "%R/event?name=%.20s&aid=%s&v", + zEventId, zUuid); } } if( verboseFlag && g.perm.Hyperlink ){ int i; Index: src/search.c ================================================================== --- src/search.c +++ src/search.c @@ -567,26 +567,33 @@ /* ** Remove bits from srchFlags which are disallowed by either the ** current server configuration or by user permissions. */ unsigned int search_restrict(unsigned int srchFlags){ + static unsigned int knownGood = 0; + static unsigned int knownBad = 0; + static const struct { unsigned m; const char *zKey; } aSetng[] = { + { SRCH_CKIN, "search-ci" }, + { SRCH_DOC, "search-doc" }, + { SRCH_TKT, "search-tkt" }, + { SRCH_WIKI, "search-wiki" }, + }; + int i; if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); - if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){ - srchFlags &= ~SRCH_CKIN; - } - if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){ - srchFlags &= ~SRCH_DOC; - } - if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){ - srchFlags &= ~SRCH_TKT; - } - if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){ - srchFlags &= ~SRCH_WIKI; - } - return srchFlags; + for(i=0; i + @
if( zClass ){ @
}else{ @
} @ + if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){ + static const struct { char *z; char *zNm; unsigned m; } aY[] = { + { "all", "All", SRCH_ALL }, + { "c", "Check-ins", SRCH_CKIN }, + { "d", "Docs", SRCH_DOC }, + { "t", "Tickets", SRCH_TKT }, + { "w", "Wiki", SRCH_WIKI }, + }; + const char *zY = PD("y","all"); + unsigned newFlags = srchFlags; + int i; + @ + srchFlags = newFlags; + } @ if( srchFlags==0 ){ @

Search is disabled

} @
@@ -937,22 +973,13 @@ ** ** Search for check-in comments, documents, tickets, or wiki that ** match a user-supplied pattern. */ void search_page(void){ - unsigned srchFlags = SRCH_ALL; - const char *zOnly = P("only"); - login_check_credentials(); - if( zOnly ){ - if( strchr(zOnly,'c') ) srchFlags &= SRCH_CKIN; - if( strchr(zOnly,'d') ) srchFlags &= SRCH_DOC; - if( strchr(zOnly,'t') ) srchFlags &= SRCH_TKT; - if( strchr(zOnly,'w') ) srchFlags &= SRCH_WIKI; - } style_header("Search"); - search_screen(srchFlags, "search"); + search_screen(SRCH_ALL, 1); style_footer(); } /* @@ -1295,11 +1322,11 @@ ); db_multi_exec( "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL," " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime))," - " printf('/timeline?y=ci&n=9&c=%%.20s',blob.uuid)," + " printf('/timeline?y=ci&c=%%.20s',blob.uuid)," " event.mtime" " FROM ftsdocs, event, blob" " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed" " AND event.objid=ftsdocs.rid" " AND blob.rid=ftsdocs.rid" Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -1263,10 +1263,27 @@ @

login_insert_csrf_secret(); @ To leave this login group press @ @

+ @

Implementation Details

+ @

The following are fields from the CONFIG table related to login-groups, + @ provided here for instructional and debugging purposes:

+ @ + @ + db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config" + " WHERE name GLOB 'peer-*'" + " OR name GLOB 'project-*'" + " ORDER BY name"); + while( db_step(&q)==SQLITE_ROW ){ + @ + @ + @ + } + db_finalize(&q); + @
Config.NameConfig.ValueConfig.mtime
%h(db_column_text(&q,0))%h(db_column_text(&q,1))%h(db_column_text(&q,2))
+ output_table_sorting_javascript("configTab","ttt",1); } style_footer(); } /* Index: src/sitemap.c ================================================================== --- src/sitemap.c +++ src/sitemap.c @@ -44,11 +44,11 @@ @
  • %z(href("%R/fileage?name=trunk"))File ages for Trunk
  • @ @
  • %z(href("%R/timeline?n=200"))Project Timeline
  • @
      @
    • %z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins
    • - @
    • %z(href("%R/timeline?n=0&namechng"))All checkins with file name + @
    • %z(href("%R/timeline?n=all&namechng"))All checkins with file name @ changes
    • @
    • %z(href("%R/reports"))Activity Reports
    • @
    @
  • Branches and Tags @
      Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -23,21 +23,42 @@ #include "style.h" /* ** Elements of the submenu are collected into the following -** structure and displayed below the main menu by style_header(). +** structure and displayed below the main menu. +** +** Populate these structure with calls to +** +** style_submenu_element() +** style_submenu_entry() +** style_submenu_checkbox() +** style_submenu_multichoice() ** -** Populate this structure with calls to style_submenu_element() -** prior to calling style_header(). +** prior to calling style_footer(). The style_footer() routine +** will generate the appropriate HTML text just below the main +** menu. */ static struct Submenu { - const char *zLabel; + const char *zLabel; /* Button label */ const char *zTitle; - const char *zLink; + const char *zLink; /* Jump to this link when button is pressed */ } aSubmenu[30]; -static int nSubmenu = 0; +static int nSubmenu = 0; /* Number of buttons */ +static struct SubmenuCtrl { + const char *zName; /* Form query parameter */ + const char *zLabel; /* Label. Might be NULL for FF_MULTI */ + int eType; /* FF_ENTRY, FF_CKBOX, FF_MULTI */ + int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */ + const char **azChoice; /* value/display pairs for FF_MULTI */ + const char *zFalse; /* FF_BINARY label when false */ +} aSubmenuCtrl[20]; +static int nSubmenuCtrl = 0; +#define FF_ENTRY 1 +#define FF_CKBOX 2 +#define FF_MULTI 3 +#define FF_BINARY 4 /* ** Remember that the header has been generated. The footer is omitted ** if an error occurs before the header. */ @@ -216,16 +237,63 @@ ... ){ va_list ap; assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) ); aSubmenu[nSubmenu].zLabel = zLabel; - aSubmenu[nSubmenu].zTitle = zTitle; + aSubmenu[nSubmenu].zTitle = zTitle ? zTitle : zLabel; va_start(ap, zLink); aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap); va_end(ap); nSubmenu++; } +void style_submenu_entry( + const char *zName, /* Query parameter name */ + const char *zLabel, /* Label before the entry box */ + int iSize /* Size of the entry box */ +){ + assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); + aSubmenuCtrl[nSubmenuCtrl].zName = zName; + aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; + aSubmenuCtrl[nSubmenuCtrl].iSize = iSize; + aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY; + nSubmenuCtrl++; +} +void style_submenu_checkbox( + const char *zName, /* Query parameter name */ + const char *zLabel /* Label before the checkbox */ +){ + assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); + aSubmenuCtrl[nSubmenuCtrl].zName = zName; + aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; + aSubmenuCtrl[nSubmenuCtrl].eType = FF_CKBOX; + nSubmenuCtrl++; +} +void style_submenu_binary( + const char *zName, /* Query parameter name */ + const char *zTrue, /* Label to show when parameter is true */ + const char *zFalse /* Label to show when the parameter is false */ +){ + assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); + aSubmenuCtrl[nSubmenuCtrl].zName = zName; + aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue; + aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse; + aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY; + nSubmenuCtrl++; +} +void style_submenu_multichoice( + const char *zName, /* Query parameter name */ + int nChoice, /* Number of options */ + const char **azChoice /* value/display pairs. 2*nChoice entries */ +){ + assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); + aSubmenuCtrl[nSubmenuCtrl].zName = zName; + aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice; + aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice; + aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI; + nSubmenuCtrl++; +} + /* ** Compare two submenu items for sorting purposes */ static int submenuCompare(const void *a, const void *b){ @@ -411,23 +479,102 @@ /* Go back and put the submenu at the top of the page. We delay the ** creation of the submenu until the end so that we can add elements ** to the submenu while generating page text. */ cgi_destination(CGI_HEADER); - if( nSubmenu>0 ){ + if( nSubmenu+nSubmenuCtrl>0 ){ int i; + if( nSubmenuCtrl ){ + cgi_printf("
      ", g.zPath); + } @ + if( nSubmenuCtrl ){ + cgi_query_parameters_to_hidden(); + cgi_tag_query_parameter(0); + @
      + } } zAd = style_adunit_text(&mAdFlags); if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){ @
      @@ -1246,22 +1393,21 @@ } for(i=0; i #endif @ g.zBaseURL = %h(g.zBaseURL)
      @ g.zHttpsURL = %h(g.zHttpsURL)
      @ g.zTop = %h(g.zTop)
      + @ g.zPath = %h(g.zPath)
      for(i=0, c='a'; c<='z'; c++){ if( login_has_capability(&c, 1) ) zCap[i++] = c; } zCap[i] = 0; @ g.userUid = %d(g.userUid)
      Index: src/th_main.c ================================================================== --- src/th_main.c +++ src/th_main.c @@ -365,10 +365,69 @@ Th_Trace("[hascap %#h] => %d
      \n", argl[1], argv[1], rc); } Th_SetResultInt(interp, rc); return TH_OK; } + +/* +** TH1 command: searchable STRING... +** +** Return true if searching in any of the document classes identified +** by STRING is enabled for the repository and user has the necessary +** capabilities to perform the search. +** +** Document classes: +** +** c Check-in comments +** d Embedded documentation +** t Tickets +** w Wiki +** +** To be clear, only one of the document classes identified by each STRING +** needs to be searchable in order for that argument to be true. But +** all arguments must be true for this routine to return true. Hence, to +** see if ALL document classes are searchable: +** +** if {[searchable c d t w]} {...} +** +** But to see if ANY document class is searchable: +** +** if {[searchable cdtw]} {...} +** +** This command is useful for enabling or disabling a "Search" entry +** on the menu bar. +*/ +static int searchableCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + int rc = 1, i, j; + unsigned int searchCap = search_restrict(SRCH_ALL); + if( argc<2 ){ + return Th_WrongNumArgs(interp, "hascap STRING ..."); + } + for(i=1; i %d
      \n", argl[1], argv[1], rc); + } + Th_SetResultInt(interp, rc); + return TH_OK; +} /* ** TH1 command: hasfeature STRING ** ** Return true if the fossil binary has the given compile-time feature @@ -1419,10 +1478,11 @@ {"randhex", randhexCmd, 0}, {"regexp", regexpCmd, 0}, {"reinitialize", reinitializeCmd, 0}, {"render", renderCmd, 0}, {"repository", repositoryCmd, 0}, + {"searchable", searchableCmd, 0}, {"setParameter", setParameterCmd, 0}, {"setting", settingCmd, 0}, {"styleHeader", styleHeaderCmd, 0}, {"styleFooter", styleFooterCmd, 0}, {"tclReady", tclReadyCmd, 0}, Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -494,10 +494,11 @@ " (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm" " FROM mlink" " WHERE mid=:mid AND (pid!=fid OR pfnid>0)" " AND (fid>0 OR" " fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))" + " AND NOT mlink.isaux" " ORDER BY 3 /*sort*/" ); fchngQueryInit = 1; } db_bind_int(&fchngQuery, ":mid", rid); @@ -666,16 +667,16 @@ if( cSep=='[' ) cgi_printf("["); cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n"); } cgi_printf("var nrail = %d\n", pGraph->mxRail+1); graph_free(pGraph); - @ var canvasDiv = gebi("canvas"); - @ var canvasStyle = window.getComputedStyle && window.getComputedStyle(canvasDiv,null); - @ var lineColor = (canvasStyle && canvasStyle.getPropertyValue('color')) || 'black'; - @ var bgColor = (canvasStyle && canvasStyle.getPropertyValue('background-color')) || 'white'; - @ if( bgColor=='transparent' ) bgColor = 'white'; - @ var boxColor = lineColor; + @ var cDiv = gebi("canvas"); + @ var csty = window.getComputedStyle && window.getComputedStyle(cDiv,null); + @ var lineClr = (csty && csty.getPropertyValue('color')) || 'black'; + @ var bgClr = (csty && csty.getPropertyValue('background-color')) ||'white'; + @ if( bgClr=='transparent' ) bgClr = 'white'; + @ var boxColor = lineClr; @ function drawBox(color,x0,y0,x1,y1){ @ var n = document.createElement("div"); @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; } @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; } @ var w = x1-x0+1; @@ -685,11 +686,11 @@ @ n.style.left = x0+"px"; @ n.style.top = y0+"px"; @ n.style.width = w+"px"; @ n.style.height = h+"px"; @ n.style.backgroundColor = color; - @ canvasDiv.appendChild(n); + @ cDiv.appendChild(n); @ return n; @ } @ function absoluteY(id){ @ var obj = gebi(id); @ if( !obj ) return; @@ -711,39 +712,39 @@ @ }while( obj = obj.offsetParent ); @ } @ return left; @ } @ function drawUpArrow(x,y0,y1){ - @ drawBox(lineColor,x,y0,x+1,y1); + @ drawBox(lineClr,x,y0,x+1,y1); @ if( y0+10>=y1 ){ - @ drawBox(lineColor,x-1,y0+1,x+2,y0+2); - @ drawBox(lineColor,x-2,y0+3,x+3,y0+4); + @ drawBox(lineClr,x-1,y0+1,x+2,y0+2); + @ drawBox(lineClr,x-2,y0+3,x+3,y0+4); @ }else{ - @ drawBox(lineColor,x-1,y0+2,x+2,y0+4); - @ drawBox(lineColor,x-2,y0+5,x+3,y0+7); + @ drawBox(lineClr,x-1,y0+2,x+2,y0+4); + @ drawBox(lineClr,x-2,y0+5,x+3,y0+7); @ } @ } @ function drawThinArrow(y,xFrom,xTo){ @ if( xFrom0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5); @ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2); if( !omitDescenders ){ @ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5); @ if( p.d ) drawUpArrow(p.x, p.y+6, btm); @@ -765,11 +766,11 @@ @ for(var i=0; ip.x ? p.x+7 : p.x-6; @ var u = rowinfo[p.au[i+1]-1]; @ if(u.id0.0 ) return mtime; } - rid = symbolic_name_to_rid(z, "ci"); - if( rid==0 ) return -1.0; - mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); + rid = symbolic_name_to_rid(z, "*"); + if( rid ){ + mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); + }else{ + mtime = db_double(-1.0, + "SELECT max(event.mtime) FROM event, tag, tagxref" + " WHERE tag.tagname GLOB 'event-%q*'" + " AND tagxref.tagid=tag.tagid AND tagxref.tagtype" + " AND event.objid=tagxref.rid", + z + ); + } return mtime; } /* ** The value of one second in julianday notation @@ -1011,10 +1021,47 @@ } db_finalize(&q); return blob_str(&out); } + +/* +** Add the select/option box to the timeline submenu that is used to +** set the y= parameter that determines which elements to display +** on the timeline. +*/ +static void timeline_y_submenu(void){ + static int i = 0; + static const char *az[12]; + if( i==0 ){ + az[0] = "all"; + az[1] = "All Types"; + i = 2; + if( g.perm.Read ){ + az[i++] = "ci"; + az[i++] = "Check-ins"; + az[i++] = "g"; + az[i++] = "Tags"; + } + if( g.perm.RdWiki ){ + az[i++] = "e"; + az[i++] = "Tech Notes"; + } + if( g.perm.RdTkt ){ + az[i++] = "t"; + az[i++] = "Tickets"; + } + if( g.perm.RdWiki ){ + az[i++] = "w"; + az[i++] = "Wiki"; + } + assert( i<=ArraySize(az) ); + } + if( i>2 ){ + style_submenu_multichoice("y", i/2, az); + } +} /* ** WEBPAGE: timeline ** ** Query parameters: @@ -1055,11 +1102,11 @@ */ void page_timeline(void){ Stmt q; /* Query used to generate the timeline */ Blob sql; /* text of SQL used to generate timeline */ Blob desc; /* Description of the timeline */ - int nEntry = atoi(PD("n","20")); /* Max number of entries on timeline */ + int nEntry; /* Max number of entries on timeline */ int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */ int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */ int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */ const char *zUser = P("u"); /* All entries by this user if not NULL */ const char *zType = PD("y","all"); /* Type of events. All if NULL */ @@ -1069,11 +1116,11 @@ const char *zTagName = P("t"); /* Show events with this tag */ const char *zBrName = P("r"); /* Show events related to this tag */ const char *zSearch = P("s"); /* Search string */ const char *zUses = P("uf"); /* Only show checkins hold this file */ const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */ - const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (week-of-year)*/ + const char *zYearWeek = P("yw"); /* Checkins for YYYY-WW (week-of-year) */ int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */ int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */ int tagid; /* Tag ID */ int tmFlags = 0; /* Timeline flags */ const char *zThisTag = 0; /* Suppress links to this tag */ @@ -1084,10 +1131,32 @@ int noMerge = P("shortest")==0; /* Follow merge links if shorter */ int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */ int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ int pd_rid; double rBefore, rAfter, rCirca; /* Boundary times */ + const char *z; + char *zOlderButton = 0; /* URL for Older button at the bottom */ + + /* Set number of rows to display */ + z = P("n"); + if( z ){ + if( fossil_strcmp(z,"all")==0 ){ + nEntry = 0; + }else{ + nEntry = atoi(z); + if( nEntry<=0 ){ + cgi_replace_query_parameter("n","10"); + nEntry = 10; + } + } + }else if( zCirca ){ + cgi_replace_query_parameter("n","11"); + nEntry = 11; + }else{ + cgi_replace_query_parameter("n","50"); + nEntry = 50; + } /* To view the timeline, must have permission to read project data. */ pd_rid = name_to_typed_rid(P("dp"),"ci"); if( pd_rid ){ @@ -1097,10 +1166,11 @@ if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ login_needed(); return; } url_initialize(&url, "timeline"); + cgi_query_parameters_to_url(&url); if( zTagName && g.perm.Read ){ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName); zThisTag = zTagName; }else if( zBrName && g.perm.Read ){ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName); @@ -1117,32 +1187,26 @@ if( zType[0]=='a' ){ tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH; }else{ tmFlags |= TIMELINE_GRAPH; } - if( nEntry>0 ) url_add_parameter(&url, "n", mprintf("%d", nEntry)); - if( P("ng")!=0 || zSearch!=0 ){ + if( PB("ng") || zSearch!=0 ){ tmFlags &= ~TIMELINE_GRAPH; - url_add_parameter(&url, "ng", 0); } - if( P("brbg")!=0 ){ + if( PB("brbg") ){ tmFlags |= TIMELINE_BRCOLOR; - url_add_parameter(&url, "brbg", 0); } - if( P("unhide")!=0 ){ + if( PB("unhide") ){ tmFlags |= TIMELINE_UNHIDE; - url_add_parameter(&url, "unhide", 0); } - if( P("ubg")!=0 ){ + if( PB("ubg") ){ tmFlags |= TIMELINE_UCOLOR; - url_add_parameter(&url, "ubg", 0); } if( zUses!=0 ){ int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); if( ufid ){ zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); - url_add_parameter(&url, "uf", zUses); db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); compute_uses_file("usesfile", ufid, 0); zType = "ci"; }else{ zUses = 0; @@ -1161,22 +1225,20 @@ timeline_temp_table(); blob_zero(&sql); blob_zero(&desc); blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1); blob_append(&sql, timeline_query_for_www(), -1); - if( P("fc")!=0 || P("v")!=0 || P("detail")!=0 ){ + if( PB("fc") || PB("v") || PB("detail") ){ tmFlags |= TIMELINE_FCHANGES; - url_add_parameter(&url, "v", 0); } if( (tmFlags & TIMELINE_UNHIDE)==0 ){ blob_append_sql(&sql, " AND NOT EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", TAG_HIDDEN ); } - if( !useDividers ) url_add_parameter(&url, "nd", 0); if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){ /* If from= and to= are present, display all nodes on a path connecting ** the two */ PathNode *p = 0; const char *zFrom = 0; @@ -1241,34 +1303,19 @@ } if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid); } blob_appendf(&desc, " of %z[%S]", href("%R/info/%s", zUuid), zUuid); - if( p_rid ){ - url_add_parameter(&url, "p", zUuid); - } if( d_rid ){ if( p_rid ){ /* If both p= and d= are set, we don't have the uuid of d yet. */ zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid); } - url_add_parameter(&url, "d", zUuid); - } - if( nEntry>20 ){ - timeline_submenu(&url, "20 Entries", "n", "20", 0); - } - if( nEntry<200 && nEntry>0 ){ - timeline_submenu(&url, "200 Entries", "n", "200", 0); - } - if( tmFlags & TIMELINE_FCHANGES ){ - timeline_submenu(&url, "Hide Files", "v", 0, 0); - }else{ - timeline_submenu(&url, "Show Files", "v", "", 0); - } - if( (tmFlags & TIMELINE_UNHIDE)==0 ){ - timeline_submenu(&url, "Unhide", "unhide", "", 0); - } + } + style_submenu_binary("v","With Files","Without Files"); + style_submenu_entry("n","Lines",1); + timeline_y_submenu(); }else if( f_rid && g.perm.Read ){ /* If f= is present, ignore all other parameters other than n= */ char *zUuid; db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" @@ -1282,16 +1329,11 @@ if( useDividers ) timeline_add_dividers(0, f_rid); blob_appendf(&desc, "Parents and children of check-in "); zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); blob_appendf(&desc, "%z[%S]", href("%R/info/%s", zUuid), zUuid); tmFlags |= TIMELINE_DISJOINT; - url_add_parameter(&url, "f", zUuid); - if( tmFlags & TIMELINE_FCHANGES ){ - timeline_submenu(&url, "Hide Files", "v", 0, 0); - }else{ - timeline_submenu(&url, "Show Files", "v", "", 0); - } + style_submenu_binary("v","With Files","Without Files"); if( (tmFlags & TIMELINE_UNHIDE)==0 ){ timeline_submenu(&url, "Unhide", "unhide", "", 0); } }else{ /* Otherwise, a timeline based on a span of time */ @@ -1316,11 +1358,10 @@ blob_append_sql(&sql, "AND (EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid); if( zBrName ){ - url_add_parameter(&url, "r", zBrName); /* The next two blob_appendf() calls add SQL that causes checkins that ** are not part of the branch which are parents or children of the ** branch to be included in the report. This related check-ins are ** useful in helping to visualize what has happened on a quiescent ** branch that is infrequently merged with a much more activate branch. @@ -1348,15 +1389,11 @@ " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", TAG_HIDDEN ); } - }else{ - url_add_parameter(&url, "mionly", "1"); } - }else{ - url_add_parameter(&url, "t", zTagName); } blob_append_sql(&sql, ")"); } if( (zType[0]=='w' && !g.perm.RdWiki) || (zType[0]=='t' && !g.perm.RdTkt) @@ -1384,11 +1421,10 @@ } blob_append_sql(&sql, ")"); } }else{ /* zType!="all" */ blob_append_sql(&sql, " AND event.type=%Q", zType); - url_add_parameter(&url, "y", zType); if( zType[0]=='c' ){ zEType = "checkin"; }else if( zType[0]=='w' ){ zEType = "wiki edit"; }else if( zType[0]=='t' ){ @@ -1406,41 +1442,35 @@ zCirca = zBefore = zAfter = 0; nEntry = -1; } blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)", zUser, zUser); - url_add_parameter(&url, "u", zUser); zThisUser = zUser; } if( zSearch ){ blob_append_sql(&sql, " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')", zSearch, zSearch); - url_add_parameter(&url, "s", zSearch); } rBefore = symbolic_name_to_mtime(zBefore); rAfter = symbolic_name_to_mtime(zAfter); rCirca = symbolic_name_to_mtime(zCirca); if( rAfter>0.0 ){ if( rBefore>0.0 ){ blob_append_sql(&sql, " AND event.mtime>=%.17g AND event.mtime<=%.17g" " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND); - url_add_parameter(&url, "a", zAfter); - url_add_parameter(&url, "b", zBefore); nEntry = -1; }else{ blob_append_sql(&sql, " AND event.mtime>=%.17g ORDER BY event.mtime ASC", rAfter-ONE_SECOND); - url_add_parameter(&url, "a", zAfter); } }else if( rBefore>0.0 ){ blob_append_sql(&sql, " AND event.mtime<=%.17g ORDER BY event.mtime DESC", rBefore+ONE_SECOND); - url_add_parameter(&url, "b", zBefore); }else if( rCirca>0.0 ){ Blob sql2; blob_init(&sql2, blob_sql_text(&sql), -1); blob_append_sql(&sql2, " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d", @@ -1452,11 +1482,10 @@ " AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca ); nEntry -= (nEntry+1)/2; if( useDividers ) timeline_add_dividers(rCirca, 0); - url_add_parameter(&url, "c", zCirca); }else{ blob_append_sql(&sql, " ORDER BY event.mtime DESC"); } if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry); db_multi_exec("%s", blob_sql_text(&sql)); @@ -1509,64 +1538,44 @@ } if( g.perm.Hyperlink ){ if( zAfter || n==nEntry ){ zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/"); timeline_submenu(&url, "Older", "b", zDate, "a"); + zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "a", 0)); free(zDate); } if( zBefore || (zAfter && n==nEntry) ){ zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/"); timeline_submenu(&url, "Newer", "a", zDate, "b"); free(zDate); - }else if( tagid==0 && zUses==0 ){ - if( zType[0]!='a' ){ - timeline_submenu(&url, "All Types", "y", "all", 0); - } - if( zType[0]!='w' && g.perm.RdWiki ){ - timeline_submenu(&url, "Wiki Only", "y", "w", 0); - } - if( zType[0]!='c' && g.perm.Read ){ - timeline_submenu(&url, "Checkins Only", "y", "ci", 0); - } - if( zType[0]!='t' && g.perm.RdTkt ){ - timeline_submenu(&url, "Tickets Only", "y", "t", 0); - } - if( zType[0]!='e' && g.perm.RdWiki ){ - timeline_submenu(&url, "Events Only", "y", "e", 0); - } - if( zType[0]!='g' && g.perm.Read ){ - timeline_submenu(&url, "Tags Only", "y", "g", 0); - } - } - if( nEntry>20 ){ - timeline_submenu(&url, "20 Entries", "n", "20", 0); - } - if( nEntry<200 && nEntry>0 ){ - timeline_submenu(&url, "200 Entries", "n", "200", 0); } if( zType[0]=='a' || zType[0]=='c' ){ - if( tmFlags & TIMELINE_FCHANGES ){ - timeline_submenu(&url, "Hide Files", "v", 0, 0); - }else{ - timeline_submenu(&url, "Show Files", "v", "", 0); - } if( (tmFlags & TIMELINE_UNHIDE)==0 ){ timeline_submenu(&url, "Unhide", "unhide", "", 0); } } + style_submenu_binary("v","With Files","Without Files"); + if( zUses==0 ) timeline_y_submenu(); + style_submenu_entry("n","Lines",1); } } if( P("showsql") ){ @
      %h(blob_sql_text(&sql))
      } + if( search_restrict(SRCH_CKIN)!=0 ){ + style_submenu_element("Search", 0, "%R/search?y=c"); + } if( P("showid") ) tmFlags |= TIMELINE_SHOWRID; blob_zero(&sql); db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); @

      %b(&desc)

      blob_reset(&desc); www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0); db_finalize(&q); + if( zOlderButton ){ + @ %z(xhref("class='button'","%z",zOlderButton))Older + } style_footer(); } /* ** The input query q selects various records. Print a human-readable @@ -2041,10 +2050,10 @@ " AND blob.rid=c.cid" ); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); @
    • - @ %S(zUuid) + @ %S(zUuid) } db_finalize(&q); style_footer(); } Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -1434,8 +1434,8 @@ */ void tkt_srchpage(void){ login_check_credentials(); style_header("Ticket Search"); ticket_standard_submenu(T_ALL_BUT(T_SRCH)); - search_screen(SRCH_TKT, "tktsrch"); + search_screen(SRCH_TKT, 0); style_footer(); } Index: src/url.c ================================================================== --- src/url.c +++ src/url.c @@ -432,47 +432,64 @@ ** An instance of this object is used to build a URL with query parameters. */ struct HQuery { Blob url; /* The URL */ const char *zBase; /* The base URL */ - int nParam; /* Number of parameters. Max 10 */ - const char *azName[15]; /* Parameter names */ - const char *azValue[15]; /* Parameter values */ + int nParam; /* Number of parameters. */ + int nAlloc; /* Number of allocated slots */ + const char **azName; /* Parameter names */ + const char **azValue; /* Parameter values */ }; #endif /* ** Initialize the URL object. */ void url_initialize(HQuery *p, const char *zBase){ + memset(p, 0, sizeof(*p)); blob_zero(&p->url); p->zBase = zBase; - p->nParam = 0; } /* ** Resets the given URL object, deallocating any memory ** it uses. */ void url_reset(HQuery *p){ blob_reset(&p->url); + fossil_free(p->azName); + fossil_free(p->azValue); url_initialize(p, p->zBase); } /* ** Add a fixed parameter to an HQuery. */ void url_add_parameter(HQuery *p, const char *zName, const char *zValue){ - assert( p->nParam < count(p->azName) ); - assert( p->nParam < count(p->azValue) ); - p->azName[p->nParam] = zName; - p->azValue[p->nParam] = zValue; + int i; + for(i=0; inParam; i++){ + if( fossil_strcmp(p->azName[i],zName)==0 ){ + p->azValue[i] = zValue; + return; + } + } + assert( i==p->nParam ); + if( i>=p->nAlloc ){ + p->nAlloc = p->nAlloc*2 + 10; + p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc); + p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc); + } + p->azName[i] = zName; + p->azValue[i] = zValue; p->nParam++; } /* ** Render the URL with a parameter override. +** +** Returned memory is transient and is overwritten on the next call to this +** routine for the same HQuery, or until the HQuery object is destroyed. */ char *url_render( HQuery *p, /* Base URL */ const char *zName1, /* First override */ const char *zValue1, /* First override value */ Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -291,11 +291,11 @@ */ void wiki_srchpage(void){ login_check_credentials(); style_header("Wiki Search"); wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX); - search_screen(SRCH_WIKI, "wikisrch"); + search_screen(SRCH_WIKI, 0); style_footer(); } /* ** WEBPAGE: wiki