Collection of themes/skins for the Fossil SCM

⌈⌋ branch:  Fossil Skins Extra


Artifact [c1e14fa349]

Artifact c1e14fa3499c9c6ef37cc6efea1e96b82abcf222:

  • File so-skin.txt — part of check-in [add39a01c8] at 2014-03-17 15:03:42 on branch trunk — Stackoverflow-skin with updated TH1X, slim #language-bar positioning (user: mario size: 21102)

# The "skin" configuration exported from
# repository "/home/mario/fossil.d/canonic_autoloader.fossil"
# on 2014-03-17 15:00:05
config /config 7895
1395068394 'css' value '/* General settings for the entire page */
body {
  margin: 0ex 1ex;
  padding: 0px;
  background-color: white;
  font-family: sans-serif;
}

/* The project logo in the upper left-hand corner of each page */
#logo {
  text-align: center;
  vertical-align: bottom;
  font-weight: bold;
  color: #f90;
  padding: 5px 20px 0px 0px;
}

/* The page title centered at the top of each page */
.title {
  font-size: 2em;
  font-weight: 200;
  color: #333;
  vertical-align: bottom;
  width: 100% ;
  position: relative; top:30px;
}
.title b { font-weight: 800; }

/* The login status message in the top right-hand corner */
.status {
  display: table-cell;
  text-align: right;
  vertical-align: bottom;
  color: #ff9900;
  font-size: 0.8em;
  font-weight: bold;
  min-width: 200px;
  white-space: nowrap;
}

/* The header across the top of the page */
header, .header {
  display: table;
  width: 100% ;
}

/* The main menu bar that appears at the top of the page beneath
** the header */
nav, .mainmenu {
  padding: 5px 10px 5px 10px;
  font-size: 0.9em;
  font-weight: bold;
  text-align: center;
  letter-spacing: 1px;
}

/* The submenu bar that *sometimes* appears below the main menu */
.submenu, .sectionmenu {
  padding: 3px 10px 3px 0px;
  font-size: 0.9em;
  text-align: center;
  background-color: #ff9900;
  color: white;
}
.mainmenu a, .mainmenu a:visited, .submenu a, .submenu a:visited,
.sectionmenu>a.button:link, .sectionmenu>a.button:visited {
  padding: 3px 10px 3px 10px;
  color: white;
  text-decoration: none;
}
.mainmenu a:hover, .submenu a:hover, .sectionmenu>a.button:hover {
  color: #ff9900;
  background-color: white;
}

/* All page content from the bottom of the menu or submenu down to
** the footer */
.content {
  padding: 0ex 1ex 0ex 2ex;
}

/* Some pages have section dividers */
div.section {
  margin-bottom: 0px;
  margin-top: 1em;
  padding: 1px 1px 1px 1px;
  font-size: 1.2em;
  font-weight: bold;
  background-color: #ff9900;
  color: white;
  white-space: nowrap;
}

/* The "Date" that occurs on the left hand side of timelines */
.divider {
  border-radius: 4px;
  color: #750;
  background: #ffeecc;
  border: 2px #f90 solid;
  font-size: 1em; font-weight: normal;
  padding: .25em;
  margin: .2em 0 .2em 0;
  float: left;
  clear: left;
  white-space: nowrap;
}

/* The footer at the very bottom of the page */
footer, .footer {
  clear: both;
}

/* verbatim blocks */
pre.verbatim {
   background-color: #f5f5f5;
   padding: 0.5em;
}

/* The label/value pairs on (for example) the ci page */
table.label-value th {
  vertical-align: top;
  text-align: right;
  padding: 0.2ex 2ex;
}

/* Side-by-side diff */
table.sbsdiff {
  background-color: white;
  font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
  font-size: 8pt;
  border-collapse:collapse;
  white-space: pre;
  width: 98%;
  border: 1px #000 dashed;
  margin-left: auto;
  margin-right: auto;
}

table.sbsdiff th.diffhdr {
  border-bottom: dotted;
  border-width: 1px;
}

table.sbsdiff tr td {
  white-space: pre;
  padding-left: 3px;
  padding-right: 3px;
  margin: 0px;
  vertical-align: top;
}

table.sbsdiff tr td.lineno {
  text-align: right;
}

table.sbsdiff tr td.srcline {
}

table.sbsdiff tr td.meta {
  background-color: rgb(170, 160, 255);
  text-align: center;
}

table.sbsdiff tr td.added {
  background-color: rgb(180, 250, 180);
}
table.sbsdiff tr td.addedvoid {
  background-color: rgb(190, 190, 180);
}

