Collection of themes/skins for the Fossil SCM

โŒˆโŒ‹ โŽ‡ branch:  Fossil Skins Extra


Artifact [f02b7ea13b]

Artifact f02b7ea13bab666dc43f6d915c2f30d9380e2de1:

  • File github.txt — part of check-in [2cc887f0ca] at 2018-06-04 05:19:42 on branch trunk — Fix for /tree lacking parameters [926d2b80d8] (user: mario size: 32270)

# Fossil skin configuration "github" for simple `fossil import skin.txt`
# 2016-09-04T12-28-29Z
#
config /config 10652
1472992109 'css' value '/* fonts */
@import url(http://fonts.googleapis.com/css?family=Viga);

/* no body spacing */
html, body {
   border: 0; padding: 0; margin: 0;
   background: #fefefe;
   font: normal normal 400 10pt/16pt Arial,sans-serif;
   max-width: 1600px;
}
html, * {
   box-sizing: border-box; 
}
/* general text settings */
body, article, p {
   color: #333;
   line-height: 145%;
}

/* layout features */
.width-container {
   padding-left: 15% !important;
   padding-right: 15% !important; 
}
.text-shadow {
   text-shadow: 0px 1px 0px #888;
}
.glyph {
   font-family: "Symbola", "Arial Unicode MS", "Quivira", "Code200",  sans-serif;
}

/* menu layout */
#menu-header {
   background: #f7f7f7;
}
#menu-header, #submenu-header, #project-header, #fossil-footer {
   width: 100%;
   height: 50pt;
   padding: 10pt;
   padding-top: 14pt;
   border-bottom: 1px solid #dfdfdf;
   display: block;
   clear: both;
   vertical-align: middle;
}
#menu-header h1 {
   font-family: Viga;
   font-weight: 700;
   display: inline;
   font-size: 23pt;
   color: #333;
   margin: 3pt 18pt 0 0;
   float: left;
}

/* search bar */
#search_form {
   display: inline;
}
#search_form {
   display: inline-box;
   padding: 0;
   height: 30px;
   line-height: 30px;
}
#search_form input, #search_form select {
   padding: 3px;
   border: 1px solid #ccc;
}
#search_form select, #search_form option {
   background: #eee;
}
#search_form input:focus, #search_form select:focus {
  box-shadow: 0 0 5px rgba(81, 203, 238, 1);
  border: 1px solid rgba(81, 203, 238, 1);  
}


/* menu links */
#menu-header a {
   color: #444;
   font-size: 11pt;
   font-weight: 600;
   padding: 7pt;
}

/* buttons */
#menu-header a.button {
   top: 2pt;
   border-radius: 5px;
   font-weight: 700;
   padding: 6pt 10pt;
   position: relative;
}
.button.green {
   border: 1px solid #493;
   background: #7e5 linear-gradient(180deg,#8e7,#593);
   color: #fff !important;
}
.button.green:hover {
   background: #5c3 linear-gradient(180deg,#6d5,#482);
}
.button.white {
   border: 1px solid #ddd;
   background: #eee;
   background: linear-gradient(180deg,#fff,#ddd);
   color: #333;
}
.button.white:hover, .submenu .label:hover {
   background: #ccc linear-gradient(180deg,#eee,#ccc);
}
.button.red {
   border: 1px solid #d97;
   background: #fdc;
   background: linear-gradient(180deg,#fdc,#eba);
   color: #333;
}
.button.red:hover {
   background: #eba linear-gradient(180deg,#ecb,#c97);
}
.download.button {
   display: block;
   padding: 4pt 10pt;
   border-radius: 4px;
   margin-top: 10pt;
}
.branch.button {
   font-family: sans-serif;
   padding: 2pt 6pt;
   border-radius: 4px;
}
.branch.button select {
   border: 0 !important;
   background: inherit;
   font-size: 103%;
   font-weight: 700;
   -webkit-appearance: none;
   -moz-appearance: none;
   appearance: none;
}
.branch.button option {
   font-weight: 700;
   background: #eee;
}

/* fossil action buttons */
.submenu a.label, .submenu select, .submenu input {
   padding: 2px 3pt;
   border-radius: 3px;
   border: 1px solid #d7d7d7;
   background: #fff linear-gradient(0deg, #eee 0%, #fff 20%);
   color: #333;
   font-size: 12pt;
}
.submenu input { width: 40px; }
.submenu select {  padding: 1px 3pt; }
.submenu select option {
   background: linear-gradient(0deg, #ddd 0%, #fff 20%);
   font-size: 11pt;
}
main .submenu {
   float: right;
   position: relative;
   top: -40px;
}

/* links */
a {
   text-decoration: none;
   color: #57c;
}

/* project info */
#project-header a {
   font-size: 16pt;
   color: #48c;
   font-weight: 100;
}
#public-prefix {
   font-size: 13pt;
   font-weight: 100;
   color: #ccc;
   position: relative;
   left: -53pt;
   top: 5pt;
}
#project-header .share-button {
   padding: 3pt 6pt;
   font-weight: 600;
   border: 1px solid #ddd;
   border-radius: 4px 0 0 4px;
   background: #eee;
   background:linear-gradient(180deg,#fff,#e7e7e7);
   color: #222;
}
#project-header .share-button a {
   font-size: 12pt;
}
#project-header .share-button-number {
   padding: 3pt 6pt;
   border: 1px solid #ddd;
   border-left: 0;
   border-radius: 0 4px 4px 0;
   background: #fcfcfc;
   color: #444;
}

