diff options
59 files changed, 7201 insertions, 2225 deletions
@@ -1,9 +1,9 @@ /data/config.pl /data/log/ +/static/f/script.js /static/s/*/style.css /static/s/*/boxbg.png /static/cv/ -/static/rg/ /static/sf/ /static/st/ /static/robots.txt @@ -1,3 +1,37 @@ +2.8 - 2009-10-24 + - Converted relation graphs to use inline SVG + - Relation graphs now use the color scheme of selected skin + - VN relations are translatable in both the interface and the graphs + - Full date is displayed in graphs instead of only month/year + - Converted to ENUM data type: + - vn_relations.relation + - anime.type + - changes.type + - releases_rev.type + - releases_media.medium + - New language: Hungarian + - Complete rewrite of the Javascript code: + - Intended to be less error prone, more maintainable, and easier to make + 'XHTML compliant' in the future (currently still has some issues here). + - Improved spoiler selection on /v+/tagmod + - Everything merged into one file. + - Optionally minified (using JavaScript::Minifier::XS) + - Language strings are translatable + - Information is automatically synchronised with data/global.pl + - Changed language selector into a Javascript dropdown + - Added producer role (developer/publisher) to releases + - Display number of unread posts in "My messages" (instead of total threads) + - Optimized dbUserGet (mostly for the user list) + - All languages are listed on /r and /v/all instead of only those in use + - Copy over search query when switching search type (htmlSearchBox) + - Fixed obscure sorting bug on user VN list + - Fixed calculation of tags_vn_bayesian.spoiler + - Fixed bug with unhiding a producer entry + - Set 'no spoilers' as default spoiler level for tags + - Added Czech and Hungarian interface translation + - Producer relations + - Increased tag dropdown search results to 15 + 2.7 - 2009-09-24 - Improved styling of the threeboxes layout - Blacklist a users' votes from the VN vote statistics @@ -12,7 +12,7 @@ Requirements global requirements: Linux, or an OS that resembles Linux. Chances are VNDB won't run on Windows. - PostgreSQL 8.3 recommended, 8.1 may also work + PostgreSQL 8.3+ perl 5.10 recommended, 5.8 may also work A webserver that works with YAWF (lighttpd and Apache are known to work) @@ -41,11 +41,20 @@ Requirements Maintenance: PerlIO::gzip RG: + XML::Parser + XML::Writer graphviz (/usr/bin/dot is used by default) Sitemap: XML::Writer PerlIO::gzip + util/skingen.pl + Image::Magick + CSS::Minifier::XS (optional, minimizes CSS output) + + util/jsgen.pl + JavaScript::Minifier::XS (optional, minimizes JS output) + Contact diff --git a/data/docs/10.cs b/data/docs/10.cs new file mode 100644 index 00000000..d8eebb52 --- /dev/null +++ b/data/docs/10.cs @@ -0,0 +1,49 @@ +:TITLE:Tagovací systém +:INC:notfinished +:INC:index + + +:SUB:Úvod +<i>TODO</i> + + +:SUB:Hodnocení tagů +<p> + I když celkem vzato může kdokoliv uvést jakýkoliv tag k jakékoliv vizuální novele, pamatujte prosím na následující věci, + pokud tagy hodnotíte: +</p> +<ul> + <li>Před přidáním tagu k nějaké vizuální novele si přečtěte jeho popis, některé tagy nemusí znamenat to, + co si myslíte, že znamenají.</li> + <li>Buďte tak přesní, jak jen můžete být. Kupříkladu, pokud má hra dramatické prvky, zkuste spíše najít + více specifické odvozené tagy, které popisují jaký typ dramatických událostí může hráč očekávat, + než přidat tag "Drama".</li> + <li>Tagu může být přiděleno hodnocení v rozsahu -3 až 3. Negativní hodnocení znamená, že s tagem + nesouhlasíte, kde -1 bude "Nemyslím si, že tento tag je opravdu ve hře" a -3 bude znamenat, že plně + nesouhlasíte a tag by neměl být použit vůbec. Pozitivní hodnocení tagu funguje naopak a znamená, že + s tagem pro danou vizuální novelu souhlasíte.</li> + <li>Některé tagy se mohou chovat jako spoiler co se děje VN týká, to může být označeno ohodnocením spoiler + statusu. Je vysoce doporučeno nenechávat tento jako "Neutral", a opravdu se snažit poskytnout nějaké vodítko + ohledně toho, zda tasg vůbec není spoiler nebo zda vizuální novelu malinko prozrazuje.</li> +</ul> + + +:SUB:Doporučení k tagům +<p> + Nové tagy si může vyžádat kdokoliv, ale než se dají procházet musí být schváleny moderátorem. + Platí následující pravidla: +</p> +<ul> + <li>Jména tagů musí mluvit za sebe. I když význam tagu může být zřejmý v okamžiku, kdy se podíváte + na jeho umístění ve stromu tagů, v okamžiku, kdy je zobrazen na stránce VN, je vidět pouze jeho jméno + a ne mateřské tagy. Jméno by tedy mělo plně popisovat co tag znamená a pro co by měl být použit. + Například: Tag k popisu mužského hlavního hrdiny by měl být pojmenován plně "Male protagonist" (Muž protagonista), + i když jeho mateřský tag je "Characters > Protagonist" (Postavy > Protagonista).</li> + <li>Tagy by měly být užívány k popisování objektivních informací o VN. Subjektivní tagy jako + "Awesome protagonist" (Úžasný protagonista), které by pro každého znamenaly něco jiného, nejsou povoleny.</li> + <li>Tagy by měly popisovat pouze informace, které VNDB ještě nezahrnuje. Tudíž tagy ohledně informací o vydání či + o délce VN nejsou povoleny.</li> + <li>Výše uvedené pravidlo platí i pro jakékoliv další informace o VN, které ještě nejsou na VNDB zahrnuty, + jako lidé v produkčním týmu nebo stav licence v jakékoliv zemi.</li> +</ul> + diff --git a/data/docs/2.cs b/data/docs/2.cs new file mode 100644 index 00000000..b7d3c7eb --- /dev/null +++ b/data/docs/2.cs @@ -0,0 +1,184 @@ +:TITLE:Přidání/editace vizuální novely +:INC:index + +:SUB:Kdy přidat vizuální novelu +<p> + Aby hra byla zahrnuta v této databázi, musí pro vyprávění příběhu konzistentně + používat vyprávěcí styl. Příklady zahrnují popisy <a + href="http://s.vndb.org/sf/58/258.jpg">vizuální</a>, <a + href="http://s.vndb.org/sf/63/2663.jpg">událostí</a>, <a + href="http://s.vndb.org/sf/74/274.jpg">akcí postav</a> nebo <a + href="http://s.vndb.org/sf/49/449.jpg">myšlenek</a>. Toto vyprávění musí být + jednou ze známých metod prezentace vizuálních novel, jako jsou <a + href="http://s.vndb.org/sf/40/3440.jpg">ADV</a>, <a + href="http://s.vndb.org/sf/52/3152.jpg">NVL</a> a jejich <a + href="http://s.vndb.org/sf/39/339.jpg">obdoby</a>.<br /> + <br /> + Hra, která 'působí' jako VN, i když plní jinou funkci než striktně vyprávění + příběhu (jako například vysoce interaktivní simulace) se může jako VN kvalifikovat + pokud je buď celá vyprávěna v ADV/NVL stylu nebo jsou její další herní prvky + jasně podřízeny prvkům textovým (prezentovaným v ADV/NVL stylu) (např. akční scény + v 3D hrách nemohou být podřízeny jejich textovým prvkům, jelikož vizuální prvky + jsou jasně 'účelem' hry; 'podzemí' ve hrách Princess Maker, která mají jednoduchou + grafiku a dají se všechna přeskočit, by podřízena byla). +</p> + +:SUB:Obecné informace +<dl> + <dt>Název (romaji)</dt><dd> + Název by měl korespondovat s názvem původního vydání.<br /> + Pokud název užívá latinky použijte našich <a href="/d5.2">doporučení ke kapitalizaci</a>.<br /> + Jinak <a href="/d5.1">romanizujte</a> podle našich doporučení. + </dd><dt>Originální název</dt><dd> + Pokud je název oficiálně odlišný od názvu v romaji (obvykle kvůli odlišným znakovým sadám), + umístěte sem originální název. + </dd><dt>Aliasy</dt><dd> + Vizuální novely mohou být známy pod různými názvy, použijte tedy toto pole pro přidání jakýchkoliv + aliasů a akronymů užívaných na internetu. Oficiální názvy vydání by se sem přidávat neměla, protože + jsou již zapsána ve vydáních. + </dd><dt>Popis</dt><dd> + Krátký popis hlavního příběhu. + </dd><dt>Délka</dt><dd> + Velmi hrubý odhad času potřebného k dohrání všech konců vizuální novely. + K určení délky hry je často lepší ignorovat délku branou v čase jako takovém a místo toho + ji porovnat s jinými hrami, které jste hráli. Všechno je to přeci jen relativní. + </dd><dt>Externí odkazy</dt><dd> + Odkazy k externím zdrojům o této vizuální novele. Abyste získali URL, navštivte tyto stránky + (<a href="http://en.wikipedia.org/">Wikipedia</a>, <a href="http://novelnews.net/"> + Novelnews.net</a>, <a href="http://renai.us/">Renai.us</a> a + <a href="http://visual-novels.net/">Visual-novels.net</a>), + najděte stránku o hře a určete ID nebo jméno této stránky, které je třeba vyplnit + do textových polí. Nepište celá URL, potřebujeme jen jejich malou část! + </dd><dt>Vztahy k anime</dt><dd> + Některé vizuální novely (jako <a href="/v4">Clannad</a> a <a href="/v3">Utawarerumono</a>) + mají anime adaptace a některé vizuální novely byly adaptovány z anime sérií. Použijte + toto pole pro upřesnění těchto anime se vztahy k vizuálním novelám.<br /> + Anime by mělo být specifikováno za užití <a href="http://anidb.net/">AniDB</a> ID. + Pro přidání anime ho najděte na AniDB a vložte číselné ID anime (dá se najít v + <i>aid=xxx</i> části URL) do editovatelného pole. Několik ID by mělo být odděleno mezerou.<br /> + Pokud má vizuální novela vztah k jiné vizuální novele a obě hry mají anime adaptaci, + to samé anime nemusí být přidáno k oběma hrám. Například <a href="/v264">Da Capo</a> má dvě + anime adaptace (<a href="http://anidb.net/a837">837</a>, <a href="http://anidb.net/a2832">2832</a>), + stejně jako <a href="/v266">Da Capo II</a>. (<a href="http://anidb.net/a5419">5419</a>, + <a href="http://anidb.net/a5652">5652</a>) Ale vztahy pro Da Capo II se nemusí přidávat + k Da Capu a vice versa - o to se postarají vnitřní vztahy vizuálních novel.<br /> + VNDB po potvrzení nového AniDB ID automaticky zanese informace o anime a bude je prezentovat + na stránce. Tato akce zabere několik minut až - přinejhorším - několik hodin. Po tento čas + uvidíte ID anime, ale ne jeho název a odkazy na AnimeNFO a Anime News Network. Není možné + tyto informace přidat ručně, budou zaneseny automaticky! + </dd> +</dl> + + +:SUB:Kategorie +<i> + Systém kategorií byl nahrazen tagy. Tato sekce je zde kvůli místu, aby se zachovala posloupnost + číslování. +</i> + + +:SUB:Obrázek +<p> + Každá vizuální novela by měla mít obrázek, preferován je oficiální art z obalu + některého vydání. V některých případech, hlavně u doujin her, pokud není žádný + oficiální art z obalu dostupný, může být použitý obecný obrázek ze stránek hry + nebo screenshot ze hry samotné.<br /> + Obrázky mohou být nahrány v JPEG nebo PNG formátu a neměly by být větší 500kB. + Všechny obrázky větší 256x400 pixelů budou automaticky zmenšeny, aby na stránku + pasovaly. Pamatujte, že zmenšení se děje odděleně, takže obrázek neuvidíte + hned po nahrání. Tento proces obvykle zabere jen pár vteřin a obrázek by měl být + dostupný krátce po potvrzení formuláře. + <br /><br /> + NSFW varování by mělo být použito v případě, kdy obrázek z obalu není vhodný pro + prostředí přátelských pracovišť. K určení toho, co je "safe for work" a co ne + použijte následující doporučení: +</p><ul> + <li>Nahota je "safe" pouze pokud jsou sexuální znaky zakryty nebo schovány</li> + <li>Bikini, pantsu aa ostatní spodní prádlo jsou "safe" jen pokud neobsahují žádné + implicitní naznačení pohlavní anatomie</li> + <li>Pokud je póza postavy přehnaně sexuálně provokativní, pak je to NSFW</li> + <li>Pokud je zobrazeno několik postav a jsou zobrazeny ve fyzickém kontaktu, který + by se dal považovat za sexuální kontakt (či předehru k němu), pak je obrázek také NSFW. + </li> +</ul><p> + I s těmito doporučeními nemusí být občas jednoduché určit zda je obrázek "safe" nebo ne. + Pokud jste v pochybách, je často nejlepší zvolit NSFW. +</p> + + +:SUB:Vztahy +<p> + Vztahy mezi vizuálními novelami se dají použít k určení toho, jak jsou mezi sebou hry + provázány.<br /> + Když přidáváte vztah, k druhé vizuální novele bude automaticky přidán vztah opačný. + Např. pokud označíte hru x jako pokračování ke hře y, pak hra y bude automaticky + označena jako prequel ke hře x. Nemusíte editovat obě hry.<br /> + Specifikujte pouze <b>přímé</b> vztahy. Pokud má hra 1 zapsán nějaký vztah ke hře 2 + a hra 2 má vztah ke hře 3, pak hra 3 nemusí nutně mít vztah ke hře 1. Toto se může + na první pohled zdát matoucí, ale dá se to pochopit z grafu vztahů. Když editujete + vydání, vždy se snažte myslet na vztahy mezi všemi hrami, které je mají - jak je + ukázáno v grafu - místo toho, abyste se dívali jen na tu jedinou vizuální novelu, + kterou editujete. + <br /> + Máme pevný seznam vztahů, ze kterých se dá vybrat (jak je popsán níže). Jako s mnoha + vztahy, i ty mezi hrami mohou být ve skutečnosti mnohem složitější, než jak je tyto + možnosti mohou popsat. Pokud si nejste jisti, kterou možnost vybrat, pak jedoduše + vyberte tu, o které si myslíte, že je nejblíže vyjádření skutečného vztahu. +</p> +<dl> + <dt>Pokračování</dt><dd> + Pokračování příběhu. <=><i>Prequel</i>. + </dd><dt>Prequel</dt><dd> + Příběh se odehrává před příběhem originálním.<=><i>Pokračování</i>. + </dd><dt>Stejný setting</dt><dd> + Stejný vesmír, svět, realita a časová linie, ale kompletně odlišné postavy. + Definici "settingu" není vždy jednoduché definovat, ale obvykle to znamená, že + pokud místa či předměty, neexistující v reálném světě, popsané v jedné hře, + existují také ve hře druhé, pak se dá použít tento vztah. + </dd><dt>Alternativní verze</dt><dd> + Stejný setting, stejné postavy, ale příběh je odvyprávěn odlišně. + </dd><dt>Sdílí postavy</dt><dd> + Odlišný příběh, který ale sdílí některé postavy. + </dd><dt>Vedlejší příběh</dt><dd> + Příběh se odehrává někdy během příběhu hlavního. <=><i>Parent story</i> + </dd><dt>Hlavní příběh</dt><dd> + Opak <i>vedlejšího příběhu</i>. + </dd><dt>Stejná série</dt><dd> + Hry jsou součástí jedné herní série. + </dd><dt>Fandisk</dt><dd> + <a href="http://en.wikipedia.org/wiki/Fan_disc">Fandisk</a>. + </dd><dt>Originální hra</dt><dd> + Opak fandisku. + </dd> +</dl> + + +:SUB:Screenshoty +Každá jedna vizuální novela může mít až 10 screenshotů. Screenshoty mohou být nahrány v JPEG a PNG +formátu a měly by vždy být v souladu s těmito doporučeními: +<ul> + <li>Všechny obrázky musí být v nejvyšším původním rozlišení VN. Původní rozlišení je takové rozlišení, + pro které jsou dělány bitmapové obrázky, což je 640x480 nebo 800x600 pro většinu vizuálních novel. + Jakékoliv zmenšování nebo zvětšování, ať už provedené enginem hry samotné nebo ručně za použití + editoru obrázků, <b>není</b> povoleno!</li> + <li>Všechny obrázky musí být screenshoty <b>hry samotné</b>, to znamená aby všechny okraje oken byly + odstraněny před nahráním. Nezajímají nás vaše těžce modifikované okraje oken ani vaše úžasné rozložení + AGTH.</li> + <li>Alespoň polovina screenshotů by měly být screenshot přímo ze hry jako takové. Což znamená, že by měly + obsahovat sprity postav nebo nějakou formu dialogu nebo uživatelské rozhraní. Jen CG obrázky událostí + bez jakéhokoliv textu nebo rozhraní jsou povoleny pro demonstraci stylu krslení, ale neměly by mezi + ostatními screenshoty převažovat.</li> + <li>Nahrávání screenshotů s erotickým obsahem je povoleno tak dlouho, dokud je poměr erotických a neerotických + screenshotů jakžtakž v souladu s poměrem erotického a neerotického obsahu ve VN samotné. Například pokud má + desetihodinová VN jen dvě desetiminutové H-scény, pak by asi nebylo patřičné nahrát více jak 2 až tři erotické + obrázky. Na druhou stranu, pokud je VN převážně o sexu, pak nebude dávat smysl nahrát erotický screenshot + jen jeden. Je doporučeno - i pro hry pouze o sexu - mít alespoň jeden neerotický screenshot.</li> + <li>Snažte se o co nejméně spoilerů. Je nemožné nahrát screenshoty, ktré jsou kompletně bez spoilerů, ale + je možné snížit množství spoilerů na minimum. Tedy jsou preferovány screenshoty ue začátku VN a záběry + z pozdějších částí hry jsou v pořádku pokud neodhalují žádné důležité informace z příběhu.</li> + <li>Obrázky, které jsou NSFW by tak měly být označeny, vizte doporučení k <a href="#4">obrázkům</a> výše.</li> + <li>Screenshoty musí být neoznačené: neměly by obsahovat copyright, internetové adresy nebo jiný text, který není + přítomný v původním screenshotu.</li> + <li>Screenshoty by ideálně měly být z anglického nebo z originálního (japonského) vydání. Ostatní jazyky + jsou pvoloeny, ale množství těchto obrázků by mělo být zachování na minimu.</li> +</ul> diff --git a/data/docs/3 b/data/docs/3 index f3f5fbdf..4ac8ded5 100644 --- a/data/docs/3 +++ b/data/docs/3 @@ -132,8 +132,22 @@ :SUB:Producers <p> - The companies/groups/individuals involved in the creation, development or translation - of this release. Does not include distributors. + The companies/groups/individuals involved in the development or publishing of + this release. Does not include distributors. The following roles can be selected: + <dl> + <dt>Developer</dt><dd> + The producer involved in the creation of the game itself, not necessarily of + this specific release. Keep in mind that producers that have made modifications + to a game but have not made the game itself should NOT be listed as developers. + </dd><dt>Publisher</dt><dd> + The producer responsible for publishing this specific release. The publisher may have + made modifications to the game (e.g. translating all text or porting to a different + platform), but was not involved in the creation process. + </dd><dt>Both</dt><dd> + When the release is developed and published by the same producer. This is often + true for doujin games and the first releases of commercial games. + </dd> + </dl> </p> diff --git a/data/docs/3.cs b/data/docs/3.cs new file mode 100644 index 00000000..2e4b5369 --- /dev/null +++ b/data/docs/3.cs @@ -0,0 +1,138 @@ +:TITLE:Přidání/editace vydání +:INC:index + + +:SUB:Kdy přidat vydání +<p> + 'Vydání' je produkt - buď fyzický nebo digitální - obsahující vizuální novelu (i část). + To vylučuje soundtracky, drama CD, fandisky a ostatní produkty, které neobsahují + vizuální novelu samotnou.<br /> + Všechna vydání by měla být přidána samostatně. Například limitovaná a běžná edice + by neměly být kombinovány do jednoho vydání, i když sdílí datum vydání a obsah. + Pro komerční hry se dají jednotlivá vydání rozlišit podle jejich kódu JAN/UPC/EAN. +</p> + + +:SUB:Obecné informace +<dl> + <dt>Typ</dt><dd> + Je vydání kompletní, částečné nebo trial verze? + Kompletní vydání obsahuje vše. + Částečná vydání obsahují něco ze hry, ale stále jsou věci, které čekají na vydání. + Trial verze jsou výrazně zkrácená volná vydání, aby si člověk mohl hru vyzkoušet + před její koupí. Občas jsou trial verze sestříhány pro stažení na webu a nepředstavují + tak úplně finální produkt.<br /> + V případě patche pro překlad by měl typ značit zda patch slouží k překladu celé hry + (kompletní, complete) nebo pouze jejích částí (částečný, partial). + </dd><dt>Patch</dt><dd> + Použijte toto zaškrtnutí k označení toho, že vydání je (překladatelským) patchem, + použitým k opatchování jiného vydání. + </dd><dt>Freeware</dt><dd> + Toto zaškrtněte, pokud je hra volně ke stažení (či jinak volně dostupná) za nulovou cenu. + </dd><dt>Doujin</dt><dd> + Publikováno doujin kruhem, amatérskou skupinou nebo jedincem, v protikladu k právnické osobě, + jakou je například firma. Toto pole je ignorováno, pokud je typ vydání patch. + </dd><dt>Název (romaji)</dt><dd> + Název vydání latinkou (za užití romanizace nebo překladu) + </dd><dt>Originální název</dt><dd> + Pokud je oficiální název odlišný (obvykle kvůli jiným znakovým sadám), napište sem originální název. + </dd><dt>Jazyk</dt><dd> + V jakém jazyce je toto vydání? Uveďte jazyk, ve kterém je napsána většina hry. + </dd><dt>JAN/UPC/EAN</dt><dd> + <a href="http://en.wikipedia.org/wiki/Global_Trade_Item_Number">GTIN</a> kód produktu. + Často zvaný "JAN" pro japonská vydání, "UPC" v USA a Kanadě a "EAN" v Evropě. Systém automaticky + pozná typ podle kódu a na stránce vydání uvede příslušný termín. + </dd><dt>Číslo v katalogu</dt><dd> + Číslo v katalogu jak je mu přiřazeno producentem. Často se používá pro identifikaci vydání + na webshopech a obvykle se dá najít někde na obalu produktu. + </dd><dt>Oficiální internetová stránka</dt><dd> + URL oficiálních stránek pro tento produkt. + </dd><dt>Datum vydání</dt><dd> + Pro komerční hry datum prodeje. + Pro vše ostatní datum, kdy bylo vydání poprvé dostupné. + Pokud byla hra uvedena na internetové stránce, pak datum příspěvku, který ji oznámil. + </dd><dt>Věková přístupnost</dt><dd> + Minimální věková přístupnost pro vydání. Pro většinu vydání je toto specifikováno na obalu + nebo na internetových stránkách produktuT. + </dd><dt>Poznámky</dt><dd> + Další různé užitečné věci. + Obecně vzato sem patří extra (ale ne předobjednávkové bonusy) a informace o vývoji/překladu. + </dd> +</dl> + + +:SUB:Formát +<dl> + <dt>Rozlišení</dt><dd> + Primární/původní rozlišení obrazovky pro hru. + </dd><dt>Hlas</dt><dd> + Značí, zda toto vydání obsahuje nadabování ve VN/ADV částech hry. + <i>Plně s hlasem</i> znamená, že všechny postavy (obvykle až na hlavního hrdinu + a některé vedlejší postavy) jsou nadabovány ve všech scénách. <i>S hlasem pouze v ero scénách</i> + mluví samo za sebe, a <i>Částečně s hlasem</i> by mělo být použito, pokud je ve hře + nějaký dabing, ale pouze pro hlavní postavu nebo pouze v některých scénách. + </dd><dt>Animace</dt><dd> + To, zda má hra animace se dá upřesnit ve dvou částech: v jedné pro normální příběhovou část + a v jedné pro ero scény, pokud je hra má. <i>Jednoduchými animacemi</i> odkazujeme na + (obvykle opakující se) efekty jako padající lístky nebo sníh v pozadí nebo animované + obličejové výrazy jako mrkání očí a pohyby úst. + <i>Plně animovanými scénami</i> pak myslíme neopakující se scény v anime stylu. Některé hry + jsou v tomto stylu celé, jiné mají plně animovaných jen několik scén. Efekty jako pohyb + postav po obrazovce, základní zoomování a třesení obrázkem v pozadí nebereme jako "animace". + Minihry a jiné herní prvky jsou také vyloučeny, uvažujeme zde pouze příběhové a ero části (v ADV/VN stylu). + </dd> +</dl> +<p> + <b>POZNÁMKA</b>: Pole rozlišení, hlasu a animace nemají žádný význam pro patche a pro tato vydání + by měla zůstat prázdná. + <br /><br /> + <b>Platforma</b><br /> + Platformy, pro které byl produkt vydán.Nezahrnuje emulované platformy (jako Playstation 2 hry na Playstationu 3) + nebo WINE. DVD Přehrávačem se myslí hry hratelné jako běžné DVD video (DVDPG) a toto by nemělo být zaměňováno +s DVD jako s médiem. + <br /><br /> + <b>Média</b> +</p> +<dl> + <dt>Blu-ray</dt><dd> + Blu-ray Disk, obvykle 30-60GB+. Vyžaduje Blu-ray přehrávač/jednotku. Playstation 3 hry + jsou obvykle Blu-ray. + </dd><dt>CD</dt><dd> + CD-ROM, obvykle 700MB. + </dd><dt>DVD</dt><dd> + DVD5, obvykle 4.5GB, nebo DVD9, obvykle 9GB. DVDPG hry jsou na DVD. + </dd><dt>Disketa</dt><dd> + 5 1/4" nebo 3 3/4", ne větší než 1.44MB. + </dd><dt>GD</dt><dd> + Hry na Dreamcast jsou obvykle na GD discích. + </dd><dt>Stažení z internetu</dt><dd> + Cokoliv bez fyzického obalu, tedy získáno stažením přes síť. + </dd><dt>Paměťová karta</dt><dd> + Jakákoliv varianta SD (Secure Digital) karty, včetně MMC varianty, Compact Flash disku nebo "USB Sticku". + Hlavní rozdíl mezi těmito a Cartridgí (níže) je v tom, že paměťová karta je přepisovatelná (RW). + </dd><dt>Cartridge</dt><dd> + Srovnejte s paměťovou kartou (výše). Pouze pro čtení. Famicom (NES), Super Nintendo (SNES), + Game Boy Advanced (GBA) a Nintendo DS používají cartridge. + </dd><dt>Nintendo Optický Disk</dt><dd> + Optické disky, které nejsou CD ani DVD, a které používají některé konzole Nintendo. + </dd><dt>Ostatní</dt><dd> + Jakékoliv médium, které se nedá zahrnout pod jakýkoliv typ z výše uvedených by měl použít tuto volbu. + Tato by ale neměla být používána jen tak a je možné, že si její užití budete muset obhájit. + </dd><dt>UMD</dt><dd> + Universal Media Disk, obvykle 2.2GB. Tento formát používá Playstation Portable. + </dd> +</dl> + + +:SUB:Producenti +<p> + Firmy/skupiny/jedinci, kteří se podíleli na tvorbě, vývoji nebo překladu tohoto vydání. + Nezahrnuje distributory. +</p> + + +:SUB:Vztahy k vizuálním novelám +<p> + Vizuální novely, které toto vydání (ať už částečně nebo plně) zahrnuje. +</p> + diff --git a/data/docs/4 b/data/docs/4 index 927b7d2d..45acb927 100644 --- a/data/docs/4 +++ b/data/docs/4 @@ -35,3 +35,38 @@ </dd> </dl> + +:SUB:Relations +These relations provide information about which producers are related to each +other, and how they are related. Choosing the correct relation may be a bit +confusing, check the relation graph of the producer entry in case of doubt. +The following relations are defined: +<dl> + <dt>Formerly</dt><dd> + The current producer was earlier known as the selected producer. This can be + because of a name change, or when the earlier producer disbanded and the same + people started again under a different name. + </dd><dt>Succeeded by</dt><dd> + Reverse of <i>Formerly</i>. + </dd><dt>Subsidiary</dt><dd> + Selected producer is a subsidiary of the current producer. A subsidiary is + still "part" of the parent producer, but consist of a different group and + publishes under a different name. + </dd><dt>Parent producer</dt><dd> + Reverse of <i>Subsidiary</i>. + </dd><dt>Imprint</dt><dd> + Selected producer is an imprint of the current producer. Simply put, an + "imprint" is a different name for the same group of people, used when + publishing games. See <a href="http://en.wikipedia.org/wiki/Imprint">Wikipedia</a> for more info. + </dd><dt>Parent brand</dt><dd> + Reverse of <i>Imprint</i>. + </dd><dt>Spawned</dt><dd> + The selected producer was formed by former members of the current producers. + The difference with the <i>Formerly</i> relation is that the producer where the + members came from is still alive. + </dd><dt>Originated from</dt><dd> + Reverse of <i>Spawned</i>. + </dd> +</dl> + + diff --git a/data/docs/4.cs b/data/docs/4.cs new file mode 100644 index 00000000..2c4644ac --- /dev/null +++ b/data/docs/4.cs @@ -0,0 +1,36 @@ +:TITLE:Přidání/editace producenta +:INC:index + + +:SUB:Kdy přidat producenta +<p> + Záznam o producentovi by měl být vytvořen, pokud přidáváte vydání vizuální novely vytvořené + producentem, který ještě není zapsán v databázi. Producenti, kteří nemají v databázi zapsána + žádná vydání mohou být po čase vymazáni, takže záznam vytvořte pouze pokud ho provážete + s vydáním. Aby byl producent na seznamu, potřebuje alespoň jeden dokončený produkt. Což + znamená, že co se překladatelských projektů týká, přidávejte skupinu jako producenta až poté, + co opravdu dokončila projekt. +</p> + + +:SUB:Obecné informace +<dl> + <dt>Typ</dt><dd> + Typ producenta. + </dd><dt>Jméno (romaji)</dt><dd> + Jméno producenta v latince, za užití <a href="/d5.1">romanizace</a> + v případě, že originální jméno již nebylo latinkou. <a href="/d5.2">Kapitalizace</a> + je pro toto pole také důležitá. + </dd><dt>Originální jméno</dt><dd> + Pokud Jméno (viz výše) bylo romanizováno, nezapomeňte zde zapsat jméno originální (pravděpodobně japonské). + </dd><dt>Aliasy</dt><dd> + Ostatní jména, pod kterými je producent znám. Více aliasů by mělo být odděleno čárkou. + </dd><dt>Primární jazyk</dt><dd> + Jazyk, ve kterém producent ponejvíce pracuje. Standardně je to japonsky. + </dd><dt>Internetová stránka</dt><dd> + Oficiální webová stránka producenta. + </dd><dt>Popis</dt><dd> + Historie producenta nebo popis toho, jaké hry tvoří. + </dd> +</dl> + diff --git a/data/docs/5.cs b/data/docs/5.cs new file mode 100644 index 00000000..f30cb323 --- /dev/null +++ b/data/docs/5.cs @@ -0,0 +1,82 @@ +:TITLE:Doporučení k editacím +:INC:index + + +:SUB:Romanizace +<p> + Většina čtenářů VNDB jsou anglicky mluvící fanoušci vizuálních novel. Při psaní + databáze předpokládáme, že čtenáři, na které se zaměřujeme, neumí číst či + nerozpozná cokoliv, co není romanizováno. Proto je romanizace užita na mnoha + místech databáze: Hlavní názvy vizuálních novel, vydání a producentů by všechny + měly být příslušně romanizovány, pokud už v takovém stavu nebyly předtím.<br /> + Abychom udrželi databázi v jednotném stylu, rozhodli jsme se pro + <a href="http://en.wikipedia.org/wiki/Hepburn_romanization">Hepburnovu romanizaci</a> + na všech místech. Toto je také konzistentní s + <a href="http://wiki.anidb.net/w/Romanisation">romanizací AniDB</a>. + <br /> + Pro anglická (nebo jiná cizí) slova v neromanizovaném textu by měla původní slova + být použita pokud tak bylo původně zamýšleno. Pokud jsou tato slova v neromanizovaném + skriptu, měla by být romanizována vhodnou romanizační technikou. +</p> + + +:SUB:Kapitalizace +<p> + Občas se stává, že oficiální názvy nebo jména jsou kompletně v malých nebo + velkých písmenech. Pokud pro tuto volbu není žádný pořádný důvod (např. to, že + název je akronym), tyto názvy a jména by měly být příslušně převedeny do + normální anglické kapitalizace (jak je popsáno + <a href="http://en.wikipedia.org/wiki/Capitalization">na Wikipedii</a>) před + zapsáním do databáze.<br /> + Toto se týká jen romanizovaných názvů a hlavních názvů vizuálních novel, pole + 'Originální název' u producentů a jednotlivých vydání by stále mělo používat + oficiální kapitalizaci. +</p> + + +:SUB:Pořadí jmen +<p> + Ve většině anglicky mluvících zemí se jména píší v pořadí "křestní jméno, příjmení". + Toto nazýváme "západním psaním". + V angličtině je obvyklé na někoho volat jeho křestním jménem, pokud ho znáte dobře, jinak se použije příslušný titul a příjmení. + <br /> + V japonštině (a i v dalších jazycích) se jména píší v pořadí "příjmení, křestní jméno". + Toto nazýváme "japonským psaním". + V japonštině na někoho voláme jedním z jeho jmen s použitím příslušného sufixu. + <br /> + Aby pomohly anglicky mluvícím, některé zdroje (např. Wikipedia) užívají japonských jmen v západním psaní. + Zde na vndb.org preferujeme užívání původního psaní, které používala hra. + <br /> + Tedy, pokud byla hra původně japonská s japonskými jmény, použije se japonské psaní. + <br /> + Pokud byla hra původně anglická nebo postava měla kompletně cizí jméno (John Smith), použije se západního psaní. +</p> + + +:SUB:Shrnutí editací +<p> + Editování jakékoliv stránky zahrnuje volitelné pole "Shrnutí editace". + <br /> + To vám umožňuje vysvětlit o čem je vaše editace bez zbytečného zahlcování hlavní stránky. + <br /> + Můžete se zmínit o tom, proč jste pozměnili kategorii, změnili odkaz, nebo ukázat, odkud jsou vaše informace (aby je další přispěvatelé mohli ověřit). + <br /> + Možná chcete přidat vizuální novelu do databáze, ale nejste schopni pro ni najít další informace. + Pokud to zde napíšete, snad je jiný přispěvatel jednou najde a stránku dokončí. +</p> + + +:SUB:Popisy z vnějších zdrojů +<p> + Je možné přidat popis nebo poznámku k vizuální novele, producentovi a jednotlivým vydáním + v databázi. Přestože jsou preferovány původní popisy, užití citovaných popisů z vnějších zdrojů + je povoleno, pokud je na původní zdroj patřičně odkázáno. Preferovaná forma odkázání + na vnější zdroj je přidáním následujícího vzorového kódu pod popis:<br /> + [From [url=<b>URL</b>]<b>title or author</b>[/url]]<br /> + Což je aplikováno jako, například, '[From <a href="http://en.wikipedia.org/Wiki/Ever17">Wikipedia</a>]' + pro popisy převzaté z Wikipedie. Pokud popis není převzta doslova, ale má nějaké změny, + pak by 'From' mělo být nahrazeno něčím podoným a naznačujícím, že popis byl pozměněn, např. 'Edited from', 'Based on'.<br /> + Jelikož není povoleno odkazovat na stránky poskytující nebo vybízející k ilegálnímu stahování, + měla by adresa (URL) pro takovéto popisy zůstat vynechána. +</p> + diff --git a/data/docs/6 b/data/docs/6 index 8bbf20a5..c9ab4375 100644 --- a/data/docs/6 +++ b/data/docs/6 @@ -72,3 +72,19 @@ the illegal spreading of visual novels. </p> + +:SUB:Can I link to VNDB? +<p> + Of course you can! We even have some icons you could use to link to and promote + VNDB. Direct linking is, unlike with all other images on this site, allowed.<br /> + <img src="http://static.yorhel.nl/2008/vndb_88x31.gif" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb1.gif" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb2.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb3.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb4.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb5.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb6.jpg" style="margin: 5px"><br /> + If you feel these banners don't suit your needs, please don't hesitate to make + one yourself. +<p> + diff --git a/data/docs/6.cs b/data/docs/6.cs new file mode 100644 index 00000000..6e9226f9 --- /dev/null +++ b/data/docs/6.cs @@ -0,0 +1,89 @@ +:TITLE:Frequently Asked Questions +:INC:index + + +:SUB:Co je to vizuální novela? +<p> + Na vizuální novelu se dá nahlížet jako na kombinaci novely a počítačové hry: + jsou to počítačové hry s dějovou linií obsahující spoustu textu a jen málo + interakce od hráče. Typická vizuální novela obsahuje text přes obrázek + v anime stylu v pozadí a je doprovázena hudbou na pozadí. V průběhu hry + obvykle hráč musí odpovědět na několik otázek, které budou mít dopad na + příběh, tudíž hraní vizuální novely podruhé s jinými odpověďmi může poskytnout + úplně jiný příběh.<br /> + <br /> + Pro více informací je tu <a href="http://en.wikipedia.org/wiki/Visual_Novel"> + článek o vizuálních novelách na Wikipedii</a> nebo popis na stránce + <a href="http://visual-novels.net/vn/index.php?option=com_content&task=view&id=259&Itemid=47">Visual-Novels.net</a>. + Pro obecný dojem z žánru doporučujeme jednu z volných krátkých vizuálních novel z + <a href="http://altogether.insani.org/">projektu al|together</a>. +</p> + + +:SUB:Co takové eroge, H-hry a dating simy? +<p> + Eroge nebo H-hra je vlastně jakákoliv japonská hra, která zahrnuje sexuální + obsah. Hodně vizuálních novel jsou eroge a hodně eroge jsou vizuální novely, + ale není to pravidlo. Definice dating simu je trochu vágnější, ale obvykle + je to to samé jako vizuální novela s tím, že herní systém dating simu staví + na statistikách.<br /> + <br /> + Neexistují žádné pevné hranice definice "vizuálních novel", většina + eroge a dating simů obsahuje prvky vizuálních novel, ale nemusí - + striktně řečeno - být vizuálními novelami jako takovými. Jelikož se VNDB + snaží o srozumitelnost, jednoduše přijímáme jakoukoliv hru, která obsahuje + prvky vizuální novely a je produkována japonskou nebo k japonsku vztaženou + společností nebo doujin kruhem, pro více informací se podívejte na + <a href="/d2">doporučení</a>. +</p> + + +:SUB:Proč databáze vizuálních novel? +<p> + Internet je veliký, hodně veliký, ale množství anglických zdrojů o vizuálních + novelách je jen velmi malé. VNDB se snaží posbírat a zveřejnit co možná nejvíce + informací, které by jinak byly jen velmi těžko k nalezení anglicky mluvícími + hráči. Touto cestou mohou mít fanoušci jednoduchý přehled o nových vydáních + a lokalizacích jejich oblíbené hry a nemusí přitom procházet množstvím + sobě si podobných japonských stránek. +</p> + + +:SUB:Jak mohu VNDB pomoct? +<p> + Je mnoho cest jak na VNDB přispět. První věc, kterou můžete udělat, je + volná editace jakýchkoliv informací, které lze nalézt na těchto stránkách, + takže pokud najdete chybu, stačí kliknout na odkaz "editovat" umístěný + na stránce vpravo nahoře. Můžete také přidávat nové informace (vizuální + novely, producenty, vydání) do databáze, i když před takovým přidáním, + prosíme, databázi nejdříve prohledejte, abychom se vyhnuli zdvojování + stránek.<br /> + <br /> + K diskusi o nových funkcích nebo pro pomoc s vývojem stránek samotných se můžete + kdykoliv účastnit diskuse na <a href="/t">diskusních boardech</a> + nebo se k nám přidat na IRC adrese <a href="irc://irc.synirc.net/vndb">#vndb @ irc.synirc.net</a>. + I pokud nepoužíváte IRC nebo jste jen líní nainstalovat si IRC klienta, můžete se stále přidat + k povídání za použití <a href="http://cgiirc.synirc.net/?chan=%23vndb">Webchatu</a>. + Stačí si vybrat přezdívku a stisknout tlačítko Login! +</p> + + +:SUB:Kde můžu stáhnout vizuální novely? +<p> + Tady ne. Neposkytujeme stahování ani odkazy na zdroje, které podporují ilegální šíření + vizuálních novel. +</p> + +:SUB:Mohu na VNDB odkázat? +<p> + Samozřejmě můžete! Máme dkonce několik ikonek, které můžete k odkazování a podpoře + VNDB použít. Přímé linkování je, na rozdíl od všech ostatních obrázků na těchto stránkách, povoleno.<br /> + <img src="http://static.yorhel.nl/2008/vndb_88x31.gif" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb1.gif" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb2.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb3.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb4.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb5.jpg" style="margin: 5px"> + <img src="http://static.yorhel.nl/2009/vndb6.jpg" style="margin: 5px"><br /> + Pokud cítíte, že ani jeden z těchto bannerů nevyhovuje tomu, co hledáte, neváhejte udělat + jeden sami. diff --git a/data/docs/7 b/data/docs/7 index b20b8e7c..9590cbd0 100644 --- a/data/docs/7 +++ b/data/docs/7 @@ -47,25 +47,50 @@ :SUB:Credits <p> - Due to the sites nature as a wiki, all information in the database is added and - kept up-to-date by active users. VNDB would not have been as large and comprehensive - as it is now without the support of the many contributers. For a complete list of - active contributors, check out the <a href="/u/all?o=d;s=changes">user list</a>. -</p> + <b>Code development</b><br /> + <dl> + <dt>Yorhel</dt><dd>Main developer.</dd> + <dt>3dB</dt><dd>Wrote the current user authentication code and implemented post throttling on the discussion board.</dd> + </dl> + <br /> + <b>Translations</b><br /> + <dl> + <dt>Czech</dt><dd><a href="mailto:nya.chan.production@gmail.com">Nya-chan Production</a></dd> + <dt>Hungarian</dt><dd><a href="mailto:bikmate@gmail.com">Bikfalvi Máté</a></dd> + <dt>Russian</dt><dd><a href="mailto:winkillerstudio@gmail.com">Dmitri Poguliayev</a></dd> + </dl> -:SUB:Link to us -<p> - Due to popular demand, here are some icons you could use to link to and promote - VNDB. Direct linking is, unlike with all other images on this site, allowed.<br /> - <img src="http://static.yorhel.nl/2008/vndb_88x31.gif" style="margin: 5px"> - <img src="http://static.yorhel.nl/2009/vndb1.gif" style="margin: 5px"> - <img src="http://static.yorhel.nl/2009/vndb2.jpg" style="margin: 5px"> - <img src="http://static.yorhel.nl/2009/vndb3.jpg" style="margin: 5px"> - <img src="http://static.yorhel.nl/2009/vndb4.jpg" style="margin: 5px"> - <img src="http://static.yorhel.nl/2009/vndb5.jpg" style="margin: 5px"> - <img src="http://static.yorhel.nl/2009/vndb6.jpg" style="margin: 5px"><br /> - If you feel these banners don't suit your needs, please don't hesitate to make - one yourself. -<p> + <br /> + <b>Skins</b> + <dl> + <dt>applehq</dt><dd> + <a href="?skin=lb">Little Busters! (pink)</a>, + <a href="?skin=term">Neon (black)</a> + </dd><dt>EchoMateria</dt><dd> + <a href="?skin=aselia_01">Aselia (sunset)</a>, + <a href="?skin=carnevale">Gekkou no Carnevale (black)</a>, + <a href="?skin=eiel">EIeL (sand)</a>, + <a href="?skin=ever17_01">Ever17 (light blue)</a>, + <a href="?skin=fate_01">Fate/stay night (rust)</a>, + <a href="?skin=fate_02">Fate/stay night (red)</a>, + <a href="?skin=higu">Higurashi no Naku Koro ni (orange)</a>, + <a href="?skin=lb_02">Little Busters! (sand)</a>, + <a href="?skin=haze">Primitive Link (haze)</a>, + <a href="?skin=saya">Saya no Uta (maroon)</a>, + <a href="?skin=seinarukana">Seinarukana (white)</a>, + <a href="?skin=taka">Sora no Iro, Mizu no Iro (turquoise)</a>, + <a href="?skin=tsukihime">Tsukihime (purple)</a>, + <a href="?skin=tsukihime_02">Tsukihime (black)</a> + </dd><dt>Yorhel</dt><dd> + <a href="?skin=angel">Angelic Serenade (dark blue)</a>, + <a href="?skin=grey">Touhou (grey)</a> + </dd> + </dl> + <br /> + + <b>Top 5 Contributors</b> +:TOP5CONTRIB: + ...and <a href="/u/all?o=d;s=changes">many more</a>. +</p> diff --git a/data/docs/7.cs b/data/docs/7.cs new file mode 100644 index 00000000..0b0ae6bc --- /dev/null +++ b/data/docs/7.cs @@ -0,0 +1,92 @@ +:TITLE:O nás +:INC:index + + +:SUB:Cíle +<p> + Naším hlavním cílem je vybudovat velkou, srozumitelnou a aktuální databázi pro + informace o všech existujících vizuálních novelách. VNDB se snaží být hlavním + místem, kde lze vyhledat obecné informace jak o vizuálních novelách samotných, + tak praktické informace okolo nich, jako jsou dostupná vydání a producenti.<br /> + Naším vedlejším cílem je propagovat toto úžasné médium zvané vizuální novely + širší veřejnosti, veřejnosti neomezené jen na lidi, kdo rozumí japonsky, ale + komukolibv, kdo se zajímá o literaturu, mangu, anime či hry, bez ohledu na + jejich místopisné zařazení či kulturní rozdíly. +</p> + + +:SUB:Stvoření +<p> + Vše to začalo poté, co <a href="/u2">yorhel</a> dočetl a dohrál + <a href="/v17">Ever17</a>. Pod mohutným dojmem z tohoto mistrovského díla ho napadlo + několik otázek: Po zjištění, že vizuální novely existují, proč stále trvalo několik + měsíců tuhle najít? Proč o ní nikdy předtím neslyšel? Jak může vizuální novela takové + kvality unikat pozornosti takřka všech online anime a hráčských komunit? A nejdůležitěji: + Je tam venku více takových vizuálních novel?<br /> + <br /> + VNDB se zrodila, aby odpověděla na poslední otázku. Kompletní absence jakéhokoliv + centrálního zdroje nebo alespoň jednoduchého seznamu vizuálních novel způsobila, že + bylo velmi obtížné najít nové hry nebo získat dobrý přehled toho, co bylo dostupné. + Problém by vyřešilo, kdyby existovalo nějaké centrální a dobře organizované místo, + kde by všichni mohli sdílet své informace a znalosti o vizuálních novelách.<br /> + <br /> + Po krátkých třech týdnech tvrdé dřiny spatřila v září 2007 první verze VNDB světlo + světa. Zmatenost a malá základna fanoušků okolo vizuálních novel, spolu s holým + minimem funkčnosti a neohebný systém přispívání, který tehdy fungoval, zapříčinily, + že databáze rostla jen pomalým tempem. Ale časem se o VNDB dozvědělo více lidí, + byly představeny nové, pokročilejší funkce a v odpověď na tyto věci začalo více lidí + přispívat informacemi. Představení vylepšeného a otevřeného systému přispívání + v únoru 2008 motivovala ještě více uživatelů k přidání dalších informací a v září 2008 + - rok po původní verzi VNDB - databáze zahrnuje přes 1000 vizuálních novel a 2000 + vydání, a i dnes stále pokračuje v růstu jak do velikosti, tak do kvality. +</p> + + +:SUB:Autoři +<p> + <b>Vývoj kódu</b><br /> + <dl> + <dt>Yorhel</dt><dd>Hlavní vývojářr.</dd> + <dt>3dB</dt><dd>Napsal současný kód ověřující autorizaci uživatelů a na diskusních boardech implementoval limitaci příspěvků za určitý čas.</dd> + </dl> + <br /> + + <b>Překlady</b><br /> + <dl> + <dt>Český</dt><dd><a href="mailto:nya.chan.production@gmail.com">Nya-chan Production</a></dd> + <dt>Maďarský</dt><dd><a href="mailto:bikmate@gmail.com">Bikfalvi Máté</a></dd> + <dt>Ruský</dt><dd><a href="mailto:winkillerstudio@gmail.com">Dmitri Poguliayev</a></dd> + </dl> + + <br /> + <b>Skiny</b> + <dl> + <dt>applehq</dt><dd> + <a href="?skin=lb">Little Busters! (pink)</a>, + <a href="?skin=term">Neon (black)</a> + </dd><dt>EchoMateria</dt><dd> + <a href="?skin=aselia_01">Aselia (sunset)</a>, + <a href="?skin=carnevale">Gekkou no Carnevale (black)</a>, + <a href="?skin=eiel">EIeL (sand)</a>, + <a href="?skin=ever17_01">Ever17 (light blue)</a>, + <a href="?skin=fate_01">Fate/stay night (rust)</a>, + <a href="?skin=fate_02">Fate/stay night (red)</a>, + <a href="?skin=higu">Higurashi no Naku Koro ni (orange)</a>, + <a href="?skin=lb_02">Little Busters! (sand)</a>, + <a href="?skin=haze">Primitive Link (haze)</a>, + <a href="?skin=saya">Saya no Uta (maroon)</a>, + <a href="?skin=seinarukana">Seinarukana (white)</a>, + <a href="?skin=taka">Sora no Iro, Mizu no Iro (turquoise)</a>, + <a href="?skin=tsukihime">Tsukihime (purple)</a>, + <a href="?skin=tsukihime_02">Tsukihime (black)</a> + </dd><dt>Yorhel</dt><dd> + <a href="?skin=angel">Angelic Serenade (dark blue)</a>, + <a href="?skin=grey">Touhou (grey)</a> + </dd> + </dl> + <br /> + + <b>5 nejaktivnějších přispívajících</b> +:TOP5CONTRIB: + ...a <a href="/u/all?o=d;s=changes">mnoho dalších</a>. +</p> diff --git a/data/docs/9.cs b/data/docs/9.cs new file mode 100644 index 00000000..f5ceef38 --- /dev/null +++ b/data/docs/9.cs @@ -0,0 +1,74 @@ +:TITLE:Diskusní boardy +:INC:index + + +:SUB:Představení +<p> + VNDB obsahuje pěkně integrované diskusní boardy, které se dají používat k, no, + diskusím. Protože nepoužíváme žádný populární nebo veřejně dostupný software + na fóra, ale místo toho jsme něco napsali sami, tyto diskusní boardy mají několik + odlišností oproti populárním boardům, na které můžete být zvyklí. +</p> + + +:SUB:Boardy +<p> + Aby se zabezpečilo, že hledající lidé najdou váš příspěvek, všechny thready patří + k jednomu nebo více 'boardům', které určují, o čem je daná diskuse. Podobá se to + boardům na ostatních fórche, ale zde má každá položka v databázi svůj vlastní board + a je možné odkázat thread do více než jednoho boardu. Použít se dají následující + boardy: +</p> +<dl> + <dt>db</dt><dd> + Diskuse o VNDB. Toto je obecný board pro thready, které nejsou o žádné určité položce + v databázi. + </dd><dt>v#</dt><dd> + Pro diskuse o určité vizuální novele. Board <i>v17</i>, například, se používá + pro všechny thready ohledně vizuální novely <a href="/v17">v17</a>. + </dd><dt>p#</dt><dd> + Stejně jako <i>v#</i>, ale pro producenty. + </dd><dt>u#</dt><dd> + <i>u#</i> board se dá použít pro upozornění některého uživatele této stránky ohledně + něčeho co by on/a měl/a vidět nebo k diskusi ohledně jeho/jejích editací. Podobá se to + klasické funkci 'soukromých zpráv' na mnoha stránkách, až na to, že to není 'soukromé'... + </dd><dt>an</dt><dd> + Požíváno pro oznámení ohledně stránek. Pouze pro moderátory. + </dd> +</dl> + + +:SUB:Formátování +<p> + Pro formátování vašich příspěvků můžete použít následující kódy: +</p> +<dl> + <dt>X# or X#.#</dt><dd> + 'VNDBID', jak jim říkáme. Jsou to písmena (d, p, r, u or v), za kterými následuje číslo a můžou být následovány + tečkou a druhým číslem. VNDBID budou automaticky převedeny na odkazy na příslušnou stránku v databázi. + Například pokud napíšete 'v4.4', pak dostanete '<a href="/v4.4">v4.4</a>'. + </dd><dt>URL</dt><dd> + Jakákoliv URL (bez použití [url] tagu, viz níže) bude převedena na odkaz, podobně jako VNDBID. + Příklad: 'http://vndb.org/' bude naformátováno jako '<a href="http://vndb.org/">link</a>'. + </dd><dt>[url]</dt><dd> + Klasický BBCode [url] tag. Dá se použít poze v podobě <i>[url=link]název odkazu[/url]</i>.<br /> + Např. '[url=/v]Seznam vizuálních novel[/url] a [url=http://blicky.net/]nějaká externí stránka[/url]' + bude zobrazeno jako '<a href="/v">Seznam vizuálních novel</a> a <a href="http://blicky.net/">nějaká externí stránka</a>' + </dd><dt>[spoiler]</dt><dd> + Tag [spoiler] by měl být použit pro skrytí informací, které by mohly pokazit potěšení z hraní vizuální novely + lidem, kteří ji ještě nehráli. + </dd><dt>[quote]</dt><dd> + Pokud se odkazujete na jiné lidi, umístěte citovaný příspěvek do [quote] .. [/quote] bloku. Prosíme, povšimněte si, + že populární syntaxe [quote=source] na VNDB nefunguje. (yet) + </dd><dt>[raw]</dt><dd> + Předveďte své dovednosti ve formátovacím kódu umístěním čehokoliv, co nechcete aby bylo naformátováno do [raw] + tagu. Jakýkoliv formátovací kód do teď zmíněný bude v [raw] .. [/raw] bloku ignorován. + </dd> +</dl> +<p> + Nemáme žádný tag [img] a pravděpodobně zde nikdy žádný nebude. Pokud chcete přidat + do příspěvku screenshoty nebo jiné obrázky, pak je, prosíme, nahrajte na externí + hostingovou službu (např. <a href="http://tinypic.com/" rel="nofollow">TinyPic</a>) a odkažte na ně ve svém příspěvku. +</p> + + diff --git a/data/docs/index.cs b/data/docs/index.cs new file mode 100644 index 00000000..08c8b500 --- /dev/null +++ b/data/docs/index.cs @@ -0,0 +1,14 @@ +<ul class="index"> + <li><b>Doporučení</b></li> + <li><a href="/d5">Doporučení k editacím</a></li> + <li><a href="/d2">Vizuální novely</a></li> + <li><a href="/d3">Vydání</a></li> + <li><a href="/d4">Producenti</a></li> + <li><a href="/d10">Tagy</a></li> + <li><b>O VNDB</b></li> + <li><a href="/d9">Diskusní boardy</a></li> + <li><a href="/d6">FAQ</a></li> + <li><a href="/d7">O nás</a></li> + <li><a href="/d8">Vývoj</a></li> +</ul> + diff --git a/data/docs/notfinished.cs b/data/docs/notfinished.cs new file mode 100644 index 00000000..ad45d31b --- /dev/null +++ b/data/docs/notfinished.cs @@ -0,0 +1,4 @@ +<div class="warning"> + Tato stránka ještě není dokončena!<br /> + Pokud byste nám chtěli pomoct s jejím dokončováním, pak se k nám, prosíme, připojte na <a href="irc://irc.synirc.net/vndb">IRC</a>. +</div> diff --git a/data/global.pl b/data/global.pl index 4ab04d5b..6dae908f 100644 --- a/data/global.pl +++ b/data/global.pl @@ -31,34 +31,34 @@ our %S = (%S, [qw| hist board boardmod edit tag mod lock del tagmod |], # 4 [qw| hist board boardmod edit tag mod lock del tagmod usermod |], # 5 ], - languages => [qw|cs da de en es fi fr it ja ko nl no pl pt ru sv tr vi zh|], + languages => [qw|cs da de en es fi fr hu it ja ko nl no pl pt ru sv tr vi zh|], producer_types => [qw|co in ng|], discussion_boards => [qw|an db v p u|], vn_lengths => [ 0..5 ], - anime_types => [ - # AniDB anime type starts counting at 1, 0 = unknown - # we start counting at 0, with NULL being unknown - 'TV Series', - 'OVA', - 'Movie', - 'Other', - 'Web', - 'TV Special', - 'Music Video', - ], - vn_relations => [ - # Name, Reverse-- - [ 'Sequel', 0 ], - [ 'Prequel', 1 ], - [ 'Same setting', 0 ], - [ 'Alternative version', 0 ], - [ 'Shares characters', 0 ], - [ 'Side story', 0 ], - [ 'Parent story', 1 ], - [ 'Same series', 0 ], - [ 'Fandisc', 0 ], - [ 'Original game', 1 ], - ], + anime_types => [qw|tv ova mov oth web spe mv|], + vn_relations => { + # id => [ order, reverse ] + seq => [ 0, 'preq' ], + preq => [ 1, 'seq' ], + set => [ 2, 'set' ], + alt => [ 3, 'alt' ], + char => [ 4, 'char' ], + side => [ 5, 'par' ], + par => [ 6, 'side' ], + ser => [ 7, 'ser' ], + fan => [ 8, 'orig' ], + orig => [ 9, 'fan' ], + }, + prod_relations => { + 'old' => [ 0, 'new' ], + 'new' => [ 1, 'old' ], + 'sub' => [ 2, 'par' ], + 'par' => [ 3, 'sub' ], + 'imp' => [ 4, 'ipa' ], + 'ipa' => [ 5, 'imp' ], + 'spa' => [ 6, 'ori' ], + 'ori' => [ 7, 'spa' ], + }, age_ratings => { -1 => [ 'Unknown' ], 0 => [ 'All ages' ,'CERO A' ], @@ -76,21 +76,21 @@ our %S = (%S, 17 => [ '17+', 'CERO D' ], 18 => [ '18+', 'CERO Z' ], }, - release_types => [0..2], + release_types => [qw|complete partial trial|], platforms => [qw|win dos lin mac dvd gba msx nds nes p98 psp ps1 ps2 ps3 drc sat sfc wii xb3 oth|], media => { - #DB display qty - cd => [ 'CD', 1 ], - dvd => [ 'DVD', 1 ], - gdr => [ 'GD', 1 ], - blr => [ 'Blu-ray', 1 ], - flp => [ 'Floppy', 1 ], - mrt => [ 'Cartridge', 1 ], - mem => [ 'Memory card', 1 ], - umd => [ 'UMD', 1 ], - nod => [ 'Nintendo Optical Disk', 1 ], - in => [ 'Internet download', 0 ], - otc => [ 'Other', 0 ], + #DB qty? + cd => 1, + dvd => 1, + gdr => 1, + blr => 1, + flp => 1, + mrt => 1, + mem => 1, + umd => 1, + nod => 1, + in => 0, + otc => 0 }, resolutions => [ [ 'Unknown / console / handheld', '' ], @@ -108,21 +108,8 @@ our %S = (%S, voiced => [ 0..4 ], animated => [ 0..4 ], wishlist_status => [ 0..3 ], - # note: keep these synchronised in script.js - vn_rstat => [ - 'Unknown', - 'Pending', - 'Obtained', # hardcoded - 'On loan', - 'Deleted', - ], - vn_vstat => [ - 'Unknown', - 'Playing', - 'Finished', # hardcoded - 'Stalled', - 'Dropped', - ], + rlst_rstat => [ 0..4 ], # 2 = hardcoded 'OK', < 2 = hardcoded 'NOK' + rlst_vstat => [ 0..4 ], # 2 = hardcoded 'OK', 0 || 4 = hardcoded 'NOK' ); diff --git a/data/lang.txt b/data/lang.txt index d4463379..bdea0d85 100644 --- a/data/lang.txt +++ b/data/lang.txt @@ -78,6 +78,10 @@ ends with ']'. The following options are supported: Same as the english [quant] as above, but has three forms for a word instead of two. + [quant,{num},{singular},{normative},{genetive}] (Czech) + Same as the english [quant] as above, but has three forms for a word + instead of two. + [url,{url},{title}] Formats a link to another page, where {url} is the location of the page and {title} the link title. {url} is usually an argument, e.g.: @@ -100,31 +104,88 @@ ends with ']'. The following options are supported: # data/global.pl - used in many places +# Age display + +:_age_years +en : [_1] [quant,_1,year,years] ago +ru : [_1] [quant,_1,год,года,лет] назад +cs : před [_1] [quant,_1,rokem,roky,roky] +hu : [_1] [quant,_1,éve,éve] + +:_age_months +en : [_1] [quant,_1,month,months] ago +ru : [_1] [quant,_1,месяц,месяца,месяцев] назад +cs : před [_1] [quant,_1,měsícem,měsíci,měsíci] +hu : [_1] [quant,_1,hónapja,hónapja] + +:_age_weeks +en : [_1] [quant,_1,week,weeks] ago +ru : [_1] [quant,_1,неделя,недели,недель] назад +cs : před [_1] [quant,_1,týdnem,týdny,týdny] +hu : [_1] [quant,_1,hete,hete] + +:_age_days +en : [_1] [quant,_1,day,days] ago +ru : [_1] [quant,_1,день,дня,дней] назад +cs : před [_1] [quant,_1,dnem,dny,dny] +hu : [_1] [quant,_1,napja,napja] + +:_age_hours +en : [_1] [quant,_1,hour,hours] ago +ru : [_1] [quant,_1,час,часа,часов] назад +cs : před [_1] [quant,_1,hodinou,hodinami,hodinami] +hu : [_1] [quant,_1,órája,órája] + +:_age_min +en : [_1] min ago +ru : [_1] [quant,_1,минута,минуты,минут] назад +cs : před [_1] min +hu : [_1] perce + +:_age_sec +en : [_1] sec ago +ru : [_1] [quant,_1,секунда,секунды,секунд] назад +cs : před [_1] s +hu : [_1] másodperce + + # user ranks :_urank_0 en : visitor ru : посетитель +cs : návštěvník +hu : vendég :_urank_1 en : banned ru : забанен +cs : zabanovaný +hu : kicsapva :_urank_2 en : loser ru : лузер +cs : loser +hu : vesztes :_urank_3 en : user ru : пользователь +cs : uživatel +hu : felhasználó :_urank_4 en : mod ru : модератор +cs : moderátor +hu : mod :_urank_5 en : admin ru : администратор +cs : administrátor +hu : admin # languages @@ -132,78 +193,122 @@ ru : администратор :_lang_cs en : Czech ru : Чешский +cs : Česky +hu : Cseh :_lang_da en : Danish ru : Датский +cs : Dánsky +hu : Dán :_lang_de en : German ru : Немецкий +cs : Německy +hu : Német :_lang_en en : English ru : Английский +cs : Anglicky +hu : Angol :_lang_es en : Spanish ru : Испанский +cs : Španělsky +hu : Spanyol :_lang_fi en : Finnish ru : Финский +cs : Finsky +hu : Finn :_lang_fr en : French ru : Французский +cs : Francouzsky +hu : Francia + +:_lang_hu +en : Hungarian +ru : Венгерский +cs : Maďarsky +hu : Magyar :_lang_it en : Italian ru : Итальянский +cs : Italsky +hu : Olasz :_lang_ja en : Japanese ru : Японский +cs : Japonsky +hu : Japán :_lang_ko en : Korean ru : Корейский +cs : Korejsky +hu : Koreai :_lang_nl en : Dutch ru : Голландский +cs : Nizozemsky +hu : Holland :_lang_no en : Norwegian ru : Норвежский +cs : Norsky +hu : Norvég :_lang_pl en : Polish ru : Польский +cs : Polsky +hu : Lengyel :_lang_pt en : Portuguese ru : Португальский +cs : Portugalsky +hu : Portugál :_lang_ru en : Russian ru : Русский +cs : Rusky +hu : Orosz :_lang_sv en : Swedish ru : Шведский +cs : Švédsky +hu : Svéd :_lang_tr en : Turkish ru : Турецкий +cs : Turecky +hu : Török :_lang_vi en : Vietnamese ru : Вьетнамский +cs : Vietnamsky +hu : Vietnámi :_lang_zh en : Chinese ru : Китайский +cs : Čínsky +hu : Kínai # platforms @@ -213,82 +318,254 @@ ru : Китайский :_plat_win en : Windows ru : Windows +cs : Windows +hu : :_plat_dos en : DOS ru : DOS +cs : DOS +hu : :_plat_lin en : Linux ru : Linux +cs : Linux +hu : :_plat_mac en : Mac OS ru : Mac OS +cs : Mac OS +hu : :_plat_dvd en : DVD Player ru : DVD Плеер +cs : DVD Přehrávač +hu : DVD Lejátszó :_plat_gba en : Game Boy Advance ru : Game Boy Advance +cs : Game Boy Advance +hu : :_plat_msx en : MSX ru : MSX +cs : MSX +hu : :_plat_nds en : Nintendo DS ru : Nintendo DS +cs : Nintendo DS +hu : :_plat_nes en : Famicom ru : Famicom (Dendy) +cs : Famicom +hu : :_plat_p98 en : PC-98 ru : PC-98 +cs : PC-98 +hu : :_plat_psp en : Playstation Portable ru : Playstation Portable +cs : Playstation Portable +hu : :_plat_ps1 en : Playstation 1 ru : Playstation 1 +cs : Playstation 1 +hu : :_plat_ps2 en : Playstation 2 ru : Playstation 2 +cs : Playstation 2 +hu : :_plat_ps3 en : Playstation 3 ru : Playstation 3 +cs : Playstation 3 +hu : :_plat_drc en : Dreamcast ru : Dreamcast +cs : Dreamcast +hu : :_plat_sat en : Sega Saturn ru : Sega Saturn +cs : Sega Saturn +hu : :_plat_sfc en : Super Nintendo ru : Super Nintendo +cs : Super Nintendo +hu : :_plat_wii en : Nintendo Wii ru : Nintendo Wii +cs : Nintendo Wii +hu : :_plat_xb3 en : Xbox 360 ru : Xbox 360 +cs : Xbox 360 +hu : :_plat_oth en : Other ru : Другая +cs : Ostatní +hu : Egyéb + + +# Release media + +:_med_cd +en : [quant,_1,CD,CDs] +ru : [quant,_1,CD,CD,CD] +cs : [quant,_1,CD,CD,CD] +hu : [quant,_1,CD,CD] + +:_med_dvd +en : [quant,_1,DVD,DVDs] +ru : [quant,_1,DVD,DVD,DVD] +cs : [quant,_1,DVD,DVD,DVD] +hu : [quant,_1,DVD,DVD] + +:_med_gdr +en : [quant,_1,GD-ROM,GD-ROMs] +ru : [quant,_1,GD-ROM,GD-ROM,GD-ROM] +cs : [quant,_1,GD-ROM,GD-ROMy,GD-ROMů] +hu : [quant,_1,GD-ROM,GD-ROM] + +:_med_blr +en : [quant,_1,Blu-ray disc,Blu-ray discs] +ru : [quant,_1,BD-ROM,BD-ROM,BD-ROM] +cs : [quant,_1,disk Blu-ray,disky Blu-ray,disků Blu-ray] +hu : [quant,_1,Blu-ray lemez,Blu-ray lemez] + +:_med_flp +en : [quant,_1,Floppy,Floppies] +ru : [quant,_1,Дискета,Дискеты,Дискетт] +cs : [quant,_1,disketa,diskety,disket] +hu : [quant,_1,Floppy,Floppy] + +:_med_mrt +en : [quant,_1,Cartridge,Cartridges] +ru : [quant,_1,Картридж,Картриджа,Картриджей] +cs : [quant,_1,Cartridge,Cartridge,Cartridgí] +hu : [quant,_1,Kazetta,Kazetta] + +:_med_mem +en : [quant,_1,Memory card,Memory cards] +ru : [quant,_1,Карта памяти,Карты памяти,Карт памяти] +cs : [quant,_1,Paměťová karta,Paměťové karty,Paměťových karet] +hu : [quant,_1,Memória kártya,Memória kártya] + +:_med_umd +en : [quant,_1,UMD,UMDs] +ru : [quant,_1,UMD,UMD,UMD] +cs : [quant,_1,UMD,UMD,UMD] +hu : [quant,_1,UMD,UMD] + +:_med_nod +en : Nintendo Optical [quant,_1,Disk,Disks] +ru : [quant,_1,Оптический диск,Оптические диски,Оптических дисков] Nintendo +cs : [quant,_1,Nintendo Optical Disk,Nintendo Optical Disky,Nintendo Optical Disků] +hu : Nintendo Optical [quant,_1,Lemez,Lemezek] + +:_med_in +en : Internet download +ru : Распространение через Интернет +cs : Stažení z internetu +hu : Internetes letöltés + +:_med_otc +en : Other +ru : Другое +cs : Ostatní +hu : Egyéb + + +# VN relations + +:_vnrel_seq +en : Sequel +ru : Продолжение +cs : Pokračování +hu : Folytatás + +:_vnrel_preq +en : Prequel +ru : Предыстория +cs : Prequel +hu : Előzmény + +:_vnrel_set +en : Same setting +ru : Тот же мир +cs : Stejné zasazení +hu : Egyforma környezet + +:_vnrel_alt +en : Alternative version +ru : Альтернативная версия +cs : Alternativní verze +hu : Alternatív változat + +:_vnrel_char +en : Shares characters +ru : Те же персонажи +cs : Sdílí postavy +hu : Megosztott szereplők + +:_vnrel_side +en : Side story +ru : Побочный сюжет +cs : Vedlejší příběh +hu : Mellék történet + +:_vnrel_par +en : Parent story +ru : Исходный сюжет +cs : Mateřský příběh +hu : Szüllő történet + +:_vnrel_ser +en : Same series +ru : Из той же серии +cs : Stejná série +hu : Ugyanaz a sorozat + +:_vnrel_fan +en : Fandisc +ru : Фандиск +cs : Fandisk +hu : Rajongó lemez + +:_vnrel_orig +en : Original game +ru : Оригинальная игра +cs : Originální hra +hu : Eredeti játék # producer types @@ -296,29 +573,137 @@ ru : Другая :_ptype_co en : Company ru : Компания +cs : Společnost +hu : Cég :_ptype_in en : Individual ru : Частное лицо +cs : Jednotlivec +hu : Magánszemély :_ptype_ng en : Amateur group ru : Любительская группа +cs : Amatérská skupina +hu : Amatőr csoport + + +# producer relations + +:_prodrel_old +en : Formerly +ru : Изначально +cs : Dříve +hu : Előzőleg + +:_prodrel_new +en : Succeeded by +ru : Унаследована +cs : Nahrazeno +hu : Utódja + +:_prodrel_sub +en : Subsidiary +ru : Второстепенная +cs : Vedlejší +hu : Alválalat + +:_prodrel_par +en : Parent producer +ru : Исходная компания +cs : Mateřský producent +hu : Szüllő készítő + +:_prodrel_imp +en : Imprint +ru : Марка +cs : Otisk +hu : Megjelenési adatok + +:_prodrel_ipa +en : Parent brand +ru : Исходный бренд +cs : mateřská značka +hu : Szüllő márka + +:_prodrel_spa +en : Spawned +ru : Порождена +cs*: +hu*: + +:_prodrel_ori +en : Originated from +ru : Вышла из +cs*: +hu*: # release types -:_rtype_0 +:_rtype_complete en : Complete ru : Полный +cs : Kompletní +hu : Teljes -:_rtype_1 +:_rtype_partial en : Partial ru : Частичный +cs : Částečné +hu : Részleges -:_rtype_2 +:_rtype_trial en : Trial ru : Триальный +cs : Trial +hu : Próba + + +# Anime types + +:_animetype_tv +en : TV Series +ru : ТВ Сериал +cs : TV série +hu : TV Sorozat + +:_animetype_ova +en : OVA +ru : ОВА +cs : OVA +hu : + +:_animetype_mov +en : Movie +ru : Фильм +cs : Film +hu : Film + +:_animetype_oth +en : Other +ru : Другое +cs : Ostatní +hu : Egyéb + +:_animetype_web +en : Web +ru : Веб-трансляция +cs : Internet +hu : + +:_animetype_spe +en : TV Special +ru : ТВ-спешл +cs : TV Speciál +hu : TV Különkiadás + +:_animetype_mv +en : Music Video +ru : Музыкальное видео +cs : Hudební video +hu : Videoklip # Discussion board types @@ -326,22 +711,32 @@ ru : Триальный :_dboard_an en : Announcements ru : Объявления +cs : Oznámení +hu : Hirdetések :_dboard_db en : VNDB Discussions ru : Форум VNDB +cs : Diskuse o VNDB +hu : VNDB Eszmecsere :_dboard_v en : Visual novels ru : Новеллы +cs : Vizuální novely +hu : Visual novellek :_dboard_p en : Producers ru : Компании +cs : Producenti +hu : Készítők :_dboard_u en : Users ru : Пользователи +cs : Uživatelé +hu : Felhasználók # Wishlist statuses @@ -349,18 +744,26 @@ ru : Пользователи :_wish_0 en : high ru : высокий +cs : vysoká +hu : magas :_wish_1 en : medium ru : средний +cs : střední +hu : közepes :_wish_2 en : low ru : низкий +cs : nízká +hu : alacsony :_wish_3 en : blacklist ru : в чёрном списке +cs : blacklist +hu : fekete lista # 'Voiced' information for releases @@ -368,22 +771,32 @@ ru : в чёрном списке :_voiced_0 en : Unknown ru : Неизвестно +cs : Není známo +hu : Ismeretlen :_voiced_1 en : Not voiced ru : Нет озвучки +cs : Bez hlasu +hu : Néma :_voiced_2 en : Only ero scenes voiced ru : Озвучены лишь эросцены +cs : S hlasem pouze v ero scénách +hu : Csak ero jelenetek szinkronosak :_voiced_3 en : Partially voiced ru : Частичная озвучка +cs : Částečně s hlasem +hu : Részlegesen szinkronos :_voiced_4 en : Fully voiced ru : Озвучено целиком +cs : Plně s hlasem +hu : Szinkronizált # 'Animated' information for releases @@ -391,22 +804,32 @@ ru : Озвучено целиком :_animated_0 en : Unknown ru : Неизвестно +cs : Není známo +hu : Ismeretlen :_animated_1 en : No animations ru : Без анимации +cs : Bez animací +hu : Nem animált :_animated_2 en : Simple animations ru : Простая анимация +cs : Jednoduché animace +hu : Egyszerű animációk :_animated_3 en : Some fully animated scenes ru : Некоторые сцены анимированы целиком +cs : Některé plně animované scény +hu : Pár teljesen animált jelenet :_animated_4 en : All scenes fully animated ru : Все сцены полностью анимированы +cs : Všechny scény plně animovány +hu : Minden jelenet animált # Rating indications @@ -414,42 +837,62 @@ ru : Все сцены полностью анимированы :_vote_1 en : worst ever ru : хуже некуда +cs : absolutně nejhorší +hu : iszonyú :_vote_2 en : awful ru : ужасно +cs : hrozná +hu : szörnyű :_vote_3 en : bad ru : плохо +cs : špatná +hu : rossz :_vote_4 en : weak ru : слабо +cs : slabá +hu : gyenge :_vote_5 en : so-so ru : так себе +cs : taktak +hu : elmegy :_vote_6 en : decent ru : неплохо +cs : slušná +hu : átlagos :_vote_7 en : good ru : хорошо +cs : dobrá +hu : jó :_vote_8 en : very good ru : здорово +cs : velmi dobrá +hu : nagyon jó :_vote_9 en : excellent ru : отлично +cs : skvělá +hu : nagyszerű :_vote_10 en : masterpiece ru : шедевр +cs : mistrovský kus +hu : mestermű # VN lengths @@ -457,26 +900,101 @@ ru : шедевр :_vnlength_0 en : Unknown ru : Неизвестно +cs : Není známo +hu : Ismeretlen :_vnlength_1 en : Very short[index,_1,, (< 2 hours), (OMGWTHOTL~, A Dream of Summer)] -ru : Очень короткая[index,_1,, (< 2 часов), (OMGWTFOTL~, A Dream Of Summer)] +ru : Крошечная[index,_1,, (< 2 часов), (OMGWTFOTL~, A Dream Of Summer)] +cs : Velmi krátká[index,_1,, (< 2 hours), (OMGWTHOTL~, A Dream of Summer)] +hu : Nagyon rövid[index,_1,, (< 2 óra), (OMGWTHOTL~, A Dream of Summer)] :_vnlength_2 en : Short[index,_1,, (2 - 10 hours), (Narcissu~, Planetarian)] -ru : Короткая[index,_1,, (2 - 10 часов), (Narcissu~, Planetarian)] +ru : Малая[index,_1,, (2 - 10 часов), (Narcissu~, Planetarian)] +cs : Krátká[index,_1,, (2 - 10 hours), (Narcissu~, Planetarian)] +hu : Rövid[index,_1,, (2 - 10 óra), (Narcissu~, Planetarian)] :_vnlength_3 en : Medium[index,_1,, (10 - 30 hours), (Kana: Little Sister)] ru : Средняя[index,_1,, (10 - 30 часов), (Kana: Little Sister)] +cs : Střední[index,_1,, (10 - 30 hours), (Kana: Little Sister)] +hu : Közepes[index,_1,, (10 - 30 óra), (Kana: Little Sister)] :_vnlength_4 en : Long[index,_1,, (30 - 50 hours), (Tsukihime)] -ru : Длинная[index,_1,, (30 - 50 часов), (Tsukihime)] +ru : Большая[index,_1,, (30 - 50 часов), (Tsukihime)] +cs : Dlouhá[index,_1,, (30 - 50 hours), (Tsukihime)] +hu : Hosszú[index,_1,, (30 - 50 óra), (Tsukihime)] :_vnlength_5 en : Very long[index,_1,, (> 50 hours), (Clannad)] -ru : Очень длинная[index,_1,, (> 50 часов), (Clannad)] +ru : Очень большая[index,_1,, (> 50 часов), (Clannad)] +cs : Velmi dlouhá[index,_1,, (> 50 hours), (Clannad)] +hu : Nagyon hosszú[index,_1,, (> 50 óra), (Clannad)] + + +# VN list statuses + +:_rlst_rstat_0 +en : Unknown +ru : Неизвестно +cs : Není známo +hu : Ismeretlen + +:_rlst_rstat_1 +en : Pending +ru : Ожидает +cs : V čekání +hu : Függő + +:_rlst_rstat_2 +en : Obtained +ru : Приобретено +cs : Obdrženo +hu : Megszerezve + +:_rlst_rstat_3 +en : On loan +ru : Взято напрокат +cs : Vypůjčeno +hu : Kölcsönadva + +:_rlst_rstat_4 +en : Deleted +ru : Удалено +cs : Smazáno +hu : Törölve + +:_rlst_vstat_0 +en : Unknown +ru : Неизвестно +cs : Není známo +hu : Ismeretlen + +:_rlst_vstat_1 +en : Playing +ru : Играю +cs : Ve hraní +hu : Játszás + +:_rlst_vstat_2 +en : Finished +ru : Завершено +cs : Dohráno +hu : Befejezve + +:_rlst_vstat_3 +en : Stalled +ru : Застряло +cs : Pozastaveno +hu : Leragadva + +:_rlst_vstat_4 +en : Dropped +ru : Заброшено +cs : Vyřazeno +hu : Lemondva # Form messages @@ -484,106 +1002,233 @@ ru : Очень длинная[index,_1,, (> 50 часов), (Clannad)] :_formerr_e_login_failed en : Invalid username or password ru : Некорректное имя пользователя или пароль +cs : Neplatné uživatelské jméno nebo heslo +hu : Hibás felhasználónév vagy jelszó :_formerr_e_nomail en : No user found with that email address ru : Пользователя с такой электронной почтой не существует +cs : Uživatel s touto e-mailovou adresou nebyl nalezen +hu : Nem található felhasználó ezzel az email címmel :_formerr_e_passmatch en : Passwords do not match ru : Пароли не совпадают +cs : Hesla nejsou stejná +hu : A jelszavak nem egyeznek meg :_formerr_e_usrexists en : Someone already has this username, please choose something else ru : Кто-то уже зарегистрировал такой ник, пожалуйста выберите другой +cs : Toto uživatelské jméno už je v užívání, vyberte prosím jiné +hu : Már foglalt ez a felhasználónév, kérlek válassz másikat :_formerr_e_mailexists en : Someone already registered with that email address ru : Кто-то уже регистрировался с таким адресом электронной почты +cs : Tuto e-mailovou adresu již někdo k registraci použil +hu : Valaki már regisztrált ezzel az email címmel :_formerr_e_noimage en : Image must be in JPEG or PNG format ru : Изображение должно быть в формате JPEG, либо в формате PNG +cs : Obrázek musí být v JPEG nebo PNG formátu +hu : A kép muszáj JPEG vagy PNG formátumba legyen :_formerr_e_toolarge en : Image is too large, only 500kB allowed ru : Изображение слишком большое, 500 кб - максимально допустимый предел +cs : Obrázek je moc velký, je povoleno pouze 500kB +hu : A kép túl nagy, 500kb a megengedett :_formerr_e_oneaday en : You can only register one account from the same IP within 24 hours ru : Вы можете зарегистрировать учётную запись с одного и того же IP лишь по прошествии 24 часов +cs : V rámci 24 hodin si lze z jedné IP adresy zaregistrovat pouze jeden účet +hu : 24 óra alatt csak egy fiókot hozhatsz létre ugyanazzal az IP-vel :_formerr_e_nochanges en : No changes, please don't create an entry that is fully identical to another ru : Изменения отсутствуют, пожалуйста не создавайте идентичных копий записей +cs : Nenalezeny změny, netvořte prosím záznam, který je plně totožný s jiným +hu : Nem történt változtatás, kérlek ne készíts egy bejegyzést ami teljesen megegyezik egy másikkal :_formerr_e_doublepost en : Please wait 30 seconds before making another post ru : Прежде чем публиковать очередное сообщение, пожалуйста подождите 30 секунд +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 :_formerr_title en : Error ru : Ошибка +cs : Chyba +hu : Hiba :_formerr_subtitle en : Form could not be sent: ru : Невозможно отправить форму: +cs : Dokument nemohl být odeslán: +hu : Az űrlapot nem sikerült elküldeni: :_formerr_required en : [_1] is a required field! ru : [_1] - обязательное поле! +cs : [_1] je pole, které musí být vyplněno! +hu : [_1] kitöltése kötelező! :_formerr_minlength en : [_1]: should have at least [_2] characters ru : [_1]: [quant,_2,необходим,необходимы,необходимо] хотя бы [_2] [quant,_2,символ,символа,символов] +cs : [_1]: musí mít minimálně [_2] znaků +hu : [_1]: legalább [_2] karaktert kell tartalmazzon :_formerr_maxlength en : [_1]: only [_2] characters allowed ru : [_1]: [quant,_2,разрешён,разрешены,разрешено] лишь [_2] [quant,_2,символ,символа,символов] +cs : [_1]: je povoleno maximálně [_2] znaků +hu : [_1]: csakis [_2] karakterek megengedettek :_formerr_enum en : [_1] must be one of the following: [_2] ru : Поле '[_1]' должно равняться одному из следующих значений: [_2] +cs : [_1] musí být z následujícího seznamu: [_2] +hu : [_1] muszáj az alábbiak közül valamelyik legyen: [_2] :_formerr_wrongboard en : Wrong board: [_1] ru : Некорректная ветка: [_1] +cs : Špatný board: [_1] +hu : Rossz alfórum: [_1] :_formerr_tagexists en : Tag [url,_1,_2] already exists! ru : Тег [url,_1,_2] уже существует! +cs : Tag [url,_1,_2] již existuje! +hu : A(z) [url,_1,_2] címke már létezik! :_formerr_tpl_mail en : Invalid email address ru : Некорректный адрес электронной почты +cs : Neplatná e-mailová adresa +hu : Érvénytelen email cím :_formerr_tpl_url en : [_1]: Invalid URL ru : [_1]: Некорректная ссылка +cs : [_1]: Neplatná URL +hu : [_1]: Érvénytelen hivatkozás :_formerr_tpl_asciiprint en : [_1] may only contain ASCII characters ru : Поле '[_1]' может содержать лишь символы диапазона ASCII +cs : [_1] smí obsahovat pouze znaky ASCII +hu : [_1] kizárólag ASCII karaktereket tartalmazhat :_formerr_tpl_int en : [_1]: Not a valid number ru : [_1]: Не является правильным числом +cs : [_1]: Neplatné číslo +hu : [_1]: Nem egy érvényes szám :_formerr_tpl_pname en : [_1] can only contain lowercase alphanumeric characters and a hyphen, and must start with a character ru : Поле '[_1]' может содержать лишь символы буквенно-цифрового диапазона в нижнем регистре и чёрточку, а так же начинаться с буквы +cs : [_1] smí obsahovat pouze malá písmena, čísla a pomlčku, a musí začínat písmenem +hu : [_1] egy karakterrel kell kezdődjön és csak kisbetűs alfanumerikus karaktereket és egy kötőjelet tartalmazhat :_form_tab_all en : All items ru : Все поля +cs : Všechny položky +hu : Minden :_form_editsum en : Edit summary ru : Суммарно о правке +cs : Shrnutí editace +hu : Összegzés :_form_submit en : Submit ru : Отправить +cs : Potvrdit +hu : Beküldés + + +# Common javascript strings + +:_js_expand +en : expand +ru : развернуть +cs : rozbalit +hu : kiterjeszt + +:_js_collapse +en : collapse +ru : свернуть +cs : složit +hu : összevonás + +:_js_loading +en : Loading... +ru : Загрузка... +cs : Nahrává se... +hu : Betöltés... + +:_js_date_year +en : -year- +ru : -год- +cs : -rok- +hu : -év- + +:_js_date_month +en : -month- +ru : -месяц- +cs : -měsíc- +hu : -hónap- + +:_js_date_day +en : -day- +ru : -день- +cs : -dne- +hu : -nap- + +:_js_ds_noresults +en : No results... +ru : Совпадений не найдено... +cs : Žádné výsledky... +hu : Nincs eredmény... + +:_js_iv_close +en : close +ru : закрыть +cs : zavřít +hu : bezárás + +:_js_iv_prev +en : previous +ru : назад +cs : předchozí +hu : előző + +:_js_iv_next +en : next +ru : далее +cs : další +hu : következő + +:_js_ds_tag_meta +en : meta +ru : мета +cs : meta +hu : + +:_js_ds_tag_mod +en : awaiting moderation +ru : ждёт модерирования +cs : čeká na schválení +hu : moderálásra vár @@ -600,12 +1245,16 @@ ru : Отправить :_site_title en : The Visual Novel Database ru : The Visual Novel Database +cs : Databáze Vizuálních Novel +hu : A Visual Novel Adatbázis # the 'ALL' in "ALL A B C D .. #" :_char_all en : ALL ru : ВСЕ +cs : VŠE +hu : Minden # Main menu @@ -613,54 +1262,80 @@ ru : ВСЕ :_menu en : Menu ru : Меню +cs : Menu +hu : Menü :_menu_home en : Home ru : Главная +cs : Home +hu : Kezdőlap :_menu_vn en : Visual novels ru : Новеллы +cs : Vizuální novely +hu : Visual novellek :_menu_releases en : Releases ru : Выпуски +cs : Vydání +hu : Kiadások :_menu_producers en : Producers ru : Компании +cs : Producenti +hu : Készítők :_menu_tags en : Tags ru : Теги +cs : Tagy +hu : Címkék :_menu_users en : Users ru : Пользователи +cs : Uživatelé +hu : Felhasználók :_menu_recent_changes en : Recent changes ru : Свежие правки +cs : Poslední změny +hu : Legútóbbi változtatások :_menu_discussion_board en : Discussion board ru : Форум +cs : Diskusní board +hu : Fórum :_menu_faq en : FAQ ru : ЧаВо +cs : FAQ +hu : GYIK :_menu_randvn en : Random visual novel ru : Случайная новелла +cs : Náhodná vizuální novela +hu : Véletlenszerű novel :_menu_webchat en : webchat ru : Веб-чат +cs : webchat +hu : :_menu_emptysearch en : search ru : поиск +cs : hledat +hu : kereső # User menu @@ -668,80 +1343,118 @@ ru : поиск :_menu_myprofile en : My Profile ru : Мой профиль +cs : Můj profil +hu : Profilom :_menu_myvnlist en : My Visual Novel List ru : Мой список новелл +cs : Můj list vizuálních novel +hu : Visual Novel listám :_menu_mywishlist en : My Wishlist ru : Мой список желаемого +cs : Můj wishlist +hu : Kivánságlistám # [_1] = number of messages :_menu_mymessages en : My Messages ([_1]) ru : Мои сообщения +cs : Mé zprávy ([_1]) +hu : Üzenenteim ([_1]) :_menu_mychanges en : My Recent Changes ru : Мои недавние правки +cs : Mé poslední změny +hu : Legutóbbi változtatásaim :_menu_mytags en : My Tags ru : Мои теги +cs : Moje tagy +hu : Címkéim :_menu_addvn en : Add Visual Novel ru : Добавить новеллу +cs : Přidat vizuální novelu +hu : Visual Novel hozzáadássa :_menu_addproducer en : Add Producer ru : Добавить компанию +cs : Přidat producenta +hu : Készítő hozzáadássa :_menu_logout en : Logout ru : Выйти +cs : Odhlásit +hu : Kijelentkezés # used for both the box title and submit button :_menu_login en : Login ru : Вход +cs : Přihlášení +hu : Bejelentkezés :_menu_loginmsg en : Need to [url,_1,register],[br] or [url,_2,forgot your password]? ru : Нужна [url,_1,регистрация],[br] или [url,_2,забыли свой пароль]? +cs : Potřebujete se [url,_1,přihlásit],[br] + nebo jste [url,_2,zapomněli heslo]? +hu : Ha még nem [url,_1,regisztráltál], akkor most megteheted,[br] + vagy netán [url,_2,elfelejtetted a jelszavad]? # database statistics :_menu_dbstats en : Database Statistics ru : Статистика базы данных +cs : Statistiky databáze +hu : Adatbázis Statisztikák :_menu_stat_vn en : Visual Novels ru : Новелл +cs : Vizuální novely +hu : Visual Novellek :_menu_stat_releases en : Releases ru : Выпусков +cs : Vydání +hu : Kiadások :_menu_stat_producers en : Producers ru : Компаний +cs : Producenti +hu : Készítők :_menu_stat_users en : Users ru : Пользователей +cs : Uživatelé +hu : Felhasználók :_menu_stat_threads en : Threads ru : Тем +cs : Thready +hu : Témák :_menu_stat_posts en : Posts ru : Сообщений +cs : Příspevky +hu : Hozzászólások # Footer @@ -749,10 +1462,14 @@ ru : Сообщений :_footer_aboutus en : about us ru : о нас +cs : o nás +hu : rólunk :_footer_source en : source ru : исходный код +cs : zdroj +hu : forráskód # Main tabs (those on the right top of the highest box) @@ -760,70 +1477,100 @@ ru : исходный код :_mtabs_hist en : history ru : история +cs : historie +hu : előzmények :_mtabs_discuss en : discussions ([_1]) ru : обсуждения ([_1]) +cs : diskuse ([_1]) +hu : beszélgetések ([_1]) # the following 4 tabs are only present on user pages :_mtabs_posts en : posts ru : сообщения +cs : příspevky +hu : hozzászólások :_mtabs_wishlist en : wishlist ru : список желаемого +cs : wishlist +hu : kivánságlista :_mtabs_list en : list ru : список +cs : seznam +hu : lista :_mtabs_tags en : tags ru : теги +cs : tagy +hu : címkék # modify tags on VN pages :_mtabs_tagmod en : modify tags ru : править теги +cs : změnit tagy +hu : címkék módosítása # copy a release :_mtabs_copy en : copy ru : копировать +cs : kopírovat +hu : másolás # following line is also used on revision pages (it's the same action, anyway) :_mtabs_edit en : edit ru : правка +cs : upravit +hu : szerkesztés # hide/unhide a DB item :_mtabs_hide en : hide ru : скрыть +cs : skrýt +hu : elrejtés :_mtabs_unhide en : unhide ru : показать +cs : odkrýt +hu : megjelenítés # lock/unlock for editing :_mtabs_lock en : lock ru : заблокировать +cs : zamknout +hu : bezárás :_mtabs_unlock en : unlock ru : снять блокировку +cs : odemknout +hu : kinyitás # delete :_mtabs_del en : del ru : удалить +cs : smazat +hu : törlés # VN relations :_mtabs_relations en : relations ru : связи +cs : vztahy +hu : összefüggések # Navigation buttons on the browse pages @@ -831,10 +1578,14 @@ ru : связи :_browse_previous en : previous ru : назад +cs : předchozí +hu : előző :_browse_next en : next ru : далее +cs : další +hu : következő # Revision pages @@ -842,31 +1593,45 @@ ru : далее :_revision_previous en : earlier revision ru : более ранняя редакция +cs : dřívější revize +hu : korábbi javítás :_revision_next en : later revision ru : более поздняя редакция +cs : pozdější revize +hu : későbbi javítás :_revision_title en : Revision [_1] ru : Редакция [_1] +cs : Revize [_1] +hu : Javítás - #[_1] # it's the summary of the edit, "edit" is not a verb here. :_revision_new_summary en : Edit summary ru : Суммарно +cs : Shrnutí editace +hu : Szerkesztés összefoglalása :_revision_edit_summary en : Edit summary of revision [_1]: ru : Суммарно о редакции [_1]: +cs : Shrnutí editace revize[_1]: +hu : [_1] javítás szerkesztésének az összefoglalása :_revision_user_date en : By [userstr,_1] on [date,_2,full] ru : [userstr,_1], [date,_2,full] +cs : Změnil [userstr,_1] [date,_2,full] +hu : [userstr,_1] által, [date,_2,full] :_revision_emptyfield en : ~[empty~] ru : ~[пусто~] +cs : ~[prázdná~] +hu : ~[üres~] # tabs above the search boxes @@ -874,27 +1639,39 @@ ru : ~[пусто~] :_searchbox_vn en : Visual novels ru : Новеллы +cs : Vizuální novely +hu : Visual novellek :_searchbox_releases en : Releases ru : Выпуски +cs : Vydání +hu : Kiadások :_searchbox_producers en : Producers ru : Компании +cs : Producenti +hu : Keszítők :_searchbox_tags en : Tags ru : Теги +cs : Tagy +hu : Címkék :_searchbox_users en : Users ru : Пользователи +cs : Uživatelé +hu : Felhasználok # text on the search button :_searchbox_submit en : Search! ru : Искать! +cs : Hledat! +hu : Keress! @@ -922,39 +1699,70 @@ ru : VNDB.org стремится быть наиболее полной базо Если хотите, можете [url,/v/all,побродить по сайту], [url,/u/register,создать учётную запись] или поучаствовать в обсуждении новелл (либо самой VNDB) на нашем [url,/t,форуме] (убедительная просьба писать только на английском). +cs : VNDB.org se snaží být srozumitelnou databází informací o vizuálních novelách.[br] + Tato stránka funguje na principu wikipedie, tedy kdokoliv může volně přispívat informacemi + do databáze, což nám umožňuje tvořit největší, nejpřesnější a nejaktuálnější databázi + vizuálních novel na webu.[br] + Registrovaní uživatelé si také mohou vytvořit osobní list her, které chtějí hrát nebo dohráli + a mohou hodnotit všechny vizuální novely.[br][br] + Můžete jen tak [url,/v/all,brouzdat kolem], [url,/u/register,zaregistrovat si účet] + nebo se účastnit diskusí o vizuálních novelách nebo VNDB na našem [url,/t,diskusním boardu]. +hu : A VNDB.org arra törekszik, hogy egy átfogó és információ dús adatbázist hozzon létre visual novellekről.[br] + Ez az oldal úgy van felépítve mint egy wiki, s annak érdekében, hogy ez legyen a legnagyobb, legpontosabb + és napra kész visual novel adatbázis a weben, mindenki szabadon hozzájárulhat a tartalom bővítéséhez.[br] + Regisztrált felhasználók ugyanakkor listát vezethetnek azokról a játékokról amelyeket már játszottak,játszanak + vagy épp játszani szeretnének, valamint szavazhatnak is az oldalon tálalható bármely novelre.[br][br] + Bátran, [url,/v/all,nézz körül], [url,/u/register,regisztrálj az oldalra] vagy vegyél részt a visual + novellek és a VNDB-ről szóló eszmecserékbe a [url,/t,fórumon]. :_home_recentchanges en : Recent Changes ru : Свежие правки +cs : Poslední změny +hu : Legútóbbi változtatások :_home_recentchanges_item en : [_1]:[_2] by [userstr,_3] ru : [_1]:[_2], [userstr,_3] +cs : [_1]:[_2], [userstr,_3] +hu : [_1]:[_2] - [userstr,_3] által :_home_announcements en : Announcements ru : Объявления +cs : Oznámení +hu : Hirdetések :_home_recentposts en : Recent Posts ru : Недавние сообщения +cs : Poslední příspěvky +hu : Legfrissebb hozzászólások :_home_recentposts_item en : [age,_1] [_2] by [userstr,_3] ru : [age,_1] [_2], [userstr,_3] +cs : [age,_1] [_2], [userstr,_3] +hu : [age,_1] [_2] - [userstr,_3] által :_home_randomvn en : Random visual novels ru : Случайные новеллы +cs : Náhodné vizuální novely +hu : Véletlenszerű visual novel :_home_upcoming en : Upcoming releases ru : Грядущие выпуски +cs : Nadcházející vydání +hu : Hamarosan megjelenő kiadások :_home_justreleased en : Just released ru : Только что вышли +cs : Právě vydáno +hu : Épp most jelentek meg @@ -968,10 +1776,14 @@ ru : Только что вышли :_hist_title en : Recent changes ru : Свежие правки +cs : Poslední změny +hu : Legutóbbi változtatások :_hist_title_item en : Edit history of [_1] ru : История изменений [_1] +cs : Editovat historii [_1] +hu : [_1] előzményeinek szerkesztése # the filter buttons @@ -979,54 +1791,80 @@ ru : История изменений [_1] :_hist_filter_showauto en : Show automated edits ru : Показать автоматические правки +cs : Ukázat automatické editace +hu : Automata szerkesztések megjelenítése :_hist_filter_hideauto en : Hide automated edits ru : Скрыть автоматические правки +cs : Skrýt automatické editace +hu : Automata szerkesztések elrejtése :_hist_filter_hidedel en : Hide deleted items ru : Скрыть удалённые страницы +cs : Skrýt smazané položky +hu : Törölt elemek elrejtése :_hist_filter_showdel en : Show deleted items ru : Показать удалённые страницы +cs : Ukázat smazané položky +hu : Törölt elemek megjelenítése :_hist_filter_alltypes en : Show all items ru : Показать все страницы +cs : Ukázat všechny položky +hu : Minden elem megjelenítése :_hist_filter_onlyvn en : Only visual novels ru : Только новеллы +cs : Pouze vizuální novely +hu : Csak visual novellek :_hist_filter_onlyreleases en : Only releases ru : Только выпуски +cs : Pouze vydání +hu : Csak kiadások :_hist_filter_onlyproducers en : Only producers ru : Только компании +cs : Pouze producenti +hu : Csak készítők :_hist_filter_allactions en : Show all changes ru : Показать все изменения +cs : Ukázat všechny změny +hu : Minden változtatás megjelenitése :_hist_filter_onlyedits en : Only edits ru : Только правки +cs : Pouze editace +hu : Csak a szerkesztések :_hist_filter_onlynew en : Only newly created pages ru : Только новые страницы +cs : Pouze nově stvořené stránky +hu : Csak újonnan létrehozott oldalakat :_hist_filter_exrel en : Exclude edits of releases ru : Исключить правки выпусков +cs : Výjmout editace vydání +hu : Ezen kiadások szerkesztésének a kihagyása :_hist_filter_increl en : Include edits of releases ru : Включить правки выпусков +cs : Zahrnout editace vydání +hu : Ezen kiadások szerkesztésének a mellékelése # column headers @@ -1035,18 +1873,26 @@ ru : Включить правки выпусков :_hist_col_rev en : Rev. ru : Ревизия +cs : Rev. +hu : Javítás :_hist_col_date en : Date ru : Дата +cs : Datum +hu : Dátúm :_hist_col_user en : User ru : Пользователь +cs : Uživatel +hu : Felhasználó :_hist_col_page en : Page ru : Страница +cs : Stránka +hu : Oldal @@ -1062,46 +1908,68 @@ ru : Страница :_thread_postedin en : Posted in ru : В теме +cs : Přispěno v +hu : Válaszolva :_thread_byuser en : by [userstr,_1] ru : [userstr,_1] +cs : , [userstr,_1] +hu : - [userstr,_1] :_thread_editpost en : edit ru : правка +cs : editovat +hu : szerkesztés :_thread_deletedpost en : Post deleted. ru : Сообщение удалено. +cs : Příspěvek smazán. +hu : Hozzászólás törölve. :_thread_lastmodified en : Last modified on [date,_1,full] ru : Последний раз редактировалось [date,_1,full] +cs : Naposledy změněno [date,_1,full] +hu : Utolsó szerkesztés [date,_1,full] :_thread_noreply_title en : Reply ru : Ответить +cs : Odpovědět +hu : Válasz :_thread_noreply_locked en : This thread has been locked, you can't reply to it anymore ru : Данная тема заблокирована, вы больше не можете в неё ответить +cs : Tento thread byl zamčen, již se do něj nedá odpovědět +hu : A téma le van zárva, nem lehet rá válaszolni :_thread_noreply_login en : You must be logged in to reply to this thread. ru : Вы должны быть авторизованы чтобы ответить в эту тему. +cs : Pro odpověď do tohoto threadu se musíte přihlásit. +hu : Be kell legyél jelentkezve, hogy hozzászólhass a témához. :_thread_quickreply_title en : Quick reply ru : Быстрый ответ +cs : Rychlá odpověď +hu : Gyors válasz :_thread_quickreply_submit en : Reply ru : Ответить +cs : Odpovědět +hu : Válasz :_thread_quickreply_full en : Go advanced... ru : В расширенный режим... +cs : Rozšířený mód... +hu : Haladó mód... # Post edit/reply/new thread form @@ -1109,54 +1977,80 @@ ru : В расширенный режим... :_postedit_newthread en : Start new thread ru : Начать новую тему +cs : Založit nový thread +hu : Új téma létrehozása :_postedit_replyto en : Reply to [_1] ru : Ответить в [_1] +cs : Odpovědět na [_1] +hu : Válasz [_1]-ra :_postedit_edit en : Edit post ru : Правка сообщения +cs : Editovat příspěvek +hu : Hozzászólás szerkesztése :_postedit_form_username en : Username ru : Имя пользователя +cs : Uživatelské jméno +hu : Felhasználónév :_postedit_form_title en : Thread title ru : Название темы +cs : Název threadu +hu : Téma cím :_postedit_form_boards en : Board(s) ru : Форум(ы) +cs : Board(y) +hu : Fórum(ok) :_postedit_form_boards_info en : Read [url,/d9.2,d9.2] for information about how to specify boards. ru : О том как указывать форумы читайте в разделе [url,/d9.2,d9.2]. +cs : Přečtěte si [url,/d9.2,d9.2] pro informace jak určit správný board. +hu : Lásd [url,/d9.2,d9.2], hogy hogy kell meghatározni a fórumokat. :_postedit_form_locked en : Locked ru : Заблокировано +cs : Zamčený +hu : Lezárva :_postedit_form_topic en : Topic ru : Тема +cs : Téma +hu : Téma :_postedit_form_hidden en : Hidden ru : Скрыто +cs : Skrytý +hu : Rejtett :_postedit_form_nolastmod en : Don't update last modified field ru : Не обновлять дату последнего изменения +cs : Neměnit poslední změněné pole +hu : Ne frissítse az utolsó változtatott mezőt :_postedit_form_msg en : Message ru : Сообщение +cs : Zpráva +hu : Üzenet :_postedit_form_msg_format en : See [url,/d9.3,d9.3] for the allowed formatting codes ru : Список разрешённых кодов смотрите в разделе [url,/d9.3,d9.3] +cs : Přečtěte si [url,/d9.3,d9.3] pro povolené formátovací kódy +hu : A formázó kódokat itt [url,/d9.3,d9.3] találod meg # Browsing threads by board (/t/{board_id}) @@ -1164,22 +2058,32 @@ ru : Список разрешённых кодов смотрите в разд :_disboard_item_title en : Related discussions for [_1] ru : Темы, относящиеся к [_1] +cs : Diskuse pro [_1] +hu : [_1]-hoz kapcsolódó beszélgetések :_disboard_rootlink en : Discussion board ru : Форум +cs : Diskusní board +hu : Fórum :_disboard_nothreads en : No related threads found ru : Связанных тем не найдено +cs : Nenalezeny žádné diskuse +hu : Nincs hozzá kapcsolódó téma :_disboard_createyourown en : Why not create one yourself? ru : Почему бы не создать новую? +cs : Proč jednu nevytvořite sami? +hu : Miért ne kezdenél te egyet? :_disboard_startnew en : Start a new thread ru : Начать новую тему +cs : Začít nový thread +hu : Új téma megnyitása # The discussion board index (/t) @@ -1187,6 +2091,8 @@ ru : Начать новую тему :_disindex_title en : Discussion board index ru : Корневая директория форума +cs : Seznam diskusních boardů +hu : Fórum kezdőoldal # Thread list (on discussion board index and board browser) @@ -1194,18 +2100,26 @@ ru : Корневая директория форума :_threadlist_col_topic en : Topic ru : Тема +cs : Téma +hu : Téma :_threadlist_col_replies en : Replies ru : Ответов +cs : Odpovědi +hu : Válaszok :_threadlist_col_starter en : Starter ru : Автор темы +cs : První příspěvek +hu : Készítő :_threadlist_col_lastpost en : Last post ru : Последнее сообщение +cs : Poslední příspěvek +hu : Utolsó hozzászólás @@ -1223,18 +2137,38 @@ ru : Последнее сообщение :_prodpage_langtype en : [_1] [_2] ru : [_2], основной язык: [_1] +cs : [_2], [_1] +hu : :_prodpage_aliases en : a.k.a. [_1] ru : a.k.a. [_1] +cs : a.k.a. [_1] +hu : :_prodpage_vnrel en : Visual Novel Relations ru : Связи новелл +cs : Vztahy k vizuálním novelám +hu : Visual Novel Összefüggések :_prodpage_norel en : We have currently no visual novels by this producer. ru : У нас пока нет сведений о новеллах авторства этой компании. +cs : Nemáme žádné vizuální novely od tohoto producenta. +hu : Jelenleg nincsen visual novel bejegyzésünk ettől a készítőtől. + +:_prodpage_dev +en : developer +ru : разработчик +cs : vývojář +hu : fejlesztő + +:_prodpage_pub +en : publisher +ru : издатель +cs : vydavatel +hu : kiadó # producer diff fields @@ -1242,30 +2176,65 @@ ru : У нас пока нет сведений о новеллах авторс :_revfield_p_type en : Type ru : Тип +cs : Typ +hu : Típus :_revfield_p_name en : Name (romaji) ru : Название (ромадзи) +cs : Jméno (romaji) +hu : Név (romaji) :_revfield_p_original en : Original name ru : Оригинальное название +cs : Originální jméno +hu : Eredeti név :_revfield_p_alias en : Aliases ru : Прочие названия +cs : Aliasy +hu : Más nevezések :_revfield_p_lang en : Language ru : Язык +cs : Jazyk +hu : Nyelv :_revfield_p_website en : Website ru : Веб-сайт +cs : Internetová stránka +hu : Weboldal :_revfield_p_desc en : Description ru : Описание +cs : Popis +hu : Leírás + +:_revfield_p_relations +en : Relations +ru : Отношения +cs : Vztahy +hu : Összefüggések + +:_proddiff_none +en : none +ru : нет +cs : žádné +hu : semmi + + +# Producer relation graph page (/p+/rg) + +:_prodrg_title +en : Relation graph for [_1] +ru : Схема отношений для [_1] +cs : Graf vztahů pro producenta [_1] +hu : [_1] összefüggés gráfja # Add/Edit producer @@ -1273,50 +2242,128 @@ ru : Описание :_pedit_title_edit en : Edit [_1] ru : Править [_1] +cs : Editovat [_1] +hu : [_1] szerkesztése :_pedit_title_add en : Add new producer ru : Добавление новой компании +cs : Přidat nového producenta +hu : Új készítő hozzáadása :_pedit_form_generalinfo en : General info ru : Основная информация +cs : Obecné informace +hu : Általános info :_pedit_form_type en : Type ru : Тип +cs : Typ +hu : Típus :_pedit_form_name en : Name (romaji) ru : Название (ромадзи) +cs : Jméno (romaji) +hu : Név (romaji) :_pedit_form_original en : Original name ru : Оригинальное название +cs : Originální jméno +hu : Eredeti név :_pedit_form_original_note en : The original name of the producer, leave blank if it is already in the Latin alphabet. ru : Оригинальное название компании, оставьте пустым если уже в латинском алфавите. +cs : Originální jméno producenta, ponechte prázdné, pokud již je v latince. +hu : A készítő eredeti neve, hagyd üresen ha latin betűkből áll s már beírtad a "Név" mezőbe. :_pedit_form_alias en : Aliases ru : Прочие названия +cs : Aliasy +hu : Más nevek :_pedit_form_alias_note en : (Un)official aliases, separated by a comma. ru : (Не)официальные альтернативные названия, через запятую. +cs : Ne(oficiální) aliasy, oddělené čárkou. +hu : (Nem)hivatalos nevek, vesszővel elválasztva :_pedit_form_lang en : Primary language ru : Основной язык +cs : Hlavní jazyk +hu : Elsődleges nyelv :_pedit_form_website en : Website ru : Веб-сайт +cs : Internetová stránka +hu : Weboldal :_pedit_form_desc en : Description ru : Описание +cs : Popis +hu : Leírás + +:_pedit_form_rel +en : Relations +ru : Отношения +cs : Vztahy +hu : Összefüggések + +:_pedit_rel_sel +en : Selected producers +ru : Выбранные компании +cs : Vybraní producenti +hu : Kiválasztott készítők + +:_pedit_rel_add +en : Add producer +ru : Добавить компанию +cs : Přidat producenta +hu : Készítő hozzáadása + +:_pedit_rel_addbut +en : add +ru : добавить +cs : přidat +hu : hozzáad + +:_pedit_rel_del +en : remove +ru : убрать +cs : odebrat +hu : eltávolít + +:_pedit_rel_none +en : Nothing selected. +ru : Ничего не выбрано. +cs : Není nic vybráno. +hu : Nincs semmi kiválasztva. + +:_pedit_rel_findformat +en : Producer textbox should start with an ID (e.g. "p7:") +ru : Строка компании должна начинаться с идентификатора (например, "p7:") +cs : Textové pole producenta by mělo začínat ID (např. "p7:") +hu : A Készítő szövegdoboz egy ID-val kell kezdődjön (pl. "p7:") + +:_pedit_rel_notfound +en : Producer not found +ru : Компания не найдена +cs : Producent nenalezen +hu : A készítő nem található + +:_pedit_rel_double +en : Producer already selected! +ru : Компания уже выбрана! +cs : Producent byl již vybrán! +hu : A készítő már ki van választva! # Browse/search producers @@ -1324,18 +2371,26 @@ ru : Описание :_pbrowse_title en : Browse producers ru : Обзор компаний +cs : Procházet producenty +hu : Készítők böngészése :_pbrowse_searchres en : Search results ru : Результаты поиска +cs : Výsledky hledání +hu : Keresés eredménye :_pbrowse_list en : Producer list ru : Список компаний +cs : Seznam producentů +hu : Készítők listája :_pbrowse_noresults en : No results found ru : Ничего не найдено +cs : Nenalezeny žádné výsledky +hu : Nem található semmi @@ -1351,86 +2406,140 @@ ru : Ничего не найдено :_revfield_r_vn en : Relations ru : Связи +cs : Vztahy +hu : Összefüggések :_revfield_r_type en : Type ru : Тип +cs : Typ +hu : Típus :_revfield_r_patch en : Patch ru : Патч +cs : Patch +hu : :_revfield_r_freeware en : Freeware ru : Freeware +cs : Freeware +hu : :_revfield_r_doujin en : Doujin ru : Додзинси +cs : Doujin +hu : :_revfield_r_title en : Title (romaji) ru : Название (ромадзи) +cs : Název (romaji) +hu : Cím (romaji) :_revfield_r_original en : Original title ru : Оригинальное название +cs : Originální název +hu : Eredeti cím :_revfield_r_gtin en : JAN/UPC/EAN ru : JAN/UPC/EAN +cs : JAN/UPC/EAN +hu : :_revfield_r_catalog en : Catalog number ru : Номер в каталоге +cs : Číslo v katalogu +hu : Katalógus szám :_revfield_r_languages en : Language ru : Язык +cs : Jazyk +hu : Nyelv :_revfield_r_website en : Website ru : Веб-сайт +cs : Internetová stránka +hu : Weboldal :_revfield_r_released en : Release date ru : Дата выпуска +cs : Datum vydání +hu : Kiadás ideje :_revfield_r_minage en : Age rating ru : Возрастной рейтинг +cs : Věková přístupnost +hu : Korhatár :_revfield_r_notes en : Notes ru : Заметки +cs : Poznámky +hu : Megjegyzések :_revfield_r_platforms en : Platforms ru : Платформы +cs : Platformy +hu : Platformok :_revfield_r_media en : Media ru : Носители +cs : Média +hu : Médium :_revfield_r_resolution en : Resolution ru : Разрешение +cs : Rozlišení +hu : Felbontás :_revfield_r_voiced en : Voiced ru : Озвучка +cs : Hlas +hu : Szinkronos :_revfield_r_ani_story en : Story animation ru : Сюжетная анимация +cs : Animace příběhu +hu : Animált történet :_revfield_r_ani_ero en : Ero animation ru : Анимация эросцен +cs : Ero animace +hu : Animált ero jelenetek :_revfield_r_producers en : Producers ru : Компании +cs : Producenti +hu : Készítők + +:_reldiff_developer +en : developer +ru : разработчик +cs : vývojář +hu : fejlesztő + +:_reldiff_publisher +en : publisher +ru : издатель +cs : vydavatel +hu : kiadó # Information table (on every release page) @@ -1438,114 +2547,176 @@ ru : Компании :_relinfo_vnrel en : Relation ru : Связи +cs : Vztah +hu : Összefüggés :_relinfo_title en : Title ru : Название +cs : Název +hu : Cím :_relinfo_original en : Original title ru : Оригинальное название +cs : Originální název +hu : Eredeti cím :_relinfo_type en : Type ru : Тип +cs : Typ +hu : Típus :_relinfo_type_format en : [_1][index,_2,, patch] ru : [_1][index,_2,, патч] +cs : [_1][index,_2,, patch] +hu : :_relinfo_lang en : Language ru : Язык +cs : Jazyk +hu : Nyelv :_relinfo_publication en : Publication ru : Публикация +cs : Publikováno +hu : Kiadás :_relinfo_pub_nopatch en : [index,_1,Freeware,Non-free], [index,_2,doujin,commercial] ru : [index,_1,Freeware,Несвободная], [index,_2,додзинси,коммерческая] +cs : [index,_1,Freeware,Non-free], [index,_2,doujin,komerční] +hu : [index,_1,Freeware,Nem Freeware], [index,_2,doujin,kereskedelmi] :_relinfo_pub_patch en : [index,_1,Freeware,Non-free] ru : [index,_1,Freeware,Несвободная] +cs : [index,_1,Freeware,Non-free] +hu : [index,_1,Freeware,Nem Freeware] :_relinfo_platform en : [quant,_1,Platform,Platforms] ru : [quant,_1,Платформа,Платформы,Платформ] +cs : [quant,_1,Platforma,Platformy,Platforem] +hu : [quant,_1,Platform,Platformok] :_relinfo_media en : [quant,_1,Medium,Media] ru : [quant,_1,Носитель,Носители,Носителей] +cs : [quant,_1,Médium,Média,Médií] +hu : [quant,_1,Medium,Mediumok] :_relinfo_resolution en : Resolution ru : Разрешение +cs : Rozlišení +hu : Felbontás :_relinfo_voiced en : Voiced ru : Озвучка +cs : Hlas +hu : Szinkronos :_relinfo_ani en : Animation ru : Анимация +cs : Animace +hu : Animáció :_relinfo_ani_story en : Story: [_1] ru : Сюжет: [_1] +cs : Příběh: [_1] +hu : Történet: [_1] :_relinfo_ani_ero en : Ero scenes: [_1] ru : Эросцены: [_1] +cs : Ero scény: [_1] +hu : Ero jelenetek: [_1] :_relinfo_released en : Released ru : Дата выпуска +cs : Vydáno +hu : Kiadva :_relinfo_minage en : Age rating ru : Возрастной рейтинг +cs : Věková přístupnost +hu : Korhatár -:_relinfo_producer -en : [quant,_1,Producer,Producers] -ru : [quant,_1,Компания,Компании,Компаний] +:_relinfo_developer +en : [quant,_1,Developer,Developers] +ru : [quant,_1,Разработчик,Разработчика,Разработчиков] +cs : [quant,_1,Vývojář,Vývojáři,Vývojářů] +hu : [quant,_1,Fejlesztő,Fejlesztők] + +:_relinfo_publisher +en : [quant,_1,Publisher,Publishers] +ru : [quant,_1,Издатель,Издателя,Издателей] +cs : [quant,_1,Vydavatel,Vydavatelé,Vydavatelů] +hu : [quant,_1,Kiadó,Kiadók] :_relinfo_catalog en : Catalog no. ru : № в каталоге +cs : Č. v katalogu +hu : Katalógus szám :_relinfo_links en : Links ru : Ссылки +cs : Odkazy +hu : Linkek :_relinfo_website en : Official website ru : Официальный веб-сайт +cs : Oficiální stránky +hu : Hivatalos weboldal :_relinfo_user en : User options ru : Настройки пользователя +cs : Možnosti uživatele +hu : Beállítások :_relinfo_user_notlist en : not in your list ru : не в вашем списке +cs : není ve vašem listu +hu : nincs a listádba :_relinfo_user_inlist en : Status: [_1] / [_2] ru : Статус: [_1] / [_2] +cs : Status: [_1] / [_2] +hu : Állapot: [_1] / [_2] :_relinfo_user_setr en : Set release status ru : Установка статуса выпуска +cs : Změnit status vydání +hu : Kiadás állapotának módosítása :_relinfo_user_setv en : Set play status ru : Установка статуса игры +cs : Změnit herní status +hu : Játszás állapotának módosítása :_relinfo_user_del en : remove from list ru : убрать из списка +cs : odstranit z listu +hu : eltávolítás a listából # Editing a release @@ -1553,144 +2724,324 @@ ru : убрать из списка :_redit_title_edit en : Edit [_1] ru : Правка [_1] +cs : Editovat [_1] +hu : [_1] szerkesztése :_redit_title_copy en : Copy [_1] ru : Копирование [_1] +cs : Kopírovat [_1] +hu : [_1] másolása :_redit_title_add en : Add release to [_1] ru : Добавление выпуска для [_1] +cs : Přidat vydání hry [_1] +hu : Kiadás hozzáadása [_1]-hoz :_redit_form_geninfo en : General info ru : Основная информация +cs : Obecné informace +hu : Általános info :_redit_form_type en : Type ru : Тип +cs : Typ +hu : Típus :_redit_form_patch en : This release is a patch to another release. ru : Данный выпуск является патчем для другого выпуска. +cs : Toto vydání je patch k jinému vydání. +hu : Ez a kiadás egy patch egy másik kiadáshoz. :_redit_form_freeware en : Freeware (i.e. available at no cost) ru : Freeware (т.е. распространяется бесплатно) +cs : Freeware (tj. dostupný zdarma) +hu : Freeware (vagyis ingyenes) :_redit_form_doujin en : Doujin (self-published, not by a company) ru : Додзинси (выпущено самостоятельно, частным лицом) +cs : Doujin (publikováno samostatně, ne firmou) +hu : Doujin (magán kiadás, nem egy cég által) :_redit_form_title en : Title (romaji) ru : Название (ромадзи) +cs : Název (romaji) +hu : Cím (romaji) :_redit_form_original en : Original title ru : Оригинальное название +cs : Originální název +hu : Eredeti cím :_redit_form_original_note en : The original title of this release, leave blank if it already is in the Latin alphabet. ru : Оригинальное название данного выпуска, осавьте пустым если уже в латинском алфавите. +cs : Originální název tohoto vydání, ponechte prázdné, pokud již je latinkou. +hu : A kiadás eredeti neve, hagyd üresen ha latin betűkből áll s már beírtad a "Cím" mezőbe. :_redit_form_languages en : Language(s) ru : Язык(и) +cs : Jazyk(y) +hu : Nyelv(ek) :_redit_form_gtin en : JAN/UPC/EAN ru : JAN/UPC/EAN +cs : JAN/UPC/EAN +hu : :_redit_form_catalog en : Catalog number ru : Номер в каталоге +cs : Číslo v katalogu +hu : Katalógus szám :_redit_form_website en : Official website ru : Официальный веб-сайт +cs : Oficiální stránky +hu : Hivatalos weboldal :_redit_form_released en : Release date ru : Дата выпуска +cs : Datum vydání +hu : Kiadás ideje :_redit_form_released_note en : Leave month or day blank if they are unknown ru : Если месяц или день неизвестны, оставьте их пустыми +cs : Ponechte měsíc či den prázdné, pokud jsou neznámé +hu : Ha a hónap és nap ismeretlen akkor hagyd üresen a mezőt. :_redit_form_minage en : Age rating ru : Возрастной рейтинг +cs : Věková přístupnist +hu : Korhatár :_redit_form_notes en : Notes ru : Заметки +cs : Poznámky +hu : Megjegyzések :_redit_form_notes_note en : Miscellaneous notes/comments, information that does not fit in the above fields. E.g.: Censored/uncensored or for which releases this patch applies. ru : Дополнительные заметки/комментарии, информация для которой не нашлось приемлимых полей. Т.е.: присутствие/отсутствие цензуры, или для каких версий применим данный патч. +cs : Různé poznámky/komentáře a informace, které se nehodí do předchozích polí. + Např.: Cenzurovaná/necenzurovaná verze nebo pro které vydání je patch. +hu : Különböző észrevételek/megjegyzések, olyan infók amelyek nem illenek a fenti mezőbe. + Pl. : Cenzúrázott/Cenzúrázatlan, vagy, hogy melyik kiadásra vonatkozik a patch, stb. :_redit_form_format en : Format ru : Формат +cs : Formát +hu : Formátúm :_redit_form_resolution en : Resolution ru : Разрешение +cs : Rozlišení +hu : Felbontás :_redit_form_voiced en : Voiced ru : Озвучка +cs : Hlas +hu : Szinkronos :_redit_form_ani_story en : Story animation ru : Сюжетная анимация +cs : Animace příběhu +hu : Animált történet :_redit_form_ani_ero en : Ero animation ru : Анимация эросцен +cs : Ero animace +hu : Animált ero jelenetek :_redit_form_ani_ero_none en : Unknown / no ero scenes ru : Неизвестно / нет эросцен +cs : Není známo / bez ero scén +hu : Ismeretlen / nincs ero jelenet :_redit_form_ani_ero_note en : Animation in erotic scenes, leave to unknown if there are no ero scenes. ru : Анимация в эротических сценах, не изменяйте поле если эросцены отсутствуют. +cs : Animace v ero scénách, ponechte na Není známo, pokud novela nemá ero scény. +hu : Animált erotikus jelenetek, hagyd 'Ismeretlen'-en ha nem tartalmaz erotikus jelenetek. :_redit_form_platforms en : Platforms ru : Платформы +cs : Platformy +hu : Platformok :_redit_form_media en : Media ru : Носители +cs : Média +hu : Médium + +:_redit_form_med_quantity +en : -quantity- +ru : -количество- +cs : -množství- +hu : -mennyiség- + +:_redit_form_med_medium +en : -medium- +ru : -носитель- +cs : -médium- +hu : -medium- + +:_redit_form_med_remove +en : remove +ru : убрать +cs : odebrat +hu : eltávolítás :_redit_form_prod en : Producers ru : Компании +cs : Producenti +hu : Készítők :_redit_form_prod_sel en : Selected producers ru : Выбранные компании +cs : Vybraní producenti +hu : Kiválasztot készítők :_redit_form_prod_add en : Add producer ru : Добавить компанию +cs : Přidat producenta +hu : Készitő hozzáadása + +:_redit_form_prod_dev +en : Developer +ru : Разработчик +cs : Vývojář +hu : Fejlesztő + +:_redit_form_prod_pub +en : Publisher +ru : Издатель +cs : Vydavatel +hu : Kiadó + +:_redit_form_prod_both +en : Both +ru : Оба +cs : Obojí +hu : Mindkettő + +:_redit_form_prod_addbut +en : add +ru : добавить +cs : přidat +hu : hozzáadd + +:_redit_form_prod_remove +en : remove +ru : убрать +cs : odebrat +hu : eltávolít + +:_redit_form_prod_none +en : Nothing selected. +ru : Ничего не выбрано. +cs : Není nic vybráno. +hu : Nincsen semmi kiválasztva. + +:_redit_form_prod_pformat +en : Producer textbox must start with an ID (e.g. p17) +ru : Строка компании должна начинаться с идентификатора (например, p17) +cs : Textové pole producenta musí začínat ID (např. p17) +hu : A Készítő szövegdoboz muszáj egy ID-val kezdődjön (pl. p17) + +:_redit_form_prod_notfound +en : Producer not found! +ru : Компания не найдена! +cs : Producent nenalezen! +hu : A készítő nem található! + +:_redit_form_prod_double +en : Producer already selected! +ru : Компания уже выбрана! +cs : Producent byl již vybrán! +hu : A készítő már ki van választva! :_redit_form_vn en : Visual novels ru : Новеллы +cs : Vizuální novely +hu : Visual novellek :_redit_form_vn_sel en : Selected visual novels ru : Выбранные новеллы +cs : Vybrané vizuální novely +hu : Kiválasztot visual novellek :_redit_form_vn_add en : Add visual novel ru : Добавить новеллу +cs : Přidat vizuální novelu +hu : Visual novel hozzáadása + +:_redit_form_vn_addbut +en : add +ru : добавить +cs : přidat +hu : hozzáadd + +:_redit_form_vn_remove +en : remove +ru : убрать +cs : odebrat +hu : eltávolít + +:_redit_form_vn_none +en : Nothing selected. +ru : Ничего не выбрано. +cs : Není nic vybráno. +hu : Nincsen semmi kiválasztva + +:_redit_form_vn_vnformat +en : Visual novel textbox must start with an ID (e.g. v17) +ru : Строка новеллы должна начинаться с идентификатора (например, v17) +cs : Vizuální novela musí začínat ID (např. v17) +hu : A Visual novel szövegdoboz muszáj egy ID-val kezdődjön (pl. v17) + +:_redit_form_vn_notfound +en : Visual novel not found! +ru : Новелла не найдена! +cs : Vizuální novela nenalezena! +hu : A visual novel nem található! + +:_redit_form_vn_double +en : VN already selected! +ru : Новелла уже выбрана! +cs : VN již byla vybrána! +hu : A visual novel már ki van választva! # Release browser @@ -1698,22 +3049,32 @@ ru : Добавить новеллу :_rbrowse_title en : Browse releases ru : Обзор выпусков +cs : Procházet vydání +hu : Kiadások böngészése :_rbrowse_col_released en : Released ru : Выпущено +cs : Vydáno +hu : Kiadva :_rbrowse_col_minage en : Rating ru : Рейтинг +cs : Hodnocení +hu : Szavazat :_rbrowse_col_title en : Title ru : Название +cs : Název +hu : Cím :_rbrowse_noresults_title en : No results found ru : Совпадений не найдено +cs : Nenalezeny žádné výsledky +hu : Nem található semmi :_rbrowse_noresults_msg en : Sorry, couldn't find anything that comes through your filters. You might want to disable a few filters to get more results. @@ -1722,102 +3083,156 @@ en : Sorry, couldn't find anything that comes through your filters. You might wa ru : Простите, по вашему запросу ничего не найдено. Для получения результатов, попробуйте отключить пару-тройку фильтров. Также, не забывайте что у нас нет информации об абсолютно всех выпусках. Поэтому, если вы, например, поставили фильтр по разрешению экрана, он исключит все новеллы, не удовлетворяющие выбранному разрешению, даже если в действительности та которую вы ищите имеет искомое разрешение. +cs : Omlouváme se, nenalezli jsme nic, co by vyhovovalo vašemu nastavení filtrů. Možná najdete více výsledků, pokud nějaké z nich vypnete. + + Také nezapomeňte, že o všech vydáních nemáme všechny údaje. Takže pokud například vyfiltrujete rozlišení obrazovky, pak ve výsledcích nebudou všechna vydání, u kterých neznáme jejich rozlišení, i když by to mohla být právě vydání v rozlišení, o které se zajímáte. +hu : Bocs, de ezekkel a szűrőkkel nem található semmi. Talán ki kéne kapcsolj egy párat, hogy több eredmény jöjjön ki. + + Valamint, ne felejtsd el, hogy nincsen minden kiadásról elégséges információnk. Szóval ha bekapcsolod a felbontásra vonatkozó szűrőt, azokat nem mutatja amelyeknek nincs megadva a felbontásuk, még akkor is ha valójában annyi, mint amennyit te keresel. :_rbrowse_filters en : Filters ru : Фильтры +cs : Filtry +hu : Szűrők :_rbrowse_minage en : Age rating ru : Возрастной рейтинг +cs : Věková přístupnost +hu : Korhatár :_rbrowse_ge en : Greater than or equal to ru : Больше или равно +cs : Větší než nebo rovno +hu : Nagyobb vagy egyenlő :_rbrowse_le en : Less than or equal to ru : Меньше или равно +cs : Menší než nebo rovno +hu : Kisebb vagy egyenlő :_rbrowse_resolution en : Screen resolution ru : Разрешение экрана +cs : Rozlišení obrazovky +hu : Felbontás :_rbrowse_type en : Release type ru : Тип выпуска +cs : Typ vydání +hu : Kiadás típus :_rbrowse_all en : All ru : Все +cs : Vše +hu : Minden :_rbrowse_patch en : Patch status ru : Статус патча +cs : Status patche +hu : Patch állapot :_rbrowse_patchonly en : Only patches ru : Только патчи +cs : Pouze patche +hu : Csak patchek :_rbrowse_patchnone en : Only standalone releases ru : Только самостоятельные выпуски +cs : Pouze samostatná vydání +hu : Csak különálló kiadások :_rbrowse_freeware en : Freeware ru : Freeware +cs : Freeware +hu : :_rbrowse_freewareonly en : Freeware only ru : Только Freeware +cs : Pouze freeware +hu : Csak Freeware :_rbrowse_freewarenone en : Only non-free releases ru : Только несвободные +cs : Pouze placená vydání +hu : Minden nem ingyenes kiadás :_rbrowse_doujin en : Doujin ru : Додзинси +cs : Doujin +hu : :_rbrowse_doujinonly en : Only doujin releases ru : Только додзинси +cs : Pouze doujin vydání +hu : Csak doujin kiadások :_rbrowse_doujinnone en : Only commercial releases ru : Только коммерческие +cs : Pouze komerční vydání +hu : Csak kereskedelmi kiadások :_rbrowse_dateafter en : Released after ru : Выпущены после +cs : Vydáno po +hu : Kiadva útán :_rbrowse_datebefore en : Released before ru : Выпущены до +cs : Vydáno před +hu : Kiadva előtt :_rbrowse_languages en : Languages ru : Языки +cs : Jazyky +hu : Nyelvek :_rbrowse_boolor en : boolean or, selecting more gives more results ru : булевое 'или', чем больше выбрано, тем больше даёт результатов +cs : boolean nebo, výběr více dá více výsledků +hu : Boole féle értékhalmaz(igaz/hamis), ha többet jelölsz be, több eredményt ad ki :_rbrowse_platforms en : Platforms ru : Платформы +cs : Platformy +hu : Platformok :_rbrowse_media en : Media ru : Носители +cs : Média +hu : Médium :_rbrowse_apply en : Apply ru : Применить +cs : Použít +hu : Alkalmaz :_rbrowse_clear en : Clear ru : Очистить +cs : Začít znovu +hu : Törlés @@ -1833,88 +3248,132 @@ ru : Очистить :_tagp_title en : [index,_1,Meta tag,Tag]: [_2] ru : [index,_1,Мета тег,Тег]: [_2] +cs : [index,_1,Meta tag,Tag]: [_2] +hu : [index,_1,Meta Címke,Címke]: [_2] :_tagp_del_title en : Tag deleted ru : Тег удалён +cs : Tag smazán +hu : Címke törölve :_tagp_del_msg en : This tag has been removed from the database, and cannot be used or re-added. File a request on the [url,/t/db,discussion board] if you disagree with this. ru : Данный тег удалён из базы данных, и не может быть использован. Напишите на [url,/t/db,форуме], если вы с этим не согласны. +cs : Tento tag byl smazán z databáze a nemůže být použit nebo znovu přidán. + Pokud s tímto nesouhlasíte, napište žádost na [url,/t/db,diskusní board]. +hu : Ez a címke törölve lett az adatbázisból, s nem lehetséges visszaállítani vagy használni. + Tegyél panaszt a [url,/t/db,fórumon] ha nem értesz egyet. :_tagp_pending_title en : Waiting for approval ru : Ждёт одобрения +cs : Čeká na schválení +hu : Megerősítésre vár :_tagp_pending_msg en : This tag is waiting for a moderator to approve it. You can still use it to tag VNs as you would with a normal tag. ru : Данный тег ожидает одобрения модератором. Однако, вы можете использовать его для пометки новелл, как и любой нормальный тег. +cs : Tento tag čeká na schválení moderátorem. Mezitím ho stále můžete používat jako normální tag. +hu : Ez a címke arra vár, hogy egy moderátor elfogadja. Azonban használhatod bármely VN-nél nyugodtan akárcsak egy normális címkét. :_tagp_addchild en : Create child tag ru : Создать дочерний тег +cs : Vytvořit závislý tag +hu : Gyerek címke létrehozása :_tagp_indexlink en : Tags ru : Теги +cs : Tagy +hu : Címkék :_tagp_aliases en : Aliases ru : Прочие названия +cs : Aliasy +hu : Más nevek :_tagp_childs en : Child tags ru : Дочерние теги +cs : Závislé tagy +hu : Gyerek címkék :_tagp_tree en : Tag tree ru : Древо тега +cs : Strom tagů +hu : Címke fa :_tagp_moretags en : [_1] more [quant,_1,tag,tags] ru : ещё [_1] [quant,_1,тег,тега,тегов] +cs : [_1] [quant,_1,další tag,další tagy,dalších tagů] +hu : [_1] több [quant,_1,címke,címkék] :_tagp_vnlist en : Visual novels ru : Новеллы +cs : Vizuální novely +hu : Visual novellek :_tagp_spoil0 en : Hide spoilers ru : Скрыть спойлеры +cs : Skrýt spoilery +hu : Spoilerek elrejtése :_tagp_spoil1 en : Show minor spoilers ru : Показать лёгкие спойлеры +cs : Ukázat menší spoilery +hu : Kisebb spoilerek megjelenítése :_tagp_spoil2 en : Show major spoilers ru : Показать жёсткие спойлеры +cs : Ukázat všechny spoilery +hu : Nagyobb spoilerek megjelenítése :_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. :_tagp_cached en : NOTE: 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. ru : ЗАМЕЧАНИЕ: Данный список кэшируется, поэтому может пройти вплоть до 24 часов, прежде чем новелла, помеченная тегом, отобразится на этой странице. +cs : POZNÁMKA: Tento list se ukládá do cache, takže může trvat až 24 hodin po otagování vizuální novely do objevení se na této stránce. +hu : FIGYELEM: Ez a lista le van mentve a gyorsítótárba, beletelhet 24 órába míg a visual novelhez újonnan hozzárendelt címke megjelenik ezen az oldalon. :_tagp_vncol_score en : Score ru : Балл +cs : Skóre +hu : Pontozás :_tagp_vncol_title en : Title ru : Название +cs : Název +hu : Cím :_tagp_vncol_rel en : Released ru : Выпуск +cs : Vydáno +hu : Kiadva :_tagp_vncol_pop en : Popularity ru : Популярность +cs : Popularita +hu : Népszerűség # Tag add/edit form (/g+/edit, /g+/add, /g/add) @@ -1922,26 +3381,38 @@ ru : Популярность :_tagedit_err_notfound en : Tag '[_1]' not found ru : Тег '[_1]' не найден +cs : Tag '[_1]' nenalezen +hu : A(z) '[_1]' címke nem található :_tagedit_title_add en : Add child tag to [_1] ru : Добавление дочернего тега для '[_1]' +cs : Přidat závislý tag tagu [_1] +hu : Gyerek címke hozzárendelése a(z) [_1] - hez :_tagedit_title_edit en : Edit tag: [_1] ru : Править тег: [_1] +cs : Editovat tag: [_1] +hu : Címke szerkesztése: [_1] :_tagedit_title_new en : Add new tag ru : Добавить новый тег +cs : Přidat nový tag +hu : Új címke készítése :_tagedit_req_title en : Requesting new tag ru : Запросить новый тег +cs : Požaduje se nový tag +hu : Új címke kérése :_tagedit_req_subtitle en : Your tag must be approved ru : Ваш тег должен быть одобрен +cs : Váš tag musí být schválen +hu : A címkédet előbb el kell fogadják :_tagedit_req_msg en : Because all tags have to be approved by moderators, it can take a while before it will show up in the tag list @@ -1952,68 +3423,108 @@ ru : В связи с тем что все теги должны быть утв займёт некоторое время. Однако, вы всё равно можете голосовать за тег, даже если тот ещё не был утверждён. [br][br] Также, пожалуйста убедитесь что прочли [url,/d10,рекомендации], тогда вам не придётся гадать, утвердят ваш тег или нет. +cs : Protože všechny tagy musí být schválené moderátorem, může chvilku trvat, než se objeví na listu tagů + nebo na stránce vizuální novely. I před schválením ale můžete stále tag hodnotit. + [br][br] + Také se ujistěte, že jste přečetli [url,/d10,doporučení], abyste věděli, zda váš tag bude přidán nebo ne. +hu : Mivel minden címkét külön el kell fogadjanak a moderátorok, beletarthat egy kis időbe míg azok megjelennek a listába + vagy a visual novellek oldalain. Viszont, akkor is tudsz egy címkére szavazni, ha még nincsen elfogadva. + [br][br] + Valamint, olvasd el az [url,/d10,útmutatót], ha biztosra akarsz menni, hogy a címkédet el fogják fogadni vagy nem. :_tagedit_frm_name en : Primary name ru : Основное название +cs : Hlavní jméno +hu : Elsődleges név :_tagedit_frm_by en : Added by ru : Добавлено +cs : Přidal +hu : Hozzáadta :_tagedit_frm_state en : State ru : Состояние +cs : Stav +hu : Állapot :_tagedit_frm_state0 en : Awaiting moderation ru : Ждёт модерации +cs : Čeká na schválení +hu : Moderálásra vár :_tagedit_frm_state1 en : Deleted/hidden ru : Удалён/скрыт +cs : Smazán/skryt +hu : Rejtett/törölve :_tagedit_frm_state2 en : Approved ru : Одобрен +cs : Schválen +hu : Elfogadva :_tagedit_frm_meta en : This is a meta-tag (only to be used as parent for other tags, not for linking to VN entries) ru : Это мета-тег (его нельзя добавлять к новеллам, он может использоваться лишь как родитель для других тегов) +cs : Toto je meta-tag (používá se pouze jako mateřský pro ostatní tagy, ne pro vlastní vizuální novely) +hu : Ez itt egy meta-címke (csakis más címkék szülője ként használandó, nem pedig VN bejegyzésekhez) :_tagedit_frm_meta_warn en : WARNING: Checking this option or selecting "Deleted" as state will permanently delete all existing VN relations! ru : ПРЕДУПРЕЖДЕНИЕ: Если выбрать данную опцию, либо выставить состояние "Удалён", произойдёт немедленное удаление всех существующих связей с новеллами! +cs : VAROVÁNÍ: Zaškrtnutí této volby nebo výběr položky "Smazáno" jako stavu smaže automaticky tag u všech vizuálních novel! +hu : FIGYELEM: Ezen opció bejelölése vagy a "Törölve" állapot kiválasztása véglegesen megsemmisít minden összefüggést a VN-ekel! :_tagedit_frm_alias en : Aliases (separated by newlines) ru : Прочие названия (каждое с новой строки) +cs : Aliasy + (odděleny novou řádkou) +hu : Más nevek + (új sorokkal elválasztva) :_tagedit_frm_desc en : Description ru : Описание +cs : Popis +hu : Leírás :_tagedit_frm_desc_msg en : What should the tag be used for? Having a good description helps users choose which tags to link to a VN. ru : Для пометки чего будет использоваться тег? Хорошее описание поможет пользователям выбрать подходящий тег для подходящей новеллы. +cs : Pro co by měl být tag používán? Tagy s dobrým popisem usnadňují uživatelům jejich používání u vizuálních novel. +hu : Mire és mikor használja valaki a címkét? Egy jó leírás megkönnyíti a felhasználók dolgát mikor hozzá akarják rendelni egy VN-hez. :_tagedit_frm_parents en : Parent tags ru : Родительские теги +cs : Mateřské tagy +hu : Szülő címkék :_tagedit_frm_parents_msg en : Comma separated list of tag names to be used as parent for this tag. ru : Теги, которые будут задействованы для этого в качестве родительских. Через запятую. +cs : Seznam tagů, oddělených čárkou, které mají být použity jako mateřské pro tento tag +hu : Vesszővel elválasztott címke nevek amelyek szülő ként szolgálnak ennek a címkének. :_tagedit_frm_merge en : Merge tags ru : Объединение тегов +cs : Sjednotit tagy +hu : Címkék egyesítése :_tagedit_frm_merge_tags en : Tags to merge ru : Теги для объединения +cs : Tagy ke sjednocení +hu : Egyesítésre váró címkék :_tagedit_frm_merge_msg en : Comma separated list of tag names to merge into this one. All votes and aliases/names will be moved over @@ -2022,6 +3533,12 @@ en : Comma separated list of tag names to merge into this one. All votes and ali ru : Список тегов для объединения в данный, через запятую. Все голоса и побочные названия будут перемещены в данный тег, а все старые теги удалены. Оставьте поле пустым, если вы не собираетесь проводить объединение. [br]ПРЕДУПРЕЖДЕНИЕ: данную операцию отменить невозможно! +cs : Seznam tagů, oddělených čárkou, ke sjednocení do tohoto. Všechny hlasy a aliasy/jména budou přesunuty do tohoto tagu + a staré tagy budou smazány. Nechte toto pole prázdné, pokud nehodláte nic sjednocovat. + [br]VAROVÁNÍ: tato akce nejde vrátit! +hu : Egy lista, veszővel elválasztott címke nevekkel amelyeket egyesíteni akarsz ebbe az egybe. Minden szavazat és alias/nevek át lesznek + helyezve ehhez az új címkéhez és a régi címkék pedig törölve lesznek. Ha nem akarsz címkéket egyesíteni akkor ezt a mezőt hagyd üresen. + [br]FIGYELEM: ez a művelet végleges, nem lehet visszavonni! # Plain tag browser (/g/list) @@ -2029,42 +3546,62 @@ ru : Список тегов для объединения в данный, че :_tagb_title en : Browse tags ru : Обзор тегов +cs : Procházet tagy +hu : Címkék böngészése :_tagb_state-1 en : All ru : Все +cs : Všechny +hu : Mind :_tagb_state0 en : Awaiting moderation ru : Ожидающие модерации +cs : Čekající na schválení +hu : Moderálásra vár :_tagb_state1 en : Deleted ru : Удалённые +cs : Smazané +hu : Törölve :_tagb_state2 en : Accepted ru : Одобренные +cs : Přijaté +hu : Elfogadva :_tagb_noresults en : No results found ru : Совпадений не найдено +cs : Nenalezeny žádné výsledky +hu : Keresés sikertelen :_tagb_col_added en : Created ru : Созданы +cs : Vytvořené +hu : Létrehozva :_tagb_col_name en : Tag ru : Тег +cs : Tag +hu : Címke :_tagb_note_awaiting en : awaiting moderation ru : ждёт модерации +cs : čeká na schválení +hu : moderálásra vár :_tagb_note_del en : deleted ru : удалён +cs : smazán +hu : törölve # VN<->Tag voting (/v+/tagmod) @@ -2072,60 +3609,132 @@ ru : удалён :_tagv_title en : Add/remove tags for [_1] ru : Добавление/удаление тегов для [_1] +cs : Přidat/odebrat tag pro novelu [_1] +hu : Címke hozzáadása/törlése [_1]-hez :_tagv_msg_title en : Tagging ru : Пометка тегами +cs : Tagování +hu : Címkézés :_tagv_msg_guidelines en : Make sure you have read the [url,/d10,guidelines]! ru : Пожалуйста, убедитесь что прочли [url,/d10,рекомендации]! +cs : Ujistěte se, že jste přečetli [url,/d10,doporučení]! +hu : Olvasd el az [url,/d10,útmutatókat]! :_tagv_msg_submit en : Don't forget to hit the submit button on the bottom of the page to make your changes permanent. ru : Не забудьте сохранить изменения, нажав соответствующую кнопку в низу страницы, иначе они не вступят в силу. +cs : Nezapomeňte potvrdit tlačítko na spodku stránky, abyste uložili změny trvale. +hu : Ne felejtsd el megnyomni a beküldés gombot a lap alján, hogy véglegesítsd a változtatásokat. :_tagv_msg_cache en : Some tag information on the site is cached, it can take up to an hour for your changes to be visible everywhere. ru : Информация о некоторых тегах на данном сайте кэшируется, поэтому ваши изменения станут видны везде в течение часа. +cs : Některé informace o tazích na této stránce jsou v cache a může zabrat až hodinu než budou změny vidět. +hu : Valamely címkékkel kapcsolatos információ le van mentve a gyorsítótárba, beletelhet egy órába is mire mindenhol láthatóvá válnak a változtatások. :_tagv_frm_title en : Tags ru : Теги +cs : Tagy +hu : Címkék :_tagv_col_you en : You ru : Вы +cs : Vy +hu : Te :_tagv_col_others en : Others ru : Остальные +cs : Ostatní +hu : Egyebek :_tagv_col_tag en : Tag ru : Тег +cs : Tag +hu : Címke :_tagv_col_rating en : Rating ru : Рейтинг +cs : Hodnocení +hu : Osztályozás :_tagv_col_spoiler en : Spoiler ru : Спойлер +cs : Spoiler +hu : Spoiler :_tagv_save en : Save changes ru : Сохранить изменения +cs : Uložit změny +hu : Változtatások mentése :_tagv_add en : Add tag ru : Добавить тег +cs : Přidat tag +hu : Címke hozzáadása :_tagv_addmsg en : Check the [url,/g,tag list] to browse all available tags.[br] Can't find what you're looking for? [url,/g/new,Request a new tag]. ru : Откройте [url,/g,список тегов] чтобы увидеть все доступные теги.[br] Не нашли что искали? Вы можете [url,/g/new,запросить новый тег]. +cs : Zkontrolujte [url,/g,list tagů] pro seznam všech dostupných tagů.[br] + Nenašli jste, co jste hledali? [url,/g/new,Vyžádejte si nový tag]. +hu : Menj a [url,/g,cimke listára] ha szeretnél böngészni az összes rendelkezésre álló címke közt.[br] + Nem találod azt amelyiket keresed? [url,/g/new,Itt kérhetsz egy új címkét]. + +:_tagv_spoil0 +en : neutral +ru : нейтральный +cs : neutrální +hu : semleges + +:_tagv_spoil1 +en : no spoiler +ru : без спойлеров +cs : bez spoilerů +hu : nem spoiler + +:_tagv_spoil2 +en : minor spoiler +ru : лёгкий спойлер +cs : menší spoilery +hu : kisebb spoiler + +:_tagv_spoil3 +en : major spoiler +ru : тяжёлый спойлер +cs : všechny spoilery +hu : nagyobb spoiler + +:_tagv_notfound +en : Item not found! +ru : Элемент не найден! +cs : Předmět nenalezen! +hu : A tárgy nem található! + +:_tagv_nometa +en : Can't use meta tags here! +ru : Использование мета-тегов здесь запрещено! +cs : Zde se nedají použít meta-tagy! +hu : Itt nem használhatsz meta címkéket! + +:_tagv_double +en : Tag is already present! +ru : Тег уже указан! +cs : Tag už je přiřazen! +hu : A címke már jelen van! # User tag list (/u+/tags) @@ -2133,34 +3742,50 @@ ru : Откройте [url,/g,список тегов] чтобы увидеть :_tagu_title en : Tags by [_1] ru : Теги [_1] +cs : Tagy podle [_1] +hu : [_1] által létrehozott címkék :_tagu_spoilerwarn en : Warning: spoilery tags are not hidden in this list! ru : Предупреждение: в этом списке отображаются теги-спойлеры! +cs : Varování: spoilerové tagy nejsou v tomto seznamu skryté! +hu : Figyelem: ebbe a listába a spoiler-t tartalmazó címkék nincsenek elrejtve! :_tagu_notags en : [_1] doesn't seem to have used the tagging system yet... ru : Похоже, что [_1] пока не использует систему тегов... +cs : Nezdá se, že by [_1] zatím použil tagovací systém... +hu : Úgy tűnik, hogy [_1] még nem használta a címkéző rendszert... :_tagu_col_num en : #VNs ru : #Новеллы +cs : #VN +hu : #VN-nek :_tagu_col_name en : Tag ru : Тег +cs : Tag +hu : Címke :_tagu_spoil0 en : No spoiler ru : Нет спойлера +cs : Bez spoilerů +hu : Nem spoiler :_tagu_spoil1 en : Minor spoiler ru : Лёгкий спойлер +cs : Menší spoilery +hu : Kisebb spoiler :_tagu_spoil2 en : Major spoiler ru : Жёсткий спойлер +cs : Všechny spoilery +hu : Nagyobb spoiler # Tag index (/g) @@ -2168,42 +3793,62 @@ ru : Жёсткий спойлер :_tagidx_title en : Tag index ru : Индекс тега +cs : Seznam tagů +hu : Címke index :_tagidx_create en : Create new tag ru : Создать новый тег +cs : Vytvořit nový tag +hu : Új címke létrehozása :_tagidx_search en : Search tags ru : Поиск тегов +cs : Hledat tag +hu : Címkék keresése :_tagidx_browseall en : Browse all tags ru : Обзор всех тегов +cs : Procházet všechny tagy +hu : Összes címke böngészése :_tagidx_recent en : Recently added ru : Недавно добавлены +cs : Poslední přidané +hu : Nemrég hozzáadva :_tagidx_popular en : Popular tags ru : Популярные теги +cs : Populární tagy +hu : Népszerű címkék :_tagidx_queue en : Awaiting moderation ru : Ожидают модерации +cs : Čekající na schválení +hu : Moderálásra vár :_tagidx_queue_empty en : Moderation queue empty! yay! ru : В очереди модерации пусто! Няа! +cs : Fronta ke schválení prázdná! Jej! +hu : Moderálásra váró lista üres! Hurrá! :_tagidx_queue_link en : Moderation queue ru : Очередь модерации +cs : Fronta ke schválení +hu : Moderálásra váró lista :_tagidx_denied en : Denied tags ru : Отклонённые теги +cs : Zamítnuté tagy +hu : Megtagadott címkék @@ -2219,42 +3864,62 @@ ru : Отклонённые теги :_wishlist_title_my en : My wishlist ru : Мой список желаемого +cs : Můj wishlist +hu : Kívánságlistám :_wishlist_title_other en : [_1]'s wishlist ru : Список желаемого [_1] +cs : Wishlist uživatele [_1] +hu : [_1] kívánság listája :_wishlist_noresults en : Wishlist empty... ru : Список пуст... +cs : Wishlist je prázdný... +hu : Kívánságlista üres... :_wishlist_prio_all en : All priorities ru : Все приоритеты +cs : Všechny priority +hu : Minden prioritás :_wishlist_col_title en : Title ru : Название +cs : Název +hu : Cím :_wishlist_col_prio en : Priority ru : Приоритет +cs : Priorita +hu : Prioritás :_wishlist_col_added en : Added ru : Добавлено +cs : Přidáno +hu : Hozzáadva :_wishlist_select en : -- with selected -- ru : -- с выбранными -- +cs : -- s vybranými -- +hu : -- kiválasztva -- :_wishlist_changeprio en : Change priority ru : Изменить приоритет +cs : Změnit prioritu +hu : Prioritás megváltoztatása :_wishlist_remove en : remove from wishlist ru : убрать из списка желаемого +cs : odstranit z wishlistu +hu : eltávolítás a kívánságlistából # VN list (/u+/list) @@ -2262,54 +3927,80 @@ ru : убрать из списка желаемого :_rlist_title_my en : My visual novel list ru : Мой список новелл +cs : Můj list vizuálních novel +hu : Visual novel listám :_rlist_title_other en : [_1]'s visual novel list ru : Список новелл [_1] +cs : List vizuálních novel uživatele [_1] +hu : [_1] visual novel listája :_rlist_voted_all en : All ru : Все +cs : Všechny +hu : Mind :_rlist_voted_only en : Only voted ru : Проголосованные +cs : Pouze hodnocené +hu : Csak a szavazottak :_rlist_voted_none en : Hide voted ru : Скрыть проголосованные +cs : Skrýt hodnocené +hu : Szavazottak elrejtése :_rlist_col_title en : Title ru : Название +cs : Název +hu : Cím :_rlist_col_releases en : Releases ru : Выпуски +cs : Vydání +hu : Kiadások :_rlist_col_vote en : Vote ru : Голос +cs : Hodnocení +hu : Szavazat :_rlist_selection en : -- with selected -- ru : -- с выбранными -- +cs : -- s vybranými -- +hu : -- kiválasztva -- :_rlist_changerel en : Change release status ru : Смена статуса выпуска +cs : Změnit stav vydání +hu : Kiadás állapotának megváltoztatása :_rlist_changeplay en : Change play status ru : Смена статуса игры +cs : Změnit stav hraní +hu : Játszás állapot megváltoztatása :_rlist_del en : remove from list ru : убрать из списка +cs : odstranit z listu +hu : eltávolítás a listából :_rlist_releasenote en : * Obtained/finished/total ru : * Приобретено/прочитано/всего +cs : * Sehnáno/dohráno/celkem +hu : * Megszerezve/befejezve/összesen @@ -2325,66 +4016,98 @@ ru : * Приобретено/прочитано/всего :_userpage_title en : [_1]'s profile ru : Учётная запись [_1] +cs : Profil uživatele [_1] +hu : [_1] profilja :_userpage_username en : Username ru : Имя пользователя +cs : Uživatelské jméno +hu : Felhasználónév :_userpage_registered en : Registered ru : Регистрация +cs : Registrován +hu : Regisztrálva :_userpage_edits en : Edits ru : Правки +cs : Editace +hu : Szerkesztések :_userpage_votes en : Votes ru : Отдано голосов +cs : Hlasování +hu : Szavazatok :_userpage_votes_item en : [url,_1,_2] ([_3] average) ru : [url,_1,_2] ([_3] в среднем) +cs : [url,_1,_2] (průměr [_3]) +hu : [url,_1,_2] ([_3] átlag) :_userpage_hidden en : hidden ru : скрыто +cs : skryto +hu : elrejtve :_userpage_tags en : Tags ru : Теги +cs : Tagy +hu : Címkék :_userpage_tags_item en : [_1] [quant,_1,vote,votes] on [_2] distinct [quant,_2,tag,tags] and [_3] visual [quant,_3,novel,novels] ru : [_1] [quant,_1,голос,голоса,голосов] по [_2][quant,_2,-у тегу,-м различным тегам,-и различным тегам] и [_3][quant,_3,-й новелле,-м новеллам,-и новеллам] +cs : [_1] [quant,_1,hlasy,hlasy,hlasů] pro [_2] [quant,_2,tag,různé tagy,různých tagů] a [_3] [quant,_3,vizuální novelu,vizuální novely,vizuálních novel] +hu : [_1] [quant,_1,szavazat,szavazat] [_2] egyedi [quant,_2,címkére,címkére] és [_3] visual [quant,_3,novelre,novelre] :_userpage_list en : List stats ru : Статистика по спискам +cs : Statistiky listu +hu : Lista adatok :_userpage_list_item en : [_1] [quant,_1,release,releases] of [_2] visual [quant,_2,novel,novels] ru : [_1] [quant,_1,выпуск,выпуска,выпусков] [_2][quant,_2,-й новеллы,-х новелл,-и новелл] +cs : [_1] [quant,_1,vydání,vydání,vydání] [_2] [quant,_2,vizuální novely,vizuálních novel,vizuálních novel] +hu : [_1] [quant,_1,kiadás,kiadás] és [_2] visual [quant,_2,novel,novel] :_userpage_forum en : Forum stats ru : Статистика по форуму +cs : Statistiky fóra +hu : Fórum adatok :_userpage_forum_item en : [_1] [quant,_1,post,posts], [_2] new [quant,_2,thead,threads]. ru : [_1] [quant,_1,сообщение,сообщения,сообщений], [_2] [quant,_2,новая тема,новые темы,новых тем] +cs : [_1] [quant,_1,příspěvek, příspěvky, příspěvků], [_2] [quant,_2,nový thread, nové thready, nových threadů]. +hu : [_1] [quant,_1,válasz,válaszok], [_2] új [quant,_2,téma,téma]. :_userpage_forum_browse en : Browse posts ru : Обзор сообщений +cs : Procházet příspěvky +hu : Hozzászólások böngészése :_userpage_votestats en : Vote statistics ru : Статистика по голосованиям +cs : Statistiky hlasování +hu : Szavazási statisztikák :_userpage_changes en : Recent changes ru : Свежие правки +cs : Poslední změny +hu : Legutóbbi változtatások # Login form (/u/login) @@ -2392,22 +4115,32 @@ ru : Свежие правки :_login_title en : Login ru : Вход +cs : Přihlásit +hu : Bejelentkezés :_login_username en : Username ru : Имя пользователя +cs : Uživatelské jméno +hu : Felhasználónév :_login_register en : No account yet? ru : Нет учётной записи? +cs : Ještě nemáte účet? +hu : Nincs még fiókod? :_login_password en : Password ru : Пароль +cs : Heslo +hu : Jelszó :_login_forgotpass en : Forgot your password? ru : Забыли пароль? +cs : Zapomněli jste své heslo? +hu : Elfelejtetted a jelszavad? # Reset password (/u/newpass) @@ -2435,13 +4168,38 @@ ru : Привет, [_1] Постарайтесь больше не забывать свой пароль! :-) vndb.org +cs : Zdravíme, [_1] + + Vaše heslo bylo změněno, nyní se můžete přihlásit na http://vndb.org + s následujícími informacemi: + + Uživatelské jmno: [_1] + Heslo: [_2] + + A snažte se už heslo znovu nezapomenout! :-) +hu : Üdv [_1] + + A jelszavad vissza lett állítva, most már bejelentkezhetsz a http://vndb.org/ címen az + új adataiddal: + + Felhasználónév: [_1] + Jelszó: [_2] + + Legközelebb próbáld meg nem elfelejteni a jelszavad! :-) + + vndb.org + :_newpass_mail_subject en : New password for [_1] ru : Новый пароль для [_1] +cs : Nové heslo pro uživatele [_1] +hu : Új jelszó [_1]-nak :_newpass_title en : Forgot password ru : Забытый пароль +cs : Zapomenuté heslo +hu : Elfelejtetted a jelszavad? :_newpass_msg en : Forgot your password and can't login to VNDB anymore? @@ -2451,22 +4209,36 @@ ru : Забыли пароль и больше не можете авториз Без паники! Всё что вам нужно - указать адрес электронной почты, который вы использовали для регистрации в VNDB, и мы вышлем вам новый пароль за считанные минуты! +cs : Zapomněli jste své heslo a nemůžete se přihlásit na VNDB? + Nebojte! Stačí zadat e-mailovou adresu, se kterou jste se na VNDB registrovali, + a my vám do několika minut pošleme heslo nové! +hu : Elfelejtetted a jelszavad és nem tudsz bejelentkezni a VNBD-be? + Ne aggódj! Csak add meg nekünk az email címet amivel regisztráltál, + s egy pár percen belül kapsz tőlünk egy új jelszót! :_newpass_reset_title en : Reset password ru : Сброс пароля +cs : Změnit heslo +hu : Jelszó visszaállitása :_newpass_mail en : Email ru : E-mail +cs : E-mail +hu : :_newpass_sent_title en : New password ru : Новый пароль +cs : Nové heslo +hu : Új jelszó :_newpass_sent_subtitle en : Password reset ru : Сбросить пароль +cs : Změnit heslo +hu : Jelszó visszaállitás :_newpass_sent_msg en : Your password has been reset and your new password should reach your mailbox in a few minutes.[br] @@ -2477,16 +4249,28 @@ ru : Ваш пароль был сброшен. Через несколько м Вы всегда можете изменить пароль после того как прошли авторизацию.[br] [br] [url,/u/login,Вход] - [url,/,Главная] +cs : Vaše heslo bylo změněno a vaše nové heslo by mělo být za několik minut doručeno do vaší schránky.[br] + Vždy můžete změnit vaše heslo po přihlášení.[br] + [br] + [url,/u/login,Přihlásit] - [url,/,Home] +hu : A jelszavad vissza lett állítva s pár percen belül kapsz egy ujjat a postafiókodba.[br] + Miután bejelentkezel, nyugodtan megváltoztathatod a jelszavad amire akarod.[br] + [br] + [url,/u/login,Bejelentkezés] - [url,/,Kezdőlap] # Register new account (/u/register) :_register_title en : Create an account ru : Создание учётной записи +cs : Vytvořit účet +hu : Új fiók létrehozása :_register_why en : Why should I register? ru : Для чего нужна регистрация? +cs : Proč bych se měl registrovat? +hu : Miért kéne regisztráljak? :_register_why_msg en : Creating an account is completely painless, the only thing we need to know is your prefered username @@ -2509,36 +4293,70 @@ ru : Создание учётной записи совершенно безо - Следить за всеми новеллами и выпусками, которые у вас есть, в которые вы бы хотели сыграть, в которые играете, либо уже доиграли[br] - Голосовать за понравившиеся или, наоборот, не понравившиеся новеллы[br] - Вступать в обсуждения на ветках форума +cs : Tvorba účtu je zcela bezbolestná, jediné údaje, které potřebujeme znát jsou uživatelské jméno, které chcete, a vaše heslo. + Můžete i použít e-mail, který není váš, jelikož ani neověřujeme, že jste nám zadali opravdu váš e-mail. + Pamatujte ale, že pokud jste zadali neplatnou e-mailovou adresu, pak byste si měli dobře zapamatovat vaše heslo...[br] + [br] + V každém případě, založení účtu vám přináší několik výhod oproti běžným návštěvníkům[br] + - Můžete přispívat do databáze editací všech dat a přidáváním dat nových[br] + - Mějte přehled o všech vizuálních novelách, které máte, chtěli byste hrát, hrajete, nebo jste dohráli[br] + - Hlasujte pro vizuální novely, které se vám líbily nebo nelíbily[br] + - Přispívejte do diskusí na boardech +hu : Egy fiókot nagyon egyszerű készíteni, minden ami kell egy felhasználónév és egy jelszó. Bármilyen email címet használhatsz, + minket nem érdekel, hogy a tied vagy nem, mivel nincs külön aktiválás. Viszont ha nem jó emailt adsz meg nekünk + akkor jól vésd az eszedbe a jelszavad, különben nem tudunk neked segíteni ha elfelejted.[br] + [br] + Nos, a saját fiókkal rendelkezők több előnybe is részesülnek mint az egyszerű látogatók[br] + - Hozzájárulhatsz az adatbázis fejlődéséhez, hiszen szerkeszthetsz, vagy létrehozhatsz új bejegyzéseket[br] + - Nyomon követheted a tulajdonodban levő visual novellek, helyzetét, állapotát...mit játszol, melyiket fejezted már be stb.[br] + - Szavazhatsz a visual novellekre, melyek tetszettek, vagy ép meggyűlöltették magukat[br] + - Bekapcsolódhatsz a fórumba levő beszélgetésekbe :_register_form_title en : New account ru : Новая учётная запись +cs : Nový účet +hu : Új fiók :_register_username en : Username ru : Имя пользователя +cs : Uživatelské jméno +hu : Felhasználónév :_register_username_msg -en : Requested username. Must be lowercase and can only consist of alphanumeric characters. +en : Preferred username. Must be lowercase and can only consist of alphanumeric characters. ru : Запрашиваемое имя пользователя. Должно состоять из буквенно-цифровых символов в нижнем регистре. +cs : Požadováné uživatelské jméno. Musí být malými písmeny a smí obsahovat pouze písmena a číslice. +hu : A kívánt felhasználónév. Muszáj kisbetűkkel legyen s csakis alfanumerikus karaktereket tartalmazhat. :_register_mail en : Email ru : E-mail +cs : E-mail +hu : :_register_mail_msg en : Your email address will only be used in case you lose your password. We will never send spam or newsletters unless you explicitly ask us for it. ru : Адрес вашей электронной почты будет использоваться лишь в случае утери пароля. Мы никогда не пришлём вам спама или новостных рассылок, пока вы недвусмысленно не попросите об обратном. +cs : Vaše e-mailová adresa bude použita pouze v případě ztráty vašeho hesla. Nikdy vám nezašleme spam nebo newslettery, + pokud o to explicitně nezažádáte. +hu : Az email címed csakis akkor kerül használatba ha elveszted a jelszavad. Soha nem fogunk + spam vagy hírleveleket küldeni, csakis ha te azt kifejezetten kéred tőlünk. :_register_password en : Password ru : Пароль +cs : Heslo +hu : Jelszó :_register_confirm en : Confirm password ru : Подтверждение пароля +cs : Potvrdit heslo +hu : Jelszó megerősítése # User edit (/u+/edit) @@ -2546,70 +4364,104 @@ ru : Подтверждение пароля :_usere_title en : My account ru : Моя учётная запись +cs : Můj účet +hu : Fiókom :_usere_saved_title en : Settings saved ru : Параметры сохранены +cs : Nastavení uloženo +hu : Beállítások mentve :_usere_saved_msg en : Settings successfully saved. ru : Параметры успешно сохранены. +cs : Nastavení úspěšně uloženo. +hu : Beállítások sikeresen mentve. :_usere_geninfo en : General info ru : Основная информация +cs : Obecné informace +hu : Általános info :_usere_username en : Username ru : Имя пользователя +cs : Uživatelské jméno +hu : Felhasználónév :_usere_rank en : Rank ru : Ранг +cs : Postavení +hu : Rang :_usere_ignvotes en : Ignore votes in VN statistics ru : Игнорировать голосования в статистике новелл +cs : Ignorovat hlasy ve statistikách vizuálních novel +hu : VN szavazatok figyelmen kívül hagyása a statisztikákban :_usere_mail en : Email ru : E-mail +cs : E-mail +hu : :_usere_changepass en : Change password ru : Смена пароля +cs : Změnit heslo +hu : Jelszó megváltoztatása :_usere_changepass_msg en : Leave blank to keep your current password ru : Оставьте пустым чтобы сохранить текущий пароль +cs : Zanechte prázdné pro zachování současného hesla +hu : Hagyd üresen ha a jelenlegi jelszavad meg akarod tartani :_usere_password en : Password ru : Пароль +cs : Heslo +hu : Jelszó :_usere_confirm en : Confirm password ru : Подтверждение пароля +cs : Potvrdit heslo +hu : Jelszó megerősítése :_usere_options en : Options ru : Настройки +cs : Nastavení +hu : Beállítások :_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]) :_usere_fnsfw en : Disable warnings for images that are not safe for work. ru : Отключить предупреждения для небезопасных изображений. +cs : Vypnout varování pro obrázky, které jsou "Not Safe For Work". +hu : Nem biztonságos képek előtti figyelmeztetés kikapcsolása. :_usere_skin en : Prefered skin ru : Предпочитаемая шкурка +cs : Preferovaný skin +hu : Előnybe részesített stílus :_usere_css en : Additional [url,http://en.wikipedia.org/wiki/Cascading_Style_Sheets,CSS] ru : Дополнительный [url,http://en.wikipedia.org/wiki/Cascading_Style_Sheets,CSS] +cs : Další [url,http://en.wikipedia.org/wiki/Cascading_Style_Sheets,CSS] +hu : Továbbiak [url,http://en.wikipedia.org/wiki/Cascading_Style_Sheets,CSS] # Users posts (/u+/posts) @@ -2617,18 +4469,26 @@ ru : Дополнительный [url,http://en.wikipedia.org/wiki/Cascading_St :_uposts_title en : Posts made by [_1] ru : Сообщения [_1] +cs : Příspěvek od uživatele [_1] +hu : Hozzászólás [_1] által :_uposts_noresults en : [_1] hasn't made any posts yet. ru : [_1] пока не имеет сообщений. +cs : [_1] ještě nenapsal žádný příspěvek. +hu : [_1] még nem szólt hozzá semmihez :_uposts_col_date en : Date ru : Дата +cs : Datum +hu : Dátum :_uposts_col_title en : Title ru : Название +cs : Název +hu : Cím # User list (/u/all) @@ -2636,26 +4496,38 @@ ru : Название :_ulist_title en : Browse users ru : Обзор пользователей +cs : Procházet uživatele +hu : Felhasználók böngészése :_ulist_col_username en : Username ru : Имя пользователя +cs : Uživatelské jméno +hu : Felhasználónév :_ulist_col_registered en : Registered ru : Регистрация +cs : Registrován +hu : Regisztrált :_ulist_col_votes en : Votes ru : Отдано голосов +cs : Hlasy +hu : Szavazatok :_ulist_col_edits en : Edits ru : Правок +cs : Editace +hu : Szerkesztések :_ulist_col_tags en : Tags ru : Тегов +cs : Tagy +hu : Címkék @@ -2674,86 +4546,128 @@ ru : Тегов :_vnbrowse_title en : Browse visual novels ru : Обзор новелл +cs : Procházet vizuální novely +hu : Visual novellek böngészése :_vnbrowse_col_score en : Score ru : Рейтинг +cs : Skóre +hu : Pontozás :_vnbrowse_col_title en : Title ru : Название +cs : Název +hu : Cím :_vnbrowse_col_released en : Released ru : Выпуск +cs : Vydáno +hu : Kiadva :_vnbrowse_col_popularity en : Popularity ru : Популярность +cs : Popularita +hu : Népszerűség :_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: :_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ő :_vnbrowse_tagign_notfound en : no such tag found ru : тег не найден +cs : nenalezeny takové tagy +hu : nem található ilyen címke :_vnbrowse_advsearch en : advanced search ru : расширенный поиск +cs : pokročilé hledání +hu : bővített keresés :_vnbrowse_tags en : Tag filters ru : Фильтры тегов +cs : Filtry tagů +hu : címke szűrők :_vnbrowse_booland en : boolean and, selecting more gives less results ru : булевое 'и', чем больше выбрано, тем меньше даёт результатов +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 :_vnbrowse_taginc en : Tags to include ru : Включить теги +cs : Tagy k zahrnutí +hu : Címkék beleszámítása :_vnbrowse_tagexc en : Tags to exclude ru : Исключить теги +cs : Tagy k vyjmutí +hu : Címkék kihagyása :_vnbrowse_spoil0 en : Hide spoilers ru : Скрыть спойлеры +cs : Skrýt spoilery +hu : Spoilerek elrejtése :_vnbrowse_spoil1 en : Show minor spoilers ru : Показать лёгкие спойлеры +cs : Ukázat menší spoilery +hu : Kisebb spoilerek megjelenítése :_vnbrowse_spoil2 en : Show major spoilers ru : Показать жёсткие спойлеры +cs : Ukázat všechny spoilery +hu : Nagyobb spoilerek megjelenítése :_vnbrowse_lang en : Languages ru : Языки +cs : Jazyky +hu : Nyelvek :_vnbrowse_boolor en : boolean or, selecting more gives more results ru : булевое 'или', чем больше выбрано, тем больше даёт результатов +cs : boolean nebo, výběr více dá více výsledků +hu : Boole féle értékhalmaz(igaz/hamis), ha többet választasz ki akkor több találatot ad ki :_vnbrowse_plat en : Platforms ru : Платформы +cs : Platformy +hu : Platformok :_vnbrowse_apply en : Apply ru : Применить +cs : Použít +hu : Alkalmazás :_vnbrowse_clear en : Clear ru : Очистить +cs : Začít znovu +hu : Törlés # VN add/edit form (/v+/edit) @@ -2761,63 +4675,95 @@ ru : Очистить :_vnedit_title_edit en : Edit [_1] ru : Правка [_1] +cs : Editovat vizuální novelu [_1] +hu : [_1] szerkesztése :_vnedit_title_add en : Add a new visual novel ru : Добавление новой новеллы +cs : Přidat novou vizuální novelu +hu : Új visual novel hozzáadása :_vnedit_geninfo en : General info ru : Основная информация +cs : Obecné informace +hu : Általános info :_vnedit_frm_title en : Title (romaji) ru : Название (ромадзи) +cs : Název (romaji) +hu : Cím (romaji) :_vnedit_original en : Original title ru : Оригинальное название +cs : Originální název +hu : Eredeti cím :_vnedit_original_msg en : The original title of this visual novel, leave blank if it already is in the Latin alphabet. 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. :_vnedit_alias en : Aliases ru : Прочие названия +cs : Aliasy +hu : Más nevek :_vnedit_alias_msg -en : Comma seperated list of alternative titles or abbreviations. Can include both official +en : Comma separated list of alternative titles or abbreviations. Can include both official (japanese/english) titles and unofficial titles used around net.[br] Titles that are listed in the releases should not be added here! ru : Список альтернативных названий или аббревиатур, перечисленных через запятую. Может включать как официальные (японские/английские/русские) названия, так и неофициальные, имеющие хождение в сети.[br] Не следует добавлять сюда названия, указанные в выпусках! +cs : Seznam alternativních názvů nebo zkratek, oddělených čárkou. Může zahrnovat oba + (japonský/anglický) názvy a neoficiální názvy používané na internetu.[br] + Názvy, vypsané ve vydáních by se sem přidávat neměly! +hu : Vesszővel elválasztott lista, más címekkel vagy rövidítésekkel. Tartalmazhat hivatalos + (japán/angol) címeket vagy nem hivatalosokat amelyet a netten használnak.[br] + Azok a címek amelyek a kiadásokba vannak ne tegyétek bele! :_vnedit_desc en : Description ru : Описание +cs : Popis +hu : Leírás :_vnedit_desc_msg en : Short description of the main story. Please do not include spoilers, and don't forget to list the source in case you didn't write the description yourself. (formatting codes are allowed) ru : Краткое описание главной сюжетной линии. Пожалуйста, не вносите сюда спойлеры, а так же не забывайте указать источник описания, если не являетесь его автором. (коды форматирования разрешены) +cs : Krátký popis hlavního příběhu. Prosím nepište spoilery a nezapomeňte uvést zdroj, + pokud jste popis nenapsali sami. (formátovací kódy jsou povoleny) +hu : A fő történet rövid leírása. Ne tartalmazzon spoilert és ha nem te írtad akkor ne felejtsd el + megemlíteni a forrást amit felhasználtál. (formázó kódok megengedettek) :_vnedit_length en : Length ru : Продолжительность +cs : Délka +hu : Hossz :_vnedit_links en : External links ru : Внешние ссылки +cs : Externí odkazy +hu : Külső linkek :_vnedit_anime en : Anime ru : Аниме +cs : Anime +hu : :_vnedit_anime_msg -en : Whitespace seperated list of [url,http://anidb.net/,AniDB] anime IDs. +en : Whitespace separated list of [url,http://anidb.net/,AniDB] anime IDs. E.g. "1015 3348" will add [url,http://anidb.net/a1015,Shingetsutan Tsukihime] and [url,http://anidb.net/a3348,Fate/stay night] as related anime.[br] Note: It can take a few minutes for the anime titles to appear on the VN page. @@ -2825,22 +4771,38 @@ ru : Список идентификаторов аниме по [url,http://ani Например, "1015 3348" добавит связь с аниме [url,http://anidb.net/a1015,Shingetsutan Tsukihime] и [url,http://anidb.net/a3348,Fate/stay night].[br] Замечание: появление названия аниме на странице новеллы может занять несколько минут. +cs : Seznam AnimeID z [url,http://anidb.net/,AniDB], oddělených mezerou. + Např. "1015 3348" přidá [url,http://anidb.net/a1015,Shingetsutan Tsukihime] + a [url,http://anidb.net/a3348,Fate/stay night] jako příbuzná anime.[br] + Poznámka: Může trvat několik minut, než se názvy anime objeví na stránce vizuální novely. +hu : Szóközzel elválasztott lista [url,http://anidb.net/,AniDB] anime ID-kal. + Pl. "1015 3348" belinkeli [url,http://anidb.net/a1015,Shingetsutan Tsukihime] + és [url,http://anidb.net/a3348,Fate/stay night] mint kapcsolódó animék.[br] + Figyelem: Beletelhet pár percbe míg az anime címek megjelennek a VN oldalán. :_vnedit_image en : Image ru : Изображение +cs : Obrázek +hu : Kép :_vnedit_image_none en : No image uploaded yet ru : Изображения пока нет +cs : Obrázek ještě nebyl nahrán +hu : Még nincs kép feltöltve :_vnedit_image_processing en : ~[processing image, please return in a few minutes~] ru : ~[обработка изображения, пожалуйста подождите несколько минут~] +cs : ~[obrázek se zpracovává, vraťte se prosím za několik minut~] +hu : ~[a kép feldolgozás alatt van, gyere vissza pár perc múlva~] :_vnedit_image_upload en : Upload new image ru : Загрузить новое изображение +cs : Nahrát nový obrázek +hu : Tölts fel egy új képet :_vnedit_image_upload_msg en : Preferably the cover of the CD/DVD/package. Image must be in JPEG or PNG format @@ -2848,36 +4810,105 @@ en : Preferably the cover of the CD/DVD/package. Image must be in JPEG or PNG fo ru : Желательно, обложка CD/DVD/коробки. Изображение должно быть в формате JPEG, либо в формате PNG, и весить не более 500 Кб. Изображения размером более 256 на 400 точек будут автоматически уменьшены. +cs : Pokud možno obálka CD/DVD/balení. Obrázek musí být ve formátu JPEG nebo PNG + a ne větší 500kB. Obrázek větší než 256x400 pixelů bude automaticky zmenšen. +hu : Lehetőleg a CD/DVD/csomagolás borítóját. A képek muszáj JPEG vagy PNG formátumba + legyenek és maximum 500kb nagyok. 254x400-nál nagyobb képek automatikusan át lesznek + méretezve. :_vnedit_image_nsfw en : NSFW ru : НБДР (NSFW) +cs : NSFW +hu : Nem Biztonságos :_vnedit_image_nsfw_check en : Not Safe For Work ru : Не безопасно для работы +cs : Not Safe For Work +hu : Nem biztonságos :_vnedit_image_nsfw_msg en : Please check this option if the image contains nudity, gore, or is otherwise not safe in a work-friendly environment. ru : Пожалуйста, поставьте эту галочку если изображение содержит наготу, кровищу, либо тем или иным образом не безопасно для рабочего окружения. +cs : Prosíme, zaškrtněte tuto možnost, pokud obrázek obsahuje nahotu, gore nebo je jinak nevhodný pro pracovní prostředí. +hu : Kérlek pipáld ezt be ha a kép tartalmaz meztelenséget, vért vagy más módon nem szalon képes dolgokat. :_vnedit_rel en : Relations ru : Связи +cs : Vztahy +hu : Összefüggések :_vnedit_rel_sel en : Selected relations ru : Выбранные связи +cs : Vybrané vztahy +hu : Kiválasztott összefüggések :_vnedit_rel_add en : Add relation ru : Добавить связь +cs : Přidat vztah +hu : Összefüggés hozzáadása + +# <title1> is a <relation> of <title2> +# this trick doesn't even work very well in English, so just an approximation is fine +:_vnedit_rel_isa +en : is a +ru : это +cs : je +hu*: - + +:_vnedit_rel_of +en : of +ru : для +cs : titulu +hu*: - + +:_vnedit_rel_addbut +en : add +ru : добавить +cs : přidat +hu : hozzáadd + +:_vnedit_rel_del +en : del +ru : убрать +cs : smazat +hu : töröl + +:_vnedit_rel_none +en : No relations selected. +ru : Отношений не выбрано. +cs : Nejsou vybrány žádné vztahy. +hu : Semmilyen összefüggés nincs kiválasztva. + +:_vnedit_rel_findformat +en : Visual novel textbox must start with an ID (e.g. v17) +ru : Строка новеллы должна начинаться с идентификатора (например, v17) +cs : textové pole vizuální novely musí začínat ID (např. v17) +hu : A visual novel szövegdoboz muszáj egy ID-val kezdődjön (pl. v17) + +:_vnedit_rel_novn +en : Visual novel not found! +ru : Новелла не найдена! +cs : Vizuální novela nenalezena! +hu : A visual novel nem található! + +:_vnedit_rel_double +en : This visual novel has already been selected! +ru : Эта новелла уже была выбрана! +cs : Tato vizuální novela již byla vybrána! +hu : Ez a visual novel már ki van választva! :_vnedit_scr en : Screenshots ru : Скриншоты +cs : Screenshoty +hu : Pillanatképek -:_vnedit_scr_msg +:_vnedit_scrmsg en : Please keep the following in mind when uploading screenshots:[br] - Screenshots have to be in the native resolution of the game,[br] - Remove any window borders and make sure the image is unmarked,[br] @@ -2890,6 +4921,168 @@ ru : Когда загружаете скриншоты, пожалуйста п - Старайтесь загружать не только одни сюжетные картинки.[br] За подробностями обращайтесь к [url,/d2#6,рекомендациям].[br] Не забудьте сохранить изменения когда закончите загружать изображения! +cs : Prosíme, pamatujte na následující věci, pokud nahráváte screenshoty:[br] + - Screenshots musí být v původním rozlišení hry,[br] + - Odstraňte všechna okna kolem a ujistěte se, že na obrázku není nic navíc,[br] + - Nenahrávejte pouze CG.[br] + Prosíme, přečtěte si [url,/d2#6,doporučení] pro více informací.[br] + Ujistěte se, že jste potvrdili odeslání informací poté, co nahrajete obrázky! +hu : Mielőtt pillanatképeket töltesz fel, tarts néhány dolgot figyelembe:[br] + - A pillanatképek a játék eredeti felbontásába kell legyenek,[br] + - Távolíts el bármilyen ablak szélet és vigyázz, hogy a képen ne legyenek jelölések,[br] + - Ne csak esemény CG-ket tölts fel.[br] + Legyél szíves és olvasd el az [url,/d2#6,útmutatókat] több információért.[br] + A feltöltés után ne felejtsd beküldeni a formulát! + +:_vnedit_scr_selrel +en : -- select release -- +ru : -- выбор выпуска -- +cs : -- vybrat vydání -- +hu : -- kiadás kiválasztása -- + +:_vnedit_scr_frmloading +en : Please wait for the screenshots to be uploaded before submitting the form. +ru : Прежде чем сохранить изменения, дождитесь загрузки скриншотов на сервер. +cs : Prosíme, počkejte na nahrání screenshotů před potvrzením změn. +hu : Kérlek várj míg a pillanatképek feltöltődnek, mielőtt beküldenéd a forma lapot. + +:_vnedit_scr_frmnorel +en : Please select the appropriate release for every screenshot. +ru : Пожалуйста, укажите подходящий выпуск для каждого скриншота. +cs : Prosíme, vyberte příslušné vydání pro každý screenshot. +hu : Kérlek válaszd ki a megfelelő kiadást mindegyik pillanatképhez. + +:_vnedit_scr_fetching +en : Fetching thumbnail... +ru : Обработка превьюшки... +cs : Načítám náhled... +hu : Indexképek előkészítése... + +:_vnedit_scr_uploading +en : Uploading screenshot +ru : Загрузка скриншота +cs : Nahrávám screenshot +hu : Pillanatkép feltöltése + +:_vnedit_scr_upl_msg +en : This can take a while, depending on the file size and your upload speed. +ru : Это может занять некоторое время, в зависимости от размера файла и скорости вашего соединения. +cs : Toto může chvíli trvat, v závislosti na velikosti souboru a vaší rychlosti uploadu. +hu : Ez eltarthat egy ideig, attól függ milyen nagy a fájl mérete és mennyivel tudsz feltölteni. + +:_vnedit_scr_cancel +en : cancel +ru : отменить +cs : zrušit +hu : mégse + +:_vnedit_scr_full +en : Enough screenshots +ru : Больше нет места +cs : Již máme dostatek screenshotů +hu : Elég ennyi pillanatkép + +:_vnedit_scr_full_msg +en : The limit of 10 screenshots per visual novel has been reached. + If you want to add a new screenshot, please remove an existing one first. +ru : Достигнуто ограничение в 10 скриншотов. + Если вы хотите добавить новый скриншот, пожалуйста удалите один из существующих. +cs : Limit deseti screenshotů na vizuální novelu byl dosažen. + Pokud chcete přidat další screenshot, prosíme odstraňte nejdříve nějaký již existující. +hu : A megengedett 10 pillanatkép/visual novel megtelt. + Ha szeretnél egy új pillanatképet feltölteni, akkor el kell távolíts egy már létezőt. + +:_vnedit_scr_add +en : Add screenshot +ru : Добавить скриншот +cs : Přidat screenshot +hu : Pillanatkép hozzáadása + +:_vnedit_scr_imgnote +en : Image must be smaller than 5MB and in PNG or JPEG format. +ru : Изображение должно быть не более 5 Мегабайт и в формате PNG, либо JPEG. +cs : Obrázek musí být menší 5MB a ve formátu PNG nebo JPEG. +hu : A kép kisebb kell legyen mint 5MB és PNG vagy JPEG formátumba kell legyen. + +:_vnedit_scr_addbut +en : Upload! +ru : Загрузить! +cs : Nahrát! +hu : Feltöltés! + +:_vnedit_scr_id +en : Screenshot #[_1] +ru : Скриншот #[_1] +cs : Screenshot #[_1] +hu : #[_1]-dik pillanatkép + +:_vnedit_scr_remove +en : remove +ru : убрать +cs : odebrat +hu : eltávolítás + +:_vnedit_scr_fullsize +en : Full size: [_1] +ru : Полный размер: [_1] +cs : Plná velikost: [_1] +hu : Teljes méret: [_1] + +:_vnedit_scr_nsfw +en : This screenshot is NSFW +ru : Этот скриншот НБДР (NSFW) +cs : Tento screenshot je NSFW +hu : Ez a pillanatkép nem biztonságos + +:_vnedit_scr_oops +en : Oops! Seems like something went wrong... + Make sure the file you're uploading doesn't exceed 5MB in size. + If that isn't the problem, then please report a bug. +ru : Э-кхм! Похоже, возникла проблема... + Пожалуйста, убедитесь что ваш файл не превышает размер в 5 Мегабайт. + Если дело не в размере, тогда пожалуйста сообщите администрации сайта об ошибке. +cs : Hups! Zdá se, že se něco pokazilo... + Ujistěte se, že soubor, který nahráváte nemá více jak 5MB. + Pokud v tom problém neleží, nahlaste systémovou chybu. +hu : Hoppá! Úgy tűnik valami rosszul sült el... + Ellenőrizd, hogy a fájl amit fel akarsz tölteni nem haladja meg az 5MB-os méretet. + Ha nem az a baj, akkor kérlek jelentsd a hibát. + +:_vnedit_scr_errformat +en : Upload failed! + Only JPEG or PNG images are accepted. +ru : Ошибка при загрузке! + Допускаются только JPEG или PNG. +cs : Nahrání se nezdařilo! + Přijímány jsou pouze obrázky formátu JPEG nebo PNG. +hu : Feltöltés sikertelen! + Csakis JPEG vagy PNG képek az elfogadhatóak. + +:_vnedit_scr_errempty +en : Upload failed! + No file selected, or an empty file? +ru : Ошибка при загрузке! + Файл не выбран или имеет нулевой размер. +cs : Nahrání se nezdařilo! + Buď jste nevybrali soubor, nebo byl tento soubor prázdný. +hu : Feltöltés sikertelen! + Nincs fájl kiválasztva, vagy üres volna a fájl? + +:_vnedit_scr_genthumb +en : Generating thumbnail... +ru : Генерация превьюшки... +cs : Tvořím náhled... +hu : Indexkép készítése + +:_vnedit_scr_genthumb_msg +en : Note: if this takes longer than 30 seconds, there\'s probably something wrong on our side. + Please try again later or report a bug if that is the case. +ru : Замечание: если эта операция длится более 30 секунд, значит у нас на сервере неполадки. + В таком случае попытайтесь снова или сообщите администрации об ошибках. +cs : Poznámka: Pokud toto zabere více jak 30 sekund, je pravděpodobně chyba někde na naší straně. + Prosíme, zkuste to znovu později nebo nahlaste systémovou chybu, pokud tomu tak je. +hu : Figyelem: ha ez tovább tart mint 30 másodperc, akkor a mivelünk van a gond. + Kérlek próbáld meg később és ha adott az eset, akkor jelentsd a hibát. # VN Relation graph page (/v+/rg) @@ -2897,6 +5090,8 @@ ru : Когда загружаете скриншоты, пожалуйста п :_vnrg_title en : Relation graph for [_1] ru : Схема связей для [_1] +cs : Graf vztahů pro vizuální novelu [_1] +hu : Összefüggés gráf [_1]-hoz # VN Diff viewer (/v+.+) @@ -2904,86 +5099,128 @@ ru : Схема связей для [_1] :_revfield_v_title en : Title (romaji) ru : Название (ромадзи) +cs : Název (romaji) +hu : Cím (romaji) :_revfield_v_original en : Original title ru : Оригинальное название +cs : Originální název +hu : Eredeti cím :_revfield_v_alias en : Alias ru : Прочие названия +cs : Alias +hu : Más nevek :_revfield_v_desc en : Description ru : Описание +cs : Popis +hu : Leírás :_revfield_v_length en : Length ru : Продолжительность +cs : Délka +hu : Hossz :_vndiff_nolink en : ~[no link~] ru : ~[нет ссылки~] +cs : ~[žádný odkaz~] +hu : ~[nincs link~] :_vndiff_none en : ~[none~] ru : ~[пусто~] +cs : ~[nic~] +hu : ~[nincs~] :_revfield_v_l_wp en : Wikipedia link ru : Ссылка Википедии +cs : Link na Wikipedii +hu : :_revfield_v_l_encubed en : Encubed tag ru : Тег Encubed'а +cs : Tag na Encubed +hu : Encubed címke :_revfield_v_l_renai en : Renai.us link ru : Ссылка Renai.us +cs : Odkaz na Renai.us +hu : :_revfield_v_l_vnn en : V-N.net link ru : Ссылка V-N.net +cs : Odkaz na V-N.net +hu : :_revfield_v_relations en : Relations ru : Связи +cs : Vztahy +hu : Összefüggések :_revfield_v_anime en : Anime ru : Аниме +cs : Anime +hu : :_revfield_v_screenshots en : Screenshots ru : Скриншоты +cs : Screenshoty +hu : Pillanatképek :_revfield_v_image en : Image ru : Изображение +cs : Obrázek +hu : Kép :_vndiff_image_nsfw en : (NSFW) ru : (НБДР|NSFW) +cs : (NSFW) +hu : (Nem Biztonságos) :_vndiff_image_proc en : ~[processing~] ru : ~[обработка~] +cs : ~[zpracovává se~] +hu : ~[feldolgozás~] :_vndiff_image_none en : No image ru : Нет изображения +cs : Obrázek není +hu : Nincs kép :_revfield_v_img_nsfw en : Image NSFW ru : Изображение НБДР (NSFW) +cs : Vhodnost obrázku +hu : A kép nem biztonságos :_vndiff_nsfw_safe en : Safe ru : Безопасно +cs : SFW +hu : Biztonságos :_vndiff_nsfw_notsafe en : Not safe ru : Не безопасно +cs : NSFW +hu : Nem biztonságos # VN page (/v+) @@ -2991,194 +5228,334 @@ ru : Не безопасно :_vnpage_noimg en : No image uploaded yet ru : Нет загруженного изображения +cs : Ještě nebyl nahrán žádný obrázek +hu : Még nincs kép feltöltve :_vnpage_imgproc en : ~[processing image, please return in a few minutes~] ru : ~[идёт обработка изображения, подождите несколько минут~] +cs : ~[obrázek se zpracovává, prosíme, vraťte se za několik minut~] +hu : ~[a kép feldolgozás alatt van, gyere vissza pár perc múlva~] :_vnpage_imgnsfw_msg en : This image has been flagged as Not Safe For Work. ru : Данное изображение помечено как не безопасное для работы. +cs : Tento obrázek byl označen jako "Not Safe For Work" +hu : Ezt a képet Nem Biztonságosnak nyilvánították. :_vnpage_imgnsfw_show en : Show me anyway ru : Всё равно показать +cs : Ukázat i přes to +hu : Mutasd meg akkor is :_vnpage_imgnsfw_note en : (This warning can be disabled in your account) ru : (Это предупреждение можно отключить в настройках вашей учётной записи) +cs : (Toto varování může být vypnuto ve vašem nastavení) +hu : (Ezt a figyelmeztetést ki lehet kapcsolni a profilodba) :_vnpage_imgnsfw_foot en : Flagged as NSFW ru : Помечено как НБДР (NSFW) +cs : Označeno jako NSFW +hu : Nem biztonságosnak van bejelölve :_vnpage_vntitle en : Title ru : Название +cs : Název +hu : Cím :_vnpage_original en : Original title ru : Оригинальное название +cs : Originální název +hu : Eredeti cím :_vnpage_alias en : Aliases ru : Прочие названия +cs : Aliasy +hu : Más nevek :_vnpage_length en : Length ru : Продолжительность +cs : Délka +hu : Hossz :_vnpage_links en : Links ru : Ссылки +cs : Odkazy +hu : Linkek :_vnpage_description en : Description ru : Описание +cs : Popis +hu : Leírás :_vnpage_tags_spoil0 en : hide spoilers ru : скрыть спойлеры +cs : skrýt spoilery +hu : spoilerek elrejtése :_vnpage_tags_spoil1 en : show minor spoilers ru : показать лёгкие спойлеры +cs : ukázat menší spoilery +hu : kisebb spoilerek megjelenítése :_vnpage_tags_spoil2 en : spoil me! ru : показать все! +cs : prozraď mi vše! +hu : rontsd el nekem! :_vnpage_tags_summary en : summary ru : суммарно +cs : shrnutí +hu : összefoglalás :_vnpage_tags_all en : all ru : все +cs : vše +hu : mind -:_vnpage_producers -en : Producers -ru : Компании +:_vnpage_developer +en : Developer +ru : Разработчик +cs : Vývojář +hu : Fejlesztő + +:_vnpage_publisher +en : Publishers +ru : Издатели +cs : Vydavatel +hu : Kiadó :_vnpage_relations en : Relations ru : Связи +cs : Vztahy +hu : Összefüggések :_vnpage_anime en : Related anime ru : Связанное аниме +cs : Příbuzná anime +hu : Kapcsolódó animék :_vnpage_anime_noinfo en : ~[no information available at this time: [url,_2,_1]~] ru : ~[к сожалению, пока никакой информации: [url,_2,_1]~] +cs : ~[nyní nejsou dostupné žádné informace: [url,_2,_1]~] +hu : ~[jelenleg nincs semmilyen információ: [url,_2,_1]~] :_vnpage_uopt en : User options ru : Настройки пользователя +cs : Možnosti uživatele +hu : Felhasználó beállítások :_vnpage_uopt_voted en : your vote: [_1] ru : ваш голос: [_1] +cs : váš hlas: [_1] +hu : szavazatod: [_1] :_vnpage_uopt_novote en : not voted yet ru : пока без голоса +cs : ještě nehlasováno +hu : még nem kapott szavazatot :_vnpage_uopt_changevote en : Change vote ru : Переголосовать +cs : Změnit hlas +hu : Szavazat megváltoztatássa :_vnpage_uopt_dovote en : Vote ru : Голосовать +cs : Hlasovat +hu : Szavazás :_vnpage_uopt_delvote en : revoke ru : снять голос +cs : zdržet se +hu : visszavonás + +:_vnpage_uopt_1vote +en : You are about to give this visual novel a 1 out of 10. This is a rather extreme rating, meaning this game has absolutely nothing to offer, and that it's the worst game you have ever played. + Are you really sure this visual novel matches that description? +ru : Вы собираетесь дать этой новелле оценку 1 из 10. Это весьма сильная оценка, означающая что это совершенно бездарная работа и худшее из всего во что вы играли. + Вы уверены, что эта новелла соответствует такому критерию? +cs : Chystáte se této vizuální novele dát hodnocení 1 z 10. To je celkem extrémní hodnocení, znamenající, že tato hra nemá absolutně nic co nabídnout, a že je to ta nejhorší hra, kterou jste kdy hráli. + Jste si opravdu jisti, že tato vizuální novela odpovídá tomuto popisu? +hu : Te most épp a 10-ből, 1-et adsz ennek a visual novelnek. Ez egy elég extrém osztályozás, ez azt jelenti, hogy ez a játék egyáltalán semmit nem tud nyújtani és, hogy ez a legrosszabb amit valaha játszottál. + Biztos vagy benne, hogy ezt a visual novel azok közzé lehet sorolni? + +:_vnpage_uopt_10vote +en : You are about to give this visual novel a 10 out of 10. This is a rather extreme rating, meaning this is one of the best visual novels you've ever played and it's unlikely that any other game could ever be better than this one. + It is generally a bad idea to have more than three games in your vote list with this rating, choose carefully! +ru : Вы собираетесь дать этой новелле оценку 10 из 10. Это весьма сильная оценка, означающая что это одна из лучших когда либо выпущенных новелл, которую вы когда-либо читали. + Вы уверены, что эта новелла соответствует такому критерию? +cs : Chystáte se této vizuální novele dát hodnocení 10 z 10. To je celkem extrémní hodnocení, znamenající, že to je jedna z nejlepších vizuálních novel, které jste kdy hráli a je nepravděpodobné, že by nějaká další hra mohla být lepší než tato. + Obecně vztao je špatný nápad mít víc jak tři hry s tímto hodnocením ve vašem listu hlasování, vybírejte pečlivě! +hu : Te most épp a 10-ből, 10-et adsz ennek a visual novelnek. Ez egy elég extrém osztályozás, ami annyit jelent, hogy ez a legjobb visual novel amit valaha játszottál és elég valószínűtlen, hogy találkozol ettől jobb játékkal. + Általában rossz ötlet ha 3-nál több ilyen játékot tartasz a szavazási listádba, válasz óvatosan! :_vnpage_uopt_wishlisted en : wishlist: [_1] ru : список желаемого: [_1] +cs : wishlist: [_1] +hu : Kivánságlista: [_1] :_vnpage_uopt_nowish en : not on your wishlist ru : не в вашем списке +cs : není na vašem wishlistu +hu : nincs benne a kivánságlistádba :_vnpage_uopt_changewish en : Change status ru : Сменить статус +cs : Změnit status +hu : Állapot megváltoztatássa :_vnpage_uopt_addwish en : Add to wishlist ru : Добавить к списку +cs : Přidat na wishlist +hu : Hozzáadás a kivánságlistdhoz :_vnpage_uopt_delwish en : remove from wishlist ru : убрать из списка +cs : odstranit z wishlistu +hu : eltávolitás a kivánságlistából + +:_vnpage_uopt_relrstat +en : Release status +ru : Статус выпуска +cs : Status vydání +hu : Kiadás állapota + +:_vnpage_uopt_relvstat +en : Play status +ru : Статус играбельности +cs : Herní status +hu : Játszás állapota + +:_vnpage_uopt_reldel +en : Remove from VN list +ru : Убрать из списка новелл +cs : Odstranit z listu VN +hu : Eltávolítás a VN listából :_vnpage_rel en : Releases ru : Выпуски +cs : Vydání +hu : Kiadások :_vnpage_rel_none en : We don't have any information about releases of this visual novel yet... ru : У нас пока нет информации о выпусках этой новеллы... +cs : K této vizuální novele zatím nemáme informace o žádném vydání... +hu : Még nincs információnk ennek a visual novelnek a kiadásairól... :_vnpage_rel_add en : add release ru : добавить выпуск +cs : přidat vydání +hu : kiadás hozzáadása :_vnpage_rel_patch en : (patch) ru : (патч) +cs : (patch) +hu : :_vnpage_rel_extlink en : External link ru : Внешняя ссылка +cs : Externí odkaz +hu : Külső link :_vnpage_scr en : Screenshots ru : Скриншоты +cs : Screenshoty +hu : Pillanatképek :_vnpage_scr_showing en : Showing [_1] out of [_2] [quant,_2,screenshot,screenshots]. ru : [_1] из [_2] [quant,_2,скриншота,скриншотов,скриншотов] отображено. +cs : Ukazuji screenshot [_1] z celkém [_2] [quant,_2,screenshotu,screenshotů,screenshotů]. +hu : [_1] megjelenitve a [_2] [quant,_2,pillanatkép,pillanatképekből]. :_vnpage_scr_nsfwhide en : show/hide NSFW ru : показать/скрыть НБДР (NSFW) +cs : skrýt/zobrazit NSFW +hu : megjelenít/elrejt NB :_vnpage_scr_num en : Screenshot #[_1] ru : Скриншот #[_1] +cs : Screenshot #[_1] +hu : Pillanatkép #[_1] :_vnpage_stats en : User stats ru : Статистика пользователей +cs : Statistiky uživatelů +hu : Felhasználó adatok :_vnpage_stats_none en : Nobody has voted on this visual novel yet... ru : Никто пока не голосовал за эту новеллу... +cs : Pro tuto vizuální novelu ještě nikdo nehlasoval... +hu : Még senki nem szavazot erre a visual novelre... :_votestats_title en : Vote stats ru : Статистика голосования +cs : Statistika hlasování +hu : Szavazási adatok :_votestats_sum en : [_1] [quant,_1,vote,votes] total, average [_2] ru : Всего [_1] [quant,_1,голос,голоса,голосов], в среднем [_2] +cs : Celkem [_1] [quant,_1,hlas,hlasy,hlasů], průměr [_2] +hu : [_1] [quant,_1,szavazat,szavazatok] összesen, átlagba [_2] :_votestats_recent en : Recent votes ru : Недавно проголосовали +cs : Poslední hlasy +hu : Legútóbbi szavazatok :_votestats_pop_title en : Popularity ru : Популярность +cs : Popularita +hu : Népszerűsség :_votestats_pop_sum en : Ranked #[_1] out of [_2] with a score of [_3]. ru : Рейтинг - #[_1] из [_2], средний балл - [_3]. +cs : Hodnoceno na [_1]. místě z celkem [_2] se skóre [_3]. +hu : [_1]. helyezet a [_2] bejegyzésből [_3] pontszámmal. @@ -3195,24 +5572,34 @@ ru : Рейтинг - #[_1] из [_2], средний балл - [_3]. :_denied_title en : Access Denied ru : В доступе отказано +cs : Přístup odepřen +hu : Belépés megtagadva # not logged in :_denied_needlogin_title en : You need to be logged in to perform this action. ru : Для выполнения этого действия требуется авторизация. +cs : Pro vykonání této akce se musíte přihlásit. +hu : Be kell legyél jelentkezve ha ezt akarod csinálni. :_denied_needlogin_msg en : Please [url,/u/login,login], or [url,/u/register,create an account] if you don't have one yet. ru : Пожалуйста [url,/u/login,авторизуйтесь], либо [url,/u/register,создайте учётную запись], если таковой у вас нет. +cs : Prosíme, [url,/u/login,přihlašte se], nebo si [url,/u/register,vytvořte účet], pokud ještě žádný nemáte. +hu : Kérlek [url,/u/login,lépj be] vagy [url,/u/register,készíts egy fiókot] ha még nem volna. # logged in, but simply no access :_denied_noaccess_title en : You are not allowed to perform this action. ru : Вам запрещено выполнять это действие. +cs : Nemáte oprávnění provést tuto akci. +hu : Ezt a műveletet nem hajthatod végre. :_denied_noaccess_msg en : It seems you don't have the proper rights to perform the action you wanted to perform... 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... # "DB Item has been deleted" page @@ -3220,12 +5607,18 @@ ru : Похоже, у вас нет прав на выполнение того, :_hiddenmsg_title en : Item deleted ru : Запись удалена +cs : Položka smazána +hu : Tárgy törölve :_hiddenmsg_msg en : This item has been deleted from the database, File a request on the [url,_1,discussion board] to undelete this page. ru : Данная запись удалена из базы данных. Пожалуйста, подайте заявку на [url,_1,форуме] для восстановления этой страницы. +cs : Tato položka byla smazána z databáze. Pro obnovení této stránky napište žádost na + [url,_1,diskusní board]. +hu : Ez a bejegyzés ki lett törölve az adatbázisból. Nyújtsd be egy kérvényt a + [url,_1,fórumon], hogy állítsák vissza ezt az oldalt. # The warning/notice messages on edit pages @@ -3233,42 +5626,64 @@ ru : Данная запись удалена из базы данных. Пож :_editmsg_copy_title en : You're not editing a release! ru : Вы не редактируете выпуск, а копируете его! +cs : Needitujete vydání! +hu : Te most nem szerkesztesz egy kiadást! :_editmsg_copy_msg en : You're about to insert a new release into the database with information based on [_1].[br] Hit the 'edit' tab on the right-top if you intended to edit the release instead of creating a new one. ru : Вы собираетесь вставить новый выпуск, основанный на информации из [_1].[br] Щёлкните вкладку 'правка' в правом верхнем углу, если собирались редактировать выпуск, а не создавать новый. +cs : Chystáte se vložit nové vydání do databáze s informacemi založenými na vydání [_1].[br] + Pokud chcete editovat vydání místo toho, abyste vložili další vydání, přejděte na záložku 'editovat' v pravém horním rohu. +hu : Te most ép arra készülsz, hogy egy új kiadást hozzál létre az adatbázisban, [_1]-ra alapozva az információkat.[br] + Klikkelj a 'szerkesztés' fülre a jobb felső sarokba ha egy új kiadás helyet, csak szerkeszteni akartad a meglévőt. :_editmsg_msg_title en : Before editing: ru : Прежде чем приступить к редактированию: +cs : Před editací: +hu : Szerkesztés előtt: :_editmsg_msg_guidelines en : Read the [url,_1,guidelines]! ru : Прочтите [url,_1,рекомендации]! +cs : Přečtěte si [url,_1,doporučení]! +hu : Olvasd el az [url,_1,útmutatókat]! :_editmsg_msg_discuss en : Check for any existing discussions on the [url,_1,discussion board] ru : Проверьте все существующие темы на [url,_1,форуме] +cs : Podívejte se do existujících diskusí na [url,_1,diskusním boardu] +hu : Keress bármilyen meglévő beszélgetésre a [url,_1,fórumon] :_editmsg_msg_history en : Browse the [url,_1,edit history] for any recent changes related to what you want to change. ru : Просмотрите [url,_1,историю правок] на предмет недавних изменений данных, которые вы хотите изменить. +cs : Projděte [url,_1,historii editací] pro nedávné změny týkající se změn, které se chystáte provést. +hu : Nézz bele a [url,_1,szerkesztési előzményekbe], hogy lásd milyen változtatások voltak mielőtt te is megváltoztatnál valamit. :_editmsg_msg_search en : [url,_1,Search the database] to see if we already have information about this [index,_2,visual novel,release,producer]. ru : [url,_1,Воспользуйтесь поиском], ведь вполне возможно что у нас уже есть информация об [index,_2,этой новелле,этом выпуске,этой компании]. +cs : [url,_1,Prohledejte databázi], zda již nemáme informaci o [index,_2,této vizuální novele,tomto vydání,tomto producentovi]. +hu : [url,_1,Nézd át az adatbázist], hogy van-e már információ erről a [index,_2,visual novelről, kiadásról, készítőről]. :_editmsg_revert_title en : Reverting ru : Восстановление +cs : Vracení +hu : Visszaállítás :_editmsg_revert_msg en : You are editing an old revision of this [index,_1,visual novel,release,producer]. If you save it, all changes made after this revision will be reverted! ru : Вы правите старую редакцию страницы [index,_1,новеллы,выпуска,компании]. Если вы сохраните её, все изменения сделанные после этой правки будут утеряны! +cs : Editujete starší verzi [index,_1,této vizuální novely,tohoto vydání,tohoto producenta]. + Pokud ji uložíte, všechny změny provedené po této revizi budou vráceny! +hu : Te egy régi átjavítását szerkeszted ennek a [index,_1,visual novelnek, kiadásnak, készítőnek]. + Ha lemented, akkor minden szerkesztés amit ezután a javítás után végeztek megsemmisül! # Messages about editing on the VN/Release/Producer pages (right beneath the tabs) @@ -3276,14 +5691,20 @@ ru : Вы правите старую редакцию страницы [index,_ :_itemmsg_locked en : Locked for editing ru : Правка заблокирована +cs : Zamčeno pro editaci +hu : Lezárva :_itemmsg_login en : You need to be [url,_1,logged in] to edit this page ru : Чтобы редактировать эту страницу, вы должны быть [url,_1,авторизованы] +cs : Pro editaci této stránky musíte být [url,_1,přihlášen] +hu : Be kell legyél [url,_1, jelentkezve], hogy szerkeszthesd ezt az oldalt :_itemmsg_denied en : You are not allowed to edit this page ru : Вам запрещено редактировать эту страницу +cs : Nemáte oprávnění editovat tuto stránku +hu : Nincs engedélyed, hogy szerkeszd ezt az oldalt # User didn't pass the spam protection @@ -3291,14 +5712,20 @@ ru : Вам запрещено редактировать эту страниц :_nospam_title en : Could not send form ru : Невозможно отправить форму +cs : Formulář nemohl být odeslán +hu : Nem lehetett elküldeni a formulát :_nospam_subtitle en : Error ru : Ошибка +cs : Chyba +hu : Hiba :_nospam_msg en : The form could not be sent, please make sure you have Javascript enabled in your browser. ru : Не удалось отправить форму, пожалуйста убедитесь что в вашем браузере включён Javascript. +cs : Formulář nemohl být odeslán, ujistěte se, že máte ve svém prohlížeči povolen Javascript. +hu : A formulát nem lehetett elküldeni, kérlek ellenőrizd, hogy engedélyezve van a JavaScript a böngésződbe. # Short message reminding the user to post in ENGLISH (Used at about every message/description input field) @@ -3306,5 +5733,21 @@ ru : Не удалось отправить форму, пожалуйста у :_inenglish en : English please! ru : Пожалуйста, пишите на английском! +cs : Prosíme, anglicky! +hu : Kérlek angolul válaszolj! + + +# No browser support to display the relation graphs + +:_rg_notsupp +en : Not supported +ru : Не поддерживается +cs : Nepodporováno +hu : Nem támogatott +:_rg_notsupp_msg +en : Your browser sucks, it doesn't have the functionality to render our nice relation graphs. +ru : Ваш браузер настолько отстоен, что даже не может отрендерить наши графики. +cs : Váš prohlížeč je na nic, nepodporuje vykreslování našich krásných vztahových grafů. +hu : A böngésződ szar, nem képes megjeleníteni a szép összefüggés gráfunkat. diff --git a/data/script.js b/data/script.js new file mode 100644 index 00000000..78169c1b --- /dev/null +++ b/data/script.js @@ -0,0 +1,1902 @@ +/* function/attribute prefixes: + * date -> Date selector + * dd -> dropdown + * ds -> dropdown search + * iv -> image viewer + * jt -> Javascript Tabs + * med -> Release media selector + * prr -> Producer relation editor + * rl -> Release List dropdown + * rpr -> Release <-> producer linking + * rvn -> Release <-> visual novel linking + * scr -> VN screenshot uploader + * tgl -> VN tag linking + * tvs -> VN page tag spoilers + * vnr -> VN relation editor + */ +var expanded_icon = '▾'; +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) + http_request.abort(); + http_request = (window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(); + if(http_request == 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) + return; + if(http_request.status != 200) + return alert('Whoops, error! :('); + func(http_request); + }; + url += (url.indexOf('?')>=0 ? ';' : '?')+(Math.floor(Math.random()*999)+1); + http_request.open('GET', url, true); + http_request.send(null); +} + +function setCookie(n,v) { + var date = new Date(); + date.setTime(date.getTime()+(365*24*60*60*1000)); + document.cookie = n+'='+v+'; expires='+date.toGMTString()+'; path=/'; +} +function getCookie(n) { + var l = document.cookie.split(';'); + for(var i=0; i<l.length; i++) { + var c = l[i]; + while(c.charAt(0) == ' ') + c = c.substring(1,c.length); + if(c.indexOf(n+'=') == 0) + return c.substring(n.length+1,c.length); + } + return null; +} + +function byId(n) { + return document.getElementById(n) +} +function byName(){ + var d = arguments.length > 1 ? arguments[0] : document; + var n = arguments.length > 1 ? arguments[1] : arguments[0]; + return d.getElementsByTagName(n); +} +function byClass() { // [class], [parent, class], [tagname, class], [parent, tagname, class] + var par = typeof arguments[0] == 'object' ? arguments[0] : document; + var tag = arguments.length == 2 && typeof arguments[0] == 'string' ? arguments[0] : arguments.length == 3 ? arguments[1] : '*'; + var c = arguments[arguments.length-1]; + var l = byName(par, tag); + var ret = []; + for(var i=0; i<l.length; i++) + if(hasClass(l[i], c)) + ret[ret.length] = l[i]; + return ret; +} + +/* wrapper around DOM element creation + * tag('string') -> createTextNode + * tag('tagname', tag(), 'string', ..) -> createElement(), appendChild(), .. + * tag('tagname', { class: 'meh', title: 'Title' }) -> createElement(), setAttribute().. + * tag('tagname', { <attributes> }, <elements>) -> create, setattr, append */ +function tag() { + if(arguments.length == 1) + return typeof arguments[0] != 'object' ? document.createTextNode(arguments[0]) : arguments[0]; + var el = typeof document.createElementNS != 'undefined' + ? document.createElementNS('http://www.w3.org/1999/xhtml', arguments[0]) + : document.createElement(arguments[0]); + for(var i=1; i<arguments.length; i++) { + if(arguments[i] == null) + continue; + if(typeof arguments[i] == 'object' && !arguments[i].appendChild) { + for(attr in arguments[i]) { + if(attr == 'style') + el.setAttribute(attr, arguments[i][attr]); + else + el[ attr == 'class' ? 'className' : attr == 'for' ? 'htmlFor' : attr ] = arguments[i][attr]; + } + } else + el.appendChild(tag(arguments[i])); + } + return el; +} +function addBody(el) { + if(document.body.appendChild) + document.body.appendChild(el); + else if(document.documentElement.appendChild) + document.documentElement.appendChild(el); + else if(document.appendChild) + document.appendChild(el); +} +function setContent() { + setText(arguments[0], ''); + for(var i=1; i<arguments.length; i++) + arguments[0].appendChild(tag(arguments[i])); +} +function getText(obj) { + return obj.textContent || obj.innerText || ''; +} +function setText(obj, txt) { + if(obj.textContent != null) + obj.textContent = txt; + else + obj.innerText = txt; +} + +function listClass(obj) { + var n = obj.className; + if(!n) + return []; + return n.split(/ /); +} +function hasClass(obj, c) { + var l = listClass(obj); + for(var i=0; i<l.length; i++) + if(l[i] == c) + return true; + return false; +} +function setClass(obj, c, set) { + var l = listClass(obj); + var n = []; + if(set) { + n = l; + if(!hasClass(obj, c)) + n[n.length] = c; + } else { + for(var i=0; i<l.length; i++) + if(l[i] != c) + n[n.length] = l[i]; + } + obj.className = n.join(' '); +} + +function shorten(v, l) { + return v.length > l ? v.substr(0, l-3)+'...' : v; +} + +/* maketext function, less powerful than the Perl equivalent: + * - Only supports [_n], ~[, ~] + * - When it finds [quant,_n,..], it will only return the first argument (and doesn't support ~ in an argument) + * assumes that a TL structure called 'L10N_STR' is defined in the header of this file */ +var mt_curlang = getCookie('l10n') || 'en'; +function mt() { + var key = arguments[0]; + var val = L10N_STR[key] ? L10N_STR[key][mt_curlang] || L10N_STR[key].en : key; + for(var i=1; i<arguments.length; i++) { + var expr = '[_'+i+']'; + while(val.indexOf(expr) >= 0) + val = val.replace(expr, arguments[i]); + } + val = val.replace(/\[quant,_\d+\,([^,]+)[^\]]+\]/g, "$1"); + while(val.indexOf('~[') >= 0 || val.indexOf('~]') >= 0) + val = val.replace('~[', '[').replace('~]', ']'); + return val; +} + + + + +/* I M A G E V I E W E R */ + +function ivInit() { + var init = 0; + var l = byName('a'); + for(var i=0;i<l.length;i++) + if(l[i].rel.substr(0,3) == 'iv:') { + init++; + l[i].onclick = ivView; + } + if(init && !byId('iv_view')) { + addBody(tag('div', {id: 'iv_view'}, + tag('b', {id:'ivimg'}, ''), + tag('br', null), + tag('a', {href:'#', id:'ivfull'}, ''), + tag('a', {href:'#', onclick: ivClose, id:'ivclose'}, mt('_js_iv_close')), + tag('a', {href:'#', onclick: ivView, id:'ivprev'}, '« '+mt('_js_iv_prev')), + tag('a', {href:'#', onclick: ivView, id:'ivnext'}, mt('_js_iv_next')+' »') + )); + addBody(tag('b', {id:'ivimgload'}, mt('_js_loading'))); + } +} + +function ivView(what) { + what = what && what.rel ? what : this; + var u = what.href; + var opt = what.rel.split(':'); + var view = byId('iv_view'); + var next = byId('ivnext'); + var prev = byId('ivprev'); + var full = byId('ivfull'); + + // fix prev/next links (if any) + if(opt[2]) { + var ol = byName('a'); + var l=[]; + for(i=0;i<ol.length;i++) + if(ol[i].rel.substr(0,3) == 'iv:' && ol[i].rel.indexOf(':'+opt[2]) > 4 && !hasClass(ol[i], 'hidden') && ol[i].id != 'ivprev' && ol[i].id != 'ivnext') + l[l.length] = ol[i]; + for(i=0;i<l.length;i++) + if(l[i].href == u) { + next.style.visibility = l[i+1] ? 'visible' : 'hidden'; + next.href = l[i+1] ? l[i+1].href : '#'; + next.rel = l[i+1] ? l[i+1].rel : ''; + prev.style.visibility = l[i-1] ? 'visible' : 'hidden'; + prev.href = l[i-1] ? l[i-1].href : '#'; + prev.rel = l[i-1] ? l[i-1].rel : ''; + } + } else + next.style.visibility = prev.style.visibility = 'hidden'; + + // calculate dimensions + var w = Math.floor(opt[1].split('x')[0]); + var h = Math.floor(opt[1].split('x')[1]); + var ww = typeof(window.innerWidth) == 'number' ? window.innerWidth : document.documentElement.clientWidth; + var wh = typeof(window.innerHeight) == 'number' ? window.innerHeight : document.documentElement.clientHeight; + var st = typeof(window.pageYOffset) == 'number' ? window.pageYOffset : document.body && document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; + if(w+100 > ww || h+70 > wh) { + full.href = u; + setText(full, w+'x'+h); + full.style.visibility = 'visible'; + if(w/h > ww/wh) { // width++ + h *= (ww-100)/w; + w = ww-100; + } else { // height++ + w *= (wh-70)/h; + h = wh-70; + } + } else + full.style.visibility = 'hidden'; + var dw = w; + var dh = h+20; + dw = dw < 200 ? 200 : dw; + + // update document + view.style.display = 'block'; + setContent(byId('ivimg'), tag('img', {src:u, onclick:ivClose, + onload: function() { byId('ivimgload').style.top='-400px'; }, + style: 'width: '+w+'px; height: '+h+'px' + })); + view.style.width = dw+'px'; + view.style.height = dh+'px'; + view.style.left = ((ww - dw) / 2 - 10)+'px'; + view.style.top = ((wh - dh) / 2 + st - 20)+'px'; + byId('ivimgload').style.left = ((ww - 100) / 2 - 10)+'px'; + byId('ivimgload').style.top = ((wh - 20) / 2 + st)+'px'; + return false; +} + +function ivClose() { + byId('iv_view').style.display = 'none'; + byId('iv_view').style.top = '-5000px'; + byId('ivimgload').style.top = '-400px'; + setText(byId('ivimg'), ''); + return false; +} + +ivInit(); + + + + +/* D R O P D O W N */ + +function ddInit(obj, align, contents) { + obj.dd_align = align; // see ddRefresh for details + obj.dd_contents = contents; + document.onmousemove = ddMouseMove; + document.onscroll = ddHide; + if(!byId('dd_box')) + addBody(tag('div', {id:'dd_box', dd_used: false})); +} + +function ddHide() { + var box = byId('dd_box'); + setText(box, ''); + box.style.left = '-500px'; + box.dd_used = false; + box.dd_lnk = null; +} + +function ddMouseMove(e) { + e = e || window.event; + var lnk = e.target || e.srcElement; + while(lnk && (lnk.nodeType == 3 || !lnk.dd_align)) + lnk = lnk.parentNode; + var box = byId('dd_box'); + if(!box.dd_used && !lnk) + return; + + if(box.dd_used) { + var mouseX = e.pageX || (e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft); + var mouseY = e.pageY || (e.clientY + document.body.scrollTop + document.documentElement.scrollTop); + if((mouseX < ddx-10 || mouseX > ddx+box.offsetWidth+10 || mouseY < ddy-10 || mouseY > ddy+box.offsetHeight+10) + || (lnk && lnk == box.dd_lnk)) + ddHide(); + } + + if(!box.dd_used && lnk) { + box.dd_lnk = lnk; + box.dd_used = true; + if(!ddRefresh()) + ddHide(); + } +} + +function ddRefresh() { + var box = byId('dd_box'); + if(!box.dd_used) + return false; + var lnk = box.dd_lnk; + var content = lnk.dd_contents(lnk, box); + if(content == null) + return false; + setContent(box, content); + + var o = lnk; + ddx = ddy = 0; + do { + ddx += o.offsetLeft; + ddy += o.offsetTop; + } while(o = o.offsetParent); + + if(lnk.dd_align == 'left') + ddx -= box.offsetWidth; + if(lnk.dd_align == 'tagmod') + ddx += lnk.offsetWidth-35; + if(lnk.dd_align == 'bottom') + ddy += lnk.offsetHeight; + box.style.left = ddx+'px'; + box.style.top = ddy+'px'; + return true; +} + + +// release list dropdown on VN pages + +function rlDropDown(lnk) { + var relid = lnk.id.substr(6); + var st = getText(lnk).split(' / '); + if(st[0].indexOf(mt('_js_loading')) >= 0) + 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]); + if(st[0] && st[0].indexOf(val) >= 0) + rs.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]); + 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))); + } + + 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')))) + ); +} + +function rlMod() { + var lnk = byId('rlsel_'+this.rl_rid); + ddHide(); + setContent(lnk, tag('b', {'class': 'grayedout'}, mt('_js_loading'))); + ajax('/xml/rlist.xml?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; + }); + return false; +} + +{ + var l = byClass('a', 'vnrlsel'); + for(var i=0;i<l.length;i++) + ddInit(l[i], 'left', rlDropDown); +} + + + + +/* J A V A S C R I P T T A B S */ + +function jtInit() { + if(!byId('jt_select')) + return; + var sel = ''; + var first = ''; + var l = byName(byId('jt_select'), 'a'); + if(l.length < 1) + return; + for(var i=0; i<l.length; i++) { + l[i].onclick = jtSel; + if(!first) + first = l[i].id; + if(location.hash && l[i].id == 'jt_sel_'+location.hash.substr(1)) + sel = l[i].id; + } + if(!sel) + sel = first; + jtSel(sel, 1); +} + +function jtSel(which, nolink) { + which = typeof(which) == 'string' ? which : which && which.id ? which.id : this.id; + which = which.substr(7); + + var l = byName(byId('jt_select'), 'a'); + for(var i=0;i<l.length;i++) { + var name = l[i].id.substr(7); + if(name != 'all') + byId('jt_box_'+name).style.display = name == which || which == 'all' ? 'block' : 'none'; + var tab = l[i].parentNode; + setClass(tab, 'tabselected', name == which); + } + + if(!nolink) + location.href = '#'+which; + return false; +} + +jtInit(); + + + + +/* V N P A G E T A G S P O I L E R S */ + +function tvsInit() { + if(!byId('tagops')) + return; + var l = byName(byId('tagops'), 'a'); + for(var i=0;i<l.length; i++) + l[i].onclick = tvsClick; + tvsSet(getCookie('tagspoil'), true); +} + +function tvsClick() { + var sel; + var l = byName(byId('tagops'), 'a'); + for(var i=0; i<l.length; i++) + if(l[i] == this) { + if(i < 3) { + tvsSet(i, null); + setCookie('tagspoil', i); + } else + tvsSet(null, i == 3 ? true : false); + } + return false; +} + +function tvsSet(lvl, lim) { + /* set/get level and limit to/from the links */ + var l = byName(byId('tagops'), 'a'); + for(var i=0; i<l.length; i++) { + if(i < 3) { /* spoiler level */ + if(lvl != null) + setClass(l[i], 'tsel', i == lvl); + if(lvl == null && hasClass(l[i], 'tsel')) + lvl = i; + } else { /* display limit (3 = summary) */ + if(lim != null) + setClass(l[i], 'tsel', lim == (i == 3)); + if(lim == null && hasClass(l[i], 'tsel')) + lim = i == 3; + } + } + + /* update tag visibility */ + l = byName(byId('vntags'), 'span'); + lim = lim ? 15 : 999; + var s=0; + for(i=0;i<l.length;i++) { + var thislvl = l[i].className.substr(6, 1); + if(thislvl <= lvl && s < lim) { + setClass(l[i], 'hidden', false); + s++; + } else + setClass(l[i], 'hidden', true); + } + return false; +} + +tvsInit(); + + + + +/* D A T E I N P U T */ + +function dateLoad(obj) { + var val = Math.floor(obj.value) || 0; + val = [ Math.floor(val/10000), Math.floor(val/100)%100, val%100 ]; + + var year = tag('select', {style: 'width: 70px', onchange: dateSerialize}, tag('option', {value:0}, mt('_js_date_year'))); + for(var i=1980; i<=(new Date()).getFullYear()+5; i++) + year.appendChild(tag('option', {value: i, selected: i==val[0]}, i)); + year.appendChild(tag('option', {value: 9999, selected: val[0]==9999}, 'TBA')); + + var month = tag('select', {style: 'width: 70px', onchange: dateSerialize}, tag('option', {value:99}, mt('_js_date_month'))); + for(var i=1; i<=12; i++) + month.appendChild(tag('option', {value: i, selected: i==val[1]}, i)); + + var day = tag('select', {style: 'width: 70px', onchange: dateSerialize}, tag('option', {value:99}, mt('_js_date_day'))); + for(var i=1; i<=31; i++) + day.appendChild(tag('option', {value: i, selected: i==val[2]}, i)); + + obj.parentNode.insertBefore(tag('div', {date_obj: obj}, year, month, day), obj); +} + +function dateSerialize() { + var div = this.parentNode; + var sel = byName(div, 'select'); + var val = [ + sel[0].options[sel[0].selectedIndex].value*1, + sel[1].options[sel[1].selectedIndex].value*1, + sel[2].options[sel[2].selectedIndex].value*1 + ]; + div.date_obj.value = val[0] == 0 ? 0 : val[0] == 9999 ? 99999999 : val[0]*10000+val[1]*100+(val[1]==99?99:val[2]); +} + +{ + var l = byClass('input', 'dateinput'); + for(i=0; i<l.length; i++) + dateLoad(l[i]); +} + + + + +/* D R O P D O W N S E A R C H */ + +function dsInit(obj, url, trfunc, serfunc, retfunc, parfunc) { + obj.setAttribute('autocomplete', 'off'); + obj.onkeydown = dsKeyDown; + obj.onblur = function() { setTimeout(function () { byId('ds_box').style.top = '-500px'; }, 500) }; + obj.ds_returnFunc = retfunc; + obj.ds_trFunc = trfunc; + obj.ds_serFunc = serfunc; + obj.ds_parFunc = parfunc; + obj.ds_searchURL = url; + obj.ds_selectedId = 0; + obj.ds_dosearch = null; + if(!byId('ds_box')) + addBody(tag('div', {id: 'ds_box', style: 'position: absolute; top: -500px'}, tag('b', mt('_js_loading')))); +} + +function dsKeyDown(ev) { + var c = document.layers ? ev.which : document.all ? event.keyCode : ev.keyCode; + var obj = this; + + if(c == 9) // tab + return true; + + // do some processing when the enter key has been pressed + if(c == 13) { + var frm = obj; + while(frm && frm.nodeName.toLowerCase() != 'form') + frm = frm.parentNode; + if(frm) { + var oldsubmit = frm.onsubmit; + frm.onsubmit = function() { return false }; + setTimeout(function() { frm.onsubmit = oldsubmit }, 100); + } + + if(obj.ds_selectedId != 0) + obj.value = obj.ds_serFunc(byId('ds_box_'+obj.ds_selectedId).ds_itemData, obj); + if(obj.ds_returnFunc) + obj.ds_returnFunc(); + + byId('ds_box').style.top = '-500px'; + setContent(byId('ds_box'), tag('b', mt('_js_loading'))); + obj.ds_selectedId = 0; + if(obj.ds_dosearch) { + clearTimeout(obj.ds_dosearch); + obj.ds_dosearch = null; + } + + return false; + } + + // process up/down keys + if(c == 38 || c == 40) { + var l = byName(byId('ds_box'), 'tr'); + if(l.length < 1) + return true; + + // get new selected id + if(obj.ds_selectedId == 0) { + if(c == 38) // up + obj.ds_selectedId = l[l.length-1].id.substr(7); + else + obj.ds_selectedId = l[0].id.substr(7); + } else { + var sel = null; + for(var i=0; i<l.length; i++) + if(l[i].id == 'ds_box_'+obj.ds_selectedId) { + if(c == 38) // up + sel = i>0 ? l[i-1] : l[l.length-1]; + else + sel = l[i+1] ? l[i+1] : l[0]; + } + obj.ds_selectedId = sel.id.substr(7); + } + + // set selected class + for(var i=0; i<l.length; i++) + setClass(l[i], 'selected', l[i].id == 'ds_box_'+obj.ds_selectedId); + return true; + } + + // perform search after a timeout + if(obj.ds_dosearch) + clearTimeout(obj.ds_dosearch); + obj.ds_dosearch = setTimeout(function() { + dsSearch(obj); + }, 500); + + return true; +} + +function dsSearch(obj) { + var box = byId('ds_box'); + var val = obj.ds_parFunc ? obj.ds_parFunc(obj.value) : obj.value; + + clearTimeout(obj.ds_dosearch); + obj.ds_dosearch = null; + + // hide the ds_box div + if(val.length < 2) { + box.style.top = '-500px'; + setContent(box, tag('b', mt('_js_loading'))); + obj.ds_selectedId = 0; + return; + } + + // position the div + var ddx=0; + var ddy=obj.offsetHeight; + var o = obj; + do { + ddx += o.offsetLeft; + ddy += o.offsetTop; + } while(o = o.offsetParent); + + box.style.position = 'absolute'; + box.style.left = ddx+'px'; + box.style.top = ddy+'px'; + box.style.width = obj.offsetWidth+'px'; + + // perform search + ajax(obj.ds_searchURL + encodeURIComponent(val), function(hr) { + dsResults(hr, obj); + }); +} + +function dsResults(hr, obj) { + var lst = hr.responseXML.getElementsByTagName('item'); + var box = byId('ds_box'); + if(lst.length < 1) { + setContent(box, tag('b', mt('_js_ds_noresults'))); + obj.selectedId = 0; + return; + } + + var tb = tag('tbody', null); + for(var i=0; i<lst.length; i++) { + var id = lst[i].getAttribute('id'); + var tr = tag('tr', {id: 'ds_box_'+id, ds_itemData: lst[i]} ); + setClass(tr, 'selected', obj.selectedId == id); + + tr.onmouseover = function() { + obj.ds_selectedId = this.id.substr(7); + var l = byName(box, 'tr'); + for(var j=0; j<l.length; j++) + setClass(l[j], 'selected', l[j].id == 'ds_box_'+obj.ds_selectedId); + }; + tr.onmousedown = function() { + obj.value = obj.ds_serFunc(this.ds_itemData, obj); + if(obj.ds_returnFunc) + obj.ds_returnFunc(); + box.style.top = '-500px'; + obj.ds_selectedId = 0; + }; + + obj.ds_trFunc(lst[i], tr); + tb.appendChild(tr); + } + setContent(box, tag('table', tb)); + + if(obj.ds_selectedId != 0 && !byId('ds_box_'+obj.ds_selectedId)) + obj.ds_selectedId = 0; +} + + + + +/* V I S U A L N O V E L R E L A T I O N S (/v+/edit) */ + +function vnrLoad() { + // read the current relations + var rels = byId('vnrelations').value.split('|||'); + for(var i=0; i<rels.length && rels[0].length>1; i++) { + var rel = rels[i].split(',', 3); + vnrAdd(rel[0], rel[1], rel[2]); + } + vnrEmpty(); + + // make sure the title is up-to-date + byId('title').onchange = function() { + var l = byClass(byId('jt_box_vn_rel'), 'td', 'tc_title'); + for(i=0; i<l.length; i++) + setText(l[i], shorten(this.value, 40)); + }; + + // bind the add-link + byName(byClass(byId('relation_new'), 'td', 'tc_add')[0], 'a')[0].onclick = vnrFormAdd; + + // dropdown + dsInit(byName(byClass(byId('relation_new'), 'td', 'tc_vn')[0], 'input')[0], '/xml/vn.xml?q=', function(item, tr) { + tr.appendChild(tag('td', { style: 'text-align: right; padding-right: 5px'}, 'v'+item.getAttribute('id'))); + tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40))); + }, function(item) { + return 'v'+item.getAttribute('id')+':'+item.firstChild.nodeValue; + }, vnrFormAdd); +} + +function vnrAdd(rel, vid, title) { + var sel = tag('select', {onchange: vnrSerialize}); + var ops = byName(byClass(byId('relation_new'), 'td', 'tc_rel')[0], 'select')[0].options; + for(var i=0; i<ops.length; i++) + sel.appendChild(tag('option', {value: ops[i].value, selected: ops[i].value==rel}, getText(ops[i]))); + + byId('relation_tbl').appendChild(tag('tr', {id:'relation_tr_'+vid}, + tag('td', {'class':'tc_vn' }, 'v'+vid+':', tag('a', {href:'/v'+vid}, shorten(title, 40))), + tag('td', {'class':'tc_rel' }, mt('_vnedit_rel_isa')+' ', sel, ' '+mt('_vnedit_rel_of')), + tag('td', {'class':'tc_title'}, shorten(byId('title').value, 40)), + tag('td', {'class':'tc_add' }, tag('a', {href:'#', onclick:vnrDel}, mt('_vnedit_rel_del'))) + )); + + vnrEmpty(); +} + +function vnrEmpty() { + var tbl = byId('relation_tbl'); + if(byName(tbl, 'tr').length < 1) + tbl.appendChild(tag('tr', {id:'relation_tr_none'}, tag('td', {colspan:4}, mt('_vnedit_rel_none')))); + else if(byId('relation_tr_none')) + tbl.removeChild(byId('relation_tr_none')); +} + +function vnrSerialize() { + var r = []; + var trs = byName(byId('relation_tbl'), 'tr'); + for(var i=0; i<trs.length; i++) { + if(trs[i].id == 'relation_tr_none') + continue; + var rel = byName(byClass(trs[i], 'td', 'tc_rel')[0], 'select')[0]; + r[r.length] = [ + rel.options[rel.selectedIndex].value, // relation + trs[i].id.substr(12), // vid + getText(byName(byClass(trs[i], 'td', 'tc_vn')[0], 'a')[0]) // title + ].join(','); + } + byId('vnrelations').value = r.join('|||'); +} + +function vnrDel() { + var tr = this; + while(tr.nodeName.toLowerCase() != 'tr') + tr = tr.parentNode; + byId('relation_tbl').removeChild(tr); + vnrSerialize(); + vnrEmpty(); + return false; +} + +function vnrFormAdd() { + var relnew = byId('relation_new'); + var txt = byName(byClass(relnew, 'td', 'tc_vn')[0], 'input')[0]; + var sel = byName(byClass(relnew, 'td', 'tc_rel')[0], 'select')[0]; + var lnk = byName(byClass(relnew, 'td', 'tc_add')[0], 'a')[0]; + var input = txt.value; + + if(!input.match(/^v[0-9]+/)) { + alert('_vnedit_rel_findformat'); + return false; + } + + txt.disabled = sel.disabled = true; + txt.value = mt('_js_loading'); + setText(lnk, mt('_js_loading')); + + ajax('/xml/vn.xml?q='+encodeURIComponent(input), function(hr) { + txt.disabled = sel.disabled = false; + txt.value = ''; + setText(lnk, mt('_vnedit_rel_addbut')); + + var items = hr.responseXML.getElementsByTagName('item'); + if(items.length < 1) + return alert(mt('_vnedit_rel_novn')); + + var id = items[0].getAttribute('id'); + if(byId('relation_tr_'+id)) + return alert(mt('_vnedit_rel_double')); + + vnrAdd(sel.options[sel.selectedIndex].value, id, items[0].firstChild.nodeValue); + sel.selectedIndex = 0; + vnrSerialize(); + }); + return false; +} + +if(byId('vnrelations')) + vnrLoad(); + + + + +/* R E L E A S E M E D I A (/r+/edit) */ + +var medTypes = [ ]; +function medLoad() { + // load the medTypes and clear the div + var sel = byName(byId('media_div'), 'select')[0].options; + for(var i=0; i<sel.length; i++) + medTypes[medTypes.length] = [ sel[i].value, getText(sel[i]), !hasClass(sel[i], 'noqty') ]; + setText(byId('media_div'), ''); + + // load the selected media + var med = byId('media').value.split(','); + for(var i=0; i<med.length && med[i].length > 1; i++) + medAdd(med[i].split(' ')[0], Math.floor(med[i].split(' ')[1])); + + medAdd('', 0); +} + +function medAdd(med, qty) { + var qsel = tag('select', {'class':'qty', onchange:medSerialize}, tag('option', {value:0}, mt('_redit_form_med_quantity'))); + for(var i=1; i<=20; i++) + qsel.appendChild(tag('option', {value:i, selected: qty==i}, i)); + + var msel = tag('select', {'class':'medium', onchange: med == '' ? medFormAdd : medSerialize}); + if(med == '') + msel.appendChild(tag('option', {value:''}, mt('_redit_form_med_medium'))); + for(var i=0; i<medTypes.length; i++) + msel.appendChild(tag('option', {value:medTypes[i][0], selected: med==medTypes[i][0]}, medTypes[i][1])); + + byId('media_div').appendChild(tag('span', qsel, msel, + med != '' ? tag('input', {type: 'button', 'class':'submit', onclick:medDel, value:mt('_redit_form_med_remove')}) : null + )); +} + +function medDel() { + var span = this; + while(span.nodeName.toLowerCase() != 'span') + span = span.parentNode; + byId('media_div').removeChild(span); + medSerialize(); + return false; +} + +function medFormAdd() { + var span = this; + while(span.nodeName.toLowerCase() != 'span') + span = span.parentNode; + var med = byClass(span, 'select', 'medium')[0]; + var qty = byClass(span, 'select', 'qty')[0]; + if(!med.selectedIndex) + return; + medAdd(med.options[med.selectedIndex].value, qty.options[qty.selectedIndex].value); + byId('media_div').removeChild(span); + medAdd('', 0); + medSerialize(); +} + +function medSerialize() { + var r = []; + var meds = byName(byId('media_div'), 'span'); + for(var i=0; i<meds.length-1; i++) { + var med = byClass(meds[i], 'select', 'medium')[0]; + var qty = byClass(meds[i], 'select', 'qty')[0]; + + /* correct quantity if necessary */ + if(medTypes[med.selectedIndex][2] && !qty.selectedIndex) + qty.selectedIndex = 1; + if(!medTypes[med.selectedIndex][2] && qty.selectedIndex) + qty.selectedIndex = 0; + + r[r.length] = medTypes[med.selectedIndex][0] + ' ' + qty.selectedIndex; + } + byId('media').value = r.join(','); +} + +if(byId('jt_box_rel_format')) + medLoad(); + + + + +/* V I S U A L N O V E L S C R E E N S H O T U P L O A D E R (/v+/edit) */ + +var scrRel = [ [ 0, mt('_vnedit_scr_selrel') ] ]; +var scrStaticURL; +var scrUplNr = 0; + +function scrLoad() { + // get scrRel and scrStaticURL + var rel = byId('scr_rel'); + scrStaticURL = rel.className; + for(var i=0; i<rel.options.length; i++) + scrRel[scrRel.length] = [ rel.options[i].value, getText(rel.options[i]) ]; + rel.parentNode.removeChild(rel); + + // load the current screenshots + var scr = byId('screenshots').value.split(' '); + for(i=0; i<scr.length && scr[i].length>1; i++) { + var r = scr[i].split(','); + scrAdd(r[0], r[1], r[2]); + } + + scrLast(); + scrCheckStatus(); + scrSetSubmit(); +} + +function scrSetSubmit() { + var frm = byId('screenshots'); + while(frm.nodeName.toLowerCase() != 'form') + frm = frm.parentNode; + oldfunc = frm.onsubmit; + frm.onsubmit = function() { + var loading = 0; + var norelease = 0; + var l = byName(byId('scr_table'), 'tr'); + for(var i=0; i<l.length-1; i++) { + if(l[i].scr_status > 0) + loading = 1; + else if(byName(l[i], 'select')[0].selectedIndex == 0) + norelease = 1; + } + if(loading) { + alert(mt('_vnedit_scr_frmloading')); + return false; + } else if(norelease) { + alert(mt('_vnedit_scr_frmnorel')); + return false; + } else if(oldfunc) + return oldfunc(); + }; +} + +function scrURL(id, t) { + return scrStaticURL+'/s'+t+'/'+(id%100<10?'0':'')+(id%100)+'/'+id+'.jpg'; +} + +function scrAdd(id, nsfw, rel) { + // tr.scr_status = 0: done, 1: uploading, 2: waiting for thumbnail, 3: deleted + + var tr = tag('tr', { id:'scr_tr_'+id, scr_id: id, scr_status: id?2:1, scr_rel: rel, scr_nsfw: nsfw}, + tag('td', { 'class': 'thumb'}, mt('_js_loading')), + tag('td', + tag('b', mt(id ? '_vnedit_scr_fetching' : '_vnedit_scr_uploading')), + tag('br', null), + id ? null : mt('_vnedit_scr_upl_msg'), + tag('br', null), + id ? null : tag('a', {href:'#', onclick:scrDel}, mt('_vnedit_scr_cancel')) + ) + ); + byId('scr_table').appendChild(tr); + scrStripe(); + return tr; +} + +function scrLast() { + if(byId('scr_last')) + byId('scr_table').removeChild(byId('scr_last')); + var full = byName(byId('scr_table'), 'tr').length >= 10; + + byId('scr_table').appendChild(tag('tr', {id:'scr_last'}, + tag('td', {'class': 'thumb'}), + full ? tag('td', + tag('b', mt('_vnedit_scr_full')), + tag('br', null), + mt('_vnedit_scr_full_msg') + ) : tag('td', + tag('b', mt('_vnedit_scr_add')), + tag('br', null), + mt('_vnedit_scr_imgnote'), + tag('br', null), + tag('input', {name:'scr_upload', id:'scr_upload', type:'file', 'class':'text'}), + tag('br', null), + tag('input', {type:'button', value:mt('_vnedit_scr_addbut'), 'class':'submit', onclick:scrUpload}) + ) + )); + scrStripe(); +} + +function scrStripe() { + var l = byName(byId('scr_table'), 'tr'); + for(var i=0; i<l.length; i++) + setClass(l[i], 'odd', i%2==0); +} + +function scrCheckStatus() { + var ids = []; + var trs = byName(byId('scr_table'), 'tr'); + for(var i=0; i<trs.length-1; i++) + if(trs[i].scr_status == 2) + ids[ids.length] = 'id='+trs[i].scr_id; + if(!ids.length) + return setTimeout(scrCheckStatus, 1000); + + var ti = setTimeout(scrCheckStatus, 10000); + ajax('/xml/screenshots.xml?'+ids.join(';'), function(hr) { + var ls = hr.responseXML.getElementsByTagName('item'); + for(var i=0; i<ls.length; i++) { + var tr = byId('scr_tr_'+ls[i].getAttribute('id')); + if(!tr || ls[i].getAttribute('processed') != '1') + continue; + tr.scr_status = 0; // ready + + // image + var dim = ls[i].getAttribute('width')+'x'+ls[i].getAttribute('height'); + setContent(byName(tr, 'td')[0], + tag('a', {href: scrURL(tr.scr_id, 'f'), rel:'iv:'+dim+':edit'}, + tag('img', {src: scrURL(tr.scr_id, 't')}) + ) + ); + + // content + var rel = tag('select', {onchange: scrSerialize, 'class':'scr_relsel'}); + for(var j=0; j<scrRel.length; j++) + rel.appendChild(tag('option', {value: scrRel[j][0], selected: tr.scr_rel == scrRel[j][0]}, scrRel[j][1])); + var nsfwid = 'scr_sfw_'+tr.scr_id; + setContent(byName(tr, 'td')[1], + tag('b', mt('_vnedit_scr_id', tr.scr_id)), + ' (', tag('a', {href: '#', onclick:scrDel}, mt('_vnedit_scr_remove')), ')', + tag('br', null), + mt('_vnedit_scr_fullsize', dim), + tag('br', null), + tag('br', null), + tag('input', {type:'checkbox', onclick:scrSerialize, id:nsfwid, name:nsfwid, checked: tr.scr_nsfw>0, 'class':'scr_nsfw'}), + tag('label', {'for':nsfwid}, mt('_vnedit_scr_nsfw')), + tag('br', null), + rel + ); + } + scrSerialize(); + ivInit(); + clearTimeout(ti); + setTimeout(scrCheckStatus, 1000); + }); +} + +function scrDel(what) { + var tr = what && what.scr_status != null ? what : this; + while(tr.nodeName.toLowerCase() != 'tr') + tr = tr.parentNode; + tr.scr_status = 3; + byId('scr_table').removeChild(tr); + scrSerialize(); + scrLast(); + return false; +} + +function scrUpload() { + scrUplNr++; + + // create temporary form + var ifid = 'scr_upl_'+scrUplNr; + var frm = tag('form', {method: 'post', action:'/xml/screenshots.xml', target: ifid, enctype:'multipart/form-data'}); + var ifr = tag('iframe', {id:ifid, name:ifid, src:'about:blank', onload:scrUploadComplete}); + addBody(tag('div', {'class':'scr_uploader'}, ifr, frm)); + + // submit form and delete it + frm.appendChild(byId('scr_upload')); + frm.submit(); + frm.parentNode.removeChild(frm); + ifr.scr_tr = scrAdd(0, 0, 0); + scrLast(); + return false; +} + +function scrUploadComplete() { + var ifr = this; + var fr = window.frames[ifr.id]; + if(fr.location.href.indexOf('screenshots') < 0) + return; + + var tr = ifr.scr_tr; + if(!tr || tr.scr_status == 3) + return; + + try { + tr.scr_id = fr.window.document.getElementsByTagName('image')[0].getAttribute('id'); + } catch(e) { + tr.scr_id = -10; + } + if(tr.scr_id < 0) { + alert(mt(tr.scr_id == -10 ? '_vnedit_scr_oops' : tr.scr_id == -1 ? '_vnedit_scr_errformat' : '_vnedit_scr_errempty')); + return scrDel(tr); + } + + tr.id = 'scr_tr_'+tr.scr_id; + tr.scr_status = 2; + setContent(byName(tr, 'td')[1], + tag('b', mt('_vnedit_scr_genthumb')), + tag('br', null), + mt('_vnedit_scr_genthumb_msg') + ); + + // remove the <div> in a timeout, otherwise some browsers think the page is still loading + setTimeout(function() { ifr.parentNode.parentNode.removeChild(ifr.parentNode) }, 100); +} + +function scrSerialize() { + var r = []; + var l = byName(byId('scr_table'), 'tr'); + for(var i=0; i<l.length-1; i++) + if(l[i].scr_status == 0) + r[r.length] = [ + l[i].scr_id, + byClass(l[i], 'input', 'scr_nsfw')[0].checked ? 1 : 0, + scrRel[byClass(l[i], 'select', 'scr_relsel')[0].selectedIndex][0] + ].join(','); + byId('screenshots').value = r.join(' '); +} + +if(byId('jt_box_vn_scr')) + scrLoad(); + + + + +/* V I S U A L N O V E L T A G L I N K I N G (/v+/tagmod) */ + +var tglSpoilers = []; + +function tglLoad() { + for(var i=0; i<=3; i++) + tglSpoilers[i] = mt('_tagv_spoil'+i); + + // tag dropdown search + dsInit(byId('tagmod_tag'), '/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')) : + item.getAttribute('state') == 0 ? tag('b', {'class':'grayedout'}, ' '+mt('_js_ds_tag_mod')) : null + )); + }, function(item) { + return item.firstChild.nodeValue; + }, tglAdd); + byId('tagmod_add').onclick = tglAdd; + + // JS'ify the voting bar and spoiler setting + tglStripe(); + var trs = byName(byId('tagtable'), 'tr'); + for(var i=0; i<trs.length; i++) { + var vote = byClass(trs[i], 'td', 'tc_myvote')[0]; + vote.tgl_vote = getText(vote)*1; + tglVoteBar(vote); + + var spoil = byClass(trs[i], 'td', 'tc_myspoil')[0]; + spoil.tgl_spoil = getText(spoil)*1+1; + setText(spoil, tglSpoilers[spoil.tgl_spoil]); + ddInit(spoil, 'tagmod', tglSpoilDD); + spoil.onclick = tglSpoilNext; + } + tglSerialize(); +} + +function tglSpoilNext() { + if(++this.tgl_spoil >= tglSpoilers.length) + this.tgl_spoil = 0; + setText(this, tglSpoilers[this.tgl_spoil]); + tglSerialize(); + ddRefresh(); +} + +function tglSpoilDD(lnk) { + var lst = tag('ul', null); + for(var i=0; i<tglSpoilers.length; i++) + lst.appendChild(tag('li', i == lnk.tgl_spoil + ? tag('i', tglSpoilers[i]) + : tag('a', {href: '#', onclick:tglSpoilSet, tgl_td:lnk, tgl_sp:i}, tglSpoilers[i]) + )); + return lst; +} + +function tglSpoilSet() { + this.tgl_td.tgl_spoil = this.tgl_sp; + setText(this.tgl_td, tglSpoilers[this.tgl_sp]); + ddHide(); + tglSerialize(); + return false; +} + +function tglVoteBar(td, vote) { + setText(td, ''); + for(var i=-3; i<=3; i++) + td.appendChild(tag('a', { + 'class':'taglvl taglvl'+i, tgl_num: i, + onmouseover:tglVoteBarSel, onmouseout:tglVoteBarSel, onclick:tglVoteBarSel + }, ' ')); + tglVoteBarSel(td, td.tgl_vote); + return false; +} + +function tglVoteBarSel(td, vote) { + // nasty trick to make this function multifunctional + if(this && this.tgl_num != null) { + var e = td || window.event; + td = this.parentNode; + vote = this.tgl_num; + if(e.type.toLowerCase() == 'click') { + td.tgl_vote = vote; + tglSerialize(); + } + if(e.type.toLowerCase() == 'mouseout') + vote = td.tgl_vote; + } + var l = byName(td, 'a'); + var num; + for(var i=0; i<l.length; i++) { + num = l[i].tgl_num; + if(num == 0) + setText(l[i], vote || '-'); + else + setClass(l[i], 'taglvlsel', num<0&&vote<=num || num>0&&vote>=num); + } +} + +function tglAdd() { + var tg = byId('tagmod_tag'); + var add = byId('tagmod_add'); + tag.disabled = add.disabled = true; + add.value = mt('_js_loading'); + + ajax('/xml/tags.xml?q=name:'+encodeURIComponent(tg.value), function(hr) { + tg.disabled = add.disabled = false; + tg.value = ''; + add.value = mt('_tagv_add'); + + var items = hr.responseXML.getElementsByTagName('item'); + if(items.length < 1) + return alert(mt('_tagv_notfound')); + if(items[0].getAttribute('meta') == 'yes') + return alert(mt('_tagv_nometa')); + + var name = items[0].firstChild.nodeValue; + var id = items[0].getAttribute('id'); + if(byId('tgl_'+id)) + return alert(mt('_tagv_double')); + + var vote = tag('td', {'class':'tc_myvote', tgl_vote: 2}, ''); + tglVoteBar(vote); + var spoil = tag('td', {'class':'tc_myspoil', tgl_spoil: 0}, tglSpoilers[0]); + ddInit(spoil, 'tagmod', tglSpoilDD); + spoil.onclick = tglSpoilNext; + + byId('tagtable').appendChild(tag('tr', {id:'tgl_'+id}, + tag('td', {'class':'tc_tagname'}, tag('a', {href:'/g'+id}, name)), + vote, spoil, + tag('td', {'class':'tc_allvote'}, '-'), + tag('td', {'class':'tc_allspoil'}, '-') + )); + tglStripe(); + tglSerialize(); + }); +} + +function tglStripe() { + var l = byName(byId('tagtable'), 'tr'); + for(var i=0; i<l.length; i++) + setClass(l[i], 'odd', i%2); +} + +function tglSerialize() { + var r = []; + var l = byName(byId('tagtable'), 'tr'); + for(var i=0; i<l.length; i++) { + var vote = byClass(l[i], 'td', 'tc_myvote')[0].tgl_vote; + if(vote != 0) + r[r.length] = [ + l[i].id.substr(4), + vote, + byClass(l[i], 'td', 'tc_myspoil')[0].tgl_spoil-1 + ].join(','); + } + byId('taglinks').value = r.join(' '); +} + +if(byId('taglinks')) + tglLoad(); + + + + +/* R E L E A S E -> V I S U A L N O V E L L I N K I N G (/r+/edit) */ + +function rvnLoad() { + var vns = byId('vn').value.split('|||'); + for(var i=0; i<vns.length && vns[i].length>1; i++) + rvnAdd(vns[i].split(',',2)[0], vns[i].split(',',2)[1]); + rvnEmpty(); + + dsInit(byId('vn_input'), '/xml/vn.xml?q=', + function(item, tr) { + tr.appendChild(tag('td', {style:'text-align: right; padding-right: 5px'}, 'v'+item.getAttribute('id'))); + tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40))); + }, function(item) { + return 'v'+item.getAttribute('id')+':'+item.firstChild.nodeValue; + }, + rvnFormAdd + ); + byId('vn_add').onclick = rvnFormAdd; +} + +function rvnAdd(id, title) { + byId('vn_tbl').appendChild(tag('tr', {id:'rvn_'+id, rvn_id:id}, + tag('td', {'class':'tc_title'}, 'v'+id+':', tag('a', {href:'/v'+id}, shorten(title, 40))), + tag('td', {'class':'tc_rm'}, tag('a', {href:'#', onclick:rvnDel}, mt('_redit_form_vn_remove'))) + )); + rvnStripe(); + rvnEmpty(); +} + +function rvnDel() { + var tr = this; + while(tr.nodeName.toLowerCase() != 'tr') + tr = tr.parentNode; + tr.parentNode.removeChild(tr); + rvnEmpty(); + rvnSerialize(); + rvnStripe(); + return false; +} + +function rvnEmpty() { + var tbl = byId('vn_tbl'); + if(byName(tbl, 'tr').length < 1) + tbl.appendChild(tag('tr', {id:'rvn_tr_none'}, tag('td', {colspan:2}, mt('_redit_form_vn_none')))); + else if(byId('rvn_tr_none')) + tbl.removeChild(byId('rvn_tr_none')); +} + +function rvnStripe() { + var l = byName(byId('vn_tbl'), 'tr'); + for(var i=0; i<l.length; i++) + setClass(l[i], 'odd', i%2); +} + +function rvnFormAdd() { + var txt = byId('vn_input'); + var lnk = byId('vn_add'); + var val = txt.value; + + if(!val.match(/^v[0-9]+/)) { + alert(mt('_redit_form_vn_vnformat')); + return false; + } + + txt.disabled = true; + txt.value = mt('_js_loading'); + setText(lnk, mt('_js_loading')); + + ajax('/xml/vn.xml?q='+encodeURIComponent(val), function(hr) { + txt.disabled = false; + txt.value = ''; + setText(lnk, mt('_redit_form_vn_addbut')); + + var items = hr.responseXML.getElementsByTagName('item'); + if(items.length < 1) + return alert(mt('_redit_form_vn_notfound')); + + var id = items[0].getAttribute('id'); + if(byId('rvn_'+id)) + return alert(mt('_redit_form_vn_double')); + + rvnAdd(id, items[0].firstChild.nodeValue); + rvnSerialize(); + }); + return false; +} + +function rvnSerialize() { + var r = []; + var l = byName(byId('vn_tbl'), 'tr'); + for(var i=0; i<l.length; i++) + if(l[i].rvn_id) + r[r.length] = l[i].rvn_id + ',' + getText(byName(byClass(l[i], 'td', 'tc_title')[0], 'a')[0]); + byId('vn').value = r.join('|||'); +} + +if(byId('jt_box_rel_vn')) + rvnLoad(); + + + + +/* R E L E A S E -> P R O D U C E R L I N K I N G (/r+/edit) */ + +function rprLoad() { + var ps = byId('producers').value.split('|||'); + for(var i=0; i<ps.length && ps[i].length>1; i++) { + var val = ps[i].split(',',3); + rprAdd(val[0], val[1], val[2]); + } + rprEmpty(); + + dsInit(byId('producer_input'), '/xml/producers.xml?q=', + function(item, tr) { + tr.appendChild(tag('td', {style:'text-align: right; padding-right: 5px'}, 'p'+item.getAttribute('id'))); + tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40))); + }, function(item) { + return 'p'+item.getAttribute('id')+':'+item.firstChild.nodeValue; + }, + rprFormAdd + ); + byId('producer_add').onclick = rprFormAdd; +} + +function rprAdd(id, role, name) { + var roles = byId('producer_role').options; + var rl = tag('select', {onchange:rprSerialize}); + for(var i=0; i<roles.length; i++) + rl.appendChild(tag('option', {value: roles[i].value, selected:role==roles[i].value}, getText(roles[i]))); + + byId('producer_tbl').appendChild(tag('tr', {id:'rpr_'+id, rpr_id:id}, + tag('td', {'class':'tc_name'}, 'p'+id+':', tag('a', {href:'/p'+id}, shorten(name, 40))), + tag('td', {'class':'tc_role'}, rl), + tag('td', {'class':'tc_rm'}, tag('a', {href:'#', onclick:rprDel}, mt('_redit_form_prod_remove'))) + )); + rprEmpty(); +} + +function rprDel() { + var tr = this; + while(tr.nodeName.toLowerCase() != 'tr') + tr = tr.parentNode; + tr.parentNode.removeChild(tr); + rprEmpty(); + rprSerialize(); + return false; +} + +function rprEmpty() { + var tbl = byId('producer_tbl'); + if(byName(tbl, 'tr').length < 1) + tbl.appendChild(tag('tr', {id:'rpr_tr_none'}, tag('td', {colspan:2}, mt('_redit_form_prod_none')))); + else if(byId('rpr_tr_none')) + tbl.removeChild(byId('rpr_tr_none')); +} + +function rprFormAdd() { + var txt = byId('producer_input'); + var lnk = byId('producer_add'); + var val = txt.value; + + if(!val.match(/^p[0-9]+/)) { + alert(mt('_redit_form_prod_pformat')); + return false; + } + + txt.disabled = true; + txt.value = mt('_js_loading'); + setText(lnk, mt('_js_loading')); + + ajax('/xml/producers.xml?q='+encodeURIComponent(val), function(hr) { + txt.disabled = false; + txt.value = ''; + setText(lnk, mt('_redit_form_prod_addbut')); + + var items = hr.responseXML.getElementsByTagName('item'); + if(items.length < 1) + return alert(mt('_redit_form_prod_notfound')); + + var id = items[0].getAttribute('id'); + if(byId('rpr_'+id)) + return alert(mt('_redit_form_prod_double')); + + var role = byId('producer_role'); + role = role[role.selectedIndex].value; + + rprAdd(id, role, items[0].firstChild.nodeValue); + rprSerialize(); + }); + return false; +} + +function rprSerialize() { + var r = []; + var l = byName(byId('producer_tbl'), 'tr'); + for(var i=0; i<l.length; i++) + if(l[i].rpr_id) { + var role = byName(byClass(l[i], 'td', 'tc_role')[0], 'select')[0]; + r[r.length] = [ + l[i].rpr_id, + role.options[role.selectedIndex].value, + getText(byName(byClass(l[i], 'td', 'tc_name')[0], 'a')[0]) + ].join(','); + } + byId('producers').value = r.join('|||'); +} + +if(byId('jt_box_rel_prod')) + rprLoad(); + + + + +/* P R O D U C E R R E L A T I O N S (/p+/edit) */ + +function prrLoad() { + // read the current relations + var rels = byId('prodrelations').value.split('|||'); + for(var i=0; i<rels.length && rels[0].length>1; i++) { + var rel = rels[i].split(',', 3); + prrAdd(rel[0], rel[1], rel[2]); + } + prrEmpty(); + + // bind the add-link + byName(byClass(byId('relation_new'), 'td', 'tc_add')[0], 'a')[0].onclick = prrFormAdd; + + // dropdown + dsInit(byName(byClass(byId('relation_new'), 'td', 'tc_prod')[0], 'input')[0], '/xml/producers.xml?q=', function(item, tr) { + tr.appendChild(tag('td', { style: 'text-align: right; padding-right: 5px'}, 'p'+item.getAttribute('id'))); + tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40))); + }, function(item) { + return 'p'+item.getAttribute('id')+':'+item.firstChild.nodeValue; + }, prrFormAdd); +} + +function prrAdd(rel, pid, title) { + var sel = tag('select', {onchange: prrSerialize}); + var ops = byName(byClass(byId('relation_new'), 'td', 'tc_rel')[0], 'select')[0].options; + for(var i=0; i<ops.length; i++) + sel.appendChild(tag('option', {value: ops[i].value, selected: ops[i].value==rel}, getText(ops[i]))); + + byId('relation_tbl').appendChild(tag('tr', {id:'relation_tr_'+pid}, + tag('td', {'class':'tc_prod' }, 'p'+pid+':', tag('a', {href:'/p'+pid}, shorten(title, 40))), + tag('td', {'class':'tc_rel' }, sel), + tag('td', {'class':'tc_add' }, tag('a', {href:'#', onclick:prrDel}, mt('_pedit_rel_del'))) + )); + + prrEmpty(); +} + +function prrEmpty() { + var tbl = byId('relation_tbl'); + if(byName(tbl, 'tr').length < 1) + tbl.appendChild(tag('tr', {id:'relation_tr_none'}, tag('td', {colspan:4}, mt('_pedit_rel_none')))); + else if(byId('relation_tr_none')) + tbl.removeChild(byId('relation_tr_none')); +} + +function prrSerialize() { + var r = []; + var trs = byName(byId('relation_tbl'), 'tr'); + for(var i=0; i<trs.length; i++) { + if(trs[i].id == 'relation_tr_none') + continue; + var rel = byName(byClass(trs[i], 'td', 'tc_rel')[0], 'select')[0]; + r[r.length] = [ + rel.options[rel.selectedIndex].value, + trs[i].id.substr(12), + getText(byName(byClass(trs[i], 'td', 'tc_prod')[0], 'a')[0]) + ].join(','); + } + byId('prodrelations').value = r.join('|||'); +} + +function prrDel() { + var tr = this; + while(tr.nodeName.toLowerCase() != 'tr') + tr = tr.parentNode; + byId('relation_tbl').removeChild(tr); + prrSerialize(); + prrEmpty(); + return false; +} + +function prrFormAdd() { + var relnew = byId('relation_new'); + var txt = byName(byClass(relnew, 'td', 'tc_prod')[0], 'input')[0]; + var sel = byName(byClass(relnew, 'td', 'tc_rel')[0], 'select')[0]; + var lnk = byName(byClass(relnew, 'td', 'tc_add')[0], 'a')[0]; + var input = txt.value; + + if(!input.match(/^p[0-9]+/)) { + alert('_pedit_rel_findformat'); + return false; + } + + txt.disabled = sel.disabled = true; + txt.value = mt('_js_loading'); + setText(lnk, mt('_js_loading')); + + ajax('/xml/producers.xml?q='+encodeURIComponent(input), function(hr) { + txt.disabled = sel.disabled = false; + txt.value = ''; + setText(lnk, mt('_pedit_rel_addbut')); + + var items = hr.responseXML.getElementsByTagName('item'); + if(items.length < 1) + return alert(mt('_pedit_rel_notfound')); + + var id = items[0].getAttribute('id'); + if(byId('relation_tr_'+id)) + return alert(mt('_pedit_rel_double')); + + prrAdd(sel.options[sel.selectedIndex].value, id, items[0].firstChild.nodeValue); + sel.selectedIndex = 0; + prrSerialize(); + }); + return false; +} + +if(byId('prodrelations')) + prrLoad(); + + + + +/* M I S C S T U F F */ + +// search box +{ + var i = byId('sq'); + i.onfocus = function () { + if(this.value == mt('_menu_emptysearch')) { + this.value = ''; + this.style.fontStyle = 'normal' + } + }; + i.onblur = function () { + if(this.value.length < 1) { + this.value = mt('_menu_emptysearch'); + this.style.fontStyle = 'italic' + } + }; +} + +// VN Voting (/v+) +if(byId('votesel')) { + byId('votesel').onchange = function() { + var s = this.options[this.selectedIndex].value; + if(s == 1 && !confirm(mt('_vnpage_uopt_1vote'))) + return; + if(s == 10 && !confirm(mt('_vnpage_uopt_10vote'))) + return; + if(s) + location.href = location.href.replace(/\.[0-9]+/, '')+'/vote?v='+s; + }; +} + +// Advanced search (/v/*, /r) +if(byId('advselect')) { + byId('advselect').onclick = function() { + var box = byId('advoptions'); + var hidden = !hasClass(box, 'hidden'); + setClass(box, 'hidden', hidden); + setText(byName(this, 'i')[0], hidden ? collapsed_icon : expanded_icon); + return false; + }; +} + +// Spoiler filters -> cookie (/v/*) +if(byId('sp_0')) { + byId('sp_0').onclick = function() { setCookie('tagspoil', 0) }; + byId('sp_1').onclick = function() { setCookie('tagspoil', 1) }; + byId('sp_2').onclick = function() { setCookie('tagspoil', 2) }; + var spoil = getCookie('tagspoil'); + byId('sp_'+(spoil == null ? 0 : spoil)).checked = true; +} + +// NSFW VN image toggle (/v+) +if(byId('nsfw_show')) { + var msg = byId('nsfw_show'); + var img = byId('nsfw_hid'); + byName(msg, 'a')[0].onclick = function() { + msg.style.display = 'none'; + img.style.display = 'block'; + return false; + }; + img.onclick = function() { + msg.style.display = 'block'; + img.style.display = 'none'; + }; +} + +// NSFW toggle for screenshots (/v+) +if(byId('nsfwhide')) { + byId('nsfwhide').onclick = function() { + var shown = 0; + var l = byName(byId('screenshots'), 'div'); + for(var i=0; i<l.length; i++) { + if(hasClass(l[i], 'nsfw')) { + var hidden = !hasClass(l[i], 'hidden'); + setClass(l[i], 'hidden', hidden); + setClass(byName(l[i], 'a')[0], 'hidden', hidden); // for the image viewer + if(!hidden) + shown++; + } else + shown++; + } + setText(byId('nsfwshown'), shown); + return false; + }; +} + +// VN Wishlist dropdown box (/v+) +if(byId('wishsel')) { + byId('wishsel').onchange = function() { + if(this.selectedIndex != 0) + location.href = location.href.replace(/\.[0-9]+/, '') + +'/wish?s='+this.options[this.selectedIndex].value; + }; +} + +// Release list dropdown box (/r+) +if(byId('listsel')) { + byId('listsel').onchange = function() { + if(this.selectedIndex != 0) + location.href = location.href.replace(/\.[0-9]+/, '') + +'/list?e='+this.options[this.selectedIndex].value; + }; +} + +// BBCode spoiler tags +{ + var l = byClass('b', 'spoiler'); + for(var i=0; i<l.length; i++) { + l[i].onmouseover = function() { setClass(this, 'spoiler', false); setClass(this, 'spoiler_shown', true) }; + l[i].onmouseout = function() { setClass(this, 'spoiler', true); setClass(this, 'spoiler_shown', false) }; + } +} + +// vndb.org domain check +// (let's just keep this untranslatable, nobody cares anyway ^^) +if(location.hostname != 'vndb.org') { + addBody(tag('div', {id:'debug'}, + tag('h2', 'This is not VNDB!'), + 'The real VNDB is ', + tag('a', {href:'http://vndb.org/'}, 'here'), + '.' + )); +} + +// make some fields readonly when patch flag is set (/r+/edit) +if(byId('jt_box_rel_geninfo')) { + var func = function() { + byId('doujin').disabled = + byId('resolution').disabled = + byId('voiced').disabled = + byId('ani_story').disabled = + byId('ani_ero').disabled = + byId('patch').checked; + }; + func(); + byId('patch').onclick = func; +} + +// Batch edit wishlist dropdown box (/u+/wish) +if(byId('batchedit')) { + byId('batchedit').onchange = function() { + if(this.selectedIndex == 0) + return true; + var frm = this; + while(frm.nodeName.toLowerCase() != 'form') + frm = frm.parentNode; + frm.submit(); + }; +} + +// expand/collapse listings (/*/hist, /u+/posts) +if(byId('expandlist')) { + var lnk = byId('expandlist'); + setexpand = function() { + var exp = getCookie('histexpand') == 1; + setText(lnk, mt(exp ? '_js_collapse' : '_js_expand')); + var tbl = lnk; + while(tbl.nodeName.toLowerCase() != 'table') + tbl = tbl.parentNode; + var l = byClass(tbl, 'tr', 'collapse'); + for(var i=0; i<l.length; i++) + setClass(l[i], 'hidden', !exp); + }; + setexpand(); + lnk.onclick = function () { + setCookie('histexpand', getCookie('histexpand') == 1 ? 0 : 1); + setexpand(); + return false; + }; +} + +// collapse/expand row groups (/u+/tags, /u+/list) (limited to one table on a page) +if(byId('expandall')) { + var table = byId('expandall'); + while(table.nodeName.toLowerCase() != 'table') + table = table.parentNode; + var heads = byClass(table, 'td', 'collapse_but'); + var allhid = false; + + var alltoggle = function() { + allhid = !allhid; + var l = byClass(table, 'tr', 'collapse'); + for(var i=0; i<l.length; i++) + setClass(l[i], 'hidden', allhid); + setText(byName(byId('expandall'), 'i')[0], allhid ? collapsed_icon : expanded_icon); + for(var i=0; i<heads.length; i++) + setText(byName(heads[i], 'i')[0], allhid ? collapsed_icon : expanded_icon); + return false; + } + byId('expandall').onclick = alltoggle; + alltoggle(); + + var singletoggle = function() { + var l = byClass(table, 'tr', 'collapse_'+this.id); + if(l.length < 1) + return; + var hid = !hasClass(l[0], 'hidden'); + for(var i=0; i<l.length; i++) + setClass(l[i], 'hidden', hid); + setText(byName(this, 'i')[0], hid ? collapsed_icon : expanded_icon); + }; + for(var i=0; i<heads.length; i++) + heads[i].onclick = singletoggle; +} + +// auto-complete tag search (/v/*) +if(byId('advselect') && byId('ti')) { + 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; + return tags.join(', '); + }; + var retfunc = function() { false; }; + var parfunc = function(val) { + return (val.split(/, */))[val.split(/, */).length-1]; + }; + dsInit(byId('ti'), '/xml/tags.xml?q=', trfunc, serfunc, retfunc, parfunc); + dsInit(byId('te'), '/xml/tags.xml?q=', trfunc, serfunc, retfunc, parfunc); +} + +// Language selector +if(byId('lang_select')) { + var d = byId('lang_select'); + ddInit(d, 'bottom', function(lnk) { + var lst = tag('ul', null); + for(var i=0; i<L10N_LANG.length; i++) { + var ln = L10N_LANG[i]; + var icon = tag('acronym', {'class':'icons lang '+ln}, ' '); + lst.appendChild(tag('li', {'class':'lang_selector'}, mt_curlang == ln + ? tag('i', icon, mt('_lang_'+ln)) + : tag('a', {href:'?l10n='+ln}, icon, L10N_STR['_lang_'+ln][ln]||mt('_lang_'+ln)) + )); + } + return lst; + }); + d.onclick = function() {return false}; +} + +// spam protection on all forms +setTimeout(function() { + for(i=1; i<document.forms.length; i++) + document.forms[i].action = document.forms[i].action.replace(/\/nospam\?/,''); +}, 500); + + diff --git a/data/style.css b/data/style.css index 53255c74..6bd0c547 100644 --- a/data/style.css +++ b/data/style.css @@ -239,7 +239,7 @@ td.field label { p.center { text-align: center; } -b.future, b.standout { +b.future, b.standout, a.standout { font-weight: normal; color: $standout$; } @@ -272,7 +272,7 @@ b.future, b.standout { font-size: 11px; color: $maintext$; } -#menulist h2 span { +#menulist h2 #lang_select { float: right; padding-top: 1px; } @@ -295,8 +295,8 @@ b.future, b.standout { margin-left: 15px; } #menulist input.submit { - width: 40px; - margin-left: 45px; + width: 70px; + margin-left: 30px; } #menulist #search input.text { width: 133px; @@ -306,6 +306,7 @@ b.future, b.standout { #menulist #search input.submit { display: none; } +#dd_box acronym { margin: 2px 5px 2px 0!important; } @@ -479,13 +480,13 @@ div.mainbox.history td.tc1_2 { } div.mainbox.history td.tc2 { width: 65px; } div.mainbox.history td.tc3 { width: 90px } -tr.editsum td { +tr.msgsum td { color: $grayedout$; padding-top: 0; text-align: right; } -a#history_comments { float: right } -a#history_comments:hover { border-bottom: none } +a#expandlist { float: right } +a#expandlist:hover { border-bottom: none } @@ -694,14 +695,16 @@ a.addnew { margin: 0; } -#vldd { position: absolute; left: -500px; border: 1px solid $border$; background-color: $secbg$; width: 180px; } -#vldd ul { float: left; width: 90px; list-style-type: none; margin: 0; padding: 0 } -#vldd li b { display: block; font-weight: normal; padding-left: 5px; } -#vldd li i { display: block; font-style: normal; padding-left: 10px; } -#vldd li a { display: block; padding-left: 10px; color: $link$; border: 0; } -#vldd li a:hover { background: url($_boxbg$) repeat } -#vldd ul.full { width: 180px; text-align: center; } -#vldd ul.full li a { padding: 0 } +#dd_box { position: absolute; left: -500px; border: 1px solid $border$; background-color: $secbg$; } +#dd_box ul { list-style-type: none; margin: 0; padding: 0 } +#dd_box li b { display: block; font-weight: normal; padding-left: 5px; } +#dd_box li i { display: block; font-style: normal; padding-left: 10px; padding-right: 5px } +#dd_box li a { display: block; padding-left: 10px; color: $link$; border: 0; padding-right: 5px } +#dd_box li a:hover { background: url($_boxbg$) repeat } +#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 } @@ -727,12 +730,12 @@ a.addnew { #jt_box_vn_rel table { margin-bottom: 10px; } #jt_box_vn_rel h2 { margin: 0 0 3px 0px; } #jt_box_vn_rel td { padding: 1px 2px; vertical-align: middle; } -#jt_box_vn_rel td.tc1 { width: 300px; text-align: right } -#jt_box_vn_rel td.tc2 { width: 170px; white-space: nowrap } -#jt_box_vn_rel td.tc3 { width: 200px; } -#jt_box_vn_rel td.tc4 { width: 40px; text-align: right } -#jt_box_vn_rel td.tc1 input { width: 280px; } -#jt_box_vn_rel td.tc2 select { width: 130px; } +#jt_box_vn_rel td.tc_vn { width: 300px; text-align: right } +#jt_box_vn_rel td.tc_rel { width: 170px; white-space: nowrap } +#jt_box_vn_rel td.tc_title { width: 200px; } +#jt_box_vn_rel td.tc_add { width: 40px; text-align: right } +#jt_box_vn_rel td.tc_vn input { width: 280px; } +#jt_box_vn_rel td.tc_rel select { width: 130px; } #ds_box { border: 1px solid $border$; @@ -763,6 +766,7 @@ a.addnew { #scr_table td { height: 108px; border-top: 1px solid #258; padding: 0; padding-right: 5px } #scr_table td.thumb { width: 136px; vertical-align: middle } #scr_table select { width: 400px; } +div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px; position: absolute; left: -500px; top: -500px; } @@ -840,6 +844,16 @@ a.addnew { padding-bottom: 10px!important } +/***** Producer edit *****/ + +#jt_box_pedit_rel table { margin-bottom: 10px; } +#jt_box_pedit_rel h2 { margin: 0 0 3px 0px; } +#jt_box_pedit_rel td { padding: 1px 2px; vertical-align: middle; } +#jt_box_pedit_rel td.tc_prod { width: 290px; padding-left: 10px } +#jt_box_pedit_rel td.tc_add { width: 40px; text-align: left } +#jt_box_pedit_rel td.tc_prod input { width: 280px; } +#jt_box_pedit_rel td.tc_rel select { width: 130px; } + @@ -863,13 +877,12 @@ a.addnew { #media_div { padding-left: 20px; } #media_div span { display: block } -#jt_box_rel_vn h2, #jt_box_rel_prod h2 { clear: left; padding-top: 10px; } -#jt_box_rel_vn div, #jt_box_rel_prod div { padding-left: 20px } -#jt_box_rel_vn input, #jt_box_rel_prod input { margin-right: 10px; width: 300px } -#jt_box_rel_vn span, #jt_box_rel_prod span { clear: left; display: block; padding: 2px; } -#jt_box_rel_vn span.odd, #jt_box_rel_prod span.odd { background: url($_boxbg$) repeat; } -#jt_box_rel_vn i, #jt_box_rel_prod i { font-style: normal; display: block; float: left; width: 310px } -#jt_box_rel_vn b, #jt_box_rel_prod b { font-weight: normal; } +#jt_box_rel_vn h2, #jt_box_rel_prod h2 { clear: left; padding-top: 10px; } +#jt_box_rel_vn div, #jt_box_rel_vn table, +#jt_box_rel_prod div, #jt_box_rel_prod table { margin-left: 20px } +#jt_box_rel_vn input, #jt_box_rel_prod input { margin-right: 10px; width: 295px } +#jt_box_rel_vn .tc_title, #jt_box_rel_prod .tc_name { width: 310px; padding: 2px } +#jt_box_rel_prod .tc_role select { width: 100px; margin-right: 10px; } @@ -905,8 +918,8 @@ a.addnew { /***** User VN list browser ******/ -.relhid_but, #relhidall { cursor: pointer } -.relhid_but i, #relhidall i { font-style: normal } +#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; } @@ -971,15 +984,15 @@ div.browse.uposts td.tc1 { /***** VN tagmod *****/ #jt_box_tagmod .formtable table td { padding: 1px 5px } -#tagtable tfoot td { padding-top: 20px!important; } -#tagtable .tc2_1 { border-right: 1px solid $border$; border-left: 1px solid $border$; width: 150px; text-align: center } -#tagtable .tc3_1 { border-left: 1px solid $border$; width: 150px; text-align: center } -#tagtable .tc1 { min-width: 200px; border-right: 1px solid $border$ } -#tagtable .tc2 { padding-left: 30px!important } -#tagtable .tc3 { border-right: 1px solid $border$; padding-right: 30px!important; text-align: right; padding-left: 10px!important } -#tagtable .tc4 { padding-left: 30px!important; } -#tagtable .tc4 i { font-style: normal; font-size: 8px } -#tagtable .tc5 { text-align: right; padding-right: 30px!important; } +table.tgl tfoot td { padding-top: 20px!important; } +table.tgl .tc_you { border-right: 1px solid $border$; border-left: 1px solid $border$; width: 150px; text-align: center } +table.tgl .tc_others { border-left: 1px solid $border$; width: 150px; text-align: center } +table.tgl .tc_tagname { min-width: 200px; border-right: 1px solid $border$ } +table.tgl .tc_myvote { padding-left: 30px!important } +table.tgl .tc_myspoil { border-right: 1px solid $border$; padding-right: 30px!important; text-align: right; padding-left: 10px!important; cursor: pointer } +table.tgl .tc_allvote { padding-left: 30px!important; } +table.tgl .tc_allvote i { font-style: normal; font-size: 8px } +table.tgl .tc_allspoil { text-align: right; padding-right: 30px!important; } .taglvl { display: block; float: left; width: 8px; height: 12px; border: 1px solid $border$; font-size: 1px; color: $maintext$!important } .taglvl0 { width: 15px; border: none!important; font-size: 10px; text-align: center; } div.taglvl0 { font-size: 8px; width: 20px!important } @@ -991,9 +1004,6 @@ a.taglvl:hover { border-bottom: 1px solid transparent!important } .taglvlsel.taglvl1 { background-color: #cf0; border-color: #cf0 } .taglvlsel.taglvl2 { background-color: #8f0; border-color: #8f0 } .taglvlsel.taglvl3 { background-color: #0f0; border-color: #0f0 } -#jt_box_tagmod #tagtable .tc3 { padding: 0 } -#tagtable .tc3 select { width: 90px; height: 15px; border: 0; margin: 0; font-size: 11px; background-color: $_blendbg$; text-align: right } -#tagtable .odd .tc3 select { background-color: $secbg$ } @@ -1131,7 +1141,7 @@ div#iv_view { height: 11px; opacity: 0.5; } -.icons.rt0, .icons.rt1, .icons.rt2 { width: 11px; } +.icons.rtcomplete, .icons.rtpartial, .icons.rttrial { width: 11px; } acronym.icons, acronym.uicons { cursor: default; } a .icons { cursor: pointer } .icons.oth { background: none; } @@ -1153,9 +1163,9 @@ a .icons { cursor: pointer } .icons.xb3 { background-position: -16px -84px; } .icons.sat { background-position: -16px -98px; } -.icons.rt0 { background-position: -32px 0px; } -.icons.rt1 { background-position: -32px -14px; } -.icons.rt2 { background-position: -32px -28px; } +.icons.rtcomplete { background-position: -32px 0px; } +.icons.rtpartial { background-position: -32px -14px; } +.icons.rttrial { background-position: -32px -28px; } .icons.ext { background-position: -32px -42px; } .icons.msx { background-position: -32px -56px; } .icons.nes { background-position: -32px -70px; } @@ -1170,6 +1180,7 @@ a .icons { cursor: pointer } .icons.fr { background-position: -48px -66px; } .icons.it { background-position: -48px -77px; } .icons.ja { background-position: -48px -88px; } +.icons.hu { background-position: -48px -99px; } .icons.nl { background-position: -61px 0px; } .icons.no { background-position: -61px -11px; } @@ -1182,3 +1193,11 @@ a .icons { cursor: pointer } .icons.ko { background-position: -61px -88px; } .icons.vi { background-position: -61px -99px; } + +/* Relation graph colors */ +svg .border { fill: none; stroke: $border$ } +svg .edge polygon.border { fill: $border$ } +svg .nodebg { fill: $tabbg$; stroke: $tabbg$ } +svg text { fill: $maintext$ } +svg .edge text { font: 8px "Tahoma" } + diff --git a/lib/LangFile.pm b/lib/LangFile.pm new file mode 100644 index 00000000..e81f7f7a --- /dev/null +++ b/lib/LangFile.pm @@ -0,0 +1,145 @@ + + +package LangFile; + +use strict; +use warnings; +use Fcntl qw(LOCK_SH LOCK_EX SEEK_SET); + + +sub new { + my($class, $action, $file) = @_; + open my $F, $action eq 'read' ? '<:utf8' : '>:utf8', $file or die "Opening $file: $!"; + flock($F, $action eq 'read' ? LOCK_SH : LOCK_EX) or die "Locking $file: $!"; + seek($F, 0, SEEK_SET) or die "Seeking $file: $!"; + return bless { + act => $action, + FH => $F, + # status vars for reading + intro => 1, + last => [], + }, $class; +} + + +sub read { + my $self = shift; + my $FH = $self->{FH}; + my @lines; + my $state = ''; + my($lang, $sync); + + while((my $l = shift(@{$self->{last}}) || <$FH>)) { + $l =~ s/[\r\n\t\s]+$//; + + # header + if($self->{intro}) { + push @lines, $l; + next if $l ne '/intro'; + $self->{intro} = 0; + return [ 'space', @lines ]; + } + + # key + if(!$state && $l =~ /^:(.+)$/) { + return [ 'key', $1 ]; + } + + # space + if((!$state || $state eq 'space') && ($l =~ /^#/ || $l eq '')) { + $state = 'space'; + push @lines, $l; + } elsif($state eq 'space') { + push @{$self->{last}}, "$l\n"; + return [ 'space', @lines ]; + } + + # tl + if(!$state && $l =~ /^([a-z_-]{2})([ *]):(?: (.+)|)$/) { + $lang = $1; + $sync = $2 eq '*' ? 0 : 1; + push @lines, $3||''; + $state = 'tl'; + } elsif($state eq 'tl' && $l =~ /^\s{5}(.+)$/) { + push @lines, $1; + } elsif($state eq 'tl' && $l eq '') { + push @lines, $l; + } elsif($state eq 'tl') { + my $trans = join "\n", @lines; + push @{$self->{last}}, "\n" while $trans =~ s/\n$//; + push @{$self->{last}}, $l; + return [ 'tl', $lang, $sync, $trans ]; + } + + die "Don\'t know what to do with \"$l\"" if !$state; + } + if($state eq 'space') { + return [ 'space', @lines ]; + } + if($state eq 'tl') { + my $trans = join "\n", @lines; + push @{$self->{last}}, "\n" while $trans =~ s/\n$//; + return [ 'tl', $lang, $sync, $trans ]; + } + return undef; +} + + +sub write { + my($self, @line) = @_; + my $FH = $self->{FH}; + + my $t = shift @line; + + if($t eq 'space') { + print $FH "$_\n" for @line; + } + + if($t eq 'key') { + print $FH ":$line[0]\n"; + } + + if($t eq 'tl') { + my($lang, $sync, $text) = @line; + $text =~ s/\n([^\n])/\n $1/g; + $text = " $text" if $text ne ''; + printf $FH "%s%s:%s\n", $lang, $sync ? ' ' : '*', $text; + } +} + + +sub close { + my $self = shift; + close $self->{FH}; +} + +1; + +__END__ +=pod + +=head1 NAME + +LangFile - Simple object oriented interface for the parsing and creation of lang.txt + +=head1 USAGE + + use LangFile; + my $read = LangFile->new(read => "data/lang.txt"); + my $write = LangFile->new(write => "lang-copy.txt"); + + while((my $line = $read->read())) { + # $line is an arrayref in one of the following formats: + # [ 'space', @lines ] + # unparsed lines, like the header, newlines and comments + # [ 'key', $key ] + # key line, $key is key name + # [ 'tl', $lang, $sync, $text ] + # translation line(s), $lang = language tag, $sync = 1/0, $text = translation (can include newlines) + # $line is undef on EOF, $read->next() die()s on a parsing error + + # create an identical copy of $read in $write + $write->write(@$line); + } + $write->close; + diff --git a/lib/Multi/Anime.pm b/lib/Multi/Anime.pm index 03134925..df2f6424 100644 --- a/lib/Multi/Anime.pm +++ b/lib/Multi/Anime.pm @@ -70,6 +70,17 @@ sub spawn { lm => 0, # timestamp of last outgoing message, 0=no running msg aid => 0, # anime ID of the last sent ANIME command tag => int(rand()*50000), + # anime types as returned by AniDB (lowercased) + anime_types => { + 'unknown' => undef, # NULL + 'tv series' => 'tv', + 'ova' => 'ova', + 'movie' => 'mov', + 'other' => 'oth', + 'web' => 'web', + 'tv special' => 'spe', + 'music video' => 'mv', + }, }, ); } @@ -224,7 +235,7 @@ sub receivepacket { # input, wheelid $col[2] = undef if !$col[2] || $col[2] =~ /^0,/; $col[3] = $1 if $col[3] =~ /^([0-9]+)/; # remove multi-year stuff $col[3] = undef if !$col[3]; - $col[4] = (grep lc($VNDB::S{anime_types}[$_]) eq lc($col[4]), 0..$#{$VNDB::S{anime_types}})[0]; + $col[4] = $_[HEAP]{anime_types}{ lc($col[4]) }; $col[5] = undef if !$col[5]; $col[6] = undef if !$col[6]; $_[KERNEL]->post(pg => do => 'UPDATE anime diff --git a/lib/Multi/IRC.pm b/lib/Multi/IRC.pm index 5018d2aa..4225e6b8 100644 --- a/lib/Multi/IRC.pm +++ b/lib/Multi/IRC.pm @@ -266,12 +266,12 @@ sub notify { # name, pid, payload return if !$_[HEAP]{$k}; my $q = $_[ARG0] eq 'newrevision' ? q|SELECT - CASE WHEN c.type = 0 THEN 'v' WHEN c.type = 1 THEN 'r' ELSE 'p' END AS type, c.rev, c.comments, c.id AS lastrev, + CASE WHEN c.type, c.rev, c.comments, c.id AS lastrev, COALESCE(vr.vid, rr.rid, pr.pid) AS id, COALESCE(vr.title, rr.title, pr.name) AS title, u.username FROM changes c - LEFT JOIN vn_rev vr ON c.type = 0 AND c.id = vr.id - LEFT JOIN releases_rev rr ON c.type = 1 AND c.id = rr.id - LEFT JOIN producers_rev pr ON c.type = 2 AND c.id = pr.id + LEFT JOIN vn_rev vr ON c.type = 'v' AND c.id = vr.id + LEFT JOIN releases_rev rr ON c.type = 'r' AND c.id = rr.id + LEFT JOIN producers_rev pr ON c.type = 'p' AND c.id = pr.id JOIN users u ON u.id = c.requester WHERE c.id > ? AND c.requester <> 1 ORDER BY c.added| diff --git a/lib/Multi/Maintenance.pm b/lib/Multi/Maintenance.pm index e29c7b52..4f816e56 100644 --- a/lib/Multi/Maintenance.pm +++ b/lib/Multi/Maintenance.pm @@ -17,13 +17,13 @@ sub spawn { package_states => [ $p => [qw| _start shutdown set_daily daily set_monthly monthly log_stats - vncache tagcache vnpopularity - usercache statscache revcache logrotate + vncache tagcache vnpopularity cleangraphs + usercache statscache logrotate |], ], heap => { - daily => [qw|vncache tagcache vnpopularity|], - monthly => [qw|usercache statscache revcache logrotate|], + daily => [qw|vncache tagcache vnpopularity cleangraphs|], + monthly => [qw|usercache statscache logrotate|], @_, }, ); @@ -50,7 +50,7 @@ sub set_daily { # (GMT because we're calculating on the UNIX timestamp, I can easily add an # offset if necessary, but it doesn't really matter what time this cron # runs, as long as it's run on a daily basis) - $_[KERNEL]->alarm(daily => int(time/86400+1)*86400); + $_[KERNEL]->alarm(daily => int((time+3)/86400+1)*86400); } @@ -70,7 +70,7 @@ sub set_monthly { # We do this by simply incrementing the timestamp with one day and checking gmtime() # for a month change. This might not be very reliable, but should be enough for # our purposes. - my $nextday = int(time/86400+1)*86400; + my $nextday = int((time+3)/86400+1)*86400; my $thismonth = (gmtime)[5]*100+(gmtime)[4]; # year*100 + month, for easy comparing $nextday += 86400 while (gmtime $nextday)[5]*100+(gmtime $nextday)[4] <= $thismonth; $_[KERNEL]->alarm(monthly => $nextday); @@ -99,24 +99,33 @@ sub log_stats { # num, res, action, time sub vncache { - # this takes about 30s to complete. We really need to search for an alternative + # this takes about 40s to complete. We really need to search for an alternative # method of keeping the c_* columns in the vn table up-to-date. $_[KERNEL]->post(pg => do => 'SELECT update_vncache(0)', undef, 'log_stats', 'vncache'); } sub tagcache { - # this still takes "only" about 3 seconds max. Let's hope that doesn't increase too much. + # takes about 18 seconds max. ouch, but still kind-of acceptable $_[KERNEL]->post(pg => do => 'SELECT tag_vn_calc()', undef, 'log_stats', 'tagcache'); } sub vnpopularity { - # still takes at most 2 seconds. Againt, let's hope that doesn't increase... + # still takes at most 2 seconds. let's hope that doesn't increase... $_[KERNEL]->post(pg => do => 'SELECT update_vnpopularity()', undef, 'log_stats', 'vnpopularity'); } +sub cleangraphs { + # should be pretty fast + $_[KERNEL]->post(pg => do => q| + DELETE FROM relgraphs vg + WHERE NOT EXISTS(SELECT 1 FROM vn WHERE rgraph = vg.id) + AND NOT EXISTS(SELECT 1 FROM producers WHERE rgraph = vg.id) + |, undef, 'log_stats', 'cleangraphs'); +} + # # M O N T H L Y J O B S @@ -164,14 +173,6 @@ sub statscache { } -sub revcache { - # This -really- shouldn't be necessary... - # Currently takes about 25 seconds to complete - $_[KERNEL]->post(pg => do => q|SELECT update_rev('vn', ''), update_rev('releases', ''), update_rev('producers', '')|, - undef, 'log_stats', 'revcache'); -} - - sub logrotate { my $dir = sprintf '%s/old', $VNDB::M{log_dir}; mkdir $dir if !-d $dir; diff --git a/lib/Multi/RG.pm b/lib/Multi/RG.pm index e427caf2..b3f9bb46 100644 --- a/lib/Multi/RG.pm +++ b/lib/Multi/RG.pm @@ -9,6 +9,8 @@ use strict; use warnings; use POE 'Wheel::Run', 'Filter::Stream'; use Encode 'encode_utf8'; +use XML::Parser; +use XML::Writer; use Time::HiRes 'time'; @@ -17,15 +19,13 @@ sub spawn { POE::Session->create( package_states => [ $p => [qw| - _start shutdown check_rg creategraph getrel builddot buildgraph savegraph + _start shutdown check_rg creategraph getrel builddot savegraph finish proc_stdin proc_stdout proc_stderr proc_closed proc_child |], ], heap => { font => 'Arial', fsize => [ 9, 7, 10 ], # nodes, edges, node_title - imgdir => '/www/vndb/static/rg', - moy => [qw| Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec |], dot => '/usr/bin/dot', check_delay => 3600, @_, @@ -51,10 +51,12 @@ sub shutdown { sub check_rg { - return if $_[HEAP]{vid}; - $_[KERNEL]->call(pg => query => - 'SELECT v.id FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE rgraph IS NULL AND hidden = FALSE LIMIT 1', - undef, 'creategraph'); + return if $_[HEAP]{id}; + $_[KERNEL]->call(pg => query => q| + SELECT 'v' AS type, v.id FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE rgraph IS NULL AND hidden = FALSE + UNION + SELECT 'p', p.id FROM producers p JOIN producers_relations pr ON pr.pid1 = p.latest WHERE rgraph IS NULL AND hidden = FALSE + LIMIT 1|, undef, 'creategraph'); } @@ -62,148 +64,138 @@ sub creategraph { # num, res return $_[KERNEL]->delay('check_rg', $_[HEAP]{check_delay}) if $_[ARG0] == 0; $_[HEAP]{start} = time; - $_[HEAP]{vid} = $_[ARG1][0]{id}; - $_[HEAP]{rels} = {}; # relations (key=vid1-vid2, value=relation) - $_[HEAP]{nodes} = {}; # nodes (key=vid, value= 0:found, 1:processed) - - $_[KERNEL]->post(pg => query => - 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?', - [ $_[HEAP]{vid} ], 'getrel', $_[HEAP]{vid}); + $_[HEAP]{id} = $_[ARG1][0]{id}; + $_[HEAP]{type} = $_[ARG1][0]{type}; + $_[HEAP]{rels} = {}; # relations (key=id1-id2, value=relation) + $_[HEAP]{nodes} = {}; # nodes (key=id, value= 0:found, 1:processed) + + $_[KERNEL]->post(pg => query => $_[HEAP]{type} eq 'v' + ? 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?' + : 'SELECT pid2 AS id, relation FROM producers p JOIN producers_relations pr ON pr.pid1 = p.latest WHERE p.id = ?', + [ $_[HEAP]{id} ], 'getrel', $_[HEAP]{id}); } -sub getrel { # num, res, vid +sub getrel { # num, res, id my $id = $_[ARG2]; $_[HEAP]{nodes}{$id} = 1; for($_[ARG0] > 0 ? @{$_[ARG1]} : ()) { - $_[HEAP]{rels}{$id.'-'.$_->{id}} = reverserel($_->{relation}) if $id < $_->{id}; - $_[HEAP]{rels}{$_->{id}.'-'.$id} = $_->{relation} if $id > $_->{id}; + $_[HEAP]{rels}{$id.'-'.$_->{id}} = $VNDB::S{ $_[HEAP]{type} eq 'v' ? 'vn_relations' : 'prod_relations' }{$_->{relation}}[1] if $id < $_->{id}; + $_[HEAP]{rels}{$_->{id}.'-'.$id} = $_->{relation} if $id > $_->{id}; if(!exists $_[HEAP]{nodes}{$_->{id}}) { $_[HEAP]{nodes}{$_->{id}} = 0; - $_[KERNEL]->post(pg => query => - 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?', + $_[KERNEL]->post(pg => query => $_[HEAP]{type} eq 'v' + ? 'SELECT vid2 AS id, relation FROM vn v JOIN vn_relations vr ON vr.vid1 = v.latest WHERE v.id = ?' + : 'SELECT pid2 AS id, relation FROM producers p JOIN producers_relations pr ON pr.pid1 = p.latest WHERE p.id = ?', [ $_->{id} ], 'getrel', $_->{id}); } } - # do we have all relations now? get VN info + # do we have all relations now? get node info if(!grep !$_, values %{$_[HEAP]{nodes}}) { - $_[KERNEL]->post(pg => query => - 'SELECT v.id, vr.title, v.c_released AS date, v.c_languages AS lang - FROM vn v JOIN vn_rev vr ON vr.id = v.latest - WHERE v.id IN('.join(', ', map '?', keys %{$_[HEAP]{nodes}}).')', + my $ids = join(', ', map '?', keys %{$_[HEAP]{nodes}}); + $_[KERNEL]->post(pg => query => $_[HEAP]{type} eq 'v' + ? "SELECT v.id, vr.title, v.c_released AS date, v.c_languages AS lang FROM vn v JOIN vn_rev vr ON vr.id = v.latest WHERE v.id IN($ids) ORDER BY v.c_released" + : "SELECT p.id, pr.name, pr.lang, pr.type FROM producers p JOIN producers_rev pr ON pr.id = p.latest WHERE p.id IN($ids) ORDER BY pr.name", [ keys %{$_[HEAP]{nodes}} ], 'builddot'); } } sub builddot { # num, res - my $vns = $_[ARG1]; + my $nodes = $_[ARG1]; my $gv = qq|graph rgraph {\n|. - qq|\tratio = "compress"\n|. - qq|\tgraph [ bgcolor="#ffffff00" ]\n|. qq|\tnode [ fontname = "$_[HEAP]{font}", shape = "plaintext",|. - qq| fontsize = $_[HEAP]{fsize}[0], style = "setlinewidth(0.5)", fontcolor = "#cccccc", color = "#225588" ]\n|. + qq| fontsize = $_[HEAP]{fsize}[0], fontcolor = "#333333", color = "#111111" ]\n|. qq|\tedge [ labeldistance = 2.5, labelangle = -20, labeljust = 1, minlen = 2, dir = "both",|. - qq| fontname = $_[HEAP]{font}, fontsize = $_[HEAP]{fsize}[1], arrowsize = 0.7, color = "#225588", fontcolor = "#cccccc" ]\n|; - - # insert all nodes, ordered by release date - for (sort { $a->{date} <=> $b->{date} } @$vns) { - my $date = sprintf '%08d', $_->{date}; - $date =~ s#^([0-9]{4})([0-9]{2}).+#$1==0?'N/A':$1==9999?'TBA':(($2&&$2<13?($_[HEAP]{moy}[$2-1].' '):'').$1)#e; - - my $title = $_->{title}; - $title = substr($title, 0, 27).'...' if length($title) > 30; - $title =~ s/&/&/g; - $title =~ s/>/>/g; - $title =~ s/</</g; - - my $tooltip = $_->{title}; - $tooltip =~ s/\\/\\\\/g; - $tooltip =~ s/"/\\"/g; - - $gv .= sprintf - qq|\tv%d [ URL = "/v%d", tooltip = "%s" label=<|. - q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#00000033">|. - q|<TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="2"><FONT POINT-SIZE="%d"> %s </FONT></TD></TR>|. - q|<TR><TD> %s </TD><TD> %s </TD></TR>|. - qq|</TABLE>> ]\n|, - $_->{id}, $_->{id}, encode_utf8($tooltip), $_[HEAP]{fsize}[2], encode_utf8($title), $date, $_->{lang}||'N/A'; - } + qq| fontname = $_[HEAP]{font}, fontsize = $_[HEAP]{fsize}[1], arrowsize = 0.7, color = "#111111", fontcolor = "#333333" ]\n|; - # @rels = ([ vid1, vid2, relation, date1, date2 ], ..), for easier processing - my @rels = map { - /^([0-9]+)-([0-9]+)$/; - my $vn1 = (grep $1 == $_->{id}, @$vns)[0]; - my $vn2 = (grep $2 == $_->{id}, @$vns)[0]; - [ $1, $2, $_[HEAP]{rels}{$_}, $vn1->{date}, $vn2->{date} ] - } keys %{$_[HEAP]{rels}}; + # insert all nodes + $gv .= $_[HEAP]{type} eq 'v' ? _vnnode($_, $_[HEAP]) : _prodnode($_, $_[HEAP]) for @$nodes; - # insert all edges, ordered by release date again - for (sort { ($a->[3]>$a->[4]?$a->[4]:$a->[3]) <=> ($b->[3]>$b->[4]?$b->[4]:$b->[3]) } @rels) { - # [older game] -> [newer game] - if($_->[4] > $_->[3]) { - ($_->[0], $_->[1]) = ($_->[1], $_->[0]); - $_->[2] = reverserel($_->[2]); - } - my $label = - $VNDB::S{vn_relations}[$_->[2]][1] - ? qq|headlabel = "$VNDB::S{vn_relations}[$_->[2]][0]", taillabel = "$VNDB::S{vn_relations}[$_->[2]-1][0]"| : - $VNDB::S{vn_relations}[$_->[2]+1][1] - ? qq|headlabel = "$VNDB::S{vn_relations}[$_->[2]][0]", taillabel = "$VNDB::S{vn_relations}[$_->[2]+1][0]"| - : qq|label = " $VNDB::S{vn_relations}[$_->[2]][0]"|; - $gv .= qq|\tv$$_[1] -- v$$_[0] [ $label ]\n|; - } + # ...and relations + $gv .= $_[HEAP]{type} eq 'v' ? _vnrels($_[HEAP]{rels}, $nodes) : _prodrels($_[HEAP]{rels}, $nodes); $gv .= "}\n"; - # get ID - $_[KERNEL]->post(pg => query => 'INSERT INTO relgraph (cmap) VALUES (\'\') RETURNING id', undef, 'buildgraph', \$gv); -} - - -sub buildgraph { # num, res, \$gv - $_[HEAP]{gid} = $_[ARG1][0]{id}; - $_[HEAP]{graph} = sprintf('%s/%02d/%d.png', $_[HEAP]{imgdir}, $_[ARG1][0]{id} % 100, $_[ARG1][0]{id}); - $_[HEAP]{cmap} = ''; - - # roughly equivalent to: - # cat layout.txt | dot -Tpng -o graph.png -Tcmapx + # Pass our dot file to graphviz + $_[HEAP]{svg} = ''; $_[HEAP]{proc} = POE::Wheel::Run->new( Program => $_[HEAP]{dot}, - ProgramArgs => [ '-Tpng', '-o', $_[HEAP]{graph}, '-Tcmapx' ], + ProgramArgs => [ '-Tsvg' ], StdioFilter => POE::Filter::Stream->new(), StdinEvent => 'proc_stdin', StdoutEvent => 'proc_stdout', StderrEvent => 'proc_stderr', CloseEvent => 'proc_closed', ); - $_[HEAP]{proc}->put(${$_[ARG2]}); + $_[HEAP]{proc}->put($gv); } sub savegraph { - my $vids = join ',', sort map int, keys %{$_[HEAP]{nodes}}; + # Before saving the SVG output, we'll modify it a little: + # - Remove comments + # - Add svg: prefix to all tags + # - Remove xmlns declarations (this is set in the html) + # - Remove <title> elements (unused) + # - Remove id attributes (unused) + # - Remove first <polygon> element (emulates the background color) + # - Replace stroke and fill attributes with classes (so that coloring is done in CSS) + my $svg = ''; + my $w = XML::Writer->new(OUTPUT => \$svg); + my $p = XML::Parser->new; + $p->setHandlers( + Start => sub { + my($expat, $el, %attr) = @_; + return if $el eq 'title' || $expat->in_element('title'); + return if $el eq 'polygon' && $expat->depth == 2; + + $attr{class} = 'border' if $attr{stroke} && $attr{stroke} eq '#111111'; + $attr{class} = 'nodebg' if $attr{fill} && $attr{fill} eq '#222222'; + + delete @attr{qw|stroke fill id xmlns xmlns:xlink|}; + $el eq 'path' || $el eq 'polygon' + ? $w->emptyTag("svg:$el", %attr) + : $w->startTag("svg:$el", %attr); + }, + End => sub { + my($expat, $el) = @_; + return if $el eq 'title' || $expat->in_element('title'); + return if $el eq 'polygon' && $expat->depth == 2; + $w->endTag("svg:$el") if $el ne 'path' && $el ne 'polygon'; + }, + Char => sub { + my($expat, $str) = @_; + return if $expat->in_element('title'); + $w->characters($str) if $str !~ /^[\s\t\r\n]*$/s; + } + ); + $p->parsestring($_[HEAP]{svg}); + $w->end(); + + # save the processed SVG in the database and fetch graph ID + $_[KERNEL]->post(pg => query => 'INSERT INTO relgraphs (svg) VALUES (?) RETURNING id', [ $svg ], 'finish'); +} - # chmod graph - chmod 0666, $_[HEAP]{graph}; - # save the image map in the database - $_[KERNEL]->post(pg => do => 'UPDATE relgraph SET cmap = ? WHERE id = ?', - [ "<!-- V:$vids -->\n$_[HEAP]{cmap}", $_[HEAP]{gid} ]); +sub finish { # num, res + my $id = $_[ARG1][0]{id}; + my $ids = join ',', sort map int, keys %{$_[HEAP]{nodes}}; + my $table = $_[HEAP]{type} eq 'v' ? 'vn' : 'producers'; - # update the VN table - $_[KERNEL]->post(pg => do => "UPDATE vn SET rgraph = ? WHERE id IN($vids)", [ $_[HEAP]{gid} ]); + # update the table + $_[KERNEL]->post(pg => do => "UPDATE $table SET rgraph = ? WHERE id IN($ids)", [ $id ]); # log - $_[KERNEL]->call(core => log => 'Generated relation graph in %.2fs, V: %s', time-$_[HEAP]{start}, $vids); + $_[KERNEL]->call(core => log => 'Generated relation graph #%d in %.2fs, %s: %s', $id, time-$_[HEAP]{start}, uc $_[HEAP]{type}, $ids); # clean up - delete @{$_[HEAP]}{qw| start vid nodes rels gid graph cmap proc |}; + delete @{$_[HEAP]}{qw| start id type nodes rels svg proc |}; # check for more things to do $_[KERNEL]->yield('check_rg'); @@ -216,7 +208,7 @@ sub proc_stdin { $_[HEAP]{proc}->shutdown_stdin; } sub proc_stdout { - $_[HEAP]{cmap} .= $_[ARG0]; + $_[HEAP]{svg} .= $_[ARG0]; } sub proc_stderr { $_[KERNEL]->call(core => log => 'GraphViz STDERR: %s', $_[ARG0]); @@ -230,9 +222,107 @@ sub proc_child { -# non-POE helper function -sub reverserel { # relation - return $VNDB::S{vn_relations}[$_[0]][1] ? $_[0]-1 : $VNDB::S{vn_relations}[$_[0]+1][1] ? $_[0]+1 : $_[0]; +# non-POE helper functions + +sub _vnnode { + my($n, $heap) = @_; + + my $date = sprintf '%08d', $n->{date}; + $date =~ s{^([0-9]{4})([0-9]{2})([0-9]{2})$}{ + $1 == 0 ? 'unknown' + : $1 == 9999 ? 'TBA' + : $2 == 99 ? $1 + : $3 == 99 ? "$1-$2" : "$1-$2-$3" + }e; + + my $title = $n->{title}; + $title = substr($title, 0, 27).'...' if length($title) > 30; + $title =~ s/&/&/g; + $title =~ s/>/>/g; + $title =~ s/</</g; + + my $tooltip = $n->{title}; + $tooltip =~ s/\\/\\\\/g; + $tooltip =~ s/"/\\"/g; + + return sprintf + qq|\tv%d [ URL = "/v%d", tooltip = "%s", label=<|. + q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#222222">|. + q|<TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="2"><FONT POINT-SIZE="%d"> %s </FONT></TD></TR>|. + q|<TR><TD> %s </TD><TD> %s </TD></TR>|. + qq|</TABLE>> ]\n|, + $_->{id}, $_->{id}, encode_utf8($tooltip), $heap->{fsize}[2], encode_utf8($title), $date, $n->{lang}||'N/A'; +} + + +sub _vnrels { + my($rels, $vns) = @_; + my $r = ''; + + # @rels = ([ vid1, vid2, relation, date1, date2 ], ..), for easier processing + my @rels = map { + /^([0-9]+)-([0-9]+)$/; + my $vn1 = (grep $1 == $_->{id}, @$vns)[0]; + my $vn2 = (grep $2 == $_->{id}, @$vns)[0]; + [ $1, $2, $rels->{$_}, $vn1->{date}, $vn2->{date} ] + } keys %$rels; + + # insert all edges, ordered by release date again + for (sort { ($a->[3]>$a->[4]?$a->[4]:$a->[3]) <=> ($b->[3]>$b->[4]?$b->[4]:$b->[3]) } @rels) { + # [older game] -> [newer game] + if($_->[4] > $_->[3]) { + ($_->[0], $_->[1]) = ($_->[1], $_->[0]); + $_->[2] = $VNDB::S{vn_relations}{$_->[2]}[1]; + } + my $rev = $VNDB::S{vn_relations}{$_->[2]}[1]; + my $label = $rev ne $_->[2] + ? qq|headlabel = "\$____vnrel_$_->[2]____\$", taillabel = "\$____vnrel_${rev}____\$"| + : qq|label = "\$____vnrel_$_->[2]____\$"|; + $r .= qq|\tv$$_[1] -- v$$_[0] [ $label ]\n|; + } + return $r; +} + + +sub _prodnode { + my($n, $heap) = @_; + + my $name = $n->{name}; + $name = substr($name, 0, 27).'...' if length($name) > 30; + $name =~ s/&/&/g; + $name =~ s/>/>/g; + $name =~ s/</</g; + + my $tooltip = $n->{name}; + $tooltip =~ s/\\/\\\\/g; + $tooltip =~ s/"/\\"/g; + + return sprintf + qq|\tp%d [ URL = "/p%d", tooltip = "%s", label=<|. + q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#222222">|. + q|<TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="2"><FONT POINT-SIZE="%d"> %s </FONT></TD></TR>|. + q|<TR><TD ALIGN="CENTER"> $_lang_%s_$ </TD><TD ALIGN="CENTER"> $_ptype_%s_$ </TD></TR>|. + qq|</TABLE>> ]\n|, + $_->{id}, $_->{id}, encode_utf8($tooltip), $heap->{fsize}[2], encode_utf8($name), $n->{lang}, $n->{type}; +} + + +sub _prodrels { + my($rels, $prods) = @_; + my $r = ''; + + for (keys %$rels) { + /^([0-9]+)-([0-9]+)$/; + my $p1 = (grep $1 == $_->{id}, @$prods)[0]; + my $p2 = (grep $2 == $_->{id}, @$prods)[0]; + + my $rev = $VNDB::S{prod_relations}{$rels->{$_}}[1]; + my $label = $rev ne $rels->{$_} + ? qq|headlabel = "\$____prodrel_${rev}____\$", taillabel = "\$____prodrel_$rels->{$_}____\$"| + : qq|label = "\$____prodrel_$rels->{$_}____\$"|; + $r .= qq|\tp$p1->{id} -- p$p2->{id} [ $label ]\n|; + } + return $r; } diff --git a/lib/VNDB/DB/Discussions.pm b/lib/VNDB/DB/Discussions.pm index 26beaaee..60487098 100644 --- a/lib/VNDB/DB/Discussions.pm +++ b/lib/VNDB/DB/Discussions.pm @@ -5,7 +5,7 @@ use strict; use warnings; use Exporter 'import'; -our @EXPORT = qw|dbThreadGet dbThreadEdit dbThreadAdd dbPostGet dbPostEdit dbPostAdd dbThreadCount|; +our @EXPORT = qw|dbThreadGet dbThreadEdit dbThreadAdd dbPostGet dbPostEdit dbPostAdd dbThreadCount dbPostRead|; # Options: id, type, iid, results, page, what, notusers @@ -243,5 +243,15 @@ sub dbPostAdd { } +sub dbPostRead { # thread id, user id, last post number + my($s, $tid, $uid, $num) = @_; + $s->dbExec(q| + UPDATE threads_boards + SET lastread = ? + WHERE tid = ? AND type = 'u' AND iid = ?|, + $num, $tid, $uid); +} + + 1; diff --git a/lib/VNDB/DB/Misc.pm b/lib/VNDB/DB/Misc.pm index eeb860b0..b3522d72 100644 --- a/lib/VNDB/DB/Misc.pm +++ b/lib/VNDB/DB/Misc.pm @@ -6,7 +6,7 @@ use warnings; use Exporter 'import'; our @EXPORT = qw| - dbStats dbRevisionInsert dbItemInsert dbRevisionGet dbItemMod dbLanguages dbRandomQuote + dbStats dbRevisionInsert dbItemInsert dbRevisionGet dbItemMod dbRandomQuote |; @@ -24,12 +24,12 @@ sub dbStats { # This function leaves the DB in an inconsistent state, the actual revision # will have to be inserted directly after calling this function, otherwise # the commit will fail. -# Arguments: type [0..2], item ID, edit summary +# Arguments: type [vrp], item ID, edit summary # Returns: local revision, global revision sub dbRevisionInsert { my($self, $type, $iid, $editsum, $uid) = @_; - my $table = [qw|vn releases producers|]->[$type]; + my $table = {qw|v vn r releases p producers|}->{$type}; my $c = $self->dbRow(q| INSERT INTO changes (type, requester, ip, comments, rev) @@ -43,7 +43,7 @@ sub dbRevisionInsert { )) RETURNING id, rev|, $type, $uid||$self->authInfo->{id}, $self->reqIP, $editsum, - $table, [qw|v r p|]->[$type], $iid + $table, $type, $iid ); $self->dbExec(q|UPDATE !s SET latest = ? WHERE id = ?|, $table, $c->{id}, $iid); @@ -54,7 +54,7 @@ sub dbRevisionInsert { # Comparable to RevisionInsert, but creates a new item with a corresponding # change. Same things about inconsistent state, etc. -# Argumments: type [0..2], edit summary, [uid] +# Argumments: type [vrp], edit summary, [uid] # Returns: item id, global revision sub dbItemInsert { my($self, $type, $editsum, $uid) = @_; @@ -70,7 +70,7 @@ sub dbItemInsert { INSERT INTO !s (latest) VALUES (?) RETURNING id|, - [qw|vn releases producers|]->[$type], $cid + {qw|v vn r releases p producers|}->{$type}, $cid )->{id}; return ($iid, $cid); @@ -94,7 +94,7 @@ sub dbRevisionGet { '((c.type = ? AND vr.vid = ?) OR (c.type = ? AND rv.vid = ?))' => [0, $o{iid}, 1, $o{iid}], ) : ( $o{type} ? ( - 'c.type = ?' => { v=>0, r=>1, p=>2 }->{$o{type}} ) : (), + 'c.type = ?' => $o{type} ) : (), $o{iid} ? ( '!sr.!sid = ?' => [ $o{type}, $o{type}, $o{iid} ] ) : (), ), @@ -113,14 +113,14 @@ sub dbRevisionGet { my @join = ( $o{iid} || $o{what} =~ /item/ || $o{hidden} || $o{releases} ? ( - 'LEFT JOIN vn_rev vr ON c.type = 0 AND c.id = vr.id', - 'LEFT JOIN releases_rev rr ON c.type = 1 AND c.id = rr.id', - 'LEFT JOIN producers_rev pr ON c.type = 2 AND c.id = pr.id', + q{LEFT JOIN vn_rev vr ON c.type = 'v' AND c.id = vr.id}, + q{LEFT JOIN releases_rev rr ON c.type = 'r' AND c.id = rr.id}, + q{LEFT JOIN producers_rev pr ON c.type = 'p' AND c.id = pr.id}, ) : (), $o{hidden} || $o{releases} ? ( - 'LEFT JOIN vn v ON c.type = 0 AND vr.vid = v.id', - 'LEFT JOIN releases r ON c.type = 1 AND rr.rid = r.id', - 'LEFT JOIN producers p ON c.type = 2 AND pr.pid = p.id', + q{LEFT JOIN vn v ON c.type = 'v' AND vr.vid = v.id}, + q{LEFT JOIN releases r ON c.type = 'r' AND rr.rid = r.id}, + q{LEFT JOIN producers p ON c.type = 'p' AND pr.pid = p.id}, ) : (), $o{what} =~ /user/ ? 'JOIN users u ON c.requester = u.id' : (), $o{releases} ? 'LEFT JOIN releases_vn rv ON c.id = rv.rid' : (), @@ -160,20 +160,6 @@ sub dbItemMod { } -# Returns a list of languages actually in use -sub dbLanguages { - my $self = shift; - return [ - map $_->{lang}, @{$self->dbAll(q| - SELECT DISTINCT rl.lang - FROM releases r - JOIN releases_lang rl ON rl.rid = r.latest - WHERE r.hidden = FALSE| - )} - ]; -} - - # Returns a random quote (hashref with keys = vid, quote) sub dbRandomQuote { return $_[0]->dbRow(q| diff --git a/lib/VNDB/DB/Producers.pm b/lib/VNDB/DB/Producers.pm index 65d1fbca..7c7da3ba 100644 --- a/lib/VNDB/DB/Producers.pm +++ b/lib/VNDB/DB/Producers.pm @@ -9,7 +9,7 @@ our @EXPORT = qw|dbProducerGet dbProducerEdit dbProducerAdd|; # options: results, page, id, search, char, rev -# what: extended, changes, vn +# what: extended changes vn relations relgraph sub dbProducerGet { my $self = shift; my %o = ( @@ -40,10 +40,12 @@ sub dbProducerGet { push @join, $o{rev} ? 'JOIN producers p ON p.id = pr.pid' : 'JOIN producers p ON pr.id = p.latest'; push @join, 'JOIN changes c ON c.id = pr.id' if $o{what} =~ /changes/ || $o{rev}; push @join, 'JOIN users u ON u.id = c.requester' if $o{what} =~ /changes/; + push @join, 'JOIN relgraphs pg ON pg.id = p.rgraph' if $o{what} =~ /relgraph/; - my $select = 'p.id, pr.type, pr.name, pr.original, pr.lang'; + my $select = 'p.id, pr.type, pr.name, pr.original, pr.lang, pr.id AS cid, p.rgraph'; $select .= ', pr.desc, pr.alias, pr.website, p.hidden, p.locked' if $o{what} =~ /extended/; $select .= q|, extract('epoch' from c.added) as added, c.requester, c.comments, p.latest, pr.id AS cid, u.username, c.rev| if $o{what} =~ /changes/; + $select .= ', pg.svg' if $o{what} =~ /relgraph/; my($r, $np) = $self->dbPage(\%o, q| SELECT !s @@ -61,7 +63,8 @@ sub dbProducerGet { } 0..$#$r; push @{$r->[$r{$_->{pid}}]{vn}}, $_ for (@{$self->dbAll(q| - SELECT MAX(vp.pid) AS pid, v.id, MAX(vr.title) AS title, MAX(vr.original) AS original, MIN(rr.released) AS date + SELECT MAX(vp.pid) AS pid, v.id, MAX(vr.title) AS title, MAX(vr.original) AS original, MIN(rr.released) AS date, + MAX(CASE WHEN vp.developer = true THEN 1 ELSE 0 END) AS developer, MAX(CASE WHEN vp.publisher = true THEN 1 ELSE 0 END) AS publisher FROM releases_producers vp JOIN releases_rev rr ON rr.id = vp.rid JOIN releases r ON r.latest = rr.id @@ -77,6 +80,22 @@ sub dbProducerGet { )}); } + if(@$r && $o{what} =~ /relations/) { + my %r = map { + $r->[$_]{relations} = []; + ($r->[$_]{cid}, $_) + } 0..$#$r; + + push @{$r->[$r{$_->{pid1}}]{relations}}, $_ for(@{$self->dbAll(q| + SELECT rel.pid1, rel.pid2 AS id, rel.relation, pr.name, pr.original + FROM producers_relations rel + JOIN producers p ON rel.pid2 = p.id + JOIN producers_rev pr ON p.latest = pr.id + WHERE rel.pid1 IN(!l)|, + [ keys %r ] + )}); + } + return wantarray ? ($r, $np) : $r; } @@ -85,7 +104,7 @@ sub dbProducerGet { # returns: ( local revision, global revision ) sub dbProducerEdit { my($self, $pid, %o) = @_; - my($rev, $cid) = $self->dbRevisionInsert(2, $pid, $o{editsum}, $o{uid}); + my($rev, $cid) = $self->dbRevisionInsert('p', $pid, $o{editsum}, $o{uid}); insert_rev($self, $cid, $pid, \%o); return ($rev, $cid); } @@ -95,14 +114,14 @@ sub dbProducerEdit { # returns: ( item id, global revision ) sub dbProducerAdd { my($self, %o) = @_; - my($pid, $cid) = $self->dbItemInsert(2, $o{editsum}, $o{uid}); + my($pid, $cid) = $self->dbItemInsert('p', $o{editsum}, $o{uid}); insert_rev($self, $cid, $pid, \%o); return ($pid, $cid); } # helper function, inserts a producer revision -# Arguments: global revision, item id, { columns in producers_rev } +# Arguments: global revision, item id, { columns in producers_rev }, relations sub insert_rev { my($self, $cid, $pid, $o) = @_; $self->dbExec(q| @@ -110,6 +129,12 @@ sub insert_rev { VALUES (!l)|, [ $cid, $pid, @$o{qw| name original website type lang desc alias|} ] ); + + $self->dbExec(q| + INSERT INTO producers_relations (pid1, pid2, relation) + VALUES (?, ?, ?)|, + $cid, $_->[1], $_->[0] + ) for (@{$o->{relations}}); } diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm index 672596da..9260b787 100644 --- a/lib/VNDB/DB/Releases.pm +++ b/lib/VNDB/DB/Releases.pm @@ -117,7 +117,7 @@ sub dbReleaseGet { if($o{what} =~ /producers/) { push(@{$r->[$r{$_->{rid}}]{producers}}, $_) for (@{$self->dbAll(q| - SELECT rp.rid, p.id, pr.name, pr.original, pr.type + SELECT rp.rid, rp.developer, rp.publisher, p.id, pr.name, pr.original, pr.type FROM releases_producers rp JOIN producers p ON rp.pid = p.id JOIN producers_rev pr ON pr.id = p.latest @@ -137,7 +137,7 @@ sub dbReleaseGet { } if($o{what} =~ /media/) { - ($_->{medium}=~s/\s+//||1)&&push(@{$r->[$r{$_->{rid}}]{media}}, $_) for (@{$self->dbAll(q| + push(@{$r->[$r{$_->{rid}}]{media}}, $_) for (@{$self->dbAll(q| SELECT rid, medium, qty FROM releases_media WHERE rid IN(!l)|, @@ -154,7 +154,7 @@ sub dbReleaseGet { # returns: ( local revision, global revision ) sub dbReleaseEdit { my($self, $rid, %o) = @_; - my($rev, $cid) = $self->dbRevisionInsert(1, $rid, $o{editsum}, $o{uid}); + my($rev, $cid) = $self->dbRevisionInsert('r', $rid, $o{editsum}, $o{uid}); insert_rev($self, $cid, $rid, \%o); return ($rev, $cid); } @@ -164,7 +164,7 @@ sub dbReleaseEdit { # returns: ( item id, global revision ) sub dbReleaseAdd { my($self, %o) = @_; - my($rid, $cid) = $self->dbItemInsert(1, $o{editsum}, $o{uid}); + my($rid, $cid) = $self->dbItemInsert('r', $o{editsum}, $o{uid}); insert_rev($self, $cid, $rid, \%o); return ($rid, $cid); } @@ -189,9 +189,9 @@ sub insert_rev { ) for (@{$o->{languages}}); $self->dbExec(q| - INSERT INTO releases_producers (rid, pid) - VALUES (?, ?)|, - $cid, $_ + INSERT INTO releases_producers (rid, pid, developer, publisher) + VALUES (?, ?, ?, ?)|, + $cid, $_->[0], $_->[1]?1:0, $_->[2]?1:0 ) for (@{$o->{producers}}); $self->dbExec(q| diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm index b2cd1a31..593c6415 100644 --- a/lib/VNDB/DB/Users.pm +++ b/lib/VNDB/DB/Users.pm @@ -5,11 +5,11 @@ use strict; use warnings; use Exporter 'import'; -our @EXPORT = qw|dbUserGet dbUserEdit dbUserAdd dbUserDel dbSessionAdd dbSessionDel dbSessionCheck|; +our @EXPORT = qw|dbUserGet dbUserEdit dbUserAdd dbUserDel dbUserMessageCount dbSessionAdd dbSessionDel dbSessionCheck|; # %options->{ username passwd mail order uid ip registered search results page what } -# what: stats mymessages +# what: stats extended sub dbUserGet { my $s = shift; my %o = ( @@ -43,8 +43,12 @@ sub dbUserGet { ); my @select = ( - qw|id username mail rank salt c_votes c_changes show_nsfw show_list skin customcss ip c_tags ign_votes|, - q|encode(passwd, 'hex') AS passwd|, q|extract('epoch' from registered) as registered|, + qw|id username c_votes c_changes show_list c_tags|, + q|extract('epoch' from registered) as registered|, + $o{what} =~ /extended/ ? ( + qw|mail rank salt skin customcss show_nsfw ign_votes|, + q|encode(passwd, 'hex') AS passwd| + ) : (), $o{what} =~ /stats/ ? ( '(SELECT COUNT(*) FROM rlists WHERE uid = u.id) AS releasecount', '(SELECT COUNT(DISTINCT rv.vid) FROM rlists rl JOIN releases r ON rl.rid = r.id JOIN releases_vn rv ON rv.rid = r.latest WHERE uid = u.id) AS vncount', @@ -53,8 +57,6 @@ sub dbUserGet { '(SELECT COUNT(DISTINCT tag) FROM tags_vn WHERE uid = u.id) AS tagcount', '(SELECT COUNT(DISTINCT vid) FROM tags_vn WHERE uid = u.id) AS tagvncount', ) : (), - $o{what} =~ /mymessages/ ? - '(SELECT COUNT(*) FROM threads_boards tb JOIN threads t ON t.id = tb.tid WHERE tb.type = \'u\' AND tb.iid = u.id AND t.hidden = FALSE) AS mymessages' : (), ); my($r, $np) = $s->dbPage(\%o, q| @@ -110,6 +112,20 @@ sub dbUserDel { } +# Returns number of unread messages +sub dbUserMessageCount { # uid + my($s, $uid) = @_; + return $s->dbRow(q{ + SELECT SUM(tbi.count) AS cnt FROM ( + SELECT t.count-COALESCE(tb.lastread,0) + FROM threads_boards tb + JOIN threads t ON t.id = tb.tid AND NOT t.hidden + WHERE tb.type = 'u' AND tb.iid = ? + ) AS tbi (count) + }, $uid)->{cnt}||0; +} + + # Adds a session to the database # If no expiration is supplied the database default is used # uid, 40 character session token, expiration time (timestamp) diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm index b3603985..bb2c1275 100644 --- a/lib/VNDB/DB/VN.pm +++ b/lib/VNDB/DB/VN.pm @@ -5,6 +5,7 @@ use strict; use warnings; use Exporter 'import'; use VNDB::Func 'gtintype'; +use Encode 'decode_utf8'; our @EXPORT = qw|dbVNGet dbVNAdd dbVNEdit dbVNImageId dbVNCache dbScreenshotAdd dbScreenshotGet dbScreenshotRandom|; @@ -76,7 +77,7 @@ sub dbVNGet { $o{what} =~ /changes/ ? 'JOIN users u ON u.id = c.requester' : (), $o{what} =~ /relgraph/ ? - 'JOIN relgraph rg ON rg.id = v.rgraph' : (), + 'JOIN relgraphs vg ON vg.id = v.rgraph' : (), ); my $tag_ids = $o{tags_include} && join ',', @{$o{tags_include}[1]}; @@ -86,7 +87,7 @@ sub dbVNGet { qw|vr.alias vr.image vr.img_nsfw vr.length vr.desc vr.l_wp vr.l_encubed vr.l_renai vr.l_vnn| ) : (), $o{what} =~ /changes/ ? ( qw|c.requester c.comments v.latest u.username c.rev c.causedby|, q|extract('epoch' from c.added) as added|) : (), - $o{what} =~ /relgraph/ ? 'rg.cmap' : (), + $o{what} =~ /relgraph/ ? 'vg.svg' : (), $o{what} =~ /ranking/ ? '(SELECT COUNT(*)+1 FROM vn iv WHERE iv.hidden = false AND iv.c_popularity > v.c_popularity) AS ranking' : (), $tag_ids ? qq|(SELECT AVG(tvb.rating) FROM tags_vn_bayesian tvb WHERE tvb.tag IN($tag_ids) AND tvb.vid = v.id AND spoiler <= $o{tags_include}[0] GROUP BY tvb.vid) AS tagscore| : (), @@ -101,6 +102,10 @@ sub dbVNGet { join(', ', @select), join(' ', @join), \%where, $o{order}, ); + if($o{what} =~ /relgraph/) { + $_->{svg} = decode_utf8($_->{svg}) for @$r; + } + if(@$r && $o{what} =~ /(anime|relations|screenshots)/) { my %r = map { $r->[$_]{anime} = []; @@ -155,7 +160,7 @@ sub dbVNGet { # returns: ( local revision, global revision ) sub dbVNEdit { my($self, $id, %o) = @_; - my($rev, $cid) = $self->dbRevisionInsert(0, $id, $o{editsum}, $o{uid}); + my($rev, $cid) = $self->dbRevisionInsert('v', $id, $o{editsum}, $o{uid}); insert_rev($self, $cid, $id, \%o); return ($rev, $cid); } @@ -165,7 +170,7 @@ sub dbVNEdit { # returns: ( item id, global revision ) sub dbVNAdd { my($self, %o) = @_; - my($id, $cid) = $self->dbItemInsert(0, $o{editsum}, $o{uid}); + my($id, $cid) = $self->dbItemInsert('v', $o{editsum}, $o{uid}); insert_rev($self, $cid, $id, \%o); return ($id, $cid); } diff --git a/lib/VNDB/Func.pm b/lib/VNDB/Func.pm index cd4c4b62..ad38215e 100644 --- a/lib/VNDB/Func.pm +++ b/lib/VNDB/Func.pm @@ -149,10 +149,10 @@ sub gtintype { sub liststat { my $l = shift; return '' if !$l; - my $rs = $YAWF::OBJ->{vn_rstat}[$l->{rstat}]; + 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 = $YAWF::OBJ->{vn_vstat}[$l->{vstat}]; + 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"; diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm index 1d74ac6a..12b55029 100644 --- a/lib/VNDB/Handler/Discussions.pm +++ b/lib/VNDB/Handler/Discussions.pm @@ -29,6 +29,11 @@ sub thread { my $p = $self->dbPostGet(tid => $tid, results => 25, page => $page, what => 'user'); return 404 if !$p->[0]; + # mark as read when this thread is posted in the board of the currently logged in user + my $uid = $self->authInfo->{id}; + $self->dbPostRead($t->{id}, $uid, $p->[$#$p]{num}) + if $uid && grep $_->{type} eq 'u' && $_->{iid} == $uid, @{$t->{boards}}; + $self->htmlHeader(title => $t->{title}); div class => 'mainbox'; @@ -293,7 +298,7 @@ sub board { order => $type eq 'an' ? 't.id DESC' : 'tpl.date DESC', ); - $self->htmlHeader(title => $title, noindex => !@$list); + $self->htmlHeader(title => $title, noindex => !@$list || $type eq 'u'); $self->htmlMainTabs($type, $obj, 'disc') if $iid; div class => 'mainbox'; diff --git a/lib/VNDB/Handler/Misc.pm b/lib/VNDB/Handler/Misc.pm index 80a9e9ea..9625372b 100644 --- a/lib/VNDB/Handler/Misc.pm +++ b/lib/VNDB/Handler/Misc.pm @@ -66,10 +66,9 @@ sub homepage { my $changes = $self->dbRevisionGet(what => 'item user', results => 10, auto => 1, hidden => 1); ul; for (@$changes) { - my $t = (qw|v r p|)[$_->{type}]; li; - lit mt '_home_recentchanges_item', $t, - sprintf('<a href="%s" title="%s">%s</a>', "/$t$_->{iid}.$_->{rev}", + lit mt '_home_recentchanges_item', $_->{type}, + sprintf('<a href="%s" title="%s">%s</a>', "/$_->{type}$_->{iid}.$_->{rev}", xml_escape($_->{ioriginal}||$_->{ititle}), xml_escape shorten $_->{ititle}, 33), $_; end; @@ -295,6 +294,12 @@ sub docpage { close $F; $ii; }eg; + s{^:TOP5CONTRIB:$}{ + my $l = $self->dbUserGet(results => 6, order => 'c_changes DESC'); + '<dl>'.join('', map $_->{id} == 1 ? () : + sprintf('<dt><a href="/u%d">%s</a></dt><dd>%d</dd>', $_->{id}, $_->{username}, $_->{c_changes}), + @$l).'</dl>'; + }eg; } $self->htmlHeader(title => $title); @@ -331,7 +336,7 @@ sub itemmod { my $obj = $type eq 'v' ? $self->dbVNGet(id => $iid)->[0] : $type eq 'r' ? $self->dbReleaseGet(id => $iid, what => 'vn extended')->[0] : - $self->dbProducerGet(id => $iid)->[0]; + $self->dbProducerGet(id => $iid, what => 'extended')->[0]; return 404 if !$obj->{id}; $self->dbItemMod($type, $iid, $act eq 'hide' ? (hidden => !$obj->{hidden}) : (locked => !$obj->{locked})); diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm index e6477567..4e75962b 100644 --- a/lib/VNDB/Handler/Producers.pm +++ b/lib/VNDB/Handler/Producers.pm @@ -3,11 +3,12 @@ package VNDB::Handler::Producers; use strict; use warnings; -use YAWF ':html', ':xml'; +use YAWF ':html', ':xml', 'xml_escape'; use VNDB::Func; YAWF::register( + qr{p([1-9]\d*)/rg} => \&rg, qr{p([1-9]\d*)(?:\.([1-9]\d*))?} => \&page, qr{p(?:([1-9]\d*)(?:\.([1-9]\d*))?/edit|/new)} => \&edit, @@ -16,12 +17,34 @@ YAWF::register( ); +sub rg { + my($self, $pid) = @_; + + my $p = $self->dbProducerGet(id => $pid, what => 'relgraph')->[0]; + return 404 if !$p->{id} || !$p->{rgraph}; + + my $title = mt '_prodrg_title', $p->{name}; + return if $self->htmlRGHeader($title, 'p', $p); + + $p->{svg} =~ s/\$___(_prodrel_[a-z]+)____\$/mt $1/eg; + $p->{svg} =~ s/\$(_lang_[a-z]+)_\$/mt $1/eg; + $p->{svg} =~ s/\$(_ptype_[a-z]+)_\$/mt $1/eg; + + div class => 'mainbox'; + h1 $title; + p class => 'center'; + lit $p->{svg}; + end; + end; + $self->htmlFooter; +} + sub page { my($self, $pid, $rev) = @_; my $p = $self->dbProducerGet( id => $pid, - what => 'vn extended'.($rev ? ' changes' : ''), + what => 'vn extended relations'.($rev ? ' changes' : ''), $rev ? ( rev => $rev ) : () )->[0]; return 404 if !$p->{id}; @@ -31,7 +54,7 @@ sub page { return if $self->htmlHiddenMessage('p', $p); if($rev) { - my $prev = $rev && $rev > 1 && $self->dbProducerGet(id => $pid, rev => $rev-1, what => 'changes extended')->[0]; + my $prev = $rev && $rev > 1 && $self->dbProducerGet(id => $pid, rev => $rev-1, what => 'changes extended relations')->[0]; $self->htmlRevision('p', $prev, $p, [ type => serialize => sub { mt "_ptype_$_[0]" } ], [ name => diff => 1 ], @@ -40,6 +63,12 @@ sub page { [ lang => serialize => sub { "$_[0] (".mt("_lang_$_[0]").')' } ], [ website => diff => 1 ], [ desc => diff => 1 ], + [ relations => join => '<br />', split => sub { + my @r = map sprintf('%s: <a href="/p%d" title="%s">%s</a>', + mt("_prodrel_$_->{relation}"), $_->{id}, xml_escape($_->{original}||$_->{name}), xml_escape shorten $_->{name}, 40 + ), sort { $a->{id} <=> $b->{id} } @{$_[0]}; + return @r ? @r : (mt '_proddiff_none'); + }], ); } @@ -56,6 +85,23 @@ sub page { } end; + if(@{$p->{relations}}) { + my %rel; + push @{$rel{$_->{relation}}}, $_ + for (sort { $a->{name} cmp $b->{name} } @{$p->{relations}}); + p class => 'center'; + txt "\n"; + for my $r (sort keys %rel) { + txt mt("_prodrel_$r").': '; + for (@{$rel{$r}}) { + a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 40; + txt ', ' if $_ ne $rel{$r}[$#{$rel{$r}}]; + } + txt "\n"; + } + end; + } + if($p->{desc}) { p class => 'description'; lit bb2html $p->{desc}; @@ -75,6 +121,8 @@ sub page { lit $self->{l10n}->datestr($_->{date}); end; a href => "/v$_->{id}", title => $_->{original}, $_->{title}; + b class => 'grayedout', ' ('.join(', ', + $_->{developer} ? mt '_prodpage_dev' : (), $_->{publisher} ? mt '_prodpage_pub' : ()).')'; end; } end; @@ -89,36 +137,55 @@ sub page { sub edit { my($self, $pid, $rev) = @_; - my $p = $pid && $self->dbProducerGet(id => $pid, what => 'changes extended', $rev ? (rev => $rev) : ())->[0]; + my $p = $pid && $self->dbProducerGet(id => $pid, what => 'changes extended relations', $rev ? (rev => $rev) : ())->[0]; return 404 if $pid && !$p->{id}; $rev = undef if !$p || $p->{cid} == $p->{latest}; return $self->htmlDenied if !$self->authCan('edit') || $pid && ($p->{locked} && !$self->authCan('lock') || $p->{hidden} && !$self->authCan('del')); - my %b4 = !$pid ? () : map { $_ => $p->{$_} } qw|type name original lang website desc alias|; + my %b4 = !$pid ? () : ( + (map { $_ => $p->{$_} } qw|type name original lang website desc alias|), + prodrelations => join('|||', map $_->{relation}.','.$_->{id}.','.$_->{name}, sort { $a->{id} <=> $b->{id} } @{$p->{relations}}), + ); my $frm; if($self->reqMethod eq 'POST') { $frm = $self->formValidate( - { name => 'type', enum => $self->{producer_types} }, - { name => 'name', maxlength => 200 }, - { name => 'original', required => 0, maxlength => 200, default => '' }, - { name => 'alias', required => 0, maxlength => 500, default => '' }, - { name => 'lang', enum => $self->{languages} }, - { name => 'website', required => 0, template => 'url', default => '' }, - { name => 'desc', required => 0, maxlength => 5000, default => '' }, - { name => 'editsum', maxlength => 5000 }, + { name => 'type', enum => $self->{producer_types} }, + { name => 'name', maxlength => 200 }, + { name => 'original', required => 0, maxlength => 200, default => '' }, + { name => 'alias', required => 0, maxlength => 500, default => '' }, + { name => 'lang', enum => $self->{languages} }, + { name => 'website', required => 0, template => 'url', default => '' }, + { name => 'desc', required => 0, maxlength => 5000, default => '' }, + { name => 'prodrelations', required => 0, maxlength => 5000, default => '' }, + { name => 'editsum', maxlength => 5000 }, ); if(!$frm->{_err}) { + # parse + my $relations = [ map { /^([a-z]+),([0-9]+),(.+)$/ && (!$pid || $2 != $pid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{prodrelations} ]; + + # normalize + $frm->{prodrelations} = join '|||', map $_->[0].','.$_->[1].','.$_->[2], sort { $a->[1] <=> $b->[1]} @{$relations}; + return $self->resRedirect("/p$pid", 'post') if $pid && !grep $frm->{$_} ne $b4{$_}, keys %b4; + $frm->{relations} = $relations; $rev = 1; + my $cid; if($pid) { - ($rev) = $self->dbProducerEdit($pid, %$frm); + ($rev, $cid) = $self->dbProducerEdit($pid, %$frm); } else { - ($pid) = $self->dbProducerAdd(%$frm); + ($pid, $cid) = $self->dbProducerAdd(%$frm); + } + + # update reverse relations + if(!$pid && $#$relations >= 0 || $pid && $frm->{prodrelations} ne $b4{prodrelations}) { + my %old = $pid ? (map { $_->{id} => $_->{relation} } @{$p->{relations}}) : (); + my %new = map { $_->[1] => $_->[0] } @$relations; + _updreverse($self, \%old, \%new, $pid, $cid, $rev); } return $self->resRedirect("/p$pid.$rev", 'post'); @@ -133,7 +200,8 @@ sub edit { $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('p', $p, 'edit') if $pid; $self->htmlEditMessage('p', $p, $title); - $self->htmlForm({ frm => $frm, action => $pid ? "/p$pid/edit" : '/p/new', editsum => 1 }, 'pedit_geninfo' => [mt('_pedit_form_generalinfo'), + $self->htmlForm({ frm => $frm, action => $pid ? "/p$pid/edit" : '/p/new', editsum => 1 }, + 'pedit_geninfo' => [ mt('_pedit_form_generalinfo'), [ select => name => mt('_pedit_form_type'), short => 'type', options => [ map [ $_, mt "_ptype_$_" ], sort @{$self->{producer_types}} ] ], [ input => name => mt('_pedit_form_name'), short => 'name' ], @@ -145,10 +213,70 @@ sub edit { options => [ map [ $_, "$_ (".mt("_lang_$_").')' ], sort @{$self->{languages}} ] ], [ input => name => mt('_pedit_form_website'), short => 'website' ], [ text => name => mt('_pedit_form_desc').'<br /><b class="standout">'.mt('_inenglish').'</b>', short => 'desc', rows => 6 ], + ], 'pedit_rel' => [ mt('_pedit_form_rel'), + [ hidden => short => 'prodrelations' ], + [ static => nolabel => 1, content => sub { + h2 mt '_pedit_rel_sel'; + table; + tbody id => 'relation_tbl'; + # to be filled using javascript + end; + end; + + h2 mt '_pedit_rel_add'; + table; + Tr id => 'relation_new'; + td class => 'tc_prod'; + input type => 'text', class => 'text'; + end; + td class => 'tc_rel'; + Select; + option value => $_, mt "_prodrel_$_" + for (sort { $self->{prod_relations}{$a}[0] <=> $self->{prod_relations}{$b}[0] } keys %{$self->{prod_relations}}); + end; + end; + td class => 'tc_add'; + a href => '#', mt '_pedit_rel_addbut'; + end; + end; + end; + }], ]); $self->htmlFooter; } +# !IMPORTANT!: Don't forget to update this function when +# adding/removing fields to/from producer entries! +sub _updreverse { + my($self, $old, $new, $pid, $cid, $rev) = @_; + my %upd; + + # compare %old and %new + for (keys %$old, keys %$new) { + if(exists $$old{$_} and !exists $$new{$_}) { + $upd{$_} = undef; + } elsif((!exists $$old{$_} and exists $$new{$_}) || ($$old{$_} ne $$new{$_})) { + $upd{$_} = $self->{prod_relations}{$$new{$_}}[1]; + } + } + + return if !keys %upd; + + # edit all related producers + for my $i (keys %upd) { + my $r = $self->dbProducerGet(id => $i, what => 'extended relations')->[0]; + my @newrel = map $_->{id} != $pid ? [ $_->{relation}, $_->{id} ] : (), @{$r->{relations}}; + push @newrel, [ $upd{$i}, $pid ] if $upd{$i}; + $self->dbProducerEdit($i, + relations => \@newrel, + editsum => "Reverse relation update caused by revision p$pid.$rev", + causedby => $cid, + uid => 1, # Multi - hardcoded + ( map { $_ => $r->{$_} } qw|type name original lang website desc alias| ) + ); + } +} + sub list { my($self, $char) = @_; diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm index d916e056..7365f235 100644 --- a/lib/VNDB/Handler/Releases.pm +++ b/lib/VNDB/Handler/Releases.pm @@ -54,17 +54,16 @@ sub page { [ notes => diff => 1 ], [ platforms => join => ', ', split => sub { map mt("_plat_$_"), @{$_[0]} } ], [ media => join => ', ', split => sub { - map { - my $med = $self->{media}{$_->{medium}}; - $med->[1] ? sprintf('%d %s%s', $_->{qty}, $med->[0], $_->{qty}>1?'s':'') : $med->[0] - } @{$_[0]}; + map $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1), @{$_[0]} } ], [ resolution => serialize => sub { $self->{resolutions}[$_[0]][0] } ], [ voiced => serialize => sub { mt '_voiced_'.$_[0] } ], [ ani_story => serialize => sub { mt '_animated_'.$_[0] } ], [ ani_ero => serialize => sub { mt '_animated_'.$_[0] } ], [ producers => join => '<br />', split => sub { - map sprintf('<a href="/p%d" title="%s">%s</a>', $_->{id}, $_->{original}||$_->{name}, shorten $_->{name}, 50), @{$_[0]}; + map sprintf('<a href="/p%d" title="%s">%s</a> (%s)', $_->{id}, $_->{original}||$_->{name}, shorten($_->{name}, 50), + join(', ', $_->{developer} ? mt '_reldiff_developer' :(), $_->{publisher} ? mt '_reldiff_publisher' :()) + ), @{$_[0]}; } ], ); } @@ -154,11 +153,9 @@ sub _infotable { if(@{$r->{media}}) { Tr ++$i % 2 ? (class => 'odd') : (); td mt '_relinfo_media', scalar @{$r->{media}}; - # TODO: TL the media - td join ', ', map { - my $med = $self->{media}{$_->{medium}}; - $med->[1] ? sprintf('%d %s%s', $_->{qty}, $med->[0], $_->{qty}>1?'s':'') : $med->[0] - } @{$r->{media}}; + td join ', ', map + $self->{media}{$_->{medium}} ? $_->{qty}.' '.mt("_med_$_->{medium}", $_->{qty}) : mt("_med_$_->{medium}",1), + @{$r->{media}}; end; } @@ -199,16 +196,19 @@ sub _infotable { end; } - if(@{$r->{producers}}) { - Tr ++$i % 2 ? (class => 'odd') : (); - td mt '_relinfo_producer', scalar @{$r->{producers}}; - td; - for (@{$r->{producers}}) { - a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 60; - br if $_ != $r->{producers}[$#{$r->{producers}}]; - } - end; - end; + for my $t (qw|developer publisher|) { + my @prod = grep $_->{$t}, @{$r->{producers}}; + if(@prod) { + Tr ++$i % 2 ? (class => 'odd') : (); + td mt "_relinfo_$t", scalar @prod; + td; + for (@prod) { + a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 60; + br if $_ != $prod[$#prod]; + } + end; + end; + } } if($r->{gtin}) { @@ -241,14 +241,14 @@ sub _infotable { td; Select id => 'listsel', name => 'listsel'; option mt !$rl ? '_relinfo_user_notlist' : - ('_relinfo_user_inlist', $self->{vn_rstat}[$rl->{rstat}], $self->{vn_vstat}[$rl->{vstat}]); + ('_relinfo_user_inlist', mt('_rlst_rstat_'.$rl->{rstat}), mt('_rlst_vstat_'.$rl->{vstat})); optgroup label => mt '_relinfo_user_setr'; - option value => "r$_", $self->{vn_rstat}[$_] - for (0..$#{$self->{vn_rstat}}); + option value => "r$_", mt '_rlst_rstat_'.$_ + for (@{$self->{rlst_rstat}}); end; optgroup label => mt '_relinfo_user_setv'; - option value => "v$_", $self->{vn_vstat}[$_] - for (0..$#{$self->{vn_vstat}}); + option value => "v$_", mt '_rlst_vstat_'.$_ + for (@{$self->{rlst_vstat}}); end; option value => 'del', mt '_relinfo_user_del' if $rl; end; @@ -289,7 +289,10 @@ sub edit { (map { $_ => $r->{$_} } qw|type title original gtin catalog languages website released notes minage platforms patch resolution voiced freeware doujin ani_story ani_ero|), media => join(',', sort map "$_->{medium} $_->{qty}", @{$r->{media}}), - producers => join('|||', map "$_->{id},$_->{name}", sort { $a->{id} <=> $b->{id} } @{$r->{producers}}), + producers => join('|||', map + sprintf('%d,%d,%s', $_->{id}, ($_->{developer}?1:0)+($_->{publisher}?2:0), $_->{name}), + sort { $a->{id} <=> $b->{id} } @{$r->{producers}} + ), ); $b4{vn} = join('|||', map "$_->{vid},$_->{title}", @$vn); my $frm; @@ -325,7 +328,7 @@ sub edit { if(!$frm->{_err}) { # de-serialize $media = [ map [ split / / ], split /,/, $frm->{media} ]; - $producers = [ map { /^([0-9]+)/ ? $1 : () } split /\|\|\|/, $frm->{producers} ]; + $producers = [ map { /^([0-9]+),([1-3])/ ? [ $1, $2&1?1:0, $2&2?1:0] : () } split /\|\|\|/, $frm->{producers} ]; $new_vn = [ map { /^([0-9]+)/ ? $1 : () } split /\|\|\|/, $frm->{vn} ]; $frm->{platforms} = [ grep $_, @{$frm->{platforms}} ]; $frm->{$_} = $frm->{$_} ? 1 : 0 for (qw|patch freeware doujin|); @@ -335,7 +338,7 @@ sub edit { my $same = $rid && (join(',', sort @{$b4{platforms}}) eq join(',', sort @{$frm->{platforms}})) && - (join(',', sort @$producers) eq join(',', sort map $_->{id}, @{$r->{producers}})) && + (join(',', map join(' ', @$_), sort { $a->[0] <=> $b->[0] } @$producers) eq join(',', sort map sprintf('%d %d %d',$_->{id}, $_->{developer}?1:0, $_->{publisher}?1:0), sort { $a->{id} <=> $b->{id} } @{$r->{producers}})) && (join(',', sort @$new_vn) eq join(',', sort map $_->{vid}, @$vn)) && (join(',', sort @{$b4{languages}}) eq join(',', sort @{$frm->{languages}})) && !grep !/^(platforms|producers|vn|languages)$/ && $frm->{$_} ne $b4{$_}, keys %b4; @@ -370,7 +373,7 @@ sub edit { $frm->{original} = $v->{original} if !defined $frm->{original} && !$r; my $title = mt $rid ? ($copy ? '_redit_title_copy' : '_redit_title_edit', $r->{title}) : ('_redit_title_add', $v->{title}); - $self->htmlHeader(js => 'forms', title => $title, noindex => 1); + $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('r', $r, $copy ? 'copy' : 'edit') if $rid; $self->htmlMainTabs('v', $v, 'edit') if $vid; $self->htmlEditMessage('r', $r, $title, $copy); @@ -435,7 +438,7 @@ sub _form { h2 mt '_redit_form_media'; div id => 'media_div'; Select; - option value => $_, class => $self->{media}{$_}[1] ? 'qty' : 'noqty', $self->{media}{$_}[0] + option value => $_, class => $self->{media}{$_} ? 'qty' : 'noqty', mt "_med_$_", 1 for (sort keys %{$self->{media}}); end; end; @@ -446,13 +449,17 @@ sub _form { [ hidden => short => 'producers' ], [ static => nolabel => 1, content => sub { h2 mt('_redit_form_prod_sel'); - div id => 'producerssel'; - end; + table; tbody id => 'producer_tbl'; end; end; h2 mt('_redit_form_prod_add'); - div; - input type => 'text', class => 'text'; - a href => '#', 'add'; - end; + table; Tr; + td class => 'tc_name'; input id => 'producer_input', type => 'text', class => 'text'; end; + td class => 'tc_role'; Select id => 'producer_role'; + option value => 1, mt '_redit_form_prod_dev'; + option value => 2, selected => 'selected', mt '_redit_form_prod_pub'; + option value => 3, mt '_redit_form_prod_both'; + end; end; + td class => 'tc_add'; a id => 'producer_add', href => '#', mt '_redit_form_prod_addbut'; end; + end; end; }], ], @@ -460,12 +467,11 @@ sub _form { [ hidden => short => 'vn' ], [ static => nolabel => 1, content => sub { h2 mt('_redit_form_vn_sel'); - div id => 'vnsel'; - end; + table; tbody id => 'vn_tbl'; end; end; h2 mt('_redit_form_vn_add'); div; - input type => 'text', class => 'text'; - a href => '#', 'add'; + input id => 'vn_input', type => 'text', class => 'text'; + a href => '#', id => 'vn_add', mt '_redit_form_vn_addbut'; end; }], ], @@ -484,7 +490,7 @@ sub browse { { name => 'ln', required => 0, multi => 1, default => '', enum => $self->{languages} }, { name => 'pl', required => 0, multi => 1, default => '', enum => $self->{platforms} }, { name => 'me', required => 0, multi => 1, default => '', enum => [ keys %{$self->{media}} ] }, - { name => 'tp', required => 0, default => -1, enum => [ -1, @{$self->{release_types}} ] }, + { name => 'tp', required => 0, default => '', enum => [ '', @{$self->{release_types}} ] }, { name => 'pa', required => 0, default => 0, enum => [ 0..2 ] }, { name => 'fw', required => 0, default => 0, enum => [ 0..2 ] }, { name => 'do', required => 0, default => 0, enum => [ 0..2 ] }, @@ -503,7 +509,7 @@ sub browse { $f->{ln}[0] ? (languages => $f->{ln}) : (), $f->{me}[0] ? (media => $f->{me}) : (), $f->{re}[0] ? (resolutions => $f->{re} ) : (), - $f->{tp} >= 0 ? (type => $f->{tp}) : (), + $f->{tp} ? (type => $f->{tp}) : (), $f->{ma_a} || $f->{ma_m} ? (minage => [$f->{ma_m}, $f->{ma_a}]) : (), $f->{pa} ? (patch => $f->{pa}) : (), $f->{fw} ? (freeware => $f->{fw}) : (), @@ -614,7 +620,7 @@ sub _filters { end; end; $self->htmlFormPart($f, [ select => short => 'tp', name => mt('_rbrowse_type'), - options => [ [-1, mt '_rbrowse_all'], map [ $_, mt "_rtype_$_" ], @{$self->{release_types}} ]]); + options => [ ['', mt '_rbrowse_all'], map [ $_, mt "_rtype_$_" ], @{$self->{release_types}} ]]); $self->htmlFormPart($f, [ select => short => 'pa', name => mt('_rbrowse_patch'), options => [ [0, mt '_rbrowse_all' ], [1, mt '_rbrowse_patchonly'], [2, mt '_rbrowse_patchnone']]]); $self->htmlFormPart($f, [ select => short => 'fw', name => mt('_rbrowse_freeware'), @@ -629,7 +635,7 @@ sub _filters { txt mt '_rbrowse_languages'; b ' ('.mt('_rbrowse_boolor').')'; end; - for my $i (sort @{$self->dbLanguages}) { + for my $i (@{$self->{languages}}) { span; input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i", grep($_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : (); label for => "lang_$i"; @@ -660,7 +666,7 @@ sub _filters { for my $i (sort keys %{$self->{media}}) { span; input type => 'checkbox', name => 'me', value => $i, id => "med_$i", grep($_ eq $i, @{$f->{me}}) ? (checked => 'checked') : (); - label for => "med_$i", $self->{media}{$i}[0]; + label for => "med_$i", mt "_med_$i", 1; end; } diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm index ae240d38..4c969436 100644 --- a/lib/VNDB/Handler/Tags.pm +++ b/lib/VNDB/Handler/Tags.pm @@ -36,7 +36,7 @@ sub tagpage { ); return 404 if $f->{_err}; my $tagspoil = $self->reqCookie('tagspoil'); - $f->{m} = $tagspoil =~ /^[0-2]$/ ? $tagspoil : 1 if $f->{m} == -1; + $f->{m} = $tagspoil =~ /^[0-2]$/ ? $tagspoil : 0 if $f->{m} == -1; my($list, $np) = $t->{meta} || $t->{state} != 2 ? ([],0) : $self->dbTagVNs( tag => $tag, @@ -422,7 +422,7 @@ sub vntagmod { my $frm; my $title = mt '_tagv_title', $v->{title}; - $self->htmlHeader(title => $title, noindex => 1, js => 'forms'); + $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('v', $v, 'tagmod'); div class => 'mainbox'; h1 $title; @@ -438,43 +438,44 @@ sub vntagmod { $self->htmlForm({ frm => $frm, action => "/v$vid/tagmod", nosubmit => 1 }, tagmod => [ mt('_tagv_frm_title'), [ hidden => short => 'taglinks', value => '' ], [ static => nolabel => 1, content => sub { - table id => 'tagtable'; + table class => 'tgl'; thead; Tr; td ''; - td colspan => 2, class => 'tc2_1', mt '_tagv_col_you'; - td colspan => 2, class => 'tc3_1', mt '_tagv_col_others'; + td colspan => 2, class => 'tc_you', mt '_tagv_col_you'; + td colspan => 2, class => 'tc_others', mt '_tagv_col_others'; end; Tr; - my $i=0; - td class => 'tc'.++$i, mt '_tagv_col_'.$_ for(qw|tag rating spoiler rating spoiler|); + td class => 'tc_tagname', mt '_tagv_col_tag'; + td class => 'tc_myvote', mt '_tagv_col_rating'; + td class => 'tc_myspoil', mt '_tagv_col_spoiler'; + td class => 'tc_allvote', mt '_tagv_col_rating'; + td class => 'tc_allspoil', mt '_tagv_col_spoiler'; end; end; tfoot; Tr; td colspan => 5; input type => 'submit', class => 'submit', value => mt('_tagv_save'), style => 'float: right'; - input type => 'text', class => 'text', name => 'addtag', value => ''; - input type => 'button', class => 'submit', value => mt '_tagv_add'; + input id => 'tagmod_tag', type => 'text', class => 'text', value => ''; + input id => 'tagmod_add', type => 'button', class => 'submit', value => mt '_tagv_add'; br; p; lit mt '_tagv_addmsg'; end; end; end; end; - tbody; + tbody id => 'tagtable'; for my $t (sort { $a->{name} cmp $b->{name} } @$tags) { my $m = (grep $_->{tag} == $t->{id}, @$my)[0] || {}; - Tr; - td class => 'tc1'; - a href => "/g$t->{id}", $t->{name}; - end; - td class => 'tc2', $m->{vote}||0; - td class => 'tc3', defined $m->{spoiler} ? $m->{spoiler} : -1; - td class => 'tc4'; + Tr id => "tgl_$t->{id}"; + td class => 'tc_tagname'; a href => "/g$t->{id}", $t->{name}; end; + td class => 'tc_myvote', $m->{vote}||0; + td class => 'tc_myspoil', defined $m->{spoiler} ? $m->{spoiler} : -1; + td class => 'tc_allvote'; tagscore !$m->{vote} ? $t->{rating} : $t->{cnt} == 1 ? 0 : ($t->{rating}*$t->{cnt} - $m->{vote}) / ($t->{cnt}-1); i ' ('.($t->{cnt} - ($m->{vote} ? 1 : 0)).')'; end; - td class => 'tc5', sprintf '%.2f', $t->{spoiler}; + td class => 'tc_allspoil', sprintf '%.2f', $t->{spoiler}; end; } end; @@ -529,7 +530,7 @@ sub usertags { header => [ sub { td class => 'tc1'; - b id => 'relhidall'; + b id => 'expandall'; lit '<i>▸</i> '.mt('_tagu_col_num').' '; end; lit $f->{s} eq 'cnt' && $f->{o} eq 'a' ? "\x{25B4}" : qq|<a href="/u$u->{id}/tags?o=a;s=cnt">\x{25B4}</a>|; @@ -542,7 +543,7 @@ sub usertags { row => sub { my($s, $n, $l) = @_; Tr $n % 2 ? (class => 'odd') : (); - td class => 'tc1 relhid_but', id => "tag$l->{id}"; + td class => 'tc1 collapse_but', id => "tag$l->{id}"; lit "<i>▸</i> $l->{cnt}"; end; td class => 'tc2', colspan => 2; @@ -550,7 +551,7 @@ sub usertags { end; end; for(@{$l->{vns}}) { - Tr class => "relhid tag$l->{id}"; + Tr class => "collapse collapse_tag$l->{id}"; td class => 'tc1_1'; tagscore $_->{vote}; end; @@ -652,7 +653,7 @@ sub tagxml { my($list, $np) = $self->dbTagGet( $q =~ /^g([1-9]\d*)/ ? (id => $1) : $q =~ /^name:(.+)$/ ? (name => $1) : (search => $q), - results => 10, + results => 15, page => 1, ); diff --git a/lib/VNDB/Handler/ULists.pm b/lib/VNDB/Handler/ULists.pm index 7d1a9304..6a0d6c9e 100644 --- a/lib/VNDB/Handler/ULists.pm +++ b/lib/VNDB/Handler/ULists.pm @@ -69,7 +69,7 @@ sub rlist { return $self->htmlDenied() if !$uid; my $f = $self->formValidate( - { name => 'e', required => 1, enum => [ 'del', map("r$_", 0..$#{$self->{vn_rstat}}), map("v$_", 0..$#{$self->{vn_vstat}}) ] }, + { name => 'e', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] }, ); return 404 if $f->{_err}; @@ -212,7 +212,7 @@ sub vnlist { if($own && $self->reqMethod eq 'POST') { my $frm = $self->formValidate( { name => 'sel', required => 0, default => 0, multi => 1, template => 'int' }, - { name => 'batchedit', required => 1, enum => [ 'del', map("r$_", 0..$#{$self->{vn_rstat}}), map("v$_", 0..$#{$self->{vn_vstat}}) ] }, + { name => 'batchedit', required => 1, enum => [ 'del', map("r$_", @{$self->{rlst_rstat}}), map("v$_", @{$self->{rlst_vstat}}) ] }, ); if(!$frm->{_err} && @{$frm->{sel}} && $frm->{sel}[0]) { $self->dbVNListDel($uid, $frm->{sel}) if $frm->{batchedit} eq 'del'; @@ -229,7 +229,7 @@ sub vnlist { uid => $uid, results => 50, page => $f->{p}, - order => $f->{s}.' '.($f->{o} eq 'd' ? 'DESC' : 'ASC'), + order => $f->{s}.' '.($f->{o} eq 'd' ? 'DESC' : 'ASC').($f->{s} eq 'vote' ? ', title ASC' : ''), voted => $f->{v}, $f->{c} ne 'all' ? (char => $f->{c}) : (), ); @@ -285,7 +285,7 @@ sub _vnlist_browse { pageurl => $url->('page'), header => [ [ mt('_rlist_col_title') => 'title', 3 ], - sub { td class => 'tc2', id => 'relhidall'; lit '<i>▸</i>'.mt('_rlist_col_releases').'*'; end; }, + sub { td class => 'tc2', id => 'expandall'; lit '<i>▸</i>'.mt('_rlist_col_releases').'*'; end; }, [ mt('_rlist_col_vote') => 'vote' ], ], row => sub { @@ -294,7 +294,7 @@ sub _vnlist_browse { td class => 'tc1', colspan => 3; a href => "/v$i->{vid}", title => $i->{original}||$i->{title}, shorten $i->{title}, 70; end; - td class => 'tc2'.(@{$i->{rels}} ? ' relhid_but' : ''), id => 'vid'.$i->{vid}; + td class => 'tc2'.(@{$i->{rels}} ? ' collapse_but' : ''), id => 'vid'.$i->{vid}; lit '<i>▸</i>'; my $obtained = grep $_->{rstat}==2, @{$i->{rels}}; my $finished = grep $_->{vstat}==2, @{$i->{rels}}; @@ -307,7 +307,7 @@ sub _vnlist_browse { end; for (@{$i->{rels}}) { - Tr class => "relhid vid$i->{vid}"; + Tr class => "collapse relhid collapse_vid$i->{vid}"; td class => 'tc1'.($own ? ' own' : ''); input type => 'checkbox', name => 'sel', value => $_->{rid} if $own; @@ -333,12 +333,12 @@ sub _vnlist_browse { Select id => 'batchedit', name => 'batchedit'; option mt '_rlist_selection'; optgroup label => mt '_rlist_changerel'; - option value => "r$_", $self->{vn_rstat}[$_] - for (0..$#{$self->{vn_rstat}}); + option value => "r$_", mt "_rlst_rstat_$_" + for (@{$self->{rlst_rstat}}); end; optgroup label => mt '_rlist_changeplay'; - option value => "v$_", $self->{vn_vstat}[$_] - for (0..$#{$self->{vn_vstat}}); + option value => "v$_", mt "_rlst_vstat_$_" + for (@{$self->{rlst_vstat}}); end; option value => 'del', mt '_rlist_del'; end; diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index d6193ddc..39aecbfc 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -267,7 +267,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)->[0]; + my $u = $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid, what => 'extended')->[0]; return 404 if !$u->{id}; # check POST data @@ -386,7 +386,7 @@ sub posts { [ '' ], [ '' ], [ mt '_uposts_col_date' ], - sub { td; a href => '#', id => 'history_comments', 'expand'; txt mt '_uposts_col_title'; end; } + sub { td; a href => '#', id => 'expandlist', mt '_js_expand'; txt mt '_uposts_col_title'; end; } ], row => sub { my($s, $n, $l) = @_; @@ -396,7 +396,7 @@ sub posts { td class => 'tc3', $self->{l10n}->date($l->{date}); td class => 'tc4'; a href => "/t$l->{tid}.$l->{num}", $l->{title}; end; end; - Tr class => $n % 2 ? 'editsum odd hidden' : 'editsum hidden'; + Tr class => $n % 2 ? 'collapse msgsum odd hidden' : 'collapse msgsum hidden'; td colspan => 4; lit bb2html $l->{msg}, 150; end; diff --git a/lib/VNDB/Handler/VNBrowse.pm b/lib/VNDB/Handler/VNBrowse.pm index 2a6d6cd7..bca21151 100644 --- a/lib/VNDB/Handler/VNBrowse.pm +++ b/lib/VNDB/Handler/VNBrowse.pm @@ -25,7 +25,7 @@ sub list { { 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('tagspoil') =~ /^([0-2])$/ ? $1 : 1, enum => [0..2] }, + { name => 'sp', required => 0, default => $self->reqCookie('tagspoil') =~ /^([0-2])$/ ? $1 : 0, enum => [0..2] }, ); return 404 if $f->{_err}; $f->{q} ||= $f->{sq}; @@ -70,7 +70,7 @@ sub list { $self->resRedirect('/v'.$list->[0]{id}, 'temp') if $f->{q} && @$list == 1; - $self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q}, js => 'forms'); + $self->htmlHeader(title => mt('_vnbrowse_title'), search => $f->{q}); _filters($self, $f, $char, \@ignored); my $url = "/v/$char?q=$f->{q};ti=$f->{ti};te=$f->{te}"; @@ -162,7 +162,7 @@ sub _filters { txt mt '_vnbrowse_lang'; b ' ('.mt('_vnbrowse_boolor').')'; end; - for my $i (sort @{$self->dbLanguages}) { + for my $i (@{$self->{languages}}) { span; input type => 'checkbox', name => 'ln', value => $i, id => "lang_$i", (scalar grep $_ eq $i, @{$f->{ln}}) ? (checked => 'checked') : (); diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm index f09d6ddd..ed7068dc 100644 --- a/lib/VNDB/Handler/VNEdit.pm +++ b/lib/VNDB/Handler/VNEdit.pm @@ -28,7 +28,7 @@ sub edit { my %b4 = !$vid ? () : ( (map { $_ => $v->{$_} } qw|title original desc alias length l_wp l_encubed l_renai l_vnn img_nsfw|), anime => join(' ', sort { $a <=> $b } map $_->{id}, @{$v->{anime}}), - relations => join('|||', map $_->{relation}.','.$_->{id}.','.$_->{title}, sort { $a->{id} <=> $b->{id} } @{$v->{relations}}), + vnrelations => join('|||', map $_->{relation}.','.$_->{id}.','.$_->{title}, sort { $a->{id} <=> $b->{id} } @{$v->{relations}}), screenshots => join(' ', map sprintf('%d,%d,%d', $_->{id}, $_->{nsfw}?1:0, $_->{rid}), @{$v->{screenshots}}), ); @@ -46,7 +46,7 @@ sub edit { { name => 'l_vnn', required => 0, default => $b4{l_vnn}||0, template => 'int' }, { name => 'anime', required => 0, default => '' }, { name => 'img_nsfw', required => 0, default => 0 }, - { name => 'relations', required => 0, default => '', maxlength => 5000 }, + { name => 'vnrelations', required => 0, default => '', maxlength => 5000 }, { name => 'screenshots', required => 0, default => '', maxlength => 1000 }, { name => 'editsum', maxlength => 5000 }, ); @@ -57,11 +57,11 @@ sub edit { if(!$frm->{_err}) { # parse and re-sort fields that have multiple representations of the same information my $anime = { map +($_=>1), grep /^[0-9]+$/, split /[ ,]+/, $frm->{anime} }; - my $relations = [ map { /^([0-9]+),([0-9]+),(.+)$/ && (!$vid || $2 != $vid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{relations} ]; + my $relations = [ map { /^([a-z]+),([0-9]+),(.+)$/ && (!$vid || $2 != $vid) ? [ $1, $2, $3 ] : () } split /\|\|\|/, $frm->{vnrelations} ]; my $screenshots = [ map /^[0-9]+,[01],[0-9]+$/ ? [split /,/] : (), split / +/, $frm->{screenshots} ]; $frm->{anime} = join ' ', sort { $a <=> $b } keys %$anime; - $frm->{relations} = join '|||', map $_->[0].','.$_->[1].','.$_->[2], sort { $a->[1] <=> $b->[1]} @{$relations}; + $frm->{vnrelations} = join '|||', map $_->[0].','.$_->[1].','.$_->[2], sort { $a->[1] <=> $b->[1]} @{$relations}; $frm->{img_nsfw} = $frm->{img_nsfw} ? 1 : 0; $frm->{screenshots} = join ' ', map sprintf('%d,%d,%d', $_->[0], $_->[1]?1:0, $_->[2]), sort { $a->[0] <=> $b->[0] } @$screenshots; @@ -83,7 +83,7 @@ sub edit { ($nvid, $cid) = $self->dbVNAdd(%args) if !$vid; # update reverse relations & relation graph - if(!$vid && $#$relations >= 0 || $vid && $frm->{relations} ne $b4{relations}) { + if(!$vid && $#$relations >= 0 || $vid && $frm->{vnrelations} ne $b4{vnrelations}) { my %old = $vid ? (map { $_->{id} => $_->{relation} } @{$v->{relations}}) : (); my %new = map { $_->[1] => $_->[0] } @$relations; _updreverse($self, \%old, \%new, $nvid, $cid, $nrev); @@ -97,7 +97,7 @@ sub edit { $frm->{editsum} = sprintf 'Reverted to revision v%d.%d', $vid, $rev if $rev && !defined $frm->{editsum}; my $title = $vid ? mt('_vnedit_title_edit', $v->{title}) : mt '_vnedit_title_add'; - $self->htmlHeader(js => 'forms', title => $title, noindex => 1); + $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('v', $v, 'edit') if $vid; $self->htmlEditMessage('v', $v, $title); _form($self, $v, $frm); @@ -183,7 +183,7 @@ sub _form { ], vn_rel => [ mt('_vnedit_rel'), - [ hidden => short => 'relations' ], + [ hidden => short => 'vnrelations' ], [ static => nolabel => 1, content => sub { h2 mt '_vnedit_rel_sel'; table; @@ -193,22 +193,22 @@ sub _form { end; h2 mt '_vnedit_rel_add'; - # TODO: localize JS relartion selector table; Tr id => 'relation_new'; - td class => 'tc1'; + td class => 'tc_vn'; input type => 'text', class => 'text'; end; - td class => 'tc2'; - txt ' is a '; + td class => 'tc_rel'; + txt mt('_vnedit_rel_isa').' '; Select; - option value => $_, $self->{vn_relations}[$_][0] for (0..$#{$self->{vn_relations}}); + option value => $_, mt "_vnrel_$_" + for (sort { $self->{vn_relations}{$a}[0] <=> $self->{vn_relations}{$b}[0] } keys %{$self->{vn_relations}}); end; - txt ' of'; + txt ' '.mt '_vnedit_rel_of'; end; - td class => 'tc3', $v ? $v->{title} : ''; - td class => 'tc4'; - a href => '#', 'add'; + td class => 'tc_title', $v ? $v->{title} : ''; + td class => 'tc_add'; + a href => '#', mt '_vnedit_rel_addbut'; end; end; end; @@ -219,10 +219,9 @@ sub _form { [ hidden => short => 'screenshots' ], [ static => nolabel => 1, content => sub { div class => 'warning'; - lit mt '_vnedit_scr_msg'; + lit mt '_vnedit_scrmsg'; end; br; - # TODO: localize screenshot uploader table; tbody id => 'scr_table', ''; end; @@ -250,11 +249,9 @@ sub _updreverse { # compare %old and %new for (keys %$old, keys %$new) { if(exists $$old{$_} and !exists $$new{$_}) { - $upd{$_} = -1; - } elsif((!exists $$old{$_} and exists $$new{$_}) || ($$old{$_} != $$new{$_})) { - $upd{$_} = $$new{$_}; - if ($self->{vn_relations}[$upd{$_} ][1]) { $upd{$_}-- } - elsif($self->{vn_relations}[$upd{$_}+1][1]) { $upd{$_}++ } + $upd{$_} = undef; + } elsif((!exists $$old{$_} and exists $$new{$_}) || ($$old{$_} ne $$new{$_})) { + $upd{$_} = $self->{vn_relations}{$$new{$_}}[1]; } } @@ -264,7 +261,7 @@ sub _updreverse { for my $i (keys %upd) { my $r = $self->dbVNGet(id => $i, what => 'extended relations anime screenshots')->[0]; my @newrel = map $_->{id} != $vid ? [ $_->{relation}, $_->{id} ] : (), @{$r->{relations}}; - push @newrel, [ $upd{$i}, $vid ] if $upd{$i} != -1; + push @newrel, [ $upd{$i}, $vid ] if $upd{$i}; $self->dbVNEdit($i, relations => \@newrel, editsum => "Reverse relation update caused by revision v$vid.$rev", diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 361963a8..ec5f4afc 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -27,16 +27,17 @@ sub rg { return 404 if !$v->{id} || !$v->{rgraph}; my $title = mt '_vnrg_title', $v->{title}; - $self->htmlHeader(title => $title); - $self->htmlMainTabs('v', $v, 'rg'); + return if $self->htmlRGHeader($title, 'v', $v); + + $v->{svg} =~ s/\$___(_vnrel_[a-z]+)____\$/mt $1/eg; + div class => 'mainbox'; h1 $title; - lit $v->{cmap}; p class => 'center'; - img src => sprintf('%s/rg/%02d/%d.png', $self->{url_static}, $v->{rgraph}%100, $v->{rgraph}), - alt => $title, usemap => '#rgraph'; + lit $v->{svg}; end; end; + $self->htmlFooter; } @@ -148,15 +149,16 @@ sub page { my $t = $self->dbTagStats(vid => $v->{id}, order => 'avg(tv.vote) DESC', minrating => 0, results => 999); if(@$t) { div id => 'tagops'; - a href => '#', mt '_vnpage_tags_spoil0'; - a href => '#', class => 'tsel', mt '_vnpage_tags_spoil1'; + # NOTE: order of these links is hardcoded in JS + a href => '#', class => 'tsel', mt '_vnpage_tags_spoil0'; + a href => '#', mt '_vnpage_tags_spoil1'; a href => '#', mt '_vnpage_tags_spoil2'; a href => '#', class => 'sec', mt '_vnpage_tags_summary'; a href => '#', mt '_vnpage_tags_all'; end; div id => 'vntags'; for (@$t) { - span class => sprintf 'tagspl%.0f %s', $_->{spoiler}, $_->{spoiler} > 1 ? 'hidden' : ''; + span class => sprintf 'tagspl%.0f %s', $_->{spoiler}, $_->{spoiler} > 0 ? 'hidden' : ''; a href => "/g$_->{id}", style => sprintf('font-size: %dpx', $_->{rating}*3.5+6), $_->{name}; b class => 'grayedout', sprintf ' %.1f', $_->{rating}; end; @@ -199,7 +201,7 @@ sub _revision { }], [ relations => join => '<br />', split => sub { my @r = map sprintf('%s: <a href="/v%d" title="%s">%s</a>', - $self->{vn_relations}[$_->{relation}][0], $_->{id}, xml_escape($_->{original}||$_->{title}), xml_escape shorten $_->{title}, 40 + mt("_vnrel_$_->{relation}"), $_->{id}, xml_escape($_->{original}||$_->{title}), xml_escape shorten $_->{title}, 40 ), sort { $a->{id} <=> $b->{id} } @{$_[0]}; return @r ? @r : (mt '_vndiff_none'); }], @@ -229,27 +231,42 @@ sub _revision { sub _producers { my($self, $i, $r) = @_; - return if !grep @{$_->{producers}}, @$r; my %lang; my @lang = grep !$lang{$_}++, map @{$_->{languages}}, @$r; - Tr ++$$i % 2 ? (class => 'odd') : (); - td mt '_vnpage_producers'; - td; - for my $l (@lang) { - my %p = map { $_->{id} => $_ } map @{$_->{producers}}, grep grep($_ eq $l, @{$_->{languages}}), @$r; - my @p = values %p; - next if !@p; - cssicon "lang $l", mt "_lang_$l"; - for (@p) { + if(grep $_->{developer}, map @{$_->{producers}}, @$r) { + my %dev = map $_->{developer} ? ($_->{id} => $_) : (), map @{$_->{producers}}, @$r; + my @dev = values %dev; + Tr ++$$i % 2 ? (class => 'odd') : (); + td mt "_vnpage_developer"; + td; + for (@dev) { a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 30; - txt ' & ' if $_ != $p[$#p]; + txt ' & ' if $_ != $dev[$#dev]; } - txt "\n"; - } - end; - end; + end; + end; + } + + if(grep $_->{publisher}, map @{$_->{producers}}, @$r) { + Tr ++$$i % 2 ? (class => 'odd') : (); + td mt "_vnpage_publisher"; + td; + for my $l (@lang) { + my %p = map $_->{publisher} ? ($_->{id} => $_) : (), map @{$_->{producers}}, grep grep($_ eq $l, @{$_->{languages}}), @$r; + my @p = values %p; + next if !@p; + cssicon "lang $l", mt "_lang_$l"; + for (@p) { + a href => "/p$_->{id}", title => $_->{original}||$_->{name}, shorten $_->{name}, 30; + txt ' & ' if $_ != $p[$#p]; + } + txt "\n"; + } + end; + end; + } } @@ -266,7 +283,7 @@ sub _relations { td class => 'relations'; dl; for(sort keys %rel) { - dt $self->{vn_relations}[$_][0]; + dt mt "_vnrel_$_"; dd; for (@{$rel{$_}}) { a href => "/v$_->{id}", title => $_->{original}||$_->{title}, shorten $_->{title}, 40; @@ -306,7 +323,7 @@ sub _anime { txt '] '; end; acronym title => $_->{title_kanji}||$_->{title_romaji}, shorten $_->{title_romaji}, 50; - b ' ('.(defined $_->{type} ? $self->{anime_types}[$_->{type}].', ' : '').$_->{year}.')'; + b ' ('.(defined $_->{type} ? mt("_animetype_$_->{type}").', ' : '').$_->{year}.')'; txt "\n"; } } @@ -395,7 +412,7 @@ sub _releases { end; td class => 'tc5'; if($self->authInfo->{id}) { - a href => "/r$rel->{id}", id => "rlsel_$rel->{id}"; + a href => "/r$rel->{id}", id => "rlsel_$rel->{id}", class => 'vnrlsel'; lit $rel->{ulist} ? liststat $rel->{ulist} : '--'; end; } else { diff --git a/lib/VNDB/L10N.pm b/lib/VNDB/L10N.pm index d4ff872c..8698cb74 100644 --- a/lib/VNDB/L10N.pm +++ b/lib/VNDB/L10N.pm @@ -5,12 +5,13 @@ use warnings; { package VNDB::L10N; use base 'Locale::Maketext'; + use LangFile; sub fallback_languages { ('en') }; # used for the language switch interface, language tags must # be the same as in the languages hash in global.pl - sub languages { ('en', 'ru') } + sub languages { qw{ cs en hu ru } } sub maketext { my $r = eval { shift->SUPER::maketext(@_) }; @@ -21,59 +22,29 @@ use warnings; # can be called as either a subroutine or a method sub loadfile { - my %lang = ( - en => \%VNDB::L10N::en::Lexicon, - ru => \%VNDB::L10N::ru::Lexicon, - ); - - open my $F, '<:utf8', $VNDB::ROOT.'/data/lang.txt' or die "Opening language file: $!\n"; - my($empty, $line, $key, $lang) = (0, 0); - while(<$F>) { - chomp; - $line++; - - # ignore intro - if(!defined $key) { - $key = 0 if /^\/intro$/; - next; + my %lang = do { + no strict 'refs'; + map +($_, \%{"VNDB::L10N::${_}::Lexicon"}), languages + }; + my $r = LangFile->new(read => "$VNDB::ROOT/data/lang.txt"); + my $key; + while(my $l = $r->read) { + my($t, @l) = @$l; + $key = $l[0] if $t eq 'key'; + if($t eq 'tl') { + my($lang, undef, $text) = @l; + next if !$text; + die "Unknown language \"$l->[1]\"\n" if !$lang{$lang}; + die "Unknown key for translation \"$lang: $text\"\n" if !$key; + $lang{$lang}{$key} = $text; } - # ignore comments - next if /^#/; - # key - if(/^:(.+)$/) { - $key = $1; - $lang = undef; - $empty = 0; - next; - } - # locale string - if(/^([a-z_-]{2,7})[ *]: (.+)$/) { - $lang = $1; - die "Unknown language on #$line: $lang\n" if !$lang{$lang}; - die "Unknown key for locale on #$line\n" if !$key; - $lang{$lang}{$key} = $2; - $empty = 0; - next; - } - # multi-line locale string - if($lang && /^\s+([^\s].*)$/) { - $lang{$lang}{$key} .= ''.("\n"x$empty)."\n$1"; - $empty = 0; - next; - } - # empty string (count them in case they're part of a multi-line locale string) - if(/^\s*$/) { - $empty++; - next; - } - # something we didn't expect - die "Don't know what to do with line $line\n" unless /^([a-z_-]{2,7})[ *]:/; } - close $F; + $r->close; } } + { package VNDB::L10N::en; use base 'VNDB::L10N'; @@ -88,8 +59,9 @@ use warnings; # Argument: unix timestamp # Returns: age sub age { - my $a = time-$_[1]; - return sprintf '%d %s ago', + my($self, $time) = @_; + my $a = time-$time; + my @f = $a > 60*60*24*365*2 ? ( $a/60/60/24/365, 'years' ) : $a > 60*60*24*(365/12)*2 ? ( $a/60/60/24/(365/12), 'months' ) : $a > 60*60*24*7*2 ? ( $a/60/60/24/7, 'weeks' ) : @@ -97,6 +69,7 @@ use warnings; $a > 60*60*2 ? ( $a/60/60, 'hours' ) : $a > 60*2 ? ( $a/60, 'min' ) : ( $a, 'sec' ); + return $self->maketext("_age_$f[1]", int $f[0]); } # argument: unix timestamp and optional format (compact/full) @@ -155,6 +128,30 @@ use warnings; { + package VNDB::L10N::cs; + use base 'VNDB::L10N::en'; + our %Lexicon; + + sub quant { + my($self, $num, $single, $couple, $lots) = @_; + return $lots if ($num % 100) >= 11 && ($num % 100) <= 14; + return $single if ($num % 10) == 1; + return $couple if ($num % 10) >= 2 && ($num % 10) <= 4; + return $lots; + } +} + + + +{ + package VNDB::L10N::hu; + use base 'VNDB::L10N::en'; + our %Lexicon; +} + + + +{ package VNDB::L10N::ru; use base 'VNDB::L10N::en'; our %Lexicon; @@ -165,25 +162,9 @@ use warnings; return $couple if ($num % 10) >= 2 && ($num % 10) <= 4 && !(($num % 100) >= 12 && ($num % 100) <= 14); return $lots; } - - sub age { - my $self = shift; - my $a = time-shift; - use utf8; - my @l = ( - $a > 60*60*24*365*2 ? ( $a/60/60/24/365, 'год', 'года', 'лет' ) : - $a > 60*60*24*(365/12)*2 ? ( $a/60/60/24/(365/12), 'месяц', 'месяца', 'месяцев' ) : - $a > 60*60*24*7*2 ? ( $a/60/60/24/7, 'неделя', 'недели', 'недель' ) : - $a > 60*60*24*2 ? ( $a/60/60/24, 'день', 'дня', 'дней' ) : - $a > 60*60*2 ? ( $a/60/60, 'час', 'часа', 'часов' ) : - $a > 60*2 ? ( $a/60, 'минута', 'минуты', 'минут' ) : - ( $a, 'секунда', 'секунды', 'секунд' ) - ); - return sprintf '%d %s назад', $l[0], $self->quant(@l); - } - } + 1; diff --git a/lib/VNDB/Plugin/TransAdmin.pm b/lib/VNDB/Plugin/TransAdmin.pm new file mode 100644 index 00000000..ed5ebb36 --- /dev/null +++ b/lib/VNDB/Plugin/TransAdmin.pm @@ -0,0 +1,349 @@ +# This plugin provides a quick and dirty user interface to editing lang.txt, +# to use it, add the following to your data/config.pl: +# +# if($INC{"YAWF.pm"}) { +# require VNDB::Plugin::TransAdmin; +# $VNDB::S{transadmin} = { +# <userid> => 'all' || <language> || <arrayref with languages> +# }; +# } +# +# And then open /tladmin in your browser. +# Also make sure data/lang.txt and data/docs/* are writable by the httpd process. +# English is considered the 'main' language, and cannot be edited using this interface. + +package VNDB::Plugin::TransAdmin; + +use strict; +use warnings; +use YAWF ':html'; +use LangFile; +use VNDB::Func; + + +my $langfile = "$VNDB::ROOT/data/lang.txt"; + + +YAWF::register( + qr{tladmin(?:/([a-z]+))?} => \&tladmin +); + + +sub uri_escape { + local $_ = shift; + s/ /%20/g; + s/\?/%3F/g; + s/;/%3B/g; + s/&/%26/g; + return $_; +} + + +sub _allowed { + my($self, $lang) = @_; + my $a = $self->{transadmin}{ $self->authInfo->{id} }; + return $a eq 'all' || $a eq $lang || ref($a) eq 'ARRAY' && grep $_ eq $lang, @$a; +} + + +sub tladmin { + my($self, $lang) = @_; + + $lang ||= ''; + my $intro = $lang =~ s/intro//; + return 404 if $lang && ($lang eq 'en' || !grep $_ eq $lang, $self->{l10n}->languages); + my $sect = $self->reqParam('sect')||''; + my $doc = $self->reqParam('doc')||''; + + my $uid = $self->authInfo->{id}; + return $self->htmlDenied if !$uid || !$self->{transadmin}{$uid}; + + if(!-w $langfile || !-w "$VNDB::ROOT/data/docs" || grep /\.[a-z]{2}$/ && !-w $_, glob "$VNDB::ROOT/data/docs/*") { + $self->htmlHeader(title => 'Language file not writable', noindex => 1); + div class => 'mainbox'; + h1 'Language file not writable'; + div class => 'warning', 'Sorry, I do not have enough permission to write to the language files.'; + end; + $self->htmlFooter; + return; + } + + _savelang($self, $lang) if $lang && $sect && $self->reqMethod eq 'POST' && _allowed($self, $lang); + _savedoc($self, $lang, $doc) if $lang && $doc && $self->reqMethod eq 'POST' && _allowed($self, $lang); + my($sects, $page) = _readlang($lang, $sect) if $lang; + + $self->htmlHeader(title => 'Quick-and-dirty Translation Editor', noindex => 1); + div class => 'mainbox'; + a class => 'addnew', href => '/tladmin/intro', 'README'; + h1 'Quick-and-dirty Translation Editor'; + h2 class => 'alttitle', 'Step #1: Choose a language'; + p class => 'browseopts'; + a $lang eq $_ ? (class => 'optselected') : (), href => "/tladmin/$_", mt "_lang_$_" + for grep !/en/, $self->{l10n}->languages; + end; + _sections($self, $lang, $sect, $sects) if $lang; + _docs($lang, $doc) if $lang; + end; + + _intro() if $intro; + _section($self, $lang, $sect, $page) if $lang && $sect; + _doc($self, $lang, $doc) if $lang && $doc; + + $self->htmlFooter; +} + + +sub _savelang { + my($self, $lang) = @_; + + # do everything in-memory, so we don't need write access to a temporary file + # (this has the downside that in the event something goes wrong, everything will be wiped) + my $f = LangFile->new(read => $langfile); + my @read; + push @read, $_ while (local $_ = $f->read); + $f->close; + + my @keys = $self->reqParam; + $f = LangFile->new(write => $langfile); + my $key; + for my $l (@read) { + $key = $l->[1] if $l->[0] eq 'key'; + if($l->[0] eq 'tl' && $l->[1] eq $lang && grep $key eq $_, @keys) { + $l->[2] = !$self->reqParam("check$key"); + $l->[3] = $self->reqParam($key); + $l->[3] =~ s/\r?\n/\n/g; + $l->[3] =~ s/\s+$//g; + } + $f->write(@$l); + } + $f->close; + + # re-read the file and regenerate the JS in case we're not running as CGI + if($INC{"FCGI.pm"}) { + VNDB::L10N::loadfile(); + VNDB::checkjs(); + } +} + + +sub _readlang { + my($lang, $sect) = @_; + my @sect; # [ title, count, unsync ] + my @page; # [ 'comment'||'line', <comment>|| ( <key>, <en>, <sync>, <tl> ) ] + + my $f = LangFile->new(read => $langfile); + my($key, $insect); + while(my $l = $f->read) { + my $t = shift @$l; + + if($t eq 'space') { + if(join("\n", @$l) =~ /((#{30,90}\n)## +(.+) +##\n\2.+)^/ms) { + my $header = $1; + (my $title = $3) =~ s/\s+$//; + $title =~ s/\s+\([^)]+\)$//; + push @sect, [ $title, 0, 0 ]; + $insect = $title eq $sect; + push @page, [ 'comment', $header ] if $insect; + } elsif($insect) { + push @page, [ 'comment', join "\n", @$l ]; + } + } + + $sect[$#sect][1]++ if $t eq 'key'; + $sect[$#sect][2]++ if $t eq 'tl' && $l->[0] eq $lang && !$l->[1]; + + next if !$insect; + push @page, [ 'line', $l->[0] ] if $t eq 'key'; + $page[$#page][2] = $l->[2] if $t eq 'tl' && $l->[0] eq 'en'; + if($t eq 'tl' && $l->[0] eq $lang) { + $page[$#page][3] = $l->[1]; + $page[$#page][4] = $l->[2]; + } + } + $f->close; + return (\@sect, \@page); +} + + +sub _intro { + my $f = LangFile->new(read => $langfile); + my $intro = $f->read; + $intro = join "\n", @$intro[1..$#$intro]; + $f->close; + div class => 'mainbox'; + h1 'Introduction to the language file'; + pre $intro; + end; +} + + +sub _sections { + my($self, $lang, $sect, $list) = @_; + + br; + h2 class => 'alttitle', 'Step #2: Choose a section'; + div style => 'margin: 0 40px'; + for (@$list) { + div style => 'float: left; width: 200px;'; + a href => "/tladmin/$lang?sect=".uri_escape($_->[0]), $_->[0] if $sect ne $_->[0]; + txt $sect if $sect eq $_->[0]; + txt " "; + txt "0/$_->[1]" if !$_->[2]; + b class => 'standout', "$_->[2]/$_->[1]" if $_->[2]; + end; + } + clearfloat; + end; + br; + br; +} + + +sub _section { + my($self, $lang, $sect, $page) = @_; + + form action => "/tladmin/$lang?sect=".uri_escape($sect), method => 'POST', 'accept-charset' => 'utf-8'; + div class => 'mainbox'; + h1 $sect; + + if(_allowed($self, $lang)) { + h2 class => 'alttitle', "Don't forget to hit the 'save' button to make your changes permament!"; + } else { + div class => 'warning'; + h2 'Read-only'; + p "You can't edit this language."; + end; + } + + for my $l (@$page) { + if($l->[0] eq 'comment') { + pre; + b class => 'grayedout', $l->[1]."\n"; + end; + next; + } + + my(undef, $key, $en, $sync, $tl) = @$l; + b class => $sync ? 'grayedout' : 'standout', ":$key"; + br; + div style => 'margin-left: 25px; font: 12px Tahoma; width: 700px; overflow-x: auto; white-space: nowrap', $en; + my $multi = $en =~ y/\n//; + + div style => 'width: 23px; float: left; text-align: right'; + input type => 'checkbox', name => "check$key", id => "check$key", !$sync ? (checked => 'checked') : (); + end; + div style => 'float: left'; + if($multi) { + $tl =~ s/&/&/g; + $tl =~ s/</</g; + $tl =~ s/>/>/g; + textarea name => $key, id => $key, rows => $multi+2, style => 'width: 700px; height: auto; white-space: nowrap; border: none', wrap => 'off'; + lit $tl; + end; + } else { + input type => 'text', class => 'text', name => $key, id => $key, value => $tl, style => 'width: 700px; border: none'; + } + end; + clearfloat; + } + if(_allowed($self, $lang)) { + br;br; + fieldset class => 'submit'; + input type => 'submit', value => 'Save', class => 'submit'; + end; + } + end; + end; +} + + +sub _savedoc { + my($self, $lang, $doc) = @_; + + my $file = "$VNDB::ROOT/data/docs/$doc.$lang"; + + open my $f, '<:utf8', "$VNDB::ROOT/data/docs/$doc" or die $!; + my $en = join '', <$f>; + close $f; + + my $tl = $self->reqParam('tl'); + $tl =~ s/\r?\n/\n/g; + + return -e $file && unlink $file if $tl eq $en; + + open $f, '>:utf8', $file or die $!; + print $f $tl; + close $f; + chmod 0666, $file; +} + + +sub _docs { + my($lang, $doc) = @_; + + my @d = map /\.[a-z]{2}$/ || /\/8$/ ? () : s{^.+\/([^/]+)$}{$1} && $_, glob "$VNDB::ROOT/data/docs/*"; + + h2 class => 'alttitle', '...or a doc page'; + div style => 'margin: 0 40px'; + for (sort { $a =~ /^\d+$/ && $b =~ /^\d+$/ ? $a <=> $b : $a cmp $b } @d) { + div style => 'float: left; width: 60px;'; + a href => "/tladmin/$lang?doc=$_", $_ if $_ ne $doc; + txt $_ if $_ eq $doc; + end; + } + clearfloat; + end; +} + + +sub _doc { + my($self, $lang, $doc) = @_; + + open my $f, '<:utf8', "$VNDB::ROOT/data/docs/$doc" or die $!; + my $en = join '', <$f>; + close $f; + + my $tl = $en; + if(open $f, '<:utf8', "$VNDB::ROOT/data/docs/$doc.$lang") { + $tl = join '', <$f>; + close $f; + } + $tl =~ s/&/&/g; + $tl =~ s/</</g; + $tl =~ s/>/>/g; + + + form action => "/tladmin/$lang?doc=$doc", method => 'POST', 'accept-charset' => 'utf-8'; + div class => 'mainbox'; + a class => 'addnew', href => "/d$doc", "View current page" if $doc =~ /^\d+$/; + h1 "Translating page $doc"; + h2 class => 'alttitle', 'Left = English, Right = translation'; + + if(!_allowed($self, $lang)) { + div class => 'warning'; + h2 'Read-only'; + p "You can't edit this language."; + end; + } + + div style => 'width: 48%; margin-right: 10px; overflow-y: auto; float: left'; + pre style => 'font: 12px Tahoma', $en; + end; + textarea name => 'tl', id => 'tl', rows => ($en =~ y/\n//), + style => 'border: none; float: left; width: 49%; white-space: nowrap', wrap => 'off'; + lit $tl; + end; + clearfloat; + if(_allowed($self, $lang)) { + br; + fieldset class => 'submit'; + input type => 'submit', value => 'Save', class => 'submit'; + end; + } + end; + end; +} + + +1; + diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm index c4daffd9..a3bf7c29 100644 --- a/lib/VNDB/Util/Auth.pm +++ b/lib/VNDB/Util/Auth.pm @@ -26,7 +26,7 @@ sub authInit { my $token = substr($cookie, 0, 40); my $uid = substr($cookie, 40); return _rmcookie($self) if $uid !~ /^\d+$/ || !$self->dbSessionCheck($uid, $token); - $self->{_auth} = $self->dbUserGet(uid => $uid, what => 'mymessages')->[0]; + $self->{_auth} = $self->dbUserGet(uid => $uid, what => 'extended')->[0]; } @@ -95,7 +95,7 @@ sub _authCheck { return 0 if !$user || length($user) > 15 || length($user) < 2 || !$pass; - my $d = $self->dbUserGet(username => $user, what => 'mymessages')->[0]; + my $d = $self->dbUserGet(username => $user, what => 'extended')->[0]; return 0 if !defined $d->{id} || !$d->{rank}; if(_authEncryptPass($self, $pass, $d->{salt}) eq $d->{passwd}) { diff --git a/lib/VNDB/Util/CommonHTML.pm b/lib/VNDB/Util/CommonHTML.pm index b1eb6432..ad99d32d 100644 --- a/lib/VNDB/Util/CommonHTML.pm +++ b/lib/VNDB/Util/CommonHTML.pm @@ -12,7 +12,7 @@ use POSIX 'ceil'; our @EXPORT = qw| htmlMainTabs htmlDenied htmlHiddenMessage htmlBrowse htmlBrowseNavigate - htmlRevision htmlEditMessage htmlItemMessage htmlVoteStats htmlHistory htmlSearchBox + htmlRevision htmlEditMessage htmlItemMessage htmlVoteStats htmlHistory htmlSearchBox htmlRGHeader |; @@ -101,7 +101,7 @@ sub htmlMainTabs { end; } - if($type eq 'v' && $obj->{rgraph}) { + if($type =~ /[vp]/ && $obj->{rgraph}) { li $sel eq 'rg' ? (class => 'tabselected') : (); a href => "/$id/rg", mt '_mtabs_relations'; end; @@ -501,16 +501,15 @@ sub htmlHistory { sub { td colspan => 2, class => 'tc1', mt '_hist_col_rev' }, [ mt '_hist_col_date' ], [ mt '_hist_col_user' ], - sub { td; a href => '#', id => 'history_comments', 'expand'; txt mt '_hist_col_page'; end; } + sub { td; a href => '#', id => 'expandlist', mt '_js_expand'; txt mt '_hist_col_page'; end; } ], row => sub { my($s, $n, $i) = @_; - my $tc = [qw|v r p|]->[$i->{type}]; - my $revurl = "/$tc$i->{iid}.$i->{rev}"; + my $revurl = "/$i->{type}$i->{iid}.$i->{rev}"; Tr $n % 2 ? ( class => 'odd' ) : (); td class => 'tc1_1'; - a href => $revurl, "$tc$i->{iid}"; + a href => $revurl, "$i->{type}$i->{iid}"; end; td class => 'tc1_2'; a href => $revurl, ".$i->{rev}"; @@ -524,7 +523,7 @@ sub htmlHistory { end; end; if($i->{comments}) { - Tr class => $n % 2 ? 'editsum odd hidden' : 'editsum hidden'; + Tr class => $n % 2 ? 'collapse msgsum odd hidden' : 'collapse msgsum hidden'; td colspan => 5; lit bb2html $i->{comments}, 150; end; @@ -538,13 +537,20 @@ sub htmlHistory { sub htmlSearchBox { my($self, $sel, $v) = @_; + # escape search query for use as a query string value + (my $q = $v||'') =~ s/&/%26/g; + $q =~ s/\?/%3F/g; + $q =~ s/;/%3B/g; + $q =~ s/ /%20/g; + $q = "?q=$q" if $q; + fieldset class => 'search'; p class => 'searchtabs'; - a href => '/v/all', $sel eq 'v' ? (class => 'sel') : (), mt '_searchbox_vn'; - a href => '/r', $sel eq 'r' ? (class => 'sel') : (), mt '_searchbox_releases'; - a href => '/p/all', $sel eq 'p' ? (class => 'sel') : (), mt '_searchbox_producers'; - a href => '/g', $sel eq 'g' ? (class => 'sel') : (), mt '_searchbox_tags'; - a href => '/u/all', $sel eq 'u' ? (class => 'sel') : (), mt '_searchbox_users'; + a href => "/v/all$q", $sel eq 'v' ? (class => 'sel') : (), mt '_searchbox_vn'; + a href => "/r$q", $sel eq 'r' ? (class => 'sel') : (), mt '_searchbox_releases'; + a href => "/p/all$q", $sel eq 'p' ? (class => 'sel') : (), mt '_searchbox_producers'; + a href => '/g'.($q?"/list$q":''), $sel eq 'g' ? (class => 'sel') : (), mt '_searchbox_tags'; + a href => "/u/all$q", $sel eq 'u' ? (class => 'sel') : (), mt '_searchbox_users'; end; input type => 'text', name => 'q', id => 'q', class => 'text', value => $v; input type => 'submit', class => 'submit', value => mt '_searchbox_submit'; @@ -552,5 +558,41 @@ sub htmlSearchBox { } +sub htmlRGHeader { + my($self, $title, $type, $obj) = @_; + + if(($self->reqHeader('Accept')||'') !~ /application\/xhtml\+xml/) { + $self->htmlHeader(title => $title); + $self->htmlMainTabs($type, $obj, 'rg'); + div class => 'mainbox'; + h1 $title; + div class => 'warning'; + h2 mt '_rg_notsupp'; + p mt '_rg_notsupp_msg'; + end; + end; + $self->htmlFooter; + return 1; + } + $self->resHeader('Content-Type' => 'application/xhtml+xml; charset=UTF-8'); + + # This is a REALLY ugly hack, need find a proper solution in YAWF + no warnings 'redefine'; + my $sub = \&YAWF::XML::html; + *YAWF::XML::html = sub () { + lit q|<!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" + "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">|; + tag 'html', + xmlns => "http://www.w3.org/1999/xhtml", + 'xmlns:svg' => 'http://www.w3.org/2000/svg', + 'xmlns:xlink' => 'http://www.w3.org/1999/xlink'; + }; + $self->htmlHeader(title => $title); + *YAWF::XML::html = $sub; + $self->htmlMainTabs($type, $obj, 'rg'); + return 0; +} + 1; diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm index 85971ba9..084b9a4e 100644 --- a/lib/VNDB/Util/LayoutHTML.pm +++ b/lib/VNDB/Util/LayoutHTML.pm @@ -10,7 +10,7 @@ use VNDB::Func; our @EXPORT = qw|htmlHeader htmlFooter|; -sub htmlHeader { # %options->{ title, js, noindex, search } +sub htmlHeader { # %options->{ title, noindex, search } my($self, %o) = @_; my $skin = $self->reqParam('skin') || $self->authInfo->{skin} || $self->{skin_default}; $skin = $self->{skin_default} if !$self->{skins}{$skin} || !-d "$VNDB::ROOT/static/s/$skin"; @@ -22,12 +22,6 @@ sub htmlHeader { # %options->{ title, js, noindex, search } 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($o{js}) { - script type => 'text/javascript', src => $self->{url_static}.'/f/forms.js?'.$self->{version}; end; - } - script type => 'text/javascript', src => $self->{url_static}.'/f/script.js?'.$self->{version}; - # most browsers don't like a self-closing <script> tag... - end; if($self->authInfo->{customcss}) { (my $css = $self->authInfo->{customcss}) =~ s/\n/ /g; style type => 'text/css', $css; @@ -55,12 +49,8 @@ sub _menu { div class => 'menubox'; h2; - span; - for (grep $self->{l10n}->language_tag() ne $_, $self->{l10n}->languages()) { - a href => "?l10n=$_"; - cssicon "lang $_", mt "_lang_$_"; # NOTE: should actually be in the destination language... - end; - } + a href => "#", id => 'lang_select'; + cssicon "lang ".$self->{l10n}->language_tag(), mt "_lang_".$self->{l10n}->language_tag(); end; txt mt '_menu'; end; @@ -89,6 +79,7 @@ sub _menu { div class => 'menubox'; if($self->authInfo->{id}) { + my $msg = $self->dbUserMessageCount($self->authInfo->{id}); my $uid = sprintf '/u%d', $self->authInfo->{id}; h2; a href => $uid, ucfirst $self->authInfo->{username}; @@ -99,7 +90,7 @@ sub _menu { a href => "$uid/edit", mt '_menu_myprofile'; br; a href => "$uid/list", mt '_menu_myvnlist'; br; a href => "$uid/wish", mt '_menu_mywishlist'; br; - a href => "/t$uid", mt '_menu_mymessages', $self->authInfo->{mymessages}; br; + a href => "/t$uid", $msg ? (class => 'standout') : (), mt '_menu_mymessages', $msg; br; a href => "$uid/hist", mt '_menu_mychanges'; br; a href => "$uid/tags", mt '_menu_mytags'; br; br; @@ -163,6 +154,7 @@ sub htmlFooter { a href => $self->{source_url}, mt '_footer_source'; end; end; # /div maincontent + script type => 'text/javascript', src => $self->{url_static}.'/f/script.js?'.$self->{version}, ''; end; # /body end; # /html diff --git a/static/f/forms.js b/static/f/forms.js deleted file mode 100644 index ce9daab5..00000000 --- a/static/f/forms.js +++ /dev/null @@ -1,955 +0,0 @@ -// various form functions -// called by script.js - -function qq(v) { - return v.replace(/&/g,"&").replace(/</,"<").replace(/>/,">").replace(/"/g,'"'); -} -function shorten(v, l) { - return qq(v.length > l ? v.substr(0, l-3)+'...' : v); -} - - - - - - - /***********************************\ - * D R O P D O W N S E A R C H * - \***********************************/ - - -function dsInit(obj, url, trfunc, serfunc, retfunc, parfunc) { - obj.setAttribute('autocomplete', 'off'); - obj.onkeydown = dsKeyDown; - obj.onblur = function() { - // timeout to make sure the tr.onclick event is called before we've hidden the object - setTimeout(function () { - if(x('ds_box')) - x('ds_box').style.top = '-500px'; - }, 500) - }; - // all local data is stored in the DOM input object - obj.returnFunc = retfunc; - obj.trFunc = trfunc; - obj.serFunc = serfunc; - obj.parFunc = parfunc; - obj.searchURL = url; - obj.selectedId = 0; -} - -function dsKeyDown(ev) { - var c = document.layers ? ev.which : document.all ? event.keyCode : ev.keyCode; - var obj = this; - - if(c == 9) // tab - return true; - - // do some processing when the enter key has been pressed - if(c == 13) { - var o = obj; - while(o && o.nodeName.toLowerCase() != 'form') - o = o.parentNode; - if(o) { - var oldsubmit = o.onsubmit; - o.onsubmit = function() { return false }; - setTimeout(function() { o.onsubmit = oldsubmit }, 100); - } - - if(obj.selectedId != 0) - obj.value = obj.serFunc(x('ds_box_'+obj.selectedId).itemData, obj); - if(obj.returnFunc) - obj.returnFunc(); - if(x('ds_box')) - x('ds_box').style.top = '-500px'; - obj.selectedId = 0; - - return false; - } - - // process up/down keys - if(x('ds_box') && (c == 38 || c == 40)) { - var l = x('ds_box').getElementsByTagName('tr'); - if(l.length < 1) - return true; - - if(obj.selectedId == 0) { - if(c == 38) // up - obj.selectedId = l[l.length-1].id.substr(7); - else - obj.selectedId = l[0].id.substr(7); - } else { - var sel = null; - for(var i=0;i<l.length;i++) - if(l[i].id == 'ds_box_'+obj.selectedId) { - if(c == 38) // up - sel = i>0 ? l[i-1] : l[l.length-1]; - else - sel = l[i+1] ? l[i+1] : l[0]; - } - obj.selectedId = sel.id.substr(7); - } - - for(var i=0;i<l.length;i++) - l[i].className = l[i].id == 'ds_box_'+obj.selectedId ? 'selected' : ''; - return true; - } - - // this.value isn't available in a keydown event - setTimeout(function() { - dsSearch(obj); - }, 10); - - return true; -} - -function dsSearch(obj) { - var b = x('ds_box'); - var v = obj.parFunc ? obj.parFunc(obj.value) : obj.value; - - // show/hide the ds_box div - if(v.length < 2) { - if(b) { - b.style.top = '-500px'; - b.innerHTML = '<b>Loading...</b>'; - } - obj.selectedId = 0; - return; - } - if(!b) { - b = document.createElement('div'); - b.setAttribute('id', 'ds_box'); - b.innerHTML = '<b>Loading...</b>'; - document.body.appendChild(b); - } - - // position the div - var ddx=0; - var ddy=obj.offsetHeight; - var o = obj; - do { - ddx += o.offsetLeft; - ddy += o.offsetTop; - } while(o = o.offsetParent); - - b.style.position = 'absolute'; - b.style.left = ddx+'px'; - b.style.top = ddy+'px'; - b.style.width = obj.offsetWidth+'px'; - - // perform search - ajax(obj.searchURL + encodeURIComponent(v), function(hr) { - dsResults(hr, obj); - }); -} - -function dsResults(hr, obj) { - var l = hr.responseXML.getElementsByTagName('item'); - var b = x('ds_box'); - if(l.length < 1) { - b.innerHTML = '<b>No results...</b>'; - obj.selectedId = 0; - return; - } - - b.innerHTML = '<table><tbody></tbody></table>'; - tb = b.getElementsByTagName('tbody')[0]; - for(var i=0;i<l.length;i++) { - var id = l[i].getAttribute('id'); - var tr = document.createElement('tr'); - tr.setAttribute('id', 'ds_box_'+id); - tr.itemData = l[i]; - if(obj.selectedId == id) - tr.setAttribute('class', 'selected'); - tr.onmouseover = function() { - obj.selectedId = this.id.substr(7); - var l = x('ds_box').getElementsByTagName('tr'); - for(var i=0;i<l.length;i++) - l[i].className = l[i].id == 'ds_box_'+obj.selectedId ? 'selected' : ''; - }; - tr.onclick = function() { - obj.value = obj.serFunc(this.itemData, obj); - if(obj.returnFunc) - obj.returnFunc(); - if(x('ds_box')) - x('ds_box').style.top = '-500px'; - obj.selectedId = 0; - }; - obj.trFunc(l[i], tr); - tb.appendChild(tr); - } - - if(obj.selectedId != 0 && !x('ds_box_'+obj.selectedId)) - obj.selectedId = 0; -} - - - - - - - /*****************************\ - * V N R E L A T I O N S * - \*****************************/ - - -var relTypes = []; -function relLoad() { - var i;var l;var o; - - // fetch the relation types from the add new relation selectbox - l = x('relation_new').getElementsByTagName('select')[0].options; - for(i=0;i<l.length;i++) - relTypes[Math.floor(l[i].value)] = l[i].text; - - // read the current relations - l = x('relations').value.split('|||'); - if(l[0]) { - for(i=0;i<l.length;i++) { - var rel = l[i].split(',', 3); - relAdd(rel[0], rel[1], rel[2]); - } - } - relEmpty(); - - // make sure the title is up-to-date - x('title').onchange = function() { - l = x('jt_box_vn_rel').getElementsByTagName('td'); - for(i=0;i<l.length;i++) - if(l[i].className == 'tc3') - l[i].innerHTML = shorten(this.value, 40); - }; - - // bind the add-link - x('relation_new').getElementsByTagName('a')[0].onclick = relFormAdd; - - // dropdown - dsInit(x('relation_new').getElementsByTagName('input')[0], '/xml/vn.xml?q=', function(item, tr) { - var td = document.createElement('td'); - td.innerHTML = 'v'+item.getAttribute('id'); - td.style.textAlign = 'right'; - td.style.paddingRight = '5px'; - tr.appendChild(td); - td = document.createElement('td'); - td.innerHTML = shorten(item.firstChild.nodeValue, 40); - tr.appendChild(td); - }, function(item) { - return 'v'+item.getAttribute('id')+':'+item.firstChild.nodeValue; - }, relFormAdd); -} - -function relAdd(rel, vid, title) { - var o = document.createElement('tr'); - o.setAttribute('id', 'relation_tr_'+vid); - - var t = document.createElement('td'); - t.className = 'tc1'; - t.innerHTML = 'v'+vid+':<a href="/v'+vid+'">'+shorten(title, 40)+'</a>'; - o.appendChild(t); - - var options = ''; - for(var i=0;i<relTypes.length;i++) - options += '<option value="'+i+'"'+(i == rel ? ' selected="selected"' : '')+'>'+qq(relTypes[i])+'</option>'; - t = document.createElement('td'); - t.className = 'tc2'; - t.innerHTML = 'is a <select onchange="relSerialize()">'+options+'</select> of'; - o.appendChild(t); - - t = document.createElement('td'); - t.className = 'tc3'; - t.innerHTML = shorten(x('title').value, 40); - o.appendChild(t); - - t = document.createElement('td'); - t.className = 'tc4'; - t.innerHTML = '<a href="#" onclick="return relDel('+vid+')">del</a>'; - o.appendChild(t); - - x('relation_tbl').appendChild(o); - relEmpty(); -} - -function relEmpty() { - if(x('relation_tbl').getElementsByTagName('tr').length > 0) { - if(x('relation_tr_none')) - x('relation_tbl').removeChild(x('relation_tr_none')); - return; - } - var o = document.createElement('tr'); - o.setAttribute('id', 'relation_tr_none'); - var t = document.createElement('td'); - t.colspan = 4; - t.innerHTML = 'No relations selected.'; - o.appendChild(t); - x('relation_tbl').appendChild(o); -} - -function relSerialize() { - var r=''; - var i; - var l = x('relation_tbl').getElementsByTagName('tr'); - for(i=0;i<l.length;i++) { - var title = l[i].getElementsByTagName('td')[0]; - title = title.innerText || title.textContent; - title = title.substr(title.indexOf(':')+1); - r += (r ? '|||' : '') - +l[i].getElementsByTagName('select')[0].selectedIndex - +','+l[i].id.substr(12)+','+title; - } - x('relations').value = r; -} - -function relDel(vid) { - x('relation_tbl').removeChild(x('relation_tr_'+vid)); - relSerialize(); - relEmpty(); - return false; -} - -function relFormAdd() { - var txt = x('relation_new').getElementsByTagName('input')[0]; - var sel = x('relation_new').getElementsByTagName('select')[0]; - var lnk = x('relation_new').getElementsByTagName('a')[0]; - var input = txt.value; - - if(!input.match(/^v[0-9]+/)) { - alert('Visual novel textbox must start with an ID (e.g. v17)'); - return false; - } - - txt.disabled = true; - txt.value = 'loading...'; - sel.disabled = true; - lnk.innerHTML = 'loading...'; - - ajax('/xml/vn.xml?q='+encodeURIComponent(input), function(hr) { - txt.disabled = false; - txt.value = ''; - sel.disabled = false; - lnk.innerHTML = 'add'; - - var items = hr.responseXML.getElementsByTagName('item'); - if(items.length < 1) - return alert('Visual novel not found!'); - - var id = items[0].getAttribute('id'); - if(x('relation_tr_'+id)) - return alert('This visual novel has already been selected!'); - - relAdd(sel.selectedIndex, id, items[0].firstChild.nodeValue); - sel.selectedIndex = 0; - relSerialize(); - }); - return false; -} - - - - - - - /*********************************\ - * V N S C R E E N S H O T S * - \*********************************/ - - -var scrRel = [ [ 0, '-- select release --' ] ]; -var scrStaticURL; -function scrLoad() { - // load the releases - scrStaticURL = x('scr_rel').className; - var l = x('scr_rel').options; - for(var i=0;i<l.length;i++) - scrRel[i+1] = [ l[i].value, l[i].text ]; - x('scr_rel').parentNode.removeChild(x('scr_rel')); - - // load the current screenshots - l = x('screenshots').value.split(' '); - for(i=0;i<l.length;i++) - if(l[i].length > 2) { - var r = l[i].split(','); - scrAdd(r[0], r[1], r[2]); - } - scrLast(); - scrCheckStatus(); - - scrSetSubmit(); -} - -// give an error when submitting the form while still uploading an image -function scrSetSubmit() { - var o=x('screenshots'); - while(o.nodeName.toLowerCase() != 'form') - o = o.parentNode; - oldfunc = o.onsubmit; - o.onsubmit = function() { - var c=0;var r=0; - var l = x('scr_table').getElementsByTagName('tr'); - for(var i=0;i<l.length-1;i++) { - if(l[i].scrStatus > 0) - c=1; - else if(l[i].getElementsByTagName('select')[0].selectedIndex == 0) - r=1; - } - if(c) { - alert('Please wait for the screenshots to be uploaded before submitting the form.'); - return false; - } else if(r) { - alert('Please select the appropriate release for every screenshot'); - return false; - } else if(oldfunc) - return oldfunc(); - }; -} - - -function scrURL(id, t) { - return scrStaticURL+'/s'+t+'/'+(id%100<10?'0':'')+(id%100)+'/'+id+'.jpg'; -} - -function scrAdd(id, nsfw, rel) { - var tr = document.createElement('tr'); - tr.scrId = id; - tr.scrStatus = id ? 2 : 1; // 0: done, 1: uploading, 2: waiting for thumbnail - tr.scrRel = rel; - tr.scrNSFW = nsfw; - - var td = document.createElement('td'); - td.className = 'thumb'; - td.innerHTML = 'loading...'; - tr.appendChild(td); - - td = document.createElement('td'); - if(id) - td.innerHTML = '<b>Generating thumbnail...</b><br />' - +'Note: if this takes longer than 30 seconds, there\'s probably something wrong on our side.' - +'Please try again later or report a bug if that is the case.'; - else - td.innerHTML = '<b>Uploading screenshot...</b><br />' - +'This can take a while, depending on the file size and your upload speed.<br />' - +'<a href="#" onclick="return scrDel(this)">cancel</a>'; - tr.appendChild(td); - - x('scr_table').appendChild(tr); - scrStripe(); - return tr; -} - -function scrLast() { - if(x('scr_last')) - x('scr_table').removeChild(x('scr_last')); - var full = x('scr_table').getElementsByTagName('tr').length >= 10; - - var tr = document.createElement('tr'); - tr.setAttribute('id', 'scr_last'); - - var td = document.createElement('td'); - td.className = 'thumb'; - tr.appendChild(td); - - var td = document.createElement('td'); - if(full) - td.innerHTML = '<b>Enough screenshots</b><br />' - +'The limit of 10 screenshots per visual novel has been reached. ' - +'If you want to add a new screenshot, please remove an existing one first.'; - else - td.innerHTML = '<b>Add screenshot</b><br />' - +'Image must be smaller than 5MB and in PNG or JPEG format.<br />' - +'<input name="scr_upload" id="scr_upload" type="file" class="text" /><br />' - +'<input type="button" value="Upload!" class="submit" onclick="scrUpload()" />'; - - tr.appendChild(td); - x('scr_table').appendChild(tr); - scrStripe(); -} - -function scrStripe() { - var l = x('scr_table').getElementsByTagName('tr'); - for(var i=0;i<l.length;i++) - l[i].className = i%2==0 ? 'odd' : ''; -} - -function scrCheckStatus() { - var ids = ''; - var l = x('scr_table').getElementsByTagName('tr'); - for(var i=0;i<l.length-1;i++) - if(l[i].scrStatus == 2) - ids += (ids ? ';' : '?')+'id='+l[i].scrId; - if(!ids) - return setTimeout(scrCheckStatus, 1000); - - var ti = setTimeout(scrCheckStatus, 10000); - ajax('/xml/screenshots.xml'+ids, function(hr) { - var ls = hr.responseXML.getElementsByTagName('item'); - var l = x('scr_table').getElementsByTagName('tr'); - var tr; - for(var s=0;s<ls.length;s++) { - for(i=0;i<l.length-1;i++) - if(l[i].scrId == ls[s].getAttribute('id') && ls[s].getAttribute('processed') == "1") - tr = l[i]; - if(!tr) - continue; - - tr.scrStatus = 0; - tr.getElementsByTagName('td')[0].innerHTML = - '<a href="'+scrURL(tr.scrId, 'f')+'" rel="iv:'+ls[s].getAttribute('width')+'x'+ls[s].getAttribute('height')+':edit">' - +'<img src="'+scrURL(tr.scrId, 't')+'" style="margin: 0; padding: 0; border: 0" /></a>'; - - var opt=''; - for(var o=0;o<scrRel.length;o++) - opt += '<option value="'+scrRel[o][0]+'"'+(tr.scrRel && tr.scrRel == scrRel[o][0] ? ' selected="selected"' : '')+'>'+scrRel[o][1]+'</option>'; - - tr.getElementsByTagName('td')[1].innerHTML = '<b>Screenshot #'+tr.scrId+'</b>' - +' (<a href="#" onclick="return scrDel(this)">remove</a>)<br />' - +'Full size: '+ls[s].getAttribute('width')+'x'+ls[s].getAttribute('height')+'<br /><br />' - +'<input type="checkbox" onclick="scrSerialize()" id="scr_ser_'+tr.scrId+'" name="scr_ser_'+tr.scrId+'"' - +' '+(tr.scrNSFW > 0 ? 'checked = "checked"' : '')+' />' - +'<label for="scr_ser_'+tr.scrId+'">This screenshot is NSFW</label><br />' - +'<select onchange="scrSerialize()">'+opt+'</select>'; - } - scrSerialize(); - ivInit(); - clearTimeout(ti); - setTimeout(scrCheckStatus, 1000); - }); -} - -function scrDel(what) { - while(what.nodeName.toLowerCase() != 'tr') - what = what.parentNode; - what.scrStatus = 3; - x('scr_table').removeChild(what); - scrSerialize(); - scrLast(); - return false; -} - -var scrUplNr=0; -function scrUpload() { - scrUplNr++; - - // create temporary form - var d = document.createElement('div'); - d.style.cssText = 'visibility: hidden; overflow: hidden; width: 1px; height: 1px; position: absolute; left: -500px; top: -500px'; - d.innerHTML = '<iframe id="scr_upl_'+scrUplNr+'" name="scr_upl_'+scrUplNr+'" style="height: 0px; width: 0px; visibility: hidden"' - +' src="about:blank" onload="scrUploadComplete(this)"></iframe>' - +'<form method="post" action="/xml/screenshots.xml" target="scr_upl_'+scrUplNr+'" enctype="multipart/form-data" id="scr_frm_'+scrUplNr+'"></form>'; - document.body.appendChild(d); - - // submit form and delete it - d = x('scr_frm_'+scrUplNr); - d.appendChild(x('scr_upload')); - d.submit(); - d.parentNode.removeChild(d); - - d = scrAdd(0, 0, 0); - x('scr_upl_'+scrUplNr).theTR = d; - scrLast(); - - return false; -} - -function scrUploadComplete(what) { - var f = window.frames[what.id]; - if(f.location.href.indexOf('screenshots') < 0) - return; - - var tr = what.theTR; - if(!tr || tr.scrStatus == 3) - return; - - try { - tr.scrId = f.window.document.getElementsByTagName('image')[0].getAttribute('id'); - } catch(e) { - tr.scrId = -10; - } - if(tr.scrId < 0) { - alert( - tr.scrId == -10 ? - 'Oops! Seems like something went wrong...\n' - +'Make sure the file you\'re uploading doesn\'t exceed 5MB in size.\n' - +'If that isn\'t the problem, then please report a bug.' : - tr.scrId == -1 ? - 'Upload failed!\nOnly JPEG or PNG images are accepted.' : - 'Upload failed!\nNo file selected, or an empty file?' - ); - return scrDel(tr); - } - - tr.scrStatus = 2; - tr.getElementsByTagName('td')[1].innerHTML = - '<b>Generating thumbnail...</b><br />' - +'Note: if this takes longer than 30 seconds, there\'s probably something wrong on our side.' - +'Please try again later or report a bug if that is the case.'; - - // remove the <div> in a timeout, otherwise some browsers think the page is still loading - setTimeout(function() { document.body.removeChild(what.parentNode) }, 100); -} - -function scrSerialize() { - var r = ''; - var l = x('scr_table').getElementsByTagName('tr'); - for(var i=0;i<l.length-1;i++) - if(l[i].scrStatus == 0) - r += (r ? ' ' : '') + l[i].scrId + ',' - + (l[i].getElementsByTagName('input')[0].checked ? 1 : 0) + ',' - + scrRel[l[i].getElementsByTagName('select')[0].selectedIndex][0]; - x('screenshots').value = r; -} - - - - - - - /***************\ - * M E D I A * - \***************/ - - -var medTypes = [ [ '', '- medium -', false ] ]; -function medLoad() { - // load the medTypes and clear the div - var l = x('media_div').getElementsByTagName('select')[0].options; - for(var i=0;i<l.length;i++) - medTypes[medTypes.length] = [ l[i].value, l[i].text, l[i].className.indexOf('noqty') ? false : true ]; - x('media_div').innerHTML = ''; - - // load the selected media - l = x('media').value.split(','); - for(var i=0;i<l.length;i++) - if(l[i].length > 2) - medAddNew(l[i].split(' ')[0], Math.floor(l[i].split(' ')[1])); - - medAddNew('', 0); - medSetSubmit(); -} - -function medSetSubmit() { - var o=x('media'); - while(o.nodeName.toLowerCase() != 'form') - o = o.parentNode; - oldfunc = o.onsubmit; - o.onsubmit = function() { - var l = x('media_div').getElementsByTagName('span'); - for(var i=0;i<l.length-1;i++) { - var s = l[i].getElementsByTagName('select'); - if(!medTypes[s[1].selectedIndex][2] && s[0].selectedIndex == 0) { - alert('Media '+medTypes[s[1].selectedIndex][1]+' requires a quantity to be specified!'); - return false; - } - } - return oldfunc ? oldfunc() : true; - }; -} - -function medAddNew(med, qty) { - var o = document.createElement('span'); - var r = '<select class="qty" onchange="medSerialize()"><option value="0">- quantity -</option>'; - for(var i=1;i<=20;i++) - r += '<option value="'+i+'"'+(qty == i ? ' selected="selected"' : '')+'>'+i+'</option>'; - r += '</select><select class="medium" onchange="return medCheckNew(this)">'; - for(i=0;i<medTypes.length;i++) - r += '<option value="'+medTypes[i][0]+'"'+(med == medTypes[i][0] ? ' selected="selected"' : '')+'>'+medTypes[i][1]+'</option>'; - r += '</select>'; - if(med != '') - r += '<input type="button" class="submit" onclick="return medDel(this)" value="remove" />'; - o.innerHTML = r; - x('media_div').appendChild(o); -} - -function medDel(what) { - what = what.nodeName ? what : this; - while(what.nodeName.toLowerCase() != 'span') - what = what.parentNode; - x('media_div').removeChild(what); - medSerialize(); - return false; -} - -function medCheckNew() { - // check for non-new items and add remove buttons - var l = x('media_div').getElementsByTagName('span'); - var createnew=1; - for(var i=0;i<l.length;i++) { - var sel = l[i].getElementsByTagName('select')[1].selectedIndex; - if(sel == 0) - createnew = 0; - else if(l[i].getElementsByTagName('input').length < 1) { - var a = document.createElement('input'); - a.type = 'button'; - a.className = 'submit'; - a.onclick = medDel; - a.value = 'remove'; - l[i].appendChild(a); - } - } - if(createnew) - medAddNew('', 0); - medSerialize(); - - return true; -} - -function medSerialize() { - var r = ''; - var l = x('media_div').getElementsByTagName('span'); - for(var i=0;i<l.length;i++) { - var sel = l[i].getElementsByTagName('select'); - if(sel[1].selectedIndex != 0) - r += (r ? ',' : '') + medTypes[sel[1].selectedIndex][0] + ' ' + (medTypes[sel[1].selectedIndex][2] ? 0 : sel[0].selectedIndex); - } - x('media').value = r; -} - - - - - - - /****************************************************\ - * V I S U A L N O V E L S / P R O D U C E R S * - \****************************************************/ - - -function vnpLoad(type) { - // load currently selected VNs - var l = x(type).value.split('|||'); - for(var i=0;i<l.length;i++) - if(l[i].length > 2) - vnpAdd(type, l[i].split(',',2)[0], l[i].split(',',2)[1]); - vnpCheckEmpty(type); - - // dropdown - var n = x('jt_box_'+(type == 'vn' ? 'rel_vn' : 'rel_prod')).getElementsByTagName('div')[1]; - dsInit(n.getElementsByTagName('input')[0], '/xml/'+type+'.xml?q=', function(item, tr) { - var td = document.createElement('td'); - td.innerHTML = type.substr(0,1)+item.getAttribute('id'); - td.style.textAlign = 'right'; - td.style.paddingRight = '5px'; - tr.appendChild(td); - td = document.createElement('td'); - td.innerHTML = shorten(item.firstChild.nodeValue, 40); - tr.appendChild(td); - }, function(item) { - return type.substr(0,1)+item.getAttribute('id')+':'+item.firstChild.nodeValue; - }, function() { vnpFormAdd(type) }); - n.getElementsByTagName('a')[0].onclick = function() { vnpFormAdd(type); return false }; -} - -function vnpAdd(type, id, title) { - var o = document.createElement('span'); - o.innerHTML = '<i>'+type.substr(0,1)+id+':<a href="/'+type.substr(0,1)+id+'">'+shorten(title, 40)+'</a></i>' - +'<a href="#" onclick="return vnpDel(this, \''+type+'\')">remove</a>'; - x(type+'sel').appendChild(o); - vnpStripe(type); - vnpCheckEmpty(type); -} - -function vnpDel(what, type) { - what = what.nodeName ? what : this; - while(what.nodeName.toLowerCase() != 'span') - what = what.parentNode; - x(type+'sel').removeChild(what); - vnpCheckEmpty(type); - vnpSerialize(type); - return false; -} - -function vnpCheckEmpty(type) { - var o = x(type+'sel'); - if(o.getElementsByTagName('span').length < 1) { - if(o.getElementsByTagName('b').length < 1) - o.innerHTML = '<b>Nothing selected...</b>'; - } else if(o.getElementsByTagName('b').length == 1) - o.removeChild(o.getElementsByTagName('b')[0]); -} - -function vnpStripe(type) { - var l = x(type+'sel').getElementsByTagName('span'); - for(var i=0;i<l.length;i++) - l[i].className = i%2 ? 'odd' : ''; -} - -function vnpFormAdd(type) { - var n = x('jt_box_'+(type == 'vn' ? 'rel_vn' : 'rel_prod')).getElementsByTagName('div')[1]; - var txt = n.getElementsByTagName('input')[0]; - var lnk = n.getElementsByTagName('a')[0]; - var input = txt.value; - - if(type == 'vn' && !input.match(/^v[0-9]+/)) { - alert('Visual novel textbox must start with an ID (e.g. v17)'); - return false; - } - if(type == 'producers' && !input.match(/^p[0-9]+/)) { - alert('Producer textbox must start with an ID (e.g. p5)'); - return false; - } - - txt.disabled = true; - txt.value = 'loading...'; - lnk.innerHTML = 'loading...'; - - ajax('/xml/'+type+'.xml?q='+encodeURIComponent(input), function(hr) { - txt.disabled = false; - txt.value = ''; - lnk.innerHTML = 'add'; - - var items = hr.responseXML.getElementsByTagName('item'); - if(items.length < 1) - return alert('Item not found!'); - - vnpAdd(type, items[0].getAttribute('id'), items[0].firstChild.nodeValue); - vnpSerialize(type); - }); - return false; -} - -function vnpSerialize(type) { - var r = ''; - var l = x(type+'sel').getElementsByTagName('span'); - for(var i=0;i<l.length;i++) - r += (r ? '|||' : '') + l[i].getElementsByTagName('i')[0].innerHTML.substr(1, l[i].getElementsByTagName('i')[0].innerHTML.indexOf(':')-1) - + ',' + l[i].getElementsByTagName('a')[0].innerHTML; - x(type).value = r; -} - - - - - - - /****************************************************\ - * V I S U A L N O V E L T A G L I N K I N G * - \****************************************************/ - - -function tglLoad() { - var n = x('tagtable').getElementsByTagName('tfoot')[0].getElementsByTagName('input'); - dsInit(n[1], '/xml/tags.xml?q=', function(item, tr) { - var td = document.createElement('td'); - td.innerHTML = shorten(item.firstChild.nodeValue, 40); - if(item.getAttribute('meta') == 'yes') - td.innerHTML += ' <b class="grayedout">meta</b>'; - else if(item.getAttribute('state') == 0) - td.innerHTML += ' <b class="grayedout">awaiting moderation</b>'; - tr.appendChild(td); - }, function(item) { - return item.firstChild.nodeValue; - }, tglAdd); - n[2].onclick = tglAdd; - - tglStripe(); - var l = x('tagtable').getElementsByTagName('tbody')[0].getElementsByTagName('tr'); - for(var i=0; i<l.length;i++) { - var o = l[i].getElementsByTagName('td'); - tglSpoiler(o[2], parseInt(o[2].innerHTML)); - tglVoteBar(o[1], parseInt(o[1].innerHTML)); - } -} - -function tglSpoiler(obj, spoil) { - var r = '<select onchange="tglSerialize()">'; - for(var i=-1; i<=2; i++) - r += '<option value="'+i+'"'+(spoil==i?' selected="selected"':'')+'>' - +(i == -1 ? 'neutral' : i == 0 ? 'no spoiler' : i == 1 ? 'minor spoiler' : 'major spoiler') - +' </option>'; - obj.innerHTML = r+'</select>'; -} - -function tglVoteBar(obj, vote) { - var r = ''; - for(var i=-3;i<=3;i++) - r += '<a href="#" class="taglvl taglvl'+i+'" onmouseover="tglVoteBarSel(this, '+i+')"' - + ' onmouseout="tglVoteBarSel(this, '+vote+')" onclick="return tglVoteBar(this.parentNode, '+i+')"> </a>'; - obj.innerHTML = r; - tglVoteBarSel(obj, vote); - tglSerialize(); - return false; -} - -function tglVoteBarSel(obj, vote) { - if(obj.className.indexOf('taglvl') >= 0) - obj = obj.parentNode; - var l = obj.getElementsByTagName('a'); - var num; - for(var i=0; i<l.length; i++) { - if((num = l[i].className.replace(/^.*taglvl(-?[0-3]).*$/, "$1")) == l[i].className) - continue; - if(num == 0) - l[i].innerHTML = vote == 0 ? '-' : vote; - else if(num<0&&vote<=num || num>0&&vote>=num) { - if(l[i].className.indexOf('taglvlsel') < 0) - l[i].className += ' taglvlsel'; - } else - if(l[i].className.indexOf('taglvlsel') >= 0) - l[i].className = l[i].className.replace(/taglvlsel/, ''); - } -} - -function tglAdd() { - var n = x('tagtable').getElementsByTagName('tfoot')[0].getElementsByTagName('input'); - n[1].disabled = n[2].disabled = true; - n[2].value = 'loading...'; - ajax('/xml/tags.xml?q=name:'+encodeURIComponent(n[1].value), function(hr) { - n[1].disabled = n[1].disabled = false; - n[2].value = 'Add tag'; - n[1].value = ''; - - var items = hr.responseXML.getElementsByTagName('item'); - if(items.length < 1) - return alert('Item not found!'); - if(items[0].getAttribute('meta') == 'yes') - return alert('Can\'t use meta tags here!'); - var name = items[0].firstChild.nodeValue; - var l = x('tagtable').getElementsByTagName('a'); - for(var i=0; i<l.length; i++) - if(l[i].innerHTML == qq(name)) - return alert('Tag is already present!'); - - var tr = document.createElement('tr'); - var td = document.createElement('td'); - td.innerHTML = '<a href="/g'+items[0].getAttribute('id')+'">'+qq(name)+'</a>'; - td.className = 'tc1'; - tr.appendChild(td); - td = document.createElement('td'); - tglVoteBar(td, 2); - td.className = 'tc2'; - tr.appendChild(td); - td = document.createElement('td'); - tglSpoiler(td, -1); - td.className = 'tc3'; - tr.appendChild(td); - td = document.createElement('td'); - td.className = 'tc4'; - td.innerHTML = '-'; - tr.appendChild(td); - td = document.createElement('td'); - td.innerHTML = '-'; - td.className = 'tc5'; - tr.appendChild(td); - x('tagtable').getElementsByTagName('tbody')[0].appendChild(tr); - tglStripe(); - tglSerialize(); - }); -} - -function tglStripe() { - var l = x('tagtable').getElementsByTagName('tbody')[0].getElementsByTagName('tr'); - for(var i=0;i<l.length;i++) - l[i].className = i%2 ? 'odd' : ''; -} - -function tglSerialize() { - var r = ''; - var l = x('tagtable').getElementsByTagName('tbody')[0].getElementsByTagName('tr'); - for(var i=0; i<l.length;i++) { - var lnk = l[i].getElementsByTagName('a')[0].href; - var vt = l[i].getElementsByTagName('td')[1].getElementsByTagName('a'); - var id; - if((id = lnk.replace(/^.*g([1-9][0-9]*)$/, "$1")) != lnk && vt.length > 3 && vt[3].innerHTML != '-') - r += (r?' ':'')+id+','+vt[3].innerHTML+','+(l[i].getElementsByTagName('select')[0].selectedIndex-1); - } - x('taglinks').value = r; -} - - diff --git a/static/f/icons.png b/static/f/icons.png Binary files differindex 78ec79fc..42c9004d 100644 --- a/static/f/icons.png +++ b/static/f/icons.png diff --git a/static/f/script.js b/static/f/script.js deleted file mode 100644 index 987963dc..00000000 --- a/static/f/script.js +++ /dev/null @@ -1,635 +0,0 @@ - -/* G L O B A L S T U F F */ - -function x(y){return document.getElementById(y)} -function cl(o,f){if(x(o))x(o).onclick=f} -function DOMLoad(y){var d=0;var f=function(){if(d++)return;y()}; -if(document.addEventListener)document.addEventListener("DOMCont" -+"entLoaded",f,false);document.write("<script id=_ie defer src=" -+"javascript:void(0)><\/script>");document.getElementById('_ie') -.onreadystatechange=function(){if(this.readyState=="complete")f() -};if(/WebKit/i.test(navigator.userAgent))var t=setInterval( -function(){if(/loaded|complete/.test(document.readyState)){ -clearInterval(t);f()}},10);window.onload=f;} - -var http_request = false; -function ajax(url, func) { - if(http_request) - http_request.abort(); - http_request = (window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(); - if(http_request == null) { - alert("Your browse does not support the functionality this website requires."); - return; - } - http_request.onreadystatechange = function() { - if(!http_request || http_request.readyState != 4 || !http_request.responseText) - return; - if(http_request.status != 200) - return alert('Whoops, error! :('); - func(http_request); - }; - url += (url.indexOf('?')>=0 ? ';' : '?')+(Math.floor(Math.random()*999)+1); - http_request.open('GET', url, true); - http_request.send(null); -} - -function setCookie(n,v) { - var date = new Date(); - date.setTime(date.getTime()+(365*24*60*60*1000)); - document.cookie = n+'='+v+'; expires='+date.toGMTString()+'; path=/'; -} -function readCookie(n) { - var l = document.cookie.split(';'); - for(var i=0; i<l.length; i++) { - var c = l[i]; - while(c.charAt(0) == ' ') - c = c.substring(1,c.length); - if(c.indexOf(n+'=') == 0) - return c.substring(n.length+1,c.length); - } - return null; -} - - - - -/* I M A G E V I E W E R */ - -function ivInit() { - var init = 0; - var l = document.getElementsByTagName('a'); - for(var i=0;i<l.length;i++) - if(l[i].rel.substr(0,3) == 'iv:') { - init++; - l[i].onclick = ivView; - } - if(init && !x('iv_view')) { - var d = document.createElement('div'); - d.id = 'iv_view'; - d.innerHTML = '<b id="ivimg"></b><br />' - +'<a href="#" id="ivfull"> </a>' - +'<a href="#" onclick="return ivClose()" id="ivclose">close</a>' - +'<a href="#" onclick="return ivView(this)" id="ivprev">« previous</a>' - +'<a href="#" onclick="return ivView(this)" id="ivnext">next »</a>'; - document.body.appendChild(d); - d = document.createElement('b'); - d.id = 'ivimgload'; - d.innerHTML = 'Loading...'; - document.body.appendChild(d); - } -} - -function ivView(what) { - what = what && what.rel ? what : this; - var u = what.href; - var opt = what.rel.split(':'); - d = x('iv_view'); - - // fix prev/next links (if any) - if(opt[2]) { - var ol = document.getElementsByTagName('a'); - var l=[]; - for(i=0;i<ol.length;i++) - if(ol[i].rel.substr(0,3) == 'iv:' && ol[i].rel.indexOf(':'+opt[2]) > 4 && ol[i].className.indexOf('hidden') < 0 && ol[i].id != 'ivprev' && ol[i].id != 'ivnext') - l[l.length] = ol[i]; - for(i=0;i<l.length;i++) - if(l[i].href == u) { - x('ivnext').style.visibility = l[i+1] ? 'visible' : 'hidden'; - x('ivnext').href = l[i+1] ? l[i+1].href : '#'; - x('ivnext').rel = l[i+1] ? l[i+1].rel : ''; - x('ivprev').style.visibility = l[i-1] ? 'visible' : 'hidden'; - x('ivprev').href = l[i-1] ? l[i-1].href : '#'; - x('ivprev').rel = l[i-1] ? l[i-1].rel : ''; - } - } else - x('ivnext').style.visibility = x('ivprev').style.visibility = 'hidden'; - - // calculate dimensions - var w = Math.floor(opt[1].split('x')[0]); - var h = Math.floor(opt[1].split('x')[1]); - var ww = typeof(window.innerWidth) == 'number' ? window.innerWidth : document.documentElement.clientWidth; - var wh = typeof(window.innerHeight) == 'number' ? window.innerHeight : document.documentElement.clientHeight; - var st = typeof(window.pageYOffset) == 'number' ? window.pageYOffset : document.body && document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; - if(w+100 > ww || h+70 > wh) { - x('ivfull').href = u; - x('ivfull').innerHTML = w+'x'+h; - x('ivfull').style.visibility = 'visible'; - if(w/h > ww/wh) { // width++ - h *= (ww-100)/w; - w = ww-100; - } else { // height++ - w *= (wh-70)/h; - h = wh-70; - } - } else - x('ivfull').style.visibility = 'hidden'; - var dw = w; - var dh = h+20; - dw = dw < 200 ? 200 : dw; - - // update document - d.style.display = 'block'; - x('ivimg').innerHTML = '<img src="'+u+'" onclick="ivClose()" onload="document.getElementById(\'ivimgload\').style.top=\'-400px\'" style="width: '+w+'px; height: '+h+'px" />'; - d.style.width = dw+'px'; - d.style.height = dh+'px'; - d.style.left = ((ww - dw) / 2 - 10)+'px'; - d.style.top = ((wh - dh) / 2 + st - 20)+'px'; - x('ivimgload').style.left = ((ww - 100) / 2 - 10)+'px'; - x('ivimgload').style.top = ((wh - 20) / 2 + st)+'px'; - return false; -} - -function ivClose() { - x('iv_view').style.display = 'none'; - x('iv_view').style.top = '-5000px'; - x('ivimgload').style.top = '-400px'; - x('ivimg').innerHTML = ''; - return false; -} - - - - - - -/* V N L I S T D R O P D O W N */ - -var rstat = [ 'Unknown', 'Pending', 'Obtained', 'On loan', 'Deleted' ]; -var vstat = [ 'Unknown', 'Playing', 'Finished', 'Stalled', 'Dropped' ]; -function vlDropDown(e) { - e = e || window.event; - var tg = e.target || e.srcElement; - while(tg && (tg.nodeType == 3 || tg.nodeName.toLowerCase() != 'a')) - tg = tg.parentNode; - - var o = x('vldd'); - if(!o && (!tg || tg.id.substr(0,6) != 'rlsel_')) - return; - - if(o) { - var mouseX = e.pageX || (e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft); - var mouseY = e.pageY || (e.clientY + document.body.scrollTop + document.documentElement.scrollTop); - if((mouseX < ddx-5 || mouseX > ddx+o.offsetWidth+100 || mouseY < ddy-5 || mouseY > ddy+o.offsetHeight+5) - || (tg && tg.id.substr(0,6) == 'rlsel_' && tg.id != 'rlsel_'+o.relId)) { - document.body.removeChild(o); - o = null; - } - } - if(!o && tg) { - o = tg; - ddx = ddy = 0; - do { - ddx += o.offsetLeft; - ddy += o.offsetTop; - } while(o = o.offsetParent); - ddx -= 185; - - var cu = tg.id.substr(6); - var st = tg.innerHTML.split(' / '); - if(st[0].indexOf('loading') >= 0) - return; - var r = '<ul><li><b>Release status</b></li>'; - for(var i=0;i<rstat.length;i++) - r += st[0] && st[0].indexOf(rstat[i]) >= 0 ? '<li><i>'+rstat[i]+'</i></li>' : '<li><a href="#" onclick="return vlMod('+cu+',\'r'+i+'\')">'+rstat[i]+'</a></li>'; - r += '</ul><ul><li><b>Play status</b></li>'; - for(var i=0;i<vstat.length;i++) - r += st[1] && st[1].indexOf(vstat[i]) >= 0 ? '<li><i>'+vstat[i]+'</i></li>' : '<li><a href="#" onclick="return vlMod('+cu+',\'v'+i+'\')">'+vstat[i]+'</a></li>'; - r += '</ul>'; - if(tg.innerHTML != '--') - r += '<ul class="full"><li><a href="#" onclick="return vlMod('+cu+',\'del\')">Remove from VN list</a></li></ul>'; - - o = document.createElement('div'); - o.id = 'vldd'; - o.relId = tg.id.substr(6); - o.style.left = ddx+'px'; - o.style.top = ddy+'px'; - o.innerHTML = r; - document.body.appendChild(o); - } -} - -function vlMod(rid, act) { - document.body.removeChild(x('vldd')); - x('rlsel_'+rid).innerHTML = '<b class="patch">loading...</b>'; - ajax('/xml/rlist.xml?id='+rid+';e='+act, function(hr) { - x('rlsel_'+rid).innerHTML = hr.responseXML.getElementsByTagName('rlist')[0].firstChild.nodeValue; - }); - return false; -} - - - - - - -/* J A V A S C R I P T T A B S */ - -function jtInit() { - var sel = ''; - var first = ''; - var l = x('jt_select').getElementsByTagName('a'); - for(var i=0;i<l.length;i++) - if(l[i].id.substr(0,7) == 'jt_sel_') { - l[i].onclick = jtSel; - if(!first) - first = l[i].id; - if(location.hash && l[i].id == 'jt_sel_'+location.hash.substr(1)) - sel = l[i].id; - } - if(!first) - return; - if(!sel) - sel = first; - jtSel(sel, 1); -} - -function jtSel(which, nolink) { - which = typeof(which) == 'string' ? which : which && which.id ? which.id : this.id; - which = which.substr(7); - - var l = x('jt_select').getElementsByTagName('a'); - for(var i=0;i<l.length;i++) - if(l[i].id.substr(0,7) == 'jt_sel_') { - var name = l[i].id.substr(7); - if(name != 'all') - x('jt_box_'+name).style.display = name == which || which == 'all' ? 'block' : 'none'; - var o = x('jt_sel_'+name).parentNode; - if(o.className.indexOf('tabselected') >= 0) { - if(name != which) - o.className = o.className.replace(/tabselected/, ''); - } else - if(name == which) - o.className += ' tabselected'; - } - - if(!nolink) - location.href = '#'+which; - return false; -} - - - - -/* Tag VN spoilers */ -/* lvl = null to not change lvl, lim = null to not change limit */ -function tvsSet(lvl, lim) { - var l = x('tagops').getElementsByTagName('a'); - for(var i=0;i<l.length;i++) { - if(i < 3) { - if(lvl == null) { /* determine level */ - if(l[i].className.indexOf('tsel') >= 0) - lvl = i; - } else { /* set level */ - if(i == lvl && l[i].className.indexOf('tsel') < 0) - l[i].className += ' tsel'; - else if(i != lvl && l[i].className.indexOf('tsel') >= 0) - l[i].className = l[i].className.replace(/tsel/, ''); - } - } else { - if(lim == null) { /* determine limit */ - if(l[i].className.indexOf('tsel') >= 0) - lim = i == 3; - } else { /* set limit */ - if((i == 3) == lim && l[i].className.indexOf('tsel') < 0) - l[i].className += ' tsel'; - else if((i == 3) != lim && l[i].className.indexOf('tsel') >= 0) - l[i].className = l[i].className.replace(/tsel/, ''); - } - } - } - - l = x('vntags').getElementsByTagName('span'); - lim = lim ? 15 : 999; - var s=0; - for(i=0;i<l.length;i++) { - if((lvl < l[i].className.substr(6, 1) || s>=lim) && l[i].className.indexOf('hidden') < 0) - l[i].className += ' hidden'; - if(lvl >= l[i].className.substr(6, 1) && ++s<=lim && l[i].className.indexOf('hidden') >= 0) - l[i].className = l[i].className.replace(/hidden/, ''); - } - return false; -} - - - - -/* date input */ -var months = ['January','February','March','April','May','June','July','August','September','October','November','December']; -function dtLoad(obj) { - var r = Math.floor(obj.value) || 0; - var v = [ Math.floor(r/10000), Math.floor(r/100)%100, r%100 ]; - var i; - r = '<select onchange="dtSerialize(this)" style="width: 70px"><option value="0">-year-</option>'; - for(i=1980; i<=(new Date()).getFullYear()+5; i++) - r += '<option value="'+i+'"'+(i == v[0] ? ' selected="selected"':'')+'>'+i+'</option>'; - r += '<option value="9999"'+(v[0] == 9999 ? ' selected="selected"':'')+'>TBA</option>'; - r += '</select><select onchange="dtSerialize(this)" style="width: 100px"><option value="99">-month-</option>'; - for(i=1; i<=12; i++) - r += '<option value="'+i+'"'+(i == v[1] ? ' selected="selected"':'')+'>'+months[i-1]+'</option>'; - r += '</select><select onchange="dtSerialize(this)" style="width: 70px"><option value="99">-day-</option>'; - for(i=1; i<=31; i++) - r += '<option value="'+i+'"'+(i == v[2] ? ' selected="selected"':'')+'>'+i+'</option>'; - r += '</select>'; - v = document.createElement('div'); - v.obj = obj; - v.innerHTML = r; - obj.parentNode.insertBefore(v, obj); -} -function dtSerialize(obj) { - obj = obj.parentNode; - var l = obj.getElementsByTagName('select'); - var v = [ l[0].options[l[0].selectedIndex].value*1, l[1].options[l[1].selectedIndex].value*1, l[2].options[l[2].selectedIndex].value*1 ]; - obj = obj.obj; - if(v[0] == 0) obj.value = 0; - else if(v[0] == 9999) obj.value = 99999999; - else obj.value = v[0]*10000 + v[1]*100 + (v[1]==99?99:v[2]); -} - - - -/* O N L O A D E V E N T */ - -DOMLoad(function() { - - // search box - var i = x('sq'); - i.onfocus = function () { - if(this.value == 'search') { - this.value = ''; - this.style.fontStyle = 'normal' - } - }; - i.onblur = function () { - if(this.value.length < 1) { - this.value = 'search'; - this.style.fontStyle = 'italic' - } - }; - - - // VN Voting - i = x('votesel'); - if(i) - i.onchange = function() { - var s = this.options[this.selectedIndex].value; - if(s == 1 && !confirm( - "You are about to give this visual novel a 1 out of 10. This is a rather extreme rating, " - +"meaning this game has absolutely nothing to offer, and that it's the worst game you have ever played.\n" - +"Are you really sure this visual novel matches that description?")) - return; - if(s == 10 && !confirm( - "You are about to give this visual novel a 10 out of 10. This is a rather extreme rating, " - +"meaning this is one of the best visual novels you've ever played and it's unlikely " - +"that any other game could ever be better than this one.\n" - +"It is generally a bad idea to have more than three games in your vote list with this rating, choose carefully!")) - return; - if(s) - location.href = location.href.replace(/\.[0-9]+/, '')+'/vote?v='+s; - }; - - // VN Wishlist editing - i = x('wishsel'); - if(i) - i.onchange = function() { - if(this.selectedIndex != 0) - location.href = location.href.replace(/\.[0-9]+/, '')+'/wish?s='+this.options[this.selectedIndex].value; - }; - // Batch Wishlist editing - i = x('batchedit'); - if(i) - i.onchange = function() { - var frm = this; - while(frm.nodeName.toLowerCase() != 'form') - frm = frm.parentNode; - if(this.selectedIndex != 0) - frm.submit(); - }; - - - // Release list editing - i = x('listsel'); - if(i) - i.onchange = function() { - if(this.selectedIndex != 0) - location.href = location.href.replace(/\.[0-9]+/, '')+'/list?e='+this.options[this.selectedIndex].value; - }; - - // User VN list - // (might want to make this a bit more generic, as it's now also used for the user tag list) - i = x('relhidall'); - if(i) { - var l = document.getElementsByTagName('tr'); - for(var i=0;i<l.length;i++) - if(l[i].className.indexOf('relhid') >= 0) - l[i].style.display = 'none'; - var l = document.getElementsByTagName('td'); - for(var i=0;i<l.length;i++) - if(l[i].className.indexOf('relhid_but') >= 0) - l[i].onclick = function() { - var l = document.getElementsByTagName('tr'); - for(var i=0;i<l.length;i++) - if(l[i].className.substr(7) == this.id) { - l[i].style.display = l[i].style.display == 'none' ? '' : 'none'; - this.getElementsByTagName('i')[0].innerHTML = l[i].style.display == 'none' ? '▸' : '▾'; - } - }; - var allhid = 1; - x('relhidall').onclick = function() { - allhid = !allhid; - var l = document.getElementsByTagName('tr'); - for(var i=0;i<l.length;i++) - if(l[i].className.indexOf('relhid') >= 0) { - l[i].style.display = allhid ? 'none' : ''; - x(l[i].className.substr(7)).getElementsByTagName('i')[0].innerHTML = allhid ? '▸' : '▾'; - } - this.getElementsByTagName('i')[0].innerHTML = allhid ? '▸' : '▾'; - }; - } - - // Advanced search - cl('advselect', function() { - var e = x('advoptions'); - e.className = e.className.indexOf('hidden')>=0 ? '' : 'hidden'; - this.getElementsByTagName('i')[0].innerHTML = e.className.indexOf('hidden')>=0 ? '▸' : '▾'; - return false; - }); - - // auto-complete tag search - if(x('advselect') && x('ti')) { - var fields=['ti','te']; - for(var field=0;field<fields.length;field++) - dsInit(x(fields[field]), '/xml/tags.xml?q=', - function(item, tr) { - var td = document.createElement('td'); - td.innerHTML = shorten(item.firstChild.nodeValue, 40); - if(item.getAttribute('meta') == 'yes') - td.innerHTML += ' <b class="grayedout">meta</b>'; - else if(item.getAttribute('state') == 0) - td.innerHTML += ' <b class="grayedout">awaiting moderation</b>'; - tr.appendChild(td); - }, - function(item, obj) { - var tags = obj.value.split(/ *, */); - tags[tags.length-1] = item.firstChild.nodeValue; - return tags.join(', '); - }, - function() { false; }, - function(val) { return (val.split(/, */))[val.split(/, */).length-1]; } - ); - } - - // update spoiler cookie on VN search radio button - if(x('sp_0')) { - cl('sp_0', function(){setCookie('tagspoil',0)}); - cl('sp_1', function(){setCookie('tagspoil',1)}); - cl('sp_2', function(){setCookie('tagspoil',2)}); - if((i = readCookie('tagspoil')) == null) - i = 1; - x('sp_'+i).checked = true; - } - - // show/hide NSFW VN image - if(x('nsfw_show')) - x('nsfw_show').getElementsByTagName('a')[0].onclick = function() { - x('nsfw_show').style.display = 'none'; - x('nsfw_hid').style.display = 'block'; - x('nsfw_hid').onclick = function() { - x('nsfw_show').style.display = 'block'; - x('nsfw_hid').style.display = 'none'; - }; - return false - }; - - // NSFW toggle for screenshots - cl('nsfwhide', function() { - var s=0; - var l = x('screenshots').getElementsByTagName('div'); - for(var i=0;i<l.length;i++) { - if(l[i].className.indexOf('nsfw') >= 0) { - if(l[i].className.indexOf('hidden') >= 0) { - s++; - l[i].className = 'nsfw'; - l[i].getElementsByTagName('a')[0].className = ''; - } else { - l[i].className += ' hidden'; - l[i].getElementsByTagName('a')[0].className = 'hidden'; - } - } else - s++; - } - x('nsfwshown').innerHTML = s; - return false; - }); - - // initialize image viewer - ivInit(); - - // vnlist dropdown - var l = document.getElementsByTagName('a'); - for(var i=0;i<l.length;i++) - if(l[i].id.substr(0,6) == 'rlsel_') { - document.onmousemove = vlDropDown; - break; - } - - // VN tag spoiler options - if(x('tagops')) { - l = x('tagops').getElementsByTagName('a'); - for(i=0;i<l.length;i++) - l[i].onclick = function() { - var l = x('tagops').getElementsByTagName('a'); - var sel = 0; - for(var i=0;i<l.length;i++) - if(l[i] == this) { - if(i < 3) { - tvsSet(i, null); - setCookie('tagspoil', i); - } else - tvsSet(null, i==3?true:false); - } - return false; - }; - tvsSet(readCookie('tagspoil'), true); - } - - // Javascript tabs - if(x('jt_select')) - jtInit(); - - // spoiler tags - l = document.getElementsByTagName('b'); - for(i=0;i<l.length;i++) - if(l[i].className == 'spoiler') { - l[i].onmouseover = function() { this.className = 'spoiler_shown' }; - l[i].onmouseout = function() { this.className = 'spoiler' }; - } - - // expand/collapse edit summaries on */hist - if(x('history_comments')) { - setcomment = function() { - var e = readCookie('histexpand') == 1; - var l = x('history_comments'); - l.innerHTML = e ? 'collapse' : 'expand'; - while(l.nodeName.toLowerCase() != 'table') - l = l.parentNode; - l = l.getElementsByTagName('tr'); - for(var i=0;i<l.length;i++) - //alert(l[i].className); - if(l[i].className.indexOf('editsum') >= 0) { - if(!e && l[i].className.indexOf('hidden') < 0) - l[i].className += ' hidden'; - if(e && l[i].className.indexOf('hidden') >= 0) - l[i].className = l[i].className.replace(/hidden/, ''); - } - }; - setcomment(); - x('history_comments').onclick = function () { - setCookie('histexpand', readCookie('histexpand') == 1 ? 0 : 1); - setcomment(); - return false; - }; - } - - // Are we really vndb? - if(location.hostname != 'vndb.org') { - var d = document.createElement('div'); - d.setAttribute('id', 'debug'); - d.innerHTML = '<h2>This is not VNDB!</h2>The real VNDB is <a href="http://vndb.org/">here</a>.'; - document.body.appendChild(d); - } - - // date selector - l = document.getElementsByTagName('input'); - for(i=0;i<l.length;i++) - if(l[i].className == 'dateinput') - dtLoad(l[i]); - - // forms.js - if(x('relations')) - relLoad(); - if(x('jt_box_vn_scr')) - scrLoad(); - if(x('media')) - medLoad(); - if(x('jt_box_rel_vn')) - vnpLoad('vn'); - if(x('jt_box_rel_prod')) - vnpLoad('producers'); - if(x('taglinks')) - tglLoad(); - - // make some fields readonly when patch flag is set - if(x('jt_box_rel_geninfo')) { - var func = function() { - x('doujin').disabled = x('resolution').disabled = x('voiced').disabled = x('ani_story').disabled = x('ani_ero').disabled = x('patch').checked; - }; - func(); - x('patch').onclick = func; - } - - // spam protection on all forms - if(document.forms.length >= 1) - for(i=0; i<document.forms.length; i++) - document.forms[i].action = document.forms[i].action.replace(/\/nospam\?/,''); - -}); diff --git a/util/dbgraph.pl b/util/dbgraph.pl index 167680cd..0b72ceba 100755 --- a/util/dbgraph.pl +++ b/util/dbgraph.pl @@ -16,13 +16,13 @@ use warnings; my %subgraphs = ( - 'Producers' => [qw| FFFFCC producers producers_rev |], + '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_categories vn_anime vn_screenshots |], - 'Users' => [qw| CCFFFF users votes rlists wlists |], + 'Visual Novels' => [qw| FFE6BE vn vn_rev vn_relations vn_anime vn_screenshots |], + 'Users' => [qw| CCFFFF users votes rlists wlists sessions |], '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 relgraph stats_cache quotes sessions |], + 'Misc' => [qw| F5F5F5 changes anime screenshots stats_cache quotes relgraphs |], ); my %tables; # table_name => [ [ col1, pri ], ... ] diff --git a/util/dump.sql b/util/dump.sql index aaa1a5e9..0fdf5696 100644 --- a/util/dump.sql +++ b/util/dump.sql @@ -1,15 +1,16 @@ - --- we don't use PgSQL's OIDS -SET default_with_oids = false; - --- for the functions to work, the following query must --- be executed on the database by a superuser: --- CREATE PROCEDURAL LANGUAGE plpgsql - +-- plpgsql is required for our (trigger) functions +CREATE LANGUAGE plpgsql; +-- data types +CREATE TYPE anime_type AS ENUM ('tv', 'ova', 'mov', 'oth', 'web', 'spe', 'mv'); +CREATE TYPE dbentry_type AS ENUM ('v', 'r', 'p'); +CREATE TYPE medium AS ENUM ('cd', 'dvd', 'gdr', 'blr', 'flp', 'mrt', 'mem', 'umd', 'nod', 'in', 'otc'); +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'); ----------------------------------------- @@ -23,7 +24,7 @@ CREATE TABLE anime ( year smallint, ann_id integer, nfo_id varchar(200), - type smallint, + type anime_type, title_romaji, title_kanji, lastfetch timestamptz @@ -32,7 +33,7 @@ CREATE TABLE anime ( -- changes CREATE TABLE changes ( id SERIAL NOT NULL PRIMARY KEY, - type smallint NOT NULL DEFAULT 0, + type dbentry_type NOT NULL, rev integer NOT NULL DEFAULT 1, added timestamptz NOT NULL DEFAULT NOW(), requester integer NOT NULL DEFAULT 0, @@ -46,7 +47,16 @@ CREATE TABLE producers ( id SERIAL NOT NULL PRIMARY KEY, latest integer NOT NULL DEFAULT 0, locked boolean NOT NULL DEFAULT FALSE, - hidden boolean NOT NULL DEFAULT FALSE + hidden boolean NOT NULL DEFAULT FALSE, + rgraph integer +); + +-- producers_relations +CREATE TABLE producers_relations ( + pid1 integer NOT NULL, + pid2 integer NOT NULL, + relation producer_relation NOT NULL, + PRIMARY KEY(pid1, pid2) ); -- producers_rev @@ -62,7 +72,6 @@ CREATE TABLE producers_rev ( alias varchar(500) NOT NULL DEFAULT '' ); - -- quotes CREATE TABLE quotes ( vid integer NOT NULL, @@ -70,7 +79,6 @@ CREATE TABLE quotes ( PRIMARY KEY(vid, quote) ); - -- releases CREATE TABLE releases ( id SERIAL NOT NULL PRIMARY KEY, @@ -89,7 +97,7 @@ CREATE TABLE releases_lang ( -- releases_media CREATE TABLE releases_media ( rid integer NOT NULL DEFAULT 0, - medium character(3) NOT NULL DEFAULT '', + medium medium NOT NULL, qty smallint NOT NULL DEFAULT 1, PRIMARY KEY(rid, medium, qty) ); @@ -105,6 +113,9 @@ CREATE TABLE releases_platforms ( CREATE TABLE releases_producers ( rid integer NOT NULL, pid integer NOT NULL, + developer boolean NOT NULL DEFAULT FALSE, + publisher boolean NOT NULL DEFAULT TRUE, + CHECK(developer OR publisher), PRIMARY KEY(pid, rid) ); @@ -114,7 +125,7 @@ CREATE TABLE releases_rev ( rid integer NOT NULL DEFAULT 0, title varchar(250) NOT NULL DEFAULT '', original varchar(250) NOT NULL DEFAULT '', - type smallint NOT NULL DEFAULT 0, + type release_type NOT NULL DEFAULT 'complete', website varchar(250) NOT NULL DEFAULT '', released integer NOT NULL, notes text NOT NULL DEFAULT '', @@ -137,10 +148,10 @@ CREATE TABLE releases_vn ( PRIMARY KEY(rid, vid) ); --- relgraph -CREATE TABLE relgraph ( - id SERIAL NOT NULL PRIMARY KEY, - cmap text NOT NULL DEFAULT '' +-- relgraphs +CREATE TABLE relgraphs ( + id SERIAL PRIMARY KEY, + svg xml NOT NULL ); -- rlists @@ -245,6 +256,7 @@ CREATE TABLE threads_boards ( tid integer NOT NULL DEFAULT 0, type character(2) NOT NULL DEFAULT 0, iid integer NOT NULL DEFAULT 0, + lastread smallint NOT NULL, PRIMARY KEY(tid, type, iid) ); @@ -292,7 +304,7 @@ CREATE TABLE vn_anime ( CREATE TABLE vn_relations ( vid1 integer NOT NULL DEFAULT 0, vid2 integer NOT NULL DEFAULT 0, - relation integer NOT NULL DEFAULT 0, + relation vn_relation NOT NULL, PRIMARY KEY(vid1, vid2) ); @@ -352,6 +364,9 @@ CREATE TABLE wlists ( ALTER TABLE changes ADD FOREIGN KEY (requester) REFERENCES users (id); ALTER TABLE changes ADD FOREIGN KEY (causedby) REFERENCES changes (id); ALTER TABLE producers ADD FOREIGN KEY (latest) REFERENCES producers_rev (id) DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE producers ADD FOREIGN KEY (rgraph) REFERENCES relgraphs (id); +ALTER TABLE producers_relations ADD FOREIGN KEY (pid1) REFERENCES producers_rev (id); +ALTER TABLE producers_relations ADD FOREIGN KEY (pid2) REFERENCES producers (id); ALTER TABLE producers_rev ADD FOREIGN KEY (id) REFERENCES changes (id); ALTER TABLE producers_rev ADD FOREIGN KEY (pid) REFERENCES producers (id); ALTER TABLE quotes ADD FOREIGN KEY (vid) REFERENCES vn (id); @@ -380,7 +395,7 @@ ALTER TABLE threads_posts ADD FOREIGN KEY (tid) REFERENCES threads ALTER TABLE threads_posts ADD FOREIGN KEY (uid) REFERENCES users (id); ALTER TABLE threads_boards ADD FOREIGN KEY (tid) REFERENCES threads (id); ALTER TABLE vn ADD FOREIGN KEY (latest) REFERENCES vn_rev (id) DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE vn ADD FOREIGN KEY (rgraph) REFERENCES relgraph (id); +ALTER TABLE vn ADD FOREIGN KEY (rgraph) REFERENCES relgraphs (id); ALTER TABLE vn_anime ADD FOREIGN KEY (aid) REFERENCES anime (id); ALTER TABLE vn_anime ADD FOREIGN KEY (vid) REFERENCES vn_rev (id); ALTER TABLE vn_relations ADD FOREIGN KEY (vid1) REFERENCES vn_rev (id); @@ -405,31 +420,6 @@ ALTER TABLE wlists ADD FOREIGN KEY (vid) REFERENCES vn ------------------------- --- update_rev(table, ids) - updates the rev column in the changes table -CREATE FUNCTION update_rev(tbl text, ids text) RETURNS void AS $$ -DECLARE - r RECORD; - r2 RECORD; - i integer; - t text; - e text; -BEGIN - SELECT INTO t SUBSTRING(tbl, 1, 1); - e := ''; - IF ids <> '' THEN - e := ' WHERE id IN('||ids||')'; - END IF; - FOR r IN EXECUTE 'SELECT id FROM '||tbl||e LOOP - i := 1; - FOR r2 IN EXECUTE 'SELECT id FROM '||tbl||'_rev WHERE '||t||'id = '||r.id||' ORDER BY id ASC' LOOP - UPDATE changes SET rev = i WHERE id = r2.id; - i := i+1; - END LOOP; - END LOOP; -END; -$$ LANGUAGE plpgsql; - - -- update_vncache(id) - updates the c_* columns in the vn table CREATE FUNCTION update_vncache(id integer) RETURNS void AS $$ DECLARE @@ -445,7 +435,7 @@ BEGIN JOIN releases r1 ON rr1.id = r1.latest JOIN releases_vn rv1 ON rr1.id = rv1.rid WHERE rv1.vid = vn.id - AND rr1.type <> 2 + AND rr1.type <> ''trial'' AND r1.hidden = FALSE AND rr1.released <> 0 GROUP BY rv1.vid @@ -457,7 +447,7 @@ BEGIN JOIN releases r2 ON rr2.id = r2.latest JOIN releases_vn rv2 ON rr2.id = rv2.rid WHERE rv2.vid = vn.id - AND rr2.type <> 2 + AND rr2.type <> ''trial'' AND rr2.released <= TO_CHAR(''today''::timestamp, ''YYYYMMDD'')::integer AND r2.hidden = FALSE GROUP BY rl2.lang @@ -470,7 +460,7 @@ BEGIN JOIN releases r3 ON rp3.rid = r3.latest JOIN releases_vn rv3 ON rp3.rid = rv3.rid WHERE rv3.vid = vn.id - AND rr3.type <> 2 + AND rr3.type <> ''trial'' AND rr3.released <= TO_CHAR(''today''::timestamp, ''YYYYMMDD'')::integer AND r3.hidden = FALSE GROUP BY rp3.platform @@ -585,7 +575,7 @@ BEGIN SELECT * FROM tags_vn UNION SELECT * FROM tag_vn_childs(); -- grouped by (tag, vid, uid), so only one user votes on one parent tag per VN entry CREATE OR REPLACE TEMPORARY VIEW tags_vn_grouped AS - SELECT tag, vid, uid, MAX(vote)::real AS vote, COALESCE(AVG(spoiler), 0)::real AS spoiler + SELECT tag, vid, uid, MAX(vote)::real AS vote, AVG(spoiler)::real AS spoiler FROM tags_vn_all GROUP BY tag, vid, uid; -- grouped by (tag, vid) and serialized into a table DROP INDEX IF EXISTS tags_vn_bayesian_tag; @@ -781,6 +771,36 @@ $$ LANGUAGE plpgsql; CREATE CONSTRAINT TRIGGER vn_relgraph_notify AFTER INSERT OR UPDATE ON vn DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE vn_relgraph_notify(); +-- Same as above for producers, with slight differences in the steps: +-- There is no 2, and +-- 3 = New producer revision of which the name, language or type differs from the previous revision (deferred) +CREATE OR REPLACE FUNCTION producer_relgraph_notify() RETURNS trigger AS $$ +BEGIN + -- 1. + IF TG_TABLE_NAME = 'producers' THEN + IF NEW.rgraph IS NULL AND EXISTS(SELECT 1 FROM producers_relations WHERE pid1 = NEW.latest) THEN + NOTIFY relgraph; + END IF; + END IF; + IF TG_TABLE_NAME = 'producers' AND TG_OP = 'UPDATE' THEN + IF NEW.rgraph IS NOT NULL AND OLD.latest > 0 THEN + -- 3 & 4 + IF OLD.latest <> NEW.latest AND ( + EXISTS(SELECT 1 FROM producers_rev p1, producers_rev p2 WHERE (p2.name <> p1.name OR p2.type <> p1.type OR p2.lang <> p1.lang) AND p1.id = OLD.latest AND p2.id = NEW.latest) + OR EXISTS(SELECT p1.pid2, p1.relation FROM producers_relations p1 WHERE p1.pid1 = OLD.latest EXCEPT SELECT p2.pid2, p2.relation FROM producers_relations p2 WHERE p2.pid1 = NEW.latest) + OR EXISTS(SELECT p1.pid2, p1.relation FROM producers_relations p1 WHERE p1.pid1 = NEW.latest EXCEPT SELECT p2.pid2, p2.relation FROM producers_relations p2 WHERE p2.pid1 = OLD.latest) + ) THEN + UPDATE producers SET rgraph = NULL WHERE id = NEW.id; + END IF; + END IF; + END IF; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE CONSTRAINT TRIGGER vn_relgraph_notify AFTER INSERT OR UPDATE ON producers DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE producer_relgraph_notify(); + + -- NOTIFY on insert into changes/posts/tags CREATE OR REPLACE FUNCTION insert_notify() RETURNS trigger AS $$ BEGIN diff --git a/util/init.pl b/util/init.pl index 5c171705..ac533174 100755 --- a/util/init.pl +++ b/util/init.pl @@ -22,7 +22,7 @@ print "\n"; print "Creating directory structures...\n"; -for my $d (qw| cv rg st sf |) { +for my $d (qw| cv st sf |) { print " /static/$d\n"; mkdir "$ROOT/static/$d" or die "mkdir '$ROOT/static/$d': $!\n"; for my $i (0..99) { diff --git a/util/jsgen.pl b/util/jsgen.pl new file mode 100755 index 00000000..1ad8fb53 --- /dev/null +++ b/util/jsgen.pl @@ -0,0 +1,97 @@ +#!/usr/bin/perl + +package VNDB; + +use strict; +use warnings; +use Encode 'encode_utf8'; +use Cwd 'abs_path'; +eval { require JavaScript::Minifier::XS; }; + +our($ROOT, %S); +BEGIN { ($ROOT = abs_path $0) =~ s{/util/jsgen\.pl$}{}; } +require $ROOT.'/data/global.pl'; + +use lib "$ROOT/lib"; +use lib "$ROOT/yawf/lib"; +use LangFile; + +# The VNDB::L10N module is not really suited to be used outside the VNDB::* +# framework, but it's the central location that defines which languages we have +# and in what order to display them. +use VNDB::L10N; + + +my $jskeys_lang = join '|', VNDB::L10N::languages(); +my $jskeys = qr{^(?: + _lang_(?:$jskeys_lang)| + _js_.+| + _menu_emptysearch| + _vnpage_uopt_(?:10?vote|rel.+)| + _rlst_[vr]stat_.+| + _vnedit_rel_(?:isa|of|addbut|del|none|findformat|novn|double)| + _redit_form_med_.+| + _vnedit_scr_.+| + _tagv_(?:add|spoil\d|notfound|nometa|double)| + _redit_form_vn_(?:addbut|remove|none|vnformat|notfound|double)| + _redit_form_prod_(?:addbut|remove|none|pformat|notfound|double)| + _pedit_rel_(?:addbut|del|none|findformat|notfound|double) + )$}x; + +sub l10n { + # Using JSON::XS or something may be shorter and less error prone, + # although I would have less power over the output (mostly the quoting of the keys) + + my $lang = LangFile->new(read => "$ROOT/data/lang.txt"); + my @r; + push @r, 'L10N_STR = {'; + my $cur; # undef = none/excluded, 1 = awaiting first TL line, 2 = after first TL line + my %lang; + while((my $l = $lang->read())) { + my $type = shift @$l; + if($type eq 'key') { + my $key = shift @$l; + push @r, ' }' if $cur; + $cur = $key =~ $jskeys ? 1 : undef; + if($cur) { + $r[$#r] .= ',' if $r[$#r] =~ /}$/; + # let's assume key names don't trigger a reserved word in JS + $key = qq{"$key"} if $key !~ /^[a-z_][a-z0-9_]*$/i; + push @r, qq| $key: {|; + } + } + $lang{$l->[0]} = 1 if $type eq 'tl'; + if($type eq 'tl' && $cur) { + my($lang, $sync, $val) = @$l; + next if !$val; + $val =~ s/"/\\"/g; + $val =~ s/\n/\\n/g; + $r[$#r] .= ',' if $cur == 2; + $lang = qq{"$l->[0]"} if $lang =~ /^(?:as|do|if|in|is)$/; # reserved two-char words + push @r, qq| $lang: "$val"|; + $cur = 2; + } + } + push @r, ' }' if $cur; + push @r, '};'; + push @r, 'L10N_LANG = [ '.join(', ', map qq{"$_"}, VNDB::L10N::languages()).' ];'; + return join "\n", @r; +} + + +sub jsgen { + # JavaScript::Minifier::XS doesn't correctly handle perl's unicode, + # so just do everything in raw bytes instead. + my $js = encode_utf8(l10n()) . "\n"; + $js .= sprintf "rlst_rstat = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{rlst_rstat}}; + $js .= sprintf "rlst_vstat = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{rlst_vstat}}; + open my $JS, '<', "$ROOT/data/script.js" or die $!; + $js .= join '', <$JS>; + close $JS; + open my $NEWJS, '>', "$ROOT/static/f/script.js" or die $!; + print $NEWJS $JavaScript::Minifier::XS::VERSION ? JavaScript::Minifier::XS::minify($js) : $js; + close $NEWJS; +} + +jsgen; + diff --git a/util/lang.pl b/util/lang.pl new file mode 100755 index 00000000..087dea2e --- /dev/null +++ b/util/lang.pl @@ -0,0 +1,167 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Cwd 'abs_path'; +our $ROOT; +BEGIN { ($ROOT = abs_path $0) =~ s{/util/lang\.pl$}{}; } + +use lib $ROOT.'/lib'; +use LangFile; + +my $langtxt = "$ROOT/data/lang.txt"; + + +sub usage { + print <<__; +$0 stats + Prints some stats. + +$0 add <lang> [<file>] + Adds new (empty) translation lines for language <lang> to <file> (defaults to + the global lang.txt) for keys that don't have a TL line yet. + +$0 only <lang>[,..] <outfile> + Makes a copy of lang.txt to <outfile> and removes all translations except the + ones of langauge <lang> (comma-seperated list of tags) + +$0 merge <lang> <file> + Merges <file> into lang.txt, copying over all the translations of <lang> in + <file> while ignoring any other changes. Keys in <file> not present in + lang.txt are silently ignored. Keys in lang.txt but not in <file> remain + unaffected. Make sure each key in lang.txt already has a line for <lang>, + otherwise do an 'add' first. + +$0 reorder <lang1>,<lang2>,.. + Re-orders the translation lines in lang.txt using the specified order. + +$0 stage <lang> + Puts all changes of <lang> into the git index, and leaves everything else untouched. +__ + exit; +} + + +sub stats { + my $r = LangFile->new(read => $langtxt); + my $keys = 0; + my %lang; + while(my $l = $r->read()) { + $keys++ if $l->[0] eq 'key'; + if($l->[0] eq 'tl') { + $lang{$l->[1]} ||= [0,0]; + $lang{$l->[1]}[0]++; + $lang{$l->[1]}[1]++ if $l->[2]; + } + } + print "lang lines sync unsync\n"; + printf "%3s %4d (%3d%%) %4d (%3d%%) %4d\n", $_, + $lang{$_}[0], $lang{$_}[0]/$keys*100, $lang{$_}[1], $lang{$_}[1]/$keys*100, $keys-$lang{$_}[1] + for keys %lang; + printf "Total keys: %d\n", $keys; +} + + +sub add { + my($lang, $file) = @_; + $file ||= $langtxt; + my $r = LangFile->new(read => $file); + my $w = LangFile->new(write => "$file~"); + my $k = 0; + while((my $l = $r->read())) { + if($k && $l->[0] ne 'tl') { + $k = 0; + $w->write('tl', $lang, 0, ''); + } + $k = 1 if $l->[0] eq 'key'; + $k = 0 if $l->[0] eq 'tl' && $l->[1] eq $lang; + $w->write(@$l); + } + $r->close; + $w->close; + rename "$file~", $file or die $!; +} + + +sub only { + my($lang, $out) = @_; + my @lang = split /,/, $lang; + my $r = LangFile->new(read => $langtxt); + my $w = LangFile->new(write => $out); + while((my $l = $r->read())) { + $w->write(@$l) unless $l->[0] eq 'tl' && !grep $_ eq $l->[1], @lang; + } + $r->close; + $w->close; +} + + +sub merge { + my($lang, $file) = @_; + + # read all translations in $lang in $file + my $trans = LangFile->new(read => $file); + my($key, %trans); + while((my $l = $trans->read)) { + $key = $l->[1] if $l->[0] eq 'key'; + $trans{$key} = [ $l->[2], $l->[3] ] if $l->[0] eq 'tl' && $l->[1] eq $lang; + } + $trans->close; + + # now update lang.txt + my $r = LangFile->new(read => $langtxt); + my $w = LangFile->new(write => "$langtxt~"); + while((my $l = $r->read)) { + $key = $l->[1] if $l->[0] eq 'key'; + ($l->[2], $l->[3]) = @{$trans{$key}} if $l->[0] eq 'tl' && $l->[1] eq $lang && $trans{$key}; + $w->write(@$l); + } + $r->close; + $w->close; + rename "$langtxt~", $langtxt or die $!; +} + + +sub reorder { + my @lang = split /,/, shift; + my $r = LangFile->new(read => $langtxt); + my $w = LangFile->new(write => "$langtxt~"); + my($key, %tl); + while((my $l = $r->read)) { + if($key && $l->[0] ne 'tl') { + $tl{$_} && $w->write(@{delete $tl{$_}}) for(@lang); + $w->write(@{$tl{$_}}) for sort keys %tl; + $key = undef; + %tl = (); + } + $key = $l->[1] if $l->[0] eq 'key'; + $tl{$l->[1]} = $l if $l->[0] eq 'tl'; + $w->write(@$l) unless $l->[0] eq 'tl'; + } + $r->close; + $w->close; + rename "$langtxt~", $langtxt or die $!; +} + + +sub stage { + my $lang = shift; + chdir "$ROOT/data"; + rename 'lang.txt', '.lang.txt.tmp' or die $!; + `git checkout lang.txt`; + merge $lang, '.lang.txt.tmp'; + `git add lang.txt`; + rename '.lang.txt.tmp', 'lang.txt'; +} + + +usage if !@ARGV; +my $act = shift; +stats if $act eq 'stats'; +add @ARGV if $act eq 'add'; +only @ARGV if $act eq 'only'; +merge @ARGV if $act eq 'merge'; +reorder @ARGV if $act eq 'reorder'; +stage @ARGV if $act eq 'stage'; + diff --git a/util/skingen.pl b/util/skingen.pl index e27ea4a1..73e4fdc7 100755 --- a/util/skingen.pl +++ b/util/skingen.pl @@ -5,13 +5,12 @@ package VNDB; use strict; use warnings; use Cwd 'abs_path'; -use Data::Dumper 'Dumper'; use Image::Magick; +eval { require CSS::Minifier::XS }; our($ROOT, %O); BEGIN { ($ROOT = abs_path $0) =~ s{/util/skingen\.pl$}{}; } -require $ROOT.'/data/global.pl'; if(@ARGV) { @@ -78,19 +77,12 @@ sub writeskin { # $obj # write the CSS open my $CSS, '<', "$ROOT/data/style.css" or die $!; + my $css = join '', <$CSS>; + close $CSS; + $css =~ s/\$$_\$/$o->{$_}/g for (keys %$o); open my $SKIN, '>', "$ROOT/static/s/$o->{_name}/style.css" or die $!; - while((my $d = <$CSS>)) { - if($O{debug}) { - chomp $d; - $d =~ s/^\s*/ /; - $d =~ s{/\*.+\*/}{}; # NOTE: multiline comments or multiple comments per line won't work - next if $d !~ /[^\s\t]/; - } - $d =~ s/\$$_\$/$o->{$_}/g for (keys %$o); - print $SKIN $d; - } + print $SKIN $CSS::Minifier::XS::VERSION ? CSS::Minifier::XS::minify($css) : $css; close $SKIN; - close $CSS; } diff --git a/util/updates/update_2.8.sql b/util/updates/update_2.8.sql new file mode 100644 index 00000000..d8373210 --- /dev/null +++ b/util/updates/update_2.8.sql @@ -0,0 +1,213 @@ + +-- !BEFORE! running this SQL file, make sure to kill Multi, +-- After running this SQL file, also make sure to do a: +-- $ rm -r static/rg/ +-- And start multi again + +-- VN Relation graphs are stored in the database as SVG - no cmaps and .png anymore +UPDATE vn SET rgraph = NULL; +ALTER TABLE vn DROP CONSTRAINT vn_rgraph_fkey; +DROP TABLE relgraph; +CREATE TABLE relgraphs ( + id SERIAL PRIMARY KEY, + svg xml NOT NULL +); +ALTER TABLE vn ADD FOREIGN KEY (rgraph) REFERENCES relgraphs (id); + + +-- VN relations stored as enum +CREATE TYPE vn_relation AS ENUM ('seq', 'preq', 'set', 'alt', 'char', 'side', 'par', 'ser', 'fan', 'orig'); +ALTER TABLE vn_relations ALTER COLUMN relation DROP DEFAULT; +ALTER TABLE vn_relations ALTER COLUMN relation TYPE vn_relation USING + CASE + WHEN relation = 0 THEN 'seq'::vn_relation + WHEN relation = 1 THEN 'preq' + WHEN relation = 2 THEN 'set' + WHEN relation = 3 THEN 'alt' + WHEN relation = 4 THEN 'char' + WHEN relation = 5 THEN 'side' + WHEN relation = 6 THEN 'par' + WHEN relation = 7 THEN 'ser' + WHEN relation = 8 THEN 'fan' + ELSE 'orig' + END; + + +-- producer relations +CREATE TYPE producer_relation AS ENUM ('old', 'new', 'sub', 'par', 'imp', 'ipa', 'spa', 'ori'); +CREATE TABLE producers_relations ( + pid1 integer NOT NULL REFERENCES producers_rev (id), + pid2 integer NOT NULL REFERENCES producers (id), + relation producer_relation NOT NULL, + PRIMARY KEY(pid1, pid2) +); +ALTER TABLE producers ADD COLUMN rgraph integer REFERENCES relgraphs (id); + +CREATE OR REPLACE FUNCTION producer_relgraph_notify() RETURNS trigger AS $$ +BEGIN + -- 1. + IF TG_TABLE_NAME = 'producers' THEN + IF NEW.rgraph IS NULL AND EXISTS(SELECT 1 FROM producers_relations WHERE pid1 = NEW.latest) THEN + NOTIFY relgraph; + END IF; + END IF; + IF TG_TABLE_NAME = 'producers' AND TG_OP = 'UPDATE' THEN + IF NEW.rgraph IS NOT NULL AND OLD.latest > 0 THEN + -- 3 & 4 + IF OLD.latest <> NEW.latest AND ( + EXISTS(SELECT 1 FROM producers_rev p1, producers_rev p2 WHERE (p2.name <> p1.name OR p2.type <> p1.type OR p2.lang <> p1.lang) AND p1.id = OLD.latest AND p2.id = NEW.latest) + OR EXISTS(SELECT p1.pid2, p1.relation FROM producers_relations p1 WHERE p1.pid1 = OLD.latest EXCEPT SELECT p2.pid2, p2.relation FROM producers_relations p2 WHERE p2.pid1 = NEW.latest) + OR EXISTS(SELECT p1.pid2, p1.relation FROM producers_relations p1 WHERE p1.pid1 = NEW.latest EXCEPT SELECT p2.pid2, p2.relation FROM producers_relations p2 WHERE p2.pid1 = OLD.latest) + ) THEN + UPDATE producers SET rgraph = NULL WHERE id = NEW.id; + END IF; + END IF; + END IF; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +CREATE CONSTRAINT TRIGGER vn_relgraph_notify AFTER INSERT OR UPDATE ON producers DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE producer_relgraph_notify(); + + +-- Anime types stored as enum +CREATE TYPE anime_type AS ENUM ('tv', 'ova', 'mov', 'oth', 'web', 'spe', 'mv'); +ALTER TABLE anime ALTER COLUMN type TYPE anime_type USING + CASE + WHEN type = 0 THEN 'tv'::anime_type + WHEN type = 1 THEN 'ova' + WHEN type = 2 THEN 'mov' + WHEN type = 3 THEN 'oth' + WHEN type = 4 THEN 'web' + WHEN type = 5 THEN 'spe' + WHEN type = 6 THEN 'mv' + ELSE NULL + END; + + +-- Release media stored as enum +CREATE TYPE medium AS ENUM ('cd', 'dvd', 'gdr', 'blr', 'flp', 'mrt', 'mem', 'umd', 'nod', 'in', 'otc'); +ALTER TABLE releases_media ALTER COLUMN medium DROP DEFAULT; +ALTER TABLE releases_media ALTER COLUMN medium TYPE medium USING TRIM(both ' ' from medium)::medium; + + +-- Differentiate between publishers and developers +ALTER TABLE releases_producers ADD COLUMN developer boolean NOT NULL DEFAULT FALSE; +ALTER TABLE releases_producers ADD COLUMN publisher boolean NOT NULL DEFAULT TRUE; +ALTER TABLE releases_producers ADD CHECK(developer OR publisher); + + +-- Keep track of last read post for PMs +ALTER TABLE threads_boards ADD COLUMN lastread smallint; + + +-- changes.type stored as enum +CREATE TYPE dbentry_type AS ENUM ('v', 'r', 'p'); +ALTER TABLE changes ALTER COLUMN type DROP DEFAULT; +ALTER TABLE changes ALTER COLUMN type TYPE dbentry_type USING + CASE + WHEN type = 0 THEN 'v'::dbentry_type + WHEN type = 1 THEN 'r' + WHEN type = 2 THEN 'p' + ELSE NULL -- not allowed to happen, otherwise FIX YOUR DATABASE! + END; + + +-- releases_rev.type stored as enum +CREATE TYPE release_type AS ENUM ('complete', 'partial', 'trial'); +ALTER TABLE releases_rev ALTER COLUMN type DROP DEFAULT; +ALTER TABLE releases_rev ALTER COLUMN type TYPE release_type USING + CASE + WHEN type = 0 THEN 'complete'::release_type + WHEN type = 1 THEN 'partial' + WHEN type = 2 THEN 'trial' + ELSE NULL + END; +ALTER TABLE releases_rev ALTER COLUMN type SET DEFAULT 'complete'; + +CREATE OR REPLACE FUNCTION update_vncache(id integer) RETURNS void AS $$ +DECLARE + w text := ''; +BEGIN + IF id > 0 THEN + w := ' WHERE id = '||id; + END IF; + EXECUTE 'UPDATE vn SET + c_released = COALESCE((SELECT + MIN(rr1.released) + FROM releases_rev rr1 + JOIN releases r1 ON rr1.id = r1.latest + JOIN releases_vn rv1 ON rr1.id = rv1.rid + WHERE rv1.vid = vn.id + AND rr1.type <> ''trial'' + AND r1.hidden = FALSE + AND rr1.released <> 0 + GROUP BY rv1.vid + ), 0), + c_languages = COALESCE(ARRAY_TO_STRING(ARRAY( + SELECT rl2.lang + FROM releases_rev rr2 + JOIN releases_lang rl2 ON rl2.rid = rr2.id + JOIN releases r2 ON rr2.id = r2.latest + JOIN releases_vn rv2 ON rr2.id = rv2.rid + WHERE rv2.vid = vn.id + AND rr2.type <> ''trial'' + AND rr2.released <= TO_CHAR(''today''::timestamp, ''YYYYMMDD'')::integer + AND r2.hidden = FALSE + GROUP BY rl2.lang + ORDER BY rl2.lang + ), ''/''), ''''), + c_platforms = COALESCE(ARRAY_TO_STRING(ARRAY( + SELECT rp3.platform + FROM releases_platforms rp3 + JOIN releases_rev rr3 ON rp3.rid = rr3.id + JOIN releases r3 ON rp3.rid = r3.latest + JOIN releases_vn rv3 ON rp3.rid = rv3.rid + WHERE rv3.vid = vn.id + AND rr3.type <> ''trial'' + AND rr3.released <= TO_CHAR(''today''::timestamp, ''YYYYMMDD'')::integer + AND r3.hidden = FALSE + GROUP BY rp3.platform + ORDER BY rp3.platform + ), ''/''), '''') + '||w; +END; +$$ LANGUAGE plpgsql; + + + +-- fix calculation of the tags_vn_bayesian.spoiler column + +CREATE OR REPLACE FUNCTION tag_vn_calc() RETURNS void AS $$ +BEGIN + -- all votes for all tags + CREATE OR REPLACE TEMPORARY VIEW tags_vn_all AS + SELECT * FROM tags_vn UNION SELECT * FROM tag_vn_childs(); + -- grouped by (tag, vid, uid), so only one user votes on one parent tag per VN entry + CREATE OR REPLACE TEMPORARY VIEW tags_vn_grouped AS + SELECT tag, vid, uid, MAX(vote)::real AS vote, AVG(spoiler)::real AS spoiler + FROM tags_vn_all GROUP BY tag, vid, uid; + -- grouped by (tag, vid) and serialized into a table + DROP INDEX IF EXISTS tags_vn_bayesian_tag; + TRUNCATE tags_vn_bayesian; + INSERT INTO tags_vn_bayesian + SELECT tag, vid, COUNT(uid) AS users, AVG(vote)::real AS rating, + (CASE WHEN AVG(spoiler) < 0.7 THEN 0 WHEN AVG(spoiler) > 1.3 THEN 2 ELSE 1 END)::smallint AS spoiler + FROM tags_vn_grouped + GROUP BY tag, vid + HAVING AVG(vote) > 0; + CREATE INDEX tags_vn_bayesian_tag ON tags_vn_bayesian (tag); + -- now perform the bayesian ranking calculation + UPDATE tags_vn_bayesian tvs SET rating = + ((SELECT AVG(users)::real * AVG(rating)::real FROM tags_vn_bayesian WHERE tag = tvs.tag) + users*rating) + / ((SELECT AVG(users)::real FROM tags_vn_bayesian WHERE tag = tvs.tag) + users)::real; + -- and update the VN count in the tags table as well + UPDATE tags SET c_vns = (SELECT COUNT(*) FROM tags_vn_bayesian WHERE tag = id); + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT tag_vn_calc(); + + +-- remove update_rev() +DROP FUNCTION update_rev(text, text); + diff --git a/util/vndb.pl b/util/vndb.pl index ed74a92f..4752c274 100755 --- a/util/vndb.pl +++ b/util/vndb.pl @@ -28,6 +28,10 @@ our(%O, %S); $S{skins} = readskins(); +# automatically regenerate script.js when required and possible +checkjs(); + + # load lang.dat VNDB::L10N::loadfile(); @@ -113,3 +117,18 @@ sub readskins { return \%skins; } + +sub checkjs { + my $script = "$ROOT/static/f/script.js"; + my $lastmod = [stat $script]->[9]; + system "$ROOT/util/jsgen.pl" if + (!-e $script && -x "$ROOT/static/f") + || (-e $script && -w $script && ( + $lastmod < [stat "$ROOT/data/script.js"]->[9] + || $lastmod < [stat "$ROOT/data/lang.txt"]->[9] + || (-e "$ROOT/data/config.pl" && $lastmod < [stat "$ROOT/data/config.pl"]->[9]) + || $lastmod < [stat "$ROOT/data/global.pl"]->[9] + || $lastmod < [stat "$ROOT/util/jsgen.pl"]->[9] + )); +} + |