Check-in [ac3f1f2ba7]
Not logged in
Overview

SHA1 Hash:ac3f1f2ba755061a7a205bc8a91c9adc757791ee
Date: 2008-10-18 02:27:13
User: drh
Comment: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.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/configure.c from [b4f6a922b2] to [2cb2adc7b4].

@@ -86,10 +86,12 @@
   { "ticket-newpage",         CONFIGSET_TKT  },
   { "ticket-viewpage",        CONFIGSET_TKT  },
   { "ticket-editpage",        CONFIGSET_TKT  },
   { "ticket-report-template", CONFIGSET_TKT  },
   { "ticket-key-template",    CONFIGSET_TKT  },
+  { "ticket-title-expr",      CONFIGSET_TKT  },
+  { "ticket-closed-expr",     CONFIGSET_TKT  },
   { "@reportfmt",             CONFIGSET_TKT  },
   { "@user",                  CONFIGSET_USER },
   { "@shun",                  CONFIGSET_SHUN },
 };
 static int iConfig = 0;

Modified src/manifest.c from [5e75594729] to [053fee443e].

@@ -894,11 +894,10 @@
     free(zComment);
   }
   if( m.type==CFTYPE_TICKET ){
     char *zTag;
     Blob comment;
-    int i;
 
     ticket_insert(&m, 1, 1);
     zTag = mprintf("tkt-%s", m.zTicketUuid);
     tag_insert(zTag, 1, 0, rid, m.rDate, rid);
     free(zTag);
@@ -919,10 +918,12 @@
           "Changed %h in ticket [%.10s]",
           m.aField[0].zName, m.zTicketUuid
         );
       }
     }else{
+#if 0
+      int i;
       const char *z;
       const char *zSep = " ";
       blob_appendf(&comment, "%d changes to ticket [%.10s]:",
                             m.nField, m.zTicketUuid);
       for(i=0; i<m.nField; i++){
@@ -929,10 +930,12 @@
         z = m.aField[i].zName;
         if( z[0]=='+' ) z++;
         blob_appendf(&comment, "%s%h", zSep, z);
         zSep = ", ";
       }
+#endif
+      blob_appendf(&comment, "Edits to ticket [%.10s]", m.zTicketUuid);
     }
     db_multi_exec(
       "REPLACE INTO event(type,mtime,objid,user,comment)"
       "VALUES('t',%.17g,%d,%Q,%Q)",
       m.rDate, rid, m.zUser, blob_str(&comment)

Modified src/setup.c from [9a76b5ae3a] to [1f3b727596].

@@ -557,11 +557,11 @@
 }
 
 /*
 ** Generate an entry box for an attribute.
 */
-static void entry_attribute(
+void entry_attribute(
   const char *zLabel,   /* The text label on the entry box */
   int width,            /* Width of the entry box */
   const char *zVar,     /* The corresponding row in the VAR table */
   const char *zQParm,   /* The query parameter */
   char *zDflt     /* Default value if VAR table entry does not exist */

Modified src/tktsetup.c from [5326eea0e1] to [cc6def603d].

@@ -40,10 +40,12 @@
 
   style_header("Ticket Setup");
   @ <table border="0" cellspacing="20">
   setup_menu_entry("Table", "tktsetup_tab",
     "Specify the schema of the  \"ticket\" table in the database.");
+  setup_menu_entry("Timeline", "tktsetup_timeline",
+    "How to display ticket status in the timeline");
   setup_menu_entry("Common", "tktsetup_com",
     "Common TH1 code run before all ticket processing.");
   setup_menu_entry("New Ticket Page", "tktsetup_newpage",
     "HTML with embedded TH1 code for the \"new ticket\" webpage.");
   setup_menu_entry("View Ticket Page", "tktsetup_viewpage",
@@ -615,6 +617,45 @@
     zDesc,
     0,
     0,
     10
   );
+}
+
+/*
+** WEBPAGE: tktsetup_timeline
+*/
+void tktsetup_timeline_page(void){
+  login_check_credentials();
+  if( !g.okSetup ){
+    login_needed();
+  }
+
+  if( P("setup") ){
+    cgi_redirect("tktsetup");
+  }
+  style_header("Ticket Display On Timelines");
+  db_begin_transaction();
+  @ <form action="%s(g.zBaseURL)/tktsetup_timeline" method="POST">
+
+  @ <hr>
+  entry_attribute("Ticket Title", 40, "ticket-title-expr", "t", "title");
+  @ <p>An SQL expression in a query against the TICKET table that will
+  @ return the title of the ticket for display purposes after hyperlinks to
+  @ that ticket</p>
+
+  @ <hr>
+  entry_attribute("Ticket Closed", 40, "ticket-closed-expr", "c",
+                  "status='Closed'");
+  @ <p>An SQL expression that evaluates to true in a TICKET table query if
+  @ the ticket is closed.</p>
+
+  @ <hr>
+  @ <p>
+  @ <input type="submit"  name="submit" value="Apply Changes">
+  @ <input type="submit" name="setup" value="Cancel">
+  @ </p>
+  @ </form>
+  db_end_transaction(0);
+  style_footer();
+
 }

Modified src/wikiformat.c from [850d2b880e] to [ef5196ba6c].

@@ -867,47 +867,130 @@
   if( !validate16(z, n) ) return 0;
   return 1;
 }
 
 /*
-** Return true if the given hyperlink should be implemented for
-** the current login.
+** zTarget is guaranteed to be a UUID.  It might be the UUID of a ticket.
+** If it is, fill zDisplay[0..nDisplay-1] with the title of the ticket
+** (or a prefix if the title is too long) and return true.  If zTarget
+** is not the UUID of a ticket, return false.
 */
