summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2021-01-09 09:41:55 +0100
committerYorhel <git@yorhel.nl>2021-01-09 11:02:18 +0100
commit1ec958ba9d2e40c65246bd29f28b2b50364b2a5a (patch)
treed25ef02ca17f339bfc773f648c7392f5a5ae757e
parentcf74837d76e2d5b00eb3dadda0b472c4b3114ce4 (diff)
v2rw/TagPage: Add /experimental/ tag page rewrite with AdvSearch support
-rw-r--r--lib/VNWeb/TT/Index.pm44
-rw-r--r--lib/VNWeb/TT/Lib.pm50
-rw-r--r--lib/VNWeb/TT/TagPage.pm185
-rw-r--r--lib/VNWeb/VN/List.pm52
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 {