# 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 ' $<project_name>: $<title>
𝌔 Fossil Hub
if {[info exists login]} { html "✉ $login 751" html "1 5 | " html "Logout | " } else { html "Login | " } if {[hascap s]} { html "Admin | " } elseif {[hascap a]} { html "Users | " } Fossil manual |
catch { ui::lang_stats }
$

$</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"; } }'