summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2011-01-02 14:17:57 +0100
committerYorhel <git@yorhel.nl>2011-01-02 14:17:57 +0100
commitb4e3c35620916852a6028ab5f6644382553408f9 (patch)
tree303248a812f6a7dc9c25bec0d8af5836c87fec3f
parent632df9599de8dbb25707b0bf8caea075c55cfa3f (diff)
parent98f4725013b6d7a65e1fd07f7f02785b12e8a9bd (diff)
Merge branch 'beta'2.16
Conflicts: ChangeLog lib/VNDB/Handler/ULists.pm
-rw-r--r--ChangeLog25
-rw-r--r--Makefile7
-rw-r--r--data/global.pl6
-rw-r--r--data/lang.txt314
-rw-r--r--data/script.js246
-rw-r--r--data/style.css92
-rw-r--r--lib/Multi/API.pm2
-rw-r--r--lib/VNDB/DB/Releases.pm11
-rw-r--r--lib/VNDB/DB/Tags.pm2
-rw-r--r--lib/VNDB/DB/ULists.pm138
-rw-r--r--lib/VNDB/DB/Users.pm39
-rw-r--r--lib/VNDB/DB/VN.pm47
-rw-r--r--lib/VNDB/Func.pm21
-rw-r--r--lib/VNDB/Handler/Discussions.pm6
-rw-r--r--lib/VNDB/Handler/Misc.pm46
-rw-r--r--lib/VNDB/Handler/Producers.pm5
-rw-r--r--lib/VNDB/Handler/Releases.pm89
-rw-r--r--lib/VNDB/Handler/Tags.pm29
-rw-r--r--lib/VNDB/Handler/ULists.pm237
-rw-r--r--lib/VNDB/Handler/Users.pm72
-rw-r--r--lib/VNDB/Handler/VNBrowse.pm73
-rw-r--r--lib/VNDB/Handler/VNEdit.pm4
-rw-r--r--lib/VNDB/Handler/VNPage.pm32
-rw-r--r--lib/VNDB/Util/Auth.pm17
-rw-r--r--lib/VNDB/Util/BrowseHTML.pm43
-rw-r--r--lib/VNDB/Util/CommonHTML.pm6
-rw-r--r--lib/VNDB/Util/FormHTML.pm4
-rw-r--r--lib/VNDB/Util/LayoutHTML.pm22
-rw-r--r--lib/VNDB/Util/Misc.pm100
-rwxr-xr-xutil/dbgraph.pl2
-rwxr-xr-xutil/jsgen.pl5
-rw-r--r--util/sql/all.sql12
-rw-r--r--util/sql/func.sql66
-rw-r--r--util/sql/schema.sql40
-rw-r--r--util/updates/update_2.16.sql86
-rwxr-xr-xutil/vndb.pl44
36 files changed, 1336 insertions, 654 deletions
diff --git a/ChangeLog b/ChangeLog
index 58c6795d..c28a8951 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,31 @@
-2.16 - ?
+2.16 - 2011-01-02
- VNDBUtil::bb2html(): Fixed bug when the string starts with a VNDBID
- VNDBUtil::bb2html(): Fixed bug with lowercasing all [url=..] URLs
- Fixed perl warning on /v/search redirect without search query
- Bugfix: Don't allow others to open /u+/votes when show_list is false
+ - Don't allow NULL for rr.minage and use -1 for unknown
+ - Check for editsum = description and give an easier to understand error
+ - RFC-01: Added vnlists feature and removed rlists.vstat option
+ - ULists::votelist: Don't give a 404 on /u+/votes when no votes found
+ - Added tab and link for /u+/votes to user tabs & main menu
+ - Added ability to batch-edit votes to /u+/votes
+ - Update the votes.date column when changing a vote
+ - ULists::votelist: Added first character selection
+ - Added advanced page-browsing tabs to threads
+ - Added notes field to the user VN list
+ - Added vnlists.status filter to /u+/list
+ - Pass VN tag filters by ID rather than name
+ - Improved VN tag filter selection with a dynamic HTML list of selected tags
+ - Don't send 'tagspoil' filter when 'tag_inc' isn't active
+ - Don't allow page > 100 or sorting on username or title on tag link browser
+ - Added users_prefs table and removed the following columns from users:
+ skin, customcss, show_nsfw, show_list, notify_announce, notify_dbedit
+ - Store l10n preference in the database for logged-in users
+ - Bugfix: check for validness of form arguments on /[uv]+/votes
+ - Bugfix: translate screen resolutions on release revision pages
+ - Bugfix: properly escape search query in links query string
+ - Bugfix: allow a VN to be available for more than 7 platforms
+ - Implemented permanent release/vn filters
2.15 - 2010-12-15
- Removed expand/collapse from history browser and /u+/posts and switched to
diff --git a/Makefile b/Makefile
index 6c8b45bf..4afd0f59 100644
--- a/Makefile
+++ b/Makefile
@@ -38,7 +38,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
+ sql-import update-2.10 update-2.11 update-2.12 update-2.13 update-2.14 update-2.15 update-2.16
all: dirs js skins robots data/config.pl
@@ -158,3 +158,8 @@ update-2.15: all
$(multi-stop)
${runpsql} < util/updates/update_2.15.sql
$(multi-start)
+
+update-2.16: all
+ $(multi-stop)
+ ${runpsql} < util/updates/update_2.16.sql
+ $(multi-start)
diff --git a/data/global.pl b/data/global.pl
index 95d966b5..f898599e 100644
--- a/data/global.pl
+++ b/data/global.pl
@@ -64,7 +64,7 @@ our %S = (%S,
'imp' => [ 6, 'ipa' ],
'ipa' => [ 7, 'imp' ],
},
- age_ratings => [undef, 0, 6..18],
+ age_ratings => [-1, 0, 6..18],
release_types => [qw|complete partial trial|],
platforms => [qw|win dos lin mac ios dvd gba msx nds nes p98 psp ps1 ps2 ps3 drc sat sfc wii xb3 oth|],
media => {
@@ -99,8 +99,8 @@ our %S = (%S,
voiced => [ 0..4 ],
animated => [ 0..4 ],
wishlist_status => [ 0..3 ],
- rlst_rstat => [ 0..4 ], # 2 = hardcoded 'OK', < 2 = hardcoded 'NOK'
- rlst_vstat => [ 0..4 ], # 2 = hardcoded 'OK', 0 || 4 = hardcoded 'NOK'
+ rlist_status => [ 0..4 ], # 2 = hardcoded 'OK'
+ vnlist_status => [ 0..4 ],
atom_feeds => { # num_entries, title, id
announcements => [ 10, 'VNDB Site Announcements', '/t/an' ],
changes => [ 25, 'VNDB Recent Changes', '/hist' ],
diff --git a/data/lang.txt b/data/lang.txt
index 3508917d..f2ab015f 100644
--- a/data/lang.txt
+++ b/data/lang.txt
@@ -1137,70 +1137,70 @@ nl : Erg lang[index,_1,, (> 50 uur), (Clannad~, Umineko~, Fate/Stay Night)]
# VN list statuses
-:_rlst_rstat_0
+:_rlist_status_0
en : Unknown
ru : Неизвестно
cs : Není známo
hu : Ismeretlen
nl : Onbekend
-:_rlst_rstat_1
+:_rlist_status_1
en : Pending
ru : Ожидается
cs : V čekání
hu : Függő
nl : Bezig
-:_rlst_rstat_2
+:_rlist_status_2
en : Obtained
ru : Приобретено
cs : Obdrženo
hu : Megszerezve
nl : In bezit
-:_rlst_rstat_3
+:_rlist_status_3
en : On loan
ru : Взято напрокат
cs : Vypůjčeno
hu : Kölcsönadva
nl : Uitgeleend
-:_rlst_rstat_4
+:_rlist_status_4
en : Deleted
ru : Удалено
cs : Smazáno
hu : Törölve
nl : Weggegooid
-:_rlst_vstat_0
+:_vnlist_status_0
en : Unknown
ru : Неизвестно
cs : Není známo
hu : Ismeretlen
nl : Onbekend
-:_rlst_vstat_1
+:_vnlist_status_1
en : Playing
ru : В процессе
cs : Ve hraní
hu : Játszás
nl : Mee bezig
-:_rlst_vstat_2
+:_vnlist_status_2
en : Finished
ru : Завершено
cs : Dohráno
hu : Befejezve
nl : Uitgespeeld
-:_rlst_vstat_3
+:_vnlist_status_3
en : Stalled
ru : Застряло
cs : Pozastaveno
hu : Leragadva
nl : Uitgesteld
-:_rlst_vstat_4
+:_vnlist_status_4
en : Dropped
ru : Заброшено
cs : Vyřazeno
@@ -1302,7 +1302,7 @@ nl : Per dag mag je maar één account aanmaken vanaf hetzelfde IP
:_formerr_e_notanswer
en : Question was not correctly answered. Are you sure you are a human?
-ru : Неправильный ответ. А ты точно человек?
+ru : Неправильный ответ. А вы точно человек?
cs : Na otázku jste neodpověděli správně. Jste si jisti, že jste člověk?
hu : A kérdésre a válasz helytelen. Biztos vagy benne, hogy ember vagy?
nl : Vraag was incorrect beantwoord. Weet je zeker dat je een mens bent?
@@ -1321,6 +1321,13 @@ cs : Před posláním dalšího příspěvku počkejte prosím 30 sekund
hu : Kérlek várj 30 másodpercet mielőtt új hozzászólást küldesz
nl : Wacht a.u.b. 30 seconden voordat je een nieuwe post maakt
+:_formerr_e_badeditsum
+en : Please read [url,/d5.4,the guidelines] on how to use the edit summary.
+ru : Пожалуйста, прочтите [url,/d5.4,руководство] о том, как использовать поле "Суммарно о правке".
+cs*:
+hu*:
+nl : Lees a.u.b. [url,/d5.4,de richtlijnen] over het gebruik van de samenvatting.
+
:_formerr_title
en : Error
ru : Ошибка
@@ -1681,6 +1688,13 @@ cs : Můj list vizuálních novel
hu : A visual novel listám
nl : Mijn Visual Novel lijst
+:_menu_myvotes
+en : My Votes
+ru : Мои отданные голоса
+cs*:
+hu*:
+nl : Mijn Stemmen
+
:_menu_mywishlist
en : My Wishlist
ru : Мой список желаемого
@@ -1850,6 +1864,13 @@ cs : wishlist
hu : kivánságlista
nl : wensenlijst
+:_mtabs_votes
+en : votes
+ru : голоса
+cs*:
+hu*:
+nl : stemmen
+
:_mtabs_list
en : list
ru : список
@@ -1900,6 +1921,13 @@ nl : relaties
# Navigation buttons on the browse pages
+:_browse_first
+en : first
+ru : первая
+cs*:
+hu*:
+nl : eerste
+
:_browse_previous
en : previous
ru : назад
@@ -1914,6 +1942,13 @@ cs : další
hu : következő
nl : volgende
+:_browse_last
+en : last
+ru : последняя
+cs*:
+hu*:
+nl : laatste
+
# Revision pages
@@ -3251,25 +3286,18 @@ hu : nincs a listádba
nl : niet op je lijst
:_relinfo_user_inlist
-en : Status: [_1] / [_2]
-ru : Статус: [_1] / [_2]
-cs : Status: [_1] / [_2]
-hu : Állapot: [_1] / [_2]
-nl :
-
-:_relinfo_user_setr
-en : Set release status
-ru : Установка статуса выпуска
-cs : Změnit status vydání
-hu : Kiadás állapotának módosítása
-nl : Zet uitgavestatus
-
-:_relinfo_user_setv
-en : Set play status
-ru : Установка статуса игры
-cs : Změnit herní status
-hu : Játszás állapotának módosítása
-nl : Zet speelstatus
+en : Status: [_1]
+ru : Статус: [_1]
+cs : Status: [_1]
+hu : Állapot: [_1]
+nl :
+
+:_relinfo_user_setstatus
+en : Set status
+ru : Установка статуса
+cs*:
+hu*:
+nl : Zet status
:_relinfo_user_del
en : remove from list
@@ -3901,6 +3929,13 @@ cs : Ero scény
hu : Ero jelenetek
nl : Erotische scenes
+:_rbrowse_filsave
+en : Save as default
+ru : Запомнить по умолчанию
+cs*:
+hu*:
+nl : Opslaan als standaard
+
:_rbrowse_apply
en : Apply
ru : Применить
@@ -3915,6 +3950,13 @@ cs : Vymazat
hu : Törlés
nl :
+:_rbrowse_savenote
+en : Your saved filters will be applied automatically to several other parts of the site as well, such as the homepage. To change these filters, come back to this page and use the "Save as default" button again. To remove your saved filters, hit "Reset" and then save.
+ru : Сохранённые фильтры также будут автоматически применены к другим частям сайта, например, к заглавной странице. Для их изменения вновь откройте эту страницу и щёлкните кнопку "Запомнить по умолчанию". Чтобы избавиться от сохранённых фильтров, щёлкните "Сброс" и сохраните настройки.
+cs*:
+hu*:
+nl : Je opgeslagen filters worden ook automatisch toegepast op een aantal andere delen van de site, waaronder de homepagina. Om deze filters te veranderen, ga terug naar deze pagina en gebruik de "Opslaan als standaard" knop weer. Om je opgeslagen filters te verwijderen, klik op "Reset" en sla je instellingen weer op.
+
@@ -4044,11 +4086,11 @@ hu : Nagyobb spoilerek megjelenítése
nl : Laat alle spoilers zien
:_tagp_novn
-en : This tag has not been linked to any visual novels yet, or they were hidden because of the spoiler settings.
-ru : Этот тег пока не содержит ссылок ни на одну новеллу, либо они скрыты из-за настроек отображения спойлеров.
-cs : Tento tag ještě nebyl použit v žádné vizuální novele nebo byly tyto skryty kvůli nastavení spoilerů.
-hu : Ez a címke eddig egy visual novelhez sincs hozzárendelve, vagy el lettek rejtve a spoiler-ek beállítása során.
-nl : Deze tag is nog niet gekoppeld aan een visual novel, of deze worden niet weergegeven in verband met de spoilerinstelling.
+en : This tag has not been linked to any visual novels yet, or they were hidden because of your spoiler settings or default filters.
+ru : Этот тег пока не содержит ссылок ни на одну новеллу, либо они скрыты из-за настроек отображения спойлеров, либо из-за фильтров.
+cs*: Tento tag ještě nebyl použit v žádné vizuální novele nebo byly tyto skryty kvůli nastavení spoilerů.
+hu*: Ez a címke eddig egy visual novelhez sincs hozzárendelve, vagy el lettek rejtve a spoiler-ek beállítása során.
+nl : Deze tag is nog niet gekoppeld aan een visual novel, of deze worden niet weergegeven in verband met je spoiler- of filterinstelling.
:_tagp_cached
en : The list below also includes all visual novels linked to child tags. This list is cached, it can take up to 24 hours after a visual novel has been tagged for it to show up on this page.
@@ -4822,6 +4864,13 @@ cs*:
hu : Szavazatok [_1]-tól(től)
nl : Stemmen van [_1]
+:_votelist_novotes
+en : No votes to list. :-(
+ru : Не найдено ни одного голоса. T_T
+cs*:
+hu*:
+nl : Geen stemmen om weer te geven. :-(
+
:_votelist_col_date
en : Cast
ru : Дата
@@ -4868,7 +4917,7 @@ cs : List vizuálních novel uživatele [_1]
hu : [_1] visual novel listája
nl : Visual novel lijst van [_1]
-:_rlist_voted_all
+:_rlist_all
en : All
ru : Все
cs : Všechny
@@ -4896,6 +4945,13 @@ cs : Název
hu : Cím
nl : Titel
+:_rlist_col_status
+en : Status
+ru : Статус
+cs*:
+hu*:
+nl :
+
:_rlist_col_releases
en : Releases
ru : Выпуски
@@ -4910,26 +4966,40 @@ cs : Hodnocení
hu : Szavazat
nl : Stem
-:_rlist_selection
-en : -- with selected --
-ru : -- с выбранными --
-cs : -- s vybranými --
-hu : -- kiválasztva --
-nl : -- met geselecteerde items --
+:_rlist_withvn
+en : -- with selected VNs --
+ru : -- с указанными новеллами --
+cs*:
+hu*:
+nl : -- met geselecteerde VNs --
+
+:_rlist_withrel
+en : -- with selected releases --
+ru : -- с указанными выпусками --
+cs*:
+hu*:
+nl : -- met geselecteerde uitgaven --
+
+:_rlist_changestat
+en : Change status
+ru : Смена статуса
+cs*:
+hu*:
+nl : Verander status
-:_rlist_changerel
-en : Change release status
-ru : Смена статуса выпуска
-cs : Změnit stav vydání
-hu : Kiadás állapotának megváltoztatása
-nl : Verander uitgavestatus
+:_rlist_setnote
+en : Set note
+ru : Установка заметки
+cs*:
+hu*:
+nl : Verander notitie
-:_rlist_changeplay
-en : Change play status
-ru : Смена статуса игры
-cs : Změnit stav hraní
-hu : Játszás állapot megváltoztatása
-nl : Verander speelstatus
+:_rlist_setnote_prompt
+en : Set notes (leave empty to remove note)
+ru : Установка заметок (оставьте поле пустым чтобы стереть заметку)
+cs*:
+hu*:
+nl : Verander notitie (laat leeg om de notitie te verwijderen)
:_rlist_del
en : remove from list
@@ -4938,12 +5008,20 @@ cs : odstranit z listu
hu : eltávolítás a listából
nl : verwijder uit lijst
+# label of the button. "Update!" in the commanding sense
+:_rlist_update
+en : Update
+ru : Обновить
+cs*:
+hu*:
+nl :
+
:_rlist_releasenote
-en : * Obtained/finished/total
-ru : * Приобретено/прочитано/всего
-cs : * Sehnáno/dohráno/celkem
-hu : * Megszerezve/befejezve/összesen
-nl : * In bezit/uitgespeeld/totaal
+en : * Obtained/total
+ru : * Приобретено/всего
+cs : * Sehnáno/celkem
+hu : * Megszerezve/összesen
+nl : * In bezit/totaal
@@ -5374,7 +5452,7 @@ nl : Herhaal wachtwoord
:_register_question
en : How many [index,_1,visual novels,releases,producers] do we have in the database? (Hint: look to your left)
-ru : Сколько [index,_1,новелл,выпусков,компаний] у нас в базе? (Подсказка: посмотри слева)
+ru : Сколько [index,_1,новелл,выпусков,компаний] у нас в базе? (Подсказка: посмотрите слева)
cs : Kolik [index,_1,vizulních novel,vydání,producentů] máme v databázi? (Nápověda: podívejte se doleva)
hu : Mennyi [index,_1,visual novels,releases,producers] van az adatbázisunkba? (Tip: vess egy pillantást balra)
nl : Hoeveel [index,_1,visual novels,uitgaven,producenten] hebben wij in de database? (Tip: zie links van de pagina)
@@ -5481,11 +5559,11 @@ hu : Beállítások
nl : Opties
:_usere_flist
-en : Allow other people to see my visual novel list ([url,_1,_1]) and wishlist ([url,_2,_2])
-ru : Разрешить остальным видеть мой список новелл ([url,_1,_1]) и список желаемого ([url,_2,_2])
-cs : Povolit ostatním zobrazit můj list vizuálních novel ([url,_1,_1]) a wishlist ([url,_2,_2])
-hu : Más embereknek megengedni, hogy lássák a visual novel listám ([url,_1,_1]) és a kívánságlistám ([url,_2,_2])
-nl : Sta andere mensen toe om mijn visual novel lijst ([url,_1,_1]) en wensenlijst ([url,_2,_2]) te zien
+en : Don't allow other people to see my visual novel list ([url,_1,_1]), votes ([url,_2,_2]) and wishlist ([url,_3,_3])
+ru : Запретить другим участникам видеть мой список новелл ([url,_1,_1]), отданные голоса ([url,_2,_2]) и список желаемого ([url,_3,_3])
+cs*:
+hu*:
+nl : Sta andere mensen niet toe om mijn visual novel lijst ([url,_1,_1]), stemmen ([url,_2,_2]) en wensenlijst ([url,_3,_3]) te zien
:_usere_fnsfw
en : Disable warnings for images that are not safe for work.
@@ -5734,7 +5812,7 @@ cs : Nastavení
hu : Beállítások
nl : Instellingen
-:_usern_set_dbedit
+:_usern_set_nodbedit
en : Notify me about edits of database entries I contributed to.
ru : Уведомлять меня о правках записей базы данных, которые я редактировал(а).
cs : Upozorňovat mne na úpravy databáze, ke kterým jsem přispěl/a.
@@ -5811,27 +5889,6 @@ cs : Hodnocení
hu : Értékelés
nl : Waardering
-:_vnbrowse_tagign_title
-en : The following tags were ignored:
-ru : Следующие теги были пропущены:
-cs : Následující tagy byly ignorovány:
-hu : Az allábi címkék nem lettek figyelembe véve:
-nl : De volgende tags zijn genegeerd:
-
-:_vnbrowse_tagign_meta
-en : can't filter on meta tags
-ru : фильтрация по мета-тегам невозможна
-cs : nedají se filtrovat meta tagy
-hu : meta címkéken nem működik a szűrő
-nl : kan niet filteren op metatags
-
-:_vnbrowse_tagign_notfound
-en : no such tag found
-ru : тег не найден
-cs : nenalezeny takové tagy
-hu : nem található ilyen címke
-nl : tag niet gevonden
-
:_vnbrowse_fil_title
en : Visual Novel Filters
ru : Фильтры новелл
@@ -5882,11 +5939,25 @@ hu : Címkék
nl :
:_vnbrowse_booland
-en : boolean and, selecting more gives less results
+en : Boolean and, selecting more gives less results
ru : логическое 'и', чем больше выбрано, тем меньше даёт результатов
-cs : boolean a, výběr více dá méně výsledků
+cs : Boolean a, výběr více dá méně výsledků
hu : Boole féle értékhalmaz(igaz/hamis), s ha többet választasz ki akkor kevesebb találatot ad
-nl : booleaanse 'en', meerdere selecties geven minder resultaten
+nl : Booleaanse 'en', meerdere selecties geven minder resultaten
+
+:_vnbrowse_tagactive
+en : These filters are ignored on tag pages (when set as default).
+ru : Эти фильтры недействительны на страницах тегов (по умолчанию).
+cs*:
+hu*:
+nl : Deze filters worden genegeerd op tagpaginas (als ze gebruikt worden als standaardfilters).
+
+:_vnbrowse_tagrem
+en : remove
+ru : убрать
+cs*:
+hu*:
+nl : verwijder
:_vnbrowse_taginc
en : Tags to include
@@ -5984,7 +6055,7 @@ nl : Originele titel
:_vnedit_original_msg
en : The original title of this visual novel, leave blank if it already is in the Latin alphabet.
-ru : Исходное название данной новеллы, оставьте пустым, если уже набрано в латинском алфавите.
+ru : Исходное название данной новеллы, оставьте пустым, если указано в латинском алфавите.
cs : Originální název této vizuální novely, ponechte prázdné, pokud již je latinkou.
hu : A visual novel eredeti címe, ha latin betűkkel van akkor ne írd be mégegyszer ide.
nl : De originele titel van deze visual novel, laat leeg als deze al in het Latijnse alfabet is.
@@ -6857,6 +6928,41 @@ hu : Te most épp a 10-ből, 10-et adsz ennek a visual novelnek. Ez egy elég ex
nl : Je staat op het punt om deze visual novel een 10 te geven. Dit is een erg extreme stem, en betekent dat dit één van de beste visual novels die je ooit hebt gespeeld is, en dat de kans klein is dat je ooit iets beters zal vinden dan dit.
Het is over het algemeen een slecht idee om meer dan drie spellen in je lijst een 10 te geven, wees zorgvuldig!
+:_vnpage_uopt_vnlisted
+en : VN list: [_1]
+ru : Список новелл: [_1]
+cs*:
+hu*:
+nl : VN-lijst: [_1]
+
+:_vnpage_uopt_novn
+en : not on your VN list
+ru : не в вашем списке
+cs*:
+hu*:
+nl : niet in je VN-lijst
+
+:_vnpage_uopt_changevn
+en : Change status
+ru : Сменить статус
+cs*: Změnit status
+hu*: Állapot megváltoztatássa
+nl : Wijzig status
+
+:_vnpage_uopt_addvn
+en : Add to VN list
+ru : Добавить в список
+cs*:
+hu*:
+nl : Voeg toe aan VN-lijst
+
+:_vnpage_uopt_delvn
+en : remove from VN list
+ru : убрать из списка
+cs*:
+hu*:
+nl : Verwijder van VN-lijst
+
:_vnpage_uopt_wishlisted
en : wishlist: [_1]
ru : список желаемого: [_1]
@@ -6892,26 +6998,12 @@ cs : odstranit z wishlistu
hu : eltávolitás a kivánságlistából
nl : Verwijder van wensenlijst
-:_vnpage_uopt_relrstat
-en : Release status
-ru : Статус выпуска
-cs : Status vydání
-hu : Kiadás állapota
-nl : Uitgavestatus
-
-:_vnpage_uopt_relvstat
-en : Play status
-ru : Статус прохождения
-cs : Herní status
-hu : Játszás állapota
-nl : Speelstatus
-
:_vnpage_uopt_reldel
-en : Remove from VN list
-ru : Убрать из списка новелл
-cs : Odstranit z listu VN
-hu : Eltávolítás a VN listából
-nl : Verwijder van VN lijst
+en : Remove from list
+ru : Убрать из списка
+cs*:
+hu*:
+nl : Verwijder van lijst
:_vnpage_rel
en : Releases
@@ -7083,7 +7175,7 @@ nl : Je bent niet toegestaan om deze actie uit te voeren.
:_denied_noaccess_msg
en : It seems you don't have the proper rights to perform the action you wanted to perform...
-ru : Похоже, у тебя нет прав для выполнения действия, которое ты пытаешься совершить. Короче, сюда нельзя...
+ru : Похоже, у вас нет прав для выполнения действия, которое вы пытаетесь совершить...
cs : Zdá se, že nemáte potřebná práva pro vykonání akce, kterou chcete provést...
hu : Úgy tűnik, hogy nem rendelkezel megfelelő jogokkal, hogy elvégezd azt a műveletet amit szerettél volna...
nl : Het lijkt er op dat je niet genoeg rechten hebt om te doen wat je wilde doen...
diff --git a/data/script.js b/data/script.js
index 4f0e808f..a946a4f3 100644
--- a/data/script.js
+++ b/data/script.js
@@ -41,22 +41,24 @@ var collapsed_icon = '▸';
/* M I N I M A L J A V A S C R I P T L I B R A R Y */
var http_request = false;
-function ajax(url, func) {
- if(http_request)
+function ajax(url, func, async) {
+ if(!async && http_request)
http_request.abort();
- http_request = (window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
- if(http_request == null)
+ var req = (window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
+ if(req == null)
return alert("Your browser does not support the functionality this website requires.");
- http_request.onreadystatechange = function() {
- if(!http_request || http_request.readyState != 4 || !http_request.responseText)
+ if(!async)
+ http_request = req;
+ req.onreadystatechange = function() {
+ if(!req || req.readyState != 4 || !req.responseText)
return;
- if(http_request.status != 200)
+ if(req.status != 200)
return alert('Whoops, error! :(');
- func(http_request);
+ func(req);
};
url += (url.indexOf('?')>=0 ? ';' : '?')+(Math.floor(Math.random()*999)+1);
- http_request.open('GET', url, true);
- http_request.send(null);
+ req.open('GET', url, true);
+ req.send(null);
}
function setCookie(n,v) {
@@ -379,40 +381,32 @@ function ddRefresh() {
function rlDropDown(lnk) {
var relid = lnk.id.substr(6);
- var st = getText(lnk).split(' / ');
- if(st[0].indexOf(mt('_js_loading')) >= 0)
+ var st = getText(lnk);
+ if(st == mt('_js_loading'))
return null;
- var rs = tag('ul', tag('li', tag('b', mt('_vnpage_uopt_relrstat'))));
- var vs = tag('ul', tag('li', tag('b', mt('_vnpage_uopt_relvstat'))));
- for(var i=0; i<rlst_rstat.length; i++) {
- var val = mt('_rlst_rstat_'+rlst_rstat[i]); // l10n /_rlst_rstat_\d+/
- if(st[0] && st[0].indexOf(val) >= 0)
- rs.appendChild(tag('li', tag('i', val)));
+ var o = tag('ul', null);
+ for(var i=0; i<rlist_status.length; i++) {
+ var val = mt('_rlist_status_'+rlist_status[i]); // l10n /_rlist_status_\d+/
+ if(st == val)
+ o.appendChild(tag('li', tag('i', val)));
else
- rs.appendChild(tag('li', tag('a', {href:'#', rl_rid:relid, rl_act:'r'+rlst_rstat[i], onclick:rlMod}, val)));
- }
- for(var i=0; i<rlst_vstat.length; i++) {
- var val = mt('_rlst_vstat_'+rlst_vstat[i]); // l10n /_rlst_vstat_\d+/
- if(st[1] && st[1].indexOf(val) >= 0)
- vs.appendChild(tag('li', tag('i', val)));
- else
- vs.appendChild(tag('li', tag('a', {href:'#', rl_rid:relid, rl_act:'v'+rlst_vstat[i], onclick:rlMod}, val)));
+ o.appendChild(tag('li', tag('a', {href:'#', rl_rid:relid, rl_act:rlist_status[i], onclick:rlMod}, val)));
}
+ if(st != '--')
+ o.appendChild(tag('li', tag('a', {href:'#', rl_rid:relid, rl_act:-1, onclick:rlMod}, mt('_vnpage_uopt_reldel'))));
- return tag('div', {'class':'vrdd'}, rs, vs, st[0] == '--' ? null :
- tag('ul', {'class':'full'}, tag('li', tag('a', {href:'#', rl_rid: relid, rl_act:'del', onclick:rlMod}, mt('_vnpage_uopt_reldel'))))
- );
+ return tag('div', o);
}
function rlMod() {
var lnk = byId('rlsel_'+this.rl_rid);
var code = getText(byId('vnrlist_code'));
+ var act = this.rl_act;
ddHide();
setContent(lnk, tag('b', {'class': 'grayedout'}, mt('_js_loading')));
- ajax('/xml/rlist.xml?formcode='+code+';id='+this.rl_rid+';e='+this.rl_act, function(hr) {
- // TODO: get rid of innerHTML here...
- lnk.innerHTML = hr.responseXML.getElementsByTagName('rlist')[0].firstChild.nodeValue;
+ ajax('/xml/rlist.xml?formcode='+code+';id='+this.rl_rid+';e='+act, function(hr) {
+ setText(lnk, act == -1 ? '--' : mt('_rlist_status_'+act));
});
return false;
}
@@ -1756,7 +1750,7 @@ function filLoad() {
var c = tag('div', null);
for(var i=1; i<l.length; i++) {
// category link
- var a = tag('a', { href: '#', onclick: filSelectCat, fil_num: i }, l[i][0]);
+ var a = tag('a', { href: '#', onclick: filSelectCat, fil_num: i, fil_onshow:[] }, l[i][0]);
p.appendChild(a);
p.appendChild(tag(' '));
@@ -1776,6 +1770,8 @@ function filLoad() {
tag('td', {'class':'cont' }, fd[2]));
if(fd[0])
fil_cats[0][fd[0]] = f;
+ if(fd[5])
+ a.fil_onshow.push([ fd[5], f.fil_contents ]);
t.appendChild(f);
}
c.appendChild(t);
@@ -1791,30 +1787,48 @@ function filLoad() {
tag('b', {'class':'ruler'}, null),
c,
tag('b', {'class':'ruler'}, null),
+ PREF_CODE != '' ? tag('input', {type:'button', 'class':'submit', value: mt('_rbrowse_filsave'), onclick:filSaveDefault }) : null,
tag('input', {type:'button', 'class':'submit', value: mt('_rbrowse_apply'), onclick:function () {
var f = byId('fil');
while(f.nodeName.toLowerCase() != 'form')
f = f.parentNode;
f.submit();
}}),
- tag('input', {type:'button', 'class':'submit', value: mt('_rbrowse_reset'), onclick:function () { byId('fil').value = ''; filDeSerialize()} })
+ tag('input', {type:'button', 'class':'submit', value: mt('_rbrowse_reset'), onclick:function () { byId('fil').value = ''; filDeSerialize()} }),
+ tag('p', {id:'fil_savenote', 'class':'hidden'}, mt('_rbrowse_savenote'))
));
filSelectCat(1);
byId('filselect').onclick = filShow;
filDeSerialize();
}
+function filSaveDefault() {
+ var but = this;
+ but.value = mt('_js_loading');
+ but.enabled = false;
+ setClass(byId('fil_savenote'), 'hidden', false);
+ var type = byId('filselect').href.match(/#r$/) ? 'release' : 'vn';
+ ajax('/xml/prefs.xml?formcode='+PREF_CODE+';key=filter_'+type+';value='+byId('fil').value, function (hr) {
+ but.value = mt('_rbrowse_filsave');
+ but.enable = true;
+ });
+}
+
function filSelectCat(n) {
+ setClass(byId('fil_savenote'), 'hidden', true);
n = this.fil_num ? this.fil_num : n;
for(var i=1; i<fil_cats.length; i++) {
setClass(fil_cats[i], 'optselected', i == n);
setClass(fil_cats[i].fil_t, 'hidden', i != n);
}
+ for(var i=0; i<fil_cats[n].fil_onshow.length; i++)
+ fil_cats[n].fil_onshow[i][0](fil_cats[n].fil_onshow[i][1]);
return false
}
function filSelectField(obj) {
var t = obj && obj.parentNode ? obj : this;
+ setClass(byId('fil_savenote'), 'hidden', true);
// update checkbox and label
var o = t;
while(o.nodeName.toLowerCase() != 'tr')
@@ -1842,8 +1856,8 @@ function filSelectField(obj) {
}
function filSerialize() {
- var l = [];
var num = 0;
+ var values = {};
for(var f in fil_cats[0]) {
if(!byId('fil_check_'+f).checked)
continue;
@@ -1852,19 +1866,24 @@ function filSerialize() {
var v = fil_cats[0][f].fil_readfunc(fil_cats[0][f].fil_contents);
var r = [];
for(var h=0; h<v.length; h++) {
- v[h] = (''+v[h]).split('');
+ var vs = (''+v[h]).split('');
r[h] = '';
// this isn't a very fast escaping method, blame JavaScript for inflexible search/replace support
- for(var i=0; i<v[h].length; i++) {
+ for(var i=0; i<vs.length; i++) {
for(var j=0; j<fil_escape.length; j++)
- if(v[h][i] == fil_escape[j])
+ if(vs[i] == fil_escape[j])
break;
- r[h] += j == fil_escape.length ? v[h][i] : '_'+(j<10?'0'+j:j);
+ r[h] += j == fil_escape.length ? vs[i] : '_'+(j<10?'0'+j:j);
}
}
if(r.length > 0 && r[0] != '')
- l.push(fil_cats[0][f].fil_code+'-'+r.join('~'));
+ values[fil_cats[0][f].fil_code] = r.join('~');
}
+ if(!values['tag_inc'])
+ delete values['tagspoil'];
+ var l = [];
+ for(var f in values)
+ l.push(f+'-'+values[f]);
byId('fil').value = l.join('.');
setText(byName(byId('filselect'), 'i')[1], num > 0 ? ' ('+num+')' : '');
}
@@ -1900,6 +1919,7 @@ function filShow() {
var hid = !hasClass(div, 'hidden');
setClass(div, 'hidden', hid);
setText(byName(byId('filselect'), 'i')[0], hid ? collapsed_icon : expanded_icon);
+ setClass(byId('fil_savenote'), 'hidden', true);
var o = this;
ddx = ddy = 0;
@@ -1971,6 +1991,89 @@ function filFOptions(c, n, opts, setfunc) {
];
}
+function filFTagInput(name, label) {
+ var visible = false;
+ var remove = function() {
+ ;
+ };
+ var addtag = function(ul, id, name) {
+ ul.appendChild(tag('li', { fil_id: id },
+ tag('a', {href:'/g'+id}, name||'g'+id),
+ ' (', tag('a', {href:'#',
+ onclick:function () {
+ // a -> li -> ul -> div
+ var ul = this.parentNode.parentNode;
+ ul.removeChild(this.parentNode);
+ filSelectField(ul.parentNode);
+ return false
+ }
+ }, mt('_vnbrowse_tagrem')), ')'
+ ));
+ }
+ var fetch = function(c) {
+ var v = c.fil_val;
+ var ul = byName(c, 'ul')[0];
+ var txt = byName(c, 'input')[0];
+ if(v == null)
+ return;
+ if(!v[0]) {
+ setText(ul, '');
+ txt.disabled = false;
+ txt.value = '';
+ return;
+ }
+ if(!visible)
+ setText(ul, '');
+ var q = [];
+ for(var i=0; i<v.length; i++) {
+ q.push('id='+v[i]);
+ if(!visible)
+ addtag(ul, v[i]);
+ }
+ txt.value = mt('_js_loading');
+ txt.disabled = true;
+ if(visible)
+ ajax('/xml/tags.xml?'+q.join(';'), function (hr) {
+ var l = [];
+ var items = hr.responseXML.getElementsByTagName('item');
+ setText(ul, '');
+ for(var i=0; i<items.length; i++)
+ addtag(ul, items[i].getAttribute('id'), items[i].firstChild.nodeValue);
+ txt.value = '';
+ txt.disabled = false;
+ c.fil_val = null;
+ }, 1);
+ };
+ var input = tag('input', {type:'text', 'class':'text', style:'width:300px', onfocus:filSelectField});
+ var list = tag('ul', null);
+ dsInit(input, '/xml/tags.xml?q=',
+ function(item, tr) {
+ tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40),
+ item.getAttribute('meta') == 'yes' ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_meta')) : null,
+ item.getAttribute('state') == 0 ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_mod')) : null
+ ));
+ },
+ function(item, obj) {
+ addtag(byName(obj.parentNode, 'ul')[0], item.getAttribute('id'), item.firstChild.nodeValue);
+ filSelectField(obj);
+ return '';
+ },
+ function(o) { filSelectField(o); false }
+ );
+
+ return [
+ name, label, tag('div', list, input),
+ function(c) {
+ var v = []; var l = byName(c, 'li');
+ for(var i=0; i<l.length; i++)
+ v.push(l[i].fil_id);
+ return v;
+ },
+ function(c,v) { c.fil_val = v; fetch(c) },
+ function(c) { visible = true; fetch(c); }
+ ];
+}
+
function filReleases() {
var types = release_types;
for(var i=0; i<types.length; i++) // l10n /_rtype_.+/
@@ -2028,28 +2131,6 @@ function filVN() {
for(var i=0; i<len.length; i++) // l10n /_vnlength_.+/
len[i] = [ len[i], mt('_vnlength_'+len[i]) ];
- // tag include/exclude dropdown search
- var taginc = tag('input', {type:'text', 'class':'text', style:'width:350px', onfocus:filSelectField});
- var tagexc = tag('input', {type:'text', 'class':'text', style:'width:350px', onfocus:filSelectField});
- var trfunc = function(item, tr) {
- tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40),
- item.getAttribute('meta') == 'yes' ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_meta')) : null,
- item.getAttribute('state') == 0 ? tag('b', {'class': 'grayedout'}, ' '+mt('_js_ds_tag_mod')) : null
- ));
- };
- var serfunc = function(item, obj) {
- var tags = obj.value.split(/ *, */);
- tags[tags.length-1] = item.firstChild.nodeValue;
- filSelectField(obj);
- return tags.join(', ');
- };
- var retfunc = function(o) { filSelectField(o); false };
- var parfunc = function(val) { return (val.split(/, */))[val.split(/, */).length-1]; };
- var readfunc = function(c) { return c.value.split(/, */) };
- var writefunc = function(c,v) { c.value = v.join(', ') };
- dsInit(taginc, '/xml/tags.xml?q=', trfunc, serfunc, retfunc, parfunc);
- dsInit(tagexc, '/xml/tags.xml?q=', trfunc, serfunc, retfunc, parfunc);
-
return [
mt('_vnbrowse_fil_title'),
[ mt('_vnbrowse_general'),
@@ -2057,9 +2138,10 @@ function filVN() {
filFOptions('hasani', mt('_vnbrowse_anime'), [[1, mt('_vnbrowse_anime_yes')],[0, mt('_vnbrowse_anime_no')]])
],
[ mt('_vnbrowse_tags'),
- [ '', ' ', tag('('+mt('_vnbrowse_booland')+')') ],
- [ 'taginc', mt('_vnbrowse_taginc'), taginc, readfunc, writefunc ],
- [ 'tagexc', mt('_vnbrowse_tagexc'), tagexc, readfunc, writefunc ],
+ [ '', ' ', tag(mt('_vnbrowse_booland')) ],
+ [ '', ' ', PREF_CODE != '' ? tag(mt('_vnbrowse_tagactive')) : null ],
+ filFTagInput('tag_inc', mt('_vnbrowse_taginc')),
+ filFTagInput('tag_exc', mt('_vnbrowse_tagexc')),
filFOptions('tagspoil', ' ', [[0, mt('_vnbrowse_spoil0')],[1, mt('_vnbrowse_spoil1')],[2, mt('_vnbrowse_spoil2')]],
function (o) { var s = getCookie('tagspoil'); if(o+'' == '') return s == null ? 0 : s; setCookie('tagspoil', o); return o})
],
@@ -2161,7 +2243,7 @@ if(byId('wishsel')) {
};
}
-// Release list dropdown box (/r+)
+// Release & VN list dropdown box (/r+ and /v+)
if(byId('listsel')) {
byId('listsel').onchange = function() {
if(this.selectedIndex != 0)
@@ -2204,7 +2286,7 @@ if(byId('jt_box_rel_geninfo')) {
byId('patch').onclick = func;
}
-// Batch edit wishlist dropdown box (/u+/wish)
+// Batch edit dropdown box (/u+/wish and /u+/votes)
if(byId('batchedit')) {
byId('batchedit').onchange = function() {
if(this.selectedIndex == 0)
@@ -2216,7 +2298,7 @@ if(byId('batchedit')) {
};
}
-// collapse/expand row groups (/u+/list) (limited to one table on a page)
+// collapse/expand row groups (/u+/list)
if(byId('expandall')) {
var table = byId('expandall');
while(table.nodeName.toLowerCase() != 'table')
@@ -2227,11 +2309,14 @@ if(byId('expandall')) {
var alltoggle = function() {
allhid = !allhid;
var l = byClass(table, 'tr', 'collapse');
- for(var i=0; i<l.length; i++)
+ for(var i=0; i<l.length; i++) {
setClass(l[i], 'hidden', allhid);
- setText(byName(byId('expandall'), 'i')[0], allhid ? collapsed_icon : expanded_icon);
+ var sel = byName(l[i], 'input')[0];
+ if(sel) setClass(sel, 'hidden', allhid);
+ }
+ setText(byId('expandall'), allhid ? collapsed_icon : expanded_icon);
for(var i=0; i<heads.length; i++)
- setText(byName(heads[i], 'i')[0], allhid ? collapsed_icon : expanded_icon);
+ setText(heads[i], allhid ? collapsed_icon : expanded_icon);
return false;
}
byId('expandall').onclick = alltoggle;
@@ -2242,14 +2327,27 @@ if(byId('expandall')) {
if(l.length < 1)
return;
var hid = !hasClass(l[0], 'hidden');
- for(var i=0; i<l.length; i++)
+ for(var i=0; i<l.length; i++) {
setClass(l[i], 'hidden', hid);
- setText(byName(this, 'i')[0], hid ? collapsed_icon : expanded_icon);
+ var sel = byName(l[i], 'input')[0];
+ if(sel) setClass(sel, 'hidden', hid);
+ }
+ setText(this, hid ? collapsed_icon : expanded_icon);
};
for(var i=0; i<heads.length; i++)
heads[i].onclick = singletoggle;
}
+
+// set note input box (/u+/list)
+if(byId('not') && byId('vns'))
+ byId('vns').onchange = function () {
+ if(this.options[this.selectedIndex].value == 999)
+ byId('not').value = prompt(mt('_rlist_setnote_prompt'), '');
+ return true;
+ };
+
+
// expand/collapse release listing (/p+)
if(byId('expandprodrel')) {
var lnk = byId('expandprodrel');
@@ -2290,7 +2388,7 @@ if(byId('lang_select')) {
var f = function() {
var l = byName('input');
for(var i=0; i<l.length; i++)
- if(l[i].type == this.type && l[i].name == this.name)
+ if(l[i].type == this.type && l[i].name == this.name && !hasClass(l[i], 'hidden'))
l[i].checked = this.checked;
};
var l = byClass('input', 'checkall');
diff --git a/data/style.css b/data/style.css
index 6c043caf..b7fbaffa 100644
--- a/data/style.css
+++ b/data/style.css
@@ -322,53 +322,36 @@ b.future, b.standout, a.standout {
/***** main tabs *****/
-#maincontent ul.maintabs {
- display: inline;
- margin: 0;
-}
-#maincontent ul.maintabs.notfirst {
- display: block;
- height: 20px;
-}
-#maincontent ul.maintabs li {
- display: inline;
- list-style-type: none;
-}
-#maincontent ul.maintabs li a {
+ul.maintabs { display: inline; margin: 0; }
+ul.maintabs.notfirst { display: block; height: 20px }
+ul.maintabs li { display: inline; list-style-type: none }
+ul.maintabs li a, ul.maintabs li b {
float: right;
display: block;
height: 14px;
- border: 1px solid $border$;
- border-bottom: none;
padding: 1px 7px 5px 7px;
margin: 0 0 0 10px;
- background-color: $tabbg$;
}
-#maincontent ul.maintabs.notfirst li a {
- margin-top: 20px;
-}
-#maincontent ul.maintabs.bottom li a {
- margin-top: 10px;
- border-bottom: 1px solid $border$;
- border-top: none;
- padding: 4px 7px 2px 7px;
-}
-#maincontent ul.maintabs li.left a {
- float: left;
- margin-left: 0;
- margin-right: 10px;
-}
-#maincontent ul.maintabs li.tabselected a,
-#maincontent ul.maintabs li a:hover {
- background-color: $_blendbg$;
- padding-bottom: 6px;
-}
-#maincontent ul.maintabs.bottom li.tabselected a,
-#maincontent ul.maintabs.bottom li a:hover {
- padding-bottom: 2px;
- padding-top: 5px;
- margin-top: 9px;
+ul.maintabs li a {
+ border: 1px solid $border$;
+ border-bottom: none;
+ background-color: $tabbg$;
}
+ul.maintabs.notfirst li a,
+ul.maintabs.notfirst li b { margin-top: 20px }
+ul.maintabs.bottom li a,
+ul.maintabs.bottom li b { margin-top: 10px; padding: 4px 7px 2px 7px }
+ul.maintabs.bottom li a { border-bottom: 1px solid $border$; border-top: none }
+ul.maintabs li.left a,
+ul.maintabs li.left b { float: left; margin-left: 0; margin-right: 10px }
+ul.maintabs li b { margin-left: -2px; margin-right: -7px }
+ul.maintabs li.left b { margin-left: -7px; margin-right: -2px }
+ul.maintabs li.tabselected a,
+ul.maintabs li a:hover { background-color: $_blendbg$; padding-bottom: 6px }
+ul.maintabs.bottom li.tabselected a,
+ul.maintabs.bottom li a:hover { padding-bottom: 2px; padding-top: 5px; margin-top: 9px }
+ul.maintabs.browsetabs li a { margin-left: 5px }
+ul.maintabs.browsetabs li.left a { margin-left: 0; margin-right: 5px }
@@ -660,7 +643,7 @@ div#vntags {
width: 90px;
}
.releases td.tc5 {
- width: 140px;
+ width: 70px;
}
.releases td.tc5 a {
color: $maintext$!important;
@@ -697,10 +680,6 @@ a.addnew {
#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 }
-#dd_box .vrdd { width: 180px; }
-#dd_box .vrdd ul { float: left; width: 90px; }
-#dd_box .vrdd ul.full { width: 180px; text-align: center; }
-#dd_box .vrdd ul.full li a { padding: 0 }
@@ -891,7 +870,7 @@ div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px
/* vote lists */
-div.votelist td.tc1 { width: 70px }
+div.votelist td.tc1 { width: 80px; padding-top: 0; padding-bottom: 0 }
div.votelist td.tc2 { width: 50px; text-align: right; padding-right: 10px }
@@ -905,15 +884,16 @@ div.votelist td.tc2 { width: 50px; text-align: right; padding-right: 10px }
/***** User VN list browser ******/
#expandall, .collapse_but { cursor: pointer }
-#expandall i, .collapse_but i { font-style: normal }
-.browse.rlist .tc2 { width: 100px; }
-.browse.rlist .tc3 { width: 70px; }
-.browse.rlist .relhid .tc1 { padding-left: 40px; width: 70px; }
-.browse.rlist .relhid .tc1.own { padding: 0 0 0 25px; width: 95px }
-.browse.rlist .relhid input { margin-right: 5px }
-.browse.rlist .relhid .tc2 { padding: 0; width: 30px; }
-.browse.rlist .relhid .tc3 { width: auto }
-.browse.rlist .relhid .tc4 { text-align: right }
+.browse.rlist .tc1 { width: 16px; padding-bottom: 0 }
+.browse.rlist .tc2 { width: 16px; padding-bottom: 0 }
+.browse.rlist .tc3 { width: 60px }
+.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 }
/***** User notifications *****/
@@ -1137,6 +1117,8 @@ div#fil_div {
#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$ }
diff --git a/lib/Multi/API.pm b/lib/Multi/API.pm
index 4872acf9..3f1b24c4 100644
--- a/lib/Multi/API.pm
+++ b/lib/Multi/API.pm
@@ -648,7 +648,7 @@ sub get_release_res {
if(grep /details/, @{$get->{info}}) {
$_->{website} ||= undef;
$_->{notes} ||= undef;
- $_->{minage} *= 1 if defined $_->{minage};
+ $_->{minage} = $_->{minage} < 0 ? undef : $_->{minage}*1;
$_->{gtin} ||= undef;
$_->{catalog} ||= undef;
}
diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm
index b0fb9a89..ffffb2a6 100644
--- a/lib/VNDB/DB/Releases.pm
+++ b/lib/VNDB/DB/Releases.pm
@@ -32,6 +32,7 @@ sub dbReleaseGet {
defined $o{type} ? ( 'rr.type = ?' => $o{type} ) : (),
defined $o{date_before} ? ( 'rr.released <= ?' => $o{date_before} ) : (),
defined $o{date_after} ? ( 'rr.released >= ?' => $o{date_after} ) : (),
+ defined $o{minage} ? ( 'rr.minage IN(!l)' => [ ref $o{minage} ? $o{minage} : [$o{minage}] ] ) : (),
defined $o{resolution} ? ( 'rr.resolution IN(!l)' => [ ref $o{resolution} ? $o{resolution} : [$o{resolution}] ] ) : (),
defined $o{voiced} ? ( 'rr.voiced IN(!l)' => [ ref $o{voiced} ? $o{voiced} : [$o{voiced}] ] ) : (),
defined $o{ani_story} ? ( 'rr.ani_story IN(!l)' => [ ref $o{ani_story} ? $o{ani_story} : [$o{ani_story}] ] ) : (),
@@ -47,16 +48,6 @@ sub dbReleaseGet {
'rr.id IN(SELECT irm.rid FROM releases_media irm JOIN releases ir ON ir.latest = irm.rid WHERE irm.medium IN(!l))' => [ ref $o{med} ? $o{med} : [ $o{med} ] ] ) : (),
);
- # TODO: don't allow NULL for rr.minage after all, since this could be a lot easier...
- if(exists $o{minage}) {
- my @m = ref $o{minage} ? @{$o{minage}} : ($o{minage});
- my @w = (
- grep(!defined $_ || $_ == -1, @m) ? 'rr.minage IS NULL' : (),
- grep(defined $_ && $_ != -1, @m) ? 'rr.minage IN(!s)' : ()
- );
- push @where, '('.join(' OR ', @w).')', [ grep defined $_ && $_ != -1, @m ];
- }
-
if($o{search}) {
for (split /[ -,._]/, $o{search}) {
s/%//g;
diff --git a/lib/VNDB/DB/Tags.pm b/lib/VNDB/DB/Tags.pm
index 4a87713b..b3e16960 100644
--- a/lib/VNDB/DB/Tags.pm
+++ b/lib/VNDB/DB/Tags.pm
@@ -24,7 +24,7 @@ sub dbTagGet {
my %where = (
$o{id} ? (
- 't.id = ?' => $o{id} ) : (),
+ 't.id IN(!l)' => [ ref $o{id} ? $o{id} : [$o{id}] ] ) : (),
$o{noid} ? (
't.id <> ?' => $o{noid} ) : (),
$o{name} ? (
diff --git a/lib/VNDB/DB/ULists.pm b/lib/VNDB/DB/ULists.pm
index e52cbf37..e6b6419a 100644
--- a/lib/VNDB/DB/ULists.pm
+++ b/lib/VNDB/DB/ULists.pm
@@ -7,56 +7,62 @@ use Exporter 'import';
our @EXPORT = qw|
- dbVNListGet dbVNListList dbVNListAdd dbVNListDel
+ dbRListGet dbVNListGet dbVNListList dbVNListAdd dbVNListDel dbRListAdd dbRListDel
dbVoteGet dbVoteStats dbVoteAdd dbVoteDel
dbWishListGet dbWishListAdd dbWishListDel
|;
-# Simpler and more efficient version of dbVNListList below
-# %options->{ uid rid }
-sub dbVNListGet {
+# Options: uid rid
+sub dbRListGet {
my($self, %o) = @_;
my %where = (
'uid = ?' => $o{uid},
- $o{rid} && !ref $o{rid} ? (
- 'rid = ?' => $o{rid} ) : (),
- $o{rid} && ref $o{rid} ? (
- 'rid IN(!l)' => [$o{rid}] ) : (),
+ $o{rid} ? ('rid IN(!l)' => [ ref $o{rid} ? $o{rid} : [$o{rid}] ]) : (),
);
return $self->dbAll(q|
- SELECT uid, rid, rstat, vstat
+ SELECT uid, rid, status
FROM rlists
!W|,
\%where
);
}
+# Options: uid vid
+sub dbVNListGet {
+ my($self, %o) = @_;
-# %options->{ uid char voted page results sort reverse }
+ my %where = (
+ 'uid = ?' => $o{uid},
+ $o{vid} ? ('vid IN(!l)' => [ ref $o{vid} ? $o{vid} : [$o{vid}] ]) : (),
+ );
+
+ return $self->dbAll(q|
+ SELECT uid, vid, status
+ FROM vnlists
+ !W|,
+ \%where
+ );
+}
+
+
+# Options: uid char voted page results sort reverse
# sort: title vote
-# NOTE: this function is mostly copied from 1.x, may need some rewriting...
sub dbVNListList {
my($self, %o) = @_;
-
$o{results} ||= 50;
$o{page} ||= 1;
- $o{voted} ||= 0; # -1: only non-voted, 0: all, 1: only voted
-
- # construct the global WHERE clause
- my $where = $o{voted} != -1 ? 'vo.vote IS NOT NULL' : '';
- $where .= ($where?' OR ':'').q|v.id = ANY(ARRAY(
- SELECT irv.vid
- FROM rlists irl
- JOIN releases ir ON ir.id = irl.rid
- JOIN releases_vn irv ON irv.rid = ir.latest
- WHERE uid = ?
- ))| if $o{voted} != 1;
- $where = '('.$where.') AND LOWER(SUBSTR(vr.title, 1, 1)) = \''.$o{char}.'\'' if $o{char};
- $where = '('.$where.') AND (ASCII(vr.title) < 97 OR ASCII(vr.title) > 122) AND (ASCII(vr.title) < 65 OR ASCII(vr.title) > 90)' if defined $o{char} && !$o{char};
- $where = '('.$where.') AND vo.vote IS NULL' if $o{voted} == -1;
+
+ my %where = (
+ 'vl.uid = ?' => $o{uid},
+ defined($o{voted}) ? ('vo.vote !s NULL' => $o{voted} ? 'IS NOT' : 'IS') : (),
+ defined($o{status})? ('vl.status = ?' => $o{status}) : (),
+ $o{char} ? ('LOWER(SUBSTR(vr.title, 1, 1)) = ?' => $o{char} ) : (),
+ defined $o{char} && !$o{char} ? (
+ '(ASCII(vr.title) < 97 OR ASCII(vr.title) > 122) AND (ASCII(vr.title) < 65 OR ASCII(vr.title) > 90)' => 1 ) : (),
+ );
my $order = sprintf {
title => 'vr.title %s',
@@ -65,14 +71,14 @@ sub dbVNListList {
# execute query
my($r, $np) = $self->dbPage(\%o, qq|
- SELECT vr.vid, vr.title, vr.original, COALESCE(vo.vote, 0) AS vote
- FROM vn v
+ SELECT vr.vid, vr.title, vr.original, vl.status, vl.notes, COALESCE(vo.vote, 0) AS vote
+ FROM vnlists vl
+ JOIN vn v ON v.id = vl.vid
JOIN vn_rev vr ON vr.id = v.latest
- !s JOIN votes vo ON vo.vid = v.id AND vo.uid = ?
- WHERE $where
+ LEFT JOIN votes vo ON vo.vid = vl.vid AND vo.uid = vl.uid
+ !W
ORDER BY !s|,
- $o{voted} == 1 ? '' : 'LEFT', $o{uid}, # JOIN if we only want votes, LEFT JOIN if we also want rlist items
- $o{voted} != 1 ? $o{uid} : (), $order
+ \%where, $order
);
# fetch releases and link to VNs
@@ -83,7 +89,7 @@ sub dbVNListList {
} @$r;
my $rel = $self->dbAll(q|
- SELECT rv.vid, rr.rid, r.latest, rr.title, rr.original, rr.released, rr.type, rl.rstat, rl.vstat
+ SELECT rv.vid, rr.rid, r.latest, rr.title, rr.original, rr.released, rr.type, rl.status
FROM rlists rl
JOIN releases r ON rl.rid = r.id
JOIN releases_rev rr ON rr.id = r.latest
@@ -114,35 +120,57 @@ sub dbVNListList {
}
-# %options->{ uid rid rstat vstat }
+# Arguments: uid vid status notes
+# vid can be an arrayref only when the rows are already present, in which case an update is done
+# status and notes can be undef when an update is done, in which case these fields aren't updated
sub dbVNListAdd {
- my($self, %o) = @_;
+ my($self, $uid, $vid, $stat, $notes) = @_;
+ $self->dbExec(
+ 'UPDATE vnlists !H WHERE uid = ? AND vid IN(!l)',
+ {defined($stat) ? ('status = ?' => $stat ):(),
+ defined($notes)? ('notes = ?' => $notes):()},
+ $uid, ref($vid) ? $vid : [ $vid ]
+ )
+ ||
+ $self->dbExec(
+ 'INSERT INTO vnlists (uid, vid, status, notes) VALUES(?, ?, ?, ?)',
+ $uid, $vid, $stat||0, $notes||''
+ );
+}
- my %s = (
- defined $o{rstat} ? ( 'rstat = ?', $o{rstat} ) : (),
- defined $o{vstat} ? ( 'vstat = ?', $o{vstat} ) : (),
+
+# Arguments: uid, vid
+sub dbVNListDel {
+ my($self, $uid, $vid) = @_;
+ $self->dbExec(
+ 'DELETE FROM vnlists WHERE uid = ? AND vid IN(!l)',
+ $uid, ref($vid) ? $vid : [ $vid ]
);
- $o{rstat}||=0;
- $o{vstat}||=0;
+}
+
+# Arguments: uid rid status
+# rid can be an arrayref only when the rows are already present, in which case an update is done
+sub dbRListAdd {
+ my($self, $uid, $rid, $stat) = @_;
$self->dbExec(
- 'UPDATE rlists !H WHERE uid = ? AND rid IN(!l)',
- \%s, $o{uid}, ref($o{rid}) eq 'ARRAY' ? $o{rid} : [ $o{rid} ]
+ 'UPDATE rlists SET status = ? WHERE uid = ? AND rid IN(!l)',
+ $stat, $uid, ref($rid) ? $rid : [ $rid ]
)
||
$self->dbExec(
- 'INSERT INTO rlists (uid, rid, rstat, vstat) VALUES(!l)',
- [@o{qw| uid rid rstat vstat |}]
+ 'INSERT INTO rlists (uid, rid, status) VALUES(?, ?, ?)',
+ $uid, $rid, $stat
);
}
# Arguments: uid, rid
-sub dbVNListDel {
+sub dbRListDel {
my($self, $uid, $rid) = @_;
$self->dbExec(
'DELETE FROM rlists WHERE uid = ? AND rid IN(!l)',
- $uid, ref($rid) eq 'ARRAY' ? $rid : [ $rid ]
+ $uid, ref($rid) ? $rid : [ $rid ]
);
}
@@ -160,8 +188,14 @@ sub dbVoteGet {
my %where = (
$o{uid} ? ( 'n.uid = ?' => $o{uid} ) : (),
$o{vid} ? ( 'n.vid = ?' => $o{vid} ) : (),
- $o{hide} ? ( 'u.show_list = TRUE' => 1 ) : (),
+ $o{hide} ? ( 'NOT EXISTS(SELECT 1 FROM users_prefs WHERE uid = n.uid AND key = \'hide_list\')' => 1 ) : (),
$o{hide_ign} ? ( '(NOT u.ign_votes OR u.id = ?)' => $self->authInfo->{id}||0 ) : (),
+ $o{vn_char} ? ( 'LOWER(SUBSTR(vr.title, 1, 1)) = ?' => $o{vn_char} ) : (),
+ defined $o{vn_char} && !$o{vn_char} ? (
+ '(ASCII(vr.title) < 97 OR ASCII(vr.title) > 122) AND (ASCII(vr.title) < 65 OR ASCII(vr.title) > 90)' => 1 ) : (),
+ $o{user_char} ? ( 'LOWER(SUBSTR(u.username, 1, 1)) = ?' => $o{user_char} ) : (),
+ defined $o{user_char} && !$o{user_char} ? (
+ '(ASCII(u.username) < 97 OR ASCII(u.username) > 122) AND (ASCII(u.username) < 65 OR ASCII(u.username) > 90)' => 1 ) : (),
);
my @select = (
@@ -221,14 +255,15 @@ sub dbVoteStats {
# Adds a new vote or updates an existing one
# Arguments: vid, uid, vote
+# vid can be an arrayref only when the rows are already present, in which case an update is done
sub dbVoteAdd {
my($self, $vid, $uid, $vote) = @_;
$self->dbExec(q|
UPDATE votes
- SET vote = ?
- WHERE vid = ?
+ SET vote = ?, date = NOW()
+ WHERE vid IN(!l)
AND uid = ?|,
- $vote, $vid, $uid
+ $vote, ref($vid) ? $vid : [$vid], $uid
) || $self->dbExec(q|
INSERT INTO votes
(vid, uid, vote)
@@ -239,10 +274,11 @@ sub dbVoteAdd {
# Arguments: uid, vid
+# vid can be an arrayref
sub dbVoteDel {
my($self, $uid, $vid) = @_;
$self->dbExec('DELETE FROM votes !W',
- { 'vid = ?' => $vid, 'uid = ?' => $uid }
+ { 'vid IN(!l)' => [ref($vid)?$vid:[$vid]], 'uid = ?' => $uid }
);
}
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm
index 7440f495..bd7db201 100644
--- a/lib/VNDB/DB/Users.pm
+++ b/lib/VNDB/DB/Users.pm
@@ -6,14 +6,14 @@ use warnings;
use Exporter 'import';
our @EXPORT = qw|
- dbUserGet dbUserEdit dbUserAdd dbUserDel
+ dbUserGet dbUserEdit dbUserAdd dbUserDel dbUserPrefSet
dbSessionAdd dbSessionDel dbSessionUpdateLastUsed
dbNotifyGet dbNotifyMarkRead dbNotifyRemove
|;
# %options->{ username passwd mail session uid ip registered search results page what sort reverse }
-# what: notifycount stats extended
+# what: notifycount stats extended prefs hide_list
# sort: username registered votes changes tags
sub dbUserGet {
my $s = shift;
@@ -21,6 +21,7 @@ sub dbUserGet {
page => 1,
results => 10,
what => '',
+ sort => '',
@_
);
@@ -51,12 +52,13 @@ sub dbUserGet {
);
my @select = (
- qw|id username c_votes c_changes show_list c_tags|,
+ qw|id username c_votes c_changes c_tags|,
q|extract('epoch' from registered) as registered|,
$o{what} =~ /extended/ ? (
- qw|mail rank salt skin customcss show_nsfw ign_votes notify_dbedit notify_announce|,
+ qw|mail rank salt ign_votes|,
q|encode(passwd, 'hex') AS passwd|
) : (),
+ $o{what} =~ /hide_list/ ? 'up.value AS hide_list' : (),
$o{what} =~ /notifycount/ ?
'(SELECT COUNT(*) FROM notifications WHERE uid = u.id AND read IS NULL) AS notifycount' : (),
$o{what} =~ /stats/ ? (
@@ -72,12 +74,14 @@ sub dbUserGet {
my @join = (
$o{session} ? 'JOIN sessions s ON s.uid = u.id' : (),
+ $o{what} =~ /hide_list/ || $o{sort} eq 'votes' ?
+ "LEFT JOIN users_prefs up ON up.uid = u.id AND up.key = 'hide_list'" : (),
);
my $order = sprintf {
username => 'u.username %s',
registered => 'u.registered %s',
- votes => 'NOT u.show_list, u.c_votes %s',
+ votes => 'up.value NULLS FIRST, u.c_votes %s',
changes => 'u.c_changes %s',
tags => 'u.c_tags %s',
}->{ $o{sort}||'username' }, $o{reverse} ? 'DESC' : 'ASC';
@@ -90,6 +94,20 @@ sub dbUserGet {
ORDER BY !s|,
join(', ', @select), join(' ', @join), \%where, $order
);
+
+ if(@$r && $o{what} =~ /prefs/) {
+ my %r = map {
+ $r->[$_]{prefs} = {};
+ ($r->[$_]{id}, $r->[$_])
+ } 0..$#$r;
+
+ $r{$_->{uid}}{prefs}{$_->{key}} = $_->{value} for (@{$s->dbAll(q|
+ SELECT uid, key, value
+ FROM users_prefs
+ WHERE uid IN(!l)|,
+ [ keys %r ]
+ )});
+ }
return wantarray ? ($r, $np) : $r;
}
@@ -100,7 +118,7 @@ sub dbUserEdit {
my %h;
defined $o{$_} && ($h{$_.' = ?'} = $o{$_})
- for (qw| username mail rank show_nsfw show_list skin customcss salt ign_votes notify_dbedit notify_announce |);
+ for (qw| username mail rank salt ign_votes |);
$h{'passwd = decode(?, \'hex\')'} = $o{passwd}
if defined $o{passwd};
@@ -127,6 +145,15 @@ sub dbUserDel {
}
+# uid, key, val
+sub dbUserPrefSet {
+ my($s, $uid, $key, $val) = @_;
+ !$val ? $s->dbExec('DELETE FROM users_prefs WHERE uid = ? AND key = ?', $uid, $key)
+ : $s->dbExec('UPDATE users_prefs SET value = ? WHERE uid = ? AND key = ?', $val, $uid, $key)
+ || $s->dbExec('INSERT INTO users_prefs (uid, key, value) VALUES (?, ?, ?)', $uid, $key, $val);
+}
+
+
# Adds a session to the database
# uid, 40 character session token
sub dbSessionAdd {
diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm
index d25a5796..2a7f2477 100644
--- a/lib/VNDB/DB/VN.pm
+++ b/lib/VNDB/DB/VN.pm
@@ -10,7 +10,8 @@ use Encode 'decode_utf8';
our @EXPORT = qw|dbVNGet dbVNRevisionInsert dbVNImageId dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|;
-# Options: id, rev, char, search, length, lang, olang, plat, tags_include, tags_exclude, hasani, results, page, what, sort, reverse
+# Options: id, rev, char, search, length, lang, olang, plat, tag_inc, tag_exc, tagspoil,
+# hasani, hasshot, results, page, what, sort, reverse
# What: extended anime relations screenshots relgraph rating ranking changes
# Sort: id rel pop rating title tagscore rand
sub dbVNGet {
@@ -19,6 +20,12 @@ sub dbVNGet {
$o{page} ||= 1;
$o{what} ||= '';
$o{sort} ||= 'title';
+ $o{tagspoil} //= 2;
+
+ # user input that is literally added to the query should be checked...
+ die "Invalid input for tagspoil or tag_inc at dbVNGet()\n" if
+ grep !defined($_) || $_!~/^\d+$/, $o{tagspoil},
+ !$o{tag_inc} ? () : (ref($o{tag_inc}) ? @{$o{tag_inc}} : $o{tag_inc});
my @where = (
$o{id} ? (
@@ -39,19 +46,20 @@ sub dbVNGet {
'('.join(' OR ', map "v.c_platforms ILIKE '%%$_%%'", ref $o{plat} ? @{$o{plat}} : $o{plat}).')' => 1 ) : (),
defined $o{hasani} ? (
'!sEXISTS(SELECT 1 FROM vn_anime va WHERE va.vid = vr.id)' => [ $o{hasani} ? '' : 'NOT ' ]) : (),
- $o{tags_include} && @{$o{tags_include}} ? (
+ defined $o{hasshot} ? (
+ '!sEXISTS(SELECT 1 FROM vn_screenshots vs WHERE vs.vid = vr.id)' => [ $o{hasshot} ? '' : 'NOT ' ]) : (),
+ $o{tag_inc} ? (
'v.id IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l) AND spoiler <= ? GROUP BY vid HAVING COUNT(tag) = ?)',
- [ $o{tags_include}[1], $o{tags_include}[0], $#{$o{tags_include}[1]}+1 ]
- ) : (),
- $o{tags_exclude} && @{$o{tags_exclude}} ? (
- 'v.id NOT IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l))' => [ $o{tags_exclude} ] ) : (),
+ [ ref $o{tag_inc} ? $o{tag_inc} : [$o{tag_inc}], $o{tagspoil}, ref $o{tag_inc} ? $#{$o{tag_inc}}+1 : 1 ]) : (),
+ $o{tag_exc} ? (
+ 'v.id NOT IN(SELECT vid FROM tags_vn_inherit WHERE tag IN(!l))' => [ ref $o{tag_exc} ? $o{tag_exc} : [$o{tag_exc}] ] ) : (),
$o{search} ? (
map +('v.c_search like ?', "%$_%"), normalize_query($o{search})) : (),
# don't fetch hidden items unless we ask for an ID
!$o{id} && !$o{rev} ? (
'v.hidden = FALSE' => 0 ) : (),
# optimize fetching random entries (only when there are no other filters present, otherwise this won't work well)
- $o{sort} eq 'rand' && $o{results} <= 10 && !grep(!/^(?:results|page|what|sort)$/, keys %o) ? (
+ $o{sort} eq 'rand' && $o{results} <= 10 && !grep(!/^(?:results|page|what|sort|tagspoil)$/, keys %o) ? (
sprintf 'v.id IN(SELECT floor(random() * last_value)::integer
FROM generate_series(1,20), (SELECT last_value FROM vn_id_seq) s1
LIMIT 20)' ) : (),
@@ -69,7 +77,7 @@ sub dbVNGet {
'JOIN relgraphs vg ON vg.id = v.rgraph' : (),
);
- my $tag_ids = $o{tags_include} && join ',', @{$o{tags_include}[1]};
+ my $tag_ids = $o{tag_inc} && join ',', ref $o{tag_inc} ? @{$o{tag_inc}} : $o{tag_inc};
my @select = ( # see https://rt.cpan.org/Ticket/Display.html?id=54224 for the cast on c_languages
qw|v.id v.locked v.hidden v.c_released v.c_languages::text[] v.c_platforms vr.title vr.original v.rgraph|, 'vr.id AS cid',
$o{what} =~ /extended/ ? (
@@ -84,7 +92,7 @@ sub dbVNGet {
) : (),
# TODO: optimize this, as it will be very slow when the selected tags match a lot of VNs (>1000)
$tag_ids ?
- qq|(SELECT AVG(tvh.rating) FROM tags_vn_inherit tvh WHERE tvh.tag IN($tag_ids) AND tvh.vid = v.id AND spoiler <= $o{tags_include}[0] GROUP BY tvh.vid) AS tagscore| : (),
+ qq|(SELECT AVG(tvh.rating) FROM tags_vn_inherit tvh WHERE tvh.tag IN($tag_ids) AND tvh.vid = v.id AND spoiler <= $o{tagspoil} GROUP BY tvh.vid) AS tagscore| : (),
);
my $order = sprintf {
@@ -216,8 +224,10 @@ sub dbScreenshotGet {
# Fetch random VN + screenshots
+# if any arguments are given, it will return one random screenshot for each VN
sub dbScreenshotRandom {
- return shift->dbAll(q|
+ my($self, @vids) = @_;
+ return $self->dbAll(q|
SELECT s.id AS scr, s.width, s.height, vr.vid, vr.title
FROM screenshots s
JOIN vn_screenshots vs ON vs.scr = s.id
@@ -230,7 +240,22 @@ sub dbScreenshotRandom {
LIMIT 20
)
LIMIT 4|
- );
+ ) if !@vids;
+ # this query is faster than it looks
+ return $self->dbAll(join(' UNION ALL ', map
+ q|SELECT s.id AS scr, s.width, s.height, vr.vid, vr.title, RANDOM() AS position
+ FROM vn v
+ JOIN vn_rev vr ON vr.id = v.latest
+ JOIN vn_screenshots vs ON vs.vid = v.latest
+ JOIN screenshots s ON s.id = vs.scr
+ WHERE v.id = ? AND s.id = (
+ SELECT vs2.scr
+ FROM vn_screenshots vs2
+ JOIN vn v2 ON v2.latest = vs2.vid
+ WHERE v2.id = v.id
+ ORDER BY RANDOM()
+ LIMIT 1
+ )|, @vids).' ORDER BY position', @vids);
}
diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm
index 65b66f9e..17d69cd5 100644
--- a/lib/VNDB/Func.pm
+++ b/lib/VNDB/Func.pm
@@ -7,7 +7,7 @@ use YAWF ':html';
use Exporter 'import';
use POSIX 'strftime', 'ceil', 'floor';
use VNDBUtil;
-our @EXPORT = (@VNDBUtil::EXPORT, qw| liststat clearfloat cssicon tagscore mt minage fil_parse fil_serialize |);
+our @EXPORT = (@VNDBUtil::EXPORT, qw| clearfloat cssicon tagscore mt minage fil_parse fil_serialize |);
# three ways to represent the same information
@@ -16,21 +16,6 @@ our @fil_escape = split //, $fil_escape;
our %fil_escape = map +($fil_escape[$_], sprintf '%02d', $_), 0..$#fil_escape;
-# Argument: hashref with rstat and vstat
-# Returns: empty string if not in list, otherwise colour-encoded list status
-sub liststat {
- my $l = shift;
- return '' if !$l;
- my $rs = mt('_rlst_rstat_'.$l->{rstat});
- $rs = qq|<b class="done">$rs</b>| if $l->{rstat} == 2; # Obtained
- $rs = qq|<b class="todo">$rs</b>| if $l->{rstat} < 2; # Unknown/pending
- my $vs = mt('_rlst_vstat_'.$l->{vstat});
- $vs = qq|<b class="done">$vs</b>| if $l->{vstat} == 2; # Finished
- $vs = qq|<b class="todo">$vs</b>| if $l->{vstat} == 0 || $l->{vstat} == 4; # Unknown/dropped
- return "$rs / $vs";
-}
-
-
# Clears a float, to make sure boxes always have the correct height
sub clearfloat {
div class => 'clearfloat', '';
@@ -82,7 +67,7 @@ sub mt {
sub minage {
my($a, $ex) = @_;
- my $str = !defined($a) ? mt '_minage_null' : !$a ? mt '_minage_all' : mt '_minage_age', $a;
+ my $str = $a == -1 ? mt '_minage_null' : !$a ? mt '_minage_all' : mt '_minage_age', $a;
$ex = !defined($a) ? '' : {
0 => 'CERO A',
12 => 'CERO B',
@@ -118,7 +103,7 @@ sub fil_serialize {
my @v = ref $fil->{$_} ? @{$fil->{$_}} : ($fil->{$_});
s/$e/_$fil_escape{$1}/g for(@v);
$_.'-'.join '~', @v
- } keys %$fil;
+ } grep defined($fil->{$_}), keys %$fil;
}
1;
diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm
index d9974b00..0b5daa6c 100644
--- a/lib/VNDB/Handler/Discussions.pm
+++ b/lib/VNDB/Handler/Discussions.pm
@@ -48,7 +48,7 @@ sub thread {
end;
end;
- $self->htmlBrowseNavigate("/t$tid/", $page, $t->{count} > $page*25, 't', 1);
+ $self->htmlBrowseNavigate("/t$tid/", $page, [ $t->{count}, 25 ], 't', 1);
div class => 'mainbox thread';
table;
for my $i (0..$#$p) {
@@ -83,7 +83,7 @@ sub thread {
}
end;
end;
- $self->htmlBrowseNavigate("/t$tid/", $page, $t->{count} > $page*25, 'b', 1);
+ $self->htmlBrowseNavigate("/t$tid/", $page, [ $t->{count}, 25 ], 'b', 1);
if($t->{locked}) {
div class => 'mainbox';
@@ -278,7 +278,7 @@ sub board {
return 404 if $f->{_err};
my $obj = !$iid ? undef :
- $type eq 'u' ? $self->dbUserGet(uid => $iid)->[0] :
+ $type eq 'u' ? $self->dbUserGet(uid => $iid, what => 'hide_list')->[0] :
$type eq 'p' ? $self->dbProducerGet(id => $iid)->[0] :
$self->dbVNGet(id => $iid)->[0];
return 404 if $iid && !$obj;
diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm
index 88e08f68..90a457aa 100644
--- a/lib/VNDB/Handler/Misc.pm
+++ b/lib/VNDB/Handler/Misc.pm
@@ -16,6 +16,7 @@ YAWF::register(
qr{setlang}, \&setlang,
qr{nospam}, \&nospam,
qr{we-dont-like-ie}, \&iemessage,
+ qr{xml/prefs\.xml}, \&prefs,
qr{opensearch\.xml}, \&opensearch,
# redirects for old URLs
@@ -44,7 +45,13 @@ sub homepage {
lit mt '_home_intro';
end;
- my $scr = $self->dbScreenshotRandom;
+ # with filters applied it's signifcantly slower, so special-code the situations with and without filters
+ my @vns;
+ if($self->authPref('filter_vn')) {
+ my $r = $self->filFetchDB(vn => undef, undef, {hasshot => 1, results => 4, order => 'rand'});
+ @vns = map $_->{id}, @$r;
+ }
+ my $scr = $self->dbScreenshotRandom(@vns);
p class => 'screenshots';
for (@$scr) {
my($w, $h) = imgsize($_->{width}, $_->{height}, @{$self->{scr_size}});
@@ -124,7 +131,7 @@ sub homepage {
h1;
a href => '/v/rand', mt '_home_randomvn';
end;
- my $random = $self->dbVNGet(results => 10, sort => 'rand');
+ my $random = $self->filFetchDB(vn => undef, undef, {results => 10, sort => 'rand'});
ul;
for (@$random) {
li;
@@ -139,7 +146,7 @@ sub homepage {
h1;
a href => strftime('/r?fil=date_after-%Y%m%d;o=a;s=released', gmtime), mt '_home_upcoming';
end;
- my $upcoming = $self->dbReleaseGet(results => 10, unreleased => 1, what => 'platforms');
+ my $upcoming = $self->filFetchDB(release => undef, undef, {results => 10, unreleased => 1, what => 'platforms'});
ul;
for (@$upcoming) {
li;
@@ -159,7 +166,7 @@ sub homepage {
h1;
a href => strftime('/r?fil=date_before-%Y%m%d;o=d;s=released', gmtime), mt '_home_justreleased';
end;
- my $justrel = $self->dbReleaseGet(results => 10, sort => 'released', reverse => 1, unreleased => 0, what => 'platforms');
+ my $justrel = $self->filFetchDB(release => undef, undef, {results => 10, sort => 'released', reverse => 1, unreleased => 0, what => 'platforms'});
ul;
for (@$justrel) {
li;
@@ -197,7 +204,7 @@ sub history {
return 404 if $f->{_err};
# get item object and title
- my $obj = $type eq 'u' ? $self->dbUserGet(uid => $id)->[0] :
+ my $obj = $type eq 'u' ? $self->dbUserGet(uid => $id, what => 'hide_list')->[0] :
$type eq 'p' ? $self->dbProducerGet(id => $id)->[0] :
$type eq 'r' ? $self->dbReleaseGet(id => $id)->[0] :
$type eq 'v' ? $self->dbVNGet(id => $id)->[0] : undef;
@@ -340,10 +347,17 @@ sub setlang {
return 404 if $lang->{_err};
$lang = $lang->{lang};
+ my $browser = VNDB::L10N->get_handle()->language_tag();
+
(my $ref = $self->reqHeader('Referer')||'/') =~ s/^\Q$self->{url}//;
$self->resRedirect($ref, 'post');
- $self->resHeader('Set-Cookie', "l10n=$lang; expires=Sat, 01-Jan-2030 00:00:00 GMT; path=/; domain=$self->{cookie_domain}")
- if $lang ne $self->{l10n}->language_tag();
+ if($lang ne $self->{l10n}->language_tag()) {
+ $self->authInfo->{id}
+ ? $self->authPref(l10n => $lang eq $browser ? undef : $lang)
+ : $self->resHeader('Set-Cookie', sprintf 'l10n=%s; expires=%s; path=/; domain=%s',
+ $lang, $lang eq $browser ? 'Sat, 01-Jan-2000 00:00:00 GMT' : 'Sat, 01-Jan-2030 00:00:00 GMT',
+ $self->{cookie_domain});
+ }
}
@@ -405,6 +419,24 @@ sub iemessage {
}
+sub prefs {
+ my $self = shift;
+ return if !$self->authCheckCode;
+ return 404 if !$self->authInfo->{id};
+ my $f = $self->formValidate(
+ { name => 'key', enum => [qw|filter_vn filter_release|] },
+ { name => 'value', required => 0, maxlength => 2000 },
+ );
+ return 404 if $f->{_err};
+ $self->authPref($f->{key}, $f->{value});
+
+ # doesn't really matter what we return, as long as it's XML
+ $self->resHeader('Content-type' => 'text/xml');
+ xml;
+ tag 'done', '';
+}
+
+
sub opensearch {
my $self = shift;
$self->resHeader('Content-Type' => 'application/opensearchdescription+xml');
diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm
index 6e11e829..f7a46c2d 100644
--- a/lib/VNDB/Handler/Producers.pm
+++ b/lib/VNDB/Handler/Producers.pm
@@ -162,7 +162,7 @@ sub _releases {
for my $rel (@{$vn{$v->{vid}}}) {
Tr class => 'rel';
td class => 'tc1'; lit $self->{l10n}->datestr($rel->{released}); end;
- td class => 'tc2', !defined($rel->{minage}) ? '' : minage $rel->{minage};
+ td class => 'tc2', $rel->{minage} < 0 ? '' : minage $rel->{minage};
td class => 'tc3';
for (sort @{$rel->{platforms}}) {
next if $_ eq 'oth';
@@ -225,10 +225,11 @@ sub edit {
{ name => 'l_wp', required => 0, maxlength => 150, default => '' },
{ name => 'desc', required => 0, maxlength => 5000, default => '' },
{ name => 'prodrelations', required => 0, maxlength => 5000, default => '' },
- { name => 'editsum', maxlength => 5000 },
+ { name => 'editsum', required => 0, maxlength => 5000 },
{ name => 'ihid', required => 0 },
{ name => 'ilock', required => 0 },
);
+ push @{$frm->{_err}}, 'badeditsum' if !$frm->{editsum} || lc($frm->{editsum}) eq lc($frm->{desc});
if(!$frm->{_err}) {
# parse
my $relations = [ map { /^([a-z]+),([0-9]+),(.+)$/ && (!$pid || $2 != $pid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{prodrelations} ];
diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm
index ce5c62c2..06dcfd0a 100644
--- a/lib/VNDB/Handler/Releases.pm
+++ b/lib/VNDB/Handler/Releases.pm
@@ -56,7 +56,7 @@ sub page {
[ media => join => ', ', split => sub {
map $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1), @{$_[0]}
} ],
- [ resolution => serialize => sub { $self->{resolutions}[$_[0]][0] } ],
+ [ resolution => serialize => sub { my $r = $self->{resolutions}[$_[0]][0]; $r =~ /^_/ ? mt($r) : $r } ],
[ voiced => serialize => sub { mt '_voiced_'.$_[0] } ],
[ ani_story => serialize => sub { mt '_animated_'.$_[0] } ],
[ ani_ero => serialize => sub { mt '_animated_'.$_[0] } ],
@@ -190,7 +190,7 @@ sub _infotable {
end;
end;
- if(defined $r->{minage}) {
+ if($r->{minage} >= 0) {
Tr ++$i % 2 ? (class => 'odd') : ();
td mt '_relinfo_minage';
td minage $r->{minage};
@@ -236,22 +236,18 @@ sub _infotable {
}
if($self->authInfo->{id}) {
- my $rl = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => $r->{id})->[0];
+ my $rl = $self->dbRListGet(uid => $self->authInfo->{id}, rid => $r->{id})->[0];
Tr ++$i % 2 ? (class => 'odd') : ();
td mt '_relinfo_user';
td;
Select id => 'listsel', name => $self->authGetCode("/r$r->{id}/list");
- option mt !$rl ? '_relinfo_user_notlist' :
- ('_relinfo_user_inlist', mt('_rlst_rstat_'.$rl->{rstat}), mt('_rlst_vstat_'.$rl->{vstat}));
- optgroup label => mt '_relinfo_user_setr';
- option value => "r$_", mt '_rlst_rstat_'.$_
- for (@{$self->{rlst_rstat}});
+ option value => -2,
+ mt !$rl ? '_relinfo_user_notlist' : ('_relinfo_user_inlist', mt('_rlist_status_'.$rl->{status}));
+ optgroup label => mt '_relinfo_user_setstatus';
+ option value => $_, mt '_rlist_status_'.$_
+ for (@{$self->{rlist_status}});
end;
- optgroup label => mt '_relinfo_user_setv';
- option value => "v$_", mt '_rlst_vstat_'.$_
- for (@{$self->{rlst_vstat}});
- end;
- option value => 'del', mt '_relinfo_user_del' if $rl;
+ option value => -1, mt '_relinfo_user_del' if $rl;
end;
end;
end;
@@ -287,9 +283,8 @@ sub edit {
my $vn = $rid ? $r->{vn} : [{ vid => $vid, title => $v->{title} }];
my %b4 = !$rid ? () : (
- (map { $_ => $r->{$_} } qw|type title original gtin catalog languages website released
+ (map { $_ => $r->{$_} } qw|type title original gtin catalog languages website released minage
notes platforms patch resolution voiced freeware doujin ani_story ani_ero ihid ilock|),
- minage => defined($r->{minage}) ? $r->{minage} : -1,
media => join(',', sort map "$_->{medium} $_->{qty}", @{$r->{media}}),
producers => join('|||', map
sprintf('%d,%d,%s', $_->{id}, ($_->{developer}?1:0)+($_->{publisher}?2:0), $_->{name}),
@@ -315,7 +310,7 @@ sub edit {
{ name => 'languages', multi => 1, enum => $self->{languages} },
{ name => 'website', required => 0, default => '', maxlength => 250, template => 'url' },
{ name => 'released', required => 0, default => 0, template => 'int' },
- { name => 'minage' , required => 0, default => -1, enum => [map !defined($_)?-1:$_, @{$self->{age_ratings}}] },
+ { name => 'minage' , required => 0, default => -1, enum => $self->{age_ratings} },
{ name => 'notes', required => 0, default => '', maxlength => 10240 },
{ name => 'platforms', required => 0, default => '', multi => 1, enum => $self->{platforms} },
{ name => 'media', required => 0, default => '' },
@@ -325,11 +320,12 @@ sub edit {
{ name => 'ani_ero', required => 0, default => 0, enum => $self->{animated} },
{ name => 'producers', required => 0, default => '' },
{ name => 'vn', maxlength => 5000 },
- { name => 'editsum', maxlength => 5000 },
+ { name => 'editsum', required => 0, maxlength => 5000 },
{ name => 'ihid', required => 0 },
{ name => 'ilock', required => 0 },
);
+ push @{$frm->{_err}}, 'badeditsum' if !$frm->{editsum} || lc($frm->{editsum}) eq lc($frm->{notes});
push @{$frm->{_err}}, [ 'released', 'required', 1 ] if !$frm->{released};
my($media, $producers, $new_vn);
@@ -356,9 +352,8 @@ sub edit {
if(!$frm->{_err}) {
my $nrev = $self->dbItemEdit(r => !$copy && $rid ? $r->{cid} : undef,
- (map { $_ => $frm->{$_} } qw| type title original gtin catalog languages website released
+ (map { $_ => $frm->{$_} } qw| type title original gtin catalog languages website released minage
notes platforms resolution editsum patch voiced freeware doujin ani_story ani_ero ihid ilock|),
- minage => $frm->{minage} < 0 ? undef : $frm->{minage},
vn => $new_vn,
producers => $producers,
media => $media,
@@ -406,7 +401,7 @@ sub _form {
[ date => short => 'released', name => mt('_redit_form_released') ],
[ static => content => mt('_redit_form_released_note') ],
[ select => short => 'minage', name => mt('_redit_form_minage'),
- options => [ map [ !defined($_)?-1:$_, minage $_, 1 ], @{$self->{age_ratings}} ] ],
+ options => [ map [ $_, minage $_, 1 ], @{$self->{age_ratings}} ] ],
[ textarea => short => 'notes', name => mt('_redit_form_notes').'<br /><b class="standout">'.mt('_inenglish').'</b>' ],
[ static => content => mt('_redit_form_notes_note') ],
],
@@ -492,19 +487,16 @@ sub browse {
{ name => 'fil',required => 0, default => '' },
);
return 404 if $f->{_err};
+ $f->{fil} = $self->authPref('filter_release') if !grep $_ eq 'fil', $self->reqParam();
- my $fil = fil_parse $f->{fil}, qw|type patch freeware doujin date_before date_after minage lang olang resolution plat med voiced ani_story ani_ero|;
- _fil_compat($self, $fil);
- $f->{fil} = fil_serialize($fil);
-
- my($list, $np) = !$f->{q} && !keys %$fil ? ([], 0) : $self->dbReleaseGet(
+ my %compat = _fil_compat($self);
+ my($list, $np) = !$f->{q} && !$f->{fil} && !keys %compat ? ([], 0) : $self->filFetchDB(release => $f->{fil}, \%compat, {
sort => $f->{s}, reverse => $f->{o} eq 'd',
page => $f->{p},
results => 50,
what => 'platforms',
$f->{q} ? ( search => $f->{q} ) : (),
- %$fil
- );
+ });
$self->htmlHeader(title => mt('_rbrowse_title'));
@@ -519,13 +511,14 @@ sub browse {
end;
end;
+ my $uri = sprintf '/r?q=%s;fil=%s', uri_escape($f->{q}), $f->{fil};
$self->htmlBrowse(
class => 'relbrowse',
items => $list,
options => $f,
nextpage => $np,
- pageurl => "/r?q=$f->{q};fil=$f->{fil};s=$f->{s};o=$f->{o}",
- sorturl => "/r?q=$f->{q};fil=$f->{fil}",
+ pageurl => "$uri;s=$f->{s};o=$f->{o}",
+ sorturl => $uri,
header => [
[ mt('_rbrowse_col_released'), 'released' ],
[ mt('_rbrowse_col_minage'), 'minage' ],
@@ -538,7 +531,7 @@ sub browse {
td class => 'tc1';
lit $self->{l10n}->datestr($l->{released});
end;
- td class => 'tc2', !defined($l->{minage}) ? '' : minage $l->{minage};
+ td class => 'tc2', $l->{minage} < 0 ? '' : minage $l->{minage};
td class => 'tc3';
$_ ne 'oth' && cssicon $_, mt "_plat_$_" for (@{$l->{platforms}});
cssicon "lang $_", mt "_lang_$_" for (@{$l->{languages}});
@@ -551,7 +544,7 @@ sub browse {
end;
},
) if @$list;
- if(($f->{q} || keys %$fil) && !@$list) {
+ if(($f->{q} || $f->{fil}) && !@$list) {
div class => 'mainbox';
h1 mt '_rbrowse_noresults_title';
div class => 'notice';
@@ -559,13 +552,14 @@ sub browse {
end;
end;
}
- $self->htmlFooter;
+ $self->htmlFooter(prefs => [qw|filter_release|]);
}
-# provide compatibility with old filter URLs
+# provide compatibility with old URLs
sub _fil_compat {
- my($self, $fil) = @_;
+ my $self = shift;
+ my %c;
my $f = $self->formValidate(
{ name => 'ln', required => 0, multi => 1, default => '', enum => $self->{languages} },
{ name => 'pl', required => 0, multi => 1, default => '', enum => $self->{platforms} },
@@ -575,23 +569,24 @@ sub _fil_compat {
{ name => 'fw', required => 0, default => 0, enum => [ 0..2 ] },
{ name => 'do', required => 0, default => 0, enum => [ 0..2 ] },
{ name => 'ma_m', required => 0, default => 0, enum => [ 0, 1 ] },
- { name => 'ma_a', required => 0, default => 0, enum => [ grep defined($_), @{$self->{age_ratings}} ] },
+ { name => 'ma_a', required => 0, default => 0, enum => $self->{age_ratings} },
{ name => 'mi', required => 0, default => 0, template => 'int' },
{ name => 'ma', required => 0, default => 99999999, template => 'int' },
{ name => 're', required => 0, multi => 1, default => 0, enum => [ 1..$#{$self->{resolutions}} ] },
);
- return if $f->{_err};
- $fil->{minage} //= [ grep defined($_) && $f->{ma_m} ? $f->{ma_a} >= $_ : defined ($_) && $f->{ma_a} <= $_, @{$self->{age_ratings}} ] if $f->{ma_a} || $f->{ma_m};
- $fil->{date_after} //= $f->{mi} if $f->{mi};
- $fil->{date_before} //= $f->{ma} if $f->{ma} < 99990000;
- $fil->{plat} //= $f->{pl} if $f->{pl}[0];
- $fil->{lang} //= $f->{ln} if $f->{ln}[0];
- $fil->{med} //= $f->{me} if $f->{me}[0];
- $fil->{resolution} //= $f->{re} if $f->{re}[0];
- $fil->{type} //= $f->{tp} if $f->{tp};
- $fil->{patch} //= $f->{pa} == 2 ? 0 : 1 if $f->{pa};
- $fil->{freeware} //= $f->{fw} == 2 ? 0 : 1 if $f->{fw};
- $fil->{doujin} //= $f->{do} == 2 ? 0 : 1 if $f->{do};
+ return () if $f->{_err};
+ $c{minage} = [ grep $_ >= 0 && ($f->{ma_m} ? $f->{ma_a} >= $_ : $f->{ma_a} <= $_), @{$self->{age_ratings}} ] if $f->{ma_a} || $f->{ma_m};
+ $c{date_after} = $f->{mi} if $f->{mi};
+ $c{date_before} = $f->{ma} if $f->{ma} < 99990000;
+ $c{plat} = $f->{pl} if $f->{pl}[0];
+ $c{lang} = $f->{ln} if $f->{ln}[0];
+ $c{med} = $f->{me} if $f->{me}[0];
+ $c{resolution} = $f->{re} if $f->{re}[0];
+ $c{type} = $f->{tp} if $f->{tp};
+ $c{patch} = $f->{pa} == 2 ? 0 : 1 if $f->{pa};
+ $c{freeware} = $f->{fw} == 2 ? 0 : 1 if $f->{fw};
+ $c{doujin} = $f->{do} == 2 ? 0 : 1 if $f->{do};
+ return %c;
}
diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm
index 0567bff6..ad736027 100644
--- a/lib/VNDB/Handler/Tags.pm
+++ b/lib/VNDB/Handler/Tags.pm
@@ -39,13 +39,15 @@ sub tagpage {
my $tagspoil = $self->reqCookie($self->{cookie_prefix}.'tagspoil');
$f->{m} = $tagspoil =~ /^[0-2]$/ ? $tagspoil : 0 if $f->{m} == -1;
- my($list, $np) = $t->{meta} || $t->{state} != 2 ? ([],0) : $self->dbVNGet(
+ my($list, $np) = $t->{meta} || $t->{state} != 2 ? ([],0) : $self->filFetchDB(vn => undef, undef, {
what => 'rating',
results => 50,
page => $f->{p},
sort => $f->{s}, reverse => $f->{o} eq 'd',
- tags_include => [ $f->{m}, [$tag ]],
- );
+ tagspoil => $f->{m},
+ tag_inc => $tag,
+ tag_exc => undef,
+ });
my $title = mt '_tagp_title', $t->{meta}?0:1, $t->{name};
$self->htmlHeader(title => $title, noindex => $t->{state} != 2);
@@ -357,12 +359,12 @@ sub taglinks {
my $f = $self->formValidate(
{ name => 'p', required => 0, default => 1, template => 'int' },
{ name => 'o', required => 0, default => 'd', enum => ['a', 'd'] },
- { name => 's', required => 0, default => 'date', enum => [qw|date username title tag|] },
+ { name => 's', required => 0, default => 'date', enum => [qw|date tag|] },
{ name => 'v', required => 0, default => 0, template => 'int' },
{ name => 'u', required => 0, default => 0, template => 'int' },
{ name => 't', required => 0, default => 0, template => 'int' },
);
- return 404 if $f->{_err};
+ return 404 if $f->{_err} || $f->{p} > 100;
my($list, $np) = $self->dbTagLinks(
what => 'details',
@@ -432,11 +434,11 @@ sub taglinks {
sorturl => $url->(s=>0,o=>0),
header => [
[ mt('_taglink_col_date'), 'date' ],
- [ mt('_taglink_col_user'), 'username' ],
+ [ mt('_taglink_col_user') ],
[ mt('_taglink_col_rating') ],
[ mt('_taglink_col_tag'), 'tag' ],
[ mt('_taglink_col_spoiler') ],
- [ mt('_taglink_col_vn'), 'title' ],
+ [ mt('_taglink_col_vn'), ],
],
row => sub {
my($s, $n, $l) = @_;
@@ -668,19 +670,22 @@ sub fulltree {
sub tagxml {
my $self = shift;
- my $q = $self->formValidate({ name => 'q', maxlength => 500 });
- return 404 if $q->{_err};
- $q = $q->{q};
+ my $f = $self->formValidate(
+ { name => 'q', required => 0, maxlength => 500 },
+ { name => 'id', required => 0, multi => 1, template => 'int' },
+ );
+ return 404 if $f->{_err} || (!$f->{q} && !$f->{id} && !$f->{id}[0]);
my($list, $np) = $self->dbTagGet(
- $q =~ /^g([1-9]\d*)/ ? (id => $1) : $q =~ /^name:(.+)$/ ? (name => $1) : (search => $q),
+ !$f->{q} ? () : $f->{q} =~ /^g([1-9]\d*)/ ? (id => $1) : $f->{q} =~ /^name:(.+)$/ ? (name => $1) : (search => $f->{q}),
+ $f->{id} && $f->{id}[0] ? (id => $f->{id}) : (),
results => 15,
page => 1,
);
$self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
xml;
- tag 'tags', more => $np ? 'yes' : 'no', query => $q;
+ tag 'tags', more => $np ? 'yes' : 'no', $f->{q} ? (query => $f->{q}) : ();
for(@$list) {
tag 'item', id => $_->{id}, meta => $_->{meta} ? 'yes' : 'no', state => $_->{state}, $_->{name};
}
diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm
index 6efb0a13..403b391d 100644
--- a/lib/VNDB/Handler/ULists.pm
+++ b/lib/VNDB/Handler/ULists.pm
@@ -10,8 +10,9 @@ use VNDB::Func;
YAWF::register(
qr{v([1-9]\d*)/vote}, \&vnvote,
qr{v([1-9]\d*)/wish}, \&vnwish,
- qr{r([1-9]\d*)/list}, \&rlist,
- qr{xml/rlist.xml}, \&rlist,
+ qr{v([1-9]\d*)/list}, \&vnlist_e,
+ qr{r([1-9]\d*)/list}, \&rlist_e,
+ qr{xml/rlist.xml}, \&rlist_e,
qr{([uv])([1-9]\d*)/votes}, \&votelist,
qr{u([1-9]\d*)/wish}, \&wishlist,
qr{u([1-9]\d*)/list}, \&vnlist,
@@ -56,7 +57,26 @@ sub vnwish {
}
-sub rlist {
+sub vnlist_e {
+ my($self, $id) = @_;
+
+ my $uid = $self->authInfo->{id};
+ return $self->htmlDenied() if !$uid;
+
+ return if !$self->authCheckCode;
+ my $f = $self->formValidate(
+ { name => 'e', enum => [ -1, @{$self->{vnlist_status}} ] }
+ );
+ return 404 if $f->{_err};
+
+ $self->dbVNListDel($uid, $id) if $f->{e} == -1;
+ $self->dbVNListAdd($uid, $id, $f->{e}) if $f->{e} != -1;
+
+ $self->resRedirect('/v'.$id, 'temp');
+}
+
+
+sub rlist_e {
my($self, $id) = @_;
my $rid = $id;
@@ -73,27 +93,21 @@ sub rlist {
return if !$self->authCheckCode;
my $f = $self->formValidate(
- { name => 'e', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] },
+ { name => 'e', required => 1, enum => [ -1, @{$self->{rlist_status}} ] }
);
return 404 if $f->{_err};
- $self->dbVNListDel($uid, $rid) if $f->{e} eq 'del';
- $self->dbVNListAdd(
- rid => $rid,
- uid => $uid,
- $f->{e} =~ /^([rv])(\d+)$/ && $1 eq 'r' ? (rstat => $2) : (vstat => $2)
- ) if $f->{e} ne 'del';
+ $self->dbRListDel($uid, $rid) if $f->{e} == -1;
+ $self->dbRListAdd($uid, $rid, $f->{e}) if $f->{e} >= 0;
if($id) {
(my $ref = $self->reqHeader('Referer')||"/r$id") =~ s/^\Q$self->{url}//;
$self->resRedirect($ref, 'temp');
} else {
+ # doesn't really matter what we return, as long as it's XML
$self->resHeader('Content-type' => 'text/xml');
- my $st = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => [$rid])->[0];
xml;
- tag 'rlist', uid => $self->authInfo->{id}, rid => $rid;
- txt $st ? liststat $st : '--';
- end;
+ tag 'done', '';
}
}
@@ -101,17 +115,32 @@ sub rlist {
sub votelist {
my($self, $type, $id) = @_;
- my $obj = $type eq 'v' ? $self->dbVNGet(id => $id)->[0] : $self->dbUserGet(uid => $id)->[0];
+ my $obj = $type eq 'v' ? $self->dbVNGet(id => $id)->[0] : $self->dbUserGet(uid => $id, what => 'hide_list')->[0];
return 404 if !$obj->{id};
my $own = $type eq 'u' && $self->authInfo->{id} && $self->authInfo->{id} == $id;
- return 404 if $type eq 'u' && !$own && !($obj->{show_list} || $self->authCan('usermod'));
+ return 404 if $type eq 'u' && !$own && !(!$obj->{hide_list} || $self->authCan('usermod'));
my $f = $self->formValidate(
{ name => 'p', required => 0, default => 1, template => 'int' },
{ name => 'o', required => 0, default => 'd', enum => ['a', 'd'] },
{ name => 's', required => 0, default => 'date', enum => [qw|date title vote|] },
+ { name => 'c', required => 0, default => 'all', enum => [ 'all', 'a'..'z', 0 ] },
);
+ return 404 if $f->{_err};
+
+ if($own && $self->reqMethod eq 'POST') {
+ return if !$self->authCheckCode;
+ my $frm = $self->formValidate(
+ { name => 'vid', required => 1, multi => 1, template => 'int' },
+ { name => 'batchedit', required => 1, enum => [ -2, -1, 1..10 ] },
+ );
+ my @vid = grep $_ > 0, @{$frm->{vid}};
+ if(!$frm->{_err} && @vid && $frm->{batchedit} > -2) {
+ $self->dbVoteDel($id, \@vid) if $frm->{batchedit} == -1;
+ $self->dbVoteAdd(\@vid, $id, $frm->{batchedit}) if $frm->{batchedit} >= 0;
+ }
+ }
my($list, $np) = $self->dbVoteGet(
$type.'id' => $id,
@@ -121,24 +150,35 @@ sub votelist {
sort => $f->{s} eq 'title' && $type eq 'v' ? 'username' : $f->{s},
reverse => $f->{o} eq 'd',
results => 50,
- page => $f->{p}
+ page => $f->{p},
+ $f->{c} ne 'all' ? ($type eq 'u' ? 'vn_char' : 'user_char', $f->{c}) : (),
);
- return 404 if !@$list;
my $title = mt $type eq 'v' ? '_votelist_title_vn' : '_votelist_title_user', $obj->{title} || $obj->{username};
$self->htmlHeader(noindex => 1, title => $title);
$self->htmlMainTabs($type => $obj, 'votes');
div class => 'mainbox';
h1 $title;
+ p class => 'browseopts';
+ for ('all', 'a'..'z', 0) {
+ a href => "/$type$id/votes?c=$_", $_ eq $f->{c} ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#';
+ }
+ end;
+ p mt '_votelist_novotes' if !@$list;
end;
- $self->htmlBrowse(
+ if($own) {
+ my $code = $self->authGetCode("/u$id/votes");
+ form action => "/u$id/votes?formcode=$code;c=$f->{c};s=$f->{s};p=$f->{p}", method => 'post';
+ }
+
+ @$list && $self->htmlBrowse(
class => 'votelist',
items => $list,
options => $f,
nextpage => $np,
- pageurl => "/$type$id/votes?o=$f->{o};s=$f->{s}",
- sorturl => "/$type$id/votes",
+ pageurl => "/$type$id/votes?c=$f->{c};o=$f->{o};s=$f->{s}",
+ sorturl => "/$type$id/votes?c=$f->{c}",
header => [
[ mt('_votelist_col_date'), 'date' ],
[ mt('_votelist_col_vote'), 'vote' ],
@@ -147,15 +187,33 @@ sub votelist {
row => sub {
my($s, $n, $l) = @_;
Tr $n % 2 ? (class => 'odd') : ();
- td class => 'tc1', $self->{l10n}->date($l->{date});
+ td class => 'tc1';
+ input type => 'checkbox', name => 'vid', value => $l->{vid} if $own;
+ txt ' '.$self->{l10n}->date($l->{date});
+ end;
td class => 'tc2', $l->{vote};
td class => 'tc3';
a href => $type eq 'v' ? ("/u$l->{uid}", $l->{username}) : ("/v$l->{vid}", shorten $l->{title}, 100);
end;
end;
},
+ $own ? (footer => sub {
+ Tr;
+ td colspan => 3, class => 'tc1';
+ input type => 'checkbox', class => 'checkall', name => 'vid', value => -1;
+ txt ' ';
+ Select name => 'batchedit', id => 'batchedit';
+ option value => -2, '-- with selected --';
+ optgroup label => 'Change vote';
+ option value => $_, "$_ (".mt("_vote_$_").')' for (reverse 1..10);
+ end;
+ option value => -1, 'revoke';
+ end;
+ end;
+ end;
+ }) : (),
);
-
+ end if $own;
$self->htmlFooter;
}
@@ -164,8 +222,8 @@ sub wishlist {
my($self, $uid) = @_;
my $own = $self->authInfo->{id} && $self->authInfo->{id} == $uid;
- my $u = $self->dbUserGet(uid => $uid)->[0];
- return 404 if !$u || !$own && !($u->{show_list} || $self->authCan('usermod'));
+ my $u = $self->dbUserGet(uid => $uid, what => 'hide_list')->[0];
+ return 404 if !$u || !$own && !(!$u->{hide_list} || $self->authCan('usermod'));
my $f = $self->formValidate(
{ name => 'p', required => 0, default => 1, template => 'int' },
@@ -266,8 +324,8 @@ sub vnlist {
my($self, $uid) = @_;
my $own = $self->authInfo->{id} && $self->authInfo->{id} == $uid;
- my $u = $self->dbUserGet(uid => $uid)->[0];
- return 404 if !$u || !$own && !($u->{show_list} || $self->authCan('usermod'));
+ my $u = $self->dbUserGet(uid => $uid, what => 'hide_list')->[0];
+ return 404 if !$u || !$own && !(!$u->{hide_list} || $self->authCan('usermod'));
my $f = $self->formValidate(
{ name => 'p', required => 0, default => 1, template => 'int' },
@@ -275,33 +333,40 @@ sub vnlist {
{ name => 's', required => 0, default => 'title', enum => [ 'title', 'vote' ] },
{ name => 'c', required => 0, default => 'all', enum => [ 'all', 'a'..'z', 0 ] },
{ name => 'v', required => 0, default => 0, enum => [ -1..1 ] },
+ { name => 't', required => 0, default => -1, enum => [ -1, @{$self->{vnlist_status}} ] },
);
return 404 if $f->{_err};
if($own && $self->reqMethod eq 'POST') {
return if !$self->authCheckCode;
my $frm = $self->formValidate(
- { name => 'sel', required => 0, default => 0, multi => 1, template => 'int' },
- { name => 'batchedit', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] },
+ { name => 'vid', required => 0, default => 0, multi => 1, template => 'int' },
+ { name => 'rid', required => 0, default => 0, multi => 1, template => 'int' },
+ { name => 'not', required => 0, default => '', maxlength => 2000 },
+ { name => 'vns', required => 1, enum => [ -2, -1, @{$self->{vnlist_status}}, 999 ] },
+ { name => 'rel', required => 1, enum => [ -2, -1, @{$self->{rlist_status}} ] },
);
- if(!$frm->{_err} && @{$frm->{sel}} && $frm->{sel}[0]) {
- $self->dbVNListDel($uid, $frm->{sel}) if $frm->{batchedit} eq 'del';
- $self->dbVNListAdd(
- rid => $frm->{sel},
- uid => $uid,
- $frm->{batchedit} =~ /^([rv])(\d+)$/ && $1 eq 'r' ? (rstat => $2) : (vstat => $2)
- ) if $frm->{batchedit} ne 'del';
+ my @vid = grep $_ > 0, @{$frm->{vid}};
+ my @rid = grep $_ > 0, @{$frm->{rid}};
+ if(!$frm->{_err} && @vid && $frm->{vns} > -2) {
+ $self->dbVNListDel($uid, \@vid) if $frm->{vns} == -1;
+ $self->dbVNListAdd($uid, \@vid, $frm->{vns}) if $frm->{vns} >= 0 && $frm->{vns} < 999;
+ $self->dbVNListAdd($uid, \@vid, undef, $frm->{not}) if $frm->{vns} == 999;
+ }
+ if(!$frm->{_err} && @rid && $frm->{rel} > -2) {
+ $self->dbRListDel($uid, \@rid) if $frm->{rel} == -1;
+ $self->dbRListAdd($uid, \@rid, $frm->{rel}) if $frm->{rel} >= 0;
}
}
-
my($list, $np) = $self->dbVNListList(
uid => $uid,
results => 50,
page => $f->{p},
sort => $f->{s}, reverse => $f->{o} eq 'd',
- voted => $f->{v},
+ voted => $f->{v} == 0 ? undef : $f->{v} < 0 ? 0 : $f->{v},
$f->{c} ne 'all' ? (char => $f->{c}) : (),
+ $f->{t} >= 0 ? (status => $f->{t}) : (),
);
my $title = $own ? mt '_rlist_title_my' : mt '_rlist_title_other', $u->{username};
@@ -315,6 +380,7 @@ sub vnlist {
local $_ = "/u$uid/list";
$_ .= '?c='.($n eq 'c' ? $v : $f->{c});
$_ .= ';v='.($n eq 'v' ? $v : $f->{v});
+ $_ .= ';t='.($n eq 't' ? $v : $f->{t});
if($n eq 'page') {
$_ .= ';o='.($n eq 'o' ? $v : $f->{o});
$_ .= ';s='.($n eq 's' ? $v : $f->{s});
@@ -330,10 +396,14 @@ sub vnlist {
}
end;
p class => 'browseopts';
- a href => $url->(v => 0), 0 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_all';
+ a href => $url->(v => 0), 0 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_all';
a href => $url->(v => 1), 1 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_only';
a href => $url->(v => -1), -1 == $f->{v} ? (class => 'optselected') : (), mt '_rlist_voted_none';
end;
+ p class => 'browseopts';
+ a href => $url->(t => -1), -1 == $f->{t} ? (class => 'optselected') : (), mt '_rlist_all';
+ a href => $url->(t => $_), $_ == $f->{t} ? (class => 'optselected') : (), mt '_vnlist_status_'.$_ for @{$self->{vnlist_status}};
+ end;
end;
_vnlist_browse($self, $own, $list, $np, $f, $url, $uid);
@@ -343,8 +413,11 @@ sub vnlist {
sub _vnlist_browse {
my($self, $own, $list, $np, $f, $url, $uid) = @_;
- form action => $url->().';formcode='.$self->authGetCode("/u$uid/list"), method => 'post'
- if $own;
+ if($own) {
+ form action => $url->(), method => 'post';
+ input type => 'hidden', class => 'hidden', name => 'not', id => 'not', value => '';
+ input type => 'hidden', class => 'hidden', name => 'formcode', id => 'formcode', value => $self->authGetCode("/u$uid/list");
+ }
$self->htmlBrowse(
class => 'rlist',
@@ -354,66 +427,84 @@ sub _vnlist_browse {
sorturl => $url->(),
pageurl => $url->('page'),
header => [
- [ mt('_rlist_col_title') => 'title', 3 ],
- sub { td class => 'tc2', id => 'expandall'; lit '<i>&#9656;</i>'.mt('_rlist_col_releases').'*'; end; },
+ [ '' ],
+ sub { td class => 'tc2', id => 'expandall'; lit '&#9656;'; end; },
+ [ mt('_rlist_col_title') => 'title' ],
+ [ '' ], [ '' ],
+ [ mt('_rlist_col_status') ],
+ [ mt('_rlist_col_releases').'*' ],
[ mt('_rlist_col_vote') => 'vote' ],
],
row => sub {
my($s, $n, $i) = @_;
Tr $n % 2 == 0 ? (class => 'odd') : ();
- td class => 'tc1', colspan => 3;
+ td class => 'tc1'; input type => 'checkbox', name => 'vid', value => $i->{vid} if $own; end;
+ if(@{$i->{rels}}) {
+ td class => 'tc2 collapse_but', id => "vid$i->{vid}"; lit '&#9656;'; end;
+ } else {
+ td class => 'tc2', '';
+ }
+ td class => 'tc3_5', colspan => 3;
a href => "/v$i->{vid}", title => $i->{original}||$i->{title}, shorten $i->{title}, 70;
+ b class => 'grayedout', $i->{notes} if $i->{notes};
end;
- td class => 'tc2'.(@{$i->{rels}} ? ' collapse_but' : ''), id => 'vid'.$i->{vid};
- lit '<i>&#9656;</i>';
- my $obtained = grep $_->{rstat}==2, @{$i->{rels}};
- my $finished = grep $_->{vstat}==2, @{$i->{rels}};
- my $txt = sprintf '%d/%d/%d', $obtained, $finished, scalar @{$i->{rels}};
- $txt = qq|<b class="done">$txt</b>| if $finished > $obtained || $finished && $finished == $obtained;
- $txt = qq|<b class="todo">$txt</b>| if $obtained > $finished;
+ td class => 'tc6', $i->{status} ? mt '_vnlist_status_'.$i->{status} : '';
+ td class => 'tc7';
+ my $obtained = grep $_->{status}==2, @{$i->{rels}};
+ my $total = scalar @{$i->{rels}};
+ my $txt = sprintf '%d/%d', $obtained, $total;
+ $txt = qq|<b class="done">$txt</b>| if $total && $obtained == $total;
+ $txt = qq|<b class="todo">$txt</b>| if $obtained < $total;
lit $txt;
end;
- td class => 'tc3', $i->{vote} || '-';
+ td class => 'tc8', $i->{vote} || '-';
end;
for (@{$i->{rels}}) {
- Tr class => "collapse relhid collapse_vid$i->{vid}";
- td class => 'tc1'.($own ? ' own' : '');
- input type => 'checkbox', name => 'sel', value => $_->{rid}
- if $own;
- lit $self->{l10n}->datestr($_->{released});
- end;
+ Tr class => "collapse relhid collapse_vid$i->{vid}".($n%2 ? '':' odd');
+ td class => 'tc1', '';
td class => 'tc2';
+ input type => 'checkbox', name => 'rid', value => $_->{rid} if $own;
+ end;
+ td class => 'tc3', $self->{l10n}->datestr($_->{released});
+ td class => 'tc4';
cssicon "lang $_", mt "_lang_$_" for @{$_->{languages}};
cssicon "rt$_->{type}", mt "_rtype_$_->{type}";
end;
- td class => 'tc3';
+ td class => 'tc5';
a href => "/r$_->{rid}", title => $_->{original}||$_->{title}, shorten $_->{title}, 50;
end;
- td colspan => 2, class => 'tc4';
- lit liststat($_);
- end;
+ td class => 'tc6', $_->{status} ? mt '_rlist_status_'.$_->{status} : '';
+ td class => 'tc7_8', colspan => 2, '';
end;
}
},
$own ? (footer => sub {
Tr;
- td class => 'tc1', colspan => 3;
- Select id => 'batchedit', name => 'batchedit';
- option mt '_rlist_selection';
- optgroup label => mt '_rlist_changerel';
- option value => "r$_", mt "_rlst_rstat_$_"
- for (@{$self->{rlst_rstat}});
+ td class => 'tc1'; input type => 'checkbox', name => 'vid', value => -1, class => 'checkall'; end;
+ td class => 'tc2'; input type => 'checkbox', name => 'rid', value => -1, class => 'checkall'; end;
+ td class => 'tc3_6', colspan => 4;
+ Select id => 'vns', name => 'vns';
+ option value => -2, mt '_rlist_withvn';
+ optgroup label => mt '_rlist_changestat';
+ option value => $_, mt "_vnlist_status_$_"
+ for (@{$self->{vnlist_status}});
end;
- optgroup label => mt '_rlist_changeplay';
- option value => "v$_", mt "_rlst_vstat_$_"
- for (@{$self->{rlst_vstat}});
+ option value => 999, mt '_rlist_setnote';
+ option value => -1, mt '_rlist_del';
+ end;
+ Select id => 'rel', name => 'rel';
+ option value => -2, mt '_rlist_withrel';
+ optgroup label => mt '_rlist_changestat';
+ option value => $_, mt "_rlist_status_$_"
+ for (@{$self->{rlist_status}});
end;
- option value => 'del', mt '_rlist_del';
+ option value => -1, mt '_rlist_del';
end;
+ input type => 'submit', value => mt '_rlist_update';
end;
- td class => 'tc2', colspan => 2, mt '_rlist_releasenote';
+ td class => 'tc7_8', colspan => 2, mt '_rlist_releasenote';
end;
}) : (),
);
diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm
index 3e2a1aef..044c72b2 100644
--- a/lib/VNDB/Handler/Users.pm
+++ b/lib/VNDB/Handler/Users.pm
@@ -27,7 +27,7 @@ YAWF::register(
sub userpage {
my($self, $uid) = @_;
- my $u = $self->dbUserGet(uid => $uid, what => 'stats')->[0];
+ my $u = $self->dbUserGet(uid => $uid, what => 'stats hide_list')->[0];
return 404 if !$u->{id};
my $votes = $u->{c_votes} && $self->dbVoteStats(uid => $uid);
@@ -69,7 +69,7 @@ sub userpage {
Tr ++$i % 2 ? (class => 'odd') : ();
td mt '_userpage_votes';
td;
- if(!$u->{show_list}) {
+ if($u->{hide_list}) {
txt mt '_userpage_hidden';
} elsif($votes) {
my($total, $count) = (0, 0);
@@ -99,7 +99,7 @@ sub userpage {
Tr ++$i % 2 ? (class => 'odd') : ();
td mt '_userpage_list';
- td !$u->{show_list} ? mt('_userpage_hidden') :
+ td $u->{hide_list} ? mt('_userpage_hidden') :
mt('_userpage_list_item', $u->{releasecount}, $u->{vncount});
end;
@@ -116,7 +116,7 @@ sub userpage {
end;
end;
- if($u->{show_list} && $votes) {
+ if(!$u->{hide_list} && $votes) {
div class => 'mainbox';
h1 mt '_userpage_votestats';
$self->htmlVoteStats(u => $u, $votes);
@@ -291,7 +291,7 @@ sub edit {
return $self->htmlDenied if !$self->authInfo->{id} || $self->authInfo->{id} != $uid && !$self->authCan('usermod');
# fetch user info (cached if uid == loggedin uid)
- my $u = $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid, what => 'extended')->[0];
+ my $u = $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid, what => 'extended prefs')->[0];
return 404 if !$u->{id};
# check POST data
@@ -300,30 +300,27 @@ sub edit {
return if !$self->authCheckCode;
$frm = $self->formValidate(
$self->authCan('usermod') ? (
- { name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
- { name => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] },
+ { name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
+ { name => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] },
{ name => 'ign_votes', required => 0, default => 0 },
) : (),
- { name => 'mail', template => 'mail' },
- { name => 'usrpass', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' },
- { name => 'usrpass2', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' },
- { name => 'flags_list', required => 0, default => 0 },
- { name => 'flags_nsfw', required => 0, default => 0 },
- { name => 'skin', enum => [ '', keys %{$self->{skins}} ], required => 0, default => '' },
- { name => 'customcss', required => 0, maxlength => 2000, default => '' },
+ { name => 'mail', template => 'mail' },
+ { name => 'usrpass', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' },
+ { name => 'usrpass2', required => 0, minlength => 4, maxlength => 64, template => 'asciiprint' },
+ { name => 'hide_list', required => 0, default => 0, enum => [0,1] },
+ { name => 'show_nsfw', required => 0, default => 0, enum => [0,1] },
+ { name => 'skin', required => 0, default => '', enum => [ '', keys %{$self->{skins}} ] },
+ { name => 'customcss', required => 0, maxlength => 2000, default => '' },
);
push @{$frm->{_err}}, 'passmatch'
if ($frm->{usrpass} || $frm->{usrpass2}) && (!$frm->{usrpass} || !$frm->{usrpass2} || $frm->{usrpass} ne $frm->{usrpass2});
if(!$frm->{_err}) {
+ $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};
$o{mail} = $frm->{mail};
- $o{skin} = $frm->{skin};
- $o{customcss} = $frm->{customcss};
($o{passwd}, $o{salt}) = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass};
- $o{show_list} = $frm->{flags_list} ? 1 : 0;
- $o{show_nsfw} = $frm->{flags_nsfw} ? 1 : 0;
$o{ign_votes} = $frm->{ign_votes} ? 1 : 0 if $self->authCan('usermod');
$self->dbUserEdit($uid, %o);
$self->dbSessionDel($uid) if $frm->{usrpass};
@@ -334,9 +331,8 @@ sub edit {
# fill out default values
$frm->{usrname} ||= $u->{username};
- $frm->{$_} ||= $u->{$_} for(qw|rank mail skin customcss|);
- $frm->{flags_list} = $u->{show_list} if !defined $frm->{flags_list};
- $frm->{flags_nsfw} = $u->{show_nsfw} if !defined $frm->{flags_nsfw};
+ $frm->{$_} ||= $u->{$_} for(qw|rank mail|);
+ $frm->{$_} //= $u->{prefs}{$_} for(qw|skin customcss show_nsfw hide_list|);
$frm->{ign_votes} = $u->{ign_votes} if !defined $frm->{ign_votes};
# create the page
@@ -368,8 +364,8 @@ sub edit {
[ passwd => short => 'usrpass2', name => mt '_usere_confirm' ],
[ part => title => mt '_usere_options' ],
- [ check => short => 'flags_list', name => mt '_usere_flist', "/u$uid/list", "/u$uid/wish" ],
- [ check => short => 'flags_nsfw', name => mt '_usere_fnsfw' ],
+ [ check => short => 'hide_list', name => mt '_usere_flist', "/u$uid/list", "/u$uid/votes", "/u$uid/wish" ],
+ [ check => short => 'show_nsfw', name => mt '_usere_fnsfw' ],
[ select => short => 'skin', name => mt('_usere_skin'), width => 300, options => [
map [ $_ eq $self->{skin_default} ? '' : $_, $self->{skins}{$_}[0].($self->debug?" [$_]":'') ], sort { $self->{skins}{$a}[0] cmp $self->{skins}{$b}[0] } keys %{$self->{skins}} ] ],
[ textarea => short => 'customcss', name => mt '_usere_css' ],
@@ -382,7 +378,7 @@ sub posts {
my($self, $uid) = @_;
# fetch user info (cached if uid == loggedin uid)
- my $u = $self->authInfo->{id} && $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid)->[0];
+ my $u = $self->authInfo->{id} && $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid, what => 'hide_list')->[0];
return 404 if !$u->{id};
my $f = $self->formValidate(
@@ -439,7 +435,7 @@ sub delete {
# confirm
if(!$act) {
my $code = $self->authGetCode("/u$uid/del/o");
- my $u = $self->dbUserGet(uid => $uid)->[0];
+ my $u = $self->dbUserGet(uid => $uid, what => 'hide_list')->[0];
return 404 if !$u->{id};
$self->htmlHeader(title => 'Delete user', noindex => 1);
$self->htmlMainTabs('u', $u, 'del');
@@ -500,6 +496,7 @@ sub list {
my($list, $np) = $self->dbUserGet(
sort => $f->{s}, reverse => $f->{o} eq 'd',
+ what => 'hide_list',
$char ne 'all' ? (
firstchar => $char ) : (),
results => 50,
@@ -527,8 +524,8 @@ sub list {
a href => '/u'.$l->{id}, $l->{username};
end;
td class => 'tc2', $self->{l10n}->date($l->{registered});
- td class => 'tc3'.(!$l->{show_list} && $self->authCan('usermod') ? ' linethrough' : '');
- lit !$l->{show_list} && !$self->authCan('usermod') ? '-' : !$l->{c_votes} ? 0 :
+ td class => 'tc3'.($l->{hide_list} && $self->authCan('usermod') ? ' linethrough' : '');
+ lit $l->{hide_list} && !$self->authCan('usermod') ? '-' : !$l->{c_votes} ? 0 :
qq|<a href="/u$l->{id}/votes">$l->{c_votes}</a>|;
end;
td class => 'tc4';
@@ -546,9 +543,9 @@ sub list {
sub notifies {
my($self, $uid) = @_;
- return $self->htmlDenied if !$self->authInfo->{id} || $uid != $self->authInfo->{id};
- my $u = $self->dbUserGet(uid => $uid)->[0];
+ my $u = $self->authInfo;
+ return $self->htmlDenied if !$u->{id} || $uid != $u->{id};
my $f = $self->formValidate(
{ name => 'p', required => 0, default => 1, template => 'int' },
@@ -562,15 +559,11 @@ sub notifies {
if($self->reqMethod() eq 'POST' && $self->reqParam('set')) {
return if !$self->authCheckCode;
my $frm = $self->formValidate(
- { name => 'notify_dbedit', required => 0 },
- { name => 'notify_announce', required => 0 }
+ { name => 'notify_nodbedit', required => 0, default => 1, enum => [0,1] },
+ { name => 'notify_announce', required => 0, default => 0, enum => [0,1] }
);
return 404 if $frm->{_err};
- for ('notify_dbedit', 'notify_announce') {
- $frm->{$_} = $frm->{$_} ? 1 : 0;
- $self->authInfo->{$_} = $frm->{$_};
- }
- $self->dbUserEdit($uid, %$frm);
+ $self->authPref($_, $frm->{$_}) for ('notify_nodbedit', 'notify_announce');
$saved = 1;
# updating notifications
@@ -661,9 +654,10 @@ sub notifies {
h1 mt '_usern_set_title';
div class => 'notice', mt '_usern_set_saved' if $saved;
p;
- for('dbedit', 'announce') {
- input type => 'checkbox', name => "notify_$_", id => "notify_$_", value => 1,
- $self->authInfo->{"notify_$_"} ? (checked => 'checked') : ();
+ for('nodbedit', 'announce') {
+ my $def = $_ eq 'nodbedit'? 0 : 1;
+ input type => 'checkbox', name => "notify_$_", id => "notify_$_", value => $def,
+ ($self->authPref("notify_$_")||0) == $def ? (checked => 'checked') : ();
label for => "notify_$_", ' '.mt("_usern_set_$_");
br;
}
diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm
index 8e30da99..8a63b79b 100644
--- a/lib/VNDB/Handler/VNBrowse.pm
+++ b/lib/VNDB/Handler/VNBrowse.pm
@@ -25,72 +25,40 @@ sub list {
);
return 404 if $f->{_err};
$f->{q} ||= $f->{sq};
- my $fil = fil_parse $f->{fil}, qw|length hasani taginc tagexc tagspoil lang olang plat|;
- _fil_compat($self, $fil);
-
- if($f->{q}) {
- return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp')
- if $f->{q} =~ /^([gvrptud])([0-9]+)(?:\.([0-9]+))?$/;
-
- # for URL compatibilty with older versions (ugly hack to get English strings)
- my @lang;
- $f->{q} =~ s/\s*$VNDB::L10N::en::Lexicon{"_lang_$_"}\s*//&&push @lang, $_ for (@{$self->{languages}});
- $fil->{lang} = $fil->{lang} ? [ ref($fil->{lang}) ? @{$fil->{lang}} : $fil->{lang}, @lang ] : \@lang if @lang;
- }
- $f->{fil} = fil_serialize $fil;
-
- # TODO: this should be moved to dbVNGet() in order for savable VN filters to be useful
- my @ignored;
- my $tagfind = sub {
- return map {
- my $i = $self->dbTagGet(name => $_)->[0];
- push @ignored, [$_, 0] if !$i;
- push @ignored, [$_, 1] if $i && $i->{meta};
- $i && !$i->{meta} ? $i->{id} : ();
- } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'')
- };
- my @ti = $tagfind->(delete $fil->{taginc});
- my @te = $tagfind->(delete $fil->{tagexc});
-
- $f->{s} = 'title' if !@ti && $f->{s} eq 'tagscore';
+ $f->{fil} = $self->authPref('filter_vn') if !grep $_ eq 'fil', $self->reqParam();
+ my %compat = _fil_compat($self);
+
+ return $self->resRedirect('/'.$1.$2.(!$3 ? '' : $1 eq 'd' ? '#'.$3 : '.'.$3), 'temp')
+ if $f->{q} && $f->{q} =~ /^([gvrptud])([0-9]+)(?:\.([0-9]+))?$/;
+
+ $f->{s} = 'title' if $f->{fil} !~ /tag_inc-/ && $f->{s} eq 'tagscore';
$f->{o} = $f->{s} eq 'tagscore' ? 'd' : 'a' if !$f->{o};
- my($list, $np) = $self->dbVNGet(
+ my($list, $np) = $self->filFetchDB(vn => $f->{fil}, \%compat, {
what => 'rating',
$char ne 'all' ? ( char => $char ) : (),
$f->{q} ? ( search => $f->{q} ) : (),
results => 50,
page => $f->{p},
sort => $f->{s}, reverse => $f->{o} eq 'd',
- @ti ? (tags_include => [ delete $fil->{tagspoil}, \@ti ]) : (),
- @te ? (tags_exclude => \@te) : (),
- %$fil
- );
+ });
$self->resRedirect('/v'.$list->[0]{id}, 'temp')
if $f->{q} && @$list == 1 && $f->{p} == 1;
$self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q});
+ my $quri = uri_escape($f->{q});
form action => '/v/all', 'accept-charset' => 'UTF-8', method => 'get';
div class => 'mainbox';
h1 mt '_vnbrowse_title';
$self->htmlSearchBox('v', $f->{q});
p class => 'browseopts';
for ('all', 'a'..'z', 0) {
- a href => "/v/$_?q=$f->{q};fil=$f->{fil}", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#';
+ a href => "/v/$_?q=$quri;fil=$f->{fil}", $_ eq $char ? (class => 'optselected') : (), $_ eq 'all' ? mt('_char_all') : $_ ? uc $_ : '#';
}
end;
- if(@ignored) {
- div class => 'warning';
- h2 mt '_vnbrowse_tagign_title';
- ul;
- li $_->[0].' ('.mt('_vnbrowse_tagign_'.($_->[1]?'meta':'notfound')).')' for @ignored;
- end;
- end;
- }
-
a id => 'filselect', href => '#v';
lit '<i>&#9656;</i> '.mt('_rbrowse_filters').'<i></i>'; # TODO: it's not *r*browse
end;
@@ -98,25 +66,24 @@ sub list {
end;
end; # /form
- $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$f->{q};fil=$f->{fil}", scalar @ti);
- $self->htmlFooter;
+ $self->htmlBrowseVN($list, $f, $np, "/v/$char?q=$quri;fil=$f->{fil}", $f->{fil} =~ /tag_inc-/);
+ $self->htmlFooter(prefs => ['filter_vn']);
}
sub _fil_compat {
- my($self, $fil) = @_;
+ my $self = shift;
+ my %c;
my $f = $self->formValidate(
{ name => 'ln', required => 0, multi => 1, enum => $self->{languages}, default => '' },
{ name => 'pl', required => 0, multi => 1, enum => $self->{platforms}, default => '' },
- { name => 'ti', required => 0, default => '', maxlength => 200 },
- { name => 'te', required => 0, default => '', maxlength => 200 },
{ name => 'sp', required => 0, default => $self->reqCookie($self->{cookie_prefix}.'tagspoil') =~ /^([0-2])$/ ? $1 : 0, enum => [0..2] },
);
- $fil->{lang} //= $f->{ln} if $f->{ln}[0];
- $fil->{plat} //= $f->{pl} if $f->{pl}[0];
- $fil->{taginc} //= $f->{ti} if $f->{ti};
- $fil->{tagexc} //= $f->{te} if $f->{te};
- $fil->{tagspoil} //= $f->{sp};
+ return () if $f->{_err};
+ $c{lang} //= $f->{ln} if $f->{ln}[0];
+ $c{plat} //= $f->{pl} if $f->{pl}[0];
+ $c{tagspoil} //= $f->{sp};
+ return %c;
}
diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm
index 8ee104af..b93c544d 100644
--- a/lib/VNDB/Handler/VNEdit.pm
+++ b/lib/VNDB/Handler/VNEdit.pm
@@ -52,11 +52,13 @@ sub edit {
{ name => 'img_nsfw', required => 0, default => 0 },
{ name => 'vnrelations', required => 0, default => '', maxlength => 5000 },
{ name => 'screenshots', required => 0, default => '', maxlength => 1000 },
- { name => 'editsum', maxlength => 5000 },
+ { name => 'editsum', required => 0, maxlength => 5000 },
{ name => 'ihid', required => 0 },
{ name => 'ilock', required => 0 },
);
+ push @{$frm->{_err}}, 'badeditsum' if !$frm->{editsum} || lc($frm->{editsum}) eq lc($frm->{desc});
+
# handle image upload
my $image = _uploadimage($self, $v, $frm);
diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm
index 1a3eb514..5e024424 100644
--- a/lib/VNDB/Handler/VNPage.pm
+++ b/lib/VNDB/Handler/VNPage.pm
@@ -16,7 +16,7 @@ YAWF::register(
sub rand {
my $self = shift;
- $self->resRedirect('/v'.$self->dbVNGet(results => 1, sort => 'rand')->[0]{id}, 'temp');
+ $self->resRedirect('/v'.$self->filFetchDB(vn => undef, undef, {results => 1, sort => 'rand'})->[0]{id}, 'temp');
}
@@ -74,12 +74,12 @@ sub page {
} elsif($v->{image} < 0) {
p mt '_vnpage_imgproc';
} else {
- p $v->{img_nsfw} ? (id => 'nsfw_hid', style => $self->authInfo->{show_nsfw} ? 'display: block' : '') : ();
+ p $v->{img_nsfw} ? (id => 'nsfw_hid', style => $self->authPref('show_nsfw') ? 'display: block' : '') : ();
img src => sprintf("%s/cv/%02d/%d.jpg", $self->{url_static}, $v->{image}%100, $v->{image}), alt => $v->{title};
i mt '_vnpage_imgnsfw_foot' if $v->{img_nsfw};
end;
if($v->{img_nsfw}) {
- p id => 'nsfw_show', $self->authInfo->{show_nsfw} ? (style => 'display: none') : ();
+ p id => 'nsfw_show', $self->authPref('show_nsfw') ? (style => 'display: none') : ();
txt mt('_vnpage_imgnsfw_msg')."\n\n";
a href => '#', mt '_vnpage_imgnsfw_show';
txt "\n\n".mt '_vnpage_imgnsfw_note';
@@ -225,7 +225,7 @@ sub _revision {
[ image => htmlize => sub {
my $url = sprintf "%s/cv/%02d/%d.jpg", $self->{url_static}, $_[0]%100, $_[0];
if($_[0] > 0) {
- return $_[1]->{img_nsfw} && !$self->authInfo->{show_nsfw} ? "<a href=\"$url\">".mt('_vndiff_image_nsfw').'</a>' : "<img src=\"$url\" />";
+ return $_[1]->{img_nsfw} && !$self->authPref('show_nsfw') ? "<a href=\"$url\">".mt('_vndiff_image_nsfw').'</a>' : "<img src=\"$url\" />";
} else {
return mt $_[0] < 0 ? '_vndiff_image_proc' : '_vndiff_image_none';
}
@@ -343,6 +343,7 @@ sub _useroptions {
my($self, $i, $v) = @_;
my $vote = $self->dbVoteGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0];
+ my $list = $self->dbVNListGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0];
my $wish = $self->dbWishListGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0];
Tr ++$$i % 2 ? (class => 'odd') : ();
@@ -358,6 +359,16 @@ sub _useroptions {
end;
br;
}
+
+ Select id => 'listsel', name => $self->authGetCode("/v$v->{id}/list");
+ option $list ? mt '_vnpage_uopt_vnlisted', mt '_vnlist_status_'.$list->{status} : mt '_vnpage_uopt_novn';
+ optgroup label => $list ? mt '_vnpage_uopt_changevn' : mt '_vnpage_uopt_addvn';
+ option value => $_, mt "_vnlist_status_$_" for (@{$self->{rlist_status}});
+ end;
+ option value => -1, mt '_vnpage_uopt_delvn' if $list;
+ end;
+ br;
+
if(!$vote || $wish) {
Select id => 'wishsel', name => $self->authGetCode("/v$v->{id}/wish");
option $wish ? mt '_vnpage_uopt_wishlisted', mt '_wish_'.$wish->{wstat} : mt '_vnpage_uopt_nowish';
@@ -385,7 +396,7 @@ sub _releases {
}
if($self->authInfo->{id}) {
- my $l = $self->dbVNListGet(uid => $self->authInfo->{id}, rid => [map $_->{id}, @$r]);
+ my $l = $self->dbRListGet(uid => $self->authInfo->{id}, rid => [map $_->{id}, @$r]);
for my $i (@$l) {
[grep $i->{rid} == $_->{id}, @$r]->[0]{ulist} = $i;
}
@@ -406,7 +417,7 @@ sub _releases {
for my $rel (grep grep($_ eq $l, @{$_->{languages}}), @$r) {
Tr;
td class => 'tc1'; lit $self->{l10n}->datestr($rel->{released}); end;
- td class => 'tc2', !defined($rel->{minage}) ? '' : minage $rel->{minage};
+ td class => 'tc2', $rel->{minage} < 0 ? '' : minage $rel->{minage};
td class => 'tc3';
for (sort @{$rel->{platforms}}) {
next if $_ eq 'oth';
@@ -420,9 +431,8 @@ sub _releases {
end;
td class => 'tc5';
if($self->authInfo->{id}) {
- a href => "/r$rel->{id}", id => "rlsel_$rel->{id}", class => 'vnrlsel';
- lit $rel->{ulist} ? liststat $rel->{ulist} : '--';
- end;
+ a href => "/r$rel->{id}", id => "rlsel_$rel->{id}", class => 'vnrlsel',
+ $rel->{ulist} ? mt '_rlist_status_'.$rel->{ulist}{status} : '--';
} else {
txt ' ';
}
@@ -451,7 +461,7 @@ sub _screenshots {
if(grep $_->{nsfw}, @{$v->{screenshots}}) {
p class => 'nsfwtoggle';
lit mt '_vnpage_scr_showing',
- sprintf('<i id="nsfwshown">%d</i>', $self->authInfo->{show_nsfw} ? scalar @{$v->{screenshots}} : scalar grep(!$_->{nsfw}, @{$v->{screenshots}})),
+ sprintf('<i id="nsfwshown">%d</i>', $self->authPref('show_nsfw') ? scalar @{$v->{screenshots}} : scalar grep(!$_->{nsfw}, @{$v->{screenshots}})),
scalar @{$v->{screenshots}};
txt " ";
a href => '#', id => "nsfwhide", mt '_vnpage_scr_nsfwhide';
@@ -471,7 +481,7 @@ sub _screenshots {
for (@scr) {
my($w, $h) = imgsize($_->{width}, $_->{height}, @{$self->{scr_size}});
a href => sprintf('%s/sf/%02d/%d.jpg', $self->{url_static}, $_->{id}%100, $_->{id}),
- class => sprintf('scrlnk%s%s', $_->{nsfw} ? ' nsfw':'', $_->{nsfw}&&!$self->authInfo->{show_nsfw}?' hidden':''),
+ class => sprintf('scrlnk%s%s', $_->{nsfw} ? ' nsfw':'', $_->{nsfw}&&!$self->authPref('show_nsfw')?' hidden':''),
rel => "iv:$_->{width}x$_->{height}:scr";
img src => sprintf('%s/st/%02d/%d.jpg', $self->{url_static}, $_->{id}%100, $_->{id}),
width => $w, height => $h, alt => mt '_vnpage_scr_num', $_->{id};
diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm
index 24e316ce..9ad76894 100644
--- a/lib/VNDB/Util/Auth.pm
+++ b/lib/VNDB/Util/Auth.pm
@@ -14,7 +14,7 @@ use YAWF ':html';
use VNDB::Func;
-our @EXPORT = qw| authInit authLogin authLogout authInfo authCan authPreparePass authGetCode authCheckCode |;
+our @EXPORT = qw| authInit authLogin authLogout authInfo authCan authPreparePass authGetCode authCheckCode authPref |;
# initializes authentication information and checks the vndb_auth cookie
@@ -27,7 +27,7 @@ sub authInit {
return _rmcookie($self) if length($cookie) < 41;
my $token = substr($cookie, 0, 40);
my $uid = substr($cookie, 40);
- $self->{_auth} = $uid =~ /^\d+$/ && $self->dbUserGet(uid => $uid, session => $token, what => 'extended notifycount')->[0];
+ $self->{_auth} = $uid =~ /^\d+$/ && $self->dbUserGet(uid => $uid, session => $token, what => 'extended notifycount prefs')->[0];
# update the sessions.lastused column if lastused < now()'6 hours'
$self->dbSessionUpdateLastUsed($uid, $token) if $self->{_auth} && $self->{_auth}{session_lastused} < time()-6*3600;
return _rmcookie($self) if !$self->{_auth};
@@ -70,6 +70,10 @@ sub authLogout {
$self->resRedirect('/', 'temp');
_rmcookie($self);
+
+ # set l10n cookie if the user has a preferred language set
+ my $l10n = $self->authPref('l10n');
+ $self->resHeader('Set-Cookie', "l10n=$l10n; expires=Sat, 01-Jan-2030 00:00:00 GMT; path=/; domain=$self->{cookie_domain}") if $l10n;
}
@@ -196,5 +200,14 @@ sub _incorrectcode {
}
+sub authPref {
+ my($self, $key, $val) = @_;
+ my $nfo = $self->authInfo;
+ return '' if !$nfo->{id};
+ return $nfo->{prefs}{$key}||'' if @_ == 2;
+ $nfo->{prefs}{$key} = $val;
+ $self->dbUserPrefSet($nfo->{id}, $key, $val);
+}
+
1;
diff --git a/lib/VNDB/Util/BrowseHTML.pm b/lib/VNDB/Util/BrowseHTML.pm
index 139ff1b0..62e01fd1 100644
--- a/lib/VNDB/Util/BrowseHTML.pm
+++ b/lib/VNDB/Util/BrowseHTML.pm
@@ -6,6 +6,7 @@ use warnings;
use YAWF ':html', 'xml_escape';
use Exporter 'import';
use VNDB::Func;
+use POSIX 'ceil';
our @EXPORT = qw| htmlBrowse htmlBrowseNavigate htmlBrowseHist htmlBrowseVN |;
@@ -83,23 +84,39 @@ sub htmlBrowse {
# creates next/previous buttons (tabs), if needed
-# Arguments: page url, current page (1..n), nextpage (0/1), alignment (t/b), noappend (0/1)
+# Arguments: page url, current page (1..n), nextpage (0/1 or [$total, $perpage]), alignment (t/b), noappend (0/1)
sub htmlBrowseNavigate {
my($self, $url, $p, $np, $al, $na) = @_;
- return if $p == 1 && !$np;
+ my($cnt, $pp) = ref($np) ? @$np : ($p+$np, 1);
+ return if $p == 1 && $cnt <= $pp;
$url .= $url =~ /\?/ ? ';p=' : '?p=' unless $na;
- ul class => 'maintabs ' . ($al eq 't' ? 'notfirst' : 'bottom');
- if($p > 1) {
- li class => 'left';
- a href => $url.($p-1), '<- '.mt '_browse_previous';
- end;
- }
- if($np) {
- li;
- a href => $url.($p+1), mt('_browse_next').' ->';
- end;
- }
+
+ my $tab = sub {
+ my($left, $page, $label) = @_;
+ li $left ? (class => 'left') : ();
+ a href => $url.$page; lit $label; end;
+ end;
+ };
+ my $ell = sub {
+ use utf8;
+ li class => 'ellipsis'.(shift() ? ' left' : '');
+ b '⋯';
+ end;
+ };
+ my $nc = 5; # max. number of buttons on each side
+
+ ul class => 'maintabs browsetabs ' . ($al eq 't' ? 'notfirst' : 'bottom');
+ $p > 2 and ref $np and $tab->(1, 1, '&laquo; '.mt '_browse_first');
+ $p > $nc+1 and ref $np and $ell->(1);
+ $p > $_ and ref $np and $tab->(1, $p-$_, $p-$_) for (reverse 2..($nc>$p-2?$p-2:$nc-1));
+ $p > 1 and $tab->(1, $p-1, '&lsaquo; '.mt '_browse_previous');
+
+ my $l = ceil($cnt/$pp)-$p+1;
+ $l > 2 and $tab->(0, $l+$p-1, mt('_browse_last').' &raquo;');
+ $l > $nc+1 and $ell->(0);
+ $l > $_ and $tab->(0, $p+$_, $p+$_) for (reverse 2..($nc>$l-2?$l-2:$nc-1));
+ $l > 1 and $tab->(0, $p+1, mt('_browse_next').' &rsaquo;');
end;
}
diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm
index c7d67647..a412949a 100644
--- a/lib/VNDB/Util/CommonHTML.pm
+++ b/lib/VNDB/Util/CommonHTML.pm
@@ -45,11 +45,15 @@ sub htmlMainTabs {
end;
}
- if($type eq 'u' && ($obj->{show_list} || $self->authCan('usermod'))) {
+ if($type eq 'u' && (!($obj->{hide_list} || $obj->{prefs}{hide_list}) || ($self->authInfo->{id} && $self->authInfo->{id} == $obj->{id}) || $self->authCan('usermod'))) {
li $sel eq 'wish' ? (class => 'tabselected') : ();
a href => "/$id/wish", mt '_mtabs_wishlist';
end;
+ li $sel eq 'votes' ? (class => 'tabselected') : ();
+ a href => "/$id/votes", mt '_mtabs_votes';
+ end;
+
li $sel eq 'list' ? (class => 'tabselected') : ();
a href => "/$id/list", mt '_mtabs_list';
end;
diff --git a/lib/VNDB/Util/FormHTML.pm b/lib/VNDB/Util/FormHTML.pm
index d619754a..41ee0ccc 100644
--- a/lib/VNDB/Util/FormHTML.pm
+++ b/lib/VNDB/Util/FormHTML.pm
@@ -27,7 +27,7 @@ sub htmlFormError {
ul;
for my $e (@{$frm->{_err}}) {
if(!ref $e) {
- li mt '_formerr_e_'.$e;
+ li; lit mt '_formerr_e_'.$e; end;
next;
}
my($field, $type, $rule) = @$e;
@@ -89,7 +89,7 @@ sub htmlFormPart {
end;
td class => 'field';
input type => 'checkbox', name => $o{short}, id => $o{short},
- value => $o{value}||'true', $frm->{$o{short}} ? ( checked => 'checked' ) : ();
+ value => $o{value}||1, ($frm->{$o{short}}||0) eq ($o{value}||1) ? ( checked => 'checked' ) : ();
label for => $o{short};
lit $o{name};
end;
diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm
index f1a6dc2d..cc34b874 100644
--- a/lib/VNDB/Util/LayoutHTML.pm
+++ b/lib/VNDB/Util/LayoutHTML.pm
@@ -12,7 +12,7 @@ our @EXPORT = qw|htmlHeader htmlFooter|;
sub htmlHeader { # %options->{ title, noindex, search, feeds }
my($self, %o) = @_;
- my $skin = $self->reqParam('skin') || $self->authInfo->{skin} || $self->{skin_default};
+ my $skin = $self->reqParam('skin') || $self->authPref('skin') || $self->{skin_default};
$skin = $self->{skin_default} if !$self->{skins}{$skin} || !-d "$VNDB::ROOT/static/s/$skin";
# heading
@@ -22,8 +22,8 @@ sub htmlHeader { # %options->{ title, noindex, search, feeds }
Link rel => 'shortcut icon', href => '/favicon.ico', type => 'image/x-icon';
Link rel => 'stylesheet', href => $self->{url_static}.'/s/'.$skin.'/style.css?'.$self->{version}, type => 'text/css', media => 'all';
Link rel => 'search', type => 'application/opensearchdescription+xml', title => 'VNDB VN Search', href => $self->{url}.'/opensearch.xml';
- if($self->authInfo->{customcss}) {
- (my $css = $self->authInfo->{customcss}) =~ s/\n/ /g;
+ if($self->authPref('customcss')) {
+ (my $css = $self->authPref('customcss')) =~ s/\n/ /g;
style type => 'text/css', $css;
}
Link rel => 'alternate', type => 'application/atom+xml', href => "/feeds/$_.atom", title => $self->{atom_feeds}{$_}[1]
@@ -88,6 +88,7 @@ sub _menu {
div;
a href => "$uid/edit", mt '_menu_myprofile'; br;
a href => "$uid/list", mt '_menu_myvnlist'; br;
+ a href => "$uid/votes",mt '_menu_myvotes'; br;
a href => "$uid/wish", mt '_menu_mywishlist'; br;
a href => "$uid/notifies", $nc ? (class => 'notifyget') : (), mt('_menu_mynotifications').($nc?" ($nc)":''); br;
a href => "$uid/hist", mt '_menu_mychanges'; br;
@@ -134,8 +135,8 @@ sub _menu {
}
-sub htmlFooter {
- my $self = shift;
+sub htmlFooter { # %options => { prefs => [pref1,..] }
+ my($self, %o) = @_;
div id => 'footer';
my $q = $self->dbRandomQuote;
@@ -155,6 +156,17 @@ sub htmlFooter {
a href => $self->{source_url}, mt '_footer_source';
end;
end; # /div maincontent
+
+ # insert users' preference data when required by JS
+ if($o{prefs}) {
+ script type => 'text/javascript';
+ txt sprintf "PREF_CODE='%s';", $self->authInfo->{id} ? $self->authGetCode('/xml/prefs.xml') : '';
+ txt 'PREFS={';
+ # assumes the preference value doesn't contain a '
+ txt join ',', map sprintf("'%s':'%s'", $_, $self->authPref($_)), @{$o{prefs}};
+ txt '};';
+ end;
+ }
script type => 'text/javascript', src => $self->{url_static}.'/f/js/'.$self->{l10n}->language_tag().'.js?'.$self->{version}, '';
end; # /body
end; # /html
diff --git a/lib/VNDB/Util/Misc.pm b/lib/VNDB/Util/Misc.pm
new file mode 100644
index 00000000..71aca7a8
--- /dev/null
+++ b/lib/VNDB/Util/Misc.pm
@@ -0,0 +1,100 @@
+
+package VNDB::Util::Misc;
+
+use strict;
+use warnings;
+use Exporter 'import';
+use VNDB::Func;
+
+our @EXPORT = qw|filFetchDB|;
+
+
+my %filfields = (
+ vn => [qw|length hasani tag_inc tag_exc taginc tagexc tagspoil lang olang plat|],
+ release => [qw|type patch freeware doujin date_before date_after minage lang olang resolution plat med voiced ani_story ani_ero|],
+);
+
+
+# Arguments:
+# type ('vn' or 'release'),
+# filter overwrite (string or undef),
+# when defined, these filters will be used instead of the preferences,
+# must point to a variable, will be modified in-place with the actually used filters
+# options to pass to db*Get() before the filters (hashref or undef)
+# these options can be overwritten by the filters or the next option
+# options to pass to db*Get() after the filters (hashref or undef)
+# these options overwrite all other options (pre-options and filters)
+
+sub filFetchDB {
+ my($self, $type, $overwrite, $pre, $post) = @_;
+ $pre = {} if !$pre;
+ $post = {} if !$post;
+ my $dbfunc = $self->can($type eq 'vn' ? 'dbVNGet' : 'dbReleaseGet');
+ my $prefname = 'filter_'.$type;
+ my $pref = $self->authPref($prefname);
+
+ # simply call the DB if we're not applying filters
+ return $dbfunc->($self, %$pre, %$post) if !$pref && !$overwrite;
+
+ my $filters = fil_parse $overwrite // $pref, @{$filfields{$type}};
+
+ # compatibility
+ $self->authPref($prefname => fil_serialize $filters)
+ if $type eq 'vn' && _fil_vn_compat($self, $filters) && !defined $overwrite;
+
+ # write the definite filter string in $overwrite
+ $_[2] = fil_serialize({map +(
+ exists($post->{$_}) ? ($_ => $post->{$_}) :
+ exists($filters->{$_}) ? ($_ => $filters->{$_}) :
+ exists($pre->{$_}) ? ($_ => $pre->{$_}) : (),
+ ), @{$filfields{$type}}}) if defined $overwrite;
+
+ return $dbfunc->($self, %$pre, %$filters, %$post) if defined $overwrite;
+
+ # since incorrect filters can throw a database error, we have to special-case
+ # filters that originate from a preference setting, so that in case these are
+ # the cause of an error, they are removed. Not doing this will result in VNDB
+ # throwing 500's even for non-browse pages. We have to do some low-level
+ # PostgreSQL stuff with savepoints to ensure that an error won't affect our
+ # existing transaction.
+ my $dbh = $self->{_YAWF}{DB}{sql};
+ $dbh->pg_savepoint('filter');
+ my($r, $np);
+ my $OK = eval {
+ ($r, $np) = $dbfunc->($self, %$pre, %$filters, %$post);
+ 1;
+ };
+ $dbh->pg_rollback_to('filter') if !$OK;
+ $dbh->pg_release('filter');
+
+ # error occured, let's try again without filters. if that succeeds we know
+ # it's the fault of the filter preference, and we should remove it.
+ if(!$OK) {
+ ($r, $np) = $dbfunc->($self, %$pre, %$post);
+ # if we're here, it means the previous function didn't die() (duh!)
+ $self->authPref($prefname => '');
+ warn sprintf "Reset filter preference for userid %d. Old: %s\n", $self->authInfo->{id}||0, $pref;
+ }
+ return wantarray ? ($r, $np) : $r;
+}
+
+
+sub _fil_vn_compat {
+ my($self, $fil) = @_;
+
+ # older tag specification (by name rather than ID)
+ if($fil->{taginc} || $fil->{tagexc}) {
+ my $tagfind = sub {
+ return map {
+ my $i = $self->dbTagGet(name => $_)->[0];
+ $i && !$i->{meta} ? $i->{id} : ();
+ } grep $_, ref $_[0] ? @{$_[0]} : ($_[0]||'')
+ };
+ $fil->{tag_inc} //= [ $tagfind->(delete $fil->{taginc}) ] if $fil->{taginc};
+ $fil->{tag_exc} //= [ $tagfind->(delete $fil->{tagexc}) ] if $fil->{tagexc};
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/util/dbgraph.pl b/util/dbgraph.pl
index a8f1cbe2..f7cb9923 100755
--- a/util/dbgraph.pl
+++ b/util/dbgraph.pl
@@ -23,7 +23,7 @@ my %subgraphs = (
'Producers' => [qw| FFFFCC producers producers_rev producers_relations |],
'Releases' => [qw| C8FFC8 releases releases_rev releases_media releases_platforms releases_producers releases_lang releases_vn |],
'Visual Novels' => [qw| FFE6BE vn vn_rev vn_relations vn_anime vn_screenshots |],
- 'Users' => [qw| CCFFFF users votes rlists wlists sessions notifications |],
+ 'Users' => [qw| CCFFFF users votes rlists wlists vnlists sessions notifications users_prefs |],
'Discussion board' => [qw| FFDCDC threads threads_boards threads_posts |],
'Tags' => [qw| FFC8C8 tags tags_aliases tags_parents tags_vn |],
'Misc' => [qw| F5F5F5 changes anime screenshots stats_cache quotes relgraphs |],
diff --git a/util/jsgen.pl b/util/jsgen.pl
index 4af20b27..58b0ac9a 100755
--- a/util/jsgen.pl
+++ b/util/jsgen.pl
@@ -101,10 +101,9 @@ sub resolutions {
sub jsgen {
l10n_load();
my $common = '';
- $common .= sprintf "rlst_rstat = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{rlst_rstat}};
- $common .= sprintf "rlst_vstat = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{rlst_vstat}};
+ $common .= sprintf "rlist_status = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{rlist_status}};
$common .= sprintf "cookie_prefix = '%s';\n", $S{cookie_prefix};
- $common .= sprintf "age_ratings = [ %s ];\n", join ',', map !defined $_ ? -1 : $_, @{$S{age_ratings}};
+ $common .= sprintf "age_ratings = [ %s ];\n", join ',', @{$S{age_ratings}};
$common .= sprintf "languages = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{languages}};
$common .= sprintf "platforms = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{platforms}};
$common .= sprintf "media = [ %s ];\n", join ', ', map qq{"$_"}, sort keys %{$S{media}};
diff --git a/util/sql/all.sql b/util/sql/all.sql
index 4408c6df..09a6d214 100644
--- a/util/sql/all.sql
+++ b/util/sql/all.sql
@@ -6,10 +6,11 @@
CREATE TYPE anime_type AS ENUM ('tv', 'ova', 'mov', 'oth', 'web', 'spe', 'mv');
CREATE TYPE dbentry_type AS ENUM ('v', 'r', 'p');
CREATE TYPE edit_rettype AS (iid integer, cid integer, rev integer);
-CREATE TYPE language AS ENUM('cs', 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt', 'ru', 'sk', 'sv', 'tr', 'vi', 'zh');
+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 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');
CREATE TYPE vn_relation AS ENUM ('seq', 'preq', 'set', 'alt', 'char', 'side', 'par', 'ser', 'fan', 'orig');
@@ -96,14 +97,19 @@ CREATE TRIGGER vn_vnsearch_notify AFTER UPDATE ON releases FOR EACH ROW
WHEN (NEW.hidden IS DISTINCT FROM OLD.hidden OR NEW.latest IS DISTINCT FROM OLD.latest)
EXECUTE PROCEDURE vn_vnsearch_notify();
+CREATE CONSTRAINT TRIGGER update_vnlist_rlist AFTER DELETE ON vnlists DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE update_vnlist_rlist();
+CREATE CONSTRAINT TRIGGER update_vnlist_rlist AFTER INSERT ON rlists DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE update_vnlist_rlist();
+
-- Sequences used for ID generation of items not in the DB
CREATE SEQUENCE covers_seq;
-- Rows that are assumed to be available
-INSERT INTO users (id, username, mail, rank, notify_dbdel) VALUES (0, 'deleted', 'del@vndb.org', 0, false);
-INSERT INTO users (username, mail, rank, notify_dbdel) VALUES ('multi', 'multi@vndb.org', 0, false);
+INSERT INTO users (id, username, mail, rank) VALUES (0, 'deleted', 'del@vndb.org', 0);
+INSERT INTO users (username, mail, rank) VALUES ('multi', 'multi@vndb.org', 0);
+INSERT INTO users_prefs (uid, key, value) VALUES (0, 'notify_nodbedit', '1');
+INSERT INTO users_prefs (uid, key, value) VALUES (1, 'notify_nodbedit', '1');
INSERT INTO stats_cache (section, count) VALUES
('users', 1),
diff --git a/util/sql/func.sql b/util/sql/func.sql
index 53af121c..0248c69a 100644
--- a/util/sql/func.sql
+++ b/util/sql/func.sql
@@ -455,6 +455,53 @@ $$ LANGUAGE plpgsql;
+-- For each row in rlists, there should be at least one corresponding row in
+-- vnlists for at least one of the VNs linked to that release.
+-- 1. When a row is deleted from vnlists, also remove all rows from rlists that
+-- would otherwise not have a corresponding row in vnlists
+-- 2. When a row is inserted to rlists and there is not yet a corresponding row
+-- in vnlists, add a row in vnlists (with status=unknown) for each vn linked
+-- to the release.
+CREATE OR REPLACE FUNCTION update_vnlist_rlist() RETURNS trigger AS $$
+BEGIN
+ -- 1.
+ IF TG_TABLE_NAME = 'vnlists' THEN
+ DELETE FROM rlists WHERE uid = OLD.uid AND rid IN(SELECT r.id
+ -- fetch all related rows in rlists
+ FROM releases_vn rv
+ JOIN releases r ON r.latest = rv.rid
+ JOIN rlists rl ON rl.rid = r.id
+ WHERE rv.vid = OLD.vid AND rl.uid = OLD.uid
+ -- and test for a corresponding row in vnlists
+ AND NOT EXISTS(
+ SELECT 1
+ FROM releases_vn rvi
+ JOIN vnlists vl ON vl.vid = rvi.vid AND uid = OLD.uid
+ WHERE rvi.rid = r.latest
+ ));
+
+ -- 2.
+ ELSE
+ INSERT INTO vnlists (uid, vid) SELECT NEW.uid, rv.vid
+ -- all VNs linked to the release
+ FROM releases_vn rv
+ JOIN releases r ON rv.rid = r.latest
+ WHERE r.id = NEW.rid
+ -- but only if there are no corresponding rows in vnlists yet
+ AND NOT EXISTS(
+ SELECT 1
+ FROM releases_vn rvi
+ JOIN releases ri ON rvi.rid = ri.latest
+ JOIN vnlists vl ON vl.vid = rvi.vid
+ WHERE ri.id = NEW.rid AND vl.uid = NEW.uid
+ );
+ END IF;
+ RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+
+
+
-- Send a notify whenever anime info should be fetched
CREATE OR REPLACE FUNCTION anime_fetch_notify() RETURNS trigger AS $$
BEGIN NOTIFY anime; RETURN NULL; END;
@@ -702,14 +749,13 @@ BEGIN
-- look for users who should get this notify
FROM (
-- voted on the VN
- SELECT uid FROM votes WHERE TG_TABLE_NAME = 'vn' AND vid = NEW.id
+ SELECT uid FROM votes WHERE TG_TABLE_NAME = 'vn' AND vid = NEW.id
+ -- VN in vnlist
+ UNION SELECT uid FROM vnlists WHERE TG_TABLE_NAME = 'vn' AND vid = NEW.id
-- VN in wishlist
- UNION SELECT uid FROM wlists WHERE TG_TABLE_NAME = 'vn' AND vid = NEW.id
+ UNION SELECT uid FROM wlists WHERE TG_TABLE_NAME = 'vn' AND vid = NEW.id
-- release in release list
- UNION SELECT uid FROM rlists WHERE TG_TABLE_NAME = 'releases' AND rid = NEW.id
- -- there's also a special case which we're ignoring here:
- -- when a VN linked to a release in a user's release list is deleted
- -- normally, the releases are also deleted, so a notify is generated anyway
+ UNION SELECT uid FROM rlists WHERE TG_TABLE_NAME = 'releases' AND rid = NEW.id
) u
-- fetch info about this edit
JOIN changes c ON c.id = NEW.latest
@@ -744,12 +790,10 @@ BEGIN
) x(id, title) ON c.id = x.id
-- join info about the deletion itself
JOIN changes c2 ON c2.id = NEW.latest
- -- join info about the user who should get this notification
- JOIN users u ON u.id = c.requester
-- exclude the user who edited the entry
WHERE c.requester <> c2.requester
-- exclude users who don't want this notify
- AND u.notify_dbedit;
+ AND NOT EXISTS(SELECT 1 FROM users_prefs up WHERE uid = c.requester AND key = 'notify_nodbedit');
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
@@ -759,11 +803,11 @@ $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION notify_announce() RETURNS trigger AS $$
BEGIN
INSERT INTO notifications (ntype, ltype, uid, iid, subid, c_title, c_byuser)
- SELECT 'announce', 't', u.id, t.id, 1, t.title, NEw.uid
+ SELECT 'announce', 't', up.uid, t.id, 1, t.title, NEw.uid
FROM threads t
JOIN threads_boards tb ON tb.tid = t.id
-- get the users who want this announcement
- JOIN users u ON u.notify_announce
+ JOIN users_prefs up ON up.key = 'notify_announce'
WHERE t.id = NEW.tid
AND tb.type = 'an' -- announcement board
AND NOT t.hidden;
diff --git a/util/sql/schema.sql b/util/sql/schema.sql
index 48367e4c..676d6400 100644
--- a/util/sql/schema.sql
+++ b/util/sql/schema.sql
@@ -156,8 +156,7 @@ CREATE TABLE relgraphs (
CREATE TABLE rlists (
uid integer NOT NULL DEFAULT 0,
rid integer NOT NULL DEFAULT 0,
- vstat smallint NOT NULL DEFAULT 0,
- rstat smallint NOT NULL DEFAULT 0,
+ status smallint NOT NULL DEFAULT 0,
added timestamptz NOT NULL DEFAULT NOW(),
PRIMARY KEY(uid, rid)
);
@@ -267,18 +266,20 @@ CREATE TABLE users (
rank smallint NOT NULL DEFAULT 3,
passwd bytea NOT NULL DEFAULT '',
registered timestamptz NOT NULL DEFAULT NOW(),
- show_nsfw boolean NOT NULL DEFAULT FALSE,
- show_list boolean NOT NULL DEFAULT TRUE,
c_votes integer NOT NULL DEFAULT 0,
c_changes integer NOT NULL DEFAULT 0,
- skin varchar(128) NOT NULL DEFAULT '',
- customcss text NOT NULL DEFAULT '',
ip inet NOT NULL DEFAULT '0.0.0.0',
c_tags integer NOT NULL DEFAULT 0,
salt character(9) NOT NULL DEFAULT '',
- ign_votes boolean NOT NULL DEFAULT FALSE,
- notify_dbedit boolean NOT NULL DEFAULT TRUE,
- notify_announce boolean NOT NULL DEFAULT FALSE
+ ign_votes boolean NOT NULL DEFAULT FALSE
+);
+
+-- users_prefs
+CREATE TABLE users_prefs (
+ uid integer NOT NULL,
+ key prefs_key NOT NULL,
+ value varchar NOT NULL,
+ PRIMARY KEY(uid, key)
);
-- vn
@@ -290,7 +291,7 @@ CREATE TABLE vn (
rgraph integer,
c_released integer NOT NULL DEFAULT 0,
c_languages language[] NOT NULL DEFAULT '{}',
- c_platforms varchar(32) NOT NULL DEFAULT '',
+ c_platforms varchar NOT NULL DEFAULT '',
c_popularity real,
c_rating real,
c_votecount integer NOT NULL DEFAULT 0,
@@ -340,6 +341,17 @@ CREATE TABLE vn_screenshots (
PRIMARY KEY(vid, scr)
);
+
+-- vnlists
+CREATE TABLE vnlists (
+ uid integer NOT NULL,
+ vid integer NOT NULL,
+ status smallint NOT NULL DEFAULT 0,
+ added TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ notes varchar NOT NULL DEFAULT '',
+ PRIMARY KEY(uid, vid)
+);
+
-- votes
CREATE TABLE votes (
vid integer NOT NULL DEFAULT 0,
@@ -394,6 +406,7 @@ ALTER TABLE threads ADD FOREIGN KEY (id, count) REFERENCES threads_p
ALTER TABLE threads_posts ADD FOREIGN KEY (tid) REFERENCES threads (id);
ALTER TABLE threads_posts ADD FOREIGN KEY (uid) REFERENCES users (id) ON DELETE SET DEFAULT;
ALTER TABLE threads_boards ADD FOREIGN KEY (tid) REFERENCES threads (id);
+ALTER TABLE users_prefs ADD FOREIGN KEY (uid) REFERENCES users (id) ON DELETE CASCADE;
ALTER TABLE vn ADD FOREIGN KEY (latest) REFERENCES vn_rev (id) DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE vn ADD FOREIGN KEY (rgraph) REFERENCES relgraphs (id);
ALTER TABLE vn_anime ADD FOREIGN KEY (aid) REFERENCES anime (id);
@@ -405,8 +418,15 @@ ALTER TABLE vn_rev ADD FOREIGN KEY (vid) REFERENCES vn
ALTER TABLE vn_screenshots ADD FOREIGN KEY (vid) REFERENCES vn_rev (id);
ALTER TABLE vn_screenshots ADD FOREIGN KEY (scr) REFERENCES screenshots (id);
ALTER TABLE vn_screenshots ADD FOREIGN KEY (rid) REFERENCES releases (id);
+ALTER TABLE vnlists ADD FOREIGN KEY (uid) REFERENCES users (id) ON DELETE CASCADE;
+ALTER TABLE vnlists ADD FOREIGN KEY (vid) REFERENCES vn (id);
ALTER TABLE votes ADD FOREIGN KEY (uid) REFERENCES users (id) ON DELETE CASCADE;
ALTER TABLE votes ADD FOREIGN KEY (vid) REFERENCES vn (id);
ALTER TABLE wlists ADD FOREIGN KEY (uid) REFERENCES users (id) ON DELETE CASCADE;
ALTER TABLE wlists ADD FOREIGN KEY (vid) REFERENCES vn (id);
+
+
+CREATE INDEX releases_vn_vid ON releases_vn (vid);
+CREATE INDEX tags_vn_date ON tags_vn (date);
+
diff --git a/util/updates/update_2.16.sql b/util/updates/update_2.16.sql
new file mode 100644
index 00000000..2d354f03
--- /dev/null
+++ b/util/updates/update_2.16.sql
@@ -0,0 +1,86 @@
+
+-- remove the NOT NULL from rr.minage and use -1 when unknown
+UPDATE releases_rev SET minage = -1 WHERE minage IS NULL;
+ALTER TABLE releases_rev ALTER COLUMN minage SET DEFAULT -1;
+ALTER TABLE releases_rev ALTER COLUMN minage DROP NOT NULL;
+
+
+-- speed up get-releases-by-vn queries
+CREATE INDEX releases_vn_vid ON releases_vn (vid);
+
+
+-- add vnlists table
+CREATE TABLE vnlists (
+ uid integer NOT NULL REFERENCES users (id) ON DELETE CASCADE,
+ vid integer NOT NULL REFERENCES vn (id),
+ status smallint NOT NULL DEFAULT 0,
+ added TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ notes varchar NOT NULL DEFAULT '',
+ PRIMARY KEY(uid, vid)
+);
+
+
+-- load new function(s)
+\i util/sql/func.sql
+
+
+-- convert from rlists.vstat
+INSERT INTO vnlists (uid, vid, status, added) SELECT
+ i.uid, i.vid, COALESCE(MIN(CASE WHEN rl.vstat = 0 THEN NULL ELSE rl.vstat END), 0), MIN(rl.added)
+ FROM (
+ SELECT DISTINCT rl.uid, rv.vid
+ FROM rlists rl
+ JOIN releases r ON r.id = rl.rid
+ JOIN releases_vn rv ON rv.rid = r.latest
+ ) AS i(uid,vid)
+ JOIN rlists rl ON rl.uid = i.uid
+ JOIN releases r ON r.id = rl.rid
+ JOIN releases_vn rv ON rv.rid = r.latest AND rv.vid = i.vid
+ GROUP BY i.uid, i.vid;
+
+
+-- add constraints triggers
+CREATE CONSTRAINT TRIGGER update_vnlist_rlist AFTER DELETE ON vnlists DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE update_vnlist_rlist();
+CREATE CONSTRAINT TRIGGER update_vnlist_rlist AFTER INSERT ON rlists DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE update_vnlist_rlist();
+
+-- remove rlists.vstat and rename rlists.rstat
+ALTER TABLE rlists DROP COLUMN vstat;
+ALTER TABLE rlists RENAME COLUMN rstat TO status;
+
+
+
+-- add users_prefs table
+CREATE TYPE prefs_key AS ENUM ('l10n', 'skin', 'customcss', 'filter_vn', 'filter_release', 'show_nsfw', 'hide_list', 'notify_nodbedit', 'notify_announce');
+CREATE TABLE users_prefs (
+ uid integer NOT NULL REFERENCES users (id) ON DELETE CASCADE,
+ key prefs_key NOT NULL,
+ value varchar NOT NULL,
+ PRIMARY KEY(uid, key)
+);
+
+-- convert from users.* to users_prefs
+INSERT INTO users_prefs (uid, key, value)
+ SELECT id, 'skin'::prefs_key, skin FROM users WHERE skin <> ''
+ UNION ALL
+ SELECT id, 'customcss', customcss FROM users WHERE customcss <> ''
+ UNION ALL
+ SELECT id, 'show_nsfw', '1' FROM users WHERE show_nsfw
+ UNION ALL
+ SELECT id, 'hide_list', '1' FROM users WHERE NOT show_list
+ UNION ALL
+ SELECT id, 'notify_nodbedit', '1' FROM users WHERE NOT notify_dbedit
+ UNION ALL
+ SELECT id, 'notify_announce', '1' FROM users WHERE notify_announce;
+
+-- remove unused columns from the user table
+ALTER TABLE users DROP COLUMN skin;
+ALTER TABLE users DROP COLUMN customcss;
+ALTER TABLE users DROP COLUMN show_nsfw;
+ALTER TABLE users DROP COLUMN show_list;
+ALTER TABLE users DROP COLUMN notify_dbedit;
+ALTER TABLE users DROP COLUMN notify_announce;
+
+
+-- remove size constraint on vn.c_platforms
+ALTER TABLE vn ALTER COLUMN c_platforms TYPE varchar;
+
diff --git a/util/vndb.pl b/util/vndb.pl
index 183cea4d..0fd6e08d 100755
--- a/util/vndb.pl
+++ b/util/vndb.pl
@@ -54,22 +54,42 @@ YAWF::init(
sub reqinit {
my $self = shift;
+ # check authentication cookies
+ $self->authInit;
+
# Determine language
- # if the cookie is set, use that. Otherwise, interpret the Accept-Language header or fall back to English.
- # if the cookie is set and is the same as either the Accept-Language header or the fallback, remove it
- my $conf = $self->reqCookie('l10n');
- $conf = '' if !$conf || !grep $_ eq $conf, VNDB::L10N::languages;
+ my $cookie = $self->reqCookie('l10n');
+ $cookie = '' if !$cookie || !grep $_ eq $cookie, VNDB::L10N::languages;
+ my $handle = VNDB::L10N->get_handle(); # falls back to English
+ my $browser = $handle->language_tag();
+ my $rmcookie = 0;
+
+ # when logged in, the setting is kept in the DB even if it's the same as what
+ # the browser requests. This is to ensure a user gets the same language even
+ # when switching PCs
+ if($self->authInfo->{id}) {
+ my $db = $self->authPref('l10n');
+ if($db && !grep $_ eq $db, VNDB::L10N::languages) {
+ $self->authPref(l10n => undef);
+ $db = '';
+ }
+ $rmcookie = 1 if $cookie;
+ if(!$db && $cookie && $cookie ne $browser) {
+ $self->authPref(l10n => $cookie);
+ $db = $cookie;
+ }
+ $handle = VNDB::L10N->get_handle($db) if $db && $db ne $browser;
+ }
- $self->{l10n} = VNDB::L10N->get_handle(); # this uses I18N::LangTags::Detect
+ else {
+ $rmcookie = 1 if $cookie && $cookie eq $browser;
+ $handle = VNDB::L10N->get_handle($cookie) if $cookie && $browser ne $cookie;
+ }
$self->resHeader('Set-Cookie', "l10n= ; expires=Sat, 01-Jan-2000 00:00:00 GMT; path=/; domain=$self->{cookie_domain}")
- if $conf && $self->{l10n}->language_tag() eq $conf;
- $self->{l10n} = VNDB::L10N->get_handle($conf) if $conf && $self->{l10n}->language_tag() ne $conf;
-
-
- # check authentication cookies
- $self->authInit;
+ if $rmcookie;
+ $self->{l10n} = $handle;
- # check for IE6
+ # check for IE
if($self->reqHeader('User-Agent') && $self->reqHeader('User-Agent') =~ /MSIE [67]/
&& !$self->reqCookie('ie-sucks') && $self->reqPath ne 'we-dont-like-ie') {
# act as if we're opening /we-dont-like-ie6 (ugly hack, until YAWF supports preventing URL handlers from firing)