Index: src/doc.c ================================================================== --- src/doc.c +++ src/doc.c @@ -510,11 +510,11 @@ ** action="$ROOT/ ** ** Convert $ROOT to the root URI of the repository. Allow ' in place of " ** and any case for href or action. */ -static void convert_href_and_output(Blob *pIn){ +void convert_href_and_output(Blob *pIn){ int i, base; int n = blob_size(pIn); char *z = blob_buffer(pIn); for(base=0, i=7; i0", TAG_USER, rid); zEComment = db_text(0, "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", TAG_COMMENT, rid); + zBrName = db_text(0, + "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", + TAG_BRANCH, rid); zOrigUser = db_column_text(&q1, 2); zUser = zEUser ? zEUser : zOrigUser; zComment = db_column_text(&q1, 3); zDate = db_column_text(&q1,1); zOrigDate = db_column_text(&q1, 4); @@ -754,11 +760,23 @@ " WHERE rid=%d AND tagtype>0 " " AND tag.tagid=tagxref.tagid " " AND +tag.tagname GLOB 'sym-*'", rid); while( db_step(&q2)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q2, 0); - @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName) + if( fossil_strcmp(zTagName,zBrName)==0 ){ + @ | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName) + if( g.perm.Write || wiki_tagid2("branch",zTagName)!=0 ){ + blob_appendf(&wiki_links, " | %z%h", + href("%R/wiki?name=branch/%h",zTagName), zTagName); + } + }else{ + @ | %z(href("%R/timeline?t=%T&unhide",zTagName))%h(zTagName) + if( g.perm.Write || wiki_tagid2("tag",zTagName)!=0 ){ + blob_appendf(&wiki_links, " | %z%h", + href("%R/wiki?name=tag/%h",zTagName), zTagName); + } + } } db_finalize(&q2); @ @ Files: @@ -803,10 +821,28 @@ @ Received From: @ %h(zUser) @ %h(zIpAddr) on %s(zDate) } db_finalize(&q2); } + + /* Only show links to wiki pages if the users can read wiki, + ** and only if the wiki pages already exist or the user has the + ** ability to create new ones. */ + if( g.perm.RdWiki + && (g.perm.Write || blob_size(&wiki_links)>0 + || (okWiki = wiki_tagid2("checkin",zUuid))!=0) + && db_get_boolean("wiki-about",1) + ){ + const char *zLinks = blob_str(&wiki_links); + if( zLinks[0] ) zLinks += 3; + @ Wiki:\ + if( g.perm.Write || okWiki ){ + @ %z(href("%R/wiki?name=checkin/%s",zUuid))this checkin | \ + } + @ %s(zLinks) + } + if( g.perm.Hyperlink ){ @ Other Links: @ @ %z(href("%R/artifact/%!S",zUuid))manifest @ | %z(href("%R/ci_tags/%!S",zUuid))tags @@ -818,15 +854,19 @@ } @ @ } @ + blob_reset(&wiki_links); }else{ style_header("Check-in Information"); login_anonymous_available(); } db_finalize(&q1); + if( !PB("nowiki") ){ + wiki_render_associated("checkin", zUuid, 0); + } render_backlink_graph(zUuid, "
References
\n"); @
Context
render_checkin_context(rid, 0); @
Changes
@
Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -938,13 +938,27 @@ return; } style_header("Wiki Configuration"); db_begin_transaction(); - @
+ @
login_insert_csrf_secret(); @

+ @
+ onoff_attribute("Associate Wiki Pages With Branches, Tags, or Checkins", + "wiki-about", "wiki-about", 1, 0); + @

+ @ Associate wiki pages with branches, tags, or checkins, based on + @ the wiki page name. Wiki pages that begin with "branch/", "checkin/" + @ or "tag/" and which continue with the name of an existing branch, checkin + @ or tag are treated specially when this feature is enabled. + @

    + @
  • branch/branch-name + @
  • checkin/full-checkin-hash + @
  • tag/tag-name + @
+ @ (Property: "wiki-about")

@
onoff_attribute("Enable WYSIWYG Wiki Editing", "wysiwyg-wiki", "wysiwyg-wiki", 0, 0); @

Enable what-you-see-is-what-you-get (WYSIWYG) editing of wiki pages. @ The WYSIWYG editor generates HTML instead of markup, which makes Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -1423,10 +1423,11 @@ ** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN ** t=TAG Show only check-ins with the given TAG ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel ** rel Show related check-ins as well as those matching t=TAG ** mionly Limit rel to show ancestors but not descendants +** nowiki Do not show wiki associated with branch or tag ** ms=MATCHSTYLE Set tag match style to EXACT, GLOB, LIKE, REGEXP ** u=USER Only show items associated with USER ** y=TYPE 'ci', 'w', 't', 'e', 'f', or 'all'. ** ss=VIEWSTYLE c: "Compact" v: "Verbose" m: "Modern" j: "Columnar" ** advm Use the "Advanced" or "Busy" menu design. @@ -1444,20 +1445,20 @@ ** name matches one of the comma-separate GLOBLIST ** brbg Background color from branch name ** ubg Background color from user ** namechng Show only check-ins that have filename changes ** forks Show only forks and their children +** cherrypicks Show all cherrypicks ** ym=YYYY-MM Show only events for the given year/month ** yw=YYYY-WW Show only events for the given week of the given year ** yw=YYYY-MM-DD Show events for the week that includes the given day ** ymd=YYYY-MM-DD Show only events on the given day ** days=N Show events over the previous N days ** datefmt=N Override the date format ** bisect Show the check-ins that are in the current bisect ** showid Show RIDs ** showsql Show the SQL text -** cherrypicks Show all cherrypicks ** ** p= and d= can appear individually or together. If either p= or d= ** appear, then u=, y=, a=, and b= are ignored. ** ** If both a= and b= appear then both upper and lower bounds are honored. @@ -2269,11 +2270,27 @@ blob_zero(&sql); db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); if( fossil_islower(desc.aData[0]) ){ desc.aData[0] = fossil_toupper(desc.aData[0]); } - @

%b(&desc)

+ if( zBrName + && !PB("nowiki") + && wiki_render_associated("branch", zBrName, + WIKIASSOC_FULL_TITLE|WIKIASSOC_MENU) + ){ + @
%b(&desc)
+ }else + if( zTagName + && matchStyle==MS_EXACT + && !PB("nowiki") + && wiki_render_associated("tag", zTagName, + WIKIASSOC_FULL_TITLE|WIKIASSOC_MENU) + ){ + @
%b(&desc)
+ } else{ + @

%b(&desc)

+ } blob_reset(&desc); /* Report any errors. */ if( zError ){ @

%h(zError)

Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -78,10 +78,14 @@ ** Return the tagid associated with a particular wiki page. */ int wiki_tagid(const char *zPageName){ return db_int(0, "SELECT tagid FROM tag WHERE tagname='wiki-%q'",zPageName); } +int wiki_tagid2(const char *zPrefix, const char *zPageName){ + return db_int(0, "SELECT tagid FROM tag WHERE tagname='wiki-%q/%q'", + zPrefix, zPageName); +} /* ** Return the RID of the next or previous version of a wiki page. ** Return 0 if rid is the last/first version. */ @@ -343,10 +347,58 @@ style_header("Wiki Search"); wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX); search_screen(SRCH_WIKI, 0); style_footer(); } + +/* +** Add an appropriate style_header() for either the /wiki or /wikiedit page +** for zPageName. +*/ +static void wiki_page_header(const char *zPageName, const char *zExtra){ + if( db_get_boolean("wiki-about",1)==0 ){ + style_header("%s%s", zExtra, zPageName); + }else + if( sqlite3_strglob("checkin/*", zPageName)==0 + && db_exists("SELECT 1 FROM blob WHERE uuid=%Q",zPageName+8) + ){ + style_header("Notes About Checkin %S", zPageName + 8); + style_submenu_element("Checkin Timeline","%R/timeline?f=%s",zPageName + 8); + style_submenu_element("Checkin Info","%R/info/%s",zPageName + 8); + }else + if( sqlite3_strglob("branch/*", zPageName)==0 ){ + style_header("Notes About Branch %h", zPageName + 7); + style_submenu_element("Branch Timeline","%R/timeline?r=%t",zPageName + 7); + }else + if( sqlite3_strglob("tag/*", zPageName)==0 ){ + style_header("Notes About Tag %h", zPageName + 4); + style_submenu_element("Tag Timeline","%R/timeline?t=%t",zPageName + 4); + } + else{ + style_header("%s%s", zExtra, zPageName); + } +} + +/* +** Wiki pages with special names "branch/...", "checkin/...", and "tag/..." +** requires perm.Write privilege in addition to perm.WrWiki in order +** to write. This function determines whether the extra perm.Write +** is required and available. Return true if writing to the wiki page +** may proceed, and return false if permission is lacking. +*/ +static int wiki_special_permission(const char *zPageName){ + if( strncmp(zPageName,"branch/",7)!=0 + && strncmp(zPageName,"checkin/",8)!=0 + && strncmp(zPageName,"tag/",4)!=0 + ){ + return 1; + } + if( db_get_boolean("wiki-about",1)==0 ){ + return 1; + } + return g.perm.Write; +} /* ** WEBPAGE: wiki ** URL: /wiki?name=PAGENAME */ @@ -396,11 +448,13 @@ zMimetype = pWiki->zMimetype; } } zMimetype = wiki_filter_mimetypes(zMimetype); if( !g.isHome ){ - if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){ + if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki)) + && wiki_special_permission(zPageName) + ){ if( db_get_boolean("wysiwyg-wiki", 0) ){ style_submenu_element("Edit", "%s/wikiedit?name=%T&wysiwyg=1", g.zTop, zPageName); }else{ style_submenu_element("Edit", "%s/wikiedit?name=%T", g.zTop, zPageName); @@ -410,11 +464,11 @@ style_submenu_element("History", "%s/whistory?name=%T", g.zTop, zPageName); } } style_set_current_page("%T?name=%T", g.zPath, zPageName); - style_header("%s", zPageName); + wiki_page_header(zPageName, ""); wiki_standard_submenu(submenuFlags); if( zBody[0]==0 ){ @ This page has been deleted }else{ blob_init(&wiki, zBody, -1); @@ -526,10 +580,14 @@ "SELECT rid FROM tagxref" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" " ORDER BY mtime DESC", zTag ); free(zTag); + if( !wiki_special_permission(zPageName) ){ + login_needed(0); + return; + } if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki); return; } if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ @@ -579,11 +637,11 @@ } if( zBody==0 ){ zBody = mprintf("Empty Page"); } style_set_current_page("%T?name=%T", g.zPath, zPageName); - style_header("Edit: %s", zPageName); + wiki_page_header(zPageName, "Edit: "); if( rid && !isSandbox && g.perm.ApndWiki ){ if( g.perm.Attach ){ style_submenu_element("Attach", "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", g.zTop, zPageName, g.zTop, zPageName); @@ -1067,26 +1125,34 @@ int wrid = db_column_int(&q, 2); double rWmtime = db_column_double(&q, 3); sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0); char *zAge; int wcnt = db_column_int(&q, 4); + char *zWDisplayName; + + if( sqlite3_strglob("checkin/*", zWName)==0 ){ + zWDisplayName = mprintf("%.25s...", zWName); + }else{ + zWDisplayName = mprintf("%s", zWName); + } if( wrid==0 ){ if( !showAll ) continue; @ \ - @ %z(href("%R/whistory?name=%T",zWName))%h(zWName) + @ %z(href("%R/whistory?name=%T",zWName))%h(zWDisplayName) }else{ @ \ - @ %z(href("%R/wiki?name=%T",zWName))%h(zWName) + @ %z(href("%R/wiki?name=%T",zWName))%h(zWDisplayName) } zAge = human_readable_age(rNow - rWmtime); @ %s(zAge) fossil_free(zAge); @ %z(href("%R/whistory?name=%T",zWName))%d(wcnt) if( showRid ){ @ %d(wrid) } @ + fossil_free(zWDisplayName); } @
db_finalize(&q); style_table_sorter(); style_footer(); @@ -1483,5 +1549,114 @@ blob_zero(&out); blob_read_from_file(&in, g.argv[2], ExtFILE); markdown_to_html(&in, 0, &out); blob_write_to_file(&out, "-"); } + +/* +** Allowed flags for wiki_render_associated +*/ +#if INTERFACE +#define WIKIASSOC_FULL_TITLE 0x00001 /* Full title */ +#define WIKIASSOC_MENU 0x00002 /* Add a submenu to the About section */ +#endif + +/* +** Show the default Section label for an associated wiki page. +*/ +static void wiki_section_label( + const char *zPrefix, /* "branch", "tag", or "checkin" */ + const char *zName, /* Name of the object */ + unsigned int mFlags /* Zero or more WIKIASSOC_* flags */ +){ + if( (mFlags & WIKIASSOC_FULL_TITLE)==0 ){ + @
About
+ }else if( zPrefix[0]=='c' ){ /* checkin/... */ + @
About checkin %.20h(zName)
+ }else{ + @
About %s(zPrefix) %h(zName)
+ } +} + +/* +** Add an "Wiki" button in a submenu for a Wiki page. +*/ +static void wiki_section_menu( + const char *zPrefix, /* "branch", "tag", or "checkin" */ + const char *zName, /* Name of the object */ + unsigned int mFlags /* Zero or more WIKIASSOC_* flags */ +){ + if( g.perm.WrWiki && (mFlags & WIKIASSOC_MENU)!=0 ){ + style_submenu_element("Wiki", "%R/wiki?name=%s/%t", zPrefix, zName); + } +} + +/* +** Check to see if there exists a wiki page with a name zPrefix/zName. +** If there is, then render a
..
and +** return true. +** +** If there is no such wiki page, return false. +*/ +int wiki_render_associated( + const char *zPrefix, /* "branch", "tag", or "checkin" */ + const char *zName, /* Name of the object */ + unsigned int mFlags /* Zero or more WIKIASSOC_* flags */ +){ + int rid; + Manifest *pWiki; + if( !db_get_boolean("wiki-about",1) ) return 0; + rid = db_int(0, + "SELECT rid FROM tagxref" + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname='wiki-%q/%q')" + " ORDER BY mtime DESC LIMIT 1", + zPrefix, zName + ); + if( rid==0 ) return 0; + pWiki = manifest_get(rid, CFTYPE_WIKI, 0); + if( pWiki==0 ) return 0; + if( fossil_strcmp(pWiki->zMimetype, "text/x-markdown")==0 ){ + Blob tail = BLOB_INITIALIZER; + Blob title = BLOB_INITIALIZER; + Blob markdown; + blob_init(&markdown, pWiki->zWiki, -1); + markdown_to_html(&markdown, &title, &tail); + if( blob_size(&title) ){ + @
%h(blob_str(&title))
+ }else{ + wiki_section_label(zPrefix, zName, mFlags); + } + wiki_section_menu(zPrefix, zName, mFlags); + convert_href_and_output(&tail); + blob_reset(&tail); + blob_reset(&title); + blob_reset(&markdown); + }else if( fossil_strcmp(pWiki->zMimetype, "text/plain")==0 ){ + wiki_section_label(zPrefix, zName, mFlags); + wiki_section_menu(zPrefix, zName, mFlags); + @
+    @ %h(pWiki->zWiki)
+    @ 
+ }else{ + Blob tail = BLOB_INITIALIZER; + Blob title = BLOB_INITIALIZER; + Blob wiki; + Blob *pBody; + blob_init(&wiki, pWiki->zWiki, -1); + if( wiki_find_title(&wiki, &title, &tail) ){ + @
%h(blob_str(&title))
+ pBody = &tail; + }else{ + wiki_section_label(zPrefix, zName, mFlags); + pBody = &wiki; + } + wiki_section_menu(zPrefix, zName, mFlags); + @
+ wiki_convert(pBody, 0, WIKI_BUTTONS); + @
+ blob_reset(&tail); + blob_reset(&title); + blob_reset(&wiki); + } + manifest_destroy(pWiki); + return 1; +}