Moved css to separate file + Wrote minimal issue tracker for ncdc
I designed the issue tracker to be a separate module for use in TUWF websites, and perhaps even as standalone. But for now it's just a small file in this repository.
This commit is contained in:
parent
ec64b3fa9d
commit
605b0643d3
3 changed files with 447 additions and 69 deletions
282
Issue.pm
Normal file
282
Issue.pm
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
|
||||
=head1 SQL Schema
|
||||
|
||||
CREATE TABLE ${p}issues (
|
||||
issue SERIAL PRIMARY KEY,
|
||||
latest integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE ${p}messages (
|
||||
id SERIAL PRIMARY KEY,
|
||||
issue integer NOT NULL,
|
||||
date timestamptz NOT NULL DEFAULT NOW(),
|
||||
summary varchar(200) NOT NULL,
|
||||
type varchar NOT NULL DEFAULT '',
|
||||
status varchar NOT NULL DEFAULT '',
|
||||
closed boolean NOT NULL DEFAULT false,
|
||||
email varchar NOT NULL DEFAULT '',
|
||||
message varchar NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
# TODO: Atom feed?
|
||||
|
||||
package TUWF::Issue;
|
||||
|
||||
use TUWF ':html', 'html_escape';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
return bless {
|
||||
prefix => 'issue_',
|
||||
types => [qw|bug feature docs other|],
|
||||
default_type => 'other',
|
||||
statusses => [qw|new accepted duplicate confirmed fixed wontfix worksforme|],
|
||||
default_status => 'new',
|
||||
admins => ['code1', 'code2'],
|
||||
@_
|
||||
}, $class;
|
||||
}
|
||||
|
||||
|
||||
sub dbListing {
|
||||
my $self = shift;
|
||||
my %o = (
|
||||
results => 100,
|
||||
page => 1,
|
||||
);
|
||||
$o{reverse} = 1 if !$o{sort};
|
||||
|
||||
my %where = (
|
||||
$o{id} ? ('i.issue = ?' => $o{id}) : (),
|
||||
);
|
||||
|
||||
my $order = sprintf {
|
||||
date => 'm.id %s',
|
||||
}->{$o{sort}||'date'}, $o{reverse} ? 'DESC' : 'ASC';
|
||||
|
||||
my($r, $np) = $TUWF::OBJ->dbPage(\%o, q{
|
||||
SELECT i.issue, m.summary, to_char(m.date, 'YYYY-MM-DD') AS date, m.type, m.status, m.closed
|
||||
FROM !sissues i
|
||||
JOIN !smessages m ON m.id = i.latest
|
||||
!W
|
||||
ORDER BY !s}, $self->{prefix}, $self->{prefix}, \%where, $order
|
||||
);
|
||||
return wantarray ? ($r, $np) : $r;
|
||||
}
|
||||
|
||||
|
||||
sub dbItem {
|
||||
my($self, $id) = @_;
|
||||
return $TUWF::OBJ->dbAll(q{
|
||||
SELECT m.issue, m.summary, to_char(m.date, 'YYYY-MM-DD HH24:MI:SS (tz)') AS date, m.type, m.status, m.closed, m.message
|
||||
FROM !smessages m
|
||||
WHERE m.issue = ?
|
||||
ORDER BY m.id}, $self->{prefix}, $id
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
sub dbEmails {
|
||||
my($self, $id) = @_;
|
||||
return [ map $_->{email}, @{$TUWF::OBJ->dbAll(q|SELECT DISTINCT m.email FROM !smessages m WHERE m.issue = ? AND m.email <> ''|, $self->{prefix}, $id)} ];
|
||||
}
|
||||
|
||||
|
||||
sub dbSave {
|
||||
my($self, $id, $closed, @a) = @_;
|
||||
$id = $TUWF::OBJ->dbRow('INSERT INTO !sissues (latest) VALUES (0) RETURNING issue', $self->{prefix})->{issue} if !$id;
|
||||
my $latest = $TUWF::OBJ->dbRow(
|
||||
'INSERT INTO !smessages (issue, closed, summary, email, type, status, message) VALUES (?, ?, !l) RETURNING id',
|
||||
$self->{prefix}, $id, $closed?1:0, \@a
|
||||
)->{id};
|
||||
$TUWF::OBJ->dbExec('UPDATE !sissues SET latest = ? WHERE issue = ?', $self->{prefix}, $latest, $id);
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
||||
# TODO: pagination / filtering
|
||||
sub htmlListing {
|
||||
my($s, $l, $lnk) = @_;
|
||||
table class => 'issue_listing';
|
||||
thead; Tr;
|
||||
td class => 'issue_col_id', 'Id';
|
||||
td class => 'issue_col_type', 'Type';
|
||||
td class => 'issue_col_status', 'Status';
|
||||
td class => 'issue_col_date', 'Updated';
|
||||
td class => 'issue_col_summary','Summary';
|
||||
end; end;
|
||||
for(@$l) {
|
||||
Tr $_->{closed} ? (class => 'issue_closed') : ();
|
||||
td class => 'issue_col_id', $_->{issue};
|
||||
td class => 'issue_col_type', $_->{type};
|
||||
td class => 'issue_col_status', $_->{status};
|
||||
td class => 'issue_col_date', $_->{date};
|
||||
td class => 'issue_col_summary';
|
||||
a href => $lnk->($_->{issue}), $_->{summary};
|
||||
end;
|
||||
end;
|
||||
}
|
||||
end;
|
||||
}
|
||||
|
||||
|
||||
sub htmlItem {
|
||||
my($s, $d) = @_;
|
||||
my $last = $d->[$#$d];
|
||||
dl class => 'issue_status';
|
||||
dt 'Id'; dd $last->{issue};
|
||||
dt 'Messages'; dd $#$d+1;
|
||||
dt 'Type'; dd $last->{type};
|
||||
dt 'Status'; dd $last->{status};
|
||||
end;
|
||||
div class => 'issue_item';
|
||||
my $num = -1;
|
||||
for my $m (@$d) {
|
||||
div class => 'issue_message';
|
||||
h1 !++$num ? 'Description' : "Reply $num";
|
||||
dl;
|
||||
dt !$num ? 'Created' : 'Added'; dd $m->{date};
|
||||
for($num ? qw|summary type status| : ()) {
|
||||
if($m->{$_} ne $d->[$num-1]{$_}) {
|
||||
dt "\u$_";
|
||||
dd sprintf '"%s" to "%s"', $d->[$num-1]{$_}, $m->{$_};
|
||||
}
|
||||
}
|
||||
if($num && !$m->{closed} != !$d->[$num-1]{$_}) {
|
||||
dt "Closed";
|
||||
dd sprintf '"%s" to "%s"', $d->[$num-1]{closed}?'yes':'no', $m->{closed}?'yes':'no';
|
||||
}
|
||||
end;
|
||||
# TODO: link formatting and some way to link to other issues
|
||||
p; lit html_escape $m->{message}; end;
|
||||
end;
|
||||
}
|
||||
end;
|
||||
}
|
||||
|
||||
|
||||
sub htmlForm {
|
||||
my($s, $l, $url) = @_;
|
||||
# TODO: anti-spam JS
|
||||
form class => 'issue_frm', action => $url, method => 'post';
|
||||
fieldset;
|
||||
input type => 'hidden', name => 'issue_id', value => $l ? $l->{issue} : 0;
|
||||
legend $l ? 'Reply' : 'Report a new issue';
|
||||
ul;
|
||||
li class => 'issue_frm_summary';
|
||||
label for => 'issue_summary', 'Summary';
|
||||
input type => 'text', name => 'issue_summary', id => 'issue_summary', size => 45, value => $l?$l->{summary}:'';
|
||||
end;
|
||||
li class => 'issue_frm_mail';
|
||||
label for => 'issue_email', 'Email';
|
||||
input type => 'text', name => 'issue_email', id => 'issue_email', size => 20;
|
||||
lit ' ';
|
||||
txt 'Optional, only used for notifications.';
|
||||
end;
|
||||
if($l) {
|
||||
li class => 'issue_frm_admin';
|
||||
label for => 'issue_type', 'Admin';
|
||||
Select name => 'issue_type';
|
||||
option value => $_, $_ eq $l->{type} ? (selected => 'selected') : (), $_ for @{$s->{types}};
|
||||
end;
|
||||
Select name => 'issue_status';
|
||||
option value => $_, $_ eq $l->{status} ? (selected => 'selected') : (), $_ for @{$s->{statusses}};
|
||||
end;
|
||||
Select name => 'issue_closed';
|
||||
option value => 0, !$l->{closed} ? (selected => 'selected') : (), 'Open';
|
||||
option value => 1, $l->{closed} ? (selected => 'selected') : (), 'Closed';
|
||||
end;
|
||||
input type => 'password', name => 'issue_code', id => 'issue_code', size => 10, value => 'code';
|
||||
end;
|
||||
} else {
|
||||
li class => 'issue_frm_type';
|
||||
label for => 'issue_type', 'Type';
|
||||
Select name => 'issue_type';
|
||||
option value => $_, $_ eq $s->{default_type} ? (selected => 'selected') : (), $_ for @{$s->{types}};
|
||||
end;
|
||||
end;
|
||||
}
|
||||
li class => 'issue_frm_message';
|
||||
textarea name => 'issue_message';end; br;
|
||||
lit 'Please use a <a href="http://p.blicky.net/">pastebin</a> if you want to include large chunks of code or program output.';
|
||||
end;
|
||||
li class => 'issue_frm_submit';
|
||||
input type => 'submit', value => 'Submit';
|
||||
end;
|
||||
end 'ul';
|
||||
end;
|
||||
end 'form';
|
||||
}
|
||||
|
||||
|
||||
sub handleForm {
|
||||
my($s, $url) = @_;
|
||||
my $f = $TUWF::OBJ->formValidate(
|
||||
{ post => 'issue_id', min => 0 },
|
||||
{ post => 'issue_summary', maxlength => 200, minlength => 2 },
|
||||
{ post => 'issue_email', required => 0, regex => qr/^[^@<>]+@[^@.<>]+(?:\.[^@.<>]+)+$/ },
|
||||
{ post => 'issue_code', required => 0, default => '' },
|
||||
{ post => 'issue_message', maxlength => 256*1024, minlength => 1 },
|
||||
);
|
||||
return($f, undef) if $f->{_err};
|
||||
|
||||
my $l;
|
||||
# Reply
|
||||
if($f->{issue_id} > 0) {
|
||||
$l = $s->dbListing(id => $f->{issue_id})->[0];
|
||||
push @{$f->{_err}}, ['issue_id', 'db_check', ''] and return($f, undef) if !$l;
|
||||
|
||||
# Check admin things
|
||||
if(grep $_ eq $f->{issue_code}, @{$s->{admins}}) {
|
||||
my $fa = $TUWF::OBJ->formValidate(
|
||||
{ post => 'issue_type', enum => $s->{types} },
|
||||
{ post => 'issue_status', enum => $s->{statusses} },
|
||||
{ post => 'issue_closed', enum => [0,1] },
|
||||
);
|
||||
$f = { %$f, %$fa };
|
||||
return($f, $l) if $f->{_err};
|
||||
} else {
|
||||
$f->{issue_type} = $l->{type};
|
||||
$f->{issue_status} = $l->{status};
|
||||
$f->{issue_closed} = $l->{closed};
|
||||
}
|
||||
|
||||
# New issue
|
||||
} else {
|
||||
$f->{issue_status} = $s->{default_status};
|
||||
$f->{issue_closed} = 0;
|
||||
my $fa = $TUWF::OBJ->formValidate({ post => 'issue_type', enum => $s->{types} });
|
||||
$f = { %$f, %$fa };
|
||||
return($f, $l) if $f->{_err};
|
||||
}
|
||||
|
||||
# No errors? Save!
|
||||
my $id = $s->dbSave(map $f->{"issue_$_"}, qw|id closed summary email type status message|);
|
||||
|
||||
# For replies, send out notification emails
|
||||
if($l) {
|
||||
my $mails = $s->dbEmails($id);
|
||||
my $u = $url->($id);
|
||||
for(grep $_ ne $f->{issue_email}, @$mails) {
|
||||
$TUWF::OBJ->mail(
|
||||
"Hello!\n\n".
|
||||
"A new reply has been posted to an issue you have previously shown\n".
|
||||
"an interest in. You can view the reply at the following URL:\n".
|
||||
" $u\n\n".
|
||||
"If you do not wish to receive any more notifications for this (and\n".
|
||||
"perhaps other) issues, please reply to this email stating your intent.",
|
||||
Subject => "Reply to $f->{issue_summary}",
|
||||
To => "$_",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$l = $s->dbListing(id => $id)->[0] if !$l;
|
||||
|
||||
return($f, $l);
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
147
index.cgi
147
index.cgi
|
|
@ -12,6 +12,7 @@ BEGIN { ($ROOT = abs_path $0) =~ s{index\.cgi$}{}; }
|
|||
|
||||
|
||||
my @changes = (
|
||||
[ '2012-03-17', '/ncdc/issue', 'Wrote a small issue tracker for ncdc' ],
|
||||
[ '2012-03-14', '/ncdc', 'ncdc 1.9 released.' ],
|
||||
[ '2012-02-15', '/doc/commvis', 'Added an article on my new communication system.' ],
|
||||
[ '2012-02-13', '/ncdc', 'ncdc 1.8 released.' ],
|
||||
|
|
@ -74,11 +75,16 @@ TUWF::register(
|
|||
qr{dump/grenamr} => sub { podpage(shift, 'dump-grenamr', 'dump', 'grenamr', 'GTK+ Mass File Renamer') },
|
||||
qr{dump/nccolour} => sub { podpage(shift, 'dump-nccolour', 'dump', 'nccolour', 'Colours in NCurses') },
|
||||
qr{feed\.atom} => \&atom,
|
||||
qr{(ncdc)/issue} => \&issue_list,
|
||||
qr{(ncdc)/issue/post} => \&issue_post,
|
||||
qr{(ncdc)/issue/new} => \&issue_new,
|
||||
qr{(ncdc)/issue/([1-9][0-9]*)} => \&issue_item,
|
||||
);
|
||||
|
||||
TUWF::set(
|
||||
logfile => '/www/err.log',
|
||||
error_404_handler => \¬found,
|
||||
mail_from => 'Yorhel\'s issue tracker <projects@yorhel.nl>',
|
||||
# this is a fairly static site, allow some aggressive caching
|
||||
pre_request_handler => sub { $_[0]->resHeader('Cache-Control', 's-max-age=86400, max-age=3600'); 1; },
|
||||
);
|
||||
|
|
@ -190,6 +196,76 @@ sub notfound {
|
|||
|
||||
|
||||
|
||||
|
||||
# Issue handling
|
||||
|
||||
sub _issue_init {
|
||||
require "$ROOT/Issue.pm";
|
||||
my($s, $p) = @_;
|
||||
$s->resHeader('Cache-Control', 'no-cache');
|
||||
$s->_load_module('TUWF::DB');
|
||||
$s->{_TUWF}{db_login} = [ undef, undef, undef ];
|
||||
$s->dbInit;
|
||||
return TUWF::Issue->new(prefix => $p.'_', admins => [ $ENV{ISSUE_CODE} ]);
|
||||
}
|
||||
|
||||
|
||||
sub issue_list {
|
||||
my($s, $p) = @_;
|
||||
my $is = _issue_init(@_);
|
||||
$s->htmlHeader(title => 'Ncdc Issue tracker', page => $p, sec => 'issue');
|
||||
br; a href => "/$p/issue/new", 'Report new issue'; br; br;
|
||||
$is->htmlListing((scalar $is->dbListing()), sub { "/$p/issue/".shift });
|
||||
br; a href => "/$p/issue/new", 'Report new issue'; br; br;
|
||||
$s->htmlFooter;
|
||||
}
|
||||
|
||||
|
||||
sub issue_new {
|
||||
my($s, $p) = @_;
|
||||
my $is = _issue_init(@_);
|
||||
$s->htmlHeader(title => 'Ncdc: Report new issue', page => $p, sec => 'issue');
|
||||
br; a href => "/$p/issue", 'Back to the issue index'; br; br;
|
||||
$is->htmlForm(undef, "/$p/issue/post");
|
||||
$s->htmlFooter;
|
||||
}
|
||||
|
||||
|
||||
sub issue_post {
|
||||
my($s, $p) = @_;
|
||||
return $s->resNotFound if $s->reqMethod() ne 'POST';
|
||||
my $is = _issue_init($s, $p);
|
||||
my($f, $l) = $is->handleForm(sub { "http://dev.yorhel.nl/$p/issue/".shift });
|
||||
|
||||
if($f->{_err}) {
|
||||
$s->htmlHeader(title => 'Error creating message', page => $p, sec => 'issue');
|
||||
p 'There was an error in the form. Please use the \'back\' button of your
|
||||
browser to go back to the form (hopefully) without losing your message.
|
||||
There was an error in the following fields: '.join(', ', map {(my$f=$_->[0])=~s/issue_// ;"\u$f"} @{$f->{_err}}).'.';
|
||||
return $s->htmlFooter;
|
||||
}
|
||||
|
||||
$s->resRedirect("/$p/issue/$l->{issue}", 'post');
|
||||
}
|
||||
|
||||
|
||||
sub issue_item {
|
||||
my($s, $p, $i) = @_;
|
||||
my $is = _issue_init($s, $p);
|
||||
my $item = $is->dbItem($i);
|
||||
return $s->resNotFound if !@$item;
|
||||
my $last = $item->[$#$item];
|
||||
$s->htmlHeader(title => 'Ncdc: '.$last->{summary}, page => $p, sec => 'issue');
|
||||
br; a href => "/$p/issue", 'Back to the issue index'; br; br;
|
||||
$is->htmlItem($item);
|
||||
$is->htmlForm($last, "/$p/issue/post") if !$last->{closed};
|
||||
br; a href => "/$p/issue", 'Back to the issue index'; br; br;
|
||||
$s->htmlFooter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
package TUWF::Object;
|
||||
use TUWF ':html';
|
||||
|
||||
|
|
@ -266,9 +342,7 @@ sub htmlHeader {
|
|||
my %o = (page => '', sec => '', sec2 => '', @_ );
|
||||
html;
|
||||
head;
|
||||
style type => 'text/css';
|
||||
$s->printCSS;
|
||||
end;
|
||||
Link rel => 'stylesheet', href => '/style.css', type => 'text/css', media => 'all';
|
||||
Link rel => 'alternate', type => 'application/atom+xml', href => '/feed.atom', title => 'Site updates';
|
||||
title $o{title};
|
||||
end;
|
||||
|
|
@ -337,6 +411,7 @@ sub htmlMenu {
|
|||
$m->('/ncdc/man', 'Manual', $o{sec} eq 'man');
|
||||
$m->('/ncdc/changes', 'Changelog', $o{sec} eq 'changes');
|
||||
$m->('/ncdc/scr', 'Screenshots', $o{sec} eq 'scr');
|
||||
$m->('/ncdc/issue', 'Issue tracker', $o{sec} eq 'issue');
|
||||
});
|
||||
$m->('/tuwf', 'Tuwf', $o{page} eq 'tuwf', sub {
|
||||
$m->('/tuwf', 'Info', !$o{sec});
|
||||
|
|
@ -361,69 +436,3 @@ sub htmlMenu {
|
|||
end;
|
||||
}
|
||||
|
||||
|
||||
sub printCSS {
|
||||
# font-face code from http://fonts.googleapis.com/css?family=Buenard:700,400
|
||||
lit <<' E;';
|
||||
@font-face {
|
||||
font-family: 'Buenard';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: local('Buenard Bold'), local('Buenard-Bold'), url('http://themes.googleusercontent.com/static/fonts/buenard/v2/8T0adwz_RAtKrxbccQmEFC3USBnSvpkopQaUR-2r7iU.ttf') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Buenard';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Buenard'), local('Buenard-Regular'), url('http://themes.googleusercontent.com/static/fonts/buenard/v2/UUYHasP8umGDjV-yeZf27Q.ttf') format('truetype');
|
||||
}
|
||||
html,body { background: #ccc; text-align: center; height: 100% }
|
||||
* { margin: 0; padding: 0; font: 15px 'Buenard',serif; color: #222 }
|
||||
#body { text-align: left; width: 800px; margin: 0 auto; background: #fff; border-left: 1px solid #aaa; border-right: 1px solid #aaa; min-height: 100% }
|
||||
#uglyhack { height: 30px }
|
||||
#main, #left { float: left; border-top: 0px dashed #aaa, margin-top: 50px }
|
||||
#left { width: 130px; border-right: 1px dashed #aaa; padding: 20px 10px; margin-bottom: 30px }
|
||||
#main { width: 609px; padding: 12px 20px 30px 20px }
|
||||
#footer { clear: left; width: 150px; margin: 0 0 0 324px; border-top: 1px dashed #aaa; height: 20px; text-align: center }
|
||||
#footer p { position: relative; top: -10px; padding: 0; background: #fff; display: inline; color: #aaa }
|
||||
#left h1 { font-weight: bold; text-align: center; font-size: 15px }
|
||||
#left li { margin: 20px 0 0 10px; list-style-type: none }
|
||||
#left li a { text-decoration: none; display: block; width: 120px; border-bottom: 1px solid #fff }
|
||||
#left li a:hover { border-bottom: 1px dashed #aaa }
|
||||
#left li li { margin-top: 10px }
|
||||
#left li li a { width: 110px }
|
||||
#left li li li { margin-top: 2px }
|
||||
#left li li li a { width: 100px }
|
||||
#left .menusel { color: #03a }
|
||||
#left .notes { margin-top: 50px; text-align: center }
|
||||
#left .notes, #left .notes a { font-size: 12px; text-decoration: none }
|
||||
#left .notes a:hover { text-decoration: underline }
|
||||
img.right { float: right; margin: 0 0 5px 10px }
|
||||
.indexgroup { margin: 30px 10px 0px 20px }
|
||||
.indexgroup li { list-style-type: none; margin-left: 0px }
|
||||
.indexgroup li li { margin-left: 20px }
|
||||
.indexgroup + .dummyTopAnchor + p { margin-top: 20px }
|
||||
a.external:after { content: url(/img/external.gif) }
|
||||
b { font-weight: bold }
|
||||
h1.title { margin-top: 0; font-size: 25px }
|
||||
h1 { margin-top: 50px; }
|
||||
h2 { margin-top: 25px; }
|
||||
h3 { margin-top: 0; margin-left: 10px }
|
||||
h1, h1 a { font-size: 19px; color: #000; margin-bottom: 5px; text-decoration: none }
|
||||
h2, h2 a { font-size: 16px; color: #000; margin-bottom: 1px; text-decoration: none }
|
||||
h3, h3 a { font-size: 15px; color: #000; margin-bottom: 1px; text-decoration: none }
|
||||
li { margin-left: 35px; margin-right: 15px; text-align: justify }
|
||||
p { margin: 3px 15px 13px 15px; text-align: justify }
|
||||
p + ul, p + ol { margin-top: -10px }
|
||||
pre { padding-left: 0 }
|
||||
pre, code, pre b { font: 11px monospace; }
|
||||
pre b { font-weight: bold }
|
||||
pre { background: #f5f5f5; border: 1px dotted #aaa; margin: 5px 10px; display: block; padding: 5px 5px 5px 0; }
|
||||
dd { margin-left: 15px }
|
||||
dt a { color: #333 }
|
||||
dt { margin-left: 10px }
|
||||
i { font-style: normal } /* TODO */
|
||||
.sig { vertical-align: super }
|
||||
.sig, .sig a { font-size: 12px; color: #333; text-decoration: none }
|
||||
E;
|
||||
}
|
||||
|
|
|
|||
87
style.css
Normal file
87
style.css
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/* font-face code from http://fonts.googleapis.com/css?family=Buenard:700,400 */
|
||||
@font-face {
|
||||
font-family: 'Buenard';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: local('Buenard Bold'), local('Buenard-Bold'), url('http://themes.googleusercontent.com/static/fonts/buenard/v2/8T0adwz_RAtKrxbccQmEFC3USBnSvpkopQaUR-2r7iU.ttf') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Buenard';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Buenard'), local('Buenard-Regular'), url('http://themes.googleusercontent.com/static/fonts/buenard/v2/UUYHasP8umGDjV-yeZf27Q.ttf') format('truetype');
|
||||
}
|
||||
html,body { background: #ccc; text-align: center; height: 100% }
|
||||
* { margin: 0; padding: 0; font: 15px 'Buenard',serif; color: #222 }
|
||||
#body { text-align: left; width: 800px; margin: 0 auto; background: #fff; border-left: 1px solid #aaa; border-right: 1px solid #aaa; min-height: 100% }
|
||||
#uglyhack { height: 30px }
|
||||
#main, #left { float: left; border-top: 0px dashed #aaa, margin-top: 50px }
|
||||
#left { width: 130px; border-right: 1px dashed #aaa; padding: 20px 10px; margin-bottom: 30px }
|
||||
#main { width: 609px; padding: 12px 20px 30px 20px }
|
||||
#footer { clear: left; width: 150px; margin: 0 0 0 324px; border-top: 1px dashed #aaa; height: 20px; text-align: center }
|
||||
#footer p { position: relative; top: -10px; padding: 0; background: #fff; display: inline; color: #aaa }
|
||||
#left h1 { font-weight: bold; text-align: center; font-size: 15px }
|
||||
#left li { margin: 20px 0 0 10px; list-style-type: none }
|
||||
#left li a { text-decoration: none; display: block; width: 120px; border-bottom: 1px solid #fff }
|
||||
#left li a:hover { border-bottom: 1px dashed #aaa }
|
||||
#left li li { margin-top: 10px }
|
||||
#left li li a { width: 110px }
|
||||
#left li li li { margin-top: 2px }
|
||||
#left li li li a { width: 100px }
|
||||
#left .menusel { color: #03a }
|
||||
#left .notes { margin-top: 50px; text-align: center }
|
||||
#left .notes, #left .notes a { font-size: 12px; text-decoration: none }
|
||||
#left .notes a:hover { text-decoration: underline }
|
||||
img.right { float: right; margin: 0 0 5px 10px }
|
||||
.indexgroup { margin: 30px 10px 0px 20px }
|
||||
.indexgroup li { list-style-type: none; margin-left: 0px }
|
||||
.indexgroup li li { margin-left: 20px }
|
||||
.indexgroup + .dummyTopAnchor + p { margin-top: 20px }
|
||||
a.external:after { content: url(/img/external.gif) }
|
||||
b { font-weight: bold }
|
||||
h1.title { margin-top: 0; font-size: 25px }
|
||||
h1 { margin-top: 50px; }
|
||||
h2 { margin-top: 25px; }
|
||||
h3 { margin-top: 0; margin-left: 10px }
|
||||
h1, h1 a { font-size: 19px; color: #000; margin-bottom: 5px; text-decoration: none }
|
||||
h2, h2 a { font-size: 16px; color: #000; margin-bottom: 1px; text-decoration: none }
|
||||
h3, h3 a { font-size: 15px; color: #000; margin-bottom: 1px; text-decoration: none }
|
||||
li { margin-left: 35px; margin-right: 15px; text-align: justify }
|
||||
p { margin: 3px 15px 13px 15px; text-align: justify }
|
||||
p + ul, p + ol { margin-top: -10px }
|
||||
pre { padding-left: 0 }
|
||||
pre, code, pre b { font: 11px monospace; }
|
||||
pre b { font-weight: bold }
|
||||
pre { background: #f5f5f5; border: 1px dotted #aaa; margin: 5px 10px; display: block; padding: 5px 5px 5px 0; }
|
||||
dd { margin-left: 15px }
|
||||
dt a { color: #333 }
|
||||
dt { margin-left: 10px }
|
||||
i { font-style: normal } /* TODO */
|
||||
.sig { vertical-align: super }
|
||||
.sig, .sig a { font-size: 12px; color: #333; text-decoration: none }
|
||||
textarea, input, select { background: #fcfcfc; color: #000; border: 1px solid #999 }
|
||||
textarea:focus, input:focus { background: #fff }
|
||||
|
||||
table { border-collapse: collapse }
|
||||
table td { padding: 0 2px }
|
||||
table thead td { font-weight: bold }
|
||||
.issue_listing tbody tr:nth-child(odd) { background-color: #f4f4f4 }
|
||||
.issue_listing { width: 95% }
|
||||
.issue_col_id, .issue_col_type, .issue_col_status, .issue_col_date { white-space: nowrap }
|
||||
.issue_closed td { text-decoration: line-through }
|
||||
.issue_status { display: block; height: 20px }
|
||||
.issue_status dt { float: left; font-weight: bold }
|
||||
.issue_status dd { float: left; }
|
||||
.issue_item h1 { margin-top: 30px }
|
||||
.issue_item dt { clear: left; float: left; font-weight: bold; width: 60px }
|
||||
.issue_item dd { float: left; padding-right: 20px }
|
||||
.issue_item p { clear: left; padding-top: 5px }
|
||||
.issue_frm fieldset { border: 0; margin-top: 40px }
|
||||
.issue_frm legend { font-size: 19px; color: #000; }
|
||||
.issue_frm li { list-style-type: none; margin-left: 10px; clear: left; padding-top: 5px }
|
||||
.issue_frm label { display: block; width: 80px; float: left }
|
||||
.issue_frm input, .issue_frm select { float: left }
|
||||
.issue_frm textarea { width: 100%; height: 200px }
|
||||
.issue_frm_submit input { float: right; width: 200px }
|
||||
|
||||
.issue_status dt::after, .issue_item dt::after, .issue_frm label::after { content: ":" }
|
||||
Loading…
Add table
Add a link
Reference in a new issue