Fossil SCM

Enhance markdown to use the same hyperlink target resolving logic as Fossil wiki. That means that wiki page names and check-in and ticket hashes can now be used as markdown hyperlink URLs. Also enhance markdown hyperlinks so that if the display text is an empty string, the URL is used as the display text.

drh 2019-08-09 18:14 trunk
Commit 774fb7712fb43fe3a59b168454f1816208dc1e0e40cbc2affcfdf9e26634bc71
+18 -5
--- src/markdown.md
+++ src/markdown.md
@@ -26,22 +26,35 @@
2626
> 3. **\[display text\]\(URL 'Title'\)**
2727
> 4. **\<URL\>**
2828
> 5. **\[display text\]\[label\]**
2929
> 6. **\[display text\]\[\]**
3030
> 7. **\[display text\]**
31
+> 8. **\[\]\(URL\)**
3132
32
-> **URL** may optionally be written **\<URL\>**. With link formats 5, 6, and 7
33
-> ("reference links"), the URL is supplied elsewhere in the document, as shown
34
-> below. Link formats 6 and 7 reuse the display text as the label. Labels are
35
-> case-insensitive. The title may be split onto the next line with optional
36
-> indenting.
33
+> With link formats 5, 6, and 7 ("reference links"), the URL is supplied
34
+> elsewhere in the document, as shown below. Link formats 6 and 7 reuse
35
+> the display text as the label. Labels are case-insensitive. The title
36
+> may be split onto the next line with optional indenting.
3737
3838
> * **\[label\]:&nbsp;URL**
3939
> * **\[label\]:&nbsp;URL&nbsp;"Title"**
4040
> * **\[label\]:&nbsp;URL&nbsp;'Title'**
4141
> * **\[label\]:&nbsp;URL&nbsp;(Title)**
4242
43
+> **URL** may optionally be written **\<URL\>**.
44
+> In addition to ordinary URLs, the **URL** may be:
45
+> <ul>
46
+> <li> A pathname starting with "/" in which case the Fossil server
47
+> URL prefix is prepended
48
+> <li> A wiki page name, or a wiki page name preceded by "wiki:"
49
+> <li> An artifact or ticket hash or hash prefix
50
+> <li> A date and time stamp: "YYYY-MM-DD HH:MM:SS" or a subset that
51
+> includes at least the day of the month.</ul>
52
+
53
+> In form 8, then the URL becomes the display text. This is useful for
54
+> hyperlinks that refer to wiki pages and check-in and ticket hashes.
55
+
4356
## Fonts ##
4457
4558
> * _\*italic\*_
4659
> * *\_italic\_*
4760
> * __\*\*bold\*\*__
4861
--- src/markdown.md
+++ src/markdown.md
@@ -26,22 +26,35 @@
26 > 3. **\[display text\]\(URL 'Title'\)**
27 > 4. **\<URL\>**
28 > 5. **\[display text\]\[label\]**
29 > 6. **\[display text\]\[\]**
30 > 7. **\[display text\]**
 
31
32 > **URL** may optionally be written **\<URL\>**. With link formats 5, 6, and 7
33 > ("reference links"), the URL is supplied elsewhere in the document, as shown
34 > below. Link formats 6 and 7 reuse the display text as the label. Labels are
35 > case-insensitive. The title may be split onto the next line with optional
36 > indenting.
37
38 > * **\[label\]:&nbsp;URL**
39 > * **\[label\]:&nbsp;URL&nbsp;"Title"**
40 > * **\[label\]:&nbsp;URL&nbsp;'Title'**
41 > * **\[label\]:&nbsp;URL&nbsp;(Title)**
42
 
 
 
 
 
 
 
 
 
 
 
 
 