/* project main pane */
#project-content {
   font-size: 12.25pt;
   padding-top: 16pt;
   color: #666;
   width: 82%;  /************** layout *************/
}

/* code statistics box */
#project-stats {
   margin-top: 10pt;
   border: 1px solid #ccc;
   border-radius: 4pt;
   height: 48px;
   padding: 0;
   overflow: hidden;
   background: #fcfcfc;
   font-family: sans-serif;
   font-size: 90%;
}
#project-stats-alternate {
   height: 34px;
   text-align: center;
   overflow: hidden;
   padding: 1pt 20pt;

}
#project-stats-alternate div {
   height: 34px;
   padding: 8px;
}
#project-stats-alternate a {
   padding: 1pt 25pt;
   color: #999;
}
#project-stats-alternate a b {
   color: #111;
}

/* language color graph */
#language-bar {
   overflow: hidden;
   width: 101%;
   height: 16px;
   border-radius: 0 0 4pt 4pt;
   background: #999;
   padding: 0;
   margin: 0;
}
#language-bar span:first-child {
   border-radius: 0 0 0 8pt;
}
#language-bar span:last-child {
   border-radius: 0 0 8pt 0;
}


/* default user pic in filebox */
.user {
   font-weight: 700;
   color: #557;
   padding-left: 26px;
   background: url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAWABYDASIAAhEBAxEB/8QAGQABAQEAAwAAAAAAAAAAAAAAAAYFBAgJ/8QAJhAAAQMEAgEEAwEAAAAAAAAAAQIDBAAFBhESIQcTMTJBM2Fxkf/EABcBAQADAAAAAAAAAAAAAAAAAAUAAQP/xAAnEQABAwMCBAcAAAAAAAAAAAABAgMEABExEiEFBgdBEyIyQlGBwf/aAAwDAQACEQMRAD8A7g5RKy61YvcAw4I819CIsKBwBEl9Z4oSVAnQClAqOx1vuoZrDfOeLXaLkUfIYuTWZKUKucdxlDKwB+RLaQOR0O0lKtk9EH7o8j8v4hbrnGZvGbWZlxO3lCRJbZSOK/iN6IAI13s1Yz8ptljx6TIvN2Zs8EBIdnS1hDUfmdJKle2+RAGj7kf2j+bZU1M+IhD5bCuzarDVqA8wGbgj1X2xk0pwQRnYryikKKck77W7fGDiuRdGp9vW6pMy2ej6iQ27IlcCsKSFHrX0ev2KVHQsrwPIUyZFoya3XKNHU00FiWl0pXwPPfySOwCNH2pW0rpvyhOfXJfaBWskk6sk7n3UEnibiQEhY2ryX8izvIj/AJPyyMZsAOIvEvluS4oAeoeKQSjZAToDf0KzrvnfmmRBj4xdcyfl2+MlJYhuXOQ5HaA2lPFChxTrR1odClKtIBQm4xioytSGgEmwtatTxNkGeTblPSLgwlhLIC0CQpJUsKHE/A9AKV/tKUoqZEZfd1uJuaDnQY8l7xHU3P3+V//Z") no-repeat;
}


/* sidebar */
#function-sidebar {
   width: 15%;
   float: right;   /************** layout *************/
   overflow: hidden;
}
#function-sidebar ul {
   padding: 0;
   margin: 20pt 0 0 0;
}
#function-sidebar li {
   border-left: 1px solid #ececec;
   box-shadow: #f4f4f4 4px 0 0 0 inset;
   display: block;
   border-bottom: 1px solid #fcfcfc;
   list-style-type: none;
   margin: 0;
}
#function-sidebar li a {
   display: block;
   white-space: nowrap;
   padding: 8pt 20pt;
}
#function-sidebar li.current, #function-sidebar li a[href="current_page"] {
   border: 0;
   border-top: 1px solid #eee;
   border-bottom: 1px solid #eee;
   border-right: 3px solid #b53;
   box-shadow: #f7f7f7 0 3px 0 0 inset;
}
#function-sidebar li:hover {
   background: linear-gradient(90deg, #bbb 0%, #eee 2%, #f7f7f7 15%, #fff 50%);
}
#function-sidebar li a b {
   color: #333;
   font-weight: 500;
}