table.sbsdiff tr td.removed {
  background-color: rgb(250, 130, 130);
}
table.sbsdiff tr td.removedvoid {
  background-color: rgb(190, 190, 180);
}

table.sbsdiff tr td.changed {
  background-color: rgb(210, 210, 200);
}
table.sbsdiff tr td.changedvoid {
  background-color: rgb(190, 190, 180);
}

/*********************************
 *
 *  SO lookalike.
 *
 */
html {
}
body {
   padding: 0;
   margin: 0;
}
body, p, article, td {
   font-family: Arial;
   font-size: 14px;
   color: #111;
}
p, article {
   line-height: 175%;
}
li {
   margin-bottom: 5pt;
}
.container {
   margin-left: auto;
   margin-right: auto;
   width: 960px;
   clear: both;
}
a {
   text-decoration: none;
   font-weight: 700;
   color: #07c;
}
a:visited {
   color: #468;
}
.header {
   background: #eee;
   color: #999;
   padding: 2px 0;
}
#title {
   float: left;
   color: #666;
   font-weight: 700;
   margin: 3px;
}

.badge1 { color: #fd5; }
.badge2 { color: #ccc; }
.badge3 { color: #c96; }

#logo {
   height: 100px;
}
#navlist {
   padding-top: 55px;
}
#navlist a { 
   color: #ffffff;
   font-weight: 900;
   font-size: 130%;
   padding: 4px 15px;
   background: #777;
}
#navlist a.current, #navlist a:hover { 
   background: #f90;
}
#navlist a.last { 
   margin-left: 50px;;
}


.mainbar {
   width: 730px;
   float: left;
   padding-top: 10px;
}

.sidebar {
   width: 220px;
   float: right;
}


.topic {
    padding: 22pt 0 15pt 0;
    border-bottom: 1px solid #999;
}
.topic tt, .topic kbd {
   font-size: 80%;
   color: #555;
   background: #f7f7f7;
   margin-left: 30px;
   display: block;
}
.topic-options a, .topic-options {
   color: #aaa;
   font-weight: 100;
   padding-top: 30px;
}
ul.topic-list {
   margin-top: 20pt;
   padding: 0;
}


#helpbox {
   background: #fec;
   border-radius: 5px;
   padding: 10px;
   margin-top: 15px;
   color: #750;
}
#helpbox h4 {
   color: #a00;
   font-size: 140%;
   margin: 0;
}

.tag {
   padding:2px 5px;
   background:#e1e9f1;
   color:#58a;
   border:1px outset #cde;
}


input[type=submit] {
   border: 1px solid black;
   padding: 3px 10px;
   font: normal normal bold 140% "Trebuchet MS", sans-serif;
}
textarea {
   border: 1px solid #999;
   border-bottom: 7px solid #ddd;
}


#footer-spacer {
   clear: both;
   padding-top: 130px;
}
footer {
   clear: both;
   background: #555;
   padding: 65px 275px;
   border-top: 9px solid black;
   color: #eee;
}
footer p {
   color: #999;
}
footer a, footer a:visited {
   color: #fec;
}
footer ul {
   list-style-type: square;
   padding: 0;
}
footer li {
   display: inline;
   padding-right: 10pt;
}
footer li:before {
   color: #f73;
   content: "⬛  ";
}
footer li:nth-child(even):before {
   color: #fc9;
}
footer li:nth-child(3):before {
   color: #57f;
}
footer li:nth-child(4):before {
   color: #6f7;
}


#main {
   padding-top: 30px;
   min-height: 700px;
}
#main .content {
   padding-top: 15pt;
   padding-bottom: 50pt;
}
h2.subtitle {
   font-size: 24px;
   font-weight: 700;   
   display: inline;
   border-bottom: 1px solid #999;
}
.submenu { 
   display: inline;
   background: white;
   border: 1px #fff solid;
   border-bottom: 1px #999 solid;
}
.submenu a, .submenu a:visited {
  font-size: 18px;
  font-weight: 200;
  color: black;
}
.submenu a:hover {
   border: 1px #666 solid;
   border-bottom: white;
}

code {
   background: #eee;
}
pre {
   background: #f3f3f3;
   padding: 3pt;
}

ul {
   margin-top: 3pt;
}

.search-result {
   padding: 5pt;
}
.search-result a {
   font-size: 140%;
   font-weight: 300;
}
.search-link a {
   font-weight: 100;
   font-size: 60%;
   color: #337733;
}
.search-excerpt {
   color: #333;
}
.search-headline {
   font-size: 300%;
   color: #999;
   letter-spacing: 5pt;
}
.search-excerpt mark {
   text-decoration: none;
   font-style: italic;
   font-weight: 700;
   color: #aa3322;
}

