summaryrefslogtreecommitdiff
path: root/lib/VNWeb/Discussions
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2019-12-01 09:22:20 +0100
committerYorhel <git@yorhel.nl>2019-12-01 13:40:59 +0100
commit165b62acc991cbf30cb721af27b04a066dbc9413 (patch)
tree34cbe7fef4a020fe121ddf1026dd6be13e9498a2 /lib/VNWeb/Discussions
parentb2ba46a9a0900d2b9d62a5ff84c4d4c9d9780abc (diff)
v2rw: Convert thread display + poll voting
I did not reimplement the 'poll_recast' and 'poll_preview' settings, these actions are now always permitted. Updated CSS a little bit to highlight the linked post and fix the double border at the bottom. The nice thing about the sql_visible_threads() function I wrote earlier is that is can also be used for access control on a single thread. More code re-use. \o/
Diffstat (limited to 'lib/VNWeb/Discussions')
-rw-r--r--lib/VNWeb/Discussions/Board.pm2
-rw-r--r--lib/VNWeb/Discussions/Lib.pm28
-rw-r--r--lib/VNWeb/Discussions/Thread.pm197
3 files changed, 215 insertions, 12 deletions
diff --git a/lib/VNWeb/Discussions/Board.pm b/lib/VNWeb/Discussions/Board.pm
index 80207ed7..4f32931d 100644
--- a/lib/VNWeb/Discussions/Board.pm
+++ b/lib/VNWeb/Discussions/Board.pm
@@ -28,7 +28,7 @@ TUWF::get qr{/t/(all|$board_regex)}, sub {
boardsearch_ $type if !$id;
p_ class => 'center', sub {
a_ href => $createurl, 'Start a new thread';
- } if auth->permBoard;
+ } if can_edit t => {};
};
threadlist_
diff --git a/lib/VNWeb/Discussions/Lib.pm b/lib/VNWeb/Discussions/Lib.pm
index a887b306..88f6cac6 100644
--- a/lib/VNWeb/Discussions/Lib.pm
+++ b/lib/VNWeb/Discussions/Lib.pm
@@ -3,7 +3,7 @@ package VNWeb::Discussions::Lib;
use VNWeb::Prelude;
use Exporter 'import';
-our @EXPORT = qw/threadlist_ boardsearch_ boardtypes_/;
+our @EXPORT = qw/sql_visible_threads enrich_boards threadlist_ boardsearch_ boardtypes_/;
# Returns a WHERE condition to filter threads that the current user is allowed to see.
@@ -14,6 +14,21 @@ sub sql_visible_threads {
}
+# Adds a 'boards' array to threads.
+sub enrich_boards {
+ my($filt, @lst) = @_;
+ enrich boards => id => tid => sub { sql q{
+ SELECT tb.tid, tb.type, tb.iid, COALESCE(u.username, v.title, p.name) AS title, COALESCE(u.username, v.original, p.original) AS original
+ FROM threads_boards tb
+ LEFT JOIN vn v ON tb.type = 'v' AND v.id = tb.iid
+ LEFT JOIN producers p ON tb.type = 'p' AND p.id = tb.iid
+ LEFT JOIN users u ON tb.type = 'u' AND u.id = tb.iid
+ WHERE }, sql_and(sql('tb.tid IN', $_[0]), $filt||()), q{
+ ORDER BY tb.type, tb.iid
+ }}, @lst;
+}
+
+
# Generate a thread list table, options:
# where => SQL for the WHERE clause ('t' is available as alias for 'threads').
# boards => SQL for the WHERE clause of the boards ('tb' as alias for 'threads_boards').
@@ -45,16 +60,7 @@ sub threadlist_ {
);
return 0 if !@$lst;
- enrich boards => id => tid => sub { sql q{
- SELECT tb.tid, tb.type, tb.iid, COALESCE(u.username, v.title, p.name) AS title, COALESCE(u.username, v.original, p.original) AS original
- FROM threads_boards tb
- LEFT JOIN vn v ON tb.type = 'v' AND v.id = tb.iid
- LEFT JOIN producers p ON tb.type = 'p' AND p.id = tb.iid
- LEFT JOIN users u ON tb.type = 'u' AND u.id = tb.iid
- WHERE }, sql_and(sql('tb.tid IN', $_[0]), $opt{boards}||()), q{
- ORDER BY tb.type, tb.iid
- }}, $lst;
-
+ enrich_boards $opt{boards}, $lst;
paginate_ $opt{paginate}, $opt{page}, [ $count, $opt{results} ], 't' if $opt{paginate};
div_ class => 'mainbox browse discussions', sub {
diff --git a/lib/VNWeb/Discussions/Thread.pm b/lib/VNWeb/Discussions/Thread.pm
new file mode 100644
index 00000000..01cb5359
--- /dev/null
+++ b/lib/VNWeb/Discussions/Thread.pm
@@ -0,0 +1,197 @@
+package VNWeb::Discussions::Thread;
+
+use VNWeb::Prelude;
+use VNWeb::Discussions::Lib;
+
+
+my $POLL_OUT = form_compile any => {
+ question => {},
+ max_options => { uint => 1 },
+ num_votes => { uint => 1 },
+ can_vote => { anybool => 1 },
+ preview => { anybool => 1 },
+ tid => { id => 1 },
+ options => { aoh => {
+ id => { id => 1 },
+ option => {},
+ votes => { uint => 1 },
+ my => { anybool => 1 },
+ } },
+};
+
+
+my $POLL_IN = form_compile any => {
+ tid => { id => 1 },
+ options => { type => 'array', values => { id => 1 } },
+};
+
+
+elm_form 'DiscussionsPoll' => $POLL_OUT, $POLL_IN;
+
+
+sub metabox_ {
+ my($t) = @_;
+ div_ class => 'mainbox', sub {
+ h1_ $t->{title};
+ h2_ 'Hidden' if $t->{hidden};
+ h2_ 'Private' if $t->{private};
+ h2_ 'Posted in';
+ ul_ sub {
+ li_ sub {
+ a_ href => "/t/$_->{type}", $BOARD_TYPE{$_->{type}}{txt};
+ if($_->{iid}) {
+ txt_ ' > ';
+ a_ style => 'font-weight: bold', href => "/t/$_->{type}$_->{iid}", "$_->{type}$_->{iid}";
+ txt_ ':';
+ if($_->{title}) {
+ a_ href => "/$_->{type}$_->{iid}", title => $_->{original}, $_->{title};
+ } else {
+ b_ '[deleted]';
+ }
+ }
+ } for $t->{boards}->@*;
+ };
+ }
+}
+
+
+sub posts_ {
+ my($t, $posts, $page) = @_;
+ my sub url { "/t$t->{id}".($_?"/$_":'') }
+
+ paginate_ \&url, $page, [ $t->{count}, 25 ], 't';
+ div_ class => 'mainbox thread', sub {
+ table_ class => 'stripe', sub {
+ tr_ mkclass(deleted => $_->{hidden}), id => $_->{num}, sub {
+ td_ class => 'tc1', sub {
+ a_ href => "/t$t->{id}.$_->{num}", "#$_->{num}";
+ if(!$_->{hidden}) {
+ txt_ ' by ';
+ user_ $_;
+ br_;
+ txt_ fmtdate $_->{date}, 'full';
+ }
+ };
+ td_ class => 'tc2', sub {
+ i_ class => 'edit', sub {
+ txt_ '< ';
+ a_ href => "/t$t->{id}.$_->{num}/edit", 'edit';
+ txt_ ' >';
+ } if can_edit t => $_;
+ if($_->{hidden}) {
+ i_ class => 'deleted', 'Post deleted.';
+ } else {
+ lit_ bb2html $_->{msg};
+ i_ class => 'lastmod', 'Last modified on '.fmtdate($_->{edited}, 'full') if $_->{edited};
+ }
+ };
+ } for @$posts;
+ };
+ };
+ paginate_ \&url, $page, [ $t->{count}, 25 ], 'b';
+}
+
+
+sub reply_ {
+ my($t, $page) = @_;
+ return if $t->{count} > $page*25;
+ if(can_edit t => $t) {
+ # TODO: Elmify
+ form_ action => "/t$t->{id}/reply", method => 'post', 'accept-charset' => 'UTF-8', sub {
+ div_ class => 'mainbox', sub {
+ fieldset_ class => 'submit', sub {
+ input_ type => 'hidden', class => 'hidden', name => 'formcode', value => auth->csrftoken;
+ h2_ sub {
+ txt_ 'Quick reply';
+ b_ class => 'standout', ' (English please!)';
+ };
+ textarea_ name => 'msg', id => 'msg', rows => 4, cols => 50, '';
+ br_;
+ input_ type => 'submit', value => 'Reply', class => 'submit';
+ input_ type => 'submit', value => 'Go advanced...', class => 'submit', name => 'fullreply';
+ }
+ }
+ }
+ } else {
+ div_ class => 'mainbox', sub {
+ h1_ 'Reply';
+ p_ class => 'center',
+ !auth ? 'You must be logged in to reply to this thread.' :
+ $t->{locked} ? 'This thread has been locked, you can\'t reply to it anymore.' : 'You can not currently reply to this thread.';
+ }
+ }
+}
+
+
+TUWF::get qr{/$RE{tid}(?:/$RE{num})?}, sub {
+ my($id, $page) = (tuwf->capture('id'), tuwf->capture('num')||1);
+
+ my $t = tuwf->dbRowi(
+ 'SELECT id, title, count, hidden, locked, private
+ , poll_question, poll_max_options
+ FROM threads t
+ WHERE', sql_visible_threads(), 'AND id =', \$id
+ );
+ return tuwf->resNotFound if !$t->{id};
+
+ enrich_boards '', $t;
+
+ my $posts = tuwf->dbPagei({ results => 25, page => $page },
+ 'SELECT tp.tid as id, tp.num, tp.hidden, tp.msg',
+ ',', sql_user(),
+ ',', sql_totime('tp.date'), ' as date',
+ ',', sql_totime('tp.edited'), ' as edited
+ FROM threads_posts tp
+ JOIN users u ON tp.uid = u.id
+ WHERE tp.tid =', \$id, '
+ ORDER BY tp.num'
+ );
+
+ my $poll_options = $t->{poll_question} && tuwf->dbAlli(
+ 'SELECT tpo.id, tpo.option, count(tpv.uid) as votes, tpm.optid IS NOT NULL as my
+ FROM threads_poll_options tpo
+ LEFT JOIN threads_poll_votes tpv ON tpv.optid = tpo.id
+ LEFT JOIN threads_poll_votes tpm ON tpm.optid = tpo.id AND tpm.uid =', \auth->uid, '
+ WHERE tpo.tid =', \$id, '
+ GROUP BY tpo.id, tpo.option, tpm.optid'
+ );
+
+ framework_ title => $t->{title}, sub {
+ metabox_ $t;
+ elm_ 'Discussions.Poll' => $POLL_OUT, {
+ question => $t->{poll_question},
+ max_options => $t->{poll_max_options},
+ num_votes => tuwf->dbVali('SELECT COUNT(DISTINCT uid) FROM threads_poll_votes WHERE tid =', \$id),
+ preview => !!tuwf->reqGet('pollview'), # Old non-Elm way to preview poll results
+ can_vote => !!auth,
+ tid => $id,
+ options => $poll_options
+ } if $t->{poll_question};
+ posts_ $t, $posts, $page;
+ reply_ $t, $page;
+ }
+};
+
+
+TUWF::get qr{/$RE{postid}}, sub {
+ my($id, $num) = (tuwf->capture('id'), tuwf->capture('num'));
+ tuwf->resRedirect("/t$id".($num > 25 ? '/'.ceil($num/25) : '').'#'.$num, 'perm')
+};
+
+
+json_api qr{/t/pollvote.json}, $POLL_IN, sub {
+ my($data) = @_;
+ return elm_Unauth if !auth;
+
+ my $t = tuwf->dbRowi('SELECT poll_question, poll_max_options FROM threads WHERE id =', \$data->{tid});
+ return tuwf->resNotFound if !$t->{poll_question};
+
+ die 'Too many options' if $data->{options}->@* > $t->{poll_max_options};
+ validate_dbid sql('SELECT id FROM threads_poll_options WHERE tid =', \$data->{tid}, 'AND id IN'), $data->{options}->@*;
+
+ tuwf->dbExeci('DELETE FROM threads_poll_votes WHERE tid =', \$data->{tid}, 'AND uid =', \auth->uid);
+ tuwf->dbExeci('INSERT INTO threads_poll_votes (tid, uid, optid) VALUES(', \$data->{tid}, ',', \auth->uid, ',', \$_, ')') for $data->{options}->@*;
+ elm_Success
+};
+
+1;