summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Multi/Feed.pm170
-rw-r--r--lib/VNDB/DB/Releases.pm63
-rw-r--r--lib/VNDB/DB/Users.pm16
-rw-r--r--lib/VNDB/DB/VN.pm16
-rw-r--r--lib/VNDB/Func.pm34
-rw-r--r--lib/VNDB/Handler/Discussions.pm4
-rw-r--r--lib/VNDB/Handler/Misc.pm29
-rw-r--r--lib/VNDB/Handler/Producers.pm8
-rw-r--r--lib/VNDB/Handler/Releases.pm195
-rw-r--r--lib/VNDB/Handler/VNBrowse.pm114
-rw-r--r--lib/VNDB/Handler/VNPage.pm4
-rw-r--r--lib/VNDB/Plugin/TransAdmin.pm2
-rw-r--r--lib/VNDB/Util/CommonHTML.pm9
-rw-r--r--lib/VNDB/Util/LayoutHTML.pm6
-rw-r--r--lib/VNDBUtil.pm127
15 files changed, 453 insertions, 344 deletions
diff --git a/lib/Multi/Feed.pm b/lib/Multi/Feed.pm
new file mode 100644
index 00000000..c97d87d8
--- /dev/null
+++ b/lib/Multi/Feed.pm
@@ -0,0 +1,170 @@
+
+#
+# Multi::Feed - Generates and updates Atom feeds
+#
+
+package Multi::Feed;
+
+use strict;
+use warnings;
+use POE;
+use XML::Writer;
+use POSIX 'strftime';
+use Time::HiRes 'time';
+use VNDBUtil 'bb2html';
+
+
+sub spawn {
+ my $p = shift;
+ POE::Session->create(
+ package_states => [
+ $p => [qw| _start shutdown generate write_atom log_stats |],
+ ],
+ heap => {
+ regenerate_interval => 900, # 15 min.
+ stats_interval => 86400, # daily
+ debug => 0,
+ @_,
+ stats => {}, # key = feed, value = [ count, total, max ]
+ },
+ );
+}
+
+
+sub _start {
+ $_[KERNEL]->alias_set('feed');
+ $_[KERNEL]->yield('generate');
+ $_[KERNEL]->alarm(log_stats => int((time+3)/$_[HEAP]{stats_interval}+1)*$_[HEAP]{stats_interval});
+ $_[KERNEL]->sig(shutdown => 'shutdown');
+}
+
+
+sub shutdown {
+ $_[KERNEL]->delay('generate');
+ $_[KERNEL]->delay('log_stats');
+ $_[KERNEL]->alias_remove('feed');
+}
+
+
+sub generate {
+ $_[KERNEL]->alarm(generate => int((time+3)/$_[HEAP]{regenerate_interval}+1)*$_[HEAP]{regenerate_interval});
+
+ # announcements
+ $_[KERNEL]->post(pg => query => q{
+ SELECT '/t'||t.id AS id, t.title, extract('epoch' from tp.date) AS published,
+ extract('epoch' from tp.edited) AS updated, u.username, u.id AS uid, tp.msg AS summary
+ FROM threads t
+ JOIN threads_posts tp ON tp.tid = t.id AND tp.num = 1
+ JOIN threads_boards tb ON tb.tid = t.id AND tb.type = 'an'
+ JOIN users u ON u.id = tp.uid
+ WHERE NOT t.hidden
+ ORDER BY t.id DESC
+ LIMIT ?}, [ $VNDB::S{atom_feeds}{announcements}[0] ], 'write_atom', 'announcements'
+ );
+
+ # changes
+ $_[KERNEL]->post(pg => query => q{
+ SELECT '/'||c.type||COALESCE(vr.vid, rr.rid, pr.pid)||'.'||c.rev AS id,
+ COALESCE(vr.title, rr.title, pr.name) AS title, extract('epoch' from c.added) AS updated,
+ u.username, u.id AS uid, c.comments AS summary
+ FROM changes c
+ LEFT JOIN vn_rev vr ON c.type = 'v' AND c.id = vr.id
+ LEFT JOIN releases_rev rr ON c.type = 'r' AND c.id = rr.id
+ LEFT JOIN producers_rev pr ON c.type = 'p' AND c.id = pr.id
+ JOIN users u ON u.id = c.requester
+ WHERE c.requester <> 1
+ ORDER BY c.id DESC
+ LIMIT ?}, [ $VNDB::S{atom_feeds}{changes}[0] ], 'write_atom', 'changes'
+ );
+
+ # posts (this query isn't all that fast)
+ $_[KERNEL]->post(pg => query => q{
+ SELECT '/t'||t.id||'.'||tp.num AS id, t.title||' (#'||tp.num||')' AS title, extract('epoch' from tp.date) AS published,
+ extract('epoch' from tp.edited) AS updated, u.username, u.id AS uid, tp.msg AS summary
+ FROM threads_posts tp
+ JOIN threads t ON t.id = tp.tid
+ JOIN users u ON u.id = tp.uid
+ WHERE NOT tp.hidden AND NOT t.hidden
+ ORDER BY tp.date DESC
+ LIMIT ?}, [ $VNDB::S{atom_feeds}{posts}[0] ], 'write_atom', 'posts'
+ );
+}
+
+
+sub write_atom { # num, res, feed, time
+ my $r = $_[ARG1];
+ my $feed = $_[ARG2];
+
+ my $start = time;
+
+ my $updated = 0;
+ for(@$r) {
+ $updated = $_->{published} if $_->{published} && $_->{published} > $updated;
+ $updated = $_->{updated} if $_->{updated} && $_->{updated} > $updated;
+ }
+
+ my $data;
+ my $x = XML::Writer->new(OUTPUT => \$data, DATA_MODE => 1, DATA_INDENT => 2);
+ $x->xmlDecl('UTF-8');
+ $x->startTag(feed => xmlns => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en', 'xml:base' => $VNDB::S{url}.'/');
+ $x->dataElement(title => $VNDB::S{atom_feeds}{$feed}[1]);
+ $x->dataElement(updated => datetime($updated));
+ $x->dataElement(id => $VNDB::S{url}.$VNDB::S{atom_feeds}{$feed}[2]);
+ $x->emptyTag(link => rel => 'self', type => 'application/atom+xml', href => "$VNDB::S{url}/feeds/$feed.atom");
+ $x->emptyTag(link => rel => 'alternate', type => 'text/html', href => $VNDB::S{atom_feeds}{$feed}[2]);
+
+ for(@$r) {
+ $x->startTag('entry');
+ $x->dataElement(id => $VNDB::S{url}.$_->{id});
+ $x->dataElement(title => $_->{title});
+ $x->dataElement(updated => $_->{updated}?datetime($_->{updated}):datetime($_->{published}));
+ $x->dataElement(published => datetime($_->{published})) if $_->{published};
+ if($_->{username}) {
+ $x->startTag('author');
+ $x->dataElement(name => $_->{username});
+ $x->dataElement(uri => '/u'.$_->{uid}) if $_->{uid};
+ $x->endTag('author');
+ }
+ $x->emptyTag(link => rel => 'alternate', type => 'text/html', href => $_->{id});
+ $x->dataElement(summary => bb2html($_->{summary}, 200), type => 'html') if $_->{summary};
+ $x->endTag('entry');
+ }
+
+ $x->endTag('feed');
+
+ open my $f, '>:utf8', "$VNDB::ROOT/www/feeds/$feed.atom" || die $!;
+ print $f $data;
+ close $f;
+
+ $_[HEAP]{debug} && $_[KERNEL]->call(core => log => 'Wrote %s.atom (%d entries, sql:%4dms, perl:%4dms)',
+ $feed, scalar(@$r), $_[ARG3]*1000, (time-$start)*1000);
+
+ $_[HEAP]{stats}{$feed} = [ 0, 0, 0 ] if !$_[HEAP]{stats}{$feed};
+ my $time = ((time-$start)+$_[ARG3])*1000;
+ $_[HEAP]{stats}{$feed}[0]++;
+ $_[HEAP]{stats}{$feed}[1] += $time;
+ $_[HEAP]{stats}{$feed}[2] = $time if $_[HEAP]{stats}{$feed}[2] < $time;
+}
+
+
+sub log_stats {
+ $_[KERNEL]->alarm(log_stats => int((time+3)/$_[HEAP]{stats_interval}+1)*$_[HEAP]{stats_interval});
+
+ for (keys %{$_[HEAP]{stats}}) {
+ my $v = $_[HEAP]{stats}{$_};
+ next if !$v->[0];
+ $_[KERNEL]->call(core => log => 'Stats summary for %s.atom: total:%5dms, avg:%4dms, max:%4dms, size: %.1fkB',
+ $_, $v->[1], $v->[1]/$v->[0], $v->[2], (-s "$VNDB::ROOT/www/feeds/$_.atom")/1024);
+ }
+ $_[HEAP]{stats} = {};
+}
+
+
+# non-POE helper function
+sub datetime {
+ strftime('%Y-%m-%dT%H:%M:%SZ', gmtime shift);
+}
+
+
+1;
+
diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm
index 01ca6b44..b0fb9a89 100644
--- a/lib/VNDB/DB/Releases.pm
+++ b/lib/VNDB/DB/Releases.pm
@@ -10,8 +10,8 @@ use VNDB::Func 'gtintype';
our @EXPORT = qw|dbReleaseGet dbReleaseRevisionInsert|;
-# Options: id vid pid rev unreleased page results what date media sort reverse
-# platforms languages type minage search resolutions freeware doujin
+# Options: id vid pid rev unreleased page results what med sort reverse date_before date_after
+# plat lang olang type minage search resolution freeware doujin voiced ani_story ani_ero
# What: extended changes vn producers platforms media
# Sort: title released minage
sub dbReleaseGet {
@@ -21,33 +21,42 @@ sub dbReleaseGet {
$o{what} ||= '';
my @where = (
- !$o{id} && !$o{rev} ? ( 'r.hidden = FALSE' => 0 ) : (),
- $o{id} ? ( 'r.id = ?' => $o{id} ) : (),
- $o{rev} ? ( 'c.rev = ?' => $o{rev} ) : (),
- $o{vid} ? ( 'rv.vid = ?' => $o{vid} ) : (),
- $o{pid} ? ( 'rp.pid = ?' => $o{pid} ) : (),
- $o{patch} ? ( 'rr.patch = ?' => $o{patch} == 1 ? 1 : 0) : (),
- $o{freeware} ? ( 'rr.freeware = ?' => $o{freeware} == 1 ? 1 : 0) : (),
- $o{doujin} ? ( 'rr.doujin = ?' => $o{doujin} == 1 ? 1 : 0) : (),
- defined $o{unreleased} ? (
- q|rr.released !s ?| => [ $o{unreleased} ? '>' : '<=', strftime('%Y%m%d', gmtime) ] ) : (),
- $o{date} ? (
- '(rr.released >= ? AND rr.released <= ?)' => [ $o{date}[0], $o{date}[1] ] ) : (),
- $o{languages} ? (
- 'rr.id IN(SELECT irl.rid FROM releases_lang irl JOIN releases ir ON ir.latest = irl.rid WHERE irl.lang IN(!l))', => [ $o{languages} ] ) : (),
- $o{platforms} ? (
- #'EXISTS(SELECT 1 FROM releases_platforms rp WHERE rp.rid = rr.id AND rp.platform IN(!l))' => [ $o{platforms} ] ) : (),
- 'rr.id IN(SELECT irp.rid FROM releases_platforms irp JOIN releases ir ON ir.latest = irp.rid WHERE irp.platform IN(!l))' => [ $o{platforms} ] ) : (),
- defined $o{type} ? (
- 'rr.type = ?' => $o{type} ) : (),
- $o{minage} ? (
- 'rr.minage !s ?' => [ $o{minage}[0] ? '<=' : '>=', $o{minage}[1] ] ) : (),
- $o{media} ? (
- 'rr.id IN(SELECT irm.rid FROM releases_media irm JOIN releases ir ON ir.latest = irm.rid WHERE irm.medium IN(!l))' => [ $o{media} ] ) : (),
- $o{resolutions} ? (
- 'rr.resolution IN(!l)' => [ $o{resolutions} ] ) : (),
+ !$o{id} && !$o{rev} ? ( 'r.hidden = FALSE' => 0 ) : (),
+ $o{id} ? ( 'r.id = ?' => $o{id} ) : (),
+ $o{rev} ? ( 'c.rev = ?' => $o{rev} ) : (),
+ $o{vid} ? ( 'rv.vid = ?' => $o{vid} ) : (),
+ $o{pid} ? ( 'rp.pid = ?' => $o{pid} ) : (),
+ defined $o{patch} ? ( 'rr.patch = ?' => $o{patch} == 1 ? 1 : 0) : (),
+ defined $o{freeware} ? ( 'rr.freeware = ?' => $o{freeware} == 1 ? 1 : 0) : (),
+ defined $o{doujin} ? ( 'rr.doujin = ?' => $o{doujin} == 1 ? 1 : 0) : (),
+ defined $o{type} ? ( 'rr.type = ?' => $o{type} ) : (),
+ defined $o{date_before} ? ( 'rr.released <= ?' => $o{date_before} ) : (),
+ defined $o{date_after} ? ( 'rr.released >= ?' => $o{date_after} ) : (),
+ defined $o{resolution} ? ( 'rr.resolution IN(!l)' => [ ref $o{resolution} ? $o{resolution} : [$o{resolution}] ] ) : (),
+ defined $o{voiced} ? ( 'rr.voiced IN(!l)' => [ ref $o{voiced} ? $o{voiced} : [$o{voiced}] ] ) : (),
+ defined $o{ani_story} ? ( 'rr.ani_story IN(!l)' => [ ref $o{ani_story} ? $o{ani_story} : [$o{ani_story}] ] ) : (),
+ defined $o{ani_ero} ? ( 'rr.ani_ero IN(!l)' => [ ref $o{ani_ero} ? $o{ani_ero} : [$o{ani_ero}] ] ) : (),
+ defined $o{unreleased} ? ( 'rr.released !s ?' => [ $o{unreleased} ? '>' : '<=', strftime('%Y%m%d', gmtime) ] ) : (),
+ $o{lang} ? (
+ 'rr.id IN(SELECT irl.rid FROM releases_lang irl JOIN releases ir ON ir.latest = irl.rid WHERE irl.lang IN(!l))' => [ ref $o{lang} ? $o{lang} : [ $o{lang} ] ] ) : (),
+ $o{olang} ? (
+ 'rr.id IN(SELECT irv.rid FROM releases_vn irv JOIN releases ir ON ir.latest = irv.rid JOIN vn v ON irv.vid = v.id WHERE v.c_olang && ARRAY[!l]::language[])' => [ ref $o{olang} ? $o{olang} : [ $o{olang} ] ] ) : (),
+ $o{plat} ? (
+ 'rr.id IN(SELECT irp.rid FROM releases_platforms irp JOIN releases ir ON ir.latest = irp.rid WHERE irp.platform IN(!l))' => [ ref $o{plat} ? $o{plat} : [ $o{plat} ] ] ) : (),
+ $o{med} ? (
+ 'rr.id IN(SELECT irm.rid FROM releases_media irm JOIN releases ir ON ir.latest = irm.rid WHERE irm.medium IN(!l))' => [ ref $o{med} ? $o{med} : [ $o{med} ] ] ) : (),
);
+ # TODO: don't allow NULL for rr.minage after all, since this could be a lot easier...
+ if(exists $o{minage}) {
+ my @m = ref $o{minage} ? @{$o{minage}} : ($o{minage});
+ my @w = (
+ grep(!defined $_ || $_ == -1, @m) ? 'rr.minage IS NULL' : (),
+ grep(defined $_ && $_ != -1, @m) ? 'rr.minage IN(!s)' : ()
+ );
+ push @where, '('.join(' OR ', @w).')', [ grep defined $_ && $_ != -1, @m ];
+ }
+
if($o{search}) {
for (split /[ -,._]/, $o{search}) {
s/%//g;
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm
index b9d66200..7440f495 100644
--- a/lib/VNDB/DB/Users.pm
+++ b/lib/VNDB/DB/Users.pm
@@ -123,21 +123,7 @@ sub dbUserAdd {
# uid
sub dbUserDel {
- my($s, $id) = @_;
- # TODO: delete/update all those referenced rows using PgSQL reference actions
- $s->dbExec($_, $id) for (
- q|DELETE FROM rlists WHERE uid = ?|,
- q|DELETE FROM wlists WHERE uid = ?|,
- q|DELETE FROM votes WHERE uid = ?|,
- q|DELETE FROM tags_vn WHERE uid = ?|,
- q|DELETE FROM sessions WHERE uid = ?|,
- q|DELETE FROM notifications WHERE uid = ?|,
- q|UPDATE notifications SET c_byuser = 0 WHERE c_byuser = ?|,
- q|UPDATE changes SET requester = 0 WHERE requester = ?|,
- q|UPDATE tags SET addedby = 0 WHERE addedby = ?|,
- q|UPDATE threads_posts SET uid = 0 WHERE uid = ?|,
- q|DELETE FROM users WHERE id = ?|
- );
+ $_[0]->dbExec(q|DELETE FROM users WHERE id = ?|, $_[1]);
}
diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm
index 6d99ba8a..d25a5796 100644
--- a/lib/VNDB/DB/VN.pm
+++ b/lib/VNDB/DB/VN.pm
@@ -10,7 +10,7 @@ use Encode 'decode_utf8';
our @EXPORT = qw|dbVNGet dbVNRevisionInsert dbVNImageId dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|;
-# Options: id, rev, char, search, lang, platform, tags_include, tags_exclude, results, page, what, sort, reverse
+# Options: id, rev, char, search, length, lang, olang, plat, tags_include, tags_exclude, hasani, results, page, what, sort, reverse
# What: extended anime relations screenshots relgraph rating ranking changes
# Sort: id rel pop rating title tagscore rand
sub dbVNGet {
@@ -29,10 +29,16 @@ sub dbVNGet {
'LOWER(SUBSTR(vr.title, 1, 1)) = ?' => $o{char} ) : (),
defined $o{char} && !$o{char} ? (
'(ASCII(vr.title) < 97 OR ASCII(vr.title) > 122) AND (ASCII(vr.title) < 65 OR ASCII(vr.title) > 90)' => 1 ) : (),
- $o{lang} && @{$o{lang}} ? (
- 'v.c_languages && ARRAY[!l]::language[]' => [ $o{lang} ]) : (),
- $o{platform} && @{$o{platform}} ? (
- '('.join(' OR ', map "v.c_platforms ILIKE '%%$_%%'", @{$o{platform}}).')' => 1 ) : (),
+ defined $o{length} ? (
+ 'vr.length IN(!l)' => [ ref $o{length} ? $o{length} : [$o{length}] ]) : (),
+ $o{lang} ? (
+ 'v.c_languages && ARRAY[!l]::language[]' => [ ref $o{lang} ? $o{lang} : [$o{lang}] ]) : (),
+ $o{olang} ? (
+ 'v.c_olang && ARRAY[!l]::language[]' => [ ref $o{olang} ? $o{olang} : [$o{olang}] ]) : (),
+ $o{plat} ? (
+ '('.join(' OR ', map "v.c_platforms ILIKE '%%$_%%'", ref $o{plat} ? @{$o{plat}} : $o{plat}).')' => 1 ) : (),
+ defined $o{hasani} ? (
+ '!sEXISTS(SELECT 1 FROM vn_anime va WHERE va.vid = vr.id)' => [ $o{hasani} ? '' : 'NOT ' ]) : (),
$o{tags_include} && @{$o{tags_include}} ? (
'v.id IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l) AND spoiler <= ? GROUP BY vid HAVING COUNT(tag) = ?)',
[ $o{tags_include}[1], $o{tags_include}[0], $#{$o{tags_include}[1]}+1 ]
diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm
index f3f9d009..65b66f9e 100644
--- a/lib/VNDB/Func.pm
+++ b/lib/VNDB/Func.pm
@@ -7,7 +7,13 @@ use YAWF ':html';
use Exporter 'import';
use POSIX 'strftime', 'ceil', 'floor';
use VNDBUtil;
-our @EXPORT = (@VNDBUtil::EXPORT, qw| liststat clearfloat cssicon tagscore mt minage |);
+our @EXPORT = (@VNDBUtil::EXPORT, qw| liststat clearfloat cssicon tagscore mt minage fil_parse fil_serialize |);
+
+
+# three ways to represent the same information
+our $fil_escape = '_ !"#$%&\'()*+,-./:;<=>?@[\]^`{}~';
+our @fil_escape = split //, $fil_escape;
+our %fil_escape = map +($fil_escape[$_], sprintf '%02d', $_), 0..$#fil_escape;
# Argument: hashref with rstat and vstat
@@ -89,5 +95,31 @@ sub minage {
}
+# arguments: $filter_string, @allowed_keys
+sub fil_parse {
+ my $str = shift;
+ my %keys = map +($_,1), @_;
+ my %r;
+ for (split /\./, $str) {
+ next if !/^([a-z0-9_]+)-([a-zA-Z0-9_~]+)$/ || !$keys{$1};
+ my($f, $v) = ($1, $2);
+ my @v = split /~/, $v;
+ s/_([0-9]{2})/$1 > $#fil_escape ? '' : $fil_escape[$1]/eg for(@v);
+ $r{$f} = @v > 1 ? \@v : $v[0]
+ }
+ return \%r;
+}
+
+
+sub fil_serialize {
+ my $fil = shift;
+ my $e = qr/([\Q$fil_escape\E])/;
+ return join '.', map {
+ my @v = ref $fil->{$_} ? @{$fil->{$_}} : ($fil->{$_});
+ s/$e/_$fil_escape{$1}/g for(@v);
+ $_.'-'.join '~', @v
+ } keys %$fil;
+}
+
1;
diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm
index 8f7ae76c..4cd9f04d 100644
--- a/lib/VNDB/Handler/Discussions.pm
+++ b/lib/VNDB/Handler/Discussions.pm
@@ -294,7 +294,7 @@ sub board {
sort => $type eq 'an' ? 'id' : 'lastpost', reverse => 1,
);
- $self->htmlHeader(title => $title, noindex => 1);
+ $self->htmlHeader(title => $title, noindex => 1, feeds => [ $type eq 'an' ? 'announcements' : 'posts' ]);
$self->htmlMainTabs($type, $obj, 'disc') if $iid;
div class => 'mainbox';
@@ -330,7 +330,7 @@ sub board {
sub index {
my $self = shift;
- $self->htmlHeader(title => mt('_disindex_title'), noindex => 1);
+ $self->htmlHeader(title => mt('_disindex_title'), noindex => 1, feeds => [ 'posts', 'announcements' ]);
div class => 'mainbox';
h1 mt '_disindex_title';
p class => 'browseopts';
diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm
index 87839730..b26868d5 100644
--- a/lib/VNDB/Handler/Misc.pm
+++ b/lib/VNDB/Handler/Misc.pm
@@ -15,7 +15,7 @@ YAWF::register(
qr{d([1-9]\d*)}, \&docpage,
qr{setlang}, \&setlang,
qr{nospam}, \&nospam,
- qr{we-dont-like-ie6}, \&ie6message,
+ qr{we-dont-like-ie}, \&iemessage,
qr{opensearch\.xml}, \&opensearch,
# redirects for old URLs
@@ -37,7 +37,7 @@ YAWF::register(
sub homepage {
my $self = shift;
- $self->htmlHeader(title => mt '_site_title');
+ $self->htmlHeader(title => mt('_site_title'), feeds => [ keys %{$self->{atom_feeds}} ]);
div class => 'mainbox';
h1 mt '_site_title';
@@ -63,7 +63,8 @@ sub homepage {
# Recent changes
td;
h1;
- a href => '/hist', mt '_home_recentchanges';
+ a href => '/hist', mt '_home_recentchanges'; txt ' ';
+ a href => '/feeds/changes.atom'; cssicon 'feed', mt '_atom_feed'; end;
end;
my $changes = $self->dbRevisionGet(what => 'item user', results => 10, auto => 1);
ul;
@@ -82,7 +83,8 @@ sub homepage {
td;
my $an = $self->dbThreadGet(type => 'an', sort => 'id', reverse => 1, results => 2);
h1;
- a href => '/t/an', mt '_home_announcements';
+ a href => '/t/an', mt '_home_announcements'; txt ' ';
+ a href => '/feeds/announcements.atom'; cssicon 'feed', mt '_atom_feed'; end;
end;
for (@$an) {
my $post = $self->dbPostGet(tid => $_->{id}, num => 1)->[0];
@@ -98,7 +100,8 @@ sub homepage {
# Recent posts
td;
h1;
- a href => '/t', mt '_home_recentposts';
+ a href => '/t', mt '_home_recentposts'; txt ' ';
+ a href => '/feeds/posts.atom'; cssicon 'feed', mt '_atom_feed'; end;
end;
my $posts = $self->dbThreadGet(what => 'lastpost boardtitles', results => 10, sort => 'lastpost', reverse => 1, notusers => 1);
ul;
@@ -135,7 +138,7 @@ sub homepage {
# Upcoming releases
td;
h1;
- a href => strftime('/r?mi=%Y%m%d;o=a;s=released', gmtime), mt '_home_upcoming';
+ a href => strftime('/r?fil=date_after-%Y%m%d;o=a;s=released', gmtime), mt '_home_upcoming';
end;
my $upcoming = $self->dbReleaseGet(results => 10, unreleased => 1, what => 'platforms');
ul;
@@ -155,7 +158,7 @@ sub homepage {
# Just released
td;
h1;
- a href => strftime('/r?ma=%Y%m%d;o=d;s=released', gmtime), mt '_home_justreleased';
+ a href => strftime('/r?fil=date_before-%Y%m%d;o=d;s=released', gmtime), mt '_home_justreleased';
end;
my $justrel = $self->dbReleaseGet(results => 10, sort => 'released', reverse => 1, unreleased => 0, what => 'platforms');
ul;
@@ -216,7 +219,7 @@ sub history {
releases => $f->{r},
);
- $self->htmlHeader(title => $title, noindex => 1);
+ $self->htmlHeader(title => $title, noindex => 1, feeds => [ 'changes' ]);
$self->htmlMainTabs($type, $obj, 'hist') if $type;
# url generator
@@ -361,12 +364,12 @@ sub nospam {
}
-sub ie6message {
+sub iemessage {
my $self = shift;
if($self->reqParam('i-still-want-access')) {
(my $ref = $self->reqHeader('Referer') || '/') =~ s/^\Q$self->{url}//;
- $ref = '/' if $ref eq '/we-dont-like-ie6';
+ $ref = '/' if $ref eq '/we-dont-like-ie';
$self->resRedirect($ref, 'temp');
$self->resHeader('Set-Cookie', "ie-sucks=1; path=/; domain=$self->{cookie_domain}");
return;
@@ -386,16 +389,16 @@ sub ie6message {
div;
h1 'Oops, we were too lazy to support your browser!';
p;
- lit qq|We decided to stop supporting Internet Explorer 6, as it's a royal pain in |
+ lit qq|We decided to stop supporting Internet Explorer 6 and 7, as it's a royal pain in |
.qq|the ass to make our site look good in a browser that doesn't want to cooperate with us.<br />|
.qq|You can try one of the following free alternatives: |
.qq|<a href="http://www.mozilla.com/firefox/">Firefox</a>, |
.qq|<a href="http://www.opera.com/">Opera</a>, |
.qq|<a href="http://www.apple.com/safari/">Safari</a>, or |
.qq|<a href="http://www.google.com/chrome">Chrome</a>.<br /><br />|
- .qq|If you're really stubborn about using Internet Explorer, upgrading to version 7 will also work.<br /><br />|
+ .qq|If you're really stubborn about using Internet Explorer, upgrading to version 8 will also work.<br /><br />|
.qq|...and if you're mad, you can also choose to ignore this warning and |
- .qq|<a href="/we-dont-like-ie6?i-still-want-access=1">open the site anyway</a>.|;
+ .qq|<a href="/we-dont-like-ie?i-still-want-access=1">open the site anyway</a>.|;
end;
end;
end;
diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm
index 2931e25f..6e11e829 100644
--- a/lib/VNDB/Handler/Producers.pm
+++ b/lib/VNDB/Handler/Producers.pm
@@ -60,13 +60,13 @@ sub page {
[ type => serialize => sub { mt "_ptype_$_[0]" } ],
[ name => diff => 1 ],
[ original => diff => 1 ],
- [ alias => diff => 1 ],
+ [ alias => diff => qr/[ ,\n\.]/ ],
[ lang => serialize => sub { "$_[0] (".mt("_lang_$_[0]").')' } ],
[ website => diff => 1 ],
[ l_wp => htmlize => sub {
$_[0] ? sprintf '<a href="http://en.wikipedia.org/wiki/%s">%1$s</a>', xml_escape $_[0] : mt '_revision_nolink'
}],
- [ desc => diff => 1 ],
+ [ desc => diff => qr/[ ,\n\.]/ ],
[ relations => join => '<br />', split => sub {
my @r = map sprintf('%s: <a href="/p%d" title="%s">%s</a>',
mt("_prodrel_$_->{relation}"), $_->{id}, xml_escape($_->{original}||$_->{name}), xml_escape shorten $_->{name}, 40
@@ -153,6 +153,10 @@ sub _releases {
td colspan => 6;
i; lit $self->{l10n}->datestr($vn{$v->{vid}}[0]{released}); end;
a href => "/v$v->{vid}", title => $v->{original}, $v->{title};
+ span '('.join(', ',
+ (grep($_->{developer}, @{$vn{$v->{vid}}}) ? mt '_prodpage_dev' : ()),
+ (grep($_->{publisher}, @{$vn{$v->{vid}}}) ? mt '_prodpage_pub' : ())
+ ).')';
end;
end;
for my $rel (@{$vn{$v->{vid}}}) {
diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm
index f9c600bd..e2a18e1a 100644
--- a/lib/VNDB/Handler/Releases.pm
+++ b/lib/VNDB/Handler/Releases.pm
@@ -51,7 +51,7 @@ sub page {
[ 'website' ],
[ released => htmlize => sub { $self->{l10n}->datestr($_[0]) } ],
[ minage => serialize => \&minage ],
- [ notes => diff => 1 ],
+ [ notes => diff => qr/[ ,\n\.]/ ],
[ platforms => join => ', ', split => sub { map mt("_plat_$_"), @{$_[0]} } ],
[ media => join => ', ', split => sub {
map $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1), @{$_[0]}
@@ -485,60 +485,46 @@ sub browse {
my $f = $self->formValidate(
{ name => 'p', required => 0, default => 1, template => 'int' },
- { name => 's', required => 0, default => 'title', enum => [qw|released minage title|] },
{ name => 'o', required => 0, default => 'a', enum => ['a', 'd'] },
{ name => 'q', required => 0, default => '', maxlength => 500 },
- { name => 'ln', required => 0, multi => 1, default => '', enum => $self->{languages} },
- { name => 'pl', required => 0, multi => 1, default => '', enum => $self->{platforms} },
- { name => 'me', required => 0, multi => 1, default => '', enum => [ keys %{$self->{media}} ] },
- { name => 'tp', required => 0, default => '', enum => [ '', @{$self->{release_types}} ] },
- { name => 'pa', required => 0, default => 0, enum => [ 0..2 ] },
- { name => 'fw', required => 0, default => 0, enum => [ 0..2 ] },
- { name => 'do', required => 0, default => 0, enum => [ 0..2 ] },
- { name => 'ma_m', required => 0, default => 0, enum => [ 0, 1 ] },
- { name => 'ma_a', required => 0, default => 0, enum => [ grep defined($_), @{$self->{age_ratings}} ] },
- { name => 'mi', required => 0, default => 0, template => 'int' },
- { name => 'ma', required => 0, default => 99999999, template => 'int' },
- { name => 're', required => 0, multi => 1, default => 0, enum => [ 1..$#{$self->{resolutions}} ] },
+ { name => 's', required => 0, default => 'title', enum => [qw|released minage title|] },
+ { name => 'fil',required => 0, default => '' },
);
return 404 if $f->{_err};
- my @filters = (
- $f->{mi} > 0 || $f->{ma} < 99990000 ? (date => [ $f->{mi}, $f->{ma} ]) : (),
- $f->{q} ? (search => $f->{q}) : (),
- $f->{pl}[0] ? (platforms => $f->{pl}) : (),
- $f->{ln}[0] ? (languages => $f->{ln}) : (),
- $f->{me}[0] ? (media => $f->{me}) : (),
- $f->{re}[0] ? (resolutions => $f->{re} ) : (),
- $f->{tp} ? (type => $f->{tp}) : (),
- $f->{ma_a} || $f->{ma_m} ? (minage => [$f->{ma_m}, $f->{ma_a}]) : (),
- $f->{pa} ? (patch => $f->{pa}) : (),
- $f->{fw} ? (freeware => $f->{fw}) : (),
- $f->{do} ? (doujin => $f->{do}) : (),
- );
- my($list, $np) = !@filters ? ([], 0) : $self->dbReleaseGet(
+ my $fil = fil_parse $f->{fil}, qw|type patch freeware doujin date_before date_after minage lang olang resolution plat med voiced ani_story ani_ero|;
+ _fil_compat($self, $fil);
+ $f->{fil} = fil_serialize($fil);
+
+ my($list, $np) = !$f->{q} && !keys %$fil ? ([], 0) : $self->dbReleaseGet(
sort => $f->{s}, reverse => $f->{o} eq 'd',
page => $f->{p},
results => 50,
what => 'platforms',
- @filters,
+ $f->{q} ? ( search => $f->{q} ) : (),
+ %$fil
);
- my $url = "/r?tp=$f->{tp};pa=$f->{pa};ma_m=$f->{ma_m};ma_a=$f->{ma_a};q=$f->{q};mi=$f->{mi};ma=$f->{ma};do=$f->{do};fw=$f->{fw}";
- $_&&($url .= ";ln=$_") for @{$f->{ln}};
- $_&&($url .= ";pl=$_") for @{$f->{pl}};
- $_&&($url .= ";re=$_") for @{$f->{re}};
- $_&&($url .= ";me=$_") for @{$f->{me}};
-
$self->htmlHeader(title => mt('_rbrowse_title'));
- _filters($self, $f, !@filters || !@$list);
+
+ form method => 'get', action => '/r', 'accept-charset' => 'UTF-8';
+ div class => 'mainbox';
+ h1 mt '_rbrowse_title';
+ $self->htmlSearchBox('r', $f->{q});
+ a id => 'filselect', href => '#r';
+ lit '<i>&#9656;</i> '.mt('_rbrowse_filters').'<i></i>';
+ end;
+ input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil};
+ end;
+ end;
+
$self->htmlBrowse(
class => 'relbrowse',
items => $list,
options => $f,
nextpage => $np,
- pageurl => "$url;s=$f->{s};o=$f->{o}",
- sorturl => $url,
+ pageurl => "/r?q=$f->{q};fil=$f->{fil};s=$f->{s};o=$f->{o}",
+ sorturl => "/r?q=$f->{q};fil=$f->{fil}",
header => [
[ mt('_rbrowse_col_released'), 'released' ],
[ mt('_rbrowse_col_minage'), 'minage' ],
@@ -564,7 +550,7 @@ sub browse {
end;
},
) if @$list;
- if(@filters && !@$list) {
+ if(($f->{q} || keys %$fil) && !@$list) {
div class => 'mainbox';
h1 mt '_rbrowse_noresults_title';
div class => 'notice';
@@ -576,108 +562,35 @@ sub browse {
}
-sub _filters {
- my($self, $f, $shown) = @_;
-
- form method => 'get', action => '/r', 'accept-charset' => 'UTF-8';
- div class => 'mainbox';
- h1 mt '_rbrowse_title';
-
- $self->htmlSearchBox('r', $f->{q});
-
- a id => 'advselect', href => '#';
- lit '<i>'.($shown?'&#9662;':'&#9656;').'</i> '.mt('_rbrowse_filters');
- end;
- div id => 'advoptions', !$shown ? (class => 'hidden') : ();
-
- h2 mt '_rbrowse_filters';
- table class => 'formtable', style => 'margin-left: 0';
- Tr class => 'newfield';
- td class => 'label'; label for => 'ma_m', mt '_rbrowse_minage'; end;
- td class => 'field';
- Select id => 'ma_m', name => 'ma_m', style => 'width: 160px';
- option value => 0, $f->{ma_m} == 0 ? ('selected' => 'selected') : (), mt '_rbrowse_ge';
- option value => 1, $f->{ma_m} == 1 ? ('selected' => 'selected') : (), mt '_rbrowse_le';
- end;
- Select id => 'ma_a', name => 'ma_a', style => 'width: 80px; text-align: center';
- defined($_) && option value => $_, $f->{ma_a} == $_ ? ('selected' => 'selected') : (), minage $_
- for (@{$self->{age_ratings}});
- end;
- end;
- td rowspan => 5, style => 'padding-left: 40px';
- label for => 're', mt '_rbrowse_resolution'; br;
- Select id => 're', name => 're', multiple => 'multiple', size => 8;
- my $l='';
- for my $i (1..$#{$self->{resolutions}}) {
- if($l ne $self->{resolutions}[$i][1]) {
- end if $l;
- $l = $self->{resolutions}[$i][1];
- optgroup label => $l;
- }
- option value => $i, scalar grep($i==$_, @{$f->{re}}) ? (selected => 'selected') : (), $self->{resolutions}[$i][0];
- }
- end if $l;
- end;
- end;
- end;
- $self->htmlFormPart($f, [ select => short => 'tp', name => mt('_rbrowse_type'),
- options => [ ['', mt '_rbrowse_all'], map [ $_, mt "_rtype_$_" ], @{$self->{release_types}} ]]);
- $self->htmlFormPart($f, [ select => short => 'pa', name => mt('_rbrowse_patch'),
- options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_patchonly'], [2, mt '_rbrowse_patchnone']]]);
- $self->htmlFormPart($f, [ select => short => 'fw', name => mt('_rbrowse_freeware'),
- options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_freewareonly'], [2, mt '_rbrowse_freewarenone']]]);
- $self->htmlFormPart($f, [ select => short => 'do', name => mt('_rbrowse_doujin'),
- options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_doujinonly'], [2, mt '_rbrowse_doujinnone']]]);
- $self->htmlFormPart($f, [ date => short => 'mi', name => mt '_rbrowse_dateafter' ]);
- $self->htmlFormPart($f, [ date => short => 'ma', name => mt '_rbrowse_datebefore' ]);
- end;
-
- h2;
- txt mt '_rbrowse_languages';
- b ' ('.mt('_rbrowse_boolor').')';
- end;
- for my $i (@{$self->{languages}}) {
- span;
- input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i", grep($_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : ();
- label for => "lang_$i";
- cssicon "lang $i", mt "_lang_$i";
- txt mt "_lang_$i";
- end;
- end;
- }
-
- h2;
- txt mt '_rbrowse_platforms';
- b ' ('.mt('_rbrowse_boolor').')';
- end;
- for my $i (sort @{$self->{platforms}}) {
- span;
- input type => 'checkbox', name => 'pl', value => $i, id => "plat_$i", grep($_ eq $i, @{$f->{pl}}) ? (checked => 'checked') : ();
- label for => "plat_$i";
- cssicon $i, mt "_plat_$i";
- txt mt "_plat_$i";
- end;
- end;
- }
-
- h2;
- txt mt '_rbrowse_media';
- b ' ('.mt('_rbrowse_boolor').')';
- end;
- for my $i (sort keys %{$self->{media}}) {
- span;
- input type => 'checkbox', name => 'me', value => $i, id => "med_$i", grep($_ eq $i, @{$f->{me}}) ? (checked => 'checked') : ();
- label for => "med_$i", mt "_med_$i", 1;
- end;
- }
-
- div style => 'text-align: center; clear: left;';
- input type => 'submit', value => mt('_rbrowse_apply'), class => 'submit';
- input type => 'reset', value => mt('_rbrowse_clear'), class => 'submit', onclick => 'location.href="/r"';
- end;
- end;
- end;
- end;
+# provide compatibility with old filter URLs
+sub _fil_compat {
+ my($self, $fil) = @_;
+ my $f = $self->formValidate(
+ { name => 'ln', required => 0, multi => 1, default => '', enum => $self->{languages} },
+ { name => 'pl', required => 0, multi => 1, default => '', enum => $self->{platforms} },
+ { name => 'me', required => 0, multi => 1, default => '', enum => [ keys %{$self->{media}} ] },
+ { name => 'tp', required => 0, default => '', enum => [ '', @{$self->{release_types}} ] },
+ { name => 'pa', required => 0, default => 0, enum => [ 0..2 ] },
+ { name => 'fw', required => 0, default => 0, enum => [ 0..2 ] },
+ { name => 'do', required => 0, default => 0, enum => [ 0..2 ] },
+ { name => 'ma_m', required => 0, default => 0, enum => [ 0, 1 ] },
+ { name => 'ma_a', required => 0, default => 0, enum => [ grep defined($_), @{$self->{age_ratings}} ] },
+ { name => 'mi', required => 0, default => 0, template => 'int' },
+ { name => 'ma', required => 0, default => 99999999, template => 'int' },
+ { name => 're', required => 0, multi => 1, default => 0, enum => [ 1..$#{$self->{resolutions}} ] },
+ );
+ return if $f->{_err};
+ $fil->{minage} //= [ grep defined($_) && $f->{ma_m} ? $f->{ma_a} >= $_ : $f->{ma_a} <= $_, @{$self->{age_ratings}} ] if $f->{ma_a} || $f->{ma_m};
+ $fil->{date_after} //= $f->{mi} if $f->{mi};
+ $fil->{date_before} //= $f->{ma} if $f->{ma} < 99990000;
+ $fil->{plat} //= $f->{pl} if $f->{pl}[0];
+ $fil->{lang} //= $f->{ln} if $f->{ln}[0];
+ $fil->{med} //= $f->{me} if $f->{me}[0];
+ $fil->{resolution} //= $f->{re} if $f->{re}[0];
+ $fil->{type} //= $f->{tp} if $f->{tp};
+ $fil->{patch} //= $f->{pa} == 2 ? 0 : 1 if $f->{pa};
+ $fil->{freeware} //= $f->{fw} == 2 ? 0 : 1 if $f->{fw};
+ $fil->{doujin} //= $f->{do} == 2 ? 0 : 1 if $f->{do};
}
diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm
index f3193b57..8e30da99 100644
--- a/lib/VNDB/Handler/VNBrowse.pm
+++ b/lib/VNDB/Handler/VNBrowse.pm
@@ -21,14 +21,12 @@ sub list {
{ name => 'p', required => 0, default => 1, template => 'int' },
{ name => 'q', required => 0, default => '' },
{ name => 'sq', required => 0, default => '' },
- { name => 'ln', required => 0, multi => 1, enum => $self->{languages}, default => '' },
- { name => 'pl', required => 0, multi => 1, enum => $self->{platforms}, default => '' },
- { name => 'ti', required => 0, default => '', maxlength => 200 },
- { name => 'te', required => 0, default => '', maxlength => 200 },
- { name => 'sp', required => 0, default => $self->reqCookie($self->{cookie_prefix}.'tagspoil') =~ /^([0-2])$/ ? $1 : 0, enum => [0..2] },
+ { name => 'fil',required => 0, default => '' },
);
return 404 if $f->{_err};
$f->{q} ||= $f->{sq};
+ my $fil = fil_parse $f->{fil}, qw|length hasani taginc tagexc tagspoil lang olang plat|;
+ _fil_compat($self, $fil);
if($f->{q}) {
return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp')
@@ -37,9 +35,11 @@ sub list {
# for URL compatibilty with older versions (ugly hack to get English strings)
my @lang;
$f->{q} =~ s/\s*$VNDB::L10N::en::Lexicon{"_lang_$_"}\s*//&&push @lang, $_ for (@{$self->{languages}});
- $f->{ln} = $f->{ln}[0] ? [ @{$f->{ln}}, @lang ] : \@lang;
+ $fil->{lang} = $fil->{lang} ? [ ref($fil->{lang}) ? @{$fil->{lang}} : $fil->{lang}, @lang ] : \@lang if @lang;
}
+ $f->{fil} = fil_serialize $fil;
+ # TODO: this should be moved to dbVNGet() in order for savable VN filters to be useful
my @ignored;
my $tagfind = sub {
return map {
@@ -47,10 +47,10 @@ sub list {
push @ignored, [$_, 0] if !$i;
push @ignored, [$_, 1] if $i && $i->{meta};
$i && !$i->{meta} ? $i->{id} : ();
- } grep $_, split /\s*,\s*/, $_[0];
+ } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'')
};
- my @ti = $tagfind->($f->{ti});
- my @te = $tagfind->($f->{te});
+ my @ti = $tagfind->(delete $fil->{taginc});
+ my @te = $tagfind->(delete $fil->{tagexc});
$f->{s} = 'title' if !@ti && $f->{s} eq 'tagscore';
$f->{o} = $f->{s} eq 'tagscore' ? 'd' : 'a' if !$f->{o};
@@ -62,28 +62,15 @@ sub list {
results => 50,
page => $f->{p},
sort => $f->{s}, reverse => $f->{o} eq 'd',
- $f->{pl}[0] ? ( platform => $f->{pl} ) : (),
- $f->{ln}[0] ? ( lang => $f->{ln} ) : (),
- @ti ? (tags_include => [ $f->{sp}, \@ti ]) : (),
+ @ti ? (tags_include => [ delete $fil->{tagspoil}, \@ti ]) : (),
@te ? (tags_exclude => \@te) : (),
+ %$fil
);
$self->resRedirect('/v'.$list->[0]{id}, 'temp')
if $f->{q} && @$list == 1 && $f->{p} == 1;
$self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q});
- _filters($self, $f, $char, \@ignored);
-
- my $url = "/v/$char?q=$f->{q};ti=$f->{ti};te=$f->{te}";
- $_ and $url .= ";pl=$_" for @{$f->{pl}};
- $_ and $url .= ";ln=$_" for @{$f->{ln}};
- $self->htmlBrowseVN($list, $f, $np, $url, scalar @ti);
- $self->htmlFooter;
-}
-
-
-sub _filters {
- my($self, $f, $char, $ign) = @_;
form action => '/v/all', 'accept-charset' => 'UTF-8', method => 'get';
div class => 'mainbox';
@@ -91,72 +78,45 @@ sub _filters {
$self->htmlSearchBox('v', $f->{q});
p class => 'browseopts';
for ('all', 'a'..'z', 0) {
- a href => "/v/$_", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#';
+ a href => "/v/$_?q=$f->{q};fil=$f->{fil}", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#';
}
end;
- if(@$ign) {
+ if(@ignored) {
div class => 'warning';
h2 mt '_vnbrowse_tagign_title';
ul;
- li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @$ign;
+ li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @ignored;
end;
end;
}
- a id => 'advselect', href => '#';
- lit '<i>&#9656;</i> '.mt('_vnbrowse_advsearch');
+ a id => 'filselect', href => '#v';
+ lit '<i>&#9656;</i> '.mt('_rbrowse_filters').'<i></i>'; # TODO: it's not *r*browse
end;
- div id => 'advoptions', class => 'hidden vnoptions';
-
- h2;
- txt mt '_vnbrowse_tags';
- b ' ('.mt('_vnbrowse_booland').')';
- end;
- table class => 'formtable', style => 'margin-left: 0';
- $self->htmlFormPart($f, [ input => short => 'ti', name => mt('_vnbrowse_taginc'), width => 350 ]);
- $self->htmlFormPart($f, [ radio => short => 'sp', name => '', options => [map [$_, mt '_vnbrowse_spoil'.$_], 0..2]]);
- $self->htmlFormPart($f, [ input => short => 'te', name => mt('_vnbrowse_tagexc'), width => 350 ]);
- end;
-
- h2;
- txt mt '_vnbrowse_lang';
- b ' ('.mt('_vnbrowse_boolor').')';
- end;
- for my $i (@{$self->{languages}}) {
- span;
- input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i",
- (scalar grep $_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : ();
- label for => "lang_$i";
- cssicon "lang $i", mt "_lang_$i";
- txt mt "_lang_$i";
- end;
- end;
- }
+ input type => 'hidden', class => 'hidden', name => 'fil', id => 'fil', value => $f->{fil};
+ end;
+ end; # /form
- h2;
- txt mt '_vnbrowse_plat';
- b ' ('.mt('_vnbrowse_boolor').')';
- end;
- for my $i (sort @{$self->{platforms}}) {
- next if $i eq 'oth';
- span;
- input type => 'checkbox', id => "plat_$i", name => 'pl', value => $i,
- (scalar grep $_ eq $i, @{$f->{pl}}) ? (checked => 'checked') : ();
- label for => "plat_$i";
- cssicon $i, mt "_plat_$i";
- txt mt "_plat_$i";
- end;
- end;
- }
+ $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$f->{q};fil=$f->{fil}", scalar @ti);
+ $self->htmlFooter;
+}
- div style => 'text-align: center; clear: left;';
- input type => 'submit', value => mt('_vnbrowse_apply'), class => 'submit';
- input type => 'reset', value => mt('_vnbrowse_clear'), class => 'submit', onclick => 'location.href="/v/all"';
- end;
- end;
- end;
- end;
+
+sub _fil_compat {
+ my($self, $fil) = @_;
+ my $f = $self->formValidate(
+ { name => 'ln', required => 0, multi => 1, enum => $self->{languages}, default => '' },
+ { name => 'pl', required => 0, multi => 1, enum => $self->{platforms}, default => '' },
+ { name => 'ti', required => 0, default => '', maxlength => 200 },
+ { name => 'te', required => 0, default => '', maxlength => 200 },
+ { name => 'sp', required => 0, default => $self->reqCookie($self->{cookie_prefix}.'tagspoil') =~ /^([0-2])$/ ? $1 : 0, enum => [0..2] },
+ );
+ $fil->{lang} //= $f->{ln} if $f->{ln}[0];
+ $fil->{plat} //= $f->{pl} if $f->{pl}[0];
+ $fil->{taginc} //= $f->{ti} if $f->{ti};
+ $fil->{tagexc} //= $f->{te} if $f->{te};
+ $fil->{tagspoil} //= $f->{sp};
}
diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm
index 4a4b3f4f..1a3eb514 100644
--- a/lib/VNDB/Handler/VNPage.pm
+++ b/lib/VNDB/Handler/VNPage.pm
@@ -191,8 +191,8 @@ sub _revision {
$self->htmlRevision('v', $prev, $v,
[ title => diff => 1 ],
[ original => diff => 1 ],
- [ alias => diff => 1 ],
- [ desc => diff => 1 ],
+ [ alias => diff => qr/[ ,\n\.]/ ],
+ [ desc => diff => qr/[ ,\n\.]/ ],
[ length => serialize => sub { mt '_vnlength_'.$_[0] } ],
[ l_wp => htmlize => sub {
$_[0] ? sprintf '<a href="http://en.wikipedia.org/wiki/%s">%1$s</a>', xml_escape $_[0] : mt '_revision_nolink'
diff --git a/lib/VNDB/Plugin/TransAdmin.pm b/lib/VNDB/Plugin/TransAdmin.pm
index 4859ea67..6d4de67e 100644
--- a/lib/VNDB/Plugin/TransAdmin.pm
+++ b/lib/VNDB/Plugin/TransAdmin.pm
@@ -121,7 +121,7 @@ sub _savelang {
# re-read the file and regenerate the JS in case we're not running as CGI
if($INC{"FCGI.pm"}) {
VNDB::L10N::loadfile();
- VNDB::checkjs();
+ system "make -sC $VNDB::ROOT js" if $self->{regen_static};
}
}
diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm
index a862911d..280ceddb 100644
--- a/lib/VNDB/Util/CommonHTML.pm
+++ b/lib/VNDB/Util/CommonHTML.pm
@@ -153,7 +153,7 @@ sub htmlHiddenMessage {
# Where @fields is a list of fields as arrayrefs with:
# [ shortname, displayname, %options ],
# Where %options:
-# diff => 1/0, whether do show a diff on this field
+# diff => 1/0/regex, whether to show a diff on this field, and what to split it with (1 = character-level diff)
# serialize => coderef, should convert the field into a readable string, no HTML allowed
# htmlize => same as serialize, but HTML is allowed and this can't be diff'ed
# split => coderef, should return an array of HTML strings that can be diff'ed. (implies diff => 1)
@@ -226,7 +226,7 @@ sub revdiff {
my($i, $type, $old, $new, $short, %o) = @_;
$o{serialize} ||= $o{htmlize};
- $o{diff}++ if $o{split};
+ $o{diff} = 1 if $o{split};
$o{join} ||= '';
my $ser1 = $o{serialize} ? $o{serialize}->($old->{$short}, $old) : $old->{$short};
@@ -234,8 +234,9 @@ sub revdiff {
return if $ser1 eq $ser2;
if($o{diff} && $ser1 && $ser2) {
- my @ser1 = $o{split} ? $o{split}->($ser1) : map xml_escape($_), split //, $ser1;
- my @ser2 = $o{split} ? $o{split}->($ser2) : map xml_escape($_), split //, $ser2;
+ my $sep = ref $o{diff} ? qr/($o{diff})/ : qr//;
+ my @ser1 = $o{split} ? $o{split}->($ser1) : map xml_escape($_), split $sep, $ser1;
+ my @ser2 = $o{split} ? $o{split}->($ser2) : map xml_escape($_), split $sep, $ser2;
return if $o{split} && $#ser1 == $#ser2 && !grep $ser1[$_] ne $ser2[$_], 0..$#ser1;
$ser1 = $ser2 = '';
diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm
index 93a35e0e..b5dc3e9c 100644
--- a/lib/VNDB/Util/LayoutHTML.pm
+++ b/lib/VNDB/Util/LayoutHTML.pm
@@ -10,7 +10,7 @@ use VNDB::Func;
our @EXPORT = qw|htmlHeader htmlFooter|;
-sub htmlHeader { # %options->{ title, noindex, search }
+sub htmlHeader { # %options->{ title, noindex, search, feeds }
my($self, %o) = @_;
my $skin = $self->reqParam('skin') || $self->authInfo->{skin} || $self->{skin_default};
$skin = $self->{skin_default} if !$self->{skins}{$skin} || !-d "$VNDB::ROOT/static/s/$skin";
@@ -26,6 +26,8 @@ sub htmlHeader { # %options->{ title, noindex, search }
(my $css = $self->authInfo->{customcss}) =~ s/\n/ /g;
style type => 'text/css', $css;
}
+ Link rel => 'alternate', type => 'application/atom+xml', href => "/feeds/$_.atom", title => $self->{atom_feeds}{$_}[1]
+ for ($o{feeds} ? @{$o{feeds}} : ());
meta name => 'robots', content => 'noindex, follow', undef if $o{noindex};
end;
body;
@@ -153,7 +155,7 @@ sub htmlFooter {
a href => $self->{source_url}, mt '_footer_source';
end;
end; # /div maincontent
- script type => 'text/javascript', src => $self->{url_static}.'/f/script.js?'.$self->{version}, '';
+ script type => 'text/javascript', src => $self->{url_static}.'/f/js/'.$self->{l10n}->language_tag().'.js?'.$self->{version}, '';
end; # /body
end; # /html
diff --git a/lib/VNDBUtil.pm b/lib/VNDBUtil.pm
index f23811a9..b0213d07 100644
--- a/lib/VNDBUtil.pm
+++ b/lib/VNDBUtil.pm
@@ -29,95 +29,118 @@ sub bb2html {
my $raw = shift;
my $maxlength = shift;
$raw =~ s/\r//g;
- $raw =~ s/\n{5,}/\n\n/g;
return '' if !$raw && $raw ne "0";
- my($result, $length, $rmnewline, @open) = ('', 0, 0, 'first');
+ my($result, $last, $length, $rmnewline, @open) = ('', 0, 0, 0, 'first');
+ # escapes, returns string, and takes care of $length and $maxlength; also
+ # takes care to remove newlines and double spaces when necessary
my $e = sub {
local $_ = shift;
+ s/^\n// if $rmnewline && $rmnewline--;
+ s/\n{5,}/\n\n/g if $open[$#open] ne 'code';
+ s/ +/ /g if $open[$#open] ne 'code';
+ $length += length $_;
+ if($maxlength && $length > $maxlength) {
+ $_ = substr($_, 0, $maxlength-$length);
+ s/[ \.,:;]+[^ \.,:;]*$//; # cleanly cut off on word boundary
+ }
s/&/&amp;/g;
s/>/&gt;/g;
s/</&lt;/g;
s/\n/<br \/>/g if !$maxlength;
- s/\n/ /g if $maxlength;
+ s/\n/ /g if $maxlength;
return $_;
};
- for (split /(\s|\n|\[[^\]]+\])/, $raw) {
- next if !defined $_;
- next if $_ eq '';
+ while($raw =~ m{(
+ ([tdvpr][1-9][0-9]*\.[1-9][0-9]*) | # 2. exid
+ ([tdvprug][1-9][0-9]*) | # 3. id
+ (\[[^\s\]]+\]) | # 4. tag
+ ((?:https?|ftp)://[^><"\n\s\]\[]+[\d\w=/-]) # 5. url
+ )}xg) {
+ my($match, $exid, $id, $tag, $url) = ($1, $2, $3, $4, $5);
- # (note to self: stop using unreadable hacks like these!)
- $rmnewline-- && $_ eq "\n" && next if $rmnewline;
+ # add string before the match
+ $result .= $e->(substr $raw, $last, (pos($raw)-length($match))-$last);
+ last if $maxlength && $length > $maxlength;
+ $last = pos $raw;
- my $lit = $_;
if($open[$#open] ne 'raw' && $open[$#open] ne 'code') {
- if (lc$_ eq '[raw]') { push @open, 'raw'; next }
- elsif (lc$_ eq '[spoiler]') { push @open, 'spoiler'; $result .= '<b class="spoiler">'; next }
- elsif (lc$_ eq '[quote]') {
- push @open, 'quote';
- $result .= '<div class="quote">' if !$maxlength;
- $rmnewline = 1;
- next
- } elsif (lc$_ eq '[code]') {
- push @open, 'code';
- $result .= '<pre>' if !$maxlength;
- $rmnewline = 1;
- next
- } elsif (lc$_ eq '[/spoiler]') {
- if($open[$#open] eq 'spoiler') {
+ # handle tags
+ if($tag) {
+ $tag = lc $tag;
+ if($tag eq '[raw]') {
+ push @open, 'raw';
+ next;
+ } elsif($tag eq '[spoiler]') {
+ push @open, 'spoiler';
+ $result .= '<b class="spoiler">';
+ next;
+ } elsif($tag eq '[quote]') {
+ push @open, 'quote';
+ $result .= '<div class="quote">' if !$maxlength;
+ $rmnewline = 1;
+ next;
+ } elsif($tag eq '[code]') {
+ push @open, 'code';
+ $result .= '<pre>' if !$maxlength;
+ $rmnewline = 1;
+ next;
+ } elsif($tag eq '[/spoiler]' && $open[$#open] eq 'spoiler') {
$result .= '</b>';
pop @open;
- }
- next;
- } elsif (lc$_ eq '[/quote]') {
- if($open[$#open] eq 'quote') {
+ next;
+ } elsif($tag eq '[/quote]' && $open[$#open] eq 'quote') {
$result .= '</div>' if !$maxlength;
$rmnewline = 1;
- pop @open;
- }
- next;
- } elsif(lc$_ eq '[/url]') {
- if($open[$#open] eq 'url') {
+ next;
+ } elsif($tag eq '[/url]' && $open[$#open] eq 'url') {
$result .= '</a>';
pop @open;
+ next;
+ } elsif($tag =~ s{\[url=((https?://|/)[^\]>]+)\]}{<a href="$1" rel="nofollow">}i) {
+ $result .= $tag;
+ push @open, 'url';
+ next;
}
- next;
- } elsif(s{\[url=((https?://|/)[^\]>]+)\]}{<a href="$1" rel="nofollow">}i) {
- $result .= $_;
- push @open, 'url';
- next;
- } elsif(!grep(/url/, @open) &&
- s{(.*)(http|https)://(.+[\d\w=/-])(.*)}
- {$e->($1).qq|<a href="$2://|.$e->($3, 1).'" rel="nofollow">'.$e->('link').'</a>'.$e->($4)}e) {
+ }
+ # handle URLs
+ if($url && !grep(/url/, @open)) {
$length += 4;
last if $maxlength && $length > $maxlength;
- $result .= $_;
+ $result .= sprintf '<a href="%s" rel="nofollow">link</a>', $url;
next;
- } elsif(!grep(/url/, @open) && (
- s{^(.*[^\w]|)([tdvpr][1-9][0-9]*)\.([1-9][0-9]*)([^\w].*|)$}{$e->($1).qq|<a href="/$2.$3">$2.$3</a>|.$e->($4)}e ||
- s{^(.*[^\w]|)([tdvprug][1-9][0-9]*)([^\w].*|)$}{$e->($1).qq|<a href="/$2">$2</a>|.$e->($3)}e)) {
- $length += length $lit;
+ }
+ # id
+ if(($id || $exid) && substr($raw, $last-1-length($match), 1) !~ /[\w]/ && substr($raw, $last, 1) !~ /[\w]/) {
+ $length += length $match;
last if $maxlength && $length > $maxlength;
- $result .= $_;
- next;
+ $result .= sprintf '<a href="/%s">%1$s</a>', $match;
+ next
}
- } elsif($open[$#open] eq 'raw' && lc$_ eq '[/raw]') {
+ }
+
+ if($tag && $open[$#open] eq 'raw' && lc$tag eq '[/raw]') {
pop @open;
next;
- } elsif($open[$#open] eq 'code' && lc$_ eq '[/code]') {
+ }
+
+ if($tag && $open[$#open] eq 'code' && lc$tag eq '[/code]') {
$result .= '</pre>' if !$maxlength;
pop @open;
next;
}
- # normal text processing
- $length += length $_;
+ # We'll only get here when the bbcode input isn't correct or something else
+ # didn't work out. In that case, just output whatever we've matched.
+ $result .= $e->($match);
last if $maxlength && $length > $maxlength;
- $result .= $e->($_);
}
+ # the last unmatched part, just escape and output
+ $result .= $e->(substr $raw, $last);
+
# close open tags
while((local $_ = pop @open) ne 'first') {
$result .= $_ eq 'url' ? '</a>' : $_ eq 'spoiler' ? '</b>' : '';