# 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 </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'')">★ 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>✄ 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><></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">โฑ Download ZIP</a>
<a class="download button white text-shadow" href="$<baseurl>/tarball/$<project_name>.tgz?uuid=trunk">โฑ 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>
<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>⎘</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> ·
<a class=sml-fb href=''https://www.facebook.com/sharer/sharer.php?u=$baseurl'' title=facebook>fb</a> ·
<a class=sml-tw href=''https://twitter.com/intent/tweet?url=$baseurl'' title=twitter>tw</a> ·
<a class=sml-rd href=''http://reddit.com/submit?url=$baseurl'' title=reddit>rd</a> ·
<a class=sml-in href=''https://www.linkedin.com/shareArticle?mini=true&url=$baseurl'' title=linkedin>in</a> ·
<a class=sml-su href=''https://www.stumbleupon.com/submit?url=$baseurl'' title=stumbleupon>su</a> ·
<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]]
}
'