-static int okToHyperlink(const char *zTarget){
-  if( g.okHistory ) return 1;
-  if( strncmp(zTarget, "http:", 5)==0
-   || strncmp(zTarget, "https:", 6)==0
-   || strncmp(zTarget, "ftp:", 4)==0
-   || strncmp(zTarget, "mailto:", 7)==0
-  ){
-    return 1;
+static int is_ticket(
+  const char *zTarget,    /* Ticket UUID */
+  char *zDisplay,         /* Space in which to write ticket title */
+  int nDisplay,           /* Bytes available in zDisplay[] */
+  int *pClosed            /* True if the ticket is closed */
+){
+  static Stmt q;
+  static int once = 1;
+  int n;
+  int rc;
+  char zLower[UUID_SIZE+1];
+  char zUpper[UUID_SIZE+1];
+  n = strlen(zTarget);
+  memcpy(zLower, zTarget, n+1);
+  canonical16(zLower, n+1);
+  memcpy(zUpper, zLower, n+1);
+  zUpper[n-1]++;
+  if( once ){
+    const char *zTitleExpr = db_get("ticket-title-expr", "title");
+    const char *zClosedExpr = db_get("ticket-closed-expr", "status='Closed'");
+    db_static_prepare(&q,
+      "SELECT %s, %s FROM ticket "
+      " WHERE tkt_uuid>=:lwr AND tkt_uuid<:upr",
+      zTitleExpr, zClosedExpr
+    );
+  }
+  db_bind_text(&q, ":lwr", zLower);
+  db_bind_text(&q, ":upr", zUpper);
+  if( db_step(&q)==SQLITE_ROW ){
+    n = db_column_bytes(&q,0);
+    if( n>nDisplay-1 ) n = nDisplay - 1;
+    memcpy(zDisplay, db_column_text(&q, 0), n);
+    zDisplay[n] = 0;
+    rc = 1;
+    *pClosed = db_column_int(&q, 1);
+  }else{
+    rc = 0;
   }
-  if( zTarget[0]=='/' || is_valid_uuid(zTarget) ) return 0;
-  if( wiki_name_is_wellformed(zTarget) ) return 1;
-  return 0;
+  db_reset(&q);
+  return rc;
 }
 
 /*
-** Resolve a hyperlink.  The argument is the content of the [...]
-** in the wiki.  Append the URL to the output of the Renderer.
+** Resolve a hyperlink.  The zTarget argument is the content of the [...]
+** in the wiki.  Append an <a> markup to the output of the Renderer.
+**
+** Actually, this routine might or might not append the hyperlink, depending
+** on current rendering rules: specifically does the current user have
+** "History" permission.  If this routine does append the <a> and thus needs
+** a </a> to follow, it returns true.  If the <a> is suppressed, then return
+** false.
+**
+** If nDisplay>0 then optionally write up to nDisplay bytes of
+** alternative display text into zDisplay.  The text must be zero
+** terminated.  The final zero is included in the nDisplay byte count
+** limit.
 */
-static void resolveHyperlink(const char *zTarget, Renderer *p){
+static int resolveHyperlink(
+  Renderer *p,            /* Rendering context */
+  const char *zTarget,    /* Hyperlink traget; text within [...] */
+  char *zDisplay,         /* Space in which to write alternative display */
+  int nDisplay            /* Bytes available in zDisplay[] */
+){
+  int rc = 0;
   if( strncmp(zTarget, "http:", 5)==0
    || strncmp(zTarget, "https:", 6)==0
    || strncmp(zTarget, "ftp:", 4)==0
    || strncmp(zTarget, "mailto:", 7)==0
   ){
-    blob_appendf(p->pOut, zTarget);
+    blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
+    rc = 1;
   }else if( zTarget[0]=='/' ){
-    blob_appendf(p->pOut, "%s%h", g.zBaseURL, zTarget);
+    if( g.okHistory ){
+      blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zBaseURL, zTarget);
+      rc = 1;
+    }
   }else if( is_valid_uuid(zTarget) ){
-    blob_appendf(p->pOut, "%s/info/%s", g.zBaseURL, zTarget);
+    int isClosed;
+    if( nDisplay && is_ticket(zTarget, zDisplay, nDisplay, &isClosed) ){
+      /* Special display processing for tickets.  Display the hyperlink
+      ** as crossed out if the ticket is closed.  Add the title after the
+      ** hyperlink.
+      */
+      if( isClosed ){
+        if( g.okHistory ){
+          blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[<s>%s</s>]</a>: %s",
+              g.zBaseURL, zTarget, zTarget, zDisplay
+          );
+        }else{
+          blob_appendf(p->pOut,"[<s>%s</s>]: %s", zTarget, zDisplay);
+        }
+      }else{
+        if( g.okHistory ){
+          blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[%s]</a>: %s",
+              g.zBaseURL, zTarget, zTarget, zDisplay
+          );
+        }else{
+          blob_appendf(p->pOut,"[%s]: %s", zTarget, zDisplay);
+        }
+      }
+      zDisplay[0] = ' ';
+      zDisplay[1] = 0;
+      rc = 0;
+    }else if( g.okHistory ){
+      blob_appendf(p->pOut, "<a href=\"%s/info/%s\">", g.zBaseURL, zTarget);
+      rc = 1;
+    }
   }else if( wiki_name_is_wellformed(zTarget) ){
-    blob_appendf(p->pOut, "%s/wiki?name=%T", g.zBaseURL, zTarget);
+    blob_appendf(p->pOut, "<a href=\"%s/wiki?name=%T\">", g.zBaseURL, zTarget);
+    rc = 1;
   }else{
-    blob_appendf(p->pOut, "error");
+    blob_appendf(p->pOut, "[bad-link: %h]", zTarget);
+    rc = 0;
   }
+  return rc;
 }
 
 /*
 ** Check to see if the given parsed markup is the correct
 ** </verbatim> tag.
@@ -1028,11 +1111,14 @@
       case TOKEN_LINK: {
         char *zTarget;
         char *zDisplay = 0;
         int i, j;
         int savedState;
-        int ok;
+        int needCloseA;
+        int altSize;
+        char zAltDisplay[100];
+
         startAutoParagraph(p);
         zTarget = &z[1];
         for(i=1; z[i] && z[i]!=']'; i++){
           if( z[i]=='|' && zDisplay==0 ){
             zDisplay = &z[i+1];
@@ -1041,25 +1127,27 @@
           }
         }
         z[i] = 0;
         if( zDisplay==0 ){
           zDisplay = zTarget;
+          altSize = sizeof(zAltDisplay);
         }else{
           while( isspace(*zDisplay) ) zDisplay++;
+          altSize = 0;
         }
-        ok = okToHyperlink(zTarget);
-        if( ok ){
-          blob_append(p->pOut, "<a href=\"", -1);
-          resolveHyperlink(zTarget, p);
-          blob_append(p->pOut, "\">", -1);
-        }
+        zAltDisplay[0] = 0;
+        needCloseA = resolveHyperlink(p, zTarget, zAltDisplay, altSize);
         savedState = p->state;
         p->state &= ~ALLOW_WIKI;
         p->state |= FONT_MARKUP_ONLY;
-        wiki_render(p, zDisplay);
+        if( zAltDisplay[0] ){
+          wiki_render(p, zAltDisplay);
+        }else{
+          wiki_render(p, zDisplay);
+        }
         p->state = savedState;
-        if( ok ) blob_append(p->pOut, "</a>", 4);
+        if( needCloseA ) blob_append(p->pOut, "</a>", 4);
         break;
       }
       case TOKEN_TEXT: {
         startAutoParagraph(p);
         blob_append(p->pOut, z, n);