diff options
author | Yorhel <git@yorhel.nl> | 2009-04-01 08:49:59 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2009-04-01 08:49:59 +0200 |
commit | 33c6bd207473e8ce6ddfc0d19b34ccb139aa8521 (patch) | |
tree | d5714b79b3e02c77d78f43a1f4b34f7194068b14 /lib/VNDB/Handler | |
parent | 61c4d04980dc0376cf0032364a5952f8d828837f (diff) | |
parent | f35175b04fc9f54680ad2578a994177e4d280b09 (diff) |
Merge branch 'beta'2.3
Conflicts:
data/docs/4
Diffstat (limited to 'lib/VNDB/Handler')
-rw-r--r-- | lib/VNDB/Handler/Discussions.pm | 70 | ||||
-rw-r--r-- | lib/VNDB/Handler/Misc.pm | 12 | ||||
-rw-r--r-- | lib/VNDB/Handler/Producers.pm | 15 | ||||
-rw-r--r-- | lib/VNDB/Handler/Releases.pm | 44 | ||||
-rw-r--r-- | lib/VNDB/Handler/Tags.pm | 706 | ||||
-rw-r--r-- | lib/VNDB/Handler/ULists.pm | 33 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 20 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNBrowse.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNEdit.pm | 30 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNPage.pm | 43 |
10 files changed, 863 insertions, 112 deletions
diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm index a0260cc8..45a2e24e 100644 --- a/lib/VNDB/Handler/Discussions.pm +++ b/lib/VNDB/Handler/Discussions.pm @@ -11,7 +11,7 @@ use VNDB::Func; YAWF::register( qr{t([1-9]\d*)(?:/([1-9]\d*))?} => \&thread, qr{t([1-9]\d*)\.([1-9]\d*)} => \&redirect, - qr{t/(db|an|[vpu])([1-9]\d*)?} => \&tagbrowse, + qr{t/(db|an|[vpu])([1-9]\d*)?} => \&board, qr{t([1-9]\d*)/reply} => \&edit, qr{t([1-9]\d*)\.([1-9]\d*)/edit} => \&edit, qr{t/(db|an|[vpu])([1-9]\d*)?/new} => \&edit, @@ -23,7 +23,7 @@ sub thread { my($self, $tid, $page) = @_; $page ||= 1; - my $t = $self->dbThreadGet(id => $tid, what => 'tagtitles')->[0]; + my $t = $self->dbThreadGet(id => $tid, what => 'boardtitles')->[0]; return 404 if !$t->{id} || $t->{hidden} && !$self->authCan('boardmod'); my $p = $self->dbPostGet(tid => $tid, results => 25, page => $page); @@ -35,9 +35,9 @@ sub thread { h1 $t->{title}; h2 'Posted in'; ul; - for (sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$t->{tags}}) { + for (sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$t->{boards}}) { li; - a href => "/t/$_->{type}", $self->{discussion_tags}{$_->{type}}; + a href => "/t/$_->{type}", $self->{discussion_boards}{$_->{type}}; if($_->{iid}) { txt ' > '; a style => 'font-weight: bold', href => "/t/$_->{type}$_->{iid}", "$_->{type}$_->{iid}"; @@ -129,17 +129,17 @@ sub edit { my($self, $tid, $num) = @_; $num ||= 0; - # in case we start a new thread, parse tag - my $tag = ''; + # in case we start a new thread, parse boards + my $board = ''; if($tid !~ /^\d+$/) { return 404 if $tid =~ /(db|an)/ && $num || $tid =~ /[vpu]/ && !$num; - $tag = $tid.($num||''); + $board = $tid.($num||''); $tid = 0; $num = 0; } # get thread and post, if any - my $t = $tid && $self->dbThreadGet(id => $tid, what => 'tags')->[0]; + my $t = $tid && $self->dbThreadGet(id => $tid, what => 'boards')->[0]; return 404 if $tid && !$t->{id}; my $p = $num && $self->dbPostGet(tid => $tid, num => $num)->[0]; @@ -156,7 +156,7 @@ sub edit { $frm = $self->formValidate( !$tid || $num == 1 ? ( { name => 'title', maxlength => 50 }, - { name => 'tags', maxlength => 50 }, + { name => 'boards', maxlength => 50 }, ) : (), $self->authCan('boardmod') ? ( { name => 'locked', required => 0 }, @@ -166,14 +166,14 @@ sub edit { { name => 'msg', maxlenght => 5000 }, ); - # parse and validate the tags - my @tags; - if(!$frm->{_err} && $frm->{tags}) { - for (split /[ ,]/, $frm->{tags}) { + # parse and validate the boards + my @boards; + if(!$frm->{_err} && $frm->{boards}) { + for (split /[ ,]/, $frm->{boards}) { my($ty, $id) = ($1, $2) if /^([a-z]{1,2})([0-9]*)$/; - push @tags, [ $ty, $id ]; - push @{$frm->{_err}}, [ 'tags', 'wrongtag', $_ ] if - !$ty || !$self->{discussion_tags}{$ty} + push @boards, [ $ty, $id ]; + push @{$frm->{_err}}, [ 'boards', 'wrongboard', $_ ] if + !$ty || !$self->{discussion_boards}{$ty} || $ty eq 'an' && ($id || !$self->authCan('boardmod')) || $ty eq 'db' && $id || $ty eq 'v' && (!$id || !$self->dbVNGet(id => $id)->[0]{id}) @@ -189,7 +189,7 @@ sub edit { if(!$tid || $num == 1) { my %thread = ( title => $frm->{title}, - tags => \@tags, + boards => \@boards, hidden => $frm->{hidden}, locked => $frm->{locked}, ); @@ -217,27 +217,27 @@ sub edit { $frm->{msg} ||= $p->{msg}; $frm->{hidden} = $p->{hidden} if $num != 1 && !exists $frm->{hidden}; if($num == 1) { - $frm->{tags} ||= join ' ', sort map $_->[1]?$_->[0].$_->[1]:$_->[0], @{$t->{tags}}; + $frm->{boards} ||= join ' ', sort map $_->[1]?$_->[0].$_->[1]:$_->[0], @{$t->{boards}}; $frm->{title} ||= $t->{title}; $frm->{locked} = $t->{locked} if !exists $frm->{locked}; $frm->{hidden} = $t->{hidden} if !exists $frm->{hidden}; } } - $frm->{tags} ||= $tag; + $frm->{boards} ||= $board; $frm->{nolastmod} = 1 if $num && $self->authCan('boardmod') && !exists $frm->{nolastmod}; # generate html my $title = !$tid ? 'Start new thread' : !$num ? 'Reply to '.$t->{title} : 'Edit post'; - my $url = !$tid ? "/t/$tag/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit"; + my $url = !$tid ? "/t/$board/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit"; $self->htmlHeader(title => $title, noindex => 1); $self->htmlForm({ frm => $frm, action => $url }, $title => [ [ static => label => 'Username', content => userstr($self->authInfo->{id}, $self->authInfo->{username}) ], !$tid || $num == 1 ? ( [ input => short => 'title', name => 'Thread title' ], - [ input => short => 'tags', name => 'Tags' ], - [ static => content => 'Read <a href="/d9.2">d9.2</a> for information about how to use tags' ], + [ input => short => 'boards', name => 'Board(s)' ], + [ static => content => 'Read <a href="/d9.2">d9.2</a> for information about how to specify boards' ], $self->authCan('boardmod') ? ( [ check => name => 'Locked', short => 'locked' ], ) : (), @@ -257,7 +257,7 @@ sub edit { } -sub tagbrowse { +sub board { my($self, $type, $iid) = @_; $iid ||= ''; return 404 if $type =~ /(db|an)/ && $iid; @@ -273,14 +273,14 @@ sub tagbrowse { $self->dbVNGet(id => $iid)->[0]; return 404 if $iid && !$obj; my $ititle = $obj && ($obj->{title}||$obj->{name}||$obj->{username}); - my $title = !$obj ? $self->{discussion_tags}{$type} : 'Related discussions for '.$ititle; + my $title = !$obj ? $self->{discussion_boards}{$type} : 'Related discussions for '.$ititle; my($list, $np) = $self->dbThreadGet( type => $type, $iid ? (iid => $iid) : (), results => 50, page => $f->{p}, - what => 'firstpost lastpost tagtitles', + what => 'firstpost lastpost boardtitles', order => $type eq 'an' ? 't.id DESC' : 'tpl.date DESC', ); @@ -292,7 +292,7 @@ sub tagbrowse { p; a href => '/t', 'Discussion board'; txt ' > '; - a href => "/t/$type", $self->{discussion_tags}{$type}; + a href => "/t/$type", $self->{discussion_boards}{$type}; if($iid) { txt ' > '; a style => 'font-weight: bold', href => "/t/$type$iid", "$type$iid"; @@ -324,7 +324,7 @@ sub index { div class => 'mainbox'; h1 'Discussion board index'; p class => 'browseopts'; - a href => '/t/'.$_, $self->{discussion_tags}{$_} + a href => '/t/'.$_, $self->{discussion_boards}{$_} for (qw|an db v p u|); end; end; @@ -334,11 +334,11 @@ sub index { type => $_, results => 5, page => 1, - what => 'firstpost lastpost tagtitles', + what => 'firstpost lastpost boardtitles', order => 'tpl.date DESC', ); h1 class => 'boxtitle'; - a href => "/t/$_", $self->{discussion_tags}{$_}; + a href => "/t/$_", $self->{discussion_boards}{$_}; end; _threadlist($self, $list, {p=>1}, 0, "/t"); } @@ -368,7 +368,7 @@ sub _threadlist { td class => 'tc3'; lit userstr $o->{fuid}, $o->{fusername}; end; - td class => 'tc4', rowspan => 2; + td class => 'tc4'; lit userstr $o->{luid}, $o->{lusername}; lit ' @ '; a href => "/t$o->{id}.$o->{count}"; @@ -377,17 +377,17 @@ sub _threadlist { end; end; Tr $n % 2 ? ( class => 'odd' ) : (); - td colspan => 4, class => 'tags'; + td colspan => 4, class => 'boards'; txt ' > '; my $i = 1; - for(sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$o->{tags}}) { + for(sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } @{$o->{boards}}) { last if $i++ > 5; txt ', ' if $i > 2; a href => "/t/$_->{type}".($_->{iid}||''), - title => $_->{original}||$self->{discussion_tags}{$_->{type}}, - shorten $_->{title}||$self->{discussion_tags}{$_->{type}}, 30; + title => $_->{original}||$self->{discussion_boards}{$_->{type}}, + shorten $_->{title}||$self->{discussion_boards}{$_->{type}}, 30; } - txt ', ...' if @{$o->{tags}} > 5; + txt ', ...' if @{$o->{boards}} > 5; end; end; } diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm index 39af8155..a9a5ad14 100644 --- a/lib/VNDB/Handler/Misc.pm +++ b/lib/VNDB/Handler/Misc.pm @@ -144,12 +144,14 @@ sub homepage { # Upcoming releases div class => 'mainbox threelayout'; h1 'Upcoming releases'; - my $upcoming = $self->dbReleaseGet(results => 10, unreleased => 1); + my $upcoming = $self->dbReleaseGet(results => 10, unreleased => 1, what => 'platforms'); ul; for (@$upcoming) { li; lit datestr $_->{released}; txt ' '; + cssicon $_, $self->{platforms}{$_} for (@{$_->{platforms}}); + txt ' '; a href => "/r$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 30; end; } @@ -159,12 +161,14 @@ sub homepage { # Just released div class => 'mainbox threelayout last'; h1 'Just released'; - my $justrel = $self->dbReleaseGet(results => 10, order => 'rr.released DESC', unreleased => 0); + my $justrel = $self->dbReleaseGet(results => 10, order => 'rr.released DESC', unreleased => 0, what => 'platforms'); ul; for (@$justrel) { li; lit datestr $_->{released}; txt ' '; + cssicon $_, $self->{platforms}{$_} for (@{$_->{platforms}}); + txt ' '; a href => "/r$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 30; end; } @@ -195,7 +199,7 @@ sub history { my $obj = $type eq 'u' ? $self->dbUserGet(uid => $id)->[0] : $type eq 'p' ? $self->dbProducerGet(id => $id)->[0] : $type eq 'r' ? $self->dbReleaseGet(id => $id)->[0] : - $self->dbVNGet(id => $id)->[0]; + $type eq 'v' ? $self->dbVNGet(id => $id)->[0] : undef; my $title = $type ? 'Edit history of '.($obj->{title} || $obj->{name} || $obj->{username}) : 'Recent changes'; return 404 if $type && !$obj->{id}; @@ -258,7 +262,7 @@ sub history { } if($type eq 'v') { p class => 'browseopts'; - a !$f->{r} ? (class => 'optselected') : (), href => $u->(r => 0), 'Exclude'; + a !$f->{r} ? (class => 'optselected') : (), href => $u->(r => 0), 'Exclude edits of releases'; a $f->{r} ? (class => 'optselected') : (), href => $u->(r => 1), 'Include edits of releases'; end; } diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm index 0efd2f47..7f43dd78 100644 --- a/lib/VNDB/Handler/Producers.pm +++ b/lib/VNDB/Handler/Producers.pm @@ -21,7 +21,7 @@ sub page { my $p = $self->dbProducerGet( id => $pid, - what => 'vn'.($rev ? ' changes' : ''), + what => 'vn extended'.($rev ? ' changes' : ''), $rev ? ( rev => $rev ) : () )->[0]; return 404 if !$p->{id}; @@ -31,11 +31,12 @@ sub page { return if $self->htmlHiddenMessage('p', $p); if($rev) { - my $prev = $rev && $rev > 1 && $self->dbProducerGet(id => $pid, rev => $rev-1, what => 'changes')->[0]; + my $prev = $rev && $rev > 1 && $self->dbProducerGet(id => $pid, rev => $rev-1, what => 'changes extended')->[0]; $self->htmlRevision('p', $prev, $p, [ type => 'Type', serialize => sub { $self->{producer_types}{$_[0]} } ], [ name => 'Name (romaji)', diff => 1 ], [ original => 'Original name', diff => 1 ], + [ alias => 'Aliases', diff => 1 ], [ lang => 'Language', serialize => sub { "$_[0] ($self->{languages}{$_[0]})" } ], [ website => 'Website', diff => 1 ], [ desc => 'Description', diff => 1 ], @@ -48,6 +49,7 @@ sub page { h2 class => 'alttitle', $p->{original} if $p->{original}; p class => 'center'; txt "$self->{languages}{$p->{lang}} \L$self->{producer_types}{$p->{type}}"; + txt "\na.k.a. $p->{alias}" if $p->{alias}; if($p->{website}) { txt "\n"; a href => $p->{website}, $p->{website}; @@ -87,14 +89,14 @@ sub page { sub edit { my($self, $pid, $rev) = @_; - my $p = $pid && $self->dbProducerGet(id => $pid, what => 'changes', $rev ? (rev => $rev) : ())->[0]; + my $p = $pid && $self->dbProducerGet(id => $pid, what => 'changes extended', $rev ? (rev => $rev) : ())->[0]; return 404 if $pid && !$p->{id}; $rev = undef if !$p || $p->{cid} == $p->{latest}; return $self->htmlDenied if !$self->authCan('edit') || $pid && ($p->{locked} && !$self->authCan('lock') || $p->{hidden} && !$self->authCan('del')); - my %b4 = !$pid ? () : map { $_ => $p->{$_} } qw|type name original lang website desc|; + my %b4 = !$pid ? () : map { $_ => $p->{$_} } qw|type name original lang website desc alias|; my $frm; if($self->reqMethod eq 'POST') { @@ -102,6 +104,7 @@ sub edit { { name => 'type', enum => [ keys %{$self->{producer_types}} ] }, { name => 'name', maxlength => 200 }, { name => 'original', required => 0, maxlength => 200, default => '' }, + { name => 'alias', required => 0, maxlength => 500, default => '' }, { name => 'lang', enum => [ keys %{$self->{languages}} ] }, { name => 'website', required => 0, template => 'url', default => '' }, { name => 'desc', required => 0, maxlength => 5000, default => '' }, @@ -137,6 +140,8 @@ sub edit { [ input => name => 'Name (romaji)', short => 'name' ], [ input => name => 'Original name', short => 'original' ], [ static => content => q|The original name of the producer, leave blank if it is already in the Latin alphabet.| ], + [ input => name => 'Aliases', short => 'alias', width => 400 ], + [ static => content => q|(Un)official aliases, separated by a comma.| ], [ select => name => 'Primary language', short => 'lang', options => [ map [ $_, "$_ ($self->{languages}{$_})" ], sort keys %{$self->{languages}} ] ], [ input => name => 'Website', short => 'website' ], @@ -193,7 +198,7 @@ sub list { for ($perlist*$c..($perlist*($c+1))-1) { li; cssicon 'lang '.$list->[$_]{lang}, $self->{languages}{$list->[$_]{lang}}; - a href => "/p$list->[$_]{id}", $list->[$_]{name}; + a href => "/p$list->[$_]{id}", title => $list->[$_]{original}, $list->[$_]{name}; end; } end; diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm index 38a1fa02..8cd2eb43 100644 --- a/lib/VNDB/Handler/Releases.pm +++ b/lib/VNDB/Handler/Releases.pm @@ -21,7 +21,7 @@ sub page { my $r = $self->dbReleaseGet( id => $rid, - what => 'vn producers platforms media'.($rev ? ' changes' : ''), + what => 'vn extended producers platforms media'.($rev ? ' changes' : ''), $rev ? (rev => $rev) : (), )->[0]; return 404 if !$r->{id}; @@ -33,7 +33,7 @@ sub page { if($rev) { my $prev = $rev && $rev > 1 && $self->dbReleaseGet( id => $rid, rev => $rev-1, - what => 'vn producers platforms media changes' + what => 'vn extended producers platforms media changes' )->[0]; $self->htmlRevision('r', $prev, $r, [ vn => 'Relations', join => '<br />', split => sub { @@ -44,6 +44,7 @@ sub page { [ title => 'Title (romaji)', diff => 1 ], [ original => 'Original title', diff => 1 ], [ gtin => 'JAN/UPC/EAN', serialize => sub { $_[0]||'[none]' } ], + [ catalog => 'Catalog number', serialize => sub { $_[0]||'[none]' } ], [ language => 'Language', serialize => sub { $self->{languages}{$_[0]} } ], [ website => 'Website', ], [ released => 'Release date', htmlize => sub { datestr $_[0] } ], @@ -86,7 +87,17 @@ sub _infotable { my $i = 0; Tr ++$i % 2 ? (class => 'odd') : (); - td class => 'key', 'Title'; + td class => 'key', 'Relation'; + td; + for (@{$r->{vn}}) { + a href => "/v$_->{vid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 60; + br if $_ != $r->{vn}[$#{$r->{vn}}]; + } + end; + end; + + Tr ++$i % 2 ? (class => 'odd') : (); + td 'Title'; td $r->{title}; end; @@ -98,16 +109,6 @@ sub _infotable { } Tr ++$i % 2 ? (class => 'odd') : (); - td 'Relation'; - td; - for (@{$r->{vn}}) { - a href => "/v$_->{vid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 60; - br if $_ != $r->{vn}[$#{$r->{vn}}]; - } - end; - end; - - Tr ++$i % 2 ? (class => 'odd') : (); td 'Type'; td; my $type = $self->{release_types}[$r->{type}]; @@ -181,6 +182,13 @@ sub _infotable { end; } + if($r->{catalog}) { + Tr ++$i % 2 ? (class => 'odd') : (); + td 'Catalog no.'; + td $r->{catalog}; + end; + } + if($r->{website}) { Tr ++$i % 2 ? (class => 'odd') : (); td 'Links'; @@ -227,7 +235,7 @@ sub edit { $rid = 0; } - my $r = $rid && $self->dbReleaseGet(id => $rid, what => 'vn producers platforms media changes', $rev ? (rev => $rev) : ())->[0]; + my $r = $rid && $self->dbReleaseGet(id => $rid, what => 'vn extended producers platforms media changes', $rev ? (rev => $rev) : ())->[0]; return 404 if $rid && !$r->{id}; $rev = undef if !$r || $r->{cid} == $r->{latest}; @@ -239,7 +247,7 @@ sub edit { my $vn = $rid ? $r->{vn} : [{ vid => $vid, title => $v->{title} }]; my %b4 = !$rid ? () : ( - (map { $_ => $r->{$_} } qw|type title original gtin language website notes minage platforms patch|), + (map { $_ => $r->{$_} } qw|type title original gtin catalog language website notes minage platforms patch|), released => $r->{released} =~ /^([0-9]{4})([0-9]{2})([0-9]{2})$/ ? [ $1, $2, $3 ] : [ 0, 0, 0 ], media => join(',', sort map "$_->{medium} $_->{qty}", @{$r->{media}}), producers => join('|||', map "$_->{id},$_->{name}", sort { $a->{id} <=> $b->{id} } @{$r->{producers}}), @@ -255,6 +263,7 @@ sub edit { { name => 'original', required => 0, default => '', maxlength => 250 }, { name => 'gtin', required => 0, default => '0', func => [ \>intype, 'Not a valid JAN/UPC/EAN code' ] }, + { name => 'catalog', required => 0, default => '', maxlength => 50 }, { name => 'language', enum => [ keys %{$self->{languages}} ] }, { name => 'website', required => 0, default => '', template => 'url' }, { name => 'released', required => 0, default => 0, multi => 1, template => 'int' }, @@ -284,7 +293,7 @@ sub edit { !grep !/^(released|platforms|producers|vn)$/ && $frm->{$_} ne $b4{$_}, keys %b4; my %opts = ( - (map { $_ => $frm->{$_} } qw| type title original gtin language website notes minage platforms editsum patch|), + (map { $_ => $frm->{$_} } qw| type title original gtin catalog language website notes minage platforms editsum patch|), vn => $new_vn, producers => $producers, media => $media, @@ -322,13 +331,14 @@ sub _form { "General info" => [ [ select => short => 'type', name => 'Type', options => [ map [ $_, $self->{release_types}[$_] ], 0..$#{$self->{release_types}} ] ], - [ check => short => 'patch', name => 'This release is a patch to an other release.' ], + [ check => short => 'patch', name => 'This release is a patch to another release.' ], [ input => short => 'title', name => 'Title (romaji)', width => 300 ], [ input => short => 'original', name => 'Original title', width => 300 ], [ static => content => 'The original title of this release, leave blank if it already is in the Latin alphabet.' ], [ select => short => 'language', name => 'Language', options => [ map [ $_, "$_ ($self->{languages}{$_})" ], sort keys %{$self->{languages}} ] ], [ input => short => 'gtin', name => 'JAN/UPC/EAN' ], + [ input => short => 'catalog', name => 'Catalog number' ], [ input => short => 'website', name => 'Official website' ], [ static => label => 'Release date', content => sub { Select id => 'released', name => 'released'; diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm new file mode 100644 index 00000000..e83fcbf7 --- /dev/null +++ b/lib/VNDB/Handler/Tags.pm @@ -0,0 +1,706 @@ + +package VNDB::Handler::Tags; + + +use strict; +use warnings; +use YAWF ':html', ':xml'; +use VNDB::Func; + + +YAWF::register( + qr{g([1-9]\d*)}, \&tagpage, + qr{g([1-9]\d*)/(edit)}, \&tagedit, + qr{g([1-9]\d*)/(add)}, \&tagedit, + qr{g/new}, \&tagedit, + qr{g/list}, \&taglist, + qr{v([1-9]\d*)/tagmod}, \&vntagmod, + qr{u([1-9]\d*)/tags}, \&usertags, + qr{g}, \&tagindex, + qr{xml/tags\.xml}, \&tagxml, + qr{g/debug}, \&tagtree, +); + + +sub tagpage { + my($self, $tag) = @_; + + my $t = $self->dbTagGet(id => $tag, what => 'parents(0) childs(2) aliases')->[0]; + return 404 if !$t; + + my $f = $self->formValidate( + { name => 's', required => 0, default => 'score', enum => [ qw|score title rel pop| ] }, + { name => 'o', required => 0, default => 'd', enum => [ 'a','d' ] }, + { name => 'p', required => 0, default => 1, template => 'int' }, + { name => 'm', required => 0, default => -1, enum => [qw|0 1 2|] }, + ); + return 404 if $f->{_err}; + my $tagspoil = $self->reqCookie('tagspoil'); + $f->{m} = $tagspoil =~ /^[0-2]$/ ? $tagspoil : 1 if $f->{m} == -1; + + my($list, $np) = $t->{meta} || $t->{state} != 2 ? ([],0) : $self->dbTagVNs( + tag => $tag, + order => {score=>'tb.rating',title=>'vr.title',rel=>'v.c_released',pop=>'v.c_popularity'}->{$f->{s}}.($f->{o}eq'a'?' ASC':' DESC'), + page => $f->{p}, + results => 50, + maxspoil => $f->{m}, + ); + + my $title = ($t->{meta} ? 'Meta tag: ' : 'Tag: ').$t->{name}; + $self->htmlHeader(title => $title); + $self->htmlMainTabs('g', $t); + + if($t->{state} != 2) { + div class => 'mainbox'; + h1 "Tag: $t->{name}"; + if($t->{state} == 1) { + div class => 'warning'; + h2 'Tag deleted'; + p; + lit qq|This tag has been removed from the database, and cannot be used or re-added.|. + qq| File a request on the <a href="/t/db">discussion board</a> if you disagree with this.|; + end; + end; + } else { + div class => 'notice'; + 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.'; + end; + } + end; + return $self->htmlFooter if $t->{state} == 1 && !$self->authCan('tagmod'); + } + + div class => 'mainbox'; + a class => 'addnew', href => "/g$tag/add", ($self->authCan('tagmod')?'Create':'Request').' child tag' if $self->authCan('tag'); + h1 $title; + + p; + my @p = @{$t->{parents}}; + my @r; + for (0..$#p) { + if($_ && $p[$_-1]{lvl} < $p[$_]{lvl}) { + pop @r for (1..($p[$_]{lvl}-$p[$_-1]{lvl})); + } + if($_ < $#p && $p[$_+1]{lvl} < $p[$_]{lvl}) { + push @r, $p[$_]; + } elsif($#p == $_ || $p[$_+1]{lvl} >= $p[$_]{lvl}) { + a href => '/g', 'Tags'; + for ($p[$_], reverse @r) { + txt ' > '; + a href => "/g$_->{tag}", $_->{name}; + } + txt " > $t->{name}\n"; + } + } + if(!@p) { + a href => '/g', 'Tags'; + txt " > $t->{name}\n"; + } + end; + + if($t->{description}) { + p class => 'description'; + lit bb2html $t->{description}; + end; + } + if(@{$t->{aliases}}) { + p class => 'center'; + b "Aliases:\n"; + txt "$_\n" for (@{$t->{aliases}}); + end; + } + end; + + _childtags($self, $t) if @{$t->{childs}}; + _vnlist($self, $t, $f, $list, $np) if !$t->{meta} && $t->{state} == 2; + + $self->htmlFooter; +} + +# used for on both /g and /g+ +sub _childtags { + my($self, $t, $index) = @_; + + my @l = @{$t->{childs}}; + my @tags; + for (0..$#l) { + if($l[$_]{lvl} == $l[0]{lvl}) { + $l[$_]{childs} = []; + push @tags, $l[$_]; + } else { + push @{$tags[$#tags]{childs}}, $l[$_]; + } + } + + div class => 'mainbox'; + if(!$index) { + h1 'Child tags'; + } else { + h1 'Tag tree'; + } + ul class => 'tagtree'; + for my $p (sort { @{$b->{childs}} <=> @{$a->{childs}} } @tags) { + li; + a href => "/g$p->{tag}", $p->{name}; + b class => 'grayedout', " ($p->{c_vns})" if $p->{c_vns}; + end, next if !@{$p->{childs}}; + ul; + for (0..$#{$p->{childs}}) { + last if $_ >= 5 && @{$p->{childs}} > 6; + li; + txt '> '; + a href => "/g$p->{childs}[$_]{tag}", $p->{childs}[$_]{name}; + b class => 'grayedout', " ($p->{childs}[$_]{c_vns})" if $p->{childs}[$_]{c_vns}; + end; + } + if(@{$p->{childs}} > 6) { + li; + txt '> '; + a href => "/g$p->{tag}", style => 'font-style: italic', sprintf '%d more tags...', @{$p->{childs}}-5; + end; + } + end; + end; + } + end; + clearfloat; + br; + end; +} + +sub _vnlist { + my($self, $t, $f, $list, $np) = @_; + div class => 'mainbox'; + h1 'Visual novels'; + p class => 'browseopts'; + a href => "/g$t->{id}?m=0", $f->{m} == 0 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 0);return true;", 'Hide spoilers'; + a href => "/g$t->{id}?m=1", $f->{m} == 1 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 1);return true;", 'Show minor spoilers'; + a href => "/g$t->{id}?m=2", $f->{m} == 2 ? (class => 'optselected') : (), onclick => "setCookie('tagspoil', 2);return true;", 'Show major spoilers'; + end; + if(!@$list) { + p "\n\nThis tag has not been linked to any visual novels yet, or they were hidden because of the spoiler settings."; + } + end; + return if !@$list; + $self->htmlBrowse( + class => 'tagvnlist', + items => $list, + options => $f, + nextpage => $np, + pageurl => "/g$t->{id}?m=$f->{m};o=$f->{o};s=$f->{s}", + sorturl => "/g$t->{id}?m=$f->{m}", + header => [ + [ 'Score', 'score' ], + [ 'Title', 'title' ], + [ '', 0 ], + [ '', 0 ], + [ 'Released', 'rel' ], + [ 'Popularity', 'pop' ], + ], + row => sub { + my($s, $n, $l) = @_; + Tr $n % 2 ? (class => 'odd') : (); + td class => 'tc1'; + tagscore $l->{rating}; + i sprintf '(%d)', $l->{users}; + end; + td class => 'tc2'; + a href => '/v'.$l->{vid}, title => $l->{original}||$l->{title}, shorten $l->{title}, 100; + end; + td class => 'tc3'; + $_ ne 'oth' && cssicon $_, $self->{platforms}{$_} + for (sort split /\//, $l->{c_platforms}); + end; + td class => 'tc4'; + cssicon "lang $_", $self->{languages}{$_} + for (reverse sort split /\//, $l->{c_languages}); + end; + td class => 'tc5'; + lit monthstr $l->{c_released}; + end; + td class => 'tc6', sprintf '%.2f', $l->{c_popularity}*100; + end; + } + ); +} + + +sub tagedit { + my($self, $tag, $act) = @_; + + my($frm, $par); + if($act && $act eq 'add') { + $par = $self->dbTagGet(id => $tag)->[0]; + return 404 if !$par; + $frm->{parents} = $par->{name}; + $tag = undef; + } + + return $self->htmlDenied if !$self->authCan('tag') || $tag && !$self->authCan('tagmod'); + + my $t = $tag && $self->dbTagGet(id => $tag, what => 'parents(1) aliases')->[0]; + return 404 if $tag && !$t; + + if($self->reqMethod eq 'POST') { + $frm = $self->formValidate( + { name => 'name', required => 1, maxlength => 250, regex => [ qr/^[^,]+$/, 'A comma is not allowed in tag names' ] }, + { name => 'state', required => 0, default => 0, enum => [ 0..2 ] }, + { name => 'meta', required => 0, default => 0 }, + { name => 'alias', required => 0, maxlength => 1024, default => '', regex => [ qr/^[^,]+$/s, 'No comma allowed in aliases' ] }, + { name => 'description', required => 0, maxlength => 1024, default => '' }, + { name => 'parents', required => 0, default => '' }, + { name => 'merge', required => 0, default => '' }, + ); + my @aliases = split /[\t\s]*\n[\t\s]*/, $frm->{alias}; + my @parents = split /[\t\s]*,[\t\s]*/, $frm->{parents}; + my @merge = split /[\t\s]*,[\t\s]*/, $frm->{merge}; + if(!$frm->{_err}) { + my $c = $self->dbTagGet(name => $frm->{name}, noid => $tag); + push @{$frm->{_err}}, [ 'name', 'tagexists', $c->[0] ] if @$c; + for (@aliases) { + $c = $self->dbTagGet(name => $_, noid => $tag); + push @{$frm->{_err}}, [ 'alias', 'tagexists', $c->[0] ] if @$c; + } + for(@parents, @merge) { + my $c = $self->dbTagGet(name => $_, noid => $tag); + push @{$frm->{_err}}, [ 'parents', 'func', [ 0, "Tag '$_' not found." ]] if !@$c; + $_ = $c->[0]{id}; + } + } + if(!$frm->{_err}) { + $frm->{state} = $frm->{meta} = 0 if !$self->authCan('tagmod'); + my %opts = ( + name => $frm->{name}, + state => $frm->{state}, + description => $frm->{description}, + meta => $frm->{meta}?1:0, + aliases => \@aliases, + parents => \@parents, + ); + if(!$tag) { + $tag = $self->dbTagAdd(%opts); + $self->multiCmd("ircnotify g$tag"); + } else { + $self->dbTagEdit($tag, %opts, upddate => $frm->{state} == 2 && $t->{state} != 2); + } + $self->dbTagMerge($tag, @merge) if $self->authCan('tagmod') && @merge; + $self->resRedirect("/g$tag", 'post'); + return; + } + } + + if($tag) { + $frm->{$_} ||= $t->{$_} for (qw|name meta description state|); + $frm->{alias} ||= join "\n", @{$t->{aliases}}; + $frm->{parents} ||= join ', ', map $_->{name}, @{$t->{parents}}; + } + + my $title = $par ? "Add child tag to $par->{name}" : $tag ? "Edit tag: $t->{name}" : 'Add new tag'; + $self->htmlHeader(title => $title, noindex => 1); + $self->htmlMainTabs('g', $par || $t, 'edit') if $t || $par; + + if(!$self->authCan('tagmod')) { + div class => 'mainbox'; + h1 'Requesting new tag'; + div class => 'notice'; + h2 'Your tag must be approved'; + p 'Because all tags have to be approved by moderators, it can take a while before it '. + 'will show up in the tag list or on visual novel pages. You can still vote on tag even if '. + 'it has not been approved yet, though.'; + end; + end; + } + + $self->htmlForm({ frm => $frm, action => $par ? "/g$par->{id}/add" : $tag ? "/g$tag/edit" : '/g/new' }, $title => [ + [ input => short => 'name', name => 'Primary name' ], + $self->authCan('tagmod') ? ( + [ select => short => 'state', name => 'State', options => [ + [ 0, 'Awaiting moderation' ], [ 1, 'Deleted/hidden' ], [ 2, 'Approved' ] ] ], + [ checkbox => short => 'meta', name => 'This is a meta-tag (only to be used as parent for other tags, not for linking to VN entries)' ], + $tag ? + [ static => content => 'WARNING: Checking this option or selecting "Deleted" as state will permanently delete all existing VN relations!' ] : (), + ) : (), + [ textarea => short => 'alias', name => "Aliases\n(separated by newlines)", cols => 30, rows => 4 ], + [ textarea => short => 'description', name => 'Description' ], + [ static => content => 'What should the tag be used for? Having a good description helps users choose which tags to link to a VN.' ], + [ input => short => 'parents', name => 'Parent tags' ], + [ static => content => "Comma separated list of tag names to be used as parent for this tag." ], + $self->authCan('tagmod') ? ( + [ part => title => 'Merge tags' ], + [ input => short => 'merge', name => 'Tags to merge' ], + [ static => content => 'Comma separated list of tag names to merge into this one.' + .' All votes and aliases/names will be moved over to this tag, and the old tags will be deleted.' + .' Just leave this field empty if you don\'t intend to do a merge.' + .'<br />WARNING: this action cannot be undone!' ], + ) : (), + ]); + $self->htmlFooter; +} + + +sub taglist { + my $self = shift; + + my $f = $self->formValidate( + { name => 's', required => 0, default => 'name', enum => ['added', 'name'] }, + { name => 'o', required => 0, default => 'a', enum => ['a', 'd'] }, + { name => 'p', required => 0, default => 1, template => 'int' }, + { name => 't', required => 0, default => -1, enum => [ -1..2 ] }, + { name => 'q', required => 0, default => '' }, + ); + $f->{t} = 0 if !$self->authCan('tagmod') && $f->{t} == 1; + return 404 if $f->{_err}; + + my($t, $np) = $self->dbTagGet( + order => $f->{s}.($f->{o}eq'd'?' DESC':' ASC'), + page => $f->{p}, + results => 50, + $f->{t} != -1 || $self->authCan('tagmod') ? ( + state => $f->{t} ) : (), + search => $f->{q}, + ); + + my $title = $f->{t} == -1 ? 'Browse tags' : $f->{t} == 0 ? 'Tags awaiting moderation' : $f->{t} == 1 ? 'Deleted tags' : 'All visible tags'; + $self->htmlHeader(title => $title); + div class => 'mainbox'; + h1 $title; + form class => 'search', action => '/g/list', 'accept-charset' => 'UTF-8', method => 'get'; + fieldset; + input type => 'hidden', name => 't', value => $f->{t}; + input type => 'text', name => 'q', id => 'q', class => 'text', value => $f->{q}; + input type => 'submit', class => 'submit', value => 'Search!'; + end; + end; + p class => 'browseopts'; + a href => "/g/list?q=$f->{q};t=-1", $f->{t} == -1 ? (class => 'optselected') : (), 'All'; + a href => "/g/list?q=$f->{q};t=0", $f->{t} == 0 ? (class => 'optselected') : (), 'Awaiting moderation'; + a href => "/g/list?q=$f->{q};t=1", $f->{t} == 1 ? (class => 'optselected') : (), 'Deleted' if $self->authCan('tagmod'); + a href => "/g/list?q=$f->{q};t=2", $f->{t} == 2 ? (class => 'optselected') : (), 'Accepted'; + end; + if(!@$t) { + p 'No results found'; + } + end; + if(@$t) { + $self->htmlBrowse( + class => 'taglist', + options => $f, + nextpage => $np, + items => $t, + pageurl => "/g/list?t=$f->{t};q=$f->{q};s=$f->{s};o=$f->{o}", + sorturl => "/g/list?t=$f->{t};q=$f->{q}", + header => [ + [ 'Created', 'added' ], + [ 'Tag', 'name' ], + ], + row => sub { + my($s, $n, $l) = @_; + Tr $n % 2 ? (class => 'odd') : (); + td class => 'tc1', age $l->{added}; + td class => 'tc3'; + a href => "/g$l->{id}", $l->{name}; + if($f->{t} == -1) { + b class => 'grayedout', ' awaiting moderation' if $l->{state} == 0; + b class => 'grayedout', ' deleted' if $l->{state} == 1; + } + end; + end; + } + ); + } + $self->htmlFooter; +} + + +sub vntagmod { + my($self, $vid) = @_; + + my $v = $self->dbVNGet(id => $vid)->[0]; + return 404 if !$v; + + return $self->htmlDenied if !$self->authCan('tag'); + + if($self->reqMethod eq 'POST') { + my $frm = $self->formValidate( + { name => 'taglinks', required => 0, default => '', maxlength => 10240, regex => [ qr/^[1-9][0-9]*,-?[1-3],-?[0-2]( [1-9][0-9]*,-?[1-3],-?[0-2])*$/, 'meh' ] } + ); + return 404 if $frm->{_err}; + $self->dbTagLinkEdit($self->authInfo->{id}, $vid, [ map [ split /,/ ], split / /, $frm->{taglinks}]); + } + + my $my = $self->dbTagLinks(vid => $vid, uid => $self->authInfo->{id}); + my $tags = $self->dbTagStats(vid => $vid, results => 9999); + + my $frm; + + $self->htmlHeader(title => "Add/remove tags for $v->{title}", noindex => 1, js => 'forms'); + $self->htmlMainTabs('v', $v, 'tagmod'); + div class => 'mainbox'; + h1 "Add/remove tags for $v->{title}"; + div class => 'notice'; + h2 'Tagging'; + ul; + li; + lit 'Make sure you have read the <a href="/g10">guidelines</a>!'; + end; + li "Don't forget to hit the submit button on the bottom of the page to make your changes permanent."; + li 'Some tag information on the site is cached, it can take up to an hour for your changes to be visible everywhere.'; + end; + end; + end; + $self->htmlForm({ frm => $frm, action => "/v$vid/tagmod", hitsubmit => 1 }, 'Tags' => [ + [ hidden => short => 'taglinks', value => '' ], + [ static => nolabel => 1, content => sub { + table id => 'tagtable'; + thead; + Tr; + td ''; + td colspan => 2, class => 'tc2_1', 'You'; + td colspan => 2, class => 'tc3_1', 'Others'; + end; + Tr; + my $i=0; + td class => 'tc'.++$i, $_ for(qw|Tag Rating Spoiler Rating Spoiler|); + end; + end; + tfoot; Tr; + td colspan => 5; + input type => 'text', class => 'text', name => 'addtag', value => ''; + input type => 'button', class => 'submit', value => 'Add tag'; + br; + p; + lit 'Check the <a href="/g">tag list</a> to browse all available tags.'. + '<br />Can\'t find what you\'re looking for? <a href="/g/new">Request a new tag</a>.'; + end; + end; + end; end; + tbody; + for my $t (sort { $a->{name} cmp $b->{name} } @$tags) { + my $m = (grep $_->{tag} == $t->{id}, @$my)[0] || {}; + Tr; + td class => 'tc1'; + a href => "/g$t->{id}", $t->{name}; + end; + td class => 'tc2', $m->{vote}||0; + td class => 'tc3', defined $m->{spoiler} ? $m->{spoiler} : -1; + td class => 'tc4'; + tagscore !$m->{vote} ? $t->{rating} : $t->{cnt} == 1 ? 0 : ($t->{rating}*$t->{cnt} - $m->{vote}) / ($t->{cnt}-1); + i ' ('.($t->{cnt} - ($m->{vote} ? 1 : 0)).')'; + end; + td class => 'tc5', sprintf '%.2f', $t->{spoiler}; + end; + } + end; + end; + } ], + ]); + $self->htmlFooter; +} + + +sub usertags { + my($self, $uid) = @_; + + my $u = $self->dbUserGet(uid => $uid)->[0]; + return 404 if !$u; + + my $f = $self->formValidate( + { name => 's', required => 0, default => 'cnt', enum => [ qw|cnt name| ] }, + { name => 'o', required => 0, default => 'd', enum => [ 'a','d' ] }, + { name => 'p', required => 0, default => 1, template => 'int' }, + ); + return 404 if $f->{_err}; + + # TODO: might want to use AJAX to load the VN list on request + my($list, $np) = $self->dbTagStats( + uid => $uid, + page => $f->{p}, + order => ($f->{s}eq'cnt'?'COUNT(*)':'name').($f->{o}eq'a'?' ASC':' DESC'), + what => 'vns', + ); + + $self->htmlHeader(title => "Tags by $u->{username}", noindex => 1); + $self->htmlMainTabs('u', $u, 'tags'); + div class => 'mainbox'; + h1 "Tags by $u->{username}"; + if(@$list) { + p 'Warning: spoilery tags are not hidden in this list!'; + } else { + p "$u->{username} doesn't seem to have used the tagging system yet..."; + } + end; + + if(@$list) { + $self->htmlBrowse( + class => 'tagstats', + options => $f, + nextpage => $np, + items => $list, + pageurl => "/u$u->{id}/tags?s=$f->{s};o=$f->{o}", + sorturl => "/u$u->{id}/tags", + header => [ + sub { + td class => 'tc1'; + b id => 'relhidall'; + lit '<i>▸</i> #VNs '; + end; + lit $f->{s} eq 'cnt' && $f->{o} eq 'a' ? "\x{25B4}" : qq|<a href="/u$u->{id}/tags?o=a;s=cnt">\x{25B4}</a>|; + lit $f->{s} eq 'cnt' && $f->{o} eq 'd' ? "\x{25BE}" : qq|<a href="/u$u->{id}/tags?o=d;s=cnt">\x{25BE}</a>|; + end; + }, + [ 'Tag', 'name' ], + [ ' ', '' ], + ], + row => sub { + my($s, $n, $l) = @_; + Tr $n % 2 ? (class => 'odd') : (); + td class => 'tc1 relhid_but', id => "tag$l->{id}"; + lit "<i>▸</i> $l->{cnt}"; + end; + td class => 'tc2', colspan => 2; + a href => "/g$l->{id}", $l->{name}; + end; + end; + for(@{$l->{vns}}) { + Tr class => "relhid tag$l->{id}"; + td class => 'tc1_1'; + tagscore $_->{vote}; + end; + td class => 'tc1_2'; + a href => "/v$_->{vid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 50; + end; + td class => 'tc1_3', !defined $_->{spoiler} ? ' ' : ['No spoiler', 'Minor spoiler', 'Major spoiler']->[$_->{spoiler}]; + end; + } + }, + ); + } + $self->htmlFooter; +} + + +sub tagindex { + my $self = shift; + + $self->htmlHeader(title => 'Browse tags'); + div class => 'mainbox'; + a class => 'addnew', href => "/g/new", ($self->authCan('tagmod')?'Create':'Request').' new tag' if $self->authCan('tag'); + h1 'Search tags'; + form class => 'search', action => '/g/list', 'accept-charset' => 'UTF-8', method => 'get'; + fieldset; + input type => 'text', name => 'q', id => 'q', class => 'text'; + input type => 'submit', class => 'submit', value => 'Search!'; + end; + end; + end; + + my $t = $self->dbTagTree(0, 2, 1); + _childtags($self, {childs => $t}, 1); + + # Recently added + div class => 'mainbox threelayout'; + a class => 'right', href => '/g/list', 'Browse all tags'; + my $r = $self->dbTagGet(order => 'added DESC', results => 10, state => 2); + h1 'Recently added'; + ul; + for (@$r) { + li; + txt age $_->{added}; + txt ' '; + a href => "/g$_->{id}", $_->{name}; + end; + } + end; + end; + + # Popular + div class => 'mainbox threelayout'; + $r = $self->dbTagGet(order => 'c_vns DESC', meta => 0, results => 10); + h1 'Popular tags'; + ul; + for (@$r) { + li; + a href => "/g$_->{id}", $_->{name}; + txt " ($_->{c_vns})"; + end; + } + end; + end; + + # Moderation queue + div class => 'mainbox threelayout last'; + a class => 'right', href => '/g/list?t=0;o=d;s=added', 'Moderation queue'; + h1 'Awaiting moderation'; + $r = $self->dbTagGet(state => 0, order => 'added DESC', results => 10); + if(@$r) { + ul; + for (@$r) { + li; + txt age $_->{added}; + txt ' '; + a href => "/g$_->{id}", $_->{name}; + end; + } + end; + } else { + p 'Moderation queue empty! yay!'; + } + end; + clearfloat; + $self->htmlFooter; +} + + +sub tagxml { + my $self = shift; + + my $q = $self->formValidate({ name => 'q', maxlength => 500 }); + return 404 if $q->{_err}; + $q = $q->{q}; + + my($list, $np) = $self->dbTagGet( + $q =~ /^g([1-9]\d*)/ ? (id => $1) : $q =~ /^name:(.+)$/ ? (name => $1) : (search => $q), + results => 10, + page => 1, + ); + + $self->resHeader('Content-type' => 'text/xml; charset=UTF-8'); + xml; + tag 'tags', more => $np ? 'yes' : 'no', query => $q; + for(@$list) { + tag 'item', id => $_->{id}, meta => $_->{meta} ? 'yes' : 'no', state => $_->{state}, $_->{name}; + } + end; +} + + +sub tagtree { + my $self = shift; + + return 404 if !$self->authCan('tagmod'); + + $self->htmlHeader(title => '[DEBUG] The complete tag tree'); + div class => 'mainbox'; + h1 '[DEBUG] The complete tag tree'; + + div style => 'margin-left: 10px'; + my $t = $self->dbTagTree(0, -1, 1); + my $lvl = $t->[0]{lvl} + 1; + for (@$t) { + map ul(style => 'margin-left: 15px; list-style-type: none'), 1..($lvl-$_->{lvl}) if $lvl > $_->{lvl}; + map end, 1..($_->{lvl}-$lvl) if $lvl < $_->{lvl}; + $lvl = $_->{lvl}; + li; + txt '> '; + a href => "/g$_->{tag}", $_->{name}; + end; + } + map end, 0..($t->[0]{lvl}-$lvl); + end; + end; + $self->htmlFooter; +} + + +1; diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm index f86cba83..8f2091db 100644 --- a/lib/VNDB/Handler/ULists.pm +++ b/lib/VNDB/Handler/ULists.pm @@ -3,7 +3,7 @@ package VNDB::Handler::ULists; use strict; use warnings; -use YAWF ':html'; +use YAWF ':html', ':xml'; use VNDB::Func; @@ -11,6 +11,7 @@ YAWF::register( qr{v([1-9]\d*)/vote}, \&vnvote, qr{v([1-9]\d*)/wish}, \&vnwish, qr{r([1-9]\d*)/list}, \&rlist, + qr{xml/rlist.xml}, \&rlist, qr{u([1-9]\d*)/wish}, \&wishlist, qr{u([1-9]\d*)/list}, \&vnlist, ); @@ -55,6 +56,15 @@ sub vnwish { sub rlist { my($self, $id) = @_; + my $rid = $id; + if(!$rid) { + my $f = $self->formValidate( + { name => 'id', required => 1, template => 'int' } + ); + return 404 if $f->{_err}; + $rid = $f->{id}; + } + my $uid = $self->authInfo->{id}; return $self->htmlDenied() if !$uid; @@ -63,15 +73,24 @@ sub rlist { ); return 404 if $f->{_err}; - $self->dbVNListDel($uid, $id) if $f->{e} eq 'del'; + $self->dbVNListDel($uid, $rid) if $f->{e} eq 'del'; $self->dbVNListAdd( - rid => $id, + rid => $rid, uid => $uid, $f->{e} =~ /^([rv])(\d+)$/ && $1 eq 'r' ? (rstat => $2) : (vstat => $2) ) if $f->{e} ne 'del'; - (my $ref = $self->reqHeader('Referer')||"/r$id") =~ s/^\Q$self->{url}//; - $self->resRedirect($ref, 'temp'); + if($id) { + (my $ref = $self->reqHeader('Referer')||"/r$id") =~ s/^\Q$self->{url}//; + $self->resRedirect($ref, 'temp'); + } else { + $self->resHeader('Content-type' => 'text/xml'); + my $st = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => [$rid])->[0]; + xml; + tag 'rlist', uid => $self->authInfo->{id}, rid => $rid; + txt $st ? liststat $st : '--'; + end; + } } @@ -85,7 +104,7 @@ sub wishlist { my $f = $self->formValidate( { name => 'p', required => 0, default => 1, template => 'int' }, { name => 'o', required => 0, default => 'a', enum => [ 'a', 'd' ] }, - { name => 's', required => 0, default => 'title', enum => [qw|title added|] }, + { name => 's', required => 0, default => 'title', enum => [qw|title added wstat|] }, { name => 'f', required => 0, default => -1, enum => [ -1..$#{$self->{wishlist_status}} ] }, ); return 404 if $f->{_err}; @@ -139,7 +158,7 @@ sub wishlist { sorturl => "/u$uid/wish?f=$f->{f}", header => [ [ Title => 'title' ], - [ Priority => '' ], + [ Priority => 'wstat' ], [ Added => 'added' ], ], row => sub { diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index 2b83b4a5..3bbcbf78 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -86,6 +86,12 @@ sub userpage { end; Tr ++$i % 2 ? (class => 'odd') : (); + td 'Tags'; + td !$u->{c_tags} ? '-' : sprintf '%d votes on %d distinct tags and %d visual novels', + $u->{c_tags}, $u->{tagcount}, $u->{tagvncount}; + end; + + Tr ++$i % 2 ? (class => 'odd') : (); td 'List stats'; td !$u->{show_list} ? 'hidden' : sprintf '%d release%s of %d visual novel%s', @@ -110,7 +116,7 @@ sub userpage { } if($u->{c_changes}) { - my $list = $self->dbRevisionGet(what => 'item user', uid => $uid, results => 5); + my $list = $self->dbRevisionGet(what => 'item user', uid => $uid, results => 5, hidden => 1); h1 class => 'boxtitle'; a href => "/u$uid/hist", 'Recent changes'; end; @@ -367,8 +373,8 @@ sub edit { qq|and wishlist (<a href="/u$uid/wish">/u$uid/wish</a>)| ], [ check => short => 'flags_nsfw', name => 'Disable warnings for images that are not safe for work.' ], [ select => short => 'skin', name => 'Prefered skin', width => 300, options => [ - map [ $_ eq $self->{skin_default} ? '' : $_, $self->{skins}{$_} ], sort { $self->{skins}{$a} cmp $self->{skins}{$b} } keys %{$self->{skins}} ] ], - [ textarea => short => 'customcss', name => 'Additional CSS' ], + map [ $_ eq $self->{skin_default} ? '' : $_, $self->{skins}{$_}.($self->debug?" [$_]":'') ], sort { $self->{skins}{$a} cmp $self->{skins}{$b} } keys %{$self->{skins}} ] ], + [ textarea => short => 'customcss', name => 'Additional <a href="http://en.wikipedia.org/wiki/Cascading_Style_Sheets">CSS</a>' ], ]); $self->htmlFooter; } @@ -417,7 +423,7 @@ sub list { my($self, $char) = @_; my $f = $self->formValidate( - { name => 's', required => 0, default => 'username', enum => [ qw|username registered votes changes| ] }, + { name => 's', required => 0, default => 'username', enum => [ qw|username registered votes changes tags| ] }, { name => 'o', required => 0, default => 'a', enum => [ 'a','d' ] }, { name => 'p', required => 0, default => 1, template => 'int' }, ); @@ -435,7 +441,7 @@ sub list { end; my($list, $np) = $self->dbUserGet( - order => ($f->{s} eq 'changes' ? 'c_' : $f->{s} eq 'votes' ? 'NOT show_list, c_' : '').$f->{s}.($f->{o} eq 'a' ? ' ASC' : ' DESC'), + order => ($f->{s} eq 'changes' || $f->{s} eq 'tags' ? 'c_' : $f->{s} eq 'votes' ? 'NOT show_list, c_' : '').$f->{s}.($f->{o} eq 'a' ? ' ASC' : ' DESC'), $char ne 'all' ? ( firstchar => $char ) : (), results => 50, @@ -453,6 +459,7 @@ sub list { [ 'Registered', 'registered' ], [ 'Votes', 'votes' ], [ 'Edits', 'changes' ], + [ 'Tags', 'tags' ], ], row => sub { my($s, $n, $l) = @_; @@ -468,6 +475,9 @@ sub list { td class => 'tc4'; lit !$l->{c_changes} ? 0 : qq|<a href="/u$l->{id}/hist">$l->{c_changes}</a>|; end; + td class => 'tc5'; + lit !$l->{c_tags} ? 0 : qq|<a href="/u$l->{id}/tags">$l->{c_tags}</a>|; + end; end; }, ); diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm index d7cacaa0..f50a8500 100644 --- a/lib/VNDB/Handler/VNBrowse.pm +++ b/lib/VNDB/Handler/VNBrowse.pm @@ -34,7 +34,7 @@ sub list { if($q) { # VNDBID return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp') - if $q =~ /^([vrptud])([0-9]+)(?:\.([0-9]+))?$/; + if $q =~ /^([gvrptud])([0-9]+)(?:\.([0-9]+))?$/; if(!($q =~ s/^title://)) { # categories diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm index 6790ae6e..8363b500 100644 --- a/lib/VNDB/Handler/VNEdit.pm +++ b/lib/VNDB/Handler/VNEdit.pm @@ -27,7 +27,6 @@ sub edit { my %b4 = !$vid ? () : ( (map { $_ => $v->{$_} } qw|title original desc alias length l_wp l_encubed l_renai l_vnn img_nsfw|), anime => join(' ', sort { $a <=> $b } map $_->{id}, @{$v->{anime}}), - categories => join(',', map $_->[0].$_->[1], sort { $a->[0] cmp $b->[0] } @{$v->{categories}}), relations => join('|||', map $_->{relation}.','.$_->{id}.','.$_->{title}, sort { $a->{id} <=> $b->{id} } @{$v->{relations}}), screenshots => join(' ', map sprintf('%d,%d,%d', $_->{id}, $_->{nsfw}?1:0, $_->{rid}), @{$v->{screenshots}}), ); @@ -45,7 +44,6 @@ sub edit { { name => 'l_renai', required => 0, default => '', maxlength => 100 }, { name => 'l_vnn', required => 0, default => 0, template => 'int' }, { name => 'anime', required => 0, default => '' }, - { name => 'categories', required => 0, default => '', maxlength => 1000 }, { name => 'img_nsfw', required => 0, default => 0 }, { name => 'relations', required => 0, default => '', maxlength => 5000 }, { name => 'screenshots', required => 0, default => '', maxlength => 1000 }, @@ -58,7 +56,6 @@ sub edit { if(!$frm->{_err}) { # parse and re-sort fields that have multiple representations of the same information my $anime = [ grep /^[0-9]+$/, split /[ ,]+/, $frm->{anime} ]; - my $categories = [ map { [ substr($_,0,3), substr($_,3,1) ] } split /,/, $frm->{categories} ]; my $relations = [ map { /^([0-9]+),([0-9]+),(.+)$/ && (!$vid || $2 != $vid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{relations} ]; my $screenshots = [ map /^[0-9]+,[01],[0-9]+$/ ? [split /,/] : (), split / +/, $frm->{screenshots} ]; @@ -75,7 +72,7 @@ sub edit { my %args = ( (map { $_ => $frm->{$_} } qw|title original alias desc length l_wp l_encubed l_renai l_vnn editsum img_nsfw|), anime => $anime, - categories => $categories, + categories => $v->{categories}, relations => $relations, image => $image, screenshots => $screenshots, @@ -181,31 +178,6 @@ sub _form { |], ], - 'Categories' => [ - [ hidden => short => 'categories' ], - [ static => nolabel => 1, content => sub { - lit 'Please read the <a href="/d1">category descriptions</a> before modifying categories!<br /><br />'; - ul; - for my $c (qw| e g t p h l s |) { - $c !~ /[thl]/ ? li : br; - txt $self->{categories}{$c}[0]; - a href => "/d1#$self->{categories}{$c}[2]", class => 'help', '?'; - ul; - for (sort keys %{$self->{categories}{$c}[1]}) { - li; - a href => "#", id => "cat_$c$_"; - b id => "b_$c$_", '-'; - txt ' '.$self->{categories}{$c}[1]{$_}; - end; - end; - } - end; - end if $c !~ /[gph]/; - } - end; - }], - ], - 'Image' => [ [ static => nolabel => 1, content => sub { div class => 'img'; diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 6c336160..d5d1c0a1 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -125,16 +125,39 @@ sub page { _anime($self, \$i, $v) if @{$v->{anime}}; _useroptions($self, \$i, $v) if $self->authInfo->{id}; - end; - end; + Tr; + td class => 'vndesc', colspan => 2; + h2 'Description'; + p; + lit bb2html $v->{desc}; + end; + end; + end; - # description - div class => 'vndescription'; - h2 'Description'; - p; - lit bb2html $v->{desc}; end; end; + clearfloat; + + # tags + my $t = $self->dbTagStats(vid => $v->{id}, order => 'avg(tv.vote) DESC', minrating => 0, results => 999); + if(@$t) { + div id => 'tagops'; + a href => '#', 'hide spoilers'; + a href => '#', class => 'tsel', 'show minor spoilers'; + a href => '#', 'spoil me!'; + a href => '#', class => 'sec', 'summary'; + a href => '#', 'all'; + end; + div id => 'vntags'; + for (@$t) { + span class => sprintf 'tagspl%.0f %s', $_->{spoiler}, $_->{spoiler} > 1 ? 'hidden' : ''; + a href => "/g$_->{id}", style => sprintf('font-size: %dpx', $_->{rating}*3.5+6), $_->{name}; + b class => 'grayedout', sprintf ' %.1f', $_->{rating}; + end; + txt ' '; + } + end; + } end; _releases($self, $v, $r); @@ -252,7 +275,8 @@ sub _categories { Tr ++$$i % 2 ? (class => 'odd') : (); td 'Categories'; td; - dl; + dl id => 'vncats', style => 'display: none'; + dt 'Note:'; dd "The category system is outdated, please use tags instead.\n\n"; for (@cat) { dt shift(@$_).':'; dd; @@ -260,6 +284,7 @@ sub _categories { end; } end; + a href => '#', onclick => "document.getElementById('vncats').style.display='';this.style.display='none';return false", 'Show categories'; end; end; } @@ -407,7 +432,7 @@ sub _releases { end; td class => 'tc4'; a href => "/r$rel->{id}", title => $rel->{original}||$rel->{title}, $rel->{title}; - b class => 'patch', ' (patch)' if $rel->{patch}; + b class => 'grayedout', ' (patch)' if $rel->{patch}; end; td class => 'tc5'; if($self->authInfo->{id}) { |