Fossil SCM

Improvements to how ticket changes are displayed in the UI. The hyperlink is show with strick-through if the ticket is closed. The title is shown after the ticket hyperlink. SQL to determine the closed condition and the title can be set in the ticket setup screens.

drh 2008-10-18 02:27 trunk
Commit ac3f1f2ba755061a7a205bc8a91c9adc757791ee
--- src/configure.c
+++ src/configure.c
@@ -86,10 +86,12 @@
8686
{ "ticket-newpage", CONFIGSET_TKT },
8787
{ "ticket-viewpage", CONFIGSET_TKT },
8888
{ "ticket-editpage", CONFIGSET_TKT },
8989
{ "ticket-report-template", CONFIGSET_TKT },
9090
{ "ticket-key-template", CONFIGSET_TKT },
91
+ { "ticket-title-expr", CONFIGSET_TKT },
92
+ { "ticket-closed-expr", CONFIGSET_TKT },
9193
{ "@reportfmt", CONFIGSET_TKT },
9294
{ "@user", CONFIGSET_USER },
9395
{ "@shun", CONFIGSET_SHUN },
9496
};
9597
static int iConfig = 0;
9698
--- src/configure.c
+++ src/configure.c
@@ -86,10 +86,12 @@
86 { "ticket-newpage", CONFIGSET_TKT },
87 { "ticket-viewpage", CONFIGSET_TKT },
88 { "ticket-editpage", CONFIGSET_TKT },
89 { "ticket-report-template", CONFIGSET_TKT },
90 { "ticket-key-template", CONFIGSET_TKT },
 
 
91 { "@reportfmt", CONFIGSET_TKT },
92 { "@user", CONFIGSET_USER },
93 { "@shun", CONFIGSET_SHUN },
94 };
95 static int iConfig = 0;
96
--- src/configure.c
+++ src/configure.c
@@ -86,10 +86,12 @@
86 { "ticket-newpage", CONFIGSET_TKT },
87 { "ticket-viewpage", CONFIGSET_TKT },
88 { "ticket-editpage", CONFIGSET_TKT },
89 { "ticket-report-template", CONFIGSET_TKT },
90 { "ticket-key-template", CONFIGSET_TKT },
91 { "ticket-title-expr", CONFIGSET_TKT },
92 { "ticket-closed-expr", CONFIGSET_TKT },
93 { "@reportfmt", CONFIGSET_TKT },
94 { "@user", CONFIGSET_USER },
95 { "@shun", CONFIGSET_SHUN },
96 };
97 static int iConfig = 0;
98
+4 -1
--- src/manifest.c
+++ src/manifest.c
@@ -894,11 +894,10 @@
894894
free(zComment);
895895
}
896896
if( m.type==CFTYPE_TICKET ){
897897
char *zTag;
898898
Blob comment;
899
- int i;
900899
901900
ticket_insert(&m, 1, 1);
902901
zTag = mprintf("tkt-%s", m.zTicketUuid);
903902
tag_insert(zTag, 1, 0, rid, m.rDate, rid);
904903
free(zTag);
@@ -919,10 +918,12 @@
919918
"Changed %h in ticket [%.10s]",
920919
m.aField[0].zName, m.zTicketUuid
921920
);
922921
}
923922
}else{
923
+#if 0
924
+ int i;
924925
const char *z;
925926
const char *zSep = " ";
926927
blob_appendf(&comment, "%d changes to ticket [%.10s]:",
927928
m.nField, m.zTicketUuid);
928929
for(i=0; i<m.nField; i++){
@@ -929,10 +930,12 @@
929930
z = m.aField[i].zName;
930931
if( z[0]=='+' ) z++;
931932
blob_appendf(&comment, "%s%h", zSep, z);
932933
zSep = ", ";
933934
}
935
+#endif
936
+ blob_appendf(&comment, "Edits to ticket [%.10s]", m.zTicketUuid);
934937
}
935938
db_multi_exec(
936939
"REPLACE INTO event(type,mtime,objid,user,comment)"
937940
"VALUES('t',%.17g,%d,%Q,%Q)",
938941
m.rDate, rid, m.zUser, blob_str(&comment)
939942
--- src/manifest.c
+++ src/manifest.c
@@ -894,11 +894,10 @@
894 free(zComment);
895 }
896 if( m.type==CFTYPE_TICKET ){
897 char *zTag;
898 Blob comment;
899 int i;
900
901 ticket_insert(&m, 1, 1);
902 zTag = mprintf("tkt-%s", m.zTicketUuid);
903 tag_insert(zTag, 1, 0, rid, m.rDate, rid);
904 free(zTag);
@@ -919,10 +918,12 @@
919 "Changed %h in ticket [%.10s]",
920 m.aField[0].zName, m.zTicketUuid
921 );
922 }
923 }else{
 
 
924 const char *z;
925 const char *zSep = " ";
926 blob_appendf(&comment, "%d changes to ticket [%.10s]:",
927 m.nField, m.zTicketUuid);
928 for(i=0; i<m.nField; i++){
@@ -929,10 +930,12 @@
929 z = m.aField[i].zName;
930 if( z[0]=='+' ) z++;
931 blob_appendf(&comment, "%s%h", zSep, z);
932 zSep = ", ";
933 }
 
 
934 }
935 db_multi_exec(
936 "REPLACE INTO event(type,mtime,objid,user,comment)"
937 "VALUES('t',%.17g,%d,%Q,%Q)",
938 m.rDate, rid, m.zUser, blob_str(&comment)
939
--- src/manifest.c
+++ src/manifest.c
@@ -894,11 +894,10 @@
894 free(zComment);
895 }
896 if( m.type==CFTYPE_TICKET ){
897 char *zTag;
898 Blob comment;
 
899
900 ticket_insert(&m, 1, 1);
901 zTag = mprintf("tkt-%s", m.zTicketUuid);
902 tag_insert(zTag, 1, 0, rid, m.rDate, rid);
903 free(zTag);
@@ -919,10 +918,12 @@
918 "Changed %h in ticket [%.10s]",
919 m.aField[0].zName, m.zTicketUuid
920 );
921 }
922 }else{
923 #if 0
924 int i;
925 const char *z;
926 const char *zSep = " ";
927 blob_appendf(&comment, "%d changes to ticket [%.10s]:",
928 m.nField, m.zTicketUuid);
929 for(i=0; i<m.nField; i++){
@@ -929,10 +930,12 @@
930 z = m.aField[i].zName;
931 if( z[0]=='+' ) z++;
932 blob_appendf(&comment, "%s%h", zSep, z);
933 zSep = ", ";
934 }
935 #endif
936 blob_appendf(&comment, "Edits to ticket [%.10s]", m.zTicketUuid);
937 }
938 db_multi_exec(
939 "REPLACE INTO event(type,mtime,objid,user,comment)"
940 "VALUES('t',%.17g,%d,%Q,%Q)",
941 m.rDate, rid, m.zUser, blob_str(&comment)
942
+1 -1
--- src/setup.c
+++ src/setup.c
@@ -557,11 +557,11 @@
557557
}
558558
559559
/*
560560
** Generate an entry box for an attribute.
561561
*/
562
-static void entry_attribute(
562
+void entry_attribute(
563563
const char *zLabel, /* The text label on the entry box */
564564
int width, /* Width of the entry box */
565565
const char *zVar, /* The corresponding row in the VAR table */
566566
const char *zQParm, /* The query parameter */
567567
char *zDflt /* Default value if VAR table entry does not exist */
568568
--- src/setup.c
+++ src/setup.c
@@ -557,11 +557,11 @@
557 }
558
559 /*
560 ** Generate an entry box for an attribute.
561 */
562 static void entry_attribute(
563 const char *zLabel, /* The text label on the entry box */
564 int width, /* Width of the entry box */
565 const char *zVar, /* The corresponding row in the VAR table */
566 const char *zQParm, /* The query parameter */
567 char *zDflt /* Default value if VAR table entry does not exist */
568
--- src/setup.c
+++ src/setup.c
@@ -557,11 +557,11 @@
557 }
558
559 /*
560 ** Generate an entry box for an attribute.
561 */
562 void entry_attribute(
563 const char *zLabel, /* The text label on the entry box */
564 int width, /* Width of the entry box */
565 const char *zVar, /* The corresponding row in the VAR table */
566 const char *zQParm, /* The query parameter */
567 char *zDflt /* Default value if VAR table entry does not exist */
568
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -40,10 +40,12 @@
4040
4141
style_header("Ticket Setup");
4242
@ <table border="0" cellspacing="20">
4343
setup_menu_entry("Table", "tktsetup_tab",
4444
"Specify the schema of the \"ticket\" table in the database.");
45
+ setup_menu_entry("Timeline", "tktsetup_timeline",
46
+ "How to display ticket status in the timeline");
4547
setup_menu_entry("Common", "tktsetup_com",
4648
"Common TH1 code run before all ticket processing.");
4749
setup_menu_entry("New Ticket Page", "tktsetup_newpage",
4850
"HTML with embedded TH1 code for the \"new ticket\" webpage.");
4951
setup_menu_entry("View Ticket Page", "tktsetup_viewpage",
@@ -616,5 +618,44 @@
616618
0,
617619
0,
618620
10
619621
);
620622
}
623
+
624
+/*
625
+** WEBPAGE: tktsetup_timeline
626
+*/
627
+void tktsetup_timeline_page(void){
628
+ login_check_credentials();
629
+ if( !g.okSetup ){
630
+ login_needed();
631
+ }
632
+
633
+ if( P("setup") ){
634
+ cgi_redirect("tktsetup");
635
+ }
636
+ style_header("Ticket Display On Timelines");
637
+ db_begin_transaction();
638
+ @ <form action="%s(g.zBaseURL)/tktsetup_timeline" method="POST">
639
+
640
+ @ <hr>
641
+ entry_attribute("Ticket Title", 40, "ticket-title-expr", "t", "title");
642
+ @ <p>An SQL expression in a query against the TICKET table that will
643
+ @ return the title of the ticket for display purposes after hyperlinks to
644
+ @ that ticket</p>
645
+
646
+ @ <hr>
647
+ entry_attribute("Ticket Closed", 40, "ticket-closed-expr", "c",
648
+ "status='Closed'");
649
+ @ <p>An SQL expression that evaluates to true in a TICKET table query if
650
+ @ the ticket is closed.</p>
651
+
652
+ @ <hr>
653
+ @ <p>
654
+ @ <input type="submit" name="submit" value="Apply Changes">
655
+ @ <input type="submit" name="setup" value="Cancel">
656
+ @ </p>
657
+ @ </form>
658
+ db_end_transaction(0);
659
+ style_footer();
660
+
661
+}
621662
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -40,10 +40,12 @@
40
41 style_header("Ticket Setup");
42 @ <table border="0" cellspacing="20">
43 setup_menu_entry("Table", "tktsetup_tab",
44 "Specify the schema of the \"ticket\" table in the database.");
 
 
45 setup_menu_entry("Common", "tktsetup_com",
46 "Common TH1 code run before all ticket processing.");
47 setup_menu_entry("New Ticket Page", "tktsetup_newpage",
48 "HTML with embedded TH1 code for the \"new ticket\" webpage.");
49 setup_menu_entry("View Ticket Page", "tktsetup_viewpage",
@@ -616,5 +618,44 @@
616 0,
617 0,
618 10
619 );
620 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -40,10 +40,12 @@
40
41 style_header("Ticket Setup");
42 @ <table border="0" cellspacing="20">
43 setup_menu_entry("Table", "tktsetup_tab",
44 "Specify the schema of the \"ticket\" table in the database.");
45 setup_menu_entry("Timeline", "tktsetup_timeline",
46 "How to display ticket status in the timeline");
47 setup_menu_entry("Common", "tktsetup_com",
48 "Common TH1 code run before all ticket processing.");
49 setup_menu_entry("New Ticket Page", "tktsetup_newpage",
50 "HTML with embedded TH1 code for the \"new ticket\" webpage.");
51 setup_menu_entry("View Ticket Page", "tktsetup_viewpage",
@@ -616,5 +618,44 @@
618 0,
619 0,
620 10
621 );
622 }
623
624 /*
625 ** WEBPAGE: tktsetup_timeline
626 */
627 void tktsetup_timeline_page(void){
628 login_check_credentials();
629 if( !g.okSetup ){
630 login_needed();
631 }
632
633 if( P("setup") ){
634 cgi_redirect("tktsetup");
635 }
636 style_header("Ticket Display On Timelines");
637 db_begin_transaction();
638 @ <form action="%s(g.zBaseURL)/tktsetup_timeline" method="POST">
639
640 @ <hr>
641 entry_attribute("Ticket Title", 40, "ticket-title-expr", "t", "title");
642 @ <p>An SQL expression in a query against the TICKET table that will
643 @ return the title of the ticket for display purposes after hyperlinks to
644 @ that ticket</p>
645
646 @ <hr>
647 entry_attribute("Ticket Closed", 40, "ticket-closed-expr", "c",
648 "status='Closed'");
649 @ <p>An SQL expression that evaluates to true in a TICKET table query if
650 @ the ticket is closed.</p>
651
652 @ <hr>
653 @ <p>
654 @ <input type="submit" name="submit" value="Apply Changes">
655 @ <input type="submit" name="setup" value="Cancel">
656 @ </p>
657 @ </form>
658 db_end_transaction(0);
659 style_footer();
660
661 }
662
+134 -46
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -867,47 +867,130 @@
867867
if( !validate16(z, n) ) return 0;
868868
return 1;
869869
}
870870
871871
/*
872
-** Return true if the given hyperlink should be implemented for
873
-** the current login.
874
-*/
875
-static int okToHyperlink(const char *zTarget){
876
- if( g.okHistory ) return 1;
877
- if( strncmp(zTarget, "http:", 5)==0
878
- || strncmp(zTarget, "https:", 6)==0
879
- || strncmp(zTarget, "ftp:", 4)==0
880
- || strncmp(zTarget, "mailto:", 7)==0
881
- ){
882
- return 1;
883
- }
884
- if( zTarget[0]=='/' || is_valid_uuid(zTarget) ) return 0;
885
- if( wiki_name_is_wellformed(zTarget) ) return 1;
886
- return 0;
887
-}
888
-
889
-/*
890
-** Resolve a hyperlink. The argument is the content of the [...]
891
-** in the wiki. Append the URL to the output of the Renderer.
892
-*/
893
-static void resolveHyperlink(const char *zTarget, Renderer *p){
894
- if( strncmp(zTarget, "http:", 5)==0
895
- || strncmp(zTarget, "https:", 6)==0
896
- || strncmp(zTarget, "ftp:", 4)==0
897
- || strncmp(zTarget, "mailto:", 7)==0
898
- ){
899
- blob_appendf(p->pOut, zTarget);
900
- }else if( zTarget[0]=='/' ){
901
- blob_appendf(p->pOut, "%s%h", g.zBaseURL, zTarget);
902
- }else if( is_valid_uuid(zTarget) ){
903
- blob_appendf(p->pOut, "%s/info/%s", g.zBaseURL, zTarget);
904
- }else if( wiki_name_is_wellformed(zTarget) ){
905
- blob_appendf(p->pOut, "%s/wiki?name=%T", g.zBaseURL, zTarget);
906
- }else{
907
- blob_appendf(p->pOut, "error");
908
- }
872
+** zTarget is guaranteed to be a UUID. It might be the UUID of a ticket.
873
+** If it is, fill zDisplay[0..nDisplay-1] with the title of the ticket
874
+** (or a prefix if the title is too long) and return true. If zTarget
875
+** is not the UUID of a ticket, return false.
876
+*/
877
+static int is_ticket(
878
+ const char *zTarget, /* Ticket UUID */
879
+ char *zDisplay, /* Space in which to write ticket title */
880
+ int nDisplay, /* Bytes available in zDisplay[] */
881
+ int *pClosed /* True if the ticket is closed */
882
+){
883
+ static Stmt q;
884
+ static int once = 1;
885
+ int n;
886
+ int rc;
887
+ char zLower[UUID_SIZE+1];
888
+ char zUpper[UUID_SIZE+1];
889
+ n = strlen(zTarget);
890
+ memcpy(zLower, zTarget, n+1);
891
+ canonical16(zLower, n+1);
892
+ memcpy(zUpper, zLower, n+1);
893
+ zUpper[n-1]++;
894
+ if( once ){
895
+ const char *zTitleExpr = db_get("ticket-title-expr", "title");
896
+ const char *zClosedExpr = db_get("ticket-closed-expr", "status='Closed'");
897
+ db_static_prepare(&q,
898
+ "SELECT %s, %s FROM ticket "
899
+ " WHERE tkt_uuid>=:lwr AND tkt_uuid<:upr",
900
+ zTitleExpr, zClosedExpr
901
+ );
902
+ }
903
+ db_bind_text(&q, ":lwr", zLower);
904
+ db_bind_text(&q, ":upr", zUpper);
905
+ if( db_step(&q)==SQLITE_ROW ){
906
+ n = db_column_bytes(&q,0);
907
+ if( n>nDisplay-1 ) n = nDisplay - 1;
908
+ memcpy(zDisplay, db_column_text(&q, 0), n);
909
+ zDisplay[n] = 0;
910
+ rc = 1;
911
+ *pClosed = db_column_int(&q, 1);
912
+ }else{
913
+ rc = 0;
914
+ }
915
+ db_reset(&q);
916
+ return rc;
917
+}
918
+
919
+/*
920
+** Resolve a hyperlink. The zTarget argument is the content of the [...]
921
+** in the wiki. Append an <a> markup to the output of the Renderer.
922
+**
923
+** Actually, this routine might or might not append the hyperlink, depending
924
+** on current rendering rules: specifically does the current user have
925
+** "History" permission. If this routine does append the <a> and thus needs
926
+** a </a> to follow, it returns true. If the <a> is suppressed, then return
927
+** false.
928
+**
929
+** If nDisplay>0 then optionally write up to nDisplay bytes of
930
+** alternative display text into zDisplay. The text must be zero
931
+** terminated. The final zero is included in the nDisplay byte count
932
+** limit.
933
+*/
934
+static int resolveHyperlink(
935
+ Renderer *p, /* Rendering context */
936
+ const char *zTarget, /* Hyperlink traget; text within [...] */
937
+ char *zDisplay, /* Space in which to write alternative display */
938
+ int nDisplay /* Bytes available in zDisplay[] */
939
+){
940
+ int rc = 0;
941
+ if( strncmp(zTarget, "http:", 5)==0
942
+ || strncmp(zTarget, "https:", 6)==0
943
+ || strncmp(zTarget, "ftp:", 4)==0
944
+ || strncmp(zTarget, "mailto:", 7)==0
945
+ ){
946
+ blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
947
+ rc = 1;
948
+ }else if( zTarget[0]=='/' ){
949
+ if( g.okHistory ){
950
+ blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zBaseURL, zTarget);
951
+ rc = 1;
952
+ }
953
+ }else if( is_valid_uuid(zTarget) ){
954
+ int isClosed;
955
+ if( nDisplay && is_ticket(zTarget, zDisplay, nDisplay, &isClosed) ){
956
+ /* Special display processing for tickets. Display the hyperlink
957
+ ** as crossed out if the ticket is closed. Add the title after the
958
+ ** hyperlink.
959
+ */
960
+ if( isClosed ){
961
+ if( g.okHistory ){
962
+ blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[<s>%s</s>]</a>: %s",
963
+ g.zBaseURL, zTarget, zTarget, zDisplay
964
+ );
965
+ }else{
966
+ blob_appendf(p->pOut,"[<s>%s</s>]: %s", zTarget, zDisplay);
967
+ }
968
+ }else{
969
+ if( g.okHistory ){
970
+ blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[%s]</a>: %s",
971
+ g.zBaseURL, zTarget, zTarget, zDisplay
972
+ );
973
+ }else{
974
+ blob_appendf(p->pOut,"[%s]: %s", zTarget, zDisplay);
975
+ }
976
+ }
977
+ zDisplay[0] = ' ';
978
+ zDisplay[1] = 0;
979
+ rc = 0;
980
+ }else if( g.okHistory ){
981
+ blob_appendf(p->pOut, "<a href=\"%s/info/%s\">", g.zBaseURL, zTarget);
982
+ rc = 1;
983
+ }
984
+ }else if( wiki_name_is_wellformed(zTarget) ){
985
+ blob_appendf(p->pOut, "<a href=\"%s/wiki?name=%T\">", g.zBaseURL, zTarget);
986
+ rc = 1;
987
+ }else{
988
+ blob_appendf(p->pOut, "[bad-link: %h]", zTarget);
989
+ rc = 0;
990
+ }
991
+ return rc;
909992
}
910993
911994
/*
912995
** Check to see if the given parsed markup is the correct
913996
** </verbatim> tag.
@@ -1028,11 +1111,14 @@
10281111
case TOKEN_LINK: {
10291112
char *zTarget;
10301113
char *zDisplay = 0;
10311114
int i, j;
10321115
int savedState;
1033
- int ok;
1116
+ int needCloseA;
1117
+ int altSize;
1118
+ char zAltDisplay[100];
1119
+
10341120
startAutoParagraph(p);
10351121
zTarget = &z[1];
10361122
for(i=1; z[i] && z[i]!=']'; i++){
10371123
if( z[i]=='|' && zDisplay==0 ){
10381124
zDisplay = &z[i+1];
@@ -1041,25 +1127,27 @@
10411127
}
10421128
}
10431129
z[i] = 0;
10441130
if( zDisplay==0 ){
10451131
zDisplay = zTarget;
1132
+ altSize = sizeof(zAltDisplay);
10461133
}else{
10471134
while( isspace(*zDisplay) ) zDisplay++;
1135
+ altSize = 0;
10481136
}
1049
- ok = okToHyperlink(zTarget);
1050
- if( ok ){
1051
- blob_append(p->pOut, "<a href=\"", -1);
1052
- resolveHyperlink(zTarget, p);
1053
- blob_append(p->pOut, "\">", -1);
1054
- }
1137
+ zAltDisplay[0] = 0;
1138
+ needCloseA = resolveHyperlink(p, zTarget, zAltDisplay, altSize);
10551139
savedState = p->state;
10561140
p->state &= ~ALLOW_WIKI;
10571141
p->state |= FONT_MARKUP_ONLY;
1058
- wiki_render(p, zDisplay);
1142
+ if( zAltDisplay[0] ){
1143
+ wiki_render(p, zAltDisplay);
1144
+ }else{
1145
+ wiki_render(p, zDisplay);
1146
+ }
10591147
p->state = savedState;
1060
- if( ok ) blob_append(p->pOut, "</a>", 4);
1148
+ if( needCloseA ) blob_append(p->pOut, "</a>", 4);
10611149
break;
10621150
}
10631151
case TOKEN_TEXT: {
10641152
startAutoParagraph(p);
10651153
blob_append(p->pOut, z, n);
10661154
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -867,47 +867,130 @@
867 if( !validate16(z, n) ) return 0;
868 return 1;
869 }
870
871 /*
872 ** Return true if the given hyperlink should be implemented for
873 ** the current login.
874 */
875 static int okToHyperlink(const char *zTarget){
876 if( g.okHistory ) return 1;
877 if( strncmp(zTarget, "http:", 5)==0
878 || strncmp(zTarget, "https:", 6)==0
879 || strncmp(zTarget, "ftp:", 4)==0
880 || strncmp(zTarget, "mailto:", 7)==0
881 ){
882 return 1;
883 }
884 if( zTarget[0]=='/' || is_valid_uuid(zTarget) ) return 0;
885 if( wiki_name_is_wellformed(zTarget) ) return 1;
886 return 0;
887 }
888
889 /*
890 ** Resolve a hyperlink. The argument is the content of the [...]
891 ** in the wiki. Append the URL to the output of the Renderer.
892 */
893 static void resolveHyperlink(const char *zTarget, Renderer *p){
894 if( strncmp(zTarget, "http:", 5)==0
895 || strncmp(zTarget, "https:", 6)==0
896 || strncmp(zTarget, "ftp:", 4)==0
897 || strncmp(zTarget, "mailto:", 7)==0
898 ){
899 blob_appendf(p->pOut, zTarget);
900 }else if( zTarget[0]=='/' ){
901 blob_appendf(p->pOut, "%s%h", g.zBaseURL, zTarget);
902 }else if( is_valid_uuid(zTarget) ){
903 blob_appendf(p->pOut, "%s/info/%s", g.zBaseURL, zTarget);
904 }else if( wiki_name_is_wellformed(zTarget) ){
905 blob_appendf(p->pOut, "%s/wiki?name=%T", g.zBaseURL, zTarget);
906 }else{
907 blob_appendf(p->pOut, "error");
908 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
909 }
910
911 /*
912 ** Check to see if the given parsed markup is the correct
913 ** </verbatim> tag.
@@ -1028,11 +1111,14 @@
1028 case TOKEN_LINK: {
1029 char *zTarget;
1030 char *zDisplay = 0;
1031 int i, j;
1032 int savedState;
1033 int ok;
 
 
 
1034 startAutoParagraph(p);
1035 zTarget = &z[1];
1036 for(i=1; z[i] && z[i]!=']'; i++){
1037 if( z[i]=='|' && zDisplay==0 ){
1038 zDisplay = &z[i+1];
@@ -1041,25 +1127,27 @@
1041 }
1042 }
1043 z[i] = 0;
1044 if( zDisplay==0 ){
1045 zDisplay = zTarget;
 
1046 }else{
1047 while( isspace(*zDisplay) ) zDisplay++;
 
1048 }
1049 ok = okToHyperlink(zTarget);
1050 if( ok ){
1051 blob_append(p->pOut, "<a href=\"", -1);
1052 resolveHyperlink(zTarget, p);
1053 blob_append(p->pOut, "\">", -1);
1054 }
1055 savedState = p->state;
1056 p->state &= ~ALLOW_WIKI;
1057 p->state |= FONT_MARKUP_ONLY;
1058 wiki_render(p, zDisplay);
 
 
 
 
1059 p->state = savedState;
1060 if( ok ) blob_append(p->pOut, "</a>", 4);
1061 break;
1062 }
1063 case TOKEN_TEXT: {
1064 startAutoParagraph(p);
1065 blob_append(p->pOut, z, n);
1066
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -867,47 +867,130 @@
867 if( !validate16(z, n) ) return 0;
868 return 1;
869 }
870
871 /*
872 ** zTarget is guaranteed to be a UUID. It might be the UUID of a ticket.
873 ** If it is, fill zDisplay[0..nDisplay-1] with the title of the ticket
874 ** (or a prefix if the title is too long) and return true. If zTarget
875 ** is not the UUID of a ticket, return false.
876 */
877 static int is_ticket(
878 const char *zTarget, /* Ticket UUID */
879 char *zDisplay, /* Space in which to write ticket title */
880 int nDisplay, /* Bytes available in zDisplay[] */
881 int *pClosed /* True if the ticket is closed */
882 ){
883 static Stmt q;
884 static int once = 1;
885 int n;
886 int rc;
887 char zLower[UUID_SIZE+1];
888 char zUpper[UUID_SIZE+1];
889 n = strlen(zTarget);
890 memcpy(zLower, zTarget, n+1);
891 canonical16(zLower, n+1);
892 memcpy(zUpper, zLower, n+1);
893 zUpper[n-1]++;
894 if( once ){
895 const char *zTitleExpr = db_get("ticket-title-expr", "title");
896 const char *zClosedExpr = db_get("ticket-closed-expr", "status='Closed'");
897 db_static_prepare(&q,
898 "SELECT %s, %s FROM ticket "
899 " WHERE tkt_uuid>=:lwr AND tkt_uuid<:upr",
900 zTitleExpr, zClosedExpr
901 );
902 }
903 db_bind_text(&q, ":lwr", zLower);
904 db_bind_text(&q, ":upr", zUpper);
905 if( db_step(&q)==SQLITE_ROW ){
906 n = db_column_bytes(&q,0);
907 if( n>nDisplay-1 ) n = nDisplay - 1;
908 memcpy(zDisplay, db_column_text(&q, 0), n);
909 zDisplay[n] = 0;
910 rc = 1;
911 *pClosed = db_column_int(&q, 1);
912 }else{
913 rc = 0;
914 }
915 db_reset(&q);
916 return rc;
917 }
918
919 /*
920 ** Resolve a hyperlink. The zTarget argument is the content of the [...]
921 ** in the wiki. Append an <a> markup to the output of the Renderer.
922 **
923 ** Actually, this routine might or might not append the hyperlink, depending
924 ** on current rendering rules: specifically does the current user have
925 ** "History" permission. If this routine does append the <a> and thus needs
926 ** a </a> to follow, it returns true. If the <a> is suppressed, then return
927 ** false.
928 **
929 ** If nDisplay>0 then optionally write up to nDisplay bytes of
930 ** alternative display text into zDisplay. The text must be zero
931 ** terminated. The final zero is included in the nDisplay byte count
932 ** limit.
933 */
934 static int resolveHyperlink(
935 Renderer *p, /* Rendering context */
936 const char *zTarget, /* Hyperlink traget; text within [...] */
937 char *zDisplay, /* Space in which to write alternative display */
938 int nDisplay /* Bytes available in zDisplay[] */
939 ){
940 int rc = 0;
941 if( strncmp(zTarget, "http:", 5)==0
942 || strncmp(zTarget, "https:", 6)==0
943 || strncmp(zTarget, "ftp:", 4)==0
944 || strncmp(zTarget, "mailto:", 7)==0
945 ){
946 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
947 rc = 1;
948 }else if( zTarget[0]=='/' ){
949 if( g.okHistory ){
950 blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zBaseURL, zTarget);
951 rc = 1;
952 }
953 }else if( is_valid_uuid(zTarget) ){
954 int isClosed;
955 if( nDisplay && is_ticket(zTarget, zDisplay, nDisplay, &isClosed) ){
956 /* Special display processing for tickets. Display the hyperlink
957 ** as crossed out if the ticket is closed. Add the title after the
958 ** hyperlink.
959 */
960 if( isClosed ){
961 if( g.okHistory ){
962 blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[<s>%s</s>]</a>: %s",
963 g.zBaseURL, zTarget, zTarget, zDisplay
964 );
965 }else{
966 blob_appendf(p->pOut,"[<s>%s</s>]: %s", zTarget, zDisplay);
967 }
968 }else{
969 if( g.okHistory ){
970 blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[%s]</a>: %s",
971 g.zBaseURL, zTarget, zTarget, zDisplay
972 );
973 }else{
974 blob_appendf(p->pOut,"[%s]: %s", zTarget, zDisplay);
975 }
976 }
977 zDisplay[0] = ' ';
978 zDisplay[1] = 0;
979 rc = 0;
980 }else if( g.okHistory ){
981 blob_appendf(p->pOut, "<a href=\"%s/info/%s\">", g.zBaseURL, zTarget);
982 rc = 1;
983 }
984 }else if( wiki_name_is_wellformed(zTarget) ){
985 blob_appendf(p->pOut, "<a href=\"%s/wiki?name=%T\">", g.zBaseURL, zTarget);
986 rc = 1;
987 }else{
988 blob_appendf(p->pOut, "[bad-link: %h]", zTarget);
989 rc = 0;
990 }
991 return rc;
992 }
993
994 /*
995 ** Check to see if the given parsed markup is the correct
996 ** </verbatim> tag.
@@ -1028,11 +1111,14 @@
1111 case TOKEN_LINK: {
1112 char *zTarget;
1113 char *zDisplay = 0;
1114 int i, j;
1115 int savedState;
1116 int needCloseA;
1117 int altSize;
1118 char zAltDisplay[100];
1119
1120 startAutoParagraph(p);
1121 zTarget = &z[1];
1122 for(i=1; z[i] && z[i]!=']'; i++){
1123 if( z[i]=='|' && zDisplay==0 ){
1124 zDisplay = &z[i+1];
@@ -1041,25 +1127,27 @@
1127 }
1128 }
1129 z[i] = 0;
1130 if( zDisplay==0 ){
1131 zDisplay = zTarget;
1132 altSize = sizeof(zAltDisplay);
1133 }else{
1134 while( isspace(*zDisplay) ) zDisplay++;
1135 altSize = 0;
1136 }
1137 zAltDisplay[0] = 0;
1138 needCloseA = resolveHyperlink(p, zTarget, zAltDisplay, altSize);
 
 
 
 
1139 savedState = p->state;
1140 p->state &= ~ALLOW_WIKI;
1141 p->state |= FONT_MARKUP_ONLY;
1142 if( zAltDisplay[0] ){
1143 wiki_render(p, zAltDisplay);
1144 }else{
1145 wiki_render(p, zDisplay);
1146 }
1147 p->state = savedState;
1148 if( needCloseA ) blob_append(p->pOut, "</a>", 4);
1149 break;
1150 }
1151 case TOKEN_TEXT: {
1152 startAutoParagraph(p);
1153 blob_append(p->pOut, z, n);
1154

Keyboard Shortcuts

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