43 ## Fonts ##
44
45 > * _\*italic\*_
46 > * *\_italic\_*
47 > * __\*\*bold\*\*__
48
--- src/markdown.md
+++ src/markdown.md
@@ -26,22 +26,35 @@
26 > 3. **\[display text\]\(URL 'Title'\)**
27 > 4. **\<URL\>**
28 > 5. **\[display text\]\[label\]**
29 > 6. **\[display text\]\[\]**
30 > 7. **\[display text\]**
31 > 8. **\[\]\(URL\)**
32
33 > With link formats 5, 6, and 7 ("reference links"), the URL is supplied
34 > elsewhere in the document, as shown below. Link formats 6 and 7 reuse
35 > the display text as the label. Labels are case-insensitive. The title
36 > may be split onto the next line with optional indenting.
 
37
38 > * **\[label\]:&nbsp;URL**
39 > * **\[label\]:&nbsp;URL&nbsp;"Title"**
40 > * **\[label\]:&nbsp;URL&nbsp;'Title'**
41 > * **\[label\]:&nbsp;URL&nbsp;(Title)**
42
43 > **URL** may optionally be written **\<URL\>**.
44 > In addition to ordinary URLs, the **URL** may be:
45 > <ul>
46 > <li> A pathname starting with "/" in which case the Fossil server
47 > URL prefix is prepended
48 > <li> A wiki page name, or a wiki page name preceded by "wiki:"
49 > <li> An artifact or ticket hash or hash prefix
50 > <li> A date and time stamp: "YYYY-MM-DD HH:MM:SS" or a subset that
51 > includes at least the day of the month.</ul>
52
53 > In form 8, then the URL becomes the display text. This is useful for
54 > hyperlinks that refer to wiki pages and check-in and ticket hashes.
55
56 ## Fonts ##
57
58 > * _\*italic\*_
59 > * *\_italic\_*
60 > * __\*\*bold\*\*__
61
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -427,24 +427,20 @@
427427
struct Blob *title,
428428
struct Blob *content,
429429
void *opaque
430430
){
431431
char *zLink = blob_buffer(link);
432
- BLOB_APPEND_LITERAL(ob, "<a href=\"");
433
- if( zLink && zLink[0]=='/' && g.zTop ){
434
- /* For any hyperlink that begins with "/", make it refer to the root
435
- ** of the Fossil repository */
436
- blob_append(ob, g.zTop, -1);
437
- }
438
- html_quote(ob, blob_buffer(link), blob_size(link));
439
- if( title && blob_size(title)>0 ){
440
- BLOB_APPEND_LITERAL(ob, "\" title=\"");
441
- html_quote(ob, blob_buffer(title), blob_size(title));
442
- }
443
- BLOB_APPEND_LITERAL(ob, "\">");
444
- BLOB_APPEND_BLOB(ob, content);
445
- BLOB_APPEND_LITERAL(ob, "</a>");
432
+ char *zTitle = title!=0 && blob_size(title)>0 ? blob_str(title) : 0;
433
+ char zClose[20];
434
+
435
+ wiki_resolve_hyperlink(ob, 0, zLink, zClose, sizeof(zClose), 0, zTitle);
436
+ if( blob_size(content)==0 ){
437
+ BLOB_APPEND_BLOB(ob, link);
438
+ }else{
439
+ BLOB_APPEND_BLOB(ob, content);
440
+ }
441
+ blob_append(ob, zClose, -1);
446442
return 1;
447443
}
448444
449445
static int html_triple_emphasis(
450446
struct Blob *ob,
451447
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -427,24 +427,20 @@
427 struct Blob *title,
428 struct Blob *content,
429 void *opaque
430 ){
431 char *zLink = blob_buffer(link);
432 BLOB_APPEND_LITERAL(ob, "<a href=\"");
433 if( zLink && zLink[0]=='/' && g.zTop ){
434 /* For any hyperlink that begins with "/", make it refer to the root
435 ** of the Fossil repository */
436 blob_append(ob, g.zTop, -1);
437 }
438 html_quote(ob, blob_buffer(link), blob_size(link));
439 if( title && blob_size(title)>0 ){
440 BLOB_APPEND_LITERAL(ob, "\" title=\"");
441 html_quote(ob, blob_buffer(title), blob_size(title));
442 }
443 BLOB_APPEND_LITERAL(ob, "\">");
444 BLOB_APPEND_BLOB(ob, content);
445 BLOB_APPEND_LITERAL(ob, "</a>");
446 return 1;
447 }
448
449 static int html_triple_emphasis(
450 struct Blob *ob,
451
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -427,24 +427,20 @@
427 struct Blob *title,
428 struct Blob *content,
429 void *opaque
430 ){
431 char *zLink = blob_buffer(link);
432 char *zTitle = title!=0 && blob_size(title)>0 ? blob_str(title) : 0;
433 char zClose[20];
434
435 wiki_resolve_hyperlink(ob, 0, zLink, zClose, sizeof(zClose), 0, zTitle);
436 if( blob_size(content)==0 ){
437 BLOB_APPEND_BLOB(ob, link);
438 }else{
439 BLOB_APPEND_BLOB(ob, content);
440 }
441 blob_append(ob, zClose, -1);
 
 
 
 
442 return 1;
443 }
444
445 static int html_triple_emphasis(
446 struct Blob *ob,
447
+12 -3
--- src/style.c
+++ src/style.c
@@ -142,17 +142,26 @@
142142
va_list ap;
143143
va_start(ap, zFormat);
144144
zUrl = vmprintf(zFormat, ap);
145145
va_end(ap);
146146
if( g.perm.Hyperlink && !g.javascriptHyperlink ){
147
- char *zHUrl = mprintf("<a %s href=\"%h\">", zExtra, zUrl);
147
+ char *zHUrl;
148
+ if( zExtra ){
149
+ zHUrl = mprintf("<a %s href=\"%h\">", zExtra, zUrl);
150
+ }else{
151
+ zHUrl = mprintf("<a href=\"%h\">", zUrl);
152
+ }
148153
fossil_free(zUrl);
149154
return zHUrl;
150155
}
151156
needHrefJs = 1;
152
- return mprintf("<a %s data-href='%z' href='%R/honeypot'>",
153
- zExtra, zUrl);
157
+ if( zExtra==0 ){
158
+ return mprintf("<a data-href='%z' href='%R/honeypot'>", zUrl);
159
+ }else{
160
+ return mprintf("<a %s data-href='%z' href='%R/honeypot'>",
161
+ zExtra, zUrl);
162
+ }
154163
}
155164
char *chref(const char *zExtra, const char *zFormat, ...){
156165
char *zUrl;
157166
va_list ap;
158167
va_start(ap, zFormat);
159168
--- src/style.c
+++ src/style.c
@@ -142,17 +142,26 @@
142 va_list ap;
143 va_start(ap, zFormat);
144 zUrl = vmprintf(zFormat, ap);
145 va_end(ap);
146 if( g.perm.Hyperlink && !g.javascriptHyperlink ){
147 char *zHUrl = mprintf("<a %s href=\"%h\">", zExtra, zUrl);
 
 
 
 
 
148 fossil_free(zUrl);
149 return zHUrl;
150 }
151 needHrefJs = 1;
152 return mprintf("<a %s data-href='%z' href='%R/honeypot'>",
153 zExtra, zUrl);
 
 
 
 
154 }
155 char *chref(const char *zExtra, const char *zFormat, ...){
156 char *zUrl;
157 va_list ap;
158 va_start(ap, zFormat);
159
--- src/style.c
+++ src/style.c
@@ -142,17 +142,26 @@
142 va_list ap;
143 va_start(ap, zFormat);
144 zUrl = vmprintf(zFormat, ap);
145 va_end(ap);
146 if( g.perm.Hyperlink && !g.javascriptHyperlink ){
147 char *zHUrl;
148 if( zExtra ){
149 zHUrl = mprintf("<a %s href=\"%h\">", zExtra, zUrl);
150 }else{
151 zHUrl = mprintf("<a href=\"%h\">", zUrl);
152 }
153 fossil_free(zUrl);
154 return zHUrl;
155 }
156 needHrefJs = 1;
157 if( zExtra==0 ){
158 return mprintf("<a data-href='%z' href='%R/honeypot'>", zUrl);
159 }else{
160 return mprintf("<a %s data-href='%z' href='%R/honeypot'>",
161 zExtra, zUrl);
162 }
163 }
164 char *chref(const char *zExtra, const char *zFormat, ...){
165 char *zUrl;
166 va_list ap;
167 va_start(ap, zFormat);
168
+36 -26
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1142,18 +1142,18 @@
11421142
/*
11431143
** Return a pointer to the name part of zTarget (skipping the "wiki:" prefix
11441144
** if there is one) if zTarget is a valid wiki page name. Return NULL if
11451145
** zTarget names a page that does not exist.
11461146
*/
1147
-static const char *validWikiPageName(Renderer *p, const char *zTarget){
1147
+static const char *validWikiPageName(int mFlags, const char *zTarget){
11481148
if( strncmp(zTarget, "wiki:", 5)==0
11491149
&& wiki_name_is_wellformed((const unsigned char*)zTarget) ){
11501150
return zTarget+5;
11511151
}
11521152
if( strcmp(zTarget, "Sandbox")==0 ) return zTarget;
11531153
if( wiki_name_is_wellformed((const unsigned char *)zTarget)
1154
- && ((p->state & WIKI_NOBADLINKS)==0 ||
1154
+ && ((mFlags & WIKI_NOBADLINKS)==0 ||
11551155
db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'wiki-%q'"
11561156
" AND (SELECT value FROM tagxref WHERE tagid=tag.tagid"
11571157
" ORDER BY mtime DESC LIMIT 1) > 0", zTarget))
11581158
){
11591159
return zTarget;
@@ -1224,93 +1224,102 @@
12241224
**
12251225
** [#fragment]
12261226
**
12271227
** [2010-02-27 07:13]
12281228
*/
1229
-static void openHyperlink(
1230
- Renderer *p, /* Rendering context */
1229
+void wiki_resolve_hyperlink(
1230
+ Blob *pOut, /* Write the HTML output here */
1231
+ int mFlags, /* Rendering option flags */
12311232
const char *zTarget, /* Hyperlink target; text within [...] */
12321233
char *zClose, /* Write hyperlink closing text here */
12331234
int nClose, /* Bytes available in zClose[] */
1234
- const char *zOrig /* Complete document text */
1235
+ const char *zOrig, /* Complete document text */
1236
+ const char *zTitle /* Title of the link */
12351237
){
12361238
const char *zTerm = "</a>";
12371239
const char *z;
1240
+ char *zExtra = 0;
1241
+ const char *zExtraNS = 0;
12381242
1243
+ if( zTitle ){
1244
+ zExtra = mprintf(" title='%h'", zTitle);
1245
+ zExtraNS = zExtra+1;
1246
+ }
12391247
assert( nClose>=20 );
12401248
if( strncmp(zTarget, "http:", 5)==0
12411249
|| strncmp(zTarget, "https:", 6)==0
12421250
|| strncmp(zTarget, "ftp:", 4)==0
12431251
|| strncmp(zTarget, "mailto:", 7)==0
12441252
){
1245
- blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1253
+ blob_appendf(pOut, "<a href=\"%s\"%s>", zTarget, zExtra);
12461254
}else if( zTarget[0]=='/' ){
1247
- blob_appendf(p->pOut, "<a href=\"%R%h\">", zTarget);
1255
+ blob_appendf(pOut, "<a href=\"%R%h\"%s>", zTarget, zExtra);
12481256
}else if( zTarget[0]=='.'
12491257
&& (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1250
- && (p->state & WIKI_LINKSONLY)==0 ){
1251
- blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1258
+ && (mFlags & WIKI_LINKSONLY)==0 ){
1259
+ blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
12521260
}else if( zTarget[0]=='#' ){
1253
- blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1261
+ blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
12541262
}else if( is_valid_hname(zTarget) ){
12551263
int isClosed = 0;
12561264
if( strlen(zTarget)<=HNAME_MAX && is_ticket(zTarget, &isClosed) ){
12571265
/* Special display processing for tickets. Display the hyperlink
12581266
** as crossed out if the ticket is closed.
12591267
*/
12601268
if( isClosed ){
12611269
if( g.perm.Hyperlink ){
1262
- blob_appendf(p->pOut,
1270
+ blob_appendf(pOut,
12631271
"%z<span class=\"wikiTagCancelled\">[",
1264
- href("%R/info/%s",zTarget)
1272
+ xhref(zExtraNS,"%R/info/%s",zTarget)
12651273
);
12661274
zTerm = "]</span></a>";
12671275
}else{
1268
- blob_appendf(p->pOut,"<span class=\"wikiTagCancelled\">[");
1276
+ blob_appendf(pOut,"<span class=\"wikiTagCancelled\">[");
12691277
zTerm = "]</span>";
12701278
}
12711279
}else{
12721280
if( g.perm.Hyperlink ){
1273
- blob_appendf(p->pOut,"%z[", href("%R/info/%s", zTarget));
1281
+ blob_appendf(pOut,"%z[", xhref(zExtraNS,"%R/info/%s", zTarget));
12741282
zTerm = "]</a>";
12751283
}else{
1276
- blob_appendf(p->pOut, "[");
1284
+ blob_appendf(pOut, "[");
12771285
zTerm = "]";
12781286
}
12791287
}
12801288
}else if( !in_this_repo(zTarget) ){
1281
- if( (p->state & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
1289
+ if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
12821290
zTerm = "";
12831291
}else{
1284
- blob_appendf(p->pOut, "<span class=\"brokenlink\">[");
1292
+ blob_appendf(pOut, "<span class=\"brokenlink\">[");
12851293
zTerm = "]</span>";
12861294
}
12871295
}else if( g.perm.Hyperlink ){
1288
- blob_appendf(p->pOut, "%z[",href("%R/info/%s", zTarget));
1296
+ blob_appendf(pOut, "%z[",xhref(zExtraNS, "%R/info/%s", zTarget));
12891297
zTerm = "]</a>";
12901298
}else{
12911299
zTerm = "";
12921300
}
12931301
}else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
12941302
&& db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
1295
- blob_appendf(p->pOut, "<a href=\"%R/timeline?c=%T\">", zTarget);
1296
- }else if( (z = validWikiPageName(p, zTarget))!=0 ){
1303
+ blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
1304
+ }else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){
12971305
const char *zOverride = wiki_is_overridden(zTarget);
12981306
if( zOverride ){
1299
- blob_appendf(p->pOut, "<a href=\"%R/info/%S\">", zOverride);
1307
+ blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra);
13001308
}else{
1301
- blob_appendf(p->pOut, "<a href=\"%R/wiki?name=%T\">", z);
1309
+ blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
13021310
}
1303
- }else if( zTarget>=&zOrig[2] && !fossil_isspace(zTarget[-2]) ){
1311
+ }else if( zOrig && zTarget>=&zOrig[2] && !fossil_isspace(zTarget[-2]) ){
13041312
/* Probably an array subscript in code */
13051313
zTerm = "";
1306
- }else if( (p->state & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
1314
+ }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
13071315
zTerm = "";
13081316
}else{
1309
- blob_appendf(p->pOut, "<span class=\"brokenlink\">[%h]", zTarget);
1317
+ blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget);
13101318
zTerm = "</span>";
13111319
}
1320
+ if( zExtra ) fossil_free(zExtra);
13121321
assert( strlen(zTerm)<nClose );
13131322
sqlite3_snprintf(nClose, zClose, "%s", zTerm);
13141323
}
13151324
13161325
/*
@@ -1491,11 +1500,12 @@
14911500
if( zDisplay==0 ){
14921501
zDisplay = zTarget;
14931502
}else{
14941503
while( fossil_isspace(*zDisplay) ) zDisplay++;
14951504
}
1496
- openHyperlink(p, zTarget, zClose, sizeof(zClose), zOrig);
1505
+ wiki_resolve_hyperlink(p->pOut, p->state,
1506
+ zTarget, zClose, sizeof(zClose), zOrig, 0);
14971507
if( linksOnly || zClose[0]==0 || p->inVerbatim ){
14981508
if( cS1 ) z[iS1] = cS1;
14991509
if( zClose[0]!=']' ){
15001510
blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
15011511
}else{
15021512
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1142,18 +1142,18 @@
1142 /*
1143 ** Return a pointer to the name part of zTarget (skipping the "wiki:" prefix
1144 ** if there is one) if zTarget is a valid wiki page name. Return NULL if
1145 ** zTarget names a page that does not exist.
1146 */
1147 static const char *validWikiPageName(Renderer *p, const char *zTarget){
1148 if( strncmp(zTarget, "wiki:", 5)==0
1149 && wiki_name_is_wellformed((const unsigned char*)zTarget) ){
1150 return zTarget+5;
1151 }
1152 if( strcmp(zTarget, "Sandbox")==0 ) return zTarget;
1153 if( wiki_name_is_wellformed((const unsigned char *)zTarget)
1154 && ((p->state & WIKI_NOBADLINKS)==0 ||
1155 db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'wiki-%q'"
1156 " AND (SELECT value FROM tagxref WHERE tagid=tag.tagid"
1157 " ORDER BY mtime DESC LIMIT 1) > 0", zTarget))
1158 ){
1159 return zTarget;
@@ -1224,93 +1224,102 @@
1224 **
1225 ** [#fragment]
1226 **
1227 ** [2010-02-27 07:13]
1228 */
1229 static void openHyperlink(
1230 Renderer *p, /* Rendering context */
 
1231 const char *zTarget, /* Hyperlink target; text within [...] */
1232 char *zClose, /* Write hyperlink closing text here */
1233 int nClose, /* Bytes available in zClose[] */
1234 const char *zOrig /* Complete document text */
 
1235 ){
1236 const char *zTerm = "</a>";
1237 const char *z;
 
 
1238
 
 
 
 
1239 assert( nClose>=20 );
1240 if( strncmp(zTarget, "http:", 5)==0
1241 || strncmp(zTarget, "https:", 6)==0
1242 || strncmp(zTarget, "ftp:", 4)==0
1243 || strncmp(zTarget, "mailto:", 7)==0
1244 ){
1245 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1246 }else if( zTarget[0]=='/' ){
1247 blob_appendf(p->pOut, "<a href=\"%R%h\">", zTarget);
1248 }else if( zTarget[0]=='.'
1249 && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1250 && (p->state & WIKI_LINKSONLY)==0 ){
1251 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1252 }else if( zTarget[0]=='#' ){
1253 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1254 }else if( is_valid_hname(zTarget) ){
1255 int isClosed = 0;
1256 if( strlen(zTarget)<=HNAME_MAX && is_ticket(zTarget, &isClosed) ){
1257 /* Special display processing for tickets. Display the hyperlink
1258 ** as crossed out if the ticket is closed.
1259 */
1260 if( isClosed ){
1261 if( g.perm.Hyperlink ){
1262 blob_appendf(p->pOut,
1263 "%z<span class=\"wikiTagCancelled\">[",
1264 href("%R/info/%s",zTarget)
1265 );
1266 zTerm = "]</span></a>";
1267 }else{
1268 blob_appendf(p->pOut,"<span class=\"wikiTagCancelled\">[");
1269 zTerm = "]</span>";
1270 }
1271 }else{
1272 if( g.perm.Hyperlink ){
1273 blob_appendf(p->pOut,"%z[", href("%R/info/%s", zTarget));
1274 zTerm = "]</a>";
1275 }else{
1276 blob_appendf(p->pOut, "[");
1277 zTerm = "]";
1278 }
1279 }
1280 }else if( !in_this_repo(zTarget) ){
1281 if( (p->state & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
1282 zTerm = "";
1283 }else{
1284 blob_appendf(p->pOut, "<span class=\"brokenlink\">[");
1285 zTerm = "]</span>";
1286 }
1287 }else if( g.perm.Hyperlink ){
1288 blob_appendf(p->pOut, "%z[",href("%R/info/%s", zTarget));
1289 zTerm = "]</a>";
1290 }else{
1291 zTerm = "";
1292 }
1293 }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
1294 && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
1295 blob_appendf(p->pOut, "<a href=\"%R/timeline?c=%T\">", zTarget);
1296 }else if( (z = validWikiPageName(p, zTarget))!=0 ){
1297 const char *zOverride = wiki_is_overridden(zTarget);
1298 if( zOverride ){
1299 blob_appendf(p->pOut, "<a href=\"%R/info/%S\">", zOverride);
1300 }else{
1301 blob_appendf(p->pOut, "<a href=\"%R/wiki?name=%T\">", z);
1302 }
1303 }else if( zTarget>=&zOrig[2] && !fossil_isspace(zTarget[-2]) ){
1304 /* Probably an array subscript in code */
1305 zTerm = "";
1306 }else if( (p->state & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
1307 zTerm = "";
1308 }else{
1309 blob_appendf(p->pOut, "<span class=\"brokenlink\">[%h]", zTarget);
1310 zTerm = "</span>";
1311 }
 
1312 assert( strlen(zTerm)<nClose );
1313 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1314 }
1315
1316 /*
@@ -1491,11 +1500,12 @@
1491 if( zDisplay==0 ){
1492 zDisplay = zTarget;
1493 }else{
1494 while( fossil_isspace(*zDisplay) ) zDisplay++;
1495 }
1496 openHyperlink(p, zTarget, zClose, sizeof(zClose), zOrig);
 
1497 if( linksOnly || zClose[0]==0 || p->inVerbatim ){
1498 if( cS1 ) z[iS1] = cS1;
1499 if( zClose[0]!=']' ){
1500 blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
1501 }else{
1502
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1142,18 +1142,18 @@
1142 /*
1143 ** Return a pointer to the name part of zTarget (skipping the "wiki:" prefix
1144 ** if there is one) if zTarget is a valid wiki page name. Return NULL if
1145 ** zTarget names a page that does not exist.
1146 */
1147 static const char *validWikiPageName(int mFlags, const char *zTarget){
1148 if( strncmp(zTarget, "wiki:", 5)==0
1149 && wiki_name_is_wellformed((const unsigned char*)zTarget) ){
1150 return zTarget+5;
1151 }
1152 if( strcmp(zTarget, "Sandbox")==0 ) return zTarget;
1153 if( wiki_name_is_wellformed((const unsigned char *)zTarget)
1154 && ((mFlags & WIKI_NOBADLINKS)==0 ||
1155 db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'wiki-%q'"
1156 " AND (SELECT value FROM tagxref WHERE tagid=tag.tagid"
1157 " ORDER BY mtime DESC LIMIT 1) > 0", zTarget))
1158 ){
1159 return zTarget;
@@ -1224,93 +1224,102 @@
1224 **
1225 ** [#fragment]
1226 **
1227 ** [2010-02-27 07:13]
1228 */
1229 void wiki_resolve_hyperlink(
1230 Blob *pOut, /* Write the HTML output here */
1231 int mFlags, /* Rendering option flags */
1232 const char *zTarget, /* Hyperlink target; text within [...] */
1233 char *zClose, /* Write hyperlink closing text here */
1234 int nClose, /* Bytes available in zClose[] */
1235 const char *zOrig, /* Complete document text */
1236 const char *zTitle /* Title of the link */
1237 ){
1238 const char *zTerm = "</a>";
1239 const char *z;
1240 char *zExtra = 0;
1241 const char *zExtraNS = 0;
1242
1243 if( zTitle ){
1244 zExtra = mprintf(" title='%h'", zTitle);
1245 zExtraNS = zExtra+1;
1246 }
1247 assert( nClose>=20 );
1248 if( strncmp(zTarget, "http:", 5)==0
1249 || strncmp(zTarget, "https:", 6)==0
1250 || strncmp(zTarget, "ftp:", 4)==0
1251 || strncmp(zTarget, "mailto:", 7)==0
1252 ){
1253 blob_appendf(pOut, "<a href=\"%s\"%s>", zTarget, zExtra);
1254 }else if( zTarget[0]=='/' ){
1255 blob_appendf(pOut, "<a href=\"%R%h\"%s>", zTarget, zExtra);
1256 }else if( zTarget[0]=='.'
1257 && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1258 && (mFlags & WIKI_LINKSONLY)==0 ){
1259 blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
1260 }else if( zTarget[0]=='#' ){
1261 blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
1262 }else if( is_valid_hname(zTarget) ){
1263 int isClosed = 0;
1264 if( strlen(zTarget)<=HNAME_MAX && is_ticket(zTarget, &isClosed) ){
1265 /* Special display processing for tickets. Display the hyperlink
1266 ** as crossed out if the ticket is closed.
1267 */
1268 if( isClosed ){
1269 if( g.perm.Hyperlink ){
1270 blob_appendf(pOut,
1271 "%z<span class=\"wikiTagCancelled\">[",
1272 xhref(zExtraNS,"%R/info/%s",zTarget)
1273 );
1274 zTerm = "]</span></a>";
1275 }else{
1276 blob_appendf(pOut,"<span class=\"wikiTagCancelled\">[");
1277 zTerm = "]</span>";
1278 }
1279 }else{
1280 if( g.perm.Hyperlink ){
1281 blob_appendf(pOut,"%z[", xhref(zExtraNS,"%R/info/%s", zTarget));
1282 zTerm = "]</a>";
1283 }else{
1284 blob_appendf(pOut, "[");
1285 zTerm = "]";
1286 }
1287 }
1288 }else if( !in_this_repo(zTarget) ){
1289 if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
1290 zTerm = "";
1291 }else{
1292 blob_appendf(pOut, "<span class=\"brokenlink\">[");
1293 zTerm = "]</span>";
1294 }
1295 }else if( g.perm.Hyperlink ){
1296 blob_appendf(pOut, "%z[",xhref(zExtraNS, "%R/info/%s", zTarget));
1297 zTerm = "]</a>";
1298 }else{
1299 zTerm = "";
1300 }
1301 }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
1302 && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
1303 blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
1304 }else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){
1305 const char *zOverride = wiki_is_overridden(zTarget);
1306 if( zOverride ){
1307 blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra);
1308 }else{
1309 blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
1310 }
1311 }else if( zOrig && zTarget>=&zOrig[2] && !fossil_isspace(zTarget[-2]) ){
1312 /* Probably an array subscript in code */
1313 zTerm = "";
1314 }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
1315 zTerm = "";
1316 }else{
1317 blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget);
1318 zTerm = "</span>";
1319 }
1320 if( zExtra ) fossil_free(zExtra);
1321 assert( strlen(zTerm)<nClose );
1322 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1323 }
1324
1325 /*
@@ -1491,11 +1500,12 @@
1500 if( zDisplay==0 ){
1501 zDisplay = zTarget;
1502 }else{
1503 while( fossil_isspace(*zDisplay) ) zDisplay++;
1504 }
1505 wiki_resolve_hyperlink(p->pOut, p->state,
1506 zTarget, zClose, sizeof(zClose), zOrig, 0);
1507 if( linksOnly || zClose[0]==0 || p->inVerbatim ){
1508 if( cS1 ) z[iS1] = cS1;
1509 if( zClose[0]!=']' ){
1510 blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
1511 }else{
1512

Keyboard Shortcuts

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