Verified Commit be1a95e4 authored by Adam Matoušek's avatar Adam Matoušek
Browse files

Are these “small commits” in the room with us?

parent 4a2137b0
......@@ -33,9 +33,45 @@ out article dirs
dep articles/$(articles)
cmd gib.nochange /bin/mkdir -p $(articles)
# assemble comments (this must be quite high, because a comment index is generated)
for a $(articles)
out $(a)/comments.html
dep article dirs
dep $(sources:articles/$(a)/*.comment)
dep templates/comments.tt
cmd $(tool) mkcomments $(a)
# selected password for each article
for a $(articles)
out $(a)/password
dep $(a)/comments.html
use transient
out universal_passwords
dep $(srcdir)/articles/passwords
cmd $(tool) into $(out) sed s/^/*\t/ $(dep)
out _comment_passwords
dep $(articles)/password
dep universal_passwords
cmd $(tool) into $(out) cat $(dep)
add deployed-files $(out)
# index of valid comments for each article
for a $(articles)
out $(a)/comment_index
dep $(a)/comments.html
use transient
out _comment_index
dep $(articles)/comment_index
cmd $(tool) into $(out) cat $(dep)
add deployed-files $(out)
# page indices (used to generate index/archive pages)
out archives.gib
dep $(sources:articles/*/meta)
dep $(articles)/comment_index
cmd $(tool) build_index
out index
......@@ -57,14 +93,6 @@ dep templates/nav.tt
dep index
cmd $(tool) mknav
# assemble comments
for a $(articles)
out $(a)/comments.html
dep article dirs
dep $(sources:articles/$(a)/*.comment)
dep templates/comments.tt
cmd $(tool) mkcomments $(a)
# TODO: md2html or sth
# copy ready-made (templated) .htmls
......@@ -150,17 +178,33 @@ dep templates/archive.tt
cmd $(tool) mkarchive $(a)
add deployed-files $(out)
# link other resources
set resources style.css _comment.cgi
# other resources
set copied-resources robots.txt
set executables _comment.cgi
set minified-resources style.css blogisek.js
set extra-templated-pages comment-queued.html comment-accepted.html geoblocked.html
set resources $(copied-resources) $(minified-resources) $(extra-templated-pages)
add deployed-files $(resources)
for res $(resources)
for res $(copied-resources) $(executables)
out $(res)
dep $(srcdir)/resources/$(res)
cmd /bin/ln -f $(dep) $(out)
add deployed-files $(resources) $(resized-images)
for res $(minified-resources)
out $(res)
dep $(srcdir)/resources/$(res)
cmd /usr/bin/minify -o $(out) $(dep)
set all $(articles-html) $(images-all) $(archives)/index.html $(resources)
for res $(extra-templated-pages)
out $(res)
dep resources/$(res)
dep nav.html
dep templates/page.tt
cmd $(tool) mkpage resources/$(res) $(out)
set all $(deployed-files) $(executables)
set install
for f $(deployed-files)
......@@ -168,3 +212,10 @@ out install $(f)
dep $(f)
cmd /usr/bin/install -D -m 0644 $(dep) $(prefix)/$(f)
add install $(out)
for f $(executables)
out install $(f)
dep $(f)
cmd /usr/bin/install -D -m 0755 $(dep) $(prefix)/$(f)
add install $(out)
#!/usr/bin/perl
# TODO Rewrite. This is a dumpster fire.
# Expects a comment in the POST parameters
use strict;
......@@ -8,10 +10,10 @@ use utf8;
use open qw/:std :encoding(utf-8)/;
use CGI::Simple;
use File::Slurper qw/read_text/;
use Data::Dumper;
use File::Slurper qw/read_text read_lines/;
use POSIX 'strftime';
use File::Temp qw/tempfile/;
use Unicode::Normalize;
$CGI::Simple::POST_MAX = 8192; # 8 Kib
$CGI::Simple::DISABLE_UPLOADS = 1;
......@@ -25,17 +27,24 @@ my $dir = "_comments";
my $suffix = ".comment";
my $queue_limit = 50;
sub errorpage {
print $q->header( @header, -status => $_[0] );
print_errorpage( $_[0] );
exit 0;
# dbg
use Data::Dumper;
print Dumper @_;
}
unless ( -t STDIN || ( $ENV{REQUEST_METHOD} // '' ) eq 'POST' )
{
my $status = '405 Method Not Allowed';
print $q->header( @header, -status => $status );
print_errorpage( $status );
exit 0;
exit errorpage( '405 Method Not Allowed' );
}
# When no 'article' parameter has been given, try to extract the
# article id (slug) from the URI
my $article_slug = $ENV{REQUEST_URI} =~ s,/comment(\?.*)?$,,rn;
$article_slug =~ s,^(https?://[^/]*)?/,,n;
my %c = (
article => scalar $q->param( 'article' ) // $article_slug,
author => scalar $q->param( 'author' ),
......@@ -47,27 +56,34 @@ my %c = (
unless ( $c{article} && $c{article} =~ m,^[-/a-z0-9]+$,
&& $c{author} && 0 < length $c{author} < 64 && no_weird_chars( $c{author} )
&& $c{body} && 0 < length $c{body} < 8000 && no_weird_chars( $c{body} )
&& ( !$c{parent} || $c{parent} =~ /^[0-9]{14}\w{4}$/ ) )
&& ( !$c{parent} || $c{parent} =~ /^[0-9]{14}\w{4}$/ ) ) # TODO: other ID formats
{
my $status = '400 Bad Request';
print $q->header( @header, -status => $status );
print_errorpage( $status );
exit 0;
exit errorpage( '400 Bad Request' );
}
my $comment_index = load_index( '_comment_index' );
my $password_index = load_index( '_comment_passwords' );
unless ( $password_index->{$c{article}} # comments are enabled
&& ( !$c{parent} || $comment_index->{$c{article}}{$c{parent}} ) ) # parent exists
{
exit errorpage( '400 Bad Request' );
}
sanitise( $c{author} );
sanitise( $c{password} );
my $passed = $c{password} eq 'strasnetajneheslo'; # TODO
my $password = password_normalise( $c{password} );
my $passed = $password && ( # index might contain empty password, if comments are enabled and no password is provided
$password_index->{$c{article}}{$password} # article password
|| $password_index->{'*'}{$password} # global passwords
);
# Deny if the queue is full, unless the password is correct
my @ls = glob( "$dir/*$suffix" );
if ( !$passed && @ls >= $queue_limit )
{
my $status = '503 Service unavailable';
print $q->header( @header, -status => $status );
print_errorpage( $status );
exit 0;
exit errorpage( '503 Service unavailable' );
}
$c{parent} ||= 'root'; # top-level comments
......@@ -84,7 +100,7 @@ my ($fh, $filename) = tempfile( "${slug}XXXX", DIR => $dir, SUFFIX => $suffix );
my $commentid = $filename =~ s/^\Q$dir\E\/(.+)\Q$suffix\E$/$1/r;
unless ( -t STDIN ) {
my $gid = getgrgid "adamat";
my $gid = getgrnam "adamat";
chown -1, $gid, $filename;
chmod 0440, $filename;
}
......@@ -101,12 +117,13 @@ Parent: $c{parent}
$c{body}
EOF
binmode $fh, ':encoding(utf-8)';
print $fh $comment;
close $fh;
print $q->header(
-status => '303 See Other',
-location => '/commented.html',
-location => $passed ? '/comment-accepted.html' : '/comment-queued.html',
);
exit 0;
......@@ -136,4 +153,24 @@ sub sanitise {
}
}
sub password_normalise {
$_ = lc NFKD( shift ); # unicode normalisation, lowerspace
s/[^-a-z0-9 ]//g;
return $_;
}
sub load_index {
my @content = read_lines( shift );
my $normalise = shift;
my %index;
foreach ( @content ) {
my @fields = split / *\t */;
next unless @fields >= 2;
# comment ids aren't any wierder than normalised passwords, anyway
my $d = $normalise ? password_normalise( $fields[1] ) : $fields[1];
$index{$fields[0]}{$d} = 1;
}
return \%index;
}
# vim: sts=4 et ts=8 sw=4
var ttc = document.getElementById('tooltipcloser');
var dim = document.getElementById('dimmer').classList;
ttc.onclick = (ev) => {
let ttip = ttc.ttip;
ttip && ttip.classList.remove('open');
ttc.hidden = true;
dim.remove('on');
};
for ( e of document.getElementsByClassName('was') ) {
e.dataset.tooltip = e.title;
e.title = '';
e.onclick = (ev) => {
let classes = ev.target.classList;
let wasopen = classes.contains('open');
if ( ttc.ttip ) { classes.remove('open'); }
ttc.ttip = ev.target;
classes.toggle('open', !wasopen);
ttc.hidden = wasopen;
dim.toggle('on', !wasopen);
};
}
<div class=pane>
<h1>Komentář byl přijat.</h1>
<p>
Vypadáte jako šunka<sup class=was title='Oproti konservě s lančmítem'>was</sup>,
takže s jeho zveřejněním nebudeme otálet a při příštím vygenerování webu
si ho po sobě konečně budete moci přečíst a zhrozit se nad překlepy.
</p>
<p>Díky! Komentáře mám moc rád, a takové, které nemusím třídit, ještě radši.</p>
</div>
<div class=pane>
<h1>Komentář byl odeslán</h1>
<p>
Nejsem si jist vaším člověčenstvím, takže si nějakou dobu pobude ve
frontě, než ručně ověřím, že se nejedná o konservu s lančmítem.
</p>
<p>Díky za komentář! Pro co jiného bych ty články psal.</p>
</div>
<div class=pane>
<h1>Geoblocked :(</h1>
<p>
Komentáře jsou povoleny pouze z počítačů, které se dle GeoIP nachází v Česku
nebo jiných zemích, kde mám zrovna kamarády.
</p><p>
Jistě, zamítání podle přibližné zeměpisné polohy je nepěkné, ale velkou část
spamu to efektivně odráží. A myslím, že si můžu dovolit předpokládat, že
moje nepočetná čtenářská základna tímto omezením dotčena nebude.
</p><p>
Pokud tím dotčeni jste – je mi to hrozně líto; stojím o vaše komentáře!
</p><p>
Pokud nevydržíte s komentováním do návratu do vlasti (a já vím, že je to
hrozná otrava), můžete mi komentář poslat e-mailem (me zavináč přezdívka
tečka dev) a rovnou vás můžu přidat do seznamu výjimek.
</p><p>
O text komentáře patrně nepřijdete, mělo by snad stačit stisknout
v prohlížeči tlačítko „Zpět“.
</p>
</div>
User-agent: *
Disallow: /*.jpg$
Disallow: /*.jpeg$
......@@ -4,9 +4,10 @@ html, body {
padding: 0;
font-family: serif;
color: black;
background-color: #ddd;
background-color: #dfdcd7;
font-size: 12pt;
-webkit-text-size-adjust: none;
-moz-text-size-adjust: none;
text-size-adjust: none;
}
......@@ -14,12 +15,20 @@ h1, h2, h3, h4, h5, h6 {
font-family: sans-serif;
}
#comments .reply input:not(:checked) + label,
#reply-root:not(:checked) + label::after,
a {
color: #137;
text-decoration: none;
color: #138;
}
a.nocolor {
color: inherit;
}
#comments .reply input:not(:checked) + label:hover,
#reply-root:not(:checked) + label:hover::after,
a:hover {
text-decoration: underline;
cursor: pointer;
}
#sidebar {
......@@ -30,16 +39,19 @@ a:hover {
#sidebar > header blockquote {
display: none;
}
#sidebar > header h1 {
margin: 0; /* only for tiny */
}
#comments .quiz em,
#sidebar a {
color: #751;
}
#navbutton {
display: block;
color: #138;
color: #137;
font-weight: bold;
border: 0.2em solid #138;
border: 0.2em solid #137;
background-color: white;
aspect-ratio: 1;
padding: 0.5em;
......@@ -71,12 +83,9 @@ a:hover {
list-style: none;
line-height: 2;
}
#navigation a {
color: #542;
}
#navigation li small {
color: #876;
font-size: 100%;
color: #333;
font-size: 92%;
}
#content {
......@@ -86,16 +95,19 @@ a:hover {
}
#content > main > article {
#content > main > article,
.pane {
background-color: white;
position: relative;
}
#content > main::before {
#content > main > header::before {
content: "";
display: table;
}
#content > main > header {
padding: 0 1em;
#content > main > header,
#comments > h2 {
padding: 0 1rem;
}
#content > main > header h1 {
line-height: initial;
......@@ -103,8 +115,7 @@ a:hover {
margin-top: 0; /* only small */
}
main + footer {
margin-top: 1em;
#content > footer {
padding: 1em;
background-color: rgba(0, 0, 0, 0.08);
color: #333;
......@@ -120,14 +131,30 @@ article .banner img {
object-fit: cover;
object-position: center center;
}
article a.banner {
background-color: #446ab6;
}
article a.banner img {
filter: contrast(80%) brightness(130%) grayscale(90%) opacity(80%);
transition: filter 250ms;
}
article a.banner:hover img,
article a.banner:focus img,
article a.banner:active img {
filter: none;
}
.article-text header + footer {
margin-top: -1em;
}
.article-text footer ul {
padding: 0;
font-size: 0;
}
.article-text footer li {
font-size: 1rem;
font-family: sans-serif;
display: block;
float: left;
display: inline;
}
.article-text footer li + li {
margin-left: 1em;
......@@ -135,28 +162,21 @@ article .banner img {
border-left: 0.1em solid #888;
}
.article-text > p,
.article-text > footer,
.article-text > header {
margin-left: 1em;
margin-right: 1em;
.pane > *,
.article-text > * {
margin-left: 1rem;
margin-right: 1rem;
}
.article-text > p {
text-align: justify;
hyphens: auto;
}
.article-text footer::after {
content: "";
display: block;
clear: both;
-moz-hyphens: auto;
-webkit-hyphens: auto;
}
.article-text .readmore a {
display: block;
padding: 0.5em 1em;
margin-top: 2em;
border-top: 0.2em dashed #ddd;
text-align: right;
font-family: sans-serif;
......@@ -173,6 +193,188 @@ article .banner img {
margin-top: -1em;
}
.article-text .readmore,
.article-text #readmore {
margin-left: 0;
margin-right: 0;
}
/* I hate margin collapsing very much. */
.article-text::before,
.article-text::after,
.pane::before,
.pane::after {
content: "";
display: table;
}
/* Tooltips */
sup.was {
font-style: italic;
text-decoration: underline dotted;
color: #137;
background-color: #ffedf0;
}
.was::before {
content: attr(title) attr(data-tooltip);
position: absolute;
background-color: pink;
color: black;
font-size: 0.9rem;
font-style: initial;
visibility: hidden;
min-width: 10em;
right: 0;
width: 100%;
padding: 0.5em;
box-sizing: border-box;
transform: translateY(-120%);
z-index: 100;
opacity: 0%;
transition-duration: 200ms;
transition-property: opacity transform;
}
.was:hover::before,
.was.open::before {
visibility: visible;
transform: translateY(-100%);
opacity: 100%;
}
#tooltipcloser,
#dimmer {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
-webkit-user-select: none;
user-select: none;
opacity: 0%;
}
#tooltipcloser {
z-index: 120;
}
#dimmer {
z-index: 20;
background-color: #03a;
transition: opacity 150ms, visibility 300ms;
visibility: hidden;
}
#dimmer.on {
visibility: visible;
opacity: 12%;
}
.article-text figure {
margin: 2em auto;
}
.article-text figure figcaption {
margin: 1em;
font-style: italic;
text-align: center;
}
.article-text figure img {
display: block;
transition: filter 250ms;
margin: 0 auto;
}
.article-text figure a:hover img {
filter: brightness(115%) contrast(90%);
}
.article-text figure.reasonable,
.article-text figure.wide {
width: 100%;
}
.article-text figure.reasonable img {
max-width: 100%;
max-height: 75vh;
}
/* Comments */
#comments ol {
list-style: none;
padding: 0;
}
#comments ol + h3 {
margin-top: 2rem;
margin-bottom: 0; /* if 'ol' was present, then the radio-text will be too */
}
#comments li > ol > li {
margin-left: 2rem
}
#comments li li {
border-top: solid 0.1em #aaa
}
#comments article footer {
margin-top: 1em;
font-family: sans-serif;
margin-bottom: 1em;
}
#comments article small {
margin-left: 0.5em;
}
#comments .reply {
text-align: right;
margin-bottom: 0.5em;
}
#comments .reply input,
#reply-root {
display: none;
}
#comments .reply input:checked + label,
#reply-root + label {
visibility: hidden;
font-size: 0;
}
#comments .reply input:checked + label::after,
#reply-root + label::after {
visibility: visible;
color: #751;
content: "Odpovídáte sem";
font-size: 1rem;
}
#reply-root:checked + label::after {
content: "Vkládáte samostatný komentář";
}