#language-bar {
   clear: both;
   position: relative;
   top: -2px;
   width: 100%;
   height: 5px;
   box-sizing: border-box;
   background: #eef;
}
'
config /config 2990
1395068013 'header' value '<html>
<head>
<base href="$baseurl/$current_page" />
<title>$<project_name>: $<title></title>
  <link rel=alternate type="application/rss+xml" title=Timeline href="$home/timeline.rss">
  <link rel=stylesheet href="$home/style.css?default" type="text/css" media="screen">
<link rel="stylesheet" href="https://google-code-prettify.googlecode.com/svn/loader/prettify.css" type="text/css" media="screen">
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="//google-code-prettify.googlecode.com/svn/loader/run_prettify.js?autoload=true"></script>
</head>
<body onLoad="$(''code,pre'').addClass(''prettyprint'');">

<header class=header>
<div class=container>
   <a href="." id="title">𝌔 Fossil Hub</a>
   <div style="float:right">
      <th1>
          if {[info exists login]} {
               html "✉ <a href=''$home/login'' class=''profile-link''>$login</a> 751"
               html "<span class=badge1>✹</span>1  <span class=badge3>✹</span>5 | "
               html "<a href=''$home/login''>Logout</a> | "
          } else {
                html "<a href=''$home/login''>Login</a> | "
                  }
          if {[hascap s]} {
                html "<a href=''$home/setup''>Admin</a> | "
          } elseif {[hascap a]} {
                html "<a href=''$home/setup_ulist''>Users</a> | "
          }
      </th1>
      <a href="//fossil-scm.org">Fossil manual</a> |
      <form id=google style="display:inline" action="wiki">
         <input name="name" size="28" value="" placeholder="search wiki+files">
      </form>
      <!--form id=google style="display:inline" action="http://google.com/search">
         <input name="q" size="28" value="" placeholder="search">
         <input type="hidden" name="as_sitesearch" value="$baseurl">
      </form-->
   </div>
</div>
</header>

<div id=language-bar><th1>catch { ui::lang_stats } </th1></div>

<div class="container logo-title-nav">
    <a href="$home"><img id=logo src="$home/logo" alt="$<title>" align=left></a>
    <span class=title>$<project_name></span>

    <nav id=navlist class=mainmenu>
    <th1>
         if {[anycap jor]} {
           html "<a href=''$home/timeline''>Timeline</a>\n"
         }
         if {[hascap oh]} {
           html "<a class=current href=''$home/tree?ci=tip''>Files</a>\n"
         }
         if {[hascap o]} {
           html "<a href=''$home/brlist''>Branches</a>\n"
           html "<a href=''$home/taglist''>Tags</a>\n"
         }
         if {[hascap r]} {
           html "<a href=''$home/rptview?rn=1''>Tickets</a>\n"
         }
         if {[hascap j]} {
           html "<a class=last href=''$home/wiki''>Wiki</a>\n"
           html "<a href=''$home/timeline?y=w''>RC</a>\n"
         }
    </th1>
    </nav>
</div>

<div class=container id=main>
   <h2 class=subtitle><b>$<title></b></h2>

<th1>
  catch {ui::search_on_wiki}
</th1>'
config /config 909
1392538406 'footer' value '</div><!--container-->

   <footer class=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 href=wiki>wiki</a>
       | <a href=login>account</a>
       | <a href=setup>admin</a>
       | <a href=home>home</a>
      </p>
      <ul>
         <li><a href=tree?ci=tip>Files</a>
         <li><a href=zip/trunk.zip?uuid=trunk>ZIP</a>
         <li><a href=tarball/trunk.zip?uuid=trunk>TGZ</a>
         <li><a href=timeline>Timeline</a>
         <li><a href=brlist>Branches</a>
         <li><a href=taglist>Tags</a>
         <li><a href=reportlist>Tickets</a>
         <li><a href=reports>Reports</a>
      </ul>
      <p>  
      Skin inspired by <a href=//s.tk/so>Stackoverflow</a>
      </p>
   </footer>

</body>
</html>
'
config /config 9097
1395068051 'th1-setup' value '


# -------------------------------------------------------------------
# Control structures
#
#   ++ varname
#   ?: [if] {then} {else}
#   isset varname
#   eq str1 str2
#
#   while {cond} {code}
#   foreach varname {list} {code}
#   switch value { val1 {code1} val2 {code2} ... }
#



