diff options
author | Yorhel <git@yorhel.nl> | 2021-01-09 09:41:55 +0100 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2021-01-09 11:02:18 +0100 |
commit | 1ec958ba9d2e40c65246bd29f28b2b50364b2a5a (patch) | |
tree | d25ef02ca17f339bfc773f648c7392f5a5ae757e | |
parent | cf74837d76e2d5b00eb3dadda0b472c4b3114ce4 (diff) |
v2rw/TagPage: Add /experimental/ tag page rewrite with AdvSearch support
-rw-r--r-- | lib/VNWeb/TT/Index.pm | 44 | ||||
-rw-r--r-- | lib/VNWeb/TT/Lib.pm | 50 | ||||
-rw-r--r-- | lib/VNWeb/TT/TagPage.pm | 185 | ||||
-rw-r--r-- | lib/VNWeb/VN/List.pm | 52 |
4 files changed, 268 insertions, 63 deletions
diff --git a/lib/VNWeb/TT/Index.pm b/lib/VNWeb/TT/Index.pm index de3fc3f3..5b3b5661 100644 --- a/lib/VNWeb/TT/Index.pm +++ b/lib/VNWeb/TT/Index.pm @@ -1,49 +1,7 @@ package VNWeb::TT::Index; use VNWeb::Prelude; -use VNWeb::TT::Lib 'enrich_group'; - - -sub tree_ { - my($type) = @_; - my $table = $type eq 'g' ? 'tag' : 'trait'; - my $top = tuwf->dbAlli( - "SELECT id, name, c_items FROM ${table}s WHERE state = 1+1 AND NOT EXISTS(SELECT 1 FROM ${table}s_parents WHERE $table = id) - ORDER BY ", $type eq 'g' ? 'name' : '"order"' - ); - - enrich childs => id => parent => sub { sql - "SELECT tp.parent, t.id, t.name, t.c_items FROM ${table}s t JOIN ${table}s_parents tp ON tp.$table = t.id WHERE state = 1+1 AND tp.parent IN", $_, 'ORDER BY name' - }, $top; - $top = [ sort { $b->{childs}->@* <=> $a->{childs}->@* } @$top ] if $type eq 'g'; - - my sub lnk_ { - a_ href => "/$type$_[0]{id}", $_[0]{name}; - b_ class => 'grayedout', " ($_[0]{c_items})" if $_[0]{c_items}; - } - div_ class => 'mainbox', sub { - h1_ $type eq 'g' ? 'Tag tree' : 'Trait tree'; - ul_ class => 'tagtree', sub { - li_ sub { - lnk_ $_; - my $sub = $_->{childs}; - ul_ sub { - li_ sub { - txt_ '> '; - lnk_ $_; - } for grep $_, $sub->@[0 .. (@$sub > 6 ? 4 : 5)]; - li_ sub { - my $num = @$sub-5; - txt_ '> '; - a_ href => "/$type$_->{id}", style => 'font-style: italic', sprintf '%d more %s%s', $num, $table, $num == 1 ? '' : 's'; - } if @$sub > 6; - } if @$sub; - } for @$top; - }; - clearfloat_; - br_; - }; -} +use VNWeb::TT::Lib 'enrich_group', 'tree_'; sub recent_ { diff --git a/lib/VNWeb/TT/Lib.pm b/lib/VNWeb/TT/Lib.pm index 7521b4f0..cb8bf64d 100644 --- a/lib/VNWeb/TT/Lib.pm +++ b/lib/VNWeb/TT/Lib.pm @@ -3,7 +3,7 @@ package VNWeb::TT::Lib; use VNWeb::Prelude; use Exporter 'import'; -our @EXPORT = qw/ tagscore_ enrich_group /; +our @EXPORT = qw/ tagscore_ enrich_group tree_ /; sub tagscore_ { my($s, $ign) = @_; @@ -20,4 +20,52 @@ sub enrich_group { enrich_merge id => 'SELECT t.id, g.name AS "group" FROM traits t JOIN traits g ON g.id = t."group" WHERE t.id IN', @lst if $type eq 'i'; } + +sub tree_ { + my($type, $id) = @_; + my $table = $type eq 'g' ? 'tag' : 'trait'; + my $top = tuwf->dbAlli( + "SELECT id, name, c_items FROM ${table}s + WHERE state = 1+1 + AND", $id ? sql "id IN(SELECT $table FROM ${table}s_parents WHERE parent = ", \$id, ')' + : "NOT EXISTS(SELECT 1 FROM ${table}s_parents WHERE $table = id)", " + ORDER BY ", $type eq 'g' || $id ? 'name' : '"order"' + ); + return if !@$top; + + enrich childs => id => parent => sub { sql + "SELECT tp.parent, t.id, t.name, t.c_items FROM ${table}s t JOIN ${table}s_parents tp ON tp.$table = t.id WHERE state = 1+1 AND tp.parent IN", $_, 'ORDER BY name' + }, $top; + $top = [ sort { $b->{childs}->@* <=> $a->{childs}->@* } @$top ] if $type eq 'g' || $id; + + my sub lnk_ { + a_ href => "/$type$_[0]{id}", $_[0]{name}; + b_ class => 'grayedout', " ($_[0]{c_items})" if $_[0]{c_items}; + } + div_ class => 'mainbox', sub { + h1_ $id ? ($type eq 'g' ? 'Child tags' : 'Child traits') : $type eq 'g' ? 'Tag tree' : 'Trait tree'; + ul_ class => 'tagtree', sub { + li_ sub { + lnk_ $_; + my $sub = $_->{childs}; + ul_ sub { + li_ sub { + txt_ '> '; + lnk_ $_; + } for grep $_, $sub->@[0 .. (@$sub > 6 ? 4 : 5)]; + li_ sub { + my $num = @$sub-5; + txt_ '> '; + a_ href => "/$type$_->{id}", style => 'font-style: italic', sprintf '%d more %s%s', $num, $table, $num == 1 ? '' : 's'; + } if @$sub > 6; + } if @$sub; + } for @$top; + }; + clearfloat_; + br_; + }; +} + + + 1; diff --git a/lib/VNWeb/TT/TagPage.pm b/lib/VNWeb/TT/TagPage.pm new file mode 100644 index 00000000..80b14d8d --- /dev/null +++ b/lib/VNWeb/TT/TagPage.pm @@ -0,0 +1,185 @@ +package VNWeb::TT::TagPage; + +use VNWeb::Prelude; +use VNWeb::Filters; +use VNWeb::AdvSearch; +use VNWeb::VN::List; +use VNWeb::TT::Lib 'tree_'; + + +sub parents_ { + my($t) = @_; + + my %t; + push $t{$_->{child}}->@*, $_ for tuwf->dbAlli(' + WITH RECURSIVE p(id,child,name) AS ( + SELECT ', \$t->{id}, '::int, 0, NULL::text + UNION + SELECT t.id, p.id, t.name FROM p JOIN tags_parents tp ON tp.tag = p.id JOIN tags t ON t.id = tp.parent + ) SELECT * FROM p WHERE child <> 0 ORDER BY name + ')->@*; + + my sub rec { + $t{$_[0]} ? map { my $e=$_; map [ @$_, $e ], __SUB__->($e->{id}) } $t{$_[0]}->@* : [] + } + + p_ sub { + join_ \&br_, sub { + a_ href => '/g', 'Tags'; + for (@$_) { + txt_ ' > '; + a_ href => "/g$_->{id}", $_->{name}; + } + txt_ ' > '; + txt_ $t->{name}; + }, rec($t->{id}); + }; +} + + +sub infobox_ { + my($t) = @_; + + p_ class => 'mainopts', sub { + a_ href => "/g$t->{id}/add", 'Create child tag'; + } if $t->{state} != 1 && can_edit g => {}; + h1_ "Tag: $t->{name}"; + debug_ $t; + + div_ class => 'warning', sub { + h2_ 'Tag deleted'; + p_ sub { + txt_ 'This tag has been removed from the database, and cannot be used or re-added.'; + br_; + txt_ 'File a request on the '; + a_ href => '/t/db', 'discussion board'; + txt_ ' if you disagree with this.'; + } + } if $t->{state} == 1; + + div_ class => 'notice', sub { + h2_ 'Waiting for approval'; + p_ 'This tag is waiting for a moderator to approve it. You can still use it to tag VNs as you would with a normal tag.'; + } if $t->{state} == 0; + + parents_ $t; + + p_ class => 'description', sub { + lit_ bb_format $t->{description}; + } if $t->{description}; + + my @prop = ( + $t->{searchable} ? () : 'Not searchable.', + $t->{applicable} ? () : 'Can not be directly applied to visual novels.' + ); + p_ class => 'center', sub { + b_ 'Properties'; + br_; + join_ \&br_, \&txt_, @prop; + } if @prop; + + p_ class => 'center', sub { + b_ 'Category'; + br_; + txt_ $TAG_CATEGORY{$t->{cat}}; + }; + + p_ class => 'center', sub { + b_ 'Aliases'; + br_; + join_ \&br_, sub { txt_ $_ }, $t->{aliases}->@*; + } if $t->{aliases}->@*; +} + + +sub vns_ { + my($t) = @_; + + my $opt = tuwf->validate(get => + p => { upage => 1 }, + f => { advsearch => 'v' }, + s => { onerror => 'tagscore', enum => [qw/tagscore title rel pop rating/] }, + o => { onerror => 'd', enum => ['a','d'] }, + m => { onerror => [auth->pref('spoilers')||0], type => 'array', scalar => 1, minlength => 1, values => { enum => [0..2] } }, + fil => { required => 0 }, + )->data; + $opt->{m} = $opt->{m}[0]; + + # URL compatibility with old filters + if(!$opt->{f}->{query} && $opt->{fil}) { + my $q = eval { + tuwf->compile({ advsearch => 'v' })->validate(filter_release_adv filter_parse v => $opt->{fil})->data; + }; + if(!$q) { + warn "Filter compatibility conversion failed\n$@"; + } else { + return tuwf->resRedirect(tuwf->reqPath().'?'.query_encode(%$opt, fil => undef, f => $q), 'temp'); + } + } + + if(auth && !$opt->{f}{query} && !defined tuwf->reqGet('f')) { + my $def = tuwf->dbVali('SELECT query FROM saved_queries WHERE qtype = \'v\' AND name = \'\' AND uid =', \auth->uid); + $opt->{f} = tuwf->compile({ advsearch => 'v' })->validate($def)->data if $def; + } + + my $where = sql 'tvi.tag =', \$t->{id}, 'AND NOT v.hidden AND tvi.spoiler <=', \$opt->{m}, 'AND', $opt->{f}->sql_where(); + + my $time = time; + my($count, $list); + db_maytimeout { + $count = tuwf->dbVali('SELECT count(*) FROM vn v JOIN tags_vn_inherit tvi ON tvi.vid = v.id WHERE', $where); + $list = $count ? tuwf->dbPagei({results => 50, page => $opt->{p}}, ' + SELECT tvi.rating AS tagscore, v.id, v.title, v.original, v.c_released, v.c_popularity, v.c_votecount, v.c_rating + , v.c_platforms::text[] AS platforms, v.c_languages::text[] AS lang + FROM vn v + JOIN tags_vn_inherit tvi ON tvi.vid = v.id + WHERE', $where, ' + ORDER BY', sprintf { + tagscore => 'tvi.rating %s, v.title', + title => 'v.title %s', + rel => 'v.c_released %s, v.title', + pop => 'v.c_popularity %s NULLS LAST, v.title', + rating => 'v.c_rating %s NULLS LAST, v.title' + }->{$opt->{s}}, $opt->{o} eq 'a' ? 'ASC' : 'DESC' + ) : []; + } || (($count, $list) = (undef, [])); + + VNWeb::VN::List::enrich_userlist $list; + $time = time - $time; + + div_ class => 'mainbox', sub { + p_ class => 'mainopts', sub { + a_ href => "/g/links?t=$t->{id}", 'Recently tagged'; + }; + h1_ 'Visual novels'; + form_ action => "/experimental/g$t->{id}", method => 'get', sub { + p_ class => 'browseopts', sub { + button_ type => 'submit', name => 'm', value => 0, $opt->{m} == 0 ? (class => 'optselected') : (), 'Hide spoilers'; + button_ type => 'submit', name => 'm', value => 1, $opt->{m} == 1 ? (class => 'optselected') : (), 'Show minor spoilers'; + button_ type => 'submit', name => 'm', value => 2, $opt->{m} == 2 ? (class => 'optselected') : (), 'Spoil me!'; + }; + input_ type => 'hidden', name => 'o', value => $opt->{o}; + input_ type => 'hidden', name => 's', value => $opt->{s}; + input_ type => 'hidden', name => 'm', value => $opt->{m}; + $opt->{f}->elm_; + advsearch_msg_ $count, $time; + }; + }; + VNWeb::VN::List::listing_ $opt, $list, $count, 1 if $count; +} + + +TUWF::get qr{/experimental/$RE{gid}}, sub { + my $t = tuwf->dbRowi('SELECT id, name, description, state, c_items, cat, defaultspoil, searchable, applicable FROM tags WHERE id =', \tuwf->capture('id')); + return tuwf->resNotFound if !$t->{id}; + + enrich_flatten aliases => id => tag => sub { 'SELECT tag, alias FROM tags_aliases WHERE tag IN', $_, 'ORDER BY alias' }, $t; + + framework_ index => $t->{state} == 2, title => "Tag: $t->{name}", type => 'g', dbobj => $t, sub { + div_ class => 'mainbox', sub { infobox_ $t; }; + tree_ g => $t->{id}; + vns_ $t if $t->{searchable} && $t->{state} == 2; + }; +}; + +1; diff --git a/lib/VNWeb/VN/List.pm b/lib/VNWeb/VN/List.pm index 712a222c..57acdb6d 100644 --- a/lib/VNWeb/VN/List.pm +++ b/lib/VNWeb/VN/List.pm @@ -3,10 +3,12 @@ package VNWeb::VN::List; use VNWeb::Prelude; use VNWeb::AdvSearch; use VNWeb::Filters; +use VNWeb::TT::Lib 'tagscore_'; +# Also used by VNWeb::TT::TagPage sub listing_ { - my($opt, $list, $count) = @_; + my($opt, $list, $count, $tagscore) = @_; my sub url { '?'.query_encode %$opt, @_ } @@ -14,7 +16,8 @@ sub listing_ { div_ class => 'mainbox browse vnbrowse', sub { table_ class => 'stripe', sub { thead_ sub { tr_ sub { - td_ class => 'tc1', sub { txt_ 'Title'; sortable_ 'title', $opt, \&url }; + td_ class => 'tc_s',sub { txt_ 'Score'; sortable_ 'tagscore', $opt, \&url } if $tagscore; + td_ class => $tagscore ? 'tc_t' : 'tc1', sub { txt_ 'Title'; sortable_ 'title', $opt, \&url }; td_ class => 'tc7', ''; td_ class => 'tc2', ''; td_ class => 'tc3', ''; @@ -23,7 +26,8 @@ sub listing_ { td_ class => 'tc6', sub { txt_ 'Rating'; sortable_ 'rating', $opt, \&url }; } }; tr_ sub { - td_ class => 'tc1', sub { a_ href => "/v$_->{id}", title => $_->{original}||$_->{title}, $_->{title} }; + td_ class => 'tc_s',sub { tagscore_ $_->{tagscore} } if $tagscore; + td_ class => $tagscore ? 'tc_t' : 'tc1', sub { a_ href => "/v$_->{id}", title => $_->{original}||$_->{title}, $_->{title} }; td_ class => 'tc7', sub { b_ class => $_->{userlist_obtained} == $_->{userlist_all} ? 'done' : 'todo', sprintf '%d/%d', $_->{userlist_obtained}, $_->{userlist_all} if $_->{userlist_all}; abbr_ title => join(', ', $_->{vnlist_labels}->@*), scalar $_->{vnlist_labels}->@* if $_->{vnlist_labels} && $_->{vnlist_labels}->@*; @@ -44,6 +48,31 @@ sub listing_ { } +# Enrich the userlist fields needed for listing_() +# Also used by VNWeb::TT::TagPage +sub enrich_userlist { + return if !auth; + + enrich_merge id => sub { sql ' + SELECT irv.vid AS id + , COUNT(*) AS userlist_all + , SUM(CASE WHEN irl.status = 1+1 THEN 1 ELSE 0 END) AS userlist_obtained + FROM rlists irl + JOIN releases_vn irv ON irv.id = irl.rid + WHERE irl.uid =', \auth->uid, 'AND irv.vid IN', $_, ' + GROUP BY irv.vid + ' }, @_; + + enrich_flatten vnlist_labels => id => vid => sub { sql ' + SELECT uvl.vid, ul.label + FROM ulist_vns_labels uvl + JOIN ulist_labels ul ON ul.uid = uvl.uid AND ul.id = uvl.lbl + WHERE uvl.uid =', \auth->uid, 'AND uvl.vid IN', $_[0], ' + ORDER BY CASE WHEN ul.id < 10 THEN ul.id ELSE 10 END, ul.label' + }, @_; +} + + TUWF::get qr{/experimental/v(?:/(?<char>all|[a-z0]))?}, sub { my $opt = tuwf->validate(get => q => { onerror => undef }, @@ -101,16 +130,7 @@ TUWF::get qr{/experimental/v(?:/(?<char>all|[a-z0]))?}, sub { $count = tuwf->dbVali('SELECT count(*) FROM vn v WHERE', $where); $list = $count ? tuwf->dbPagei({results => 50, page => $opt->{p}}, ' SELECT v.id, v.title, v.original, v.c_released, v.c_popularity, v.c_votecount, v.c_rating, v.c_platforms::text[] AS platforms, v.c_languages::text[] AS lang - , vl.userlist_all, vl.userlist_obtained FROM vn v - LEFT JOIN ( - SELECT irv.vid, COUNT(*) AS userlist_all - , SUM(CASE WHEN irl.status = 1+1 THEN 1 ELSE 0 END) AS userlist_obtained - FROM rlists irl - JOIN releases_vn irv ON irv.id = irl.rid - WHERE irl.uid =', \auth->uid, ' - GROUP BY irv.vid - ) AS vl ON vl.vid = v.id WHERE', $where, ' ORDER BY', sprintf { title => 'v.title %s', @@ -123,13 +143,7 @@ TUWF::get qr{/experimental/v(?:/(?<char>all|[a-z0]))?}, sub { return tuwf->resRedirect("/v$list->[0]{id}") if $count && $count == 1 && $opt->{q} && !defined $opt->{ch}; - enrich_flatten vnlist_labels => id => vid => sub { sql ' - SELECT uvl.vid, ul.label - FROM ulist_vns_labels uvl - JOIN ulist_labels ul ON ul.uid = uvl.uid AND ul.id = uvl.lbl - WHERE uvl.uid =', \auth->uid, 'AND uvl.vid IN', $_[0], ' - ORDER BY CASE WHEN ul.id < 10 THEN ul.id ELSE 10 END, ul.label' - }, $list if auth; + enrich_userlist $list; $time = time - $time; framework_ title => 'Browse visual novels', sub { |