diff options
author | Yorhel <git@yorhel.nl> | 2019-12-01 09:22:20 +0100 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2019-12-01 13:40:59 +0100 |
commit | 165b62acc991cbf30cb721af27b04a066dbc9413 (patch) | |
tree | 34cbe7fef4a020fe121ddf1026dd6be13e9498a2 /lib/VNWeb/Discussions | |
parent | b2ba46a9a0900d2b9d62a5ff84c4d4c9d9780abc (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.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/Discussions/Lib.pm | 28 | ||||
-rw-r--r-- | lib/VNWeb/Discussions/Thread.pm | 197 |
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; |