#-- 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 list
#
# 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]]];
      }
   }
}






# -------------------------------------------------------------------
# String functions
#
#   str::contains needle haystack
#   str::next needle haystack startindex
#   str::wrap haystack needle addbefore addafter
#


   
#-- 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"]]
}   






# -------------------------------------------------------------------
# User Interface utility code
#
#   sql::allowed safe_string
#   ui::page_exists WikiPage
#   ui::file_exists file.name
#
#   ui::search terms baseurl
#   ui::search_on_wiki   
#   ui::stats
#   ui::lang_stats
#



#-- Whitelist permissible characters for SQL context
# * A workaround for the lack of SQL escaping here (or the new query API branch)
# * Used in LIKE context, so ? and % are not allowed
# * And '' and \ or " not included in the whitelist for obvious reasons.
proc sql::allowed {str} {
   return [regexp {^[a-zA-Z0-9 !$&/(){}=<>,.;:-_+#*@]+$} $str]
}


#-- Search function
# * Requires fossil-search.php to build the according table
#   (reading from the raw blobs is impossible) as cronjob
# * And a patched `fossil` binary src/report.c to allow
#   SELECTs on the `search` table.
proc ui::search {terms baseurl} {

   # cleanup $terms
   if [sql::allowed $terms] {
   
      # prepare search query
      set WHERE "1"
      foreach search $terms {
         set WHERE "$WHERE AND content LIKE ''%$search%''"
      }

      # perform search
      query "SELECT path, type, name, content FROM fx_search WHERE $WHERE" {

         # conent excerpt and highlighting      
         set p [string first $terms $content]
         set excerpt [string range $content [expr $p-50] [expr $p+450]]
         foreach search $terms {
            set excerpt [str::wrap "$excerpt" $search <mark> </mark>]
         }
      
         # format result list
         html "
         <div class=search-result>
            <b>[htmlize $type]</b>
            <a href=[htmlize $path]>[htmlize $name]</a> <br>
            <span class=search-link><a href=[htmlize $path]>$baseurl/[htmlize $path]</a></span> <br>
            <small class=search-excerpt>$excerpt</small>
         </div>\n";
      }
   }
}


#-- Check for existence of wiki page
proc ui::page_exists {name} {
   if [sql::allowed $name] {
      query "SELECT 1 FROM tag WHERE tagname = ''wiki-$name''" {
         return 1
      }
   }
   return 0
}


#-- Check if file exists in repository
proc ui::file_exists {name} {
   if [sql::allowed $name] {
      query "SELECT 1 FROM filename WHERE name = ''$name''" {
         return 1
      }
   }
   return 0
}


#-- Call ui::search on non-existant wiki pages
# * We can get $<title> as search terms
#   (no way to access query string parameters otherwise)
# * But this is also convenient, as it doubles as wiki page search
proc ui::search_on_wiki {} {
   upvar 1  title title  baseurl baseurl  current_page current_page
   if [expr {[regexp {^wiki[?]name=} $current_page] && ! [ui::page_exists $title]}] {
      html "<h2 class=search-headline>Search</h2>";
      ui::search $title $baseurl
      html "<br><br><br><br>"
   }
}


#-- 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[\\-\\w_.]+\\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</a></span></th></tr>";
   }
}


#-- outputs table rows containing top-level filenames and recent checkin comments
proc ui::recent_files {} {
   set seen ""

   # files
   query {
       SELECT DISTINCT
             instr(name, ''/'') as dir,
             (CASE instr(name,''/'') WHEN 0 THEN name
              ELSE substr(name,0,instr(name, ''/'')) END) AS name,
             substr(comment, 0, 70) AS comment,
             uuid,
             CAST(julianday(''now'')-mtime AS INT) AS age
       FROM filename
       JOIN mlink ON filename.fnid=mlink.fnid
       JOIN event ON mlink.mid=event.objid
       JOIN blob ON blob.rid=event.objid
       GROUP BY name ORDER BY dir DESC, mtime DESC
   } {
      if {$dir && [str::contains $name $seen]} { continue } else { set seen "$name,$seen" }
      html "               <tr><td>";
      if {$dir > 0} {
          html "<a class=dir href=''tree?name=[htmlize $name]''><b class=glyph>📂</b> [htmlize $name]</a>";
      } else {
          html "<a class=file href=''ci/$uuid?sbs=0''><b class=glyph>📄</b> [htmlize $name]</a>";
      }
      html "</td> <td>[htmlize $comment]</td> <td>[htmlize $age] days ago</td></tr>\n";
   }
}'