/* file list box */
#recent-files {
   border: 1px solid #edf;
   border-radius: 3pt;
   box-shadow: #f0f4f8 0 0 6px 4px;
   padding: 0;
}
#recent-files table {
   background: #f7f8f9;
   width: 100%;
   margin: 0; padding: 0; border-spacing: 0;
   border-collapse: collapse;
}
#recent-files tr {
   width: 100%;
   border-bottom: 1px solid #e4e8ea;
   font-size: 89%;
   padding: 0 !important; margin: 0 !important;
}
#recent-files th {
   padding: 5pt;
   text-align: left;
   font-weight: 300;
   background: #e0e7f7;
   border-bottom: 1px solid #f3f5f7;
}
#recent-files td {
   padding: 5pt;
   text-align: left;
   font-size: 89%;
}
#recent-files a b {
   font-weight: 600;
   font-size: 115%;
}
#recent-files a.dir b { color: #33c; }
#recent-files a.file b { color: #dcb; }

/* actual fossil content (wiki/timeline/files/etc.) */
main {
   display: block;
   min-height: 500pt;
   border: 1px solid #eee;
   box-shadow: #f7f7f7 0 0 5px 3px;
}
main h2.page-title {
   display: block;
   margin: 0;
   padding: 9pt;
   background: #eee;
   background: linear-gradient(180deg, #fff 0%, #fcfcfc 33%, #f7f7f7 75%, #ebebeb 100%);
}

/* end */
#fossil-footer {
   margin-top: 30pt;
   border-top: 1px solid #eee;
   height: 175pt;
}
#fossil-footer .button {
   padding: 3pt 6pt;
   border-radius: 2pt;
}


/* have different layout on frontpage than elsewhere */
                .optional { display: none; }
body.page-index .optional { display: inherit; }
body.page-index span.optional { display: inline; }
                #function-sidebar { width: 6%; }
body.page-index #function-sidebar { width: 15%; }
                #project-content { width: 91%; }
body.page-index #project-content { width: 82%; }


/* ui::search decoration */
.searchResult li {
}
.searchResult li a {
   font-size: 120%;
   font-weight: 300;
}
.searchResult li a.search-link {
   display: block;
   font-weight: 100;
   font-size: 70%;
   color: #337733;
   line-height: 125%;
}
.searchResult li .snippet {
   color: #333;
}
.searchResult li .snippet mark {
   text-decoration: none;
   font-style: italic;
   font-weight: 700;
   color: #aa3322;
   background: #f7f3cc;
}



/* actual HTML content decoration */
kbd {
   border: 1px dotted #bbb;
   border-radius: 3px;
   padding: 1px 3px;
   background: #eee linear-gradient(#fafcff,#e7e9ec);
}

code {
   background: #f5f6f7;
}

pre.prettyprint {
   border: 1px dashed #eee !important;
   background: #f7f7f7;
}

main table {
   border: 1px solid #edf;
   box-shadow: #f0f4f8 0 0 6px 4px;
   background: #ddd linear-gradient(180deg,#fff,#f7f7f7);
}
main table th {
   background: #eee linear-gradient(180deg,#fff,#ddd);
   border-collapse: collapse;
   padding: 5pt;
   text-align: left;
}
main table tr {
   border: 1px solid #f7f7f7;
}
#timelineTable tr {
   border: 0;
}
main table td {
   padding: 3pt;
}

main code, main pre {
   /*font-family: "Source Sans Pro", sans-serif;
   font-size: 110%;*/
}

main h2, main h3, main h4, main h5, main h6 {
   margin-top: 22pt;
}

main li {
   margin-top: 5.5pt;
}

main em {
   color: #522;
}

main .content p, main .content tr, main .content li {
   line-height: 155%;
}

