diff options
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | data/lang.txt | 21 | ||||
-rw-r--r-- | data/script.js | 114 | ||||
-rw-r--r-- | lib/VNDB/DB/Tags.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/DB/VN.pm | 24 | ||||
-rw-r--r-- | lib/VNDB/Handler/Tags.pm | 16 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNBrowse.pm | 40 |
7 files changed, 115 insertions, 103 deletions
@@ -12,6 +12,7 @@ - Added advanced page-browsing tabs to threads - Added notes field to the user VN list - Added vnlists.status filter to /u+/list + - Pass VN tag filters by ID rather than name 2.15 - 2010-12-15 - Removed expand/collapse from history browser and /u+/posts and switched to diff --git a/data/lang.txt b/data/lang.txt index 505da71d..7310e881 100644 --- a/data/lang.txt +++ b/data/lang.txt @@ -5875,27 +5875,6 @@ cs : Hodnocení hu : Értékelés nl : Waardering -:_vnbrowse_tagign_title -en : The following tags were ignored: -ru : Следующие теги были пропущены: -cs : Následující tagy byly ignorovány: -hu : Az allábi címkék nem lettek figyelembe véve: -nl : De volgende tags zijn genegeerd: - -:_vnbrowse_tagign_meta -en : can't filter on meta tags -ru : фильтрация по мета-тегам невозможна -cs : nedají se filtrovat meta tagy -hu : meta címkéken nem működik a szűrő -nl : kan niet filteren op metatags - -:_vnbrowse_tagign_notfound -en : no such tag found -ru : тег не найден -cs : nenalezeny takové tagy -hu : nem található ilyen címke -nl : tag niet gevonden - :_vnbrowse_fil_title en : Visual Novel Filters ru : Фильтры новелл diff --git a/data/script.js b/data/script.js index e5565a11..b208af56 100644 --- a/data/script.js +++ b/data/script.js @@ -41,22 +41,24 @@ var collapsed_icon = '▸'; /* M I N I M A L J A V A S C R I P T L I B R A R Y */ var http_request = false; -function ajax(url, func) { - if(http_request) +function ajax(url, func, async) { + if(!async && http_request) http_request.abort(); - http_request = (window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(); - if(http_request == null) + var req = (window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(); + if(req == null) return alert("Your browser does not support the functionality this website requires."); - http_request.onreadystatechange = function() { - if(!http_request || http_request.readyState != 4 || !http_request.responseText) + if(!async) + http_request = req; + req.onreadystatechange = function() { + if(!req || req.readyState != 4 || !req.responseText) return; - if(http_request.status != 200) + if(req.status != 200) return alert('Whoops, error! :('); - func(http_request); + func(req); }; url += (url.indexOf('?')>=0 ? ';' : '?')+(Math.floor(Math.random()*999)+1); - http_request.open('GET', url, true); - http_request.send(null); + req.open('GET', url, true); + req.send(null); } function setCookie(n,v) { @@ -1748,7 +1750,7 @@ function filLoad() { var c = tag('div', null); for(var i=1; i<l.length; i++) { // category link - var a = tag('a', { href: '#', onclick: filSelectCat, fil_num: i }, l[i][0]); + var a = tag('a', { href: '#', onclick: filSelectCat, fil_num: i, fil_onshow:[] }, l[i][0]); p.appendChild(a); p.appendChild(tag(' ')); @@ -1768,6 +1770,8 @@ function filLoad() { tag('td', {'class':'cont' }, fd[2])); if(fd[0]) fil_cats[0][fd[0]] = f; + if(fd[5]) + a.fil_onshow.push([ fd[5], f.fil_contents ]); t.appendChild(f); } c.appendChild(t); @@ -1802,6 +1806,8 @@ function filSelectCat(n) { setClass(fil_cats[i], 'optselected', i == n); setClass(fil_cats[i].fil_t, 'hidden', i != n); } + for(var i=0; i<fil_cats[n].fil_onshow.length; i++) + fil_cats[n].fil_onshow[i][0](fil_cats[n].fil_onshow[i][1]); return false } @@ -1844,14 +1850,14 @@ function filSerialize() { var v = fil_cats[0][f].fil_readfunc(fil_cats[0][f].fil_contents); var r = []; for(var h=0; h<v.length; h++) { - v[h] = (''+v[h]).split(''); + var vs = (''+v[h]).split(''); r[h] = ''; // this isn't a very fast escaping method, blame JavaScript for inflexible search/replace support - for(var i=0; i<v[h].length; i++) { + for(var i=0; i<vs.length; i++) { for(var j=0; j<fil_escape.length; j++) - if(v[h][i] == fil_escape[j]) + if(vs[i] == fil_escape[j]) break; - r[h] += j == fil_escape.length ? v[h][i] : '_'+(j<10?'0'+j:j); + r[h] += j == fil_escape.length ? vs[i] : '_'+(j<10?'0'+j:j); } } if(r.length > 0 && r[0] != '') @@ -1963,6 +1969,58 @@ function filFOptions(c, n, opts, setfunc) { ]; } +function filFTagInput(name, label) { + var visible = false; + var input = tag('input', {type:'text', 'class':'text', style:'width:410px', onfocus:filSelectField}); + var trfunc = function(item, tr) { + tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40), + item.getAttribute('meta') == 'yes' ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_meta')) : null, + item.getAttribute('state') == 0 ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_mod')) : null + )); + }; + var serfunc = function(item, obj) { + var tags = obj.value.split(/ *, */); + var id = item.getAttribute('id'); + tags[tags.length-1] = 'g'+id+':'+item.firstChild.nodeValue; + filSelectField(obj); + return tags.join(', '); + }; + var retfunc = function(o) { filSelectField(o); false }; + var parfunc = function(val) { return (val.split(/, */))[val.split(/, */).length-1]; }; + var readfunc = function(c) { + var l = []; + c.value.replace(/g([0-9]+):/g, function (a, e) { l.push(e); return '' }); + return l; + }; + var fetch = function(c) { + var v = c.fil_val; + if(!v[0]) { + c.value = ''; + return; + } + var q = []; var t = []; + for(var i=0; i<v.length; i++) { + q.push('id='+v[i]); + t.push('g'+v[i]+':'); + } + c.value = mt('_js_loading')+' (tags: '+t.join(',')+')'; + c.disabled = true; + if(visible) + ajax('/xml/tags.xml?'+q.join(';'), function (hr) { + var l = []; + var items = hr.responseXML.getElementsByTagName('item'); + for(var i=0; i<items.length; i++) + l.push('g'+items[i].getAttribute('id')+':'+items[i].firstChild.nodeValue); + c.value = l.join(', '); + c.disabled = false; + }, 1); + }; + var writefunc = function(c,v) { c.fil_val = v; fetch(c) }; + var showfunc = function(c) { visible = true; fetch(c); }; + dsInit(input, '/xml/tags.xml?q=', trfunc, serfunc, retfunc, parfunc); + return [name, label, input, readfunc, writefunc, showfunc]; +} + function filReleases() { var types = release_types; for(var i=0; i<types.length; i++) // l10n /_rtype_.+/ @@ -2020,28 +2078,6 @@ function filVN() { for(var i=0; i<len.length; i++) // l10n /_vnlength_.+/ len[i] = [ len[i], mt('_vnlength_'+len[i]) ]; - // tag include/exclude dropdown search - var taginc = tag('input', {type:'text', 'class':'text', style:'width:350px', onfocus:filSelectField}); - var tagexc = tag('input', {type:'text', 'class':'text', style:'width:350px', onfocus:filSelectField}); - var trfunc = function(item, tr) { - tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40), - item.getAttribute('meta') == 'yes' ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_meta')) : null, - item.getAttribute('state') == 0 ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_mod')) : null - )); - }; - var serfunc = function(item, obj) { - var tags = obj.value.split(/ *, */); - tags[tags.length-1] = item.firstChild.nodeValue; - filSelectField(obj); - return tags.join(', '); - }; - var retfunc = function(o) { filSelectField(o); false }; - var parfunc = function(val) { return (val.split(/, */))[val.split(/, */).length-1]; }; - var readfunc = function(c) { return c.value.split(/, */) }; - var writefunc = function(c,v) { c.value = v.join(', ') }; - dsInit(taginc, '/xml/tags.xml?q=', trfunc, serfunc, retfunc, parfunc); - dsInit(tagexc, '/xml/tags.xml?q=', trfunc, serfunc, retfunc, parfunc); - return [ mt('_vnbrowse_fil_title'), [ mt('_vnbrowse_general'), @@ -2050,8 +2086,8 @@ function filVN() { ], [ mt('_vnbrowse_tags'), [ '', ' ', tag('('+mt('_vnbrowse_booland')+')') ], - [ 'taginc', mt('_vnbrowse_taginc'), taginc, readfunc, writefunc ], - [ 'tagexc', mt('_vnbrowse_tagexc'), tagexc, readfunc, writefunc ], + filFTagInput('tag_inc', mt('_vnbrowse_taginc')), + filFTagInput('tag_exc', mt('_vnbrowse_tagexc')), filFOptions('tagspoil', ' ', [[0, mt('_vnbrowse_spoil0')],[1, mt('_vnbrowse_spoil1')],[2, mt('_vnbrowse_spoil2')]], function (o) { var s = getCookie('tagspoil'); if(o+'' == '') return s == null ? 0 : s; setCookie('tagspoil', o); return o}) ], diff --git a/lib/VNDB/DB/Tags.pm b/lib/VNDB/DB/Tags.pm index 4a87713b..b3e16960 100644 --- a/lib/VNDB/DB/Tags.pm +++ b/lib/VNDB/DB/Tags.pm @@ -24,7 +24,7 @@ sub dbTagGet { my %where = ( $o{id} ? ( - 't.id = ?' => $o{id} ) : (), + 't.id IN(!l)' => [ ref $o{id} ? $o{id} : [$o{id}] ] ) : (), $o{noid} ? ( 't.id <> ?' => $o{noid} ) : (), $o{name} ? ( diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm index d25a5796..b01d3328 100644 --- a/lib/VNDB/DB/VN.pm +++ b/lib/VNDB/DB/VN.pm @@ -10,7 +10,8 @@ use Encode 'decode_utf8'; our @EXPORT = qw|dbVNGet dbVNRevisionInsert dbVNImageId dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|; -# Options: id, rev, char, search, length, lang, olang, plat, tags_include, tags_exclude, hasani, results, page, what, sort, reverse +# Options: id, rev, char, search, length, lang, olang, plat, tag_inc, tag_exc, tagspoil, +# 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 { @@ -19,6 +20,12 @@ sub dbVNGet { $o{page} ||= 1; $o{what} ||= ''; $o{sort} ||= 'title'; + $o{tagspoil} //= 2; + + # user input that is literally added to the query should be checked... + die "Invalid input for tagspoil or tag_inc at dbVNGet()\n" if + grep !defined($_) || $_!~/^\d+$/, $o{tagspoil}, + !$o{tag_inc} ? () : (ref($o{tag_inc}) ? @{$o{tag_inc}} : $o{tag_inc}); my @where = ( $o{id} ? ( @@ -39,19 +46,18 @@ sub dbVNGet { '('.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}} ? ( + $o{tag_inc} ? ( '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 ] - ) : (), - $o{tags_exclude} && @{$o{tags_exclude}} ? ( - 'v.id NOT IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l))' => [ $o{tags_exclude} ] ) : (), + [ ref $o{tag_inc} ? $o{tag_inc} : [$o{tag_inc}], $o{tagspoil}, ref $o{tag_inc} ? $#{$o{tag_inc}}+1 : 1 ]) : (), + $o{tag_exc} ? ( + 'v.id NOT IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l))' => [ ref $o{tag_exc} ? $o{tag_exc} : [$o{tag_exc}] ] ) : (), $o{search} ? ( map +('v.c_search like ?', "%$_%"), normalize_query($o{search})) : (), # don't fetch hidden items unless we ask for an ID !$o{id} && !$o{rev} ? ( 'v.hidden = FALSE' => 0 ) : (), # optimize fetching random entries (only when there are no other filters present, otherwise this won't work well) - $o{sort} eq 'rand' && $o{results} <= 10 && !grep(!/^(?:results|page|what|sort)$/, keys %o) ? ( + $o{sort} eq 'rand' && $o{results} <= 10 && !grep(!/^(?:results|page|what|sort|tagspoil)$/, keys %o) ? ( sprintf 'v.id IN(SELECT floor(random() * last_value)::integer FROM generate_series(1,20), (SELECT last_value FROM vn_id_seq) s1 LIMIT 20)' ) : (), @@ -69,7 +75,7 @@ sub dbVNGet { 'JOIN relgraphs vg ON vg.id = v.rgraph' : (), ); - my $tag_ids = $o{tags_include} && join ',', @{$o{tags_include}[1]}; + my $tag_ids = $o{tag_inc} && join ',', ref $o{tag_inc} ? @{$o{tag_inc}} : $o{tag_inc}; my @select = ( # see https://rt.cpan.org/Ticket/Display.html?id=54224 for the cast on c_languages qw|v.id v.locked v.hidden v.c_released v.c_languages::text[] v.c_platforms vr.title vr.original v.rgraph|, 'vr.id AS cid', $o{what} =~ /extended/ ? ( @@ -84,7 +90,7 @@ sub dbVNGet { ) : (), # TODO: optimize this, as it will be very slow when the selected tags match a lot of VNs (>1000) $tag_ids ? - qq|(SELECT AVG(tvh.rating) FROM tags_vn_inherit tvh WHERE tvh.tag IN($tag_ids) AND tvh.vid = v.id AND spoiler <= $o{tags_include}[0] GROUP BY tvh.vid) AS tagscore| : (), + qq|(SELECT AVG(tvh.rating) FROM tags_vn_inherit tvh WHERE tvh.tag IN($tag_ids) AND tvh.vid = v.id AND spoiler <= $o{tagspoil} GROUP BY tvh.vid) AS tagscore| : (), ); my $order = sprintf { diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm index 0567bff6..6e373d04 100644 --- a/lib/VNDB/Handler/Tags.pm +++ b/lib/VNDB/Handler/Tags.pm @@ -44,7 +44,8 @@ sub tagpage { results => 50, page => $f->{p}, sort => $f->{s}, reverse => $f->{o} eq 'd', - tags_include => [ $f->{m}, [$tag ]], + tagspoil => $f->{m}, + tag_inc => $tag, ); my $title = mt '_tagp_title', $t->{meta}?0:1, $t->{name}; @@ -668,19 +669,22 @@ sub fulltree { sub tagxml { my $self = shift; - my $q = $self->formValidate({ name => 'q', maxlength => 500 }); - return 404 if $q->{_err}; - $q = $q->{q}; + my $f = $self->formValidate( + { name => 'q', required => 0, maxlength => 500 }, + { name => 'id', required => 0, multi => 1, template => 'int' }, + ); + return 404 if $f->{_err} || (!$f->{q} && !$f->{id} && !$f->{id}[0]); my($list, $np) = $self->dbTagGet( - $q =~ /^g([1-9]\d*)/ ? (id => $1) : $q =~ /^name:(.+)$/ ? (name => $1) : (search => $q), + !$f->{q} ? () : $f->{q} =~ /^g([1-9]\d*)/ ? (id => $1) : $f->{q} =~ /^name:(.+)$/ ? (name => $1) : (search => $f->{q}), + $f->{id} && $f->{id}[0] ? (id => $f->{id}) : (), results => 15, page => 1, ); $self->resHeader('Content-type' => 'text/xml; charset=UTF-8'); xml; - tag 'tags', more => $np ? 'yes' : 'no', query => $q; + tag 'tags', more => $np ? 'yes' : 'no', $f->{q} ? (query => $f->{q}) : (); for(@$list) { tag 'item', id => $_->{id}, meta => $_->{meta} ? 'yes' : 'no', state => $_->{state}, $_->{name}; } diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm index 8e30da99..25166717 100644 --- a/lib/VNDB/Handler/VNBrowse.pm +++ b/lib/VNDB/Handler/VNBrowse.pm @@ -25,7 +25,7 @@ sub list { ); return 404 if $f->{_err}; $f->{q} ||= $f->{sq}; - my $fil = fil_parse $f->{fil}, qw|length hasani taginc tagexc tagspoil lang olang plat|; + my $fil = fil_parse $f->{fil}, qw|length hasani tag_inc tag_exc taginc tagexc tagspoil lang olang plat|; _fil_compat($self, $fil); if($f->{q}) { @@ -39,20 +39,7 @@ sub list { } $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 { - my $i = $self->dbTagGet(name => $_)->[0]; - push @ignored, [$_, 0] if !$i; - push @ignored, [$_, 1] if $i && $i->{meta}; - $i && !$i->{meta} ? $i->{id} : (); - } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'') - }; - my @ti = $tagfind->(delete $fil->{taginc}); - my @te = $tagfind->(delete $fil->{tagexc}); - - $f->{s} = 'title' if !@ti && $f->{s} eq 'tagscore'; + $f->{s} = 'title' if !$fil->{tag_inc} && $f->{s} eq 'tagscore'; $f->{o} = $f->{s} eq 'tagscore' ? 'd' : 'a' if !$f->{o}; my($list, $np) = $self->dbVNGet( @@ -62,8 +49,6 @@ sub list { results => 50, page => $f->{p}, sort => $f->{s}, reverse => $f->{o} eq 'd', - @ti ? (tags_include => [ delete $fil->{tagspoil}, \@ti ]) : (), - @te ? (tags_exclude => \@te) : (), %$fil ); @@ -82,15 +67,6 @@ sub list { } end; - if(@ignored) { - div class => 'warning'; - h2 mt '_vnbrowse_tagign_title'; - ul; - li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @ignored; - end; - end; - } - a id => 'filselect', href => '#v'; lit '<i>▸</i> '.mt('_rbrowse_filters').'<i></i>'; # TODO: it's not *r*browse end; @@ -98,7 +74,7 @@ sub list { end; end; # /form - $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$f->{q};fil=$f->{fil}", scalar @ti); + $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$f->{q};fil=$f->{fil}", $fil->{tag_inc}); $self->htmlFooter; } @@ -117,6 +93,16 @@ sub _fil_compat { $fil->{taginc} //= $f->{ti} if $f->{ti}; $fil->{tagexc} //= $f->{te} if $f->{te}; $fil->{tagspoil} //= $f->{sp}; + + # older tag specification (by name rather than ID) + my $tagfind = sub { + return map { + my $i = $self->dbTagGet(name => $_)->[0]; + $i && !$i->{meta} ? $i->{id} : (); + } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'') + }; + $fil->{tag_inc} //= [ $tagfind->(delete $fil->{taginc}) ] if $fil->{taginc}; + $fil->{tag_exc} //= [ $tagfind->(delete $fil->{tagexc}) ] if $fil->{tagexc}; } |