diff options
33 files changed, 953 insertions, 903 deletions
@@ -1,8 +1,21 @@ -2.20 - ? +2.20 - 2011-05-01 + - Added support for sponsored links on VN pages - Order the VNs listed on char browser by release date - Order the traits groups on /i by their 'order' column - Use same browsing-table on trait pages and char browser + - Added spoiler warning to character revision pages + - Generate dbedit/dbdel notifications on character edits - CSS: Hide links in [spoiler] tags + - Added 'select' all to wishlist and moved 'select all' down on notifies + - Added char/tag/trait stats to database statistics box + - Update traits_chars cache daily using Multi::Maintenance + - Toggle [spoiler] tag visibility with global setting rather than mouse-over + - Added "Add character" link to VN pages + - Added "Image ID" field to VN image uploader + - Added "All except characters" filter to history browser + - Cleaned up CSS code + - Cleaned up permissions + - Replaced user ranks with a permission system - Bugfix: don't accidentally remove char traits when editing - Bugfix: fixed possible SQL table name clash on history browser - Bugfix: properly announce chars and traits in Multi::IRC @@ -17,6 +30,8 @@ - Bugfix: Properly format future dates on my vn list - Bugfix: Properly position the sub-tabs on VN page without tags - Bugfix: Allow unhiding of posts by mods + - Bugfix: Forgot to make two JS strings translatable + - Bugfix: Don't allow unicode numbers as 'int' in formValidate 2.19 - 2011-03-30 - Character database: @@ -39,7 +39,7 @@ .PHONY: all dirs js skins robots chmod chmod-tladmin multi-stop multi-start multi-restart sql-import\ update-2.10 update-2.11 update-2.12 update-2.13 update-2.14 update-2.15 update-2.16 update-2.17\ - update-2.18 update-2.19 + update-2.18 update-2.19 update-2.20 all: dirs js skins robots data/config.pl @@ -179,3 +179,9 @@ update-2.19: all $(multi-stop) ${runpsql} < util/updates/update_2.19.sql $(multi-start) + +update-2.20: all + $(multi-stop) + ${runpsql} < util/updates/update_2.20.sql + $(multi-start) + diff --git a/data/global.pl b/data/global.pl index 5fd2bf85..2453ed6b 100644 --- a/data/global.pl +++ b/data/global.pl @@ -31,15 +31,8 @@ our %S = (%S, scr_size => [ 136, 102 ], # w*h of screenshot thumbnails ch_size => [ 256, 300 ], # max. w*h of char images cv_size => [ 256, 400 ], # max. w*h of cover images - user_ranks => [ - # allowed actions # DB number - [qw| hist |], # 0 - [qw| hist |], # 1 - [qw| hist board |], # 2 - [qw| hist board edit tag |], # 3 - [qw| hist board boardmod edit charedit tag mod lock del tagmod |], # 4 - [qw| hist board boardmod edit charedit tag mod lock del tagmod usermod |], # 5 - ], + # bit flags + permissions => {qw| board 1 boardmod 2 edit 4 charedit 8 tag 16 dbmod 32 tagmod 64 usermod 128 affiliate 256 |}, languages => [qw|cs da de en es fi fr hu it ja ko nl no pl pt-br pt-pt ru sk sv tr vi zh|], producer_types => [qw|co in ng|], discussion_boards => [qw|an db ge v p u|], # <- note that some properties of these boards are hard-coded @@ -137,4 +130,3 @@ require $ROOT.'/data/config.pl' if -f $ROOT.'/data/config.pl'; 1; - diff --git a/data/lang.txt b/data/lang.txt index f037b61e..f3171116 100644 --- a/data/lang.txt +++ b/data/lang.txt @@ -104,51 +104,6 @@ ends with ']'. The following options are supported: # data/global.pl - used in many places -# user ranks - -:_urank_0 -en : visitor -ru : посетитель -cs : návštěvník -hu : vendég -nl : bezoeker - -:_urank_1 -en : banned -ru : забанен -cs : zabanovaný -hu : kicsapva -nl : gebanned - -:_urank_2 -en : loser -ru : лузер -cs : loser -hu : vesztes -nl : sukkel - -:_urank_3 -en : user -ru : пользователь -cs : uživatel -hu : felhasználó -nl : gebruiker - -:_urank_4 -en : mod -ru : модератор -cs : moderátor -hu : mod -nl : - -:_urank_5 -en : admin -ru : администратор -cs : administrátor -hu : admin -nl : - - # languages :_lang_cs @@ -1985,6 +1940,27 @@ cs : Producenti hu : Készítők nl : Producenten +:_menu_stat_chars +en : Characters +ru*: +cs*: +hu*: +nl : Karakters + +:_menu_stat_tags +en : VN Tags +ru*: +cs*: +hu*: +nl : + +:_menu_stat_traits +en : Character traits +ru*: +cs*: +hu*: +nl : Karakter kenmerken + :_menu_stat_users en : Users ru : Пользователей @@ -2143,6 +2119,20 @@ nl : laatste # Revision pages +:_revision_spoil_title +en : SPOILER WARNING! +ru*: +cs*: +hu*: +nl : SPOILERWAARSCHUWING! + +:_revision_spoil_msg +en : This revision page may contain major spoilers. You may want to view the [url,_1,final page] instead. +ru*: +cs*: +hu*: +nl : Deze revisiepagina kan spoilers bevatten. Je kan misschien beter de [url,_1,uiteindelijke pagina] openen. + :_revision_previous en : earlier revision ru : более ранняя редакция @@ -2485,6 +2475,13 @@ cs : Pouze postavy hu : Csak szereplők nl : Alleen karakters +:_hist_filter_nochars +en : All except characters +ru*: +cs*: +hu*: +nl : Alles behalve karakters + :_hist_filter_allactions en : Show all changes ru : Показать все изменения @@ -5431,6 +5428,13 @@ cs : Přidat vlastnost hu : Sajátosság hozzáadása nl : Voeg kenmerk toe +:_chare_traits_del +en : del +ru*: +cs*: +hu*: +nl : + :_chare_traits_present en : Selected trait is already present. ru*: @@ -5515,6 +5519,13 @@ cs : VN je již přítomna. hu : A VN már jelen van. nl : VN is al geselecteerd. +:_chare_vns_relexists +en : Release already present. +ru*: +cs*: +hu*: +nl : Uitgave is al geselecteerd. + # Character browser (/c/*) @@ -5647,6 +5658,13 @@ cs : Tato vlastnost ještě nebyla provázána s žádnými postavami nebo tyto hu : Ez a sajátosság még nincs hozzárendelve egy szereplőhöz se, vagy a spoiler beállításaid miatt, el vannak rejtve. nl : Dit kenmerk is nog niet gekoppeld aan een karakter, of deze worden niet weergegeven in verband met je spoilerinstelling. +:_traitp_cached +en : The list below also includes all characters linked to child traits. This list is cached, it can take up to 24 hours after a character has been edited for it to show up on this page. +ru*: +cs*: +hu*: +nl : De volgende lijst bevat ook alle karakters die gelinkt zijn aan subkenmerken. Deze lijst is gecached, het kan 24 uur duren voordat een karakter op deze pagina te zien is. + # Trait add/edit form (/i+/edit, /i+/add, /i/new) @@ -6686,12 +6704,12 @@ cs : Uživatelské jméno hu : Felhasználónév nl : Gebruikersnaam -:_usere_rank -en : Rank -ru : Ранг -cs : Postavení -hu : Rang -nl : +:_usere_perm +en : Permissions +ru*: +cs*: +hu*: +nl : Permissies :_usere_ignvotes en : Ignore votes in VN statistics @@ -7441,6 +7459,20 @@ cs : ~[obrázek se zpracovává, vraťte se prosím za několik minut~] hu : ~[a kép feldolgozás alatt van, gyere vissza pár perc múlva~] nl : ~[bezig met het verwerken van het plaatje, kom a.u.b. terug in een paar minuten~] +:_vnedit_image_id +en : Image ID +ru*: +cs*: ID obrázku +hu*: Kép ID (azonosító) +nl : Plaatje ID + +:_vnedit_image_id_msg +en : Use a character image that is already on the server. Set to '0' to remove the current image. +ru*: +cs*: +hu*: Használj egy olyan képet ami már megtalálható a szerveren. Ha '0'-ra állítod törölheted a jelenlegi képet. +nl : Gebruik een karakterplaatje dat al op de server staat. Gebruik '0' om een huidig plaatje te verwijderen. + :_vnedit_image_upload en : Upload new image ru : Загрузить новое изображение @@ -8315,6 +8347,13 @@ cs : K této vizuální novele zatím nemáme informace o žádném vydání... hu : Még nincs információnk ennek a visual novelnek a kiadásairól... nl : We hebben op dit moment nog geen informatie over uitgaven van deze visual novel... +:_vnpage_char_add +en : add character +ru*: +cs*: +hu*: +nl : voeg karakter toe + :_vnpage_rel_add en : add release ru : добавить выпуск diff --git a/data/notes/sponsored-links b/data/notes/sponsored-links index c31001d2..222553aa 100644 --- a/data/notes/sponsored-links +++ b/data/notes/sponsored-links @@ -1,7 +1,7 @@ Advertisements -Last modified: 2011-01 -Status: Long-term plans / nothing implemented yet +Last modified: 2011-04-10 +Status: Implemented / Implementation may differ from these notes Idea: (semi-)large "Buy now" / "Download now" button on VN pages, linking @@ -26,24 +26,18 @@ Possible parties interested in advertising: - Play-asia Has an affiliate system that includes direct links stores JAN, UPC, and catalog numbers -- Himeyashop / Erogeshop - Has no affiliate system but has shown interest in link exchanges in the past - Does not store JAN/UPC/catalog numbers - "Temporarily" closed, so probably not a good time to ask for ads? - DLSite English - Seems to have an affiliate system, haven't really looked at it yet + Has an affiliate system that includes direct links Most releases don't even have a JAN code or catalog number - MangaGamer Rather specific "shop", but could count as one. Has no affiliate system, but is planning to add one, as announced in http://mangagamer.wordpress.com/2010/12/31/holidays-passing/ Releases don't have catalog numbers or EAN codes -- Eroge-Europe.com - Seems to have an affiliate system, haven't really looked at it yet - Does not store JAN/UPC/catalog numbers - PaletWeb Has no affiliate system Does have JAN codes for a few titles, but inconsistent + Rather messy website... finding/updating links will be a chore - CDJapan Doesn't have that many VNs from what I've browsed, but still several Has an affiliate system (seems to include direct links) @@ -52,6 +46,13 @@ Possible parties interested in advertising: Does not seem to have many VNs (3 or 4?) Has no affiliate system Does not have JAN or catalog numbers +- Himeyashop / Erogeshop (out of business?) + Has no affiliate system but has shown interest in link exchanges in the past + Does not store JAN/UPC/catalog numbers + "Temporarily" closed, so probably not a good time to ask for ads? +- Eroge-Europe.com (out of business?) + Seems to have an affiliate system, haven't really looked at it yet + Does not store JAN/UPC/catalog numbers So who is going to update all those links? @@ -103,6 +104,3 @@ Three possibilities: special-case advertisers and give them special treatment or fetch additional information. -I would greatly prefer option #1, but since that's not very practical option -#3 (the VGMdb-like solution) is probably the best. - diff --git a/data/script.js b/data/script.js index 97f5fa30..41998405 100644 --- a/data/script.js +++ b/data/script.js @@ -1814,7 +1814,7 @@ function ctrAdd(item, spoil) { tag('b', {'class':'grayedout'}, group?group+' / ':''), tag('a', {'href':'/i'+id}, name)), sp, - tag('td', {'class':'tc_del'}, tag('a', {href:'#', onclick:ctrDel}, 'del')) + tag('td', {'class':'tc_del'}, tag('a', {href:'#', onclick:ctrDel}, mt('_chare_traits_del'))) )); ctrEmpty(); ctrSerialize(); @@ -1994,7 +1994,7 @@ function cvnRelChange() { while(tr.nodeName.toLowerCase() != 'tr') tr = tr.parentNode; if(byId('cvn_v'+tr.cvn_vid+'r'+val)) { - alert('Release already selected.'); + alert(mt('_chare_vns_relexists')); for(var i=0; i<this.options.length; i++) this.options[i].selected = this.options[i].value == tr.cvn_rid; return; @@ -2738,7 +2738,7 @@ if(byId('expandall')) { } -// charspoil handling (ugly) +// charspoil handling if(byId('charspoil_sel')) { var k = byClass('charspoil'); var h = byName(byId('charspoil_sel'), 'a'); @@ -2746,6 +2746,7 @@ if(byId('charspoil_sel')) { for(var i=0; i<k.length; i++) setClass(k[i], 'hidden', hasClass(k[i], 'charspoil_0') ? false : + hasClass(k[i], 'charspoil_-1') ? spoil > 1 : hasClass(k[i], 'charspoil_1') ? spoil < 1 : spoil < 2); for(var i=0; i<h.length; i++) setClass(h[i], 'sel', spoil == i); @@ -2762,6 +2763,23 @@ if(byId('charspoil_sel')) { } +// mouse-over price information / disclaimer +if(byId('buynow')) { + var l = byClass(byId('buynow'), 'acronym', 'pricenote'); + for(var i=0; i<l.length; i++) { + l[i].buynow_last = l[i].title; + l[i].title = null; + ddInit(l[i], 'bottom', function(acr) { + return tag('p', {onmouseover:ddHide, style:'padding: 3px'}, + acr.buynow_last, tag('br', null), + '* The displayed price only serves as an indication and', + tag('br', null), 'usually excludes shipping. Actual price may differ.' + ); + }); + } +} + + // set note input box (/u+/list) if(byId('not') && byId('vns')) byId('vns').onchange = function () { diff --git a/data/style.css b/data/style.css index 5b35e7af..9ed059c3 100644 --- a/data/style.css +++ b/data/style.css @@ -1,50 +1,15 @@ - - -* { - margin: 0; - padding: 0; -} - -body, td { - font: 11px "Tahoma"; -} -body { - $_bodybg$; - color: $maintext$ -} -a { - color: $maintext$; - text-decoration: none; -} -a:hover { - border-bottom: 1px dotted $maintext$; -} -table { - border-collapse: collapse; -} -table td { - vertical-align: top; - padding: 3px; -} -table tr.odd { - background: url($_boxbg$) repeat; -} -img { - border: none; -} - -#bgright { - position: absolute; - top: 0px; - right: 0px; - $_bgright$ -} - -#header { - position: absolute; - top: 80px; - left: 400px; -} +* { margin: 0; padding: 0; } +body, td { font: 11px "Tahoma"; } +body { $_bodybg$; color: $maintext$ } +a { color: $link$; text-decoration: none; } +a:hover { border-bottom: 1px dotted $maintext$; } +table { border-collapse: collapse; } +table td { vertical-align: top; padding: 3px; } +table tr.odd { background: url($_boxbg$) repeat; } +img { border: none; } + +#bgright { position: absolute; top: 0px; right: 0px; $_bgright$ } +#header { position: absolute; top: 80px; left: 400px; } #header h1, #header h1 a { font-family: "Futura", "Century New Gothic", "Arial", Serif; font-size: 24px; @@ -52,16 +17,8 @@ img { border: none!important; $_maintitle$ } - -#footer { - margin: 15px auto 0 auto; - text-align: center; - color: $footer$; -} -#footer a { - color: $footer$; - text-decoration: underline; -} +#footer { margin: 15px auto 0 auto; text-align: center; color: $footer$; } +#footer a { color: $footer$; text-decoration: underline; } #debug { position: fixed; @@ -77,42 +34,59 @@ img { #debug h2 { color: #f00!important; font-size: 20px; } #debug, #debug a { color: #fff!important; } -ul, ol { - margin-left: 35px; -} -p.locked { - float: right; - color: $standout$; - font-style: italic; - margin: 0!important; -} -b.grayedout { font-weight: normal; color: $grayedout$ } -i.grayedout { font-style: normal; color: $grayedout$ } -#maincontent h2 b { - font: 11px "Tahoma"; - font-weight: normal; -} -p.description { - margin: 10px 100px!important; -} -b.done { font-weight: normal; color: $statok$ } -b.todo { font-weight: normal; color: $statnok$ } -.clearfloat { - clear: both; - height: 0; -} -.hidden { - display: none!important -} +/* Warning/Notice Box */ +div.warning, div.notice { margin: 5px 10%; padding: 15px; background-color: $warnbg$; border: 1px solid $warnborder$; } +div.notice { background-color: $noticebg$; border: 1px solid $noticeborder$; } +div.warning ul, div.notice ul { margin-left: 0; } +div.warning li, div.notice li { margin-left: 20px; } +div.warning h2, div.notice h2 { font-size: 11px; font-weight: bold; margin: 0; } -b.spoiler, b.spoiler a { - color: #000!important; - background-color: #000; - font-weight: normal; +/* dropdown box */ +#dd_box { position: absolute; left: 0px; border: 1px solid $border$; background-color: $secbg$; z-index: 2 } +#dd_box ul { list-style-type: none; margin: 0; padding: 0 } +#dd_box li b { display: block; font-weight: normal; padding-left: 5px; } +#dd_box li i { display: block; font-style: normal; padding-left: 10px; padding-right: 5px } +#dd_box li a { display: block; padding-left: 10px; border: 0; padding-right: 5px } +#dd_box li a:hover { background: url($_boxbg$) repeat } + +/* dropdown search */ +#ds_box { + position: absolute; + top: 0; + border: 1px solid $border$; + border-top: none; + background-color: $secbg$; + cursor: pointer; + z-index: 2 } +#ds_box b { padding: 2px 0 0 10px; } +#ds_box tr.selected { background: url($_boxbg$) repeat; } +#ds_box table { width: 100%; } + + + +/* general text formatting */ + +ul, ol { margin-left: 35px; } +p.locked { float: right; color: $standout$; font-style: italic; margin: 0!important; } +b.grayedout { font-weight: normal; color: $grayedout$ } +i.grayedout { font-style: normal; color: $grayedout$ } +#maincontent h2 b { font: 11px "Tahoma"; font-weight: normal; } +p.description { margin: 10px 100px!important; } +b.done { font-weight: normal; color: $statok$ } +b.todo { font-weight: normal; color: $statnok$ } +p.center { text-align: center; } +b.future, +b.standout, +a.standout { font-weight: normal; color: $standout$; } +.clearfloat { clear: both; height: 0; } +.hidden { display: none!important } +.linethrough { text-decoration: line-through } +b.spoiler, b.spoiler a { color: #000!important; background-color: #000; font-weight: normal; } b.spoiler_shown { font-weight: normal } b.spoiler_shown a { color: $link$!important } + #maincontent div.quote { padding: 1px 5px; margin: 0px 10px; @@ -121,7 +95,6 @@ b.spoiler_shown a { color: $link$!important } border-left: 1px dotted $border$; text-align: left; } -.linethrough { text-decoration: line-through } pre { padding:1px 5px; margin: 5px 15px; @@ -136,13 +109,6 @@ pre { /***** general form markup *****/ -form, fieldset { - border: 0; - display: block; -} -legend { - display: none; -} input.text, input.submit, select, textarea { background-color: $secbg$; color: $maintext$; @@ -150,53 +116,46 @@ input.text, input.submit, select, textarea { font: 12px "Tahoma"; margin: 1px; } -optgroup option { - padding-left: 10px; - font-style: normal; -} -input.submit { - background: url($_boxbg$) repeat; - padding: 1px; -} -input.text, select { - width: 200px; -} -fieldset.submit { - width: 100%; - text-align: center; - margin: 5px; -} -fieldset.submit input { - width: 150px; -} -fieldset.submit h2 { - font-size: 11px!important; -} -fieldset.submit textarea { - margin: 0 20px 5px 20px; -} -table.formtable { - margin: 0 20px 20px 20px; -} -table.formtable td { - padding: 0; -} -table.formtable tr.newfield td { - padding-top: 5px; -} -table.formtable tr.newpart td { - padding-top: 20px; - font-weight: bold; -} -td.label, td.label label { - width: 90px; -} -td.label label { - display: block; -} -td.field label { - margin: 0 5px 0 5px; -} +form, fieldset { border: 0; display: block; } +legend { display: none; } +optgroup option { padding-left: 10px; font-style: normal; } +input.submit { background: url($_boxbg$) repeat; padding: 1px; } +input.text, select { width: 200px; } +fieldset.submit { width: 100%; text-align: center; margin: 5px; } +fieldset.submit input { width: 150px; } +fieldset.submit h2 { font-size: 11px!important; } +fieldset.submit textarea { margin: 0 20px 5px 20px; } +td.label, td.label label { width: 90px; } +td.label label { display: block; } +td.field label { margin: 0 5px 0 5px; } +table.formtable { margin: 0 20px 20px 20px; } +table.formtable td { padding: 0; } +table.formtable tr.newfield td { padding-top: 5px; } +table.formtable tr.newpart td { padding-top: 20px; font-weight: bold; } + + + + +/***** menu *****/ + + +#menulist a { color: $maintext$; text-decoration: none; } +#menulist a:hover { border-bottom: 1px dotted $maintext$; } +#menulist { position: absolute; left: 30px; top: 190px; width: 150px; } +#menulist div.menubox { margin: 0 0 10px 0; border: 1px solid $border$; background: url($_boxbg$) repeat; } +#menulist div.menubox div { padding: 2px 7px; } +#menulist h2 { border-bottom: 1px solid $border$; background: url($_boxbg$) repeat; padding: 1px 3px; } +#menulist h2, #menulist h2 a { font-size: 11px; color: $maintext$; } +#menulist h2 #lang_select { float: right; padding-top: 1px; } +#menulist dt { display: block; float: left; width: 93px; font-style: italic; } +#menulist dd { width: 40px; float: left; text-align: right; } +#menulist p { text-align: center; } +#menulist input.text { width: 100px; margin-left: 15px; } +#menulist input.submit { width: 90px; margin-left: 20px; } +#menulist #search input.text { width: 133px; margin: 0 0 3px 7px; font-style: italic; } +#menulist #search input.submit { display: none; } +#dd_box acronym { margin: 2px 5px 2px 0!important; } +#menulist .notifyget { display: inline-block; width: 125px; padding: 4px; background: $warnbg$; border: 1px solid $warnborder$; } @@ -211,114 +170,32 @@ td.field label { margin: 0; padding-bottom: 50px!important; } -#maincontent h1, #maincontent h2 { +.mainbox h1, .mainbox h2 { font-family: "Futura", "Century New Gothic", "Arial", Serif; font-weight: normal; font-size: 13px; } -#maincontent h1 { - color: $boxtitle$; - font-size: 19px; - margin: -5px 0 15px 0; -} -#maincontent h2.alttitle { - color: $alttitle$; - margin: -17px 0 15px 15px; - font-weight: normal; -} -#maincontent div.mainbox, #maincontent table.mainbox td { +div.mainbox, table.mainbox td { border: 1px solid $border$; margin: 21px 0 -10px 0; padding: 5px; background: url($_boxbg$) repeat; } -#maincontent .mainbox a { - color: $link$; -} -#maincontent p { - margin: 3px 20px; -} -#maincontent div div p, #maincontent div table p { - margin: 0; -} -#maincontent h2 { - font-weight: bold; - font-size: 14px; - margin: 10px 0 0 5px; -} -p.center { - text-align: center; -} -b.future, b.standout, a.standout { - font-weight: normal; - color: $standout$; -} - - - - -/***** menu *****/ - -#menulist { - position: absolute; - left: 30px; - top: 190px; - width: 150px; -} -#menulist div.menubox { - margin: 0 0 10px 0; - border: 1px solid $border$; - background: url($_boxbg$) repeat; -} -#menulist div.menubox div { - padding: 2px 7px; -} -#menulist h2 { - border-bottom: 1px solid $border$; - background: url($_boxbg$) repeat; - padding: 1px 3px; -} -#menulist h2, #menulist h2 a { - font-size: 11px; - color: $maintext$; -} -#menulist h2 #lang_select { - float: right; - padding-top: 1px; -} -#menulist dt { - display: block; - float: left; - width: 85px; - font-style: italic; -} -#menulist dd { - width: 40px; - float: left; - text-align: right; -} -#menulist p { - text-align: center; -} -#menulist input.text { - width: 100px; - margin-left: 15px; -} -#menulist input.submit { - width: 90px; - margin-left: 20px; -} -#menulist #search input.text { - width: 133px; - margin: 0 0 3px 7px; - font-style: italic; -} -#menulist #search input.submit { - display: none; -} -#dd_box acronym { margin: 2px 5px 2px 0!important; } -#menulist .notifyget { display: inline-block; width: 125px; padding: 4px; background: $warnbg$; border: 1px solid $warnborder$; } +.mainbox h1 { color: $boxtitle$; font-size: 19px; margin: -5px 0 15px 0; } +.mainbox h2.alttitle { color: $alttitle$; margin: -17px 0 15px 15px; font-weight: normal; } +.mainbox p { margin: 3px 20px; } +.mainbox div p, +.mainbox table p { margin: 0; } +.mainbox h2 { font-weight: bold; font-size: 14px; margin: 10px 0 0 5px; } +a.addnew, p.addnew { float: right; margin: 0 } +.mainbox.threelayout { border-collapse: separate; border-spacing: 10px; margin: 10px -10px -20px -10px; min-width: 100%; } +.mainbox.threelayout td { width: 32%; padding: 0 2px 10px 2px; } +.mainbox.threelayout h1 { margin: 0; font-size: 14px; font-weight: bold; } +.mainbox.threelayout h2 { font-size: 12px; margin-top: 3px; } +.mainbox.threelayout a.right { float: right; } +.mainbox.threelayout ul { list-style-type: none; margin-left: 10px; } +.mainbox.threelayout h1 a { color: $boxtitle$; } @@ -362,99 +239,33 @@ ul.maintabs.browsetabs li.left a { margin-left: 0; margin-right: 5px } /***** Homepage ******/ -#maincontent .mainbox.threelayout { - border-collapse: separate; - border-spacing: 10px; - margin: 10px -10px -20px -10px; - min-width: 100%; -} -#maincontent .mainbox.threelayout td { - width: 32%; - padding: 0 2px 10px 2px; -} -#maincontent .mainbox.threelayout h1 { - margin: 0; - font-size: 14px; - font-weight: bold; -} -#maincontent .mainbox.threelayout h2 { - font-size: 12px; - margin-top: 3px; -} -#maincontent .mainbox.threelayout a.right { - float: right; -} -#maincontent .mainbox.threelayout ul { - list-style-type: none; - margin-left: 10px; -} -p.screenshots { - text-align: center; - margin-top: 10px; - padding: 0; - height: 105px; -} -p.screenshots img { - margin: 2px; -} -#maincontent .mainbox.threelayout h1 a { - color: $boxtitle$; -} -a.feed { float: right } - +p.screenshots { text-align: center; margin-top: 10px; padding: 0; height: 105px; } +p.screenshots img { margin: 2px; } +a.feed { float: right } /***** Browsing ******/ -p.browseopts { - text-align: center; - padding: 2px; -} p.browseopts a { padding: 1px 3px; - color: $maintext$!important; + color: $maintext$; border: 1px solid $border$; margin: 0 2px; white-space: nowrap; } +p.browseopts { text-align: center; padding: 2px; } p.browseopts a.optselected, -p.browseopts a:hover { - border: 0; - padding: 2px 4px; -} -#maincontent div.mainbox.browse { - padding: 0; -} -div.mainbox.browse table { - width: 100%; -} -div.mainbox.browse table td.tc1 { - padding-left: 25px; -} -table thead td { - font-weight: bold; - background-color: $secbg$; -} -fieldset.search { - display: block; - width: 100%; - text-align: center; - margin: 0 0 10px 0; -} -fieldset.search .submit { - padding: 0 1px; -} -p#searchtabs { - height: 12px; - padding-right: 70px; -} -p#searchtabs a { - padding: 2px 6px 2px 6px; - margin: 0 2px; - color: $maintext$!important; -} +p.browseopts a:hover { border: 0; padding: 2px 4px; } +div.mainbox.browse { padding: 0; } +div.mainbox.browse table { width: 100%; } +div.mainbox.browse table td.tc1 { padding-left: 25px; } +table thead td { font-weight: bold; background-color: $secbg$; } +fieldset.search { display: block; width: 100%; text-align: center; margin: 0 0 10px 0; } +fieldset.search .submit { padding: 0 1px; } +p#searchtabs { height: 12px; padding-right: 70px; } +p#searchtabs a { padding: 2px 6px 2px 6px; margin: 0 2px; color: $maintext$; } p#searchtabs a:hover, p#searchtabs a.sel { border: 1px solid $secborder$; border-bottom: none; @@ -463,7 +274,10 @@ p#searchtabs a:hover, p#searchtabs a.sel { } #q { width: 450px } + + /* history browser */ + div.history table { table-layout: fixed } div.history td { white-space: nowrap } div.history td.tc1_1 { width: 60px; padding-left: 0; padding-right: 0; text-align: right } @@ -476,66 +290,28 @@ div.history td.tc4 b { margin-left: 10px } - - /***** Discussions ******/ /* threads page */ -#maincontent div.mainbox.thread { - padding: 0; -} -div.thread table { - width: 100%; -} -div.thread td { - border-bottom: 1px solid $border$; -} -div.thread td.tc1 { - width: 150px; - padding: 5px 10px; - border-right: 1px solid $border$; -} -div.thread td.tc2 { - padding: 10px 20px 10px 10px; -} -div.thread tr.deleted td { - padding: 1px 10px; -} -div.thread i.deleted { - font-style: normal; - color: $grayedout$; -} -div.thread i.lastmod { - float: right; - font-size: 10px; - color: $grayedout$; - margin: 0 -10px -5px 0; -} -div.thread i.edit { - float: right; - color: $grayedout$; - font-style: normal; - margin: -10px -10px 0 0; -} +#maincontent div.thread { padding: 0; } +div.thread table { width: 100%; } +div.thread td { border-bottom: 1px solid $border$; } +div.thread td.tc1 { width: 150px; padding: 5px 10px; border-right: 1px solid $border$; } +div.thread td.tc2 { padding: 10px 20px 10px 10px; } +div.thread tr.deleted td { padding: 1px 10px; } +div.thread i.deleted { font-style: normal; color: $grayedout$; } +div.thread i.lastmod { float: right; font-size: 10px; color: $grayedout$; margin: 0 -10px -5px 0; } +div.thread i.edit { float: right; color: $grayedout$; font-style: normal; margin: -10px -10px 0 0; } /* threads browser */ -div.mainbox.discussions td.tc4 { - text-align: right; -} -div.mainbox.discussions a.locked { - text-decoration: line-through; -} -div.mainbox.discussions b.boards { - padding-left: 10px; - font-weight: normal; -} -div.mainbox.discussions b.boards a { - color: $grayedout$!important; -} -div.discussions td.tc2 { width: 50px; } -div.discussions td.tc3 { width: 90px; } -div.discussions td.tc4 { width: 170px; } -#maincontent h1.boxtitle, #maincontent h1.boxtitle a { +div.mainbox.discussions td.tc4 { text-align: right; } +div.mainbox.discussions a.locked { text-decoration: line-through; } +div.mainbox.discussions b.boards { padding-left: 10px; font-weight: normal; } +div.mainbox.discussions b.boards a { color: $grayedout$; } +div.discussions td.tc2 { width: 50px; } +div.discussions td.tc3 { width: 90px; } +div.discussions td.tc4 { width: 170px; } +h1.boxtitle, h1.boxtitle a { font-family: "Futura", "Century New Gothic", "Arial", Serif; font-weight: bold; font-style: italic; @@ -547,119 +323,49 @@ div.discussions td.tc4 { width: 170px; } - /***** VN page *******/ -div.vndetails { - margin: 0 auto; - width: 800px; -} -div.vnimg { - float: left; - width: 250px; - margin: 0 10px; -} -div.vnimg i { - display: block; - width: 100%; - text-align: center; - font-size: 10px; -} -div.vnimg p { - text-align: center; - padding: 0px; - margin: 0; -} -.vndesc h2 { - margin: 5px 0 0 0!important; -} -.vndesc p { - padding: 0 0 0 5px; -} -p#nsfw_hid { - display: none; - cursor: pointer; -} -div.vndetails table { - float: left; - width: 530px; -} -div.vndetails table td.key { - width: 80px; -} -div.vndetails table dt { - float: left; - font-style: italic; -} -div.vndetails table dd { - margin-left: 90px; -} - -div.vndetails td.relations dt { - float: none; - font-style: normal; -} -div.vndetails td.relations dd { - margin-left: 15px; -} -div.vndetails td.anime b { - font-size: 8px; - font-weight: normal; - padding-right: 4px; -} -div#vntags { - margin: 15px 30px 0 30px; - border-top: 1px solid $border$; - padding: 1px 5% 0 5%; - text-align: center; -} -#vntags span { white-space: nowrap; margin-left: 15px; } -#vntags b { color: $grayedout$; font-weight: normal; font-size: 8px } -#tagops { - float: right; - text-align: right; - width: auto; - margin: 0 30px; -} -#tagops a { margin: 0 0 0 10px; border: 0; outline: none } -#tagops a.sec { border-left: 1px solid $border$; padding-left: 10px } -#maincontent #tagops a.tsel { color: $maintext$; } - -.releases table, #screenshots table { - width: 100%; -} -.releases tr.lang td, #screenshots tr.rel td { - background: url($_boxbg$) repeat; - font-weight: bold; -} -.releases td.tc1 { - padding-left: 30px; - width: 80px; -} -.releases td.tc2 { - text-align: center; - width: 50px; -} -.releases td.tc3 { - text-align: right; - padding: 0; - width: 90px; -} -.releases td.tc5 { - width: 70px; -} -.releases td.tc5 a { - color: $maintext$!important; - border: 0; -} -.releases td.tc6 { - text-align: right; - width: 25px; - padding: 0; -} -a.addnew { - float: right; -} +div.vndetails { margin: 0 auto; width: 800px; } +div.vnimg { float: left; width: 250px; margin: 0 10px; } +div.vnimg i { display: block; width: 100%; text-align: center; font-size: 10px; } +div.vnimg p { text-align: center; padding: 0px; margin: 0; } +.vndesc h2 { margin: 5px 0 0 0; } +.vndesc p { padding: 0 0 0 5px; } +p#nsfw_hid { display: none; cursor: pointer; } +div.vndetails table { float: left; width: 530px; } +div.vndetails table td.key { width: 80px; } +div.vndetails table dt { float: left; font-style: italic; } +div.vndetails table dd { margin-left: 90px; } +div.vndetails td.relations dt { float: none; font-style: normal; } +div.vndetails td.relations dd { margin-left: 15px; } +div.vndetails td.anime b { font-size: 8px; font-weight: normal; padding-right: 4px; } + +td#buynow h1 { display: block; width: 100px; background: #bf9228; font-size: 13px; text-align: center; margin: -3px 0 0 0 } +td#buynow h1 a { display: block; width: 100%; color: #fff; border: none; padding: 2px; font-weight: bold } +td#buynow ul { background: #ffe095; width: 100%; margin: 0; border: 1px solid #bf9228 } +td#buynow li { list-style-type: none; margin: 0 } +td#buynow li a { display: block; width: 100%; color: #000; border: none } +td#buynow li a:hover { background: #ffebba } +td#buynow .pricenote { border: 0 } + +div#vntags { margin: 15px 30px 0 30px; border-top: 1px solid $border$; padding: 1px 5% 0 5%; text-align: center; } +#vntags span { white-space: nowrap; margin-left: 15px; } +#vntags b { color: $grayedout$; font-weight: normal; font-size: 8px } +#tagops { float: right; text-align: right; width: auto; margin: 0 30px; } +#tagops a { margin: 0 0 0 10px; border: 0; outline: none } +#tagops a.sec { border-left: 1px solid $border$; padding-left: 10px } +#tagops a.tsel { color: $maintext$; } + +.releases table, +#screenshots table { width: 100%; } +.releases tr.lang td, +#screenshots tr.rel td { background: url($_boxbg$) repeat; font-weight: bold; } +.releases td.tc1 { padding-left: 30px; width: 80px; } +.releases td.tc2 { text-align: center; width: 50px; } +.releases td.tc3 { text-align: right; padding: 0; width: 90px; } +.releases td.tc5 { width: 70px; } +.releases td.tc5 a { color: $maintext$; border: 0; } +.releases td.tc6 { text-align: right; width: 25px; padding: 0; } #screenshots p.rel { background: url($_boxbg$) repeat; @@ -677,28 +383,19 @@ a.addnew { #screenshots p.nsfwtoggle { float: right; margin: 0; } -#dd_box { position: absolute; left: 0px; border: 1px solid $border$; background-color: $secbg$; z-index: 2 } -#dd_box ul { list-style-type: none; margin: 0; padding: 0 } -#dd_box li b { display: block; font-weight: normal; padding-left: 5px; } -#dd_box li i { display: block; font-style: normal; padding-left: 10px; padding-right: 5px } -#dd_box li a { display: block; padding-left: 10px; color: $link$; border: 0; padding-right: 5px } -#dd_box li a:hover { background: url($_boxbg$) repeat } - - - /***** Vote stats ****/ -.votestats { width: 610px; margin: 0 auto; } -.votegraph { float: left; margin-right: 20px } +.votestats { width: 610px; margin: 0 auto; } +.votegraph { float: left; margin-right: 20px } .votegraph td { padding: 0 2px; } .votegraph td.number { text-align: right } .votegraph td div { float: left; height: 14px; background-color: $border$; margin-right: 2px; padding: 0; } -.votestats thead td { background: transparent; text-align: center; padding: 2px; } +.votestats thead td { background: transparent; text-align: center; padding: 2px; } .votestats tfoot td { text-align: right } -.votestats div { text-align: center; padding-top: 5px; } +.votestats div { text-align: center; padding-top: 5px; } -.recentvotes { width: 300px } +.recentvotes { width: 300px } .recentvotes thead tr td b { font-weight: normal; padding-left: 5px } @@ -715,52 +412,25 @@ a.addnew { #jt_box_vn_rel td.tc_vn input { width: 280px; } #jt_box_vn_rel td.tc_rel select { width: 130px; } -#ds_box { - position: absolute; - top: 0; - border: 1px solid $border$; - border-top: none; - background-color: $secbg$; - cursor: pointer; - z-index: 2 -} -#ds_box b { - padding: 2px 0 0 10px; -} -#ds_box tr.selected { - background: url($_boxbg$) repeat; -} -#ds_box table { - width: 100%; -} - -#jt_box_vn_img div.img { - float: left; - height: 400px; - padding-right: 20px; -} -#jt_box_vn_img h2 { - margin: 0; -} +#jt_box_vn_img div.img { float: left; height: 400px; padding-right: 20px; } +#jt_box_vn_img h2 { margin: 0; } #jt_box_vn_scr table { width: 95% } -#scr_table td { height: 108px; border-top: 1px solid #258; padding: 0; padding-right: 5px } -#scr_table td.thumb { width: 136px; vertical-align: middle } -#scr_table select { width: 400px; } -div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px; position: absolute; display: none; left: 0; top: 0; } - - +#scr_table td { height: 108px; border-top: 1px solid #258; padding: 0; padding-right: 5px } +#scr_table td.thumb { width: 136px; vertical-align: middle } +#scr_table select { width: 400px; } +div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px; position: absolute; display: none; left: 0; top: 0; } /****** VN browse ********/ .vnbrowse thead .tc_s { padding-left: 30px } -.vnbrowse .tc_s { width: 65px } -.vnbrowse .tc2 { text-align: right; padding: 0; } -.vnbrowse .tc3 { padding: 0; } -.vnbrowse .tc5 { text-align: right; padding-right: 10px } -.vnbrowse .tc6 { width: 80px } +.vnbrowse .tc_s { width: 65px } +.vnbrowse .tc2 { text-align: right; padding: 0; } +.vnbrowse .tc3 { padding: 0; } +.vnbrowse .tc5 { text-align: right; padding-right: 10px } +.vnbrowse .tc6 { width: 80px } #filselect { text-align: center; display: block; @@ -788,22 +458,11 @@ div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px #prodrel td.tc6 { width: 25px; text-align: right; padding: 0; } #expandprodrel { float: right; font-weight: bold; padding-bottom: 2px; border: none } -.producerbrowse ul { - float: left; - margin-top: -5px; - margin-left: 3%; - width: 28%; -} -.producerbrowse ul li { - list-style-type: none; -} -.producerbrowse ul li acronym { - margin-right: 5px; - margin-top: 1px; -} -.producerbrowse { - padding-bottom: 10px!important -} +div.producerbrowse { padding-bottom: 10px } +.producerbrowse ul { float: left; margin-top: -5px; margin-left: 3%; width: 28%; } +.producerbrowse ul li { list-style-type: none; } +.producerbrowse ul li acronym { margin-right: 5px; margin-top: 1px; } + /***** Producer edit *****/ @@ -820,30 +479,27 @@ div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px /***** Release page *****/ -.release table { - width: 400px; - margin: 0 auto; -} -.release .key { - width: 70px; -} +.release table { width: 400px; margin: 0 auto; } +.release .key { width: 70px; } + + +/* Release edit */ -/* edit */ -.platforms { padding-left: 20px; } +.platforms { padding-left: 20px; } .platforms span { display: block; float: left; width: 150px; } -#jt_box_rel_format h2 { clear: left; padding-top: 10px; } -#media_div select.qty { width: 90px; } +#jt_box_rel_format h2 { clear: left; padding-top: 10px; } +#media_div select.qty { width: 90px; } #media_div select.medium { width: 150px } -#media_div { padding-left: 20px; } -#media_div span { display: block } +#media_div { padding-left: 20px; } +#media_div span { display: block } #jt_box_rel_vn h2, #jt_box_rel_prod h2 { clear: left; padding-top: 10px; } #jt_box_rel_vn div, #jt_box_rel_vn table, #jt_box_rel_prod div, #jt_box_rel_prod table { margin-left: 20px } #jt_box_rel_vn input, #jt_box_rel_prod input { margin-right: 10px; width: 295px } #jt_box_rel_vn .tc_title, #jt_box_rel_prod .tc_name { width: 310px; padding: 2px } -#jt_box_rel_prod .tc_role select { width: 100px; margin-right: 10px; } +#jt_box_rel_prod .tc_role select { width: 100px; margin-right: 10px; } @@ -860,38 +516,40 @@ div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px div.chardetails { margin: 0 auto; width: 800px; } div.charimg { float: left; width: 250px; margin: 0 10px; text-align: center } div.charimg p { text-align: center; padding: 0px; margin: 0; } -.chardesc h2 { margin: 0!important; } +.chardesc h2 { margin: 0; } .chardesc p { padding: 0 0 0 5px; } -div.chardetails table { float: left; width: 530px; } +div.chardetails table { float: left; width: 530px; } div.chardetails table td.key { width: 80px; } -div.chardetails.charsep { padding-top: 5px; margin-top: 5px; border-top: 1px solid $border$ } -#charspoil_sel { clear: right; float: right; } -#charspoil_sel a { margin: 0 0 0 10px; border: 0; outline: none } -#maincontent #charspoil_sel a.sel { color: $maintext$; } +div.chardetails.charsep { padding-top: 5px; margin-top: 5px; border-top: 1px solid $border$ } +#charspoil_sel { clear: right; float: right; } +#charspoil_sel a { margin: 0 0 0 10px; border: 0; outline: none } +#charspoil_sel a.sel { color: $maintext$; } /***** Char edit *****/ + #jt_box_chare_img div.img { float: left; height: 300px; padding-right: 20px; } -#jt_box_chare_img h2 { margin: 0; } +#jt_box_chare_img h2 { margin: 0; } #jt_box_chare_traits table { margin-bottom: 10px; margin-left: 10px; } #jt_box_chare_traits h2 { margin: 0 0 3px 0px; } -#jt_box_chare_traits td.tc_name { width: 200px } +#jt_box_chare_traits td.tc_name { width: 200px } #jt_box_chare_traits td.tc_name input { width: 280px; } -#jt_box_chare_traits td.tc_spoil { width: 80px; } +#jt_box_chare_traits td.tc_spoil { width: 80px; } #jt_box_chare_vns table { margin-bottom: 10px; margin-left: 10px; } #jt_box_chare_vns h2 { margin: 0 0 3px 0px; } -#jt_box_chare_vns td.tc_vn { font-weight: bold; padding: 5px 0 3px 0 } -#jt_box_chare_vns td.tc_vn i{ font-weight: normal; padding-left: 5px; font-style: normal } -#jt_box_chare_vns td.tc_rel { width: 340px; padding-left: 15px } -#jt_box_chare_vns td.tc_rel select { width: 340px; } +#jt_box_chare_vns td.tc_vn { font-weight: bold; padding: 5px 0 3px 0 } +#jt_box_chare_vns td.tc_vn i { font-weight: normal; padding-left: 5px; font-style: normal } +#jt_box_chare_vns td.tc_rel { width: 340px; padding-left: 15px } +#jt_box_chare_vns td.tc_rel select { width: 340px; } #jt_box_chare_vns td.tc_rol, -#jt_box_chare_vns td.tc_rol select { width: 150px } +#jt_box_chare_vns td.tc_rol select { width: 150px } #jt_box_chare_vns td.tc_spl, -#jt_box_chare_vns td.tc_spl select { width: 100px } -#jt_box_chare_vns td.tc_del { padding-left: 5px } +#jt_box_chare_vns td.tc_spl select { width: 100px } +#jt_box_chare_vns td.tc_del { padding-left: 5px } #jt_box_chare_vns td.tc_vnadd input { width: 280px } + /***** Char browse *****/ div.charb table { table-layout: fixed } @@ -902,19 +560,19 @@ div.charb td.tc2 b { margin-left: 10px } div.charb td.tc2 b a { color: $grayedout$!important } + /***** Documentation pages *****/ -.docs { padding: 0 15% 20px 15%; } -.docs h3 { margin-top: 30px; font-size: 14px } -.docs h4 { margin-top: 15px; font-size: 12px } -.docs dd { padding-bottom: 5px; margin-left: 120px; } -.docs dt { float: left } -.docs ul.index { display: block; float: right; width: 150px; padding: 2px; margin: 0 0 10px 5px; background: url($_boxbg$) repeat; border: 1px solid $border$; } -.docs ul.index li { list-style-type: none; } +.docs { padding: 0 15% 20px 15%; } +.docs h3 { margin-top: 30px; font-size: 14px } +.docs h4 { margin-top: 15px; font-size: 12px } +.docs dd { padding-bottom: 5px; margin-left: 120px; } +.docs dt { float: left } +.docs ul.index { display: block; float: right; width: 150px; padding: 2px; margin: 0 0 10px 5px; background: url($_boxbg$) repeat; border: 1px solid $border$; } +.docs ul.index li { list-style-type: none; } .docs ul.index li a { margin: 0 0 0 10px; } -.docs .retired { text-decoration: line-through; } -.docs dt b { color: $grayedout$; font-weight: normal; font-style: normal; font-size: 12px; } - +.docs .retired { text-decoration: line-through; } +.docs dt b { color: $grayedout$; font-weight: normal; font-style: normal; font-size: 12px; } @@ -926,7 +584,7 @@ div.votelist td.tc2 { width: 50px; text-align: right; padding-right: 10px } /***** Wishlist browser ******/ -.wishlist .tc1 { padding-top: 0; padding-bottom: 0; } +.wishlist .tc1 { padding-top: 0; padding-bottom: 0; } .wishlist tfoot td { padding: 0 0 0 25px } @@ -940,33 +598,28 @@ div.votelist td.tc2 { width: 50px; text-align: right; padding-right: 10px } .browse.rlist .tc3_5 b { margin-left: 10px } .browse.rlist .tc4 { width: 60px; text-align: right; padding-top: 0; padding-bottom: 0 } .browse.rlist .tc6 { width: 100px } -.browse.rlist .relhid .tc6 { padding-left: 15px; width: auto } .browse.rlist .tc7 { width: 90px } .browse.rlist .tc8 { width: 70px } .browse.rlist tfoot select { width: 200px } +.browse.rlist .relhid .tc6 { padding-left: 15px; width: auto } /***** User notifications *****/ -.browse.notifies td.tc1 { width: 14px } -.browse.notifies td.tc3 { width: 90px } -.browse.notifies td.tc4 { width: 60px } +.browse.notifies td.tc1 { width: 14px } +.browse.notifies td.tc3 { width: 90px } +.browse.notifies td.tc4 { width: 60px } .browse.notifies tbody td.tc5 { color: $grayedout$; cursor: pointer } -.browse.notifies td.tc5 i { font-style: normal; color: $maintext$ } -.browse.notifies .unread td { font-weight: bold } -.browse.notifies tfoot td { padding: 0 0 0 25px } +.browse.notifies td.tc5 i { font-style: normal; color: $maintext$ } +.browse.notifies .unread td { font-weight: bold } +.browse.notifies tfoot td { padding: 0 0 0 25px } /***** Userpage *****/ -.userpage table { - width: 400px; - margin: 0 auto; -} -.userpage .key { - width: 70px; -} +.userpage table { width: 400px; margin: 0 auto; } +.userpage .key { width: 70px; } /***** User posts browser ****/ @@ -983,20 +636,22 @@ div.uposts td.tc4 b { margin-left: 10px } /***** Tag page *****/ -.tagtree { margin-left: 20px; margin-top: -20px; list-style-type: none; } -.tagtree li { float: left; width: 200px; margin-top: 10px; } -.tagtree li li { float: none; width: auto; margin-top: 0; } -.tagtree ul { margin-left: 10px; list-style-type: none; } +.tagtree { margin-left: 20px; margin-top: -20px; list-style-type: none; } +.tagtree li { float: left; width: 200px; margin-top: 10px; } +.tagtree li li { float: none; width: auto; margin-top: 0; } +.tagtree ul { margin-left: 10px; list-style-type: none; } .tagvnlist .tc1 { width: 105px; } .tagvnlist .tc1 i { font-style: normal; font-size: 8px } .tagvnlist .tc3 { text-align: right; padding: 0; } .tagvnlist .tc4 { padding: 0; } .tagvnlist .tc6 { text-align: right; padding-right: 10px; } + /***** Tag/trait list (/g/list, /i/list) *****/ .browse.taglist .tc1 { width: 80px } + /***** Tag links *****/ .browse.taglinks .tc1 { width: 70px } @@ -1006,110 +661,56 @@ div.uposts td.tc4 b { margin-left: 10px } .browse.taglinks .ignored .taglvl.taglvl0 { color: $grayedout$!important } .browse.taglinks .setfil { font-size: 8px; padding-right: 3px } + /***** VN tagmod *****/ #jt_box_tagmod .formtable table td { padding: 1px 5px } -table.tgl tfoot td { padding-top: 20px!important; } -table.tgl .tc_you { border-right: 1px solid $border$; border-left: 1px solid $border$; width: 150px; text-align: center } -table.tgl .tc_others { border-left: 1px solid $border$; width: 150px; text-align: center } -table.tgl .tc_tagname { min-width: 200px; border-right: 1px solid $border$ } +table.tgl tfoot td { padding-top: 20px!important; } +table.tgl .tc_you { border-right: 1px solid $border$; border-left: 1px solid $border$; width: 150px; text-align: center } +table.tgl .tc_others { border-left: 1px solid $border$; width: 150px; text-align: center } +table.tgl .tc_tagname { min-width: 200px; border-right: 1px solid $border$ } table.tgl tbody .tc_tagname { padding-left: 15px!important } -table.tgl .tc_myvote { padding-left: 30px!important } -table.tgl .tc_myover { padding: 0!important } -table.tgl .tc_myspoil { border-right: 1px solid $border$; padding-right: 30px!important; text-align: right; padding-left: 10px!important; cursor: pointer } -table.tgl .tc_allvote { padding-left: 30px!important; } -table.tgl .tc_allvote i { font-style: normal; font-size: 8px } -table.tgl .tc_allspoil { text-align: right; padding-right: 15px!important; } -table.tgl .tagmod_cat td { font-weight: bold } -.taglvl { display: block; float: left; width: 8px; height: 12px; border: 1px solid $border$; font-size: 1px; color: $maintext$!important } -.taglvl0 { width: 15px; border: none!important; font-size: 10px; text-align: center; } -div.taglvl0 { font-size: 8px; width: 20px!important } -div.taglvl { border: none!important; width: 10px; height: 14px } -a.taglvl:hover { border-bottom: 1px solid transparent!important } +table.tgl .tc_myvote { padding-left: 30px!important } +table.tgl .tc_myover { padding: 0!important } +table.tgl .tc_myspoil { border-right: 1px solid $border$; padding-right: 30px!important; text-align: right; padding-left: 10px!important; cursor: pointer } +table.tgl .tc_allvote { padding-left: 30px!important; } +table.tgl .tc_allvote i { font-style: normal; font-size: 8px } +table.tgl .tc_allspoil { text-align: right; padding-right: 15px!important; } +table.tgl .tagmod_cat td { font-weight: bold } +.taglvl { display: block; float: left; width: 8px; height: 12px; border: 1px solid $border$; font-size: 1px; color: $maintext$!important } +.taglvl0 { width: 15px; border: none; font-size: 10px; text-align: center; } +div.taglvl0 { font-size: 8px; width: 20px!important } +div.taglvl { border: none; width: 10px; height: 14px } +a.taglvl:hover { border-bottom: 1px solid transparent } .taglvlsel.taglvl-3 { background-color: #f00; border-color: #f00 } .taglvlsel.taglvl-2 { background-color: #f40; border-color: #f40 } .taglvlsel.taglvl-1 { background-color: #f80; border-color: #f80 } -.taglvlsel.taglvl1 { background-color: #cf0; border-color: #cf0 } -.taglvlsel.taglvl2 { background-color: #8f0; border-color: #8f0 } -.taglvlsel.taglvl3 { background-color: #0f0; border-color: #0f0 } - - - - -/***** Warning/Notice Box *****/ - -div.warning, div.notice { - margin: 5px 10%; - padding: 15px; - background-color: $warnbg$; - border: 1px solid $warnborder$; -} -div.notice { - background-color: $noticebg$; - border: 1px solid $noticeborder$; -} -div.warning ul, div.notice ul { - margin-left: 0; -} -div.warning li, div.notice li { - margin-left: 20px; -} -#maincontent div.warning h2, #maincontent div.notice h2 { - font-size: 11px; - font-weight: bold; - margin: 0; -} +.taglvlsel.taglvl1 { background-color: #cf0; border-color: #cf0 } +.taglvlsel.taglvl2 { background-color: #8f0; border-color: #8f0 } +.taglvlsel.taglvl3 { background-color: #0f0; border-color: #0f0 } /****** Revision information ******/ -div.revision { - padding-bottom: 10px!important; -} -div.revision div, div.revision table { +div.revision div.rev, div.revision table { border: 1px solid $border$; margin: 0 auto; width: 90%; background-color: $secbg$; clear: both; } -div.revision table thead tr td { - background-color: transparent!important; - text-align: center; - font-weight: normal; -} -div.revision table td { - border-right: 1px solid $border$; - padding: 5px; -} -div.revision td.tcval { - width: 44%; -} -div.revision div { - padding: 5px; - text-align: center; -} -.diff_add { - font-weight: normal; - background-color: $diffadd$; -} -.diff_del { - font-weight: normal; - background-color: $diffdel$; -} -div.revision .next { - float: right; - margin-right: 5%; -} -div.revision .prev { - float: left; - margin-left: 5%; -} -div.revision .item { - text-align: center; -} +div.revision { padding-bottom: 10px; } +div.revision table thead tr td { background-color: transparent!important; text-align: center; font-weight: normal; } +div.revision table td { border-right: 1px solid $border$; padding: 5px; } +div.revision td.tcval { width: 44%; } +div.revision div.rev { padding: 5px; text-align: center; } +.diff_add { font-weight: normal; background-color: $diffadd$; } +.diff_del { font-weight: normal; background-color: $diffdel$; } +div.revision .next { float: right; margin-right: 5%; } +div.revision .prev { float: left; margin-left: 5%; } +div.revision .item { text-align: center; } @@ -1124,12 +725,12 @@ div#iv_view { padding: 5px; text-align: center; } -#iv_view a { border: 0; font-weight: bold; font-size: 12px } +#iv_view a { border: 0; font-weight: bold; font-size: 12px } #iv_view img { cursor: pointer } -#ivclose { float: right; padding-left: 10px } -#ivnext { padding-left: 5px; } -#ivprev { padding-right: 5px; } -#ivfull { float: left; padding-right: 10px; } +#ivclose { float: right; padding-left: 10px } +#ivnext { padding-left: 5px; } +#ivprev { padding-right: 5px; } +#ivfull { float: left; padding-right: 10px; } #ivimgload { position: absolute; display: block; @@ -1145,7 +746,6 @@ div#iv_view { - /****** filter selector *****/ div#fil_div { @@ -1158,23 +758,22 @@ div#fil_div { width: 600px; text-align: center; } -#fil_div a.close { float: right; color: $link$; border: 0; font-weight: bold } -#fil_div p.browseopts { padding: 2px 50px; line-height: 23px } -#fil_div .browseopts a { outline: none } +#fil_div a.close { float: right; border: 0; font-weight: bold } +#fil_div p.browseopts { padding: 2px 50px; line-height: 23px } +#fil_div .browseopts a { outline: none; color: $maintext$ } #fil_div .browseopts a.active { font-weight: bold } -#fil_div b.ruler { display: block; margin: auto; width: 93%; height: 1px; border-bottom: 1px solid $border$; margin-bottom: 5px } -#fil_div h3 { width: 100%; text-align: center; font-size: 11px } -#fil_div table { width: 93%; text-align: left; margin: 0 auto 5px auto } +#fil_div b.ruler { display: block; margin: auto; width: 93%; height: 1px; border-bottom: 1px solid $border$; margin-bottom: 5px } +#fil_div h3 { width: 100%; text-align: center; font-size: 11px } +#fil_div table { width: 93%; text-align: left; margin: 0 auto 5px auto } #fil_div table td.label label { width: 120px } #fil_div table td.label b { display: block; font-weight: normal; padding: 10px 5px 0 0 } #fil_div table td.check { width: 15px } #fil_div label.active { font-weight: bold } -#fil_div .opts a { border: 0; outline: none; color: $link$ } +#fil_div .opts a { border: 0; outline: none } #fil_div .opts b { margin: 0 7px; font-weight: normal } #fil_div .opts a.tsel { color: $maintext$; } -#filselect i { font-style: normal } -#fil_div table ul { margin: 0 0 0 15px } -#fil_div table ul a { color: $link$ } +#filselect i { font-style: normal } +#fil_div table ul { margin: 0 0 0 15px } diff --git a/lib/Multi/Core.pm b/lib/Multi/Core.pm index 9eaa6269..3c9d8bae 100644 --- a/lib/Multi/Core.pm +++ b/lib/Multi/Core.pm @@ -98,7 +98,7 @@ sub log_msg { # msg sub log { # level, msg (my $p = eval { $_[SENDER][2]{$_[CALLER_STATE]}[0] } || '') =~ s/^Multi:://; log_msg sprintf '%s::%s: %s', $p, $_[CALLER_STATE], - $_[ARG1] ? sprintf($_[ARG0], @_[ARG1..$#_]) : $_[ARG0]; + $#_>ARG0 ? sprintf($_[ARG0], @_[ARG1..$#_]) : $_[ARG0]; } diff --git a/lib/Multi/Maintenance.pm b/lib/Multi/Maintenance.pm index 5b092d0a..33154f9b 100644 --- a/lib/Multi/Maintenance.pm +++ b/lib/Multi/Maintenance.pm @@ -18,13 +18,13 @@ sub spawn { package_states => [ $p => [qw| _start shutdown set_daily daily set_monthly monthly log_stats - vncache_inc tagcache vnpopularity vnrating cleangraphs cleansessions cleannotifications + vncache_inc tagcache traitcache vnpopularity vnrating cleangraphs cleansessions cleannotifications vncache_full usercache statscache logrotate vnsearch_check vnsearch_gettitles vnsearch_update |], ], heap => { - daily => [qw|vncache_inc tagcache vnpopularity vnrating cleangraphs cleansessions cleannotifications|], + daily => [qw|vncache_inc tagcache traitcache vnpopularity vnrating cleangraphs cleansessions cleannotifications|], monthly => [qw|vncache_full usercache statscache logrotate|], vnsearch_checkdelay => 3600, @_, @@ -46,6 +46,7 @@ sub _start { sub shutdown { $_[KERNEL]->delay('daily'); $_[KERNEL]->delay('monthly'); + $_[KERNEL]->delay('vnsearch_check'); $_[KERNEL]->alias_remove('maintenance'); } @@ -121,13 +122,19 @@ sub vncache_inc { sub tagcache { - # takes about 2 seconds max, still OK + # takes about 5 seconds max, still OK $_[KERNEL]->post(pg => do => 'SELECT tag_vn_calc()', undef, 'log_stats', 'tagcache'); } +sub traitcache { + # still takes less than a second + $_[KERNEL]->post(pg => do => 'SELECT traits_chars_calc()', undef, 'log_stats', 'traitcache'); +} + + sub vnpopularity { - # still takes at most 3 seconds. let's hope that doesn't increase... + # takes a bit more than 8 seconds, meh... $_[KERNEL]->post(pg => do => 'SELECT update_vnpopularity()', undef, 'log_stats', 'vnpopularity'); } @@ -216,6 +223,9 @@ sub statscache { q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM vn WHERE hidden = FALSE) WHERE section = 'vn'|, q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM releases WHERE hidden = FALSE) WHERE section = 'releases'|, q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM producers WHERE hidden = FALSE) WHERE section = 'producers'|, + q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM chars WHERE hidden = FALSE) WHERE section = 'chars'|, + q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM tags WHERE state = 2) WHERE section = 'tags'|, + q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM traits WHERE state = 2) WHERE section = 'traits'|, q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM threads WHERE hidden = FALSE) WHERE section = 'threads'|, q|UPDATE stats_cache SET count = (SELECT COUNT(*) FROM threads_posts WHERE hidden = FALSE AND EXISTS(SELECT 1 FROM threads WHERE threads.id = tid AND threads.hidden = FALSE)) WHERE section = 'threads_posts'| diff --git a/lib/VNDB/DB/Affiliates.pm b/lib/VNDB/DB/Affiliates.pm new file mode 100644 index 00000000..51320b47 --- /dev/null +++ b/lib/VNDB/DB/Affiliates.pm @@ -0,0 +1,69 @@ + +package VNDB::DB::Affiliates; + +use strict; +use warnings; +use POSIX 'strftime'; +use Exporter 'import'; + +our @EXPORT = qw|dbAffiliateGet dbAffiliateEdit dbAffiliateDel dbAffiliateAdd|; + + +# options: id rids affiliate hidden sort reverse +# what: release +sub dbAffiliateGet { + my($self, %o) = @_; + $o{sort} ||= 'id'; + $o{reverse} //= 0; + + my %where = ( + $o{id} ? ('id = ?' => $o{id}) : (), + $o{rids} ? ('rid IN(!l)' => [$o{rids}]) : (), + defined($o{affiliate}) ? ('affiliate = ?' => $o{affiliate}) : (), + defined($o{hidden}) ? ('!s af.hidden' => $o{hidden} ? '' : 'NOT') : (), + ); + + my $join = $o{what} ? 'JOIN releases r ON r.id = af.rid JOIN releases_rev rr ON rr.id = r.latest' : ''; + my $select = $o{what} ? ', rr.title' : ''; + + my $order = sprintf { + id => 'af.id %s', + rel => 'rr.title %s', + prio => 'af.priority %s', + url => 'af.url %s', + lastfetch => 'af.lastfetch %s', + }->{$o{sort}}, $o{reverse} ? 'DESC' : 'ASC'; + + return $self->dbAll(qq| + SELECT af.id, af.rid, af.hidden, af.priority, af.affiliate, af.url, af.version, + extract('epoch' from af.lastfetch) as lastfetch, af.price$select + FROM affiliate_links af + $join + !W + ORDER BY !s|, \%where, $order); +} + + +sub dbAffiliateDel { + my($self, $id) = @_; + $self->dbExec('DELETE FROM affiliate_links WHERE id = ?', $id); +} + + +sub dbAffiliateEdit { + my($self, $id, %ops) = @_; + my %set; + exists($ops{$_}) && ($set{"$_ = ?"} = $ops{$_}) for(qw|rid priority hidden affiliate url version|); + return if !keys %set; + $self->dbExec('UPDATE affiliate_links !H WHERE id = ?', \%set, $id); +} + +sub dbAffiliateAdd { + my($self, %ops) = @_; + $self->dbExec('INSERT INTO affiliate_links (rid, priority, hidden, affiliate, url, version) VALUES(!l)', + [@ops{qw| rid priority hidden affiliate url version|}]); +} + + +1; + diff --git a/lib/VNDB/DB/Misc.pm b/lib/VNDB/DB/Misc.pm index 2f98abe2..3155ca56 100644 --- a/lib/VNDB/DB/Misc.pm +++ b/lib/VNDB/DB/Misc.pm @@ -64,6 +64,7 @@ sub dbRevisionGet { # what types should we join? my @types = ( !$o{type} ? ('v', 'r', 'p', 'c') : + ref($o{type}) ? @{$o{type}} : $o{type} ne 'v' ? $o{type} : $o{releases} ? ('v', 'r') : 'v' ); @@ -73,7 +74,7 @@ sub dbRevisionGet { q{((h.type = 'v' AND vr.vid = ?) OR (h.type = 'r' AND h.id = ANY(ARRAY(SELECT rv.rid FROM releases_vn rv WHERE rv.vid = ?))))} => [$o{iid}, $o{iid}], ) : ( $o{type} ? ( - 'h.type = ?' => $o{type} ) : (), + 'h.type IN(!l)' => [ ref($o{type})?$o{type}:[$o{type}] ] ) : (), $o{iid} ? ( '!sr.!sid = ?' => [ $o{type}, $o{type}, $o{iid} ] ) : (), ), diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm index bd2d5bd3..6f9465f8 100644 --- a/lib/VNDB/DB/Releases.pm +++ b/lib/VNDB/DB/Releases.pm @@ -12,7 +12,7 @@ our @EXPORT = qw|dbReleaseGet dbReleaseRevisionInsert|; # Options: id vid pid rev released 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 +# What: extended changes vn producers platforms media affiliates # Sort: title released minage sub dbReleaseGet { my($self, %o) = @_; diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm index 17f360cc..abea9bec 100644 --- a/lib/VNDB/DB/Users.pm +++ b/lib/VNDB/DB/Users.pm @@ -55,7 +55,7 @@ sub dbUserGet { qw|id username c_votes c_changes c_tags|, q|extract('epoch' from registered) as registered|, $o{what} =~ /extended/ ? ( - qw|mail rank salt ign_votes|, + qw|mail perm salt ign_votes|, q|encode(passwd, 'hex') AS passwd| ) : (), $o{what} =~ /hide_list/ ? 'up.value AS hide_list' : (), @@ -118,7 +118,7 @@ sub dbUserEdit { my %h; defined $o{$_} && ($h{$_.' = ?'} = $o{$_}) - for (qw| username mail rank salt ign_votes |); + for (qw| username mail perm salt ign_votes |); $h{'passwd = decode(?, \'hex\')'} = $o{passwd} if defined $o{passwd}; diff --git a/lib/VNDB/Handler/Affiliates.pm b/lib/VNDB/Handler/Affiliates.pm new file mode 100644 index 00000000..679d6ee2 --- /dev/null +++ b/lib/VNDB/Handler/Affiliates.pm @@ -0,0 +1,146 @@ + +package VNDB::Handler::Affiliates; + +use strict; +use warnings; +use TUWF ':html'; +use VNDB::Func; + + +TUWF::register( + qr{affiliates} => \&list, + qr{affiliates/del/([1-9]\d*)} => \&linkdel, + qr{affiliates/edit/([1-9]\d*)} => \&edit, + qr{affiliates/new} => \&edit, +); + + +sub list { + my $self = shift; + + return $self->htmlDenied if !$self->authCan('affiliate'); + my $f = $self->formValidate( + { get => 'a', required => 0, enum => [ 0..$#{$self->{affiliates}} ] }, + { get => 'h', required => 0, default => 0, enum => [ -1..1 ] }, + { get => 'o', required => 0, default => 'a', enum => ['a', 'd'] }, + { get => 's', required => 0, default => 'rel', enum => [qw|rel prio url lastfetch|] }, + ); + return $self->resNotFound if $f->{_err}; + + $self->htmlHeader(title => 'Affiliate administration interface'); + div class => 'mainbox'; + h1 'Affiliate administration interface'; + p class => 'browseopts'; + a defined($f->{a}) && $f->{a} == $_ ? (class => 'optselected') : (), href => "/affiliates?a=$_", $self->{affiliates}[$_]{name} + for (grep $self->{affiliates}[$_], 0..$#{$self->{affiliates}}); + end; + if(defined $f->{a}) { + p class => 'browseopts'; + a $f->{h} == -1 ? (class => 'optselected') : (), href => "/affiliates?a=$f->{a};h=-1",'all'; + a $f->{h} == 1 ? (class => 'optselected') : (), href => "/affiliates?a=$f->{a};h=1", 'hidden'; + a $f->{h} == 0 ? (class => 'optselected') : (), href => "/affiliates?a=$f->{a};h=0", 'non-hidden'; + end; + } + end; + + if(defined $f->{a}) { + my $list = $self->dbAffiliateGet( + affiliate => $f->{a}, hidden => $f->{h}==-1?undef:$f->{h}, + what => 'release', + sort => $f->{s}, reverse => $f->{o} eq 'd' + ); + $self->htmlBrowse( + items => $list, + nextpage => 0, + options => {p=>0, %$f}, + pageurl => '', + sorturl => "/affiliates?a=$f->{a};h=$f->{h}", + header => [ + ['Release', 'rel'], + ['Version'], + ['Hid'], + ['Prio', 'prio'], + ['Price / Lastfetch', 'lastfetch'], + ['', 'url' ] + ], + row => sub { + my($s, $n, $l) = @_; + Tr $n % 2 ? (class => 'odd') : (); + td class => 'tc1'; a href => "/r$l->{rid}", shorten $l->{title}, 50; end; + td class => 'tc2', $l->{version} || '<default>'; + td class => 'tc3', $l->{hidden} ? 'YES' : 'no'; + td class => 'tc4', $l->{priority}; + td class => 'tc5', sprintf '%s / %s', $l->{price}, $l->{lastfetch} ? $self->{l10n}->age($l->{lastfetch}) : '-'; + td class => 'tc6'; + a href => $l->{url}, 'link'; + txt ' | '; + a href => "/affiliates/edit/$l->{id}", 'edit'; + txt ' | '; + a href => "/affiliates/del/$l->{id}?formcode=".$self->authGetCode("/affiliates/del/$l->{id}"), 'del'; + end; + end; + }, + ); + } + $self->htmlFooter; +} + + +sub linkdel { + my($self, $id) = @_; + return $self->htmlDenied if !$self->authCan('affiliate'); + return if !$self->authCheckCode; + my $l = $self->dbAffiliateGet(id => $id)->[0]; + return $self->resNotFound if !$l; + $self->dbAffiliateDel($id); + $self->resRedirect("/affiliates?a=$l->{affiliate}"); +} + + +sub edit { + my($self, $id) = @_; + return $self->htmlDenied if !$self->authCan('affiliate'); + + my $r = $id && $self->dbAffiliateGet(id => $id)->[0]; + return $self->resNotFound if $id && !$r; + + my $frm; + if($self->reqMethod eq 'POST') { + return if !$self->authCheckCode; + $frm = $self->formValidate( + { post => 'rid', required => 1, template => 'int' }, + { post => 'priority', required => 0, default => 0, template => 'int' }, + { post => 'hidden', required => 0, default => 0, enum => [0,1] }, + { post => 'affiliate',required => 1, enum => [0..$#{$self->{affiliates}}] }, + { post => 'url', required => 1 }, + { post => 'version', required => 0, default => '' }, + ); + if(!$frm->{_err}) { + $self->dbAffiliateEdit($id, %$frm) if $id; + $self->dbAffiliateAdd(%$frm) if !$id; + return $self->resRedirect("/affiliates?a=$frm->{affiliate}", 'post'); + } + } + + if($id) { + $frm->{$_} = $r->{$_} for(qw|rid priority hidden affiliate url version|); + } else { + $frm->{rid} = $self->reqGet('rid'); + } + + $self->htmlHeader(title => 'Edit affiliate link'); + $self->htmlForm({ frm => $frm, action => $id ? "/affiliates/edit/$id" : '/affiliates/new' }, 'blah' => [ 'Edit affiliate link', + [ input => short => 'rid', name => 'Release ID', width => 100 ], + [ input => short => 'priority', name => 'Priority', width => 50 ], + [ check => short => 'hidden', name => 'Hidden' ], + [ select => short => 'affiliate', name => 'Affiliate', options => [ map + [ $_, $self->{affiliates}[$_]{name} ], grep $self->{affiliates}[$_], 0..$#{$self->{affiliates}} ] ], + [ input => short => 'url', name => 'URL', width => 400 ], + [ input => short => 'version', name => 'Version', width => 400 ], + ]); + $self->htmlFooter; +} + + +1; + diff --git a/lib/VNDB/Handler/Chars.pm b/lib/VNDB/Handler/Chars.pm index 7968b66c..8edb6236 100644 --- a/lib/VNDB/Handler/Chars.pm +++ b/lib/VNDB/Handler/Chars.pm @@ -233,7 +233,7 @@ sub charTable { td class => 'chardesc', colspan => 2; h2 mt '_charp_description'; p; - lit bb2html $r->{desc}; + lit bb2html $r->{desc}, 0, 1; end; end; end; @@ -257,7 +257,7 @@ sub edit { $rev = undef if !$r || $r->{cid} == $r->{latest}; return $self->htmlDenied if !$self->authCan('charedit') - || $id && ($r->{locked} && !$self->authCan('lock') || $r->{hidden} && !$self->authCan('del')); + || $id && (($r->{locked} || $r->{hidden}) && !$self->authCan('dbmod')); my %b4 = !$id ? () : ( (map +($_ => $r->{$_}), qw|name original alias desc image ihid ilock s_bust s_waist s_hip height weight bloodt gender main_spoil|), @@ -331,14 +331,14 @@ sub edit { $frm->{vns} = \@vns; my $nrev = $self->dbItemEdit(c => !$copy && $id ? $r->{cid} : undef, %$frm); - - # TEMPORARY SOLUTION! I'll investigate more efficient solutions and incremental updates whenever I have more data - $self->dbExec('SELECT traits_chars_calc()'); - return $self->resRedirect("/c$nrev->{iid}.$nrev->{rev}", 'post'); } } + if(!$id) { + my $vid = $self->formValidate({ get => 'vid', required => 1, template => 'int'}); + $frm->{vns} //= "$vid->{vid}-0-0-primary" if !$vid->{_err}; + } $frm->{$_} //= $b4{$_} for keys %b4; $frm->{editsum} //= sprintf 'Reverted to revision c%d.%d', $id, $rev if !$copy && $rev; $frm->{editsum} = sprintf 'New character based on c%d.%d', $id, $r->{rev} if $copy; diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm index 8f5b4949..643af1cf 100644 --- a/lib/VNDB/Handler/Misc.pm +++ b/lib/VNDB/Handler/Misc.pm @@ -196,7 +196,7 @@ sub history { { get => 'p', required => 0, default => 1, template => 'int' }, { get => 'm', required => 0, default => !$type, enum => [ 0, 1 ] }, { get => 'h', required => 0, default => 0, enum => [ -1..1 ] }, - { get => 't', required => 0, default => '', enum => [qw|v r p c|] }, + { get => 't', required => 0, default => '', enum => [qw|v r p c a|] }, { get => 'e', required => 0, default => 0, enum => [ -1..1 ] }, { get => 'r', required => 0, default => 0, enum => [ 0, 1 ] }, ); @@ -216,7 +216,7 @@ sub history { what => 'item user', $type && $type ne 'u' ? ( type => $type, iid => $id ) : (), $type eq 'u' ? ( uid => $id ) : (), - $f->{t} ? ( type => $f->{t} ) : (), + $f->{t} ? ( type => $f->{t} eq 'a' ? [qw|v r p|] : $f->{t} ) : (), page => $f->{p}, results => 50, auto => $f->{m}, @@ -250,7 +250,7 @@ sub history { end; } if(!$type || $type eq 'u') { - if($self->authCan('del')) { + if($self->authCan('dbmod')) { p class => 'browseopts'; a $f->{h} == 1 ? (class => 'optselected') : (), href => $u->(h => 1), mt '_hist_filter_hidedel'; a $f->{h} == -1 ? (class => 'optselected') : (), href => $u->(h => -1), mt '_hist_filter_showdel'; @@ -262,6 +262,7 @@ sub history { a $f->{t} eq 'r' ? (class => 'optselected') : (), href => $u->(t => 'r'), mt '_hist_filter_onlyreleases'; a $f->{t} eq 'p' ? (class => 'optselected') : (), href => $u->(t => 'p'), mt '_hist_filter_onlyproducers'; a $f->{t} eq 'c' ? (class => 'optselected') : (), href => $u->(t => 'c'), mt '_hist_filter_onlychars'; + a $f->{t} eq 'a' ? (class => 'optselected') : (), href => $u->(t => 'a'), mt '_hist_filter_nochars'; end; p class => 'browseopts'; a !$f->{e} ? (class => 'optselected') : (), href => $u->(e => 0), mt '_hist_filter_allactions'; diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm index 5030c1a3..52db6edf 100644 --- a/lib/VNDB/Handler/Producers.pm +++ b/lib/VNDB/Handler/Producers.pm @@ -205,7 +205,7 @@ sub edit { $rev = undef if !$p || $p->{cid} == $p->{latest}; return $self->htmlDenied if !$self->authCan('edit') - || $pid && ($p->{locked} && !$self->authCan('lock') || $p->{hidden} && !$self->authCan('del')); + || $pid && (($p->{locked} || $p->{hidden}) && !$self->authCan('dbmod')); my %b4 = !$pid ? () : ( (map { $_ => $p->{$_} } qw|type name original lang website desc alias ihid ilock|), diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm index 159b7c3f..dd2b278e 100644 --- a/lib/VNDB/Handler/Releases.pm +++ b/lib/VNDB/Handler/Releases.pm @@ -280,7 +280,7 @@ sub edit { return $self->resNotFound if $vid && !$v->{id}; return $self->htmlDenied if !$self->authCan('edit') - || $rid && ($r->{locked} && !$self->authCan('lock') || $r->{hidden} && !$self->authCan('del')); + || $rid && (($r->{locked} || $r->{hidden}) && !$self->authCan('dbmod')); my $vn = $rid ? $r->{vn} : [{ vid => $vid, title => $v->{title} }]; my %b4 = !$rid ? () : ( diff --git a/lib/VNDB/Handler/Traits.pm b/lib/VNDB/Handler/Traits.pm index 1f3c2872..ea599ed1 100644 --- a/lib/VNDB/Handler/Traits.pm +++ b/lib/VNDB/Handler/Traits.pm @@ -104,8 +104,7 @@ sub traitpage { if(!@$chars) { p; br; br; txt mt '_traitp_nochars'; end; } - # not really cached at the moment - # p; br; txt mt '_traitp_cached'; end; + p; br; txt mt '_traitp_cached'; end; end 'div'; @$chars && $self->charBrowseTable($chars, $np, $f, "/i$trait?m=$f->{m}"); @@ -171,9 +170,6 @@ sub traitedit { } else { $self->dbTraitEdit($trait, %opts, upddate => $frm->{state} == 2 && $t->{state} != 2) if $trait; _set_childs_group($self, $trait, $group||$trait) if ($group||0) != ($t->{group}||0); - - # TEMPORARY SOLUTION! I'll investigate more efficient solutions and incremental updates whenever I have more data - $self->dbExec('SELECT traits_chars_calc()'); } $self->resRedirect("/i$trait", 'post'); return; diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm index 363c2262..1c82d982 100644 --- a/lib/VNDB/Handler/ULists.pm +++ b/lib/VNDB/Handler/ULists.pm @@ -239,6 +239,7 @@ sub wishlist { { post => 'sel', required => 0, default => 0, multi => 1, template => 'int' }, { post => 'batchedit', required => 1, enum => [ -1, @{$self->{wishlist_status}} ] }, ); + $frm->{sel} = [ grep $_, @{$frm->{sel}} ]; # weed out "select all" checkbox if(!$frm->{_err} && @{$frm->{sel}} && $frm->{sel}[0]) { $self->dbWishListDel($uid, $frm->{sel}) if $frm->{batchedit} == -1; $self->dbWishListAdd($frm->{sel}, $uid, $frm->{batchedit}) if $frm->{batchedit} >= 0; @@ -303,6 +304,8 @@ sub wishlist { $own ? (footer => sub { Tr; td colspan => 3; + input type => 'checkbox', class => 'checkall', name => 'sel', value => 0; + txt ' '; Select name => 'batchedit', id => 'batchedit'; option mt '_wishlist_select'; optgroup label => mt '_wishlist_changeprio'; diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index 75ddb547..dc748f75 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -301,7 +301,7 @@ sub edit { $frm = $self->formValidate( $self->authCan('usermod') ? ( { post => 'usrname', template => 'pname', minlength => 2, maxlength => 15 }, - { post => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] }, + { post => 'perms', required => 0, multi => 1, enum => [ keys %{$self->{permissions}} ] }, { post => 'ign_votes', required => 0, default => 0 }, ) : (), { post => 'mail', template => 'mail' }, @@ -318,7 +318,10 @@ sub edit { $self->dbUserPrefSet($uid, $_ => $frm->{$_}) for (qw|skin customcss show_nsfw hide_list |); my %o; $o{username} = $frm->{usrname} if $frm->{usrname}; - $o{rank} = $frm->{rank} if $frm->{rank}; + if($self->authCan('usermod')) { + $o{perm} = 0; + $o{perm} += $self->{permissions}{$_} for(@{ delete $frm->{perms} }); + } $o{mail} = $frm->{mail}; ($o{passwd}, $o{salt}) = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass}; $o{ign_votes} = $frm->{ign_votes} ? 1 : 0 if $self->authCan('usermod'); @@ -330,8 +333,9 @@ sub edit { } # fill out default values - $frm->{usrname} ||= $u->{username}; - $frm->{$_} ||= $u->{$_} for(qw|rank mail|); + $frm->{usrname} ||= $u->{username}; + $frm->{mail} ||= $u->{mail}; + $frm->{perms} ||= [ grep $u->{perm} & $self->{permissions}{$_}, keys %{$self->{permissions}} ]; $frm->{$_} //= $u->{prefs}{$_} for(qw|skin customcss show_nsfw hide_list|); $frm->{ign_votes} = $u->{ign_votes} if !defined $frm->{ign_votes}; @@ -350,8 +354,8 @@ sub edit { [ part => title => mt '_usere_geninfo' ], $self->authCan('usermod') ? ( [ input => short => 'usrname', name => mt('_usere_username') ], - [ select => short => 'rank', name => mt('_usere_rank'), options => [ - map [ $_, mt '_urank_'.$_ ], 1..$#{$self->{user_ranks}} ] ], + [ select => short => 'perms', name => mt('_usere_perm'), multi => 1, size => (scalar keys %{$self->{permissions}}), options => [ + map [ $_, $_ ], sort keys %{$self->{permissions}} ] ], [ check => short => 'ign_votes', name => mt '_usere_ignvotes' ], ) : ( [ static => label => mt('_usere_username'), content => $frm->{usrname} ], @@ -611,7 +615,7 @@ sub notifies { class => 'notifies', pageurl => "/u$uid/notifies?r=$f->{r}", header => [ - [ '<input type="checkbox" class="checkall" name="notifysel" value="0" />' ], + [ '' ], [ mt '_usern_col_type' ], [ mt '_usern_col_age' ], [ mt '_usern_col_id' ], @@ -639,6 +643,8 @@ sub notifies { footer => sub { Tr; td colspan => 5; + input type => 'checkbox', class => 'checkall', name => 'notifysel', value => 0; + txt ' '; input type => 'submit', name => 'markread', value => mt '_usern_but_markread'; input type => 'submit', name => 'remove', value => mt '_usern_but_remove'; b class => 'grayedout', ' '.mt '_usern_autodel'; diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm index ce564fc0..d0848c09 100644 --- a/lib/VNDB/Handler/VNEdit.pm +++ b/lib/VNDB/Handler/VNEdit.pm @@ -23,12 +23,12 @@ sub edit { $rev = undef if !$vid || $v->{cid} == $v->{latest}; return $self->htmlDenied if !$self->authCan('edit') - || $vid && ($v->{locked} && !$self->authCan('lock') || $v->{hidden} && !$self->authCan('del')); + || $vid && (($v->{locked} || $v->{hidden}) && !$self->authCan('dbmod')); my $r = $v ? $self->dbReleaseGet(vid => $v->{id}) : []; my %b4 = !$vid ? () : ( - (map { $_ => $v->{$_} } qw|title original desc alias length l_wp l_encubed l_renai l_vnn img_nsfw ihid ilock|), + (map { $_ => $v->{$_} } qw|title original desc alias length l_wp l_encubed l_renai l_vnn image img_nsfw ihid ilock|), anime => join(' ', sort { $a <=> $b } map $_->{id}, @{$v->{anime}}), vnrelations => join('|||', map $_->{relation}.','.$_->{id}.','.($_->{official}?1:0).','.$_->{title}, sort { $a->{id} <=> $b->{id} } @{$v->{relations}}), screenshots => join(' ', map sprintf('%d,%d,%d', $_->{id}, $_->{nsfw}?1:0, $_->{rid}), @{$v->{screenshots}}), @@ -48,7 +48,7 @@ sub edit { { post => 'l_renai', required => 0, default => '', maxlength => 100 }, { post => 'l_vnn', required => 0, default => $b4{l_vnn}||0, template => 'int' }, { post => 'anime', required => 0, default => '' }, - { post => 'previmage', required => 0, default => 0, template => 'int' }, + { post => 'image', required => 0, default => 0, template => 'int' }, { post => 'img_nsfw', required => 0, default => 0 }, { post => 'vnrelations', required => 0, default => '', maxlength => 5000 }, { post => 'screenshots', required => 0, default => '', maxlength => 1000 }, @@ -60,7 +60,7 @@ sub edit { push @{$frm->{_err}}, 'badeditsum' if !$frm->{editsum} || lc($frm->{editsum}) eq lc($frm->{desc}); # handle image upload - my $image = _uploadimage($self, $v, $frm); + $frm->{image} = _uploadimage($self, $v, $frm); if(!$frm->{_err}) { # parse and re-sort fields that have multiple representations of the same information @@ -86,15 +86,13 @@ sub edit { # nothing changed? just redirect return $self->resRedirect("/v$vid", 'post') - if $vid && !$self->reqPost('img') && $image == $v->{image} - && !grep $frm->{$_} ne $b4{$_}, keys %b4; + if $vid && !grep $frm->{$_} ne $b4{$_}, keys %b4; # perform the edit/add my $nrev = $self->dbItemEdit(v => $vid ? $v->{cid} : undef, - (map { $_ => $frm->{$_} } qw|title original alias desc length l_wp l_encubed l_renai l_vnn editsum img_nsfw ihid ilock|), + (map { $_ => $frm->{$_} } qw|title original image alias desc length l_wp l_encubed l_renai l_vnn editsum img_nsfw ihid ilock|), anime => [ keys %$anime ], relations => $relations, - image => $image, screenshots => $screenshots, ); @@ -123,7 +121,7 @@ sub edit { sub _uploadimage { my($self, $v, $frm) = @_; - return $v ? $frm->{previmage} : 0 if $frm->{_err} || !$self->reqPost('img'); + return $v ? $frm->{image} : 0 if $frm->{_err} || !$self->reqPost('img'); # perform some elementary checks my $imgdata = $self->reqUploadRaw('img'); @@ -163,29 +161,31 @@ sub _form { [ static => content => mt '_vnedit_anime_msg' ], ], - vn_img => [ mt('_vnedit_image'), - [ hidden => short => 'previmage', value => $v ? $v->{image} : 0 ], - [ static => nolabel => 1, content => sub { - div class => 'img'; - p mt '_vnedit_image_none' if !$v || !$v->{image}; - p mt '_vnedit_image_processing' if $v && $v->{image} < 0; - img src => sprintf("%s/cv/%02d/%d.jpg", $self->{url_static}, $v->{image}%100, $v->{image}), alt => $v->{title} if $v && $v->{image} > 0; - end; - div; - - h2 mt '_vnedit_image_upload'; - input type => 'file', class => 'text', name => 'img', id => 'img'; - p mt('_vnedit_image_upload_msg'); - br; br; br; - - h2 mt '_vnedit_image_nsfw'; - input type => 'checkbox', class => 'checkbox', id => 'img_nsfw', name => 'img_nsfw', - $frm->{img_nsfw} ? (checked => 'checked') : (); - label class => 'checkbox', for => 'img_nsfw', mt '_vnedit_image_nsfw_check'; - p mt '_vnedit_image_nsfw_msg'; - end 'div'; - }], - ], + vn_img => [ mt('_vnedit_image'), [ static => nolabel => 1, content => sub { + div class => 'img'; + p mt '_vnedit_image_none' if !$v || !$v->{image}; + p mt '_vnedit_image_processing' if $v && $v->{image} < 0; + img src => sprintf("%s/cv/%02d/%d.jpg", $self->{url_static}, $v->{image}%100, $v->{image}), alt => $v->{title} if $v && $v->{image} > 0; + end; + + div; + h2 mt '_vnedit_image_id'; + input type => 'text', class => 'text', name => 'image', id => 'image', value => $frm->{image}||''; + p mt '_vnedit_image_id_msg'; + br; br; + + h2 mt '_vnedit_image_upload'; + input type => 'file', class => 'text', name => 'img', id => 'img'; + p mt('_vnedit_image_upload_msg'); + br; br; br; + + h2 mt '_vnedit_image_nsfw'; + input type => 'checkbox', class => 'checkbox', id => 'img_nsfw', name => 'img_nsfw', + $frm->{img_nsfw} ? (checked => 'checked') : (); + label class => 'checkbox', for => 'img_nsfw', mt '_vnedit_image_nsfw_check'; + p mt '_vnedit_image_nsfw_msg'; + end 'div'; + }]], vn_rel => [ mt('_vnedit_rel'), [ hidden => short => 'vnrelations' ], diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 37efb04a..e10b4414 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -141,6 +141,7 @@ sub page { _relations($self, \$i, $v) if @{$v->{relations}}; _anime($self, \$i, $v) if @{$v->{anime}}; _useroptions($self, \$i, $v) if $self->authInfo->{id}; + _affiliate_links($self, $r); Tr; td class => 'vndesc', colspan => 2; @@ -404,11 +405,56 @@ sub _useroptions { } +sub _affiliate_links { + my($self, $r) = @_; + return if !keys @$r; + my %r = map +($_->{id}, $_), @$r; + my $links = $self->dbAffiliateGet(rids => [ keys %r ], hidden => 0); + return if !@$links; + + $links = [ sort { $b->{priority}||$self->{affiliates}[$b->{affiliate}]{default_prio} <=> $a->{priority}||$self->{affiliates}[$a->{affiliate}]{default_prio} } @$links ]; + my $en = VNDB::L10N->get_handle('en'); + + Tr; td colspan => 2, id => 'buynow'; # don't call it "affiliate", most adblock filters have that included >_> + h1; a rel => 'nofollow', href => $self->{affiliates}[$links->[0]{affiliate}]{link_format} ? $self->{affiliates}[$links->[0]{affiliate}]{link_format}->($links->[0]{url}) : $links->[0]{url}, 'Buy now!'; end; + ul; + for my $link (@$links) { + my $f = $self->{affiliates}[$link->{affiliate}]; + + my $rel = $r{$link->{rid}}; + my $plat = grep($_ eq 'win', @{$rel->{platforms}}) ? '' : ' '.join(' and ', map $en->maketext("_plat_$_"), @{$rel->{platforms}}); + my $version = join(', ', map $en->maketext("_lang_$_"), @{$rel->{languages}}).$plat.' version'; + + li; a rel => 'nofollow', href => $f->{link_format} ? $f->{link_format}->($link->{url}) : $link->{url}; + use utf8; + txt '→ '; + txt $link->{version} + || ($f->{default_version} && $f->{default_version}->($self, $link, $rel)) + || $version; + txt ' '; + acronym class => 'pricenote', title => sprintf('Last updated: %s.', $en->age($link->{lastfetch})), "for $link->{price}*" + if $link->{price}; + txt " at $f->{name}."; + end; end; + } + end; + end; end; +} + + sub _releases { my($self, $v, $r) = @_; div class => 'mainbox releases'; - a class => 'addnew', href => "/v$v->{id}/add", mt '_vnpage_rel_add'; + if($self->authCan('edit')) { + p class => 'addnew'; + if($self->authCan('charedit')) { + a href => "/c/new?vid=$v->{id}", mt '_vnpage_char_add'; + txt ' | '; + } + a href => "/v$v->{id}/add", mt '_vnpage_rel_add'; + end; + } h1 mt '_vnpage_rel'; if(!@$r) { p mt '_vnpage_rel_none'; @@ -459,6 +505,7 @@ sub _releases { } end; td class => 'tc6'; + a href => "/affiliates/new?rid=$rel->{id}", 'a' if $self->authCan('affiliate'); if($rel->{website}) { a href => $rel->{website}, rel => 'nofollow'; cssicon 'ext', mt '_vnpage_rel_extlink'; diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm index 88e68edc..89807bef 100644 --- a/lib/VNDB/Util/Auth.pm +++ b/lib/VNDB/Util/Auth.pm @@ -88,8 +88,7 @@ sub authInfo { # a certain action. Argument is the action name as defined in global.pl sub authCan { my($self, $act) = @_; - my $r = $self->{_auth} ? $self->{_auth}{rank} : 0; - return scalar grep $_ eq $act, @{$self->{user_ranks}[$r]}[0..$#{$self->{user_ranks}[$r]}]; + return $self->{_auth} ? $self->{_auth}{perm} & $self->{permissions}{$act} : 0; } @@ -102,7 +101,7 @@ sub _authCheck { return 0 if !$user || length($user) > 15 || length($user) < 2 || !$pass; my $d = $self->dbUserGet(username => $user, what => 'extended notifycount')->[0]; - return 0 if !defined $d->{id} || !$d->{rank}; + return 0 if !$d->{id}; if(_authEncryptPass($self, $pass, $d->{salt}) eq $d->{passwd}) { $self->{_auth} = $d; diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm index 41370b4b..5f2fb330 100644 --- a/lib/VNDB/Util/CommonHTML.pm +++ b/lib/VNDB/Util/CommonHTML.pm @@ -72,8 +72,8 @@ sub htmlMainTabs { } if( $type eq 'u' && ($self->authInfo->{id} && $obj->{id} == $self->authInfo->{id} || $self->authCan('usermod')) - || $type =~ /[vrp]/ && $self->authCan('edit') && (!$obj->{locked} || $self->authCan('lock')) && (!$obj->{hidden} || $self->authCan('del')) - || $type eq 'c' && $self->authCan('charedit') && (!$obj->{locked} || $self->authCan('lock')) && (!$obj->{hidden} || $self->authCan('del')) + || $type =~ /[vrp]/ && $self->authCan('edit') && ((!$obj->{locked} && !$obj->{hidden}) || $self->authCan('dbmod')) + || $type eq 'c' && $self->authCan('charedit') && ((!$obj->{locked} && !$obj->{hidden}) || $self->authCan('dbmod')) || $type =~ /[gi]/ && $self->authCan('tagmod') ) { li $sel eq 'edit' ? (class => 'tabselected') : (); @@ -143,7 +143,7 @@ sub htmlHiddenMessage { end; end; end 'div'; - return $self->htmlFooter() || 1 if !$self->authCan('del'); + return $self->htmlFooter() || 1 if !$self->authCan('dbmod'); return 0; } @@ -163,6 +163,15 @@ sub htmlRevision { div class => 'mainbox revision'; h1 mt '_revision_title', $new->{rev}; + # character information may be rather spoilerous + if($type eq 'c') { + div class => 'warning'; + h2 mt '_revision_spoil_title'; + lit mt '_revision_spoil_msg', "/c$new->{id}"; + end; + br;br; + } + # previous/next revision links a class => 'prev', href => sprintf('/%s%d.%d', $type, $new->{id}, $new->{rev}-1), '<- '.mt '_revision_previous' if $new->{rev} > 1; @@ -174,7 +183,7 @@ sub htmlRevision { # no previous revision, just show info about the revision itself if(!$old) { - div; + div class => 'rev'; revheader($self, $type, $new); br; b mt '_revision_new_summary'; diff --git a/lib/VNDB/Util/FormHTML.pm b/lib/VNDB/Util/FormHTML.pm index 68b6a101..805467f6 100644 --- a/lib/VNDB/Util/FormHTML.pm +++ b/lib/VNDB/Util/FormHTML.pm @@ -214,15 +214,11 @@ sub htmlForm { fieldset class => 'submit'; if($options->{editsum}) { # hidden / locked checkbox - if($self->authCan('del')) { + if($self->authCan('dbmod')) { input type => 'checkbox', name => 'ihid', id => 'ihid', value => 1, $options->{frm}{ihid} ? (checked => 'checked') : (); label for => 'ihid', mt '_form_ihid'; - } - if($self->authCan('lock')) { input type => 'checkbox', name => 'ilock', id => 'ilock', value => 1, $options->{frm}{ilock} ? (checked => 'checked') : (); label for => 'ilock', mt '_form_ilock'; - } - if($self->authCan('lock') || $self->authCan('del')) { br; txt mt('_form_hidlock_note'); br; } diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm index 22052a75..752f6728 100644 --- a/lib/VNDB/Util/LayoutHTML.pm +++ b/lib/VNDB/Util/LayoutHTML.pm @@ -89,7 +89,6 @@ sub _menu { my $nc = $self->authInfo->{notifycount}; h2; a href => $uid, ucfirst $self->authInfo->{username}; - txt ' ('.mt('_urank_'.$self->authInfo->{rank}).')'; end; div; a href => "$uid/edit", mt '_menu_myprofile'; br; @@ -100,9 +99,13 @@ sub _menu { a href => "$uid/hist", mt '_menu_mychanges'; br; a href => '/g/links?u='.$self->authInfo->{id}, mt '_menu_mytags'; br; br; - a href => '/v/new', mt '_menu_addvn'; br; - a href => '/p/new', mt '_menu_addproducer'; br; - a href => '/c/new', mt '_menu_addcharacter'; br; + if($self->authCan('edit')) { + a href => '/v/new', mt '_menu_addvn'; br; + a href => '/p/new', mt '_menu_addproducer'; br; + } + if($self->authCan('charedit')) { + a href => '/c/new', mt '_menu_addcharacter'; br; + } br; a href => "$uid/logout", mt '_menu_logout'; end; @@ -130,7 +133,7 @@ sub _menu { h2 mt '_menu_dbstats'; div; dl; - for (qw|vn releases producers users threads posts|) { + for (qw|vn releases producers chars tags traits users threads posts|) { dt mt "_menu_stat_$_"; dd $self->{stats}{$_}; } diff --git a/lib/VNDBUtil.pm b/lib/VNDBUtil.pm index a0469a1c..76290013 100644 --- a/lib/VNDBUtil.pm +++ b/lib/VNDBUtil.pm @@ -27,8 +27,7 @@ sub shorten { # v+, v+.+ # http://../ sub bb2html { - my $raw = shift; - my $maxlength = shift; + my($raw, $maxlength, $charspoil) = @_; $raw =~ s/\r//g; return '' if !$raw && $raw ne "0"; @@ -76,7 +75,8 @@ sub bb2html { next; } elsif($tag eq '[spoiler]') { push @open, 'spoiler'; - $result .= '<b class="spoiler">'; + $result .= !$charspoil ? '<b class="spoiler">' + : '<b class="grayedout charspoil charspoil_-1"><hidden by spoiler settings></b><span class="charspoil charspoil_2 hidden">'; next; } elsif($tag eq '[quote]') { push @open, 'quote'; @@ -89,7 +89,7 @@ sub bb2html { $rmnewline = 1; next; } elsif($tag eq '[/spoiler]' && $open[$#open] eq 'spoiler') { - $result .= '</b>'; + $result .= !$charspoil ? '</b>' : '</span>'; pop @open; next; } elsif($tag eq '[/quote]' && $open[$#open] eq 'quote') { diff --git a/util/sql/all.sql b/util/sql/all.sql index d05297da..eac8d58a 100644 --- a/util/sql/all.sql +++ b/util/sql/all.sql @@ -12,7 +12,7 @@ CREATE TYPE gender AS ENUM ('unknown', 'm', 'f', 'b'); CREATE TYPE language AS ENUM ('cs', 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt-pt', 'pt-br', 'ru', 'sk', 'sv', 'tr', 'vi', 'zh'); CREATE TYPE medium AS ENUM ('cd', 'dvd', 'gdr', 'blr', 'flp', 'mrt', 'mem', 'umd', 'nod', 'in', 'otc'); CREATE TYPE notification_ntype AS ENUM ('pm', 'dbdel', 'listdel', 'dbedit', 'announce'); -CREATE TYPE notification_ltype AS ENUM ('v', 'r', 'p', 't'); +CREATE TYPE notification_ltype AS ENUM ('v', 'r', 'p', 'c', 't'); CREATE TYPE prefs_key AS ENUM ('l10n', 'skin', 'customcss', 'filter_vn', 'filter_release', 'show_nsfw', 'hide_list', 'notify_nodbedit', 'notify_announce'); CREATE TYPE producer_relation AS ENUM ('old', 'new', 'sub', 'par', 'imp', 'ipa', 'spa', 'ori'); CREATE TYPE release_type AS ENUM ('complete', 'partial', 'trial'); @@ -47,6 +47,12 @@ CREATE TRIGGER stats_cache_new AFTER INSERT ON producers CREATE TRIGGER stats_cache_edit AFTER UPDATE ON producers FOR EACH ROW WHEN (OLD.hidden IS DISTINCT FROM NEW.hidden) EXECUTE PROCEDURE update_stats_cache(); CREATE TRIGGER stats_cache_new AFTER INSERT ON releases FOR EACH ROW WHEN (NEW.hidden = FALSE) EXECUTE PROCEDURE update_stats_cache(); CREATE TRIGGER stats_cache_edit AFTER UPDATE ON releases FOR EACH ROW WHEN (OLD.hidden IS DISTINCT FROM NEW.hidden) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_new AFTER INSERT ON chars FOR EACH ROW WHEN (NEW.hidden = FALSE) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_edit AFTER UPDATE ON chars FOR EACH ROW WHEN (OLD.hidden IS DISTINCT FROM NEW.hidden) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_new AFTER INSERT ON tags FOR EACH ROW WHEN (NEW.state = 2) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_edit AFTER UPDATE ON tags FOR EACH ROW WHEN (OLD.state IS DISTINCT FROM NEW.state) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_new AFTER INSERT ON traits FOR EACH ROW WHEN (NEW.state = 2) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_edit AFTER UPDATE ON traits FOR EACH ROW WHEN (OLD.state IS DISTINCT FROM NEW.state) EXECUTE PROCEDURE update_stats_cache(); CREATE TRIGGER stats_cache_new AFTER INSERT ON threads FOR EACH ROW WHEN (NEW.hidden = FALSE) EXECUTE PROCEDURE update_stats_cache(); CREATE TRIGGER stats_cache_edit AFTER UPDATE ON threads FOR EACH ROW WHEN (OLD.hidden IS DISTINCT FROM NEW.hidden) EXECUTE PROCEDURE update_stats_cache(); CREATE TRIGGER stats_cache_new AFTER INSERT ON threads_posts FOR EACH ROW WHEN (NEW.hidden = FALSE) EXECUTE PROCEDURE update_stats_cache(); @@ -88,11 +94,13 @@ CREATE TRIGGER notify_pm AFTER INSERT ON threads_pos CREATE TRIGGER notify_dbdel AFTER UPDATE ON vn FOR EACH ROW WHEN (NOT OLD.hidden AND NEW.hidden) EXECUTE PROCEDURE notify_dbdel(); CREATE TRIGGER notify_dbdel AFTER UPDATE ON producers FOR EACH ROW WHEN (NOT OLD.hidden AND NEW.hidden) EXECUTE PROCEDURE notify_dbdel(); CREATE TRIGGER notify_dbdel AFTER UPDATE ON releases FOR EACH ROW WHEN (NOT OLD.hidden AND NEW.hidden) EXECUTE PROCEDURE notify_dbdel(); +CREATE TRIGGER notify_dbdel AFTER UPDATE ON chars FOR EACH ROW WHEN (NOT OLD.hidden AND NEW.hidden) EXECUTE PROCEDURE notify_dbdel(); CREATE TRIGGER notify_listdel AFTER UPDATE ON vn FOR EACH ROW WHEN (NOT OLD.hidden AND NEW.hidden) EXECUTE PROCEDURE notify_listdel(); CREATE TRIGGER notify_listdel AFTER UPDATE ON releases FOR EACH ROW WHEN (NOT OLD.hidden AND NEW.hidden) EXECUTE PROCEDURE notify_listdel(); CREATE TRIGGER notify_dbedit AFTER UPDATE ON vn FOR EACH ROW WHEN (OLD.latest IS DISTINCT FROM NEW.latest AND NOT NEW.hidden) EXECUTE PROCEDURE notify_dbedit(); CREATE TRIGGER notify_dbedit AFTER UPDATE ON producers FOR EACH ROW WHEN (OLD.latest IS DISTINCT FROM NEW.latest AND NOT NEW.hidden) EXECUTE PROCEDURE notify_dbedit(); CREATE TRIGGER notify_dbedit AFTER UPDATE ON releases FOR EACH ROW WHEN (OLD.latest IS DISTINCT FROM NEW.latest AND NOT NEW.hidden) EXECUTE PROCEDURE notify_dbedit(); +CREATE TRIGGER notify_dbedit AFTER UPDATE ON chars FOR EACH ROW WHEN (OLD.latest IS DISTINCT FROM NEW.latest AND NOT NEW.hidden) EXECUTE PROCEDURE notify_dbedit(); CREATE TRIGGER notify_announce AFTER INSERT ON threads_posts FOR EACH ROW WHEN (NEW.num = 1) EXECUTE PROCEDURE notify_announce(); CREATE TRIGGER vn_vnsearch_notify AFTER UPDATE ON vn FOR EACH ROW @@ -124,6 +132,9 @@ INSERT INTO stats_cache (section, count) VALUES ('vn', 0), ('producers', 0), ('releases', 0), + ('chars', 0), + ('tags', 0), + ('traits', 0), ('threads', 0), ('threads_posts', 0); diff --git a/util/sql/func.sql b/util/sql/func.sql index 75e5ecad..3af04562 100644 --- a/util/sql/func.sql +++ b/util/sql/func.sql @@ -490,13 +490,16 @@ $$ LANGUAGE 'plpgsql'; -- the stats_cache table CREATE OR REPLACE FUNCTION update_stats_cache() RETURNS TRIGGER AS $$ +DECLARE + unhidden boolean; + hidden boolean; BEGIN IF TG_OP = 'INSERT' THEN IF TG_TABLE_NAME = 'users' THEN UPDATE stats_cache SET count = count+1 WHERE section = TG_TABLE_NAME; ELSE IF TG_TABLE_NAME = 'threads_posts' THEN - IF EXISTS(SELECT 1 FROM threads WHERE id = NEW.tid AND hidden = FALSE) THEN + IF EXISTS(SELECT 1 FROM threads WHERE id = NEW.tid AND threads.hidden = FALSE) THEN UPDATE stats_cache SET count = count+1 WHERE section = TG_TABLE_NAME; END IF; ELSE @@ -504,13 +507,20 @@ BEGIN END IF; END IF; - ELSIF TG_OP = 'UPDATE' AND TG_TABLE_NAME <> 'users' THEN - IF OLD.hidden = TRUE THEN + ELSIF TG_OP = 'UPDATE' THEN + IF TG_TABLE_NAME IN('tags', 'traits') THEN + unhidden := OLD.state <> 2 AND NEW.state = 2; + hidden := OLD.state = 2 AND NEW.state <> 2; + ELSE + unhidden := OLD.hidden AND NOT NEW.hidden; + hidden := NOT unhidden; + END IF; + IF unhidden THEN IF TG_TABLE_NAME = 'threads' THEN UPDATE stats_cache SET count = count+NEW.count WHERE section = 'threads_posts'; END IF; UPDATE stats_cache SET count = count+1 WHERE section = TG_TABLE_NAME; - ELSIF OLD.hidden = FALSE THEN + ELSIF hidden THEN IF TG_TABLE_NAME = 'threads' THEN UPDATE stats_cache SET count = count-NEW.count WHERE section = 'threads_posts'; END IF; @@ -802,16 +812,16 @@ END; $$ LANGUAGE plpgsql; --- called on UPDATE vn / producers / releases when (NOT OLD.hidden AND NEW.hidden) +-- called on UPDATE vn / producers / releases / chars when (NOT OLD.hidden AND NEW.hidden) CREATE OR REPLACE FUNCTION notify_dbdel() RETURNS trigger AS $$ BEGIN INSERT INTO notifications (ntype, ltype, uid, iid, subid, c_title, c_byuser) SELECT DISTINCT 'dbdel'::notification_ntype, - (CASE TG_TABLE_NAME WHEN 'vn' THEN 'v' WHEN 'releases' THEN 'r' ELSE 'p' END)::notification_ltype, - c.requester, NEW.id, c2.rev, x.title, c2.requester + (CASE TG_TABLE_NAME WHEN 'vn' THEN 'v' WHEN 'releases' THEN 'r' WHEN 'producers' THEN 'p' ELSE 'c' END)::notification_ltype, + h.requester, NEW.id, h2.rev, x.title, h2.requester -- look for changes of the deleted entry -- this method may look a bit unintuitive, but it's way faster than doing LEFT JOINs - FROM changes c + FROM changes h JOIN ( SELECT vr.id, vr2.title FROM vn_rev vr JOIN vn v ON v.id = vr.vid JOIN vn_rev vr2 ON vr2.id = v.latest WHERE TG_TABLE_NAME = 'vn' AND vr.vid = NEW.id @@ -821,12 +831,15 @@ BEGIN UNION SELECT pr.id, pr2.name FROM producers_rev pr JOIN producers p ON p.id = pr.pid JOIN producers_rev pr2 ON pr2.id = p.latest WHERE TG_TABLE_NAME = 'producers' AND pr.pid = NEW.id - ) x(id, title) ON c.id = x.id + UNION SELECT cr.id, cr2.name FROM chars_rev cr + JOIN chars c ON c.id = cr.cid JOIN chars_rev cr2 ON cr2.id = c.latest + WHERE TG_TABLE_NAME = 'chars' AND cr.cid = NEW.id + ) x(id, title) ON h.id = x.id -- join info about the deletion itself - JOIN changes c2 ON c2.id = NEW.latest - WHERE c.requester <> 1 -- exclude Multi + JOIN changes h2 ON h2.id = NEW.latest + WHERE h.requester <> 1 -- exclude Multi -- exclude the user who deleted the entry - AND c.requester <> c2.requester; + AND h.requester <> h2.requester; RETURN NULL; END; $$ LANGUAGE plpgsql; @@ -862,16 +875,16 @@ END; $$ LANGUAGE plpgsql; --- called on UPDATE vn / producers / releases when (OLD.latest IS DISTINCT FROM NEW.latest AND NOT NEW.hidden) +-- called on UPDATE vn / producers / releases / chars when (OLD.latest IS DISTINCT FROM NEW.latest AND NOT NEW.hidden) -- this trigger is very similar to notify_dbdel() CREATE OR REPLACE FUNCTION notify_dbedit() RETURNS trigger AS $$ BEGIN INSERT INTO notifications (ntype, ltype, uid, iid, subid, c_title, c_byuser) SELECT DISTINCT 'dbedit'::notification_ntype, - (CASE TG_TABLE_NAME WHEN 'vn' THEN 'v' WHEN 'releases' THEN 'r' ELSE 'p' END)::notification_ltype, - c.requester, NEW.id, c2.rev, x.title, c2.requester + (CASE TG_TABLE_NAME WHEN 'vn' THEN 'v' WHEN 'releases' THEN 'r' WHEN 'producers' THEN 'p' ELSE 'c' END)::notification_ltype, + h.requester, NEW.id, h2.rev, x.title, h2.requester -- look for changes of the edited entry - FROM changes c + FROM changes h JOIN ( SELECT vr.id, vr2.title FROM vn_rev vr JOIN vn v ON v.id = vr.vid JOIN vn_rev vr2 ON vr2.id = v.latest WHERE TG_TABLE_NAME = 'vn' AND vr.vid = NEW.id @@ -881,13 +894,16 @@ BEGIN UNION SELECT pr.id, pr2.name FROM producers_rev pr JOIN producers p ON p.id = pr.pid JOIN producers_rev pr2 ON pr2.id = p.latest WHERE TG_TABLE_NAME = 'producers' AND pr.pid = NEW.id - ) x(id, title) ON c.id = x.id + UNION SELECT cr.id, cr2.name FROM chars_rev cr + JOIN chars c ON c.id = cr.cid JOIN chars_rev cr2 ON cr2.id = c.latest + WHERE TG_TABLE_NAME = 'chars' AND cr.cid = NEW.id + ) x(id, title) ON h.id = x.id -- join info about the deletion itself - JOIN changes c2 ON c2.id = NEW.latest + JOIN changes h2 ON h2.id = NEW.latest -- exclude the user who edited the entry - WHERE c.requester <> c2.requester + WHERE h.requester <> h2.requester -- exclude users who don't want this notify - AND NOT EXISTS(SELECT 1 FROM users_prefs up WHERE uid = c.requester AND key = 'notify_nodbedit'); + AND NOT EXISTS(SELECT 1 FROM users_prefs up WHERE uid = h.requester AND key = 'notify_nodbedit'); RETURN NULL; END; $$ LANGUAGE plpgsql; diff --git a/util/sql/schema.sql b/util/sql/schema.sql index 39e9351a..6cbd2b14 100644 --- a/util/sql/schema.sql +++ b/util/sql/schema.sql @@ -1,5 +1,19 @@ +-- affiliate_links +CREATE TABLE affiliate_links ( + id SERIAL PRIMARY KEY, + rid integer NOT NULL, + hidden boolean NOT NULL DEFAULT false, + priority smallint NOT NULL DEFAULT 0, + affiliate smallint NOT NULL DEFAULT 0, + url varchar NOT NULL, + version varchar NOT NULL DEFAULT '', + lastfetch timestamptz, + price varchar NOT NULL DEFAULT '', + data varchar NOT NULL DEFAULT '' +); + -- anime CREATE TABLE anime ( id integer NOT NULL PRIMARY KEY, @@ -343,7 +357,7 @@ CREATE TABLE users ( id SERIAL NOT NULL PRIMARY KEY, username varchar(20) NOT NULL UNIQUE, mail varchar(100) NOT NULL, - rank smallint NOT NULL DEFAULT 3, + perm smallint NOT NULL DEFAULT 1+4+16, passwd bytea NOT NULL DEFAULT '', registered timestamptz NOT NULL DEFAULT NOW(), c_votes integer NOT NULL DEFAULT 0, @@ -452,6 +466,7 @@ CREATE TABLE wlists ( +ALTER TABLE affiliate_links ADD FOREIGN KEY (rid) REFERENCES releases (id); ALTER TABLE changes ADD FOREIGN KEY (requester) REFERENCES users (id) ON DELETE SET DEFAULT; ALTER TABLE chars ADD FOREIGN KEY (latest) REFERENCES chars_rev (id) DEFERRABLE INITIALLY DEFERRED; ALTER TABLE chars_rev ADD FOREIGN KEY (id) REFERENCES changes (id); @@ -521,6 +536,7 @@ ALTER TABLE wlists ADD FOREIGN KEY (uid) REFERENCES users ALTER TABLE wlists ADD FOREIGN KEY (vid) REFERENCES vn (id); +CREATE INDEX affiliate_links_rid ON affiliate_links (rid) WHERE NOT hidden; CREATE INDEX releases_vn_vid ON releases_vn (vid); CREATE INDEX tags_vn_date ON tags_vn (date); CREATE UNIQUE INDEX chars_vns_pkey ON chars_vns (cid, vid, COALESCE(rid, 0)); diff --git a/util/updates/update_2.20.sql b/util/updates/update_2.20.sql new file mode 100644 index 00000000..390e9a41 --- /dev/null +++ b/util/updates/update_2.20.sql @@ -0,0 +1,54 @@ + +ALTER TYPE notification_ltype RENAME TO tmp; +CREATE TYPE notification_ltype AS ENUM ('v', 'r', 'p', 'c', 't'); +ALTER TABLE notifications ALTER COLUMN ltype TYPE notification_ltype USING ltype::text::notification_ltype; +DROP TYPE tmp; + +\i util/sql/func.sql + +CREATE TRIGGER notify_dbdel AFTER UPDATE ON chars FOR EACH ROW WHEN (NOT OLD.hidden AND NEW.hidden) EXECUTE PROCEDURE notify_dbdel(); +CREATE TRIGGER notify_dbedit AFTER UPDATE ON chars FOR EACH ROW WHEN (OLD.latest IS DISTINCT FROM NEW.latest AND NOT NEW.hidden) EXECUTE PROCEDURE notify_dbedit(); + + +INSERT INTO stats_cache VALUES + ('chars', (SELECT COUNT(*) FROM chars WHERE NOT hidden)), + ('tags', (SELECT COUNT(*) FROM tags WHERE state = 2)), + ('traits', (SELECT COUNT(*) FROM traits WHERE state = 2)); + +CREATE TRIGGER stats_cache_new AFTER INSERT ON chars FOR EACH ROW WHEN (NEW.hidden = FALSE) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_edit AFTER UPDATE ON chars FOR EACH ROW WHEN (OLD.hidden IS DISTINCT FROM NEW.hidden) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_new AFTER INSERT ON tags FOR EACH ROW WHEN (NEW.state = 2) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_edit AFTER UPDATE ON tags FOR EACH ROW WHEN (OLD.state IS DISTINCT FROM NEW.state) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_new AFTER INSERT ON traits FOR EACH ROW WHEN (NEW.state = 2) EXECUTE PROCEDURE update_stats_cache(); +CREATE TRIGGER stats_cache_edit AFTER UPDATE ON traits FOR EACH ROW WHEN (OLD.state IS DISTINCT FROM NEW.state) EXECUTE PROCEDURE update_stats_cache(); + + + +CREATE TABLE affiliate_links ( + id SERIAL PRIMARY KEY, + rid integer NOT NULL REFERENCES releases (id), + hidden boolean NOT NULL DEFAULT false, -- to hide a link for some reason + priority smallint NOT NULL DEFAULT 0, -- manual ordering when competing on a VN page, usually not necessary + affiliate smallint NOT NULL DEFAULT 0, -- index to a semi-static array in data/config.pl + url varchar NOT NULL, + version varchar NOT NULL DEFAULT '', -- "x edition" or "x version", default used is "<language> version" + lastfetch timestamptz, -- last update of price + price varchar NOT NULL DEFAULT '', -- formatted, including currency, e.g. "$50" or "€34.95 / $50.46" + data varchar NOT NULL DEFAULT '' -- to be used by a fetch bot, if any +); + +CREATE INDEX affiliate_links_rid ON affiliate_links (rid) WHERE NOT hidden; + + + +-- rank -> permissions + +ALTER TABLE users RENAME rank TO perm; +ALTER TABLE users ALTER COLUMN perm SET DEFAULT 1+4+16; +UPDATE users SET perm = CASE + WHEN perm = 2 THEN 1 + WHEN perm = 3 THEN 1+4+16 + WHEN perm = 4 THEN 1+2+4+8+16+32+64 + WHEN perm = 5 THEN 1+2+4+8+16+32+64+128+256 + ELSE 0 END; + diff --git a/util/vndb.pl b/util/vndb.pl index b6056dd4..765138fe 100755 --- a/util/vndb.pl +++ b/util/vndb.pl @@ -52,7 +52,7 @@ TUWF::set( mail => { regex => qr/^[^@<>]+@[^@.<>]+(?:\.[^@.<>]+)+$/ }, url => { regex => qr/^(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?$/ }, asciiprint => { regex => qr/^[\x20-\x7E]*$/ }, - int => { regex => qr/^-?\d+$/ }, + int => { regex => qr/^-?[0-9]+$/ }, pname => { regex => qr/^[a-z0-9-]*$/ }, }, ); |