summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2009-06-07 10:36:13 +0200
committerYorhel <git@yorhel.nl>2009-06-07 10:36:53 +0200
commitf74e08f3d2665c3af5adf4d3668998ed29da275e (patch)
treede52ac9d5e986deebfee26a11be6e8a45075fa76
parent44451507a9cc577aa6c6d127cbc9e301691b97fd (diff)
parentf258f3d8d6b7a6dab970ba6e0c4cbd7172c652e5 (diff)
Merge branch 'beta'2.4
+ Set 2.4 date in ChangeLog
-rw-r--r--ChangeLog14
-rw-r--r--data/docs/344
-rw-r--r--data/global.pl45
-rw-r--r--data/style.css44
-rw-r--r--lib/VNDB/DB/Releases.pm58
-rw-r--r--lib/VNDB/DB/VN.pm23
-rw-r--r--lib/VNDB/Func.pm9
-rw-r--r--lib/VNDB/Handler/Misc.pm2
-rw-r--r--lib/VNDB/Handler/Producers.pm7
-rw-r--r--lib/VNDB/Handler/Releases.pm300
-rw-r--r--lib/VNDB/Handler/Tags.pm16
-rw-r--r--lib/VNDB/Handler/VNBrowse.pm42
-rw-r--r--lib/VNDB/Handler/VNEdit.pm6
-rw-r--r--lib/VNDB/Handler/VNPage.pm2
-rw-r--r--lib/VNDB/Util/CommonHTML.pm28
-rw-r--r--lib/VNDB/Util/FormHTML.pm17
-rw-r--r--lib/VNDB/Util/LayoutHTML.pm3
-rw-r--r--static/f/script.js86
-rw-r--r--util/dump.sql12
-rw-r--r--util/updates/update_2.4.sql99
20 files changed, 684 insertions, 173 deletions
diff --git a/ChangeLog b/ChangeLog
index d9512765..03e29094 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2.4 - 2009-06-07
+ - Release search + browser + filters
+ - Javascript date input
+ - More release information:
+ - Screen resolution
+ - Voiced
+ - Freeware/doujin
+ - Animated
+ - Show comparable CERO ratings on /r+/edit input field
+ - Allow search queries with only one character
+ - Removed category filter from /v/all
+ - Added expand/collapse feature to the history browser
+ - Added tabs on v/r/p/g search fields
+
2.3 - 2009-04-01
- No page reload needed when changing rlist status from vn page
- Random VN quotes to the footer of every page
diff --git a/data/docs/3 b/data/docs/3
index 4700cfb3..6ec7bb14 100644
--- a/data/docs/3
+++ b/data/docs/3
@@ -1,5 +1,4 @@
:TITLE:Adding/Editing a Release
-:INC:notfinished
:INC:index
@@ -30,6 +29,11 @@
</dd><dt>Patch</dt><dd>
Use this checkbox to indicate that the release is a (translation) patch, used to
patch an other release.
+ </dd><dt>Freeware</dt><dd>
+ Check if this box if the game is downloadable (or otherwise distributed) at no cost.
+ </dd><dt>Doujin</dt><dd>
+ Published by a doujin circle, amateur group or individual, as opposed to a legal
+ entity such as a company. This field is ignored when the release type is set to patch.
</dd><dt>Title (romaji)</dt><dd>
The name of the release, in the Latin character set (using Romanisation or translation)
</dd><dt>Original title</dt><dd>
@@ -42,6 +46,9 @@
of the product. Often called "JAN" for Japanese releases, "UPC" for USA and Canada
and "EAN" for Europe. The system will automatically detect the type from the code and
use the appropriate term on the release page.
+ </dd><dt>Catalog number</dt><dd>
+ Catalog number as assigned by the producer. Often used to identify releases on
+ webshops, and can usually be found somewhere on the packaging of the product.
</dd><dt>Official website</dt><dd>
URL of the official homepage for this product.
</dd><dt>Release date</dt><dd>
@@ -58,13 +65,35 @@
</dl>
-:SUB:Platforms &amp; Media
+:SUB:Format
+<dl>
+ <dt>Resolution</dt><dd>
+ Primary/native screen resolution of the game.
+ </dd><dt>Voiced</dt><dd>
+ Indicates whether this release includes voice acting. <i>Fully voiced</i> indicates
+ that all characters (maybe with the exeption of a few minor characters) are voiced in
+ all scenes. <i>Only ero scenes voiced</i> speaks for itself, and <i>Partially voiced</i>
+ should be used when there is some voice acting, but only for the main characters or only
+ in some scenes.
+ </dd><dt>Animation</dt><dd>
+ Whether the game has any animation can be specified with two seperate options: one for
+ the normal story mode and one for the ero scenes, if the game has any. With <i>Simple
+ animations</i>, we refer to (usually looping) effects like falling leaves or snow in the
+ background or animated facial expressions like blinking eyes and a moving mouth.
+ <i>Fully animated scenes</i> refers to non-looping anime-like scenes. Some games are
+ entirely like this, others only have a few scenes that are fully animated. Effects like
+ moving sprites around the screen, basic zooming and shaking background images are not
+ considered as "animations". Minigames or other gameplay elements are excluded as well,
+ only the (ADV/VN-like) story parts and ero scenes should be considered.
+ </dd>
+</dl>
<p>
+ <b>Platform</b><br />
The platforms that the product was released for. Does not include emulated platforms
(e.g. Playstation 2 games on Playstation 3) or WINE. DVD Player refers to games playable
as a normal DVD Video (DVDPG) and should not be confused with the DVD as a medium.
<br /><br />
- The following media can be selected:
+ <b>Media</b>
</p>
<dl>
<dt>Blu-ray</dt><dd>
@@ -99,9 +128,14 @@
:SUB:Producers
-..
+<p>
+ The companies/groups/individuals involved in the creation, development or translation
+ of this release. Does not include distributors.
+</p>
:SUB:Visual novel relations
-..
+<p>
+ The visual novels that this release (either partially or fully) covers.
+</p>
diff --git a/data/global.pl b/data/global.pl
index 739578bf..24b28d8e 100644
--- a/data/global.pl
+++ b/data/global.pl
@@ -66,7 +66,7 @@ our %S = (%S,
u => 'Users', # uid
},
vn_lengths => [
- [ 'Unkown', '', '' ],
+ [ 'Unknown', '', '' ],
[ 'Very short', '< 2 hours', 'OMGWTFOTL, A Dream of Summer' ],
[ 'Short', '2 - 10 hours', 'Narcissu, Planetarian' ],
[ 'Medium', '10 - 30 hours', 'Kana: Little Sister' ],
@@ -150,9 +150,21 @@ our %S = (%S,
[ 'Other', 0 ],
],
age_ratings => {
- -1 => 'Unknown',
- 0 => 'All ages',
- map { $_ => $_.'+' } 6..18
+ -1 => [ 'Unknown' ],
+ 0 => [ 'All ages' ,'CERO A' ],
+ 6 => [ '6+' ],
+ 7 => [ '7+' ],
+ 8 => [ '8+' ],
+ 9 => [ '9+' ],
+ 10 => [ '10+' ],
+ 11 => [ '11+' ],
+ 12 => [ '12+', 'CERO B' ],
+ 13 => [ '13+' ],
+ 14 => [ '14+' ],
+ 15 => [ '15+', 'CERO C' ],
+ 16 => [ '16+' ],
+ 17 => [ '17+', 'CERO D' ],
+ 18 => [ '18+', 'CERO Z' ],
},
release_types => [
'Complete',
@@ -192,6 +204,31 @@ our %S = (%S,
in => [ 'Internet download', 0 ],
otc => [ 'Other', 0 ],
},
+ resolutions => [
+ [ 'Unknown / console / handheld', '' ],
+ [ 'Custom', '' ],
+ [ '640x480 (480p)', '4:3' ],
+ [ '800x600', '4:3' ],
+ [ '1024x768', '4:3' ],
+ [ '640x400', 'widescreen' ],
+ [ '1024x640', 'widescreen' ],
+ [ '1280x720 (720p)', 'widescreen' ],
+ [ '1920x1080 (1080p)', 'widescreen' ],
+ ],
+ voiced => [
+ 'Unknown',
+ 'Not voiced',
+ 'Only ero scenes voiced',
+ 'Partially voiced',
+ 'Fully voiced',
+ ],
+ animated => [
+ 'Unknown',
+ 'No animations',
+ 'Simple animations',
+ 'Some fully animated scenes',
+ 'All scenes fully animated',
+ ],
votes => [
'worst ever',
'awful',
diff --git a/data/style.css b/data/style.css
index 042beb33..ce510235 100644
--- a/data/style.css
+++ b/data/style.css
@@ -101,6 +101,9 @@ b.todo { font-weight: normal; color: $statnok$ }
clear: both;
height: 0;
}
+.hidden {
+ display: none
+}
b.spoiler, b.spoiler a {
color: #000;
@@ -429,15 +432,33 @@ table thead td {
font-weight: bold;
background-color: $secbg$;
}
-form.search {
+fieldset.search {
display: block;
width: 100%;
text-align: center;
margin: 0 0 10px 0;
}
-form.search .submit {
+fieldset.search .submit {
padding: 0 1px;
}
+p.searchtabs {
+ width: 84%;
+ padding-right: 15%;
+ text-align: center;
+ height: 12px;
+}
+p.searchtabs a {
+ padding: 2px 6px 2px 6px;
+ margin: 0 2px;
+ color: $maintext$!important;
+}
+p.searchtabs a:hover, p.searchtabs a.sel {
+ border: 1px solid $secborder$;
+ border-bottom: none;
+ padding: 1px 5px 2px 5px;
+ background: $secbg$ url($_boxbg$) repeat;
+}
+#q { width: 350px }
/* history browser */
div.mainbox.history td.tc1_1 {
@@ -453,11 +474,13 @@ div.mainbox.history td.tc1_2 {
}
div.mainbox.history td.tc2 { width: 65px; }
div.mainbox.history td.tc3 { width: 90px }
-div.mainbox.history td.editsum {
+div.mainbox.history tr.editsum td {
color: $grayedout$;
padding-top: 0;
text-align: right;
}
+a#history_comments { float: right }
+a#history_comments:hover { border-bottom: none }
@@ -605,7 +628,6 @@ div#vntags {
text-align: center;
}
#vntags span { white-space: nowrap; margin-left: 15px; }
-#vntags span.hidden { display: none }
#vntags b { color: $grayedout$; font-weight: normal; font-size: 8px }
#tagops {
float: right;
@@ -667,7 +689,6 @@ a.addnew {
#screenshots td.scr div.nsfw img { border: 3px solid $statnok$; }
#screenshots td.scr a:hover img { border: 3px solid $border$; }
#screenshots td.scr a { border: none; }
-#screenshots div.hidden { display: none }
#screenshots #nsfwshown { font-style: normal }
#screenshots p.nsfwtoggle {
float: right;
@@ -794,7 +815,6 @@ a.help {
.vnbrowse .tc2 { text-align: right; padding: 0; }
.vnbrowse .tc3 { padding: 0; }
.vnbrowse .tc5 { text-align: right; padding-right: 10px; }
-#q { width: 300px }
#advselect {
text-align: center;
display: block;
@@ -808,7 +828,6 @@ a.help {
margin: 0 auto;
border-top: 1px solid $border$;
}
-#advoptions.hidden { display: none }
#advoptions h2 {
clear: left;
padding-top: 10px;
@@ -903,12 +922,10 @@ ul#catselect li li.exc { background-position: 0px -33px; color: $statnok$; }
}
/* edit */
-#released, #released_d { width: 70px }
-#released_m { width: 100px }
.platforms { padding-left: 20px; }
.platforms span { display: block; float: left; width: 150px; }
-#jt_box_platforms_media h2 { clear: left; padding-top: 10px; }
+#jt_box_format h2 { clear: left; padding-top: 10px; }
#media_div select.qty { width: 90px; }
#media_div select.medium { width: 150px }
#media_div { padding-left: 20px; }
@@ -924,6 +941,13 @@ ul#catselect li li.exc { background-position: 0px -33px; color: $statnok$; }
+/***** Release browser *****/
+
+.relbrowse .tc1 { width: 80px }
+.relbrowse .tc2 { width: 60px; text-align: center; }
+.relbrowse .tc3 { width: 85px; text-align: right; padding: 0; }
+
+
/***** Documentation pages *****/
diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm
index 1df13894..bd5d8fcb 100644
--- a/lib/VNDB/DB/Releases.pm
+++ b/lib/VNDB/DB/Releases.pm
@@ -9,7 +9,8 @@ use Exporter 'import';
our @EXPORT = qw|dbReleaseGet dbReleaseAdd dbReleaseEdit|;
-# Options: id vid rev order unreleased page results what
+# Options: id vid rev order unreleased page results what date media
+# platforms languages type minage search resolutions freeware doujin
# What: extended changes vn producers platforms media
sub dbReleaseGet {
my($self, %o) = @_;
@@ -18,19 +19,46 @@ sub dbReleaseGet {
$o{what} ||= '';
$o{order} ||= 'rr.released ASC';
- 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} ) : (),
+ 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{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.language 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 ? AND rr.minage <> -1)' => [ $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} ] ) : (),
);
+ if($o{search}) {
+ for (split /[ -,._]/, $o{search}) {
+ s/%//g;
+ if(/^\d+$/ && gtintype($_)) {
+ push @where, 'rr.gtin = ?', $_;
+ } elsif(length($_) > 0) {
+ $_ = "%$_%";
+ push @where, '(rr.title ILIKE ? OR rr.original ILIKE ? OR rr.catalog = ?)',
+ [ $_, $_, $_ ];
+ }
+ }
+ }
+
my @join = (
$o{rev} ? 'JOIN releases r ON r.id = rr.rid' : 'JOIN releases r ON rr.id = r.latest',
$o{vid} ? 'JOIN releases_vn rv ON rv.rid = rr.id' : (),
@@ -43,7 +71,7 @@ sub dbReleaseGet {
my @select = (
qw|r.id rr.title rr.original rr.language rr.website rr.released rr.minage rr.type rr.patch|,
'rr.id AS cid',
- $o{what} =~ /extended/ ? qw|rr.notes rr.catalog rr.gtin r.hidden r.locked| : (),
+ $o{what} =~ /extended/ ? qw|rr.notes rr.catalog rr.gtin rr.resolution rr.voiced rr.freeware rr.doujin rr.ani_story rr.ani_ero r.hidden r.locked| : (),
$o{what} =~ /changes/ ? qw|c.added c.requester c.comments r.latest u.username c.rev| : (),
);
@@ -53,7 +81,7 @@ sub dbReleaseGet {
!s
!W
ORDER BY !s|,
- join(', ', @select), join(' ', @join), \%where, $o{order}
+ join(', ', @select), join(' ', @join), \@where, $o{order}
);
if(@$r && $o{what} =~ /(vn|producers|platforms|media)/) {
@@ -138,9 +166,11 @@ sub insert_rev {
my($self, $cid, $rid, $o) = @_;
$self->dbExec(q|
- INSERT INTO releases_rev (id, rid, title, original, gtin, catalog, language, website, released, notes, minage, type, patch)
+ INSERT INTO releases_rev (id, rid, title, original, gtin, catalog, language, website, released,
+ notes, minage, type, patch, resolution, voiced, freeware, doujin, ani_story, ani_ero)
VALUES (!l)|,
- [ $cid, $rid, @$o{qw| title original gtin catalog language website released notes minage type patch|} ]);
+ [ $cid, $rid, @$o{qw| title original gtin catalog language website released
+ notes minage type patch resolution voiced freeware doujin ani_story ani_ero|} ]);
$self->dbExec(q|
INSERT INTO releases_producers (rid, pid)
diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm
index 4f04edd5..d0a38ced 100644
--- a/lib/VNDB/DB/VN.pm
+++ b/lib/VNDB/DB/VN.pm
@@ -9,7 +9,7 @@ use VNDB::Func 'gtintype';
our @EXPORT = qw|dbVNGet dbVNAdd dbVNEdit dbVNImageId dbVNCache dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|;
-# Options: id, rev, char, search, cati, cate, lang, platform, results, page, order, what
+# Options: id, rev, char, search, lang, platform, results, page, order, what
# What: extended categories anime relations screenshots relgraph ranking changes
sub dbVNGet {
my($self, %o) = @_;
@@ -27,19 +27,6 @@ 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{cati} && @{$o{cati}} ? ( q|
- v.id IN(SELECT iv.id
- FROM vn_categories ivc
- JOIN vn iv ON iv.latest = ivc.vid
- WHERE cat IN(!l)
- GROUP BY iv.id
- HAVING COUNT(cat) = ?)| => [ $o{cati}, $#{$o{cati}}+1 ] ) : (),
- $o{cate} && @{$o{cate}} ? ( q|
- v.id NOT IN(SELECT iv.id
- FROM vn_categories ivc
- JOIN vn iv ON iv.latest = ivc.vid
- WHERE cat IN(!l)
- GROUP BY iv.id)| => [ $o{cate} ] ) : (),
$o{lang} && @{$o{lang}} ? (
'('.join(' OR ', map "v.c_languages ILIKE '%%$_%%'", @{$o{lang}}).')' => 1 ) : (),
$o{platform} && @{$o{platform}} ? (
@@ -50,20 +37,18 @@ sub dbVNGet {
);
if($o{search}) {
- my @w = (
- '(irr.id IS NULL OR ir.latest = irr.id)' => 1
- );
+ my @w;
for (split /[ -,._]/, $o{search}) {
s/%//g;
- next if length($_) < 2;
if(/^\d+$/ && gtintype($_)) {
push @w, 'irr.gtin = ?', $_;
- } else {
+ } elsif(length($_) > 0) {
$_ = "%$_%";
push @w, '(ivr.title ILIKE ? OR ivr.original ILIKE ? OR ivr.alias ILIKE ? OR irr.title ILIKE ? OR irr.original ILIKE ?)',
[ $_, $_, $_, $_, $_ ];
}
}
+ push @w, '(irr.id IS NULL OR ir.latest = irr.id)' => 1 if @w;
$where{ q|
v.id IN(SELECT iv.id
FROM vn iv
diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm
index 0a5a9038..4ca20dc5 100644
--- a/lib/VNDB/Func.pm
+++ b/lib/VNDB/Func.pm
@@ -42,9 +42,9 @@ sub date {
# argument: database release date format (yyyymmdd)
-# y = 0000 -> unkown
+# y = 0000 -> unknown
# y = 9999 -> TBA
-# m = 99 -> month+day unkown
+# m = 99 -> month+day unknown
# d = 99 -> day unknown
# return value: (unknown|TBA|yyyy|yyyy-mm|yyyy-mm-dd)
# if date > now: <b class="future">str</b>
@@ -66,11 +66,12 @@ sub datestr {
# e.g.: 'Jan 2009', '2009', 'unknown', 'TBA'
sub monthstr {
my $date = sprintf '%08d', shift||0;
- my($y, $m) = ($1, $2) if $date =~ /^([0-9]{4})([0-9]{2})/;
+ my($y, $m, $d) = ($1, $2, $3) if $date =~ /^([0-9]{4})([0-9]{2})([0-9]{2})/;
return 'TBA' if $y == 9999;
return 'unknown' if $y == 0;
return $y if $m == 99;
- return sprintf '%s %d', [qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)]->[$m-1], $y;
+ my $r = sprintf '%s %d', [qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)]->[$m-1], $y;
+ return $d == 99 ? "<i>$r</i>" : $r;
}
diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm
index fca3e5a7..f2f66f0b 100644
--- a/lib/VNDB/Handler/Misc.pm
+++ b/lib/VNDB/Handler/Misc.pm
@@ -212,7 +212,7 @@ sub history {
page => $f->{p},
results => 50,
auto => $f->{m},
- hidden => $f->{h},
+ hidden => $type && $type ne 'u' ? 0 : $f->{h},
edit => $f->{e},
releases => $f->{r},
);
diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm
index 7f43dd78..5977ac1e 100644
--- a/lib/VNDB/Handler/Producers.pm
+++ b/lib/VNDB/Handler/Producers.pm
@@ -171,11 +171,8 @@ sub list {
div class => 'mainbox';
h1 'Browse producers';
- form class => 'search', action => '/p/all', 'accept-charset' => 'UTF-8', method => 'get';
- fieldset;
- input type => 'text', name => 'q', id => 'q', class => 'text', value => $f->{q};
- input type => 'submit', class => 'submit', value => 'Search!';
- end;
+ form action => '/p/all', 'accept-charset' => 'UTF-8', method => 'get';
+ $self->htmlSearchBox('p', $f->{q});
end;
p class => 'browseopts';
for ('all', 'a'..'z', 0) {
diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm
index 8cd2eb43..d031340d 100644
--- a/lib/VNDB/Handler/Releases.pm
+++ b/lib/VNDB/Handler/Releases.pm
@@ -5,12 +5,12 @@ use strict;
use warnings;
use YAWF ':html';
use VNDB::Func;
-use POSIX 'strftime';
YAWF::register(
qr{r([1-9]\d*)(?:\.([1-9]\d*))?} => \&page,
qr{(v)([1-9]\d*)/add} => \&edit,
+ qr{r} => \&browse,
qr{r(?:([1-9]\d*)(?:\.([1-9]\d*))?/edit)}
=> \&edit,
);
@@ -41,6 +41,8 @@ sub page {
} ],
[ type => 'Type', serialize => sub { $self->{release_types}[$_[0]] } ],
[ patch => 'Patch', serialize => sub { $_[0] ? 'Patch' : 'Not a patch' } ],
+ [ freeware => 'Freeware', serialize => sub { $_[0] ? 'yes' : 'nope' } ],
+ [ doujin => 'Doujin', serialize => sub { $_[0] ? 'yups' : 'nope' } ],
[ title => 'Title (romaji)', diff => 1 ],
[ original => 'Original title', diff => 1 ],
[ gtin => 'JAN/UPC/EAN', serialize => sub { $_[0]||'[none]' } ],
@@ -57,6 +59,10 @@ sub page {
$med->[1] ? sprintf('%d %s%s', $_->{qty}, $med->[0], $_->{qty}>1?'s':'') : $med->[0]
} @{$_[0]};
} ],
+ [ resolution => 'Resolution', serialize => sub { $self->{resolutions}[$_[0]][0] } ],
+ [ voiced => 'Voiced', serialize => sub { $self->{voiced}[$_[0]] } ],
+ [ ani_story => 'Story animation',serialize => sub { $self->{animated}[$_[0]] } ],
+ [ ani_ero => 'Ero animation', serialize => sub { $self->{animated}[$_[0]] } ],
[ producers => 'Producers', join => '<br />', split => sub {
map sprintf('<a href="/p%d" title="%s">%s</a>', $_->{id}, $_->{original}||$_->{name}, shorten $_->{name}, 50), @{$_[0]};
} ],
@@ -126,6 +132,11 @@ sub _infotable {
end;
end;
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Publication';
+ td join ', ', $r->{freeware} ? 'Freeware' : 'Non-free', $r->{patch} ? () : $r->{doujin} ? 'doujin' : 'commercial';
+ end;
+
if(@{$r->{platforms}}) {
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Platform'.($#{$r->{platforms}} ? 's' : '');
@@ -149,6 +160,29 @@ sub _infotable {
end;
}
+ if($r->{resolution}) {
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Resolution';
+ td $self->{resolutions}[$r->{resolution}][0];
+ end;
+ }
+
+ if($r->{voiced}) {
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Voiced';
+ td $self->{voiced}[$r->{voiced}];
+ end;
+ }
+
+ if($r->{ani_story} || $r->{ani_ero}) {
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Animation';
+ td join ', ',
+ $r->{ani_story} ? ('Story: ' .$self->{animated}[$r->{ani_story}]):(),
+ $r->{ani_ero} ? ('Ero scenes: '.$self->{animated}[$r->{ani_ero} ]):();
+ end;
+ }
+
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Released';
td;
@@ -159,7 +193,7 @@ sub _infotable {
if($r->{minage} >= 0) {
Tr ++$i % 2 ? (class => 'odd') : ();
td 'Age rating';
- td $self->{age_ratings}{$r->{minage}};
+ td $self->{age_ratings}{$r->{minage}}[0];
end;
}
@@ -247,8 +281,8 @@ sub edit {
my $vn = $rid ? $r->{vn} : [{ vid => $vid, title => $v->{title} }];
my %b4 = !$rid ? () : (
- (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 ],
+ (map { $_ => $r->{$_} } qw|type title original gtin catalog language website released
+ notes minage platforms patch resolution voiced freeware doujin ani_story ani_ero|),
media => join(',', sort map "$_->{medium} $_->{qty}", @{$r->{media}}),
producers => join('|||', map "$_->{id},$_->{name}", sort { $a->{id} <=> $b->{id} } @{$r->{producers}}),
);
@@ -259,6 +293,8 @@ sub edit {
$frm = $self->formValidate(
{ name => 'type', enum => [ 0..$#{$self->{release_types}} ] },
{ name => 'patch', required => 0, default => 0 },
+ { name => 'freeware', required => 0, default => 0 },
+ { name => 'doujin', required => 0, default => 0 },
{ name => 'title', maxlength => 250 },
{ name => 'original', required => 0, default => '', maxlength => 250 },
{ name => 'gtin', required => 0, default => '0',
@@ -266,38 +302,41 @@ sub edit {
{ 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' },
+ { name => 'released', required => 0, default => 0, template => 'int' },
{ name => 'minage' , required => 0, default => -1, enum => [ keys %{$self->{age_ratings}} ] },
{ name => 'notes', required => 0, default => '', maxlength => 10240 },
{ name => 'platforms', required => 0, default => '', multi => 1, enum => [ keys %{$self->{platforms}} ] },
{ name => 'media', required => 0, default => '' },
+ { name => 'resolution',required => 0, default => 0, enum => [ 0..$#{$self->{resolutions}} ] },
+ { name => 'voiced', required => 0, default => 0, enum => [ 0..$#{$self->{voiced}} ] },
+ { name => 'ani_story', required => 0, default => 0, enum => [ 0..$#{$self->{animated}} ] },
+ { name => 'ani_ero', required => 0, default => 0, enum => [ 0..$#{$self->{animated}} ] },
{ name => 'producers', required => 0, default => '' },
{ name => 'vn', maxlength => 5000 },
{ name => 'editsum', maxlength => 5000 },
);
if(!$frm->{_err}) {
# de-serialize
- my $released = !$frm->{released}[0] ? 0 : $frm->{released}[0] == 9999 ? 99999999 :
- sprintf '%04d%02d%02d', $frm->{released}[0], $frm->{released}[1]||99, $frm->{released}[2]||99;
my $media = [ map [ split / / ], split /,/, $frm->{media} ];
my $producers = [ map { /^([0-9]+)/ ? $1 : () } split /\|\|\|/, $frm->{producers} ];
my $new_vn = [ map { /^([0-9]+)/ ? $1 : () } split /\|\|\|/, $frm->{vn} ];
$frm->{platforms} = [ grep $_, @{$frm->{platforms}} ];
- $frm->{patch} = $frm->{patch} ? 1 : 0;
+ $frm->{$_} = $frm->{$_} ? 1 : 0 for (qw|patch freeware doujin|);
+ $frm->{doujin} = 0 if $frm->{patch};
return $self->resRedirect("/r$rid", 'post')
- if $rid && $released == $r->{released} &&
+ if $rid &&
(join(',', sort @{$b4{platforms}}) eq join(',', sort @{$frm->{platforms}})) &&
(join(',', sort @$producers) eq join(',', sort map $_->{id}, @{$r->{producers}})) &&
(join(',', sort @$new_vn) eq join(',', sort map $_->{vid}, @$vn)) &&
- !grep !/^(released|platforms|producers|vn)$/ && $frm->{$_} ne $b4{$_}, keys %b4;
+ !grep !/^(platforms|producers|vn)$/ && $frm->{$_} ne $b4{$_}, keys %b4;
my %opts = (
- (map { $_ => $frm->{$_} } qw| type title original gtin catalog language website notes minage platforms editsum patch|),
+ (map { $_ => $frm->{$_} } qw| type title original gtin catalog language website released
+ notes minage platforms resolution editsum patch voiced freeware doujin ani_story ani_ero|),
vn => $new_vn,
producers => $producers,
media => $media,
- released => $released,
);
$rev = 1;
@@ -332,6 +371,8 @@ sub _form {
[ select => short => 'type', name => 'Type',
options => [ map [ $_, $self->{release_types}[$_] ], 0..$#{$self->{release_types}} ] ],
[ check => short => 'patch', name => 'This release is a patch to another release.' ],
+ [ check => short => 'freeware', name => 'Freeware (i.e. available at no cost)' ],
+ [ check => short => 'doujin', name => 'Doujin (self-published / not by a commercial company)' ],
[ 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.' ],
@@ -340,32 +381,26 @@ sub _form {
[ 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';
- option value => $_, $frm->{released} && $frm->{released}[0] == $_ ? (selected => 'selected') : (),
- !$_ ? '-year-' : $_ < 9999 ? $_ : 'TBA'
- for (0, 1980..((localtime())[5]+1905), 9999);
- end;
- Select id => 'released_m', name => 'released';
- option value => $_, $frm->{released} && $frm->{released}[1] == $_ ? (selected => 'selected') : (),
- !$_ ? '-month-' : strftime '%B', 0, 0, 0, 0, $_, 0, 0, 0
- for(0..12);
- end;
- Select id => 'released_d', name => 'released';
- option value => $_, $frm->{released} && $frm->{released}[2] == $_ ? (selected => 'selected') : (),
- !$_ ? '-day-' : $_
- for(0..31);
- end;
- }],
+ [ date => short => 'released', name => 'Release date' ],
[ static => content => 'Leave month or day blank if they are unknown' ],
[ select => short => 'minage', name => 'Age rating',
- options => [ map [ $_, $self->{age_ratings}{$_} ], sort { $a <=> $b } keys %{$self->{age_ratings}} ] ],
+ options => [ map [ $_, $self->{age_ratings}{$_}[0].($self->{age_ratings}{$_}[1]?" (e.g. $self->{age_ratings}{$_}[1])":'') ],
+ sort { $a <=> $b } keys %{$self->{age_ratings}} ] ],
[ textarea => short => 'notes', name => 'Notes' ],
[ static => content => 'Miscellaneous notes/comments, information that does not fit in the above fields. '
.'E.g.: Censored/uncensored or for which releases this patch applies. Max. 250 characters.' ],
],
- 'Platforms & Media' => [
+ 'Format' => [
+ [ select => short => 'resolution', name => 'Resolution', options => [
+ map [ $_, @{$self->{resolutions}[$_]} ], 0..$#{$self->{resolutions}} ] ],
+ [ select => short => 'voiced', name => 'Voiced', options => [
+ map [ $_, $self->{voiced}[$_] ], 0..$#{$self->{voiced}} ] ],
+ [ select => short => 'ani_story', name => 'Story animation', options => [
+ map [ $_, $self->{animated}[$_] ], 0..$#{$self->{animated}} ] ],
+ [ select => short => 'ani_ero', name => 'Ero animation', options => [
+ map [ $_, $_ ? $self->{animated}[$_] : 'Unknown / no ero scenes' ], 0..$#{$self->{animated}} ] ],
+ [ static => content => 'Animation in erotic scenes, leave to unknown if there are no ero scenes.' ],
[ hidden => short => 'media' ],
[ static => nolabel => 1, content => sub {
h2 'Platforms';
@@ -423,5 +458,208 @@ sub _form {
}
+sub browse {
+ my $self = shift;
+
+ 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 => [ keys %{$self->{languages}} ] },
+ { name => 'pl', required => 0, multi => 1, default => '', enum => [ keys %{$self->{platforms}} ] },
+ { name => 'me', required => 0, multi => 1, default => '', enum => [ keys %{$self->{media}} ] },
+ { name => 'tp', required => 0, default => -1, enum => [ -1..$#{$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 => [ keys %{$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 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} >= 0 ? (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(
+ order => $f->{s}.($f->{o}eq'd'?' DESC':' ASC'),
+ page => $f->{p},
+ results => 50,
+ what => 'platforms',
+ @filters,
+ );
+
+ 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}";
+ $_&&($url .= ";ln=$_") for @{$f->{ln}};
+ $_&&($url .= ";pl=$_") for @{$f->{pl}};
+ $_&&($url .= ";re=$_") for @{$f->{re}};
+ $_&&($url .= ";me=$_") for @{$f->{me}};
+
+ $self->htmlHeader(title => 'Browse releases');
+ _filters($self, $f, !@filters || !@$list);
+ $self->htmlBrowse(
+ class => 'relbrowse',
+ items => $list,
+ options => $f,
+ nextpage => $np,
+ pageurl => "$url;s=$f->{s};o=$f->{o}",
+ sorturl => $url,
+ header => [
+ [ 'Released', 'released' ],
+ [ 'Rating', 'minage' ],
+ [ '', '' ],
+ [ 'Title', 'title' ],
+ ],
+ row => sub {
+ my($s, $n, $l) = @_;
+ Tr $n % 2 ? (class => 'odd') : ();
+ td class => 'tc1';
+ lit datestr $l->{released};
+ end;
+ td class => 'tc2', $l->{minage} > -1 ? $self->{age_ratings}{$l->{minage}}[0] : '';
+ td class => 'tc3';
+ $_ ne 'oth' && cssicon $_, $self->{platforms}{$_} for (@{$l->{platforms}});
+ cssicon "lang $l->{language}", $self->{languages}{$l->{language}};
+ cssicon lc(substr($self->{release_types}[$l->{type}],0,3)), $self->{release_types}[$l->{type}];
+ end;
+ td class => 'tc4';
+ a href => "/r$l->{id}", title => $l->{original}||$l->{title}, shorten $l->{title}, 90;
+ b class => 'grayedout', ' (patch)' if $l->{patch};
+ end;
+ end;
+ },
+ ) if @$list;
+ if(@filters && !@$list) {
+ div class => 'mainbox';
+ h1 'No results found';
+ div class => 'notice';
+ p qq|Sorry, couldn't find anything that comes through your filters. You might want to disable a few filters to get more results.\n\n|
+ .qq|Also, keep in mind that we don't have all information about all releases. So e.g. filtering on screen resolution will exclude |
+ .qq|all releases of which we don't know it's resolution, even though it might in fact be in the resolution you're looking for.|;
+ end;
+ end;
+ }
+ $self->htmlFooter;
+}
+
+
+sub _filters {
+ my($self, $f, $shown) = @_;
+
+ form method => 'get', action => '/r', 'accept-charset' => 'UTF-8';
+ div class => 'mainbox';
+ h1 'Browse releases';
+
+ $self->htmlSearchBox('r', $f->{q});
+
+ a id => 'advselect', href => '#';
+ lit '<i>'.($shown?'&#9662;':'&#9656;').'</i> filters';
+ end;
+ div id => 'advoptions', !$shown ? (class => 'hidden') : ();
+
+ h2 'Filters';
+ table class => 'formtable', style => 'margin-left: 0';
+ Tr class => 'newfield';
+ td class => 'label'; label for => 'ma_m', 'Age rating'; end;
+ td class => 'field';
+ Select id => 'ma_m', name => 'ma_m', style => 'width: 70px';
+ option value => 0, $f->{ma_m} == 0 ? ('selected' => 'selected') : (), 'greater';
+ option value => 1, $f->{ma_m} == 1 ? ('selected' => 'selected') : (), 'smaller';
+ end;
+ txt ' than or equal to ';
+ Select id => 'ma_a', name => 'ma_a', style => 'width: 80px; text-align: center';
+ $_>=0 && option value => $_, $f->{ma_a} == $_ ? ('selected' => 'selected') : (), $self->{age_ratings}{$_}[0]
+ for (sort { $a <=> $b } keys %{$self->{age_ratings}});
+ end;
+ end;
+ td rowspan => 5, style => 'padding-left: 40px';
+ label for => 're', 'Screen 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 => 'Release type',
+ options => [ [-1, 'All'], map [ $_, $self->{release_types}[$_] ], 0..$#{$self->{release_types}} ]]);
+ $self->htmlFormPart($f, [ select => short => 'pa', name => 'Patch status',
+ options => [ [0, 'All'], [1, 'Only patches'], [2, 'Only standalone releases']]]);
+ $self->htmlFormPart($f, [ select => short => 'fw', name => 'Freeware',
+ options => [ [0, 'All'], [1, 'Freeware only'], [2, 'Only non-free releases']]]);
+ $self->htmlFormPart($f, [ select => short => 'do', name => 'Doujin',
+ options => [ [0, 'All'], [1, 'Only doujin releases'], [2, 'Only commercial releases']]]);
+ $self->htmlFormPart($f, [ date => short => 'mi', name => 'Released after' ]);
+ $self->htmlFormPart($f, [ date => short => 'ma', name => 'Released before' ]);
+ end;
+
+ h2;
+ lit 'Languages <b>(boolean or, selecting more gives more results)</b>';
+ end;
+ for my $i (sort @{$self->dbLanguages}) {
+ 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", $self->{languages}{$i};
+ txt $self->{languages}{$i};
+ end;
+ end;
+ }
+
+ h2;
+ lit 'Platforms <b>(boolean or, selecting more gives more results)</b>';
+ end;
+ for my $i (sort keys %{$self->{platforms}}) {
+ next if $i eq 'oth';
+ span;
+ input type => 'checkbox', name => 'pl', value => $i, id => "plat_$i", grep($_ eq $i, @{$f->{pl}}) ? (checked => 'checked') : ();
+ label for => "plat_$i";
+ cssicon $i, $self->{platforms}{$i};
+ txt $self->{platforms}{$i};
+ end;
+ end;
+ }
+
+ h2;
+ lit 'Media <b>(boolean or, selecting more gives more results)</b>';
+ end;
+ for my $i (sort keys %{$self->{media}}) {
+ next if $i eq 'otc';
+ span;
+ input type => 'checkbox', name => 'me', value => $i, id => "med_$i", grep($_ eq $i, @{$f->{me}}) ? (checked => 'checked') : ();
+ label for => "med_$i", $self->{media}{$i}[0];
+ end;
+ }
+
+ div style => 'text-align: center; clear: left;';
+ input type => 'submit', value => 'Apply', class => 'submit';
+ input type => 'reset', value => 'Clear', class => 'submit', onclick => 'location.href="/r"';
+ end;
+ end;
+ end;
+ end;
+}
+
+
1;
diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm
index d10de195..a6d02698 100644
--- a/lib/VNDB/Handler/Tags.pm
+++ b/lib/VNDB/Handler/Tags.pm
@@ -367,12 +367,9 @@ sub taglist {
$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;
+ form action => '/g/list', 'accept-charset' => 'UTF-8', method => 'get';
+ input type => 'hidden', name => 't', value => $f->{t};
+ $self->htmlSearchBox('g', $f->{q});
end;
p class => 'browseopts';
a href => "/g/list?q=$f->{q};t=-1", $f->{t} == -1 ? (class => 'optselected') : (), 'All';
@@ -589,11 +586,8 @@ sub tagindex {
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;
+ form action => '/g/list', 'accept-charset' => 'UTF-8', method => 'get';
+ $self->htmlSearchBox('g', '');
end;
end;
diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm
index f50a8500..d5791907 100644
--- a/lib/VNDB/Handler/VNBrowse.pm
+++ b/lib/VNDB/Handler/VNBrowse.pm
@@ -25,11 +25,7 @@ sub list {
return 404 if $f->{_err};
$f->{q} ||= $f->{sq};
- # NOTE: this entire search thingy can also be done using a PgSQL fulltext search,
- # which is faster and requires less code. It does require an extra database
- # column, index and some triggers, though
-
- my(@cati, @cate, @plat, @lang);
+ my(@plat, @lang);
my $q = $f->{q};
if($q) {
# VNDBID
@@ -37,15 +33,6 @@ sub list {
if $q =~ /^([gvrptud])([0-9]+)(?:\.([0-9]+))?$/;
if(!($q =~ s/^title://)) {
- # categories
- my %catl = map {
- my $ic = $_;
- map { $ic.$_ => $self->{categories}{$ic}[1]{$_} } keys %{$self->{categories}{$ic}[1]}
- } keys %{$self->{categories}};
-
- $q =~ s/-(?:$catl{$_}|c:$_)//ig && push @cate, $_ for keys %catl;
- $q =~ s/(?:$catl{$_}|c:$_)//ig && push @cati, $_ for keys %catl;
-
# platforms
$_ ne 'oth' && $q =~ s/(?:$self->{platforms}{$_}|p:$_)//ig && push @plat, $_ for keys %{$self->{platforms}};
@@ -62,8 +49,6 @@ sub list {
results => 50,
page => $f->{p},
order => ($f->{s} eq 'rel' ? 'c_released' : $f->{s} eq 'pop' ? 'c_popularity' : 'title').($f->{o} eq 'a' ? ' ASC' : ' DESC'),
- @cati ? ( cati => \@cati ) : (),
- @cate ? ( cate => \@cate ) : (),
@lang ? ( lang => \@lang ) : (),
@plat ? ( platform => \@plat ) : (),
);
@@ -117,11 +102,8 @@ sub _filters {
div class => 'mainbox';
h1 'Browse visual novels';
- form class => 'search', action => '/v/all', 'accept-charset' => 'UTF-8', method => 'get';
- fieldset;
- input type => 'text', name => 'q', id => 'q', class => 'text', value => $f->{q};
- input type => 'submit', class => 'submit', value => 'Search!';
- end;
+ form action => '/v/all', 'accept-charset' => 'UTF-8', method => 'get';
+ $self->htmlSearchBox('v', $f->{q});
end;
p class => 'browseopts';
for ('all', 'a'..'z', 0) {
@@ -131,23 +113,7 @@ sub _filters {
a id => 'advselect', href => '#';
lit '<i>&#9656;</i> advanced search';
end;
- div id => 'advoptions', class => 'hidden';
-
- h2;
- lit 'Categories <b>(boolean and, selecting more gives less results. The categories are explained on <a href="/d1">this page</a>)</b>';
- end;
- ul id => 'catselect';
- for my $c (qw| e g t p h l s |) {
- $c !~ /[thl]/ ? li : br;
- txt $self->{categories}{$c}[0];
- ul;
- li id => "cat_$c$_", $self->{categories}{$c}[1]{$_}
- for (sort keys %{$self->{categories}{$c}[1]});
- end;
- end if $c !~ /[gph]/;
- }
- end;
-
+ div id => 'advoptions', class => 'hidden vnoptions';
h2;
lit 'Languages <b>(boolean or, selecting more gives more results)</b>';
end;
diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm
index 8363b500..3b0619ab 100644
--- a/lib/VNDB/Handler/VNEdit.pm
+++ b/lib/VNDB/Handler/VNEdit.pm
@@ -55,11 +55,11 @@ 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 $anime = { map +($_=>1), grep /^[0-9]+$/, split /[ ,]+/, $frm->{anime} };
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} ];
- $frm->{anime} = join ' ', sort { $a <=> $b } @$anime;
+ $frm->{anime} = join ' ', sort { $a <=> $b } keys %$anime;
$frm->{relations} = join '|||', map $_->[0].','.$_->[1].','.$_->[2], sort { $a->[1] <=> $b->[1]} @{$relations};
$frm->{img_nsfw} = $frm->{img_nsfw} ? 1 : 0;
$frm->{screenshots} = join ' ', map sprintf('%d,%d,%d', $_->[0], $_->[1]?1:0, $_->[2]), sort { $a->[0] <=> $b->[0] } @$screenshots;
@@ -71,7 +71,7 @@ sub edit {
# execute the edit/add
my %args = (
(map { $_ => $frm->{$_} } qw|title original alias desc length l_wp l_encubed l_renai l_vnn editsum img_nsfw|),
- anime => $anime,
+ anime => [ keys %$anime ],
categories => $v->{categories},
relations => $relations,
image => $image,
diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm
index d5d1c0a1..fac6305b 100644
--- a/lib/VNDB/Handler/VNPage.pm
+++ b/lib/VNDB/Handler/VNPage.pm
@@ -422,7 +422,7 @@ sub _releases {
for my $rel (grep $l eq $_->{language}, @$r) {
Tr;
td class => 'tc1'; lit datestr $rel->{released}; end;
- td class => 'tc2', $rel->{minage} < 0 ? '' : $self->{age_ratings}{$rel->{minage}};
+ td class => 'tc2', $rel->{minage} < 0 ? '' : $self->{age_ratings}{$rel->{minage}}[0];
td class => 'tc3';
for (sort @{$rel->{platforms}}) {
next if $_ eq 'oth';
diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm
index 744216c1..7421b1c0 100644
--- a/lib/VNDB/Util/CommonHTML.pm
+++ b/lib/VNDB/Util/CommonHTML.pm
@@ -8,10 +8,11 @@ use Exporter 'import';
use Algorithm::Diff::XS 'compact_diff';
use VNDB::Func;
use Encode 'encode_utf8', 'decode_utf8';
+use POSIX 'ceil';
our @EXPORT = qw|
htmlMainTabs htmlDenied htmlHiddenMessage htmlBrowse htmlBrowseNavigate
- htmlRevision htmlEditMessage htmlItemMessage htmlVoteStats htmlHistory
+ htmlRevision htmlEditMessage htmlItemMessage htmlVoteStats htmlHistory htmlSearchBox
|;
@@ -419,7 +420,7 @@ sub htmlVoteStats {
end; end;
tfoot; Tr;
td colspan => 2, sprintf '%d vote%s total, average %.2f%s', $count, $count != 1 ? 's' : '', $total/$count,
- $type eq 'v' ? ' ('.$self->{votes}[sprintf '%.0f', $total/$count-1].')' : '';
+ $type eq 'v' ? ' ('.$self->{votes}[ceil($total/$count-1)].')' : '';
end; end;
for (reverse 0..$#$stats) {
Tr;
@@ -483,7 +484,7 @@ sub htmlHistory {
sub { td colspan => 2, class => 'tc1', 'Rev.' },
[ 'Date' ],
[ 'User' ],
- [ 'Page' ],
+ sub { td; a href => '#', id => 'history_comments', 'expand'; txt 'Page'; end; }
],
row => sub {
my($s, $n, $i) = @_;
@@ -506,8 +507,8 @@ sub htmlHistory {
end;
end;
if($i->{comments}) {
- Tr $n % 2 ? ( class => 'odd' ) : ();
- td colspan => 5, class => 'editsum';
+ Tr class => $n % 2 ? 'editsum odd hidden' : 'editsum hidden';
+ td colspan => 5;
lit bb2html $i->{comments}, 150;
end;
end;
@@ -517,4 +518,21 @@ sub htmlHistory {
}
+sub htmlSearchBox {
+ my($self, $sel, $v) = @_;
+
+ p class => 'searchtabs';
+ a href => '/v/all', $sel eq 'v' ? (class => 'sel') : (), 'Visual novels';
+ a href => '/r', $sel eq 'r' ? (class => 'sel') : (), 'Releases';
+ a href => '/p/all', $sel eq 'p' ? (class => 'sel') : (), 'Producers';
+ a href => '/g', $sel eq 'g' ? (class => 'sel') : (), 'Tags';
+ end;
+ fieldset class => 'search';
+ input type => 'text', name => 'q', id => 'q', class => 'text', value => $v;
+ input type => 'submit', class => 'submit', value => 'Search!';
+ end;
+}
+
+
+
1;
diff --git a/lib/VNDB/Util/FormHTML.pm b/lib/VNDB/Util/FormHTML.pm
index 17de6663..111fa5a6 100644
--- a/lib/VNDB/Util/FormHTML.pm
+++ b/lib/VNDB/Util/FormHTML.pm
@@ -5,6 +5,7 @@ use strict;
use warnings;
use YAWF ':html';
use Exporter 'import';
+use POSIX 'strftime';
our @EXPORT = qw| htmlFormError htmlFormPart htmlForm |;
@@ -116,6 +117,7 @@ sub htmlFormError {
# check name, short, (value)
# select name, short, options, (width)
# text name, short, (rows, cols)
+# date name, short
# part title
# TODO: Find a way to write this function in a readable way...
sub htmlFormPart {
@@ -184,11 +186,22 @@ sub htmlFormPart {
lit ref $o{content} eq 'CODE' ? $o{content}->($self, \%o) : $o{content};
}
if(/select/) {
+ my $l='';
Select name => $o{short}, id => $o{short}, $o{width} ? (style => "width: $o{width}px") : ();
- option value => $_->[0], defined $frm->{$o{short}} && $frm->{$o{short}} eq $_->[0] ? (selected => 'selected') : (), $_->[1]
- for @{$o{options}};
+ for (@{$o{options}}) {
+ if($_->[2] && $l ne $_->[2]) {
+ end if $l;
+ $l = $_->[2];
+ optgroup label => $l;
+ }
+ option value => $_->[0], defined $frm->{$o{short}} && $frm->{$o{short}} eq $_->[0] ? (selected => 'selected') : (), $_->[1];
+ }
+ end if $l;
end;
}
+ if(/date/) {
+ input type => 'hidden', id => $o{short}, name => $o{short}, value => $frm->{$o{short}}||'', class => 'dateinput';
+ }
if(/text/) {
(my $txt = $frm->{$o{short}}||'') =~ s/&/&amp;/;
$txt =~ s/</&lt;/;
diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm
index fcd088a4..1e6d8a60 100644
--- a/lib/VNDB/Util/LayoutHTML.pm
+++ b/lib/VNDB/Util/LayoutHTML.pm
@@ -57,8 +57,9 @@ sub _menu {
div;
a href => '/', 'Home'; br;
a href => '/v/all', 'Visual novels'; br;
- a href => '/g', 'Tags'; br;
+ a href => '/r', 'Releases'; br;
a href => '/p/all', 'Producers'; br;
+ a href => '/g', 'Tags'; br;
a href => '/u/all', 'Users'; br;
a href => '/hist', 'Recent changes'; br;
a href => '/t', 'Discussion board'; br;
diff --git a/static/f/script.js b/static/f/script.js
index b555c662..b39ba11f 100644
--- a/static/f/script.js
+++ b/static/f/script.js
@@ -66,14 +66,9 @@ function searchInit() {
return false;
});
- var l = x('catselect').getElementsByTagName('li');
- for(i=0;i<l.length;i++)
- if(l[i].id.substr(0,4) == 'cat_')
- l[i].onclick = function() {
- searchParse(1, this.innerHTML);
- };
-
- l = x('advoptions').getElementsByTagName('input');
+ if(x('advoptions').className.indexOf('vnoptions') < 0)
+ return;
+ var l = x('advoptions').getElementsByTagName('input');
for(i=0;i<l.length;i++)
if(l[i].id.substr(0,5) == 'lang_' || l[i].id.substr(0,5) == 'plat_')
l[i].onclick = function() {
@@ -110,14 +105,7 @@ function searchParse(add, term) {
}
q = q.toLowerCase();
- var l = x('catselect').getElementsByTagName('li');
- for(i=0;i<l.length;i++)
- if(l[i].id.substr(0,4) == 'cat_') {
- var cat = l[i].innerHTML.toLowerCase();
- l[i].className = q.indexOf('-'+cat) >= 0 ? 'exc' : q.indexOf(cat) >= 0 ? 'inc' : '';
- }
-
- l = x('advoptions').getElementsByTagName('input');
+ var l = x('advoptions').getElementsByTagName('input');
for(i=0;i<l.length;i++)
if(l[i].id.substr(0,5) == 'lang_' || l[i].id.substr(0,5) == 'plat_')
l[i].checked = q.indexOf(l[i].parentNode.getElementsByTagName('acronym')[0].title.toLowerCase()) >= 0 ? true : false;
@@ -389,6 +377,40 @@ function tvsSet(lvl, lim) {
+/* date input */
+var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
+function dtLoad(obj) {
+ var r = Math.floor(obj.value) || 0;
+ var v = [ Math.floor(r/10000), Math.floor(r/100)%100, r%100 ];
+ var i;
+ r = '<select onchange="dtSerialize(this)" style="width: 70px"><option value="0">-year-</option>';
+ for(i=1980; i<=(new Date()).getFullYear()+5; i++)
+ r += '<option value="'+i+'"'+(i == v[0] ? ' selected="selected"':'')+'>'+i+'</option>';
+ r += '<option value="9999"'+(v[0] == 9999 ? ' selected="selected"':'')+'>TBA</option>';
+ r += '</select><select onchange="dtSerialize(this)" style="width: 100px"><option value="99">-month-</option>';
+ for(i=1; i<=12; i++)
+ r += '<option value="'+i+'"'+(i == v[1] ? ' selected="selected"':'')+'>'+months[i-1]+'</option>';
+ r += '</select><select onchange="dtSerialize(this)" style="width: 70px"><option value="99">-day-</option>';
+ for(i=1; i<=31; i++)
+ r += '<option value="'+i+'"'+(i == v[2] ? ' selected="selected"':'')+'>'+i+'</option>';
+ r += '</select>';
+ v = document.createElement('div');
+ v.obj = obj;
+ v.innerHTML = r;
+ obj.parentNode.insertBefore(v, obj);
+}
+function dtSerialize(obj) {
+ obj = obj.parentNode;
+ var l = obj.getElementsByTagName('select');
+ var v = [ l[0].options[l[0].selectedIndex].value*1, l[1].options[l[1].selectedIndex].value*1, l[2].options[l[2].selectedIndex].value*1 ];
+ obj = obj.obj;
+ if(v[0] == 0) obj.value = 0;
+ else if(v[0] == 9999) obj.value = 99999999;
+ else obj.value = v[0]*10000 + v[1]*100 + (v[1]==99?99:v[2]);
+}
+
+
+
/* O N L O A D E V E N T */
DOMLoad(function() {
@@ -570,6 +592,32 @@ DOMLoad(function() {
l[i].onmouseout = function() { this.className = 'spoiler' };
}
+ // expand/collapse edit summaries on */hist
+ if(x('history_comments')) {
+ setcomment = function() {
+ var e = readCookie('histexpand') == 1;
+ var l = x('history_comments');
+ l.innerHTML = e ? 'collapse' : 'expand';
+ while(l.nodeName.toLowerCase() != 'table')
+ l = l.parentNode;
+ l = l.getElementsByTagName('tr');
+ for(var i=0;i<l.length;i++)
+ //alert(l[i].className);
+ if(l[i].className.indexOf('editsum') >= 0) {
+ if(!e && l[i].className.indexOf('hidden') < 0)
+ l[i].className += ' hidden';
+ if(e && l[i].className.indexOf('hidden') >= 0)
+ l[i].className = l[i].className.replace(/hidden/, '');
+ }
+ };
+ setcomment();
+ x('history_comments').onclick = function () {
+ setCookie('histexpand', readCookie('histexpand') == 1 ? 0 : 1);
+ setcomment();
+ return false;
+ };
+ }
+
// Are we really vndb?
if(location.hostname != 'vndb.org') {
var d = document.createElement('div');
@@ -578,6 +626,12 @@ DOMLoad(function() {
document.body.appendChild(d);
}
+ // date selector
+ l = document.getElementsByTagName('input');
+ for(i=0;i<l.length;i++)
+ if(l[i].className == 'dateinput')
+ dtLoad(l[i]);
+
// forms.js
if(x('relations'))
relLoad();
diff --git a/util/dump.sql b/util/dump.sql
index 5003c6e8..5d13e027 100644
--- a/util/dump.sql
+++ b/util/dump.sql
@@ -115,7 +115,13 @@ CREATE TABLE releases_rev (
minage smallint NOT NULL DEFAULT -1,
gtin bigint NOT NULL DEFAULT 0,
patch boolean NOT NULL DEFAULT FALSE,
- catalog varchar(50) NOT NULL DEFAULT ''
+ catalog varchar(50) NOT NULL DEFAULT '',
+ resolution smallint NOT NULL DEFAULT 0,
+ voiced smallint NOT NULL DEFAULT 0,
+ freeware boolean NOT NULL DEFAULT FALSE,
+ doujin boolean NOT NULL DEFAULT FALSE,
+ ani_story smallint NOT NULL DEFAULT 0,
+ ani_ero smallint NOT NULL DEFAULT 0
);
-- releases_vn
@@ -576,8 +582,8 @@ BEGIN
SELECT * FROM tags_vn UNION SELECT * FROM tag_vn_childs();
-- grouped by (tag, vid, uid), so only one user votes on one parent tag per VN entry
CREATE OR REPLACE TEMPORARY VIEW tags_vn_grouped AS
- SELECT tag, vid, uid, AVG(vote)::real AS vote, COALESCE(AVG(spoiler), 0)::real AS spoiler
- FROM tags_vn_all GROUP BY tag, vid, uid;
+ SELECT tag, vid, uid, MAX(vote)::real AS vote, COALESCE(AVG(spoiler), 0)::real AS spoiler
+ FROM tags_vn_all WHERE vote > 0 GROUP BY tag, vid, uid;
-- grouped by (tag, vid) and serialized into a table
DROP INDEX IF EXISTS tags_vn_bayesian_tag;
TRUNCATE tags_vn_bayesian;
diff --git a/util/updates/update_2.4.sql b/util/updates/update_2.4.sql
new file mode 100644
index 00000000..d55ce0ef
--- /dev/null
+++ b/util/updates/update_2.4.sql
@@ -0,0 +1,99 @@
+
+
+-- don't consider vns with vote < 0 on tag pages
+
+CREATE OR REPLACE FUNCTION tag_vn_calc() RETURNS void AS $$
+BEGIN
+ -- all votes for all tags
+ CREATE OR REPLACE TEMPORARY VIEW tags_vn_all AS
+ SELECT * FROM tags_vn UNION SELECT * FROM tag_vn_childs();
+ -- grouped by (tag, vid, uid), so only one user votes on one parent tag per VN entry
+ CREATE OR REPLACE TEMPORARY VIEW tags_vn_grouped AS
+ SELECT tag, vid, uid, MAX(vote)::real AS vote, COALESCE(AVG(spoiler), 0)::real AS spoiler
+ FROM tags_vn_all WHERE vote > 0 GROUP BY tag, vid, uid;
+ -- grouped by (tag, vid) and serialized into a table
+ DROP INDEX IF EXISTS tags_vn_bayesian_tag;
+ TRUNCATE tags_vn_bayesian;
+ INSERT INTO tags_vn_bayesian
+ SELECT tag, vid, COUNT(uid) AS users, AVG(vote)::real AS rating,
+ (CASE WHEN AVG(spoiler) < 0.7 THEN 0 WHEN AVG(spoiler) > 1.3 THEN 2 ELSE 1 END)::smallint AS spoiler
+ FROM tags_vn_grouped
+ GROUP BY tag, vid;
+ CREATE INDEX tags_vn_bayesian_tag ON tags_vn_bayesian (tag);
+ -- now perform the bayesian ranking calculation
+ UPDATE tags_vn_bayesian tvs SET rating =
+ ((SELECT AVG(users)::real * AVG(rating)::real FROM tags_vn_bayesian WHERE tag = tvs.tag) + users*rating)
+ / ((SELECT AVG(users)::real FROM tags_vn_bayesian WHERE tag = tvs.tag) + users)::real;
+ -- and update the VN count in the tags table as well
+ UPDATE tags SET c_vns = (SELECT COUNT(*) FROM tags_vn_bayesian WHERE tag = id);
+ RETURN;
+END;
+$$ LANGUAGE plpgsql;
+SELECT tag_vn_calc();
+
+
+
+
+-- resolution field
+ALTER TABLE releases_rev ADD COLUMN resolution smallint NOT NULL DEFAULT 0;
+-- voiced
+ALTER TABLE releases_rev ADD COLUMN voiced smallint NOT NULL DEFAULT 0;
+-- freeware / doujin
+ALTER TABLE releases_rev ADD COLUMN freeware boolean NOT NULL DEFAULT FALSE;
+ALTER TABLE releases_rev ADD COLUMN doujin boolean NOT NULL DEFAULT FALSE;
+-- animated
+ALTER TABLE releases_rev ADD COLUMN ani_story smallint NOT NULL DEFAULT 0;
+ALTER TABLE releases_rev ADD COLUMN ani_ero smallint NOT NULL DEFAULT 0;
+
+
+
+
+-- set doujin flag for all non-patch releases which have an "amateur group" as producer
+-- set freeware flag for all patches
+-- (the revision system makes this slightly more complex than doing a simple UPDATE)
+
+CREATE FUNCTION tmp_edit_release(iid integer) RETURNS void AS $$
+DECLARE
+ cid integer;
+ oid integer;
+ fw boolean;
+ do boolean;
+ comm text;
+BEGIN
+ SELECT INTO oid latest FROM releases WHERE id = iid;
+ SELECT INTO fw EXISTS(SELECT 1 FROM releases_rev WHERE id = oid AND patch);
+ SELECT INTO do EXISTS(SELECT 1 FROM releases_producers rp JOIN releases_rev rr ON rp.rid = rr.id
+ JOIN producers p ON p.id = rp.pid JOIN producers_rev pr ON pr.id = p.latest WHERE rp.rid = oid AND pr.type = 'ng' AND rr.patch = false);
+ IF NOT do AND NOT fw THEN
+ RETURN;
+ END IF;
+ comm := E'Automated edit with the update to VNDB 2.4.\n\n';
+ IF fw THEN
+ comm := comm || E'This release is a patch, freeware flag is assumed\n';
+ END IF;
+ IF do THEN
+ comm := comm || E'This release has an \'amateur group\' as producer and as such is likely to be a doujin release.\n';
+ END IF;
+ comm := comm || E'Feel free to revert if this assumption happens to be incorrect for this entry.';
+ INSERT INTO changes (type, requester, ip, comments, rev)
+ VALUES (1, 1, '0.0.0.0', comm, (SELECT rev+1 FROM changes WHERE id = oid))
+ RETURNING id INTO cid;
+ INSERT INTO releases_media (rid, medium, qty) SELECT cid, medium, qty FROM releases_media WHERE rid = oid;
+ INSERT INTO releases_platforms (rid, platform) SELECT cid, platform FROM releases_platforms WHERE rid = oid;
+ INSERT INTO releases_producers (rid, pid) SELECT cid, pid FROM releases_producers WHERE rid = oid;
+ INSERT INTO releases_rev (id, rid, title, original, type, language, website, released, notes,
+ minage, gtin, patch, catalog, resolution, voiced, freeware, doujin, ani_story, ani_ero)
+ SELECT cid, rid, title, original, type, language, website, released, notes,
+ minage, gtin, patch, catalog, resolution, voiced, fw, do, ani_story, ani_ero
+ FROM releases_rev WHERE id = oid;
+ INSERT INTO releases_vn (rid, vid) SELECT cid, vid FROM releases_vn WHERE rid = oid;
+ UPDATE releases SET latest = cid WHERE id = iid;
+END;
+$$ LANGUAGE plpgsql;
+
+-- this can be done a lot more efficiently, but this method is just easier :-)
+SELECT tmp_edit_release(id) FROM releases WHERE hidden = FALSE;
+
+DROP FUNCTION tmp_edit_release(integer);
+
+