main td.tktDspValue {
   background: #f3f3f3 linear-gradient(45deg,#ebebf5,#fff);
}
main td.tktDspLabel {
   background: linear-gradient(115deg,#fff 70%,#fff9f7 100%);
   text-align: right;
   font-size: 90%;
   color: #666;
}

/* ------------------------ Fossil internal styles ------------------------ */




'
config /config 8232
1528091111 'header' value '<th1>

 #-- Determine current page type
 set pagename ""
 if {[regexp {^(index|home)[?]?} $current_page]} {
    set pagecat "index"
 } else { if {[regexp {^wiki\?name=} $current_page]} {
       set pagecat "wiki"
       set pagename [string range $current_page 10 2048]
 } else {
    set pagecat $current_page
 } }

 #-- For outputting class=current in #sidebar
 proc current {name} {
    upvar 1 pagecat pagecat
    if [regexp "^($name)" $pagecat] { puts { class=current} }
 }

 #-- Split domain name from baseurl
 set basedomain [string range $baseurl [expr 7+[expr {[string range $baseurl 4 4] eq "s"}]] [expr 7+[string first "/" [string range "$baseurl/" 8 50]]]]

 #-- Project stats
 set stats_description [setting project-description]
 set stats_social 0
 set stats_forks 1
 catch { query { SELECT name,value FROM fx_stats WHERE name GLOB ''stats_*'' } {
    set "$name" "$value"
 } }

 #-- end setup

</th1>

<html>
<head>

  <title>$<project_name>: $<title></title>
  <base href="$<baseurl>/$current_page" />
  <meta http-equiv=Content-Type content="text/html; charset=UTF-8; version=5">
  <link rel=alternate type="application/rss+xml" title=Timeline href="$<baseurl>/timeline.rss">
  <link rel=stylesheet href="$<baseurl>/style.css?gitlike" type="text/css" media=screen>

  <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
  <link rel=stylesheet href="//cdn.rawgit.com/google/code-prettify/master/loader/prettify.css" type="text/css" media="screen">

  <script language=JavaScript>

     var baseurl = "$<baseurl>";
     var current_branch = "trunk";

     function json_api(what, callback) {
         $.getJSON(baseurl + "/json/" + what, {}, function(data) {
             callback(data.payload);
         });
     }

     function show_branches(payload) {
         current_branch = payload.current
         var brb = $("#branch-button select");
         brb.empty();
         payload.branches.push("tip");
         $.each(payload.branches, function (i,v){
             brb.append("<option class=branch-name>"+v);
         });
     }


  </script>

</head>
<body
   class="page-$pagecat"
   onLoad="
      $(''code,pre'').addClass(''prettyprint'');
      $.getScript(''//cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?autoload=true'')
   "
>



  <footer id=menu-header><div class=width-container>
      <h1>Fossil</h1>
      <form id=search_form action=search method=get style=display:inline><span>
        <select>
           <option>This repository
        </select><input type=search name=s placeholder="Search through files and wiki" size=30>
      </span></form>
      <a href="http://chiselapp.com/repositories/">Explore</a>
      <a href="http://www.fossil-scm.org/">Features</a>
      <a href="timeline?y=w">Wiki</a>
      <a href="timeline?n=20&y=e">Blog</a>
      <span style=float:right>
        <th1>
            if {[hascap s]} { html {<a href=setup class="button red text-shadow">Admin</a>}
            } else { html {<a href=setup class="button green text-shadow">Sign up</a>} }
        </th1>
        <a href=login class="button white text-shadow"><th1>if {[info exists login]} { puts $login } else { puts "Sign in" }</th1></a>
      </span>
  </div></footer>



  <footer id=project-header><div class=width-container>
      <span style=position:absolute><span id=public-prefix>PUBLIC&nbsp;</span></span>
      <img src="/fossil-icon.png" align=middle height=32 width=32>
      <a href="../..">$basedomain</a> / <a href=index><b>$project_name</b></a>
      <span style="float:right;">
         <span class=share-button id=share-button onclick="$(''#share-button>span'').toggle(''75'')">&#9733; Star
               <span class=social-links style=display:none><th1>catch { ui::social_links $baseurl }</th1></span></span><span class=share-button-number>$stats_social</span>
         <span class=share-button glyph>&#9988; Fork</span><span class=share-button-number>$stats_forks</span>
      </span>
  </div></footer>
  



  <section id=main-content><div class=width-container>
  
    <aside id=function-sidebar>
       <ul>
         <li style=padding:5pt>
          <li<th1>current index|tree|dir|finfo|artifact|raw|hex</th1>><a href=tree?type=tree&ci=trunk>  <b class=glyph>&lt;&gt;</b>  <span class=optional> Code </span></a></li>
          <li<th1>current reportlist|tkt|rpt</th1>><a href=reportlist title=Tickets>  <b class=glyph>๐Ÿ“Œ</b>   <span class=optional> Issues </span></a></li>
          <li<th1>current wiki|wcontent|whist|attach|wdiff</th1>><a href=wcontent title=Wiki>       <b class=glyph>๐Ÿ“–</b>   <span class=optional> Wiki </span></a></li>
          <li<th1>current timeline</th1>><a href=timeline title=Time>       <b class=glyph>๐Ÿ“ฐ</b>   <span class=optional> Pulse </span></a></li>
          <li<th1>current reports</th1>><a href=reports title=Graphs>      <b class=glyph>๐Ÿ“Š</b>   <span class=optional> Graphs </span></a></li>
          <li<th1>current tag</th1>><a href=taglist title=Tags>        <b class=glyph>๐Ÿ“›</b>   <span class=optional> Tags </span></a></li>
          <li style=padding:3pt>
       </ul>
       <p class=optional>
          <b>HTTP</b>S sync URL<br>
          <input type=url size=15 value="$<baseurl>/xfer" style="border: 1px solid #ddd; border-radius: 3px" onClick="select()"><br>
          <a class="download button white text-shadow" href="$<baseurl>/zip/$<project_name>.zip?uuid=trunk">โžฑ&nbsp;Download ZIP</a>
          <a class="download button white text-shadow" href="$<baseurl>/tarball/$<project_name>.tgz?uuid=trunk">โžฑ&nbsp;Download TGZ</a>
       </p>
    </aside>



    <section id=project-content>
       <article class=optional>
          $stats_description
       </article>


       
       <section id=project-stats class=optional>
       <th1>
       if {$pagecat eq "index"} {
          set stats_checkins [set stats_branches [set stats_releases [set stats_developers 0]]] ; catch { ui::stats }
          html "
           <div id=project-stats-alternate>
           <div>
            <a href=''timeline?y=ci''><span class=glyph>โ—ถ</span> <b>$stats_checkins</b> commits</a>
            <a href=brlist>         <span class=glyph>โŽ‡</span>  <b>$stats_branches</b> branches</a>
            <a href=taglist>        <span class=glyph>โŒซ</span>  <b>$stats_releases</b> releases</a>
                  <a href=''timeline?u=*''> <span class=glyph>โ™™</span> <b>$stats_developers</b> developers</a>
               </div>
               <div>
                  <a href=#><b>100%</b> open source</a>
               </div>
           </div>
           "
           html { <div onclick="$(''#project-stats-alternate div:eq(0)'').toggle(''slow'')" id=language-bar> }
           catch { ui::lang_stats }
           html { </div> }
       }
       </th1>
       </section>



       <br>
       <section id=branch-info>
         <a class="branch button green" id=diff-button href=ci/tip>โŒˆโŒ‹</a>
         <a class="branch button white" id=branch-button href=brlist><span class=glyph>โŽ‡</span>  <small>branch:</small>
             <select name=branch onclick="json_api(''branch/list'', show_branches); event.preventDefault();" onChange="location.replace(''timeline?r=''+this.value)"><option style="font-weight:900;color:red;">trunk</select></a>
         &nbsp;<a href=index><b>$project_name</b></a>
       </section>



       <br>
     <th1>
     catch {
       if {$pagecat eq "index" || [string length $pagename] && [sql::dir_exists $pagename]} {
         html {
          <section id=recent-files>
          <table><colgroup><col style="width:25%"><col style="width:60%"><col style="width:15%"></colgroup><tbody>
         }

         ui::last_commit ;

         ui::recent_files [?: {$pagecat eq "index"} "" "$pagename"]

         html {
           </tbody></table>
           </section>
         }
       }
     }
     </th1>


 
       <br>
       <main>
          <h2 class=page-title>$title</h2>
          <article style=padding:7pt>




'
config /config 1998
1472992109 'footer' value '             <br><br><br>


          </article>
       </main>

    </section>

  </div></section>




  <div class=width-container>
    <footer id=fossil-footer>
        <p><a href=http://fossil-scm.org/>Fossil</a> version <tt>$release_version</tt> | Manifest: $manifest_version | Manifest date: $manifest_date
        </p>
        <p>
           <a class="button green" href=login>Account</a>
           <a class="button white" href=Index>Index</a>
           <a class="button white" href=sitemap>Sitemap</a>
        </p>
           <a class="button red" href=setup>Setup</a>
           <a href=setup_ulist>Users</a> /
           <a href=setup_access>Access</a> /
           <a href=setup_config>Config</a> /
           <a href=setup_timeline>Timeline</a> /
           <a href=setup_editcss>CSS</a> /
           <a href=setup_header>Header</a> /
           <a href=setup_footer>Footer</a> /
           <a href=rcvfromlist>Log</a> /
           <a href=admin_sql>SQL</a> /
           <a href=stat>Stats</a>
        </p>
        <p>
           <a class="button white" href=wiki>Wiki</a>
           <a href=wcontent>All Pages</a> /
           <a href=timeline?y=w>Recent Changes</a> /
           <a href=wikinew>New Page</a> /
           <a href=eventedit>New Event</a> /
           <a href=modreq>Moderation</a>
        </p>
        <p>
           <a class="button green" href=help>Help</a>
           <a class="button white" href="tree?ci=tip">Files</a>
           <a class="button white" href=zip/trunk.zip?uuid=trunk>ZIP</a>
           <a class="button white" href=tarball/trunk.tgz?uuid=trunk>TGZ</a>
           <a class="button white" href=timeline>Timeline</a>
           <a class="button white" href=brlist>Branches</a>
           <a class="button white" href=taglist>Tags</a>
           <a class="button white" href=reportlist>Tickets</a>
           <a class="button white" href=reports>Reports</a>
        </p>
        
    </footer>
  </div>

</body>
</html>

'
config /config 41
1394330578 'timeline-plaintext' value '1'
config /config 11144
1472992109 'th1-setup' value '
#-- Pre-increment  [++ varname]
proc ++ {varname} {
   upvar 1 $varname i
   return [uplevel 1 "set {$varname} [expr 1+$i]"]
}

#-- ternary / if-shorthand (cond/then/else may be literals, or {[expressions]} themselves)
proc ?: {cond then else} {
   uplevel 1 "if {$cond} { return $then; } else { return $else; }"
}

#-- info exists shorthand
proc isset {varname} {
   return [uplevel 1 "info exists {$varname}"]
}

#-- string equality shorthand
proc eq {str1 str2} {
   return [expr {$str1 eq $str2}]
}

#-- while loop
proc while {condition code} {
   return [uplevel 1 "for {} {$condition} {} {$code}"]
}

#-- foreach VAR "abc xyz 123" { puts "($VAR) " }
proc foreach {varname list code} {
   upvar 1 $varname val
   for {set i 0}  {$i < [llength $list]}  {++ i} {
      set val [lindex $list $i]
      uplevel 1 "$code"
   }
}

#-- A switch statement.
#
# switch "val" {
#        "cmp1" {code1}
#        "cmp2" {code2}
#        "cmp3" {code3}
#   {{default}} {codeN}
# }
#
proc switch {compare_value val_code_pairs} {
   set len [llength $val_code_pairs]
   # loop over compare values + code pairs
   for  {set n 0}  {$n < $len}  {++ n} {
      set cmp [lindex $val_code_pairs $n];
      if {[expr $cmp eq $compare_value || $cmp eq {{default}} ]} {
         return [uplevel 1 [lindex $val_code_pairs [++ n]]];
      }
   }
}


#-- Ordered list of project statistics (will populate global $stats() array)
proc ui::stats {} {
   uplevel 1 { query {SELECT
     (SELECT count(objid) FROM event WHERE type=''ci'' LIMIT 1) AS `stats_checkins`,
     (SELECT count(name) FROM filename LIMIT 1) AS `stats_files`,
     (SELECT count(status) FROM ticket LIMIT 1) AS `stats_tickets`,
     (SELECT count(DISTINCT user) FROM event LIMIT 1) AS `stats_developers`,
     (SELECT count(DISTINCT value) FROM tagxref WHERE tagid=8) AS `stats_branches`,
     (SELECT count(tagname) FROM tag WHERE tagname LIKE ''sym-%'') AS `stats_tags`,
     (SELECT count(tagname) FROM tag WHERE tagname REGEXP ''^sym[-a-z0-9_.]+\d+\.\d+'') AS `stats_releases`
   } {} }
}

#-- Language/Content statistics (outputs colored bar graph)
proc ui::lang_stats {} {
   # fetch $lang(js/...), $lang_color(js), $lang_list, $total_size
   query {SELECT name, value FROM fx_stats ORDER by VALUE DESC} {
      set $name $value
   }
   # output color bar for language proportions
   #html "<div class=language-bar style=''width:100%; height:3pt; box-sizing:border-box;''>"
   foreach name $lang_list {
      set percent "[expr $lang($name)*100]%"
      html "<span class=code-rate-$name style=''height:100%; width:$percent; display:inline-block; background-color:#$lang_color($name)'' title=''$percent $name''></span>";
   }
   #html "</div>";
}

#-- print two table rows for last commit
proc ui::last_commit {} {
   query {
       SELECT *, CAST(julianday(''now'')-mtime AS INT) AS age, substr(comment,0,199) AS msg, substr(uuid, 0, 10) AS short_uuid
       FROM event JOIN blob ON blob.rid=event.objid
       WHERE type=''ci'' ORDER BY mtime DESC LIMIT 1
   } {
      html "  <tr><th colspan=3>$msg</th></tr>";
      html "  <tr><th colspan=3 style=background:#fff><a href=''timeline?u=$user'' class=user>$user</a> authored $age days ago
            <span style=float:right>last checkin <a href=''ci/$uuid''>$short_uuid <span class=glyph>&#x2398;</span></a></span></th></tr>";
   }
}

#-- outputs table rows containing top-level filenames and recent checkin comments
proc ui::recent_files {dirname} {
   set seen "(.gitignore)"
   
   # search files using directory as base path
   set branch "trunk"
   set cutname 0
   set AND_DIR ""
   if {[string length $dirname]} {
       set dirname "$dirname/"
       set cutname [string length $dirname]
       set AND_DIR " AND substr(name, 0, \$cutname+1) = \$dirname "
   }

   # files   // vcache.rid=mlink.fid would be easier to skip deleted files, but access is prohibited(?)
   query "
       SELECT DISTINCT
          m.fnid,
          INSTR(SUBSTR(name,\$cutname+1),''/'')>0  AS  is_dir,
          name                                AS  pathname,
          bf.rid   AS  fn_rid,     bf.uuid    AS  fn_uuid,
          bm.rid   AS  ci_rid,     bm.uuid    AS  ci_uuid,
          SUBSTR(comment, 0, 70)              AS  comment,
          CAST(JULIANDAY(''now'')-e.mtime AS INT) AS  age
       FROM
          filename
          LEFT JOIN mlink m ON m.fnid = filename.fnid
          LEFT JOIN tagxref ON m.mid = tagxref.rid
          LEFT JOIN blob bf ON bf.rid = m.fid
          LEFT JOIN blob bm ON bm.rid = m.mid
          LEFT JOIN event e ON e.objid = m.mid
       WHERE
          --tagxref.value = \$branch  AND
          m.fnid NOT IN (SELECT fnid FROM mlink m LEFT JOIN tagxref x ON m.mid=x.rid WHERE fid=0 AND x.value=\$branch)
          $AND_DIR
       GROUP BY
          name
       ORDER BY
          is_dir DESC, name ASC, e.mtime DESC
   " {

      # separate directories and files
      set name [string range $pathname $cutname 2048]
      set dir [string first "/" $name]
      if {$dir>0} { set name [string range $name 0 [expr $dir-1]] }

      # skip seen files
      if [str::contains "($name)" $seen] { continue } else { set seen "($name),$seen" }
      
      # output table entries
      html "               <tr><td>";
      if {$dir>0} {
          # if there is an equivalent wiki page for a directory, then we mix filebox + wiki
          set display "wiki"
          #set display [?: [sql::page_exists "$dirname$name"] "wiki" "tree"]
          html "<a class=dir href=''$display?name=[htmlize $dirname$name]''><b class=glyph>๐Ÿ“‚</b> [htmlize $name]</a>";
      } else {
          html "<a class=file href=''artifact/$fn_uuid''><b class=glyph>๐Ÿ“„</b> [htmlize $name]</a>";
      }
      html "</td> <td>[htmlize $comment]<a href=''ci/$ci_uuid''>โ€นโ€บ</a></td> <td>[htmlize $age] days ago</td></tr>\n";
   }
}

#-- social media share links
proc ui::social_links {baseurl} {
  html "
   <a class=sml-go href=''https://plus.google.com/share?url=$baseurl'' title=google+>g+</a> &middot;
   <a class=sml-fb href=''https://www.facebook.com/sharer/sharer.php?u=$baseurl'' title=facebook>fb</a> &middot;
   <a class=sml-tw href=''https://twitter.com/intent/tweet?url=$baseurl'' title=twitter>tw</a> &middot;
   <a class=sml-rd href=''http://reddit.com/submit?url=$baseurl'' title=reddit>rd</a> &middot;
   <a class=sml-in href=''https://www.linkedin.com/shareArticle?mini=true&amp;url=$baseurl'' title=linkedin>in</a> &middot;
   <a class=sml-su href=''https://www.stumbleupon.com/submit?url=$baseurl'' title=stumbleupon>su</a> &middot;
   <a class=sml-dl href=''https://del.icio.us/post?url=$baseurl'' title=delicious>dl</a>
  ";
}


# Outputs a textual /changelog
proc webpage_changelog {} {
  html "<!-- NEWS-style timeline --> <meta http-equiv=\"Content-Type\" content=\"text/plain\"> <pre>\n\n";
  set version "trunk"
  puts "$version (unreleased)\n";
  query {
     SELECT event.mtime, tag.tagname, MAX(tag.tagid), DATE(event.mtime) AS d,
            REPLACE(TRIM(REPLACE(event.comment, char(10,10), char(10)), char(8,10,13,32)), char(10), char(10,32,32,32)) AS comment
     FROM event
      LEFT JOIN tagxref ON event.objid=tagxref.rid
      LEFT JOIN tag ON tagxref.tagid=tag.tagid
     WHERE type=''ci''
     GROUP BY objid
     ORDER BY event.mtime DESC
     LIMIT 750
  } {
     if {[regexp {^sym-.*\d+\.\d+} $tagname]} {
        for {} {[string length $tagname] >= 3 && [regexp {^\d+\.} $tagname] == 0} {} {
          set tagname [string range $tagname 1 100]
        }
        puts "\n$tagname ($d)\n";
     }
     puts " * $comment\n";
  }
  puts "\n\n";
}

# Alternative to /raw trunk file access without ?name=uuid,
# Doesn''t work with CONTENT() yet.
proc webpage_cat {} {
  set name [getParameter name ""]
  if {![string length $name]} { puts "No filename given."; break; }
  query {
     SELECT uuid
     FROM blob LEFT JOIN mlink ON blob.rid=mlink.fid
               LEFT JOIN filename ON mlink.fnid=filename.fnid
     WHERE name = $name
     ORDER BY rid DESC LIMIT 1
  } { html [artifact "$uuid"]; }
}

# Generate a text/uri-list for available files
proc webpage_uri-list {} {
  query {
     SELECT filename.name, uuid
     FROM blob LEFT JOIN mlink ON blob.rid=mlink.fid LEFT JOIN filename ON mlink.fnid=filename.fnid
     GROUP BY filename.name  ORDER BY rid DESC
  } { puts "$name?name=$uuid\n" }
}

# Invokes web request page procs
proc webpage_hook {} {
  #if {! [anycap ro]} { break }
  catch { "webpage_$::web_name"; return -code 2 found; } rc
  if {"$rc" eq "found"} { break continue }
}


#-- Whitelist for SQL params
# Just realized this is redundant; because query {} accepts
# uninterpolated \$varnames as parameter placeholders.
proc sql::allowed {str} {
   return [regexp {^[-a-zA-Z0-9 !$&/(){}=<>,.;:_+#*@]+$} $str]
}
#-- Also prohibit regex special chars
proc sql::allowed_regexp {str} {
   return [regexp {^[-a-zA-Z0-9 !$&/    =<>,.;:_ # @]+$} $str]
}


#-- Check for existence of wiki page
proc sql::page_exists {name} {
   query {SELECT 1 FROM tag WHERE tagname = (''wiki-'' || $name)} { return 1 }
   return 0
}


#-- Check if exact file name (including path) exists in repository
proc sql::file_exists {name} {
   query {SELECT 1 FROM filename WHERE name = $name} { return 1 }
   return 0
}


#-- Find file by basename
proc sql::find_file {path} {
   if {![sql::allowed_regexp $path]} { return 0 }
   query {SELECT name FROM filename WHERE name REGEXP (''(^|/)'' || $path || ''\$'')} { return $name }
   return ""
}


#-- Check if directory exists
proc sql::dir_exists {path} {
   if {![sql::allowed_regexp $path]} { return 0 }
   query {SELECT name FROM filename WHERE name REGEXP (''^'' || $path || ''/.+'')} { return 1 }
   return 0
}

   
#-- returns true if string contained in another string
proc str::contains {needle haystack} {
   return [expr {-1 != [string first $needle $haystack]}]
}

#-- wrapper for [string first ...] to support startindex
proc str::next {search content start} {
   # cut out $content at $start before searching
   set p [string first $search [string range $content $start [string length $content]]]
   if [expr $p>=0] {
      set p [expr $start+$p]
   }
   return $p
}

#-- enclose string in e.g. html tags
proc str::wrap {content search before after} {
   set len [string length $search]
   set p 0
   while {[expr [set p [str::next $search $content $p]]>=0]} {
      set content "[string range $content 0 [expr $p-1]]$before$search$after[string range $content [expr $p+$len] 2000]";
      set p [expr $p+[string length "$before+$search+$after"]]; # skip a little further
   }
   return $content
}

#-- Split string into list on delimiter character
# (basically just turns delimiter into space)
#
proc str::explode {delim str} {
   set r ""
   set len [string length $str]
   while {-1 != [set p [string first $delim $str]]} {
      set r "$r [string range $str 0 [expr $p-1]]"
      set str [string range $str [++ p] $len]
   }
   return [list [string trim "$r $str"]]
}   

#-- Extract dirname from path/file/name
proc str::dirname {path} {
   return [string range $path 0 [expr [string last "/" $path]-1]]
}

'