97
autoload.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
if (!file_exists(__DIR__ . "/.installed")) {
|
||||
header("Location: install.php");
|
||||
die("System not installed.");
|
||||
}
|
||||
|
||||
// Main-config
|
||||
require_once "config.php";
|
||||
$config["debug"] == true ? error_reporting(E_ALL) && ini_set('display_errors', 1) : error_reporting(0) && ini_set('display_errors', 0);
|
||||
require_once "funky.php";
|
||||
|
||||
// SleekDB
|
||||
require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php");
|
||||
$db["users"] = new \SleekDB\Store("users", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["sessions"] = new \SleekDB\Store("sessions", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["alerts"] = new \SleekDB\Store("alerts", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["alertReads"] = new \SleekDB\Store("alertReads", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["titles"] = new \SleekDB\Store("titles", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["chapters"] = new \SleekDB\Store("chapters", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["visitLogs"] = new \SleekDB\Store("visitLogs", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["chapterComments"] = new \SleekDB\Store("chapterComments", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["titleComments"] = new \SleekDB\Store("titleComments", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["profileComments"] = new \SleekDB\Store("profileComments", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["titleViews"] = new \SleekDB\Store("titleViews", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["chapterViews"] = new \SleekDB\Store("chapterViews", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["profileViews"] = new \SleekDB\Store("profileViews", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["activation"] = new \SleekDB\Store("activation", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$db["readChapters"] = new \SleekDB\Store("readChapters", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
|
||||
// Session
|
||||
require_once "session.php";
|
||||
|
||||
// Theme, language and reading language
|
||||
$usertheme = getUserTheme($logged, $user["theme"] ?? "");
|
||||
$userlang = getUserLang($logged, $user["lang"] ?? "");
|
||||
$preflang = getPrefLang();
|
||||
|
||||
// Parsedown
|
||||
require_once ps(__DIR__ . $config["path"]["parsedown"] . "/Parsedown.php");
|
||||
$parsedown = new Parsedown();
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
// HTML-Purifier
|
||||
require_once ps(__DIR__ . $config["path"]["htmlpurifier"] . "/HTMLPurifier.auto.php");
|
||||
$purifier = new HTMLPurifier(HTMLPurifier_Config::createDefault());
|
||||
|
||||
// PHPMailer
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
require_once ps(__DIR__ . $config["path"]["phpmailer"] . "/Exception.php");
|
||||
require_once ps(__DIR__ . $config["path"]["phpmailer"] . "/PHPMailer.php");
|
||||
require_once ps(__DIR__ . $config["path"]["phpmailer"] . "/SMTP.php");
|
||||
|
||||
$mailer = new PHPMailer(true);
|
||||
|
||||
// Smarty
|
||||
require_once ps(__DIR__ . $config["path"]["smarty"] . "/Smarty.class.php");
|
||||
$smarty = new Smarty();
|
||||
$smarty->setTemplateDir(ps(__DIR__ . $config["smarty"]["template"] . "/" . $usertheme));
|
||||
$smarty->setConfigDir(ps(__DIR__ . $config["smarty"]["config"]));
|
||||
$smarty->setCompileDir(ps(__DIR__ . $config["smarty"]["compile"]));
|
||||
$smarty->setCacheDir(ps(__DIR__ . $config["smarty"]["cache"]));
|
||||
|
||||
// Getting all plugins for the Theme
|
||||
require ps(__DIR__ . $config["smarty"]["template"] . "/{$usertheme}/info.php");
|
||||
foreach ($theme["plugins"] as $reqPlugin) {
|
||||
if (!file_exists(ps(__DIR__ . $config["path"]["plugins"] . "/enabled/" . $reqPlugin . ".php")))
|
||||
die("This theme requires following plugin to be enabled: " . $reqPlugin);
|
||||
require_once ps(__DIR__ . $config["path"]["plugins"] . "/enabled/" . $reqPlugin . ".php");
|
||||
}
|
||||
|
||||
// Plugins (Legacy)
|
||||
// $plugins = glob(ps(__DIR__ . $config["path"]["plugins"] . "/enabled/*.php"));
|
||||
// foreach ($plugins as $plugin) {
|
||||
// require_once $plugin;
|
||||
// }
|
||||
|
||||
// And finally
|
||||
$usertheme = getUserTheme($logged, $user["theme"] ?? "");
|
||||
$userlang = getUserLang($logged, $user["lang"] ?? "");
|
||||
$preflang = getPrefLang();
|
||||
require ps(__DIR__ . $config["smarty"]["template"] . "/{$usertheme}/info.php");
|
||||
require ps(__DIR__ . $config["path"]["langs"] . "/{$userlang}.php");
|
||||
|
||||
visit();
|
||||
|
||||
$smarty->assign("config", $config);
|
||||
$smarty->assign("lang", $lang);
|
||||
$smarty->assign("theme", $theme);
|
||||
$smarty->assign("userlang", $userlang);
|
||||
$smarty->assign("usertheme", $usertheme);
|
||||
$smarty->assign("version", file_get_contents(ps(__DIR__ . "/version.txt")));
|
||||
$smarty->assign("logged", $logged);
|
||||
$smarty->assign("user", $user);
|
||||
$smarty->assign("preflang", $preflang);
|
81
config.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
$config["title"] = "FoOlSlideX";
|
||||
$config["divider"] = ".::.";
|
||||
$config["slogan"] = "Mangas for Fools!";
|
||||
$config["logs"] = true;
|
||||
$config["debug"] = true;
|
||||
$config["url"] = "http://localhost/fsx/public/";
|
||||
$config["email"] = "saintly@h33t.moe";
|
||||
$config["activation"] = false; // Activate account through email?
|
||||
$config["shareAnonymousAnalytics"] = true;
|
||||
$config["api"] = true; // not really working rn, still please let it enabled until newer versions
|
||||
|
||||
$config["db"]["type"] = "sleek"; // "sleek"
|
||||
$config["db"]["sleek"]["dir"] = "/database";
|
||||
$config["db"]["sleek"]["config"] = array(
|
||||
"auto_cache" => true,
|
||||
"cache_lifetime" => null,
|
||||
"timeout" => false, // deprecated! Set it to false!
|
||||
"primary_key" => "id",
|
||||
"search" => array(
|
||||
"min_length" => 2,
|
||||
"mode" => "or",
|
||||
"score_key" => "scoreKey"
|
||||
),
|
||||
"folder_permissions" => 0777
|
||||
);
|
||||
|
||||
// MaxFileSize (in Bytes, visit https://www.gbmb.org/mb-to-bytes for help)
|
||||
$config["mfs"]["cover"] = 5242880; // 5MB
|
||||
$config["mfs"]["chapter"] = 52428800; // 50MB
|
||||
|
||||
// Default-Variablen
|
||||
$config["default"]["theme"] = "nucleus";
|
||||
$config["default"]["lang"] = "en";
|
||||
$config["default"]["avatar"] = "https://rav.h33t.moe/data/8c4f8647-4bec-420e-96e0-284125793baf.jpeg";
|
||||
|
||||
// Captcha
|
||||
$config["captcha"]["enabled"] = false;
|
||||
$config["captcha"]["type"] = "hcaptcha"; // "hcaptcha"
|
||||
$config["captcha"]["hcaptcha"]["secret"] = "";
|
||||
$config["captcha"]["hcaptcha"]["sitekey"] = "";
|
||||
|
||||
// Themes and Languages
|
||||
$config["themes"] = array(
|
||||
"nucleus" => "Nucleus"
|
||||
);
|
||||
$config["langs"] = array(
|
||||
"en" => "English",
|
||||
);
|
||||
|
||||
// Avatars
|
||||
$config["avatars"] = array(
|
||||
"https://rav.h33t.moe/data/8c4f8647-4bec-420e-96e0-284125793baf.jpeg",
|
||||
"https://rav.h33t.moe/data/cf39f370-73f9-4428-bc65-136e1d509cc9.jpeg",
|
||||
"https://rav.shishnet.org/af4cf8aadc45565bbeecb0ced4857ed5.jpeg",
|
||||
"https://rav.shishnet.org/4f054d8a364b1cb658a458df687621c6.jpeg",
|
||||
"https://rav.shishnet.org/21e78a09e342964a8c7ce20e410697d0.jpeg",
|
||||
"https://rav.shishnet.org/436e8788f8ed655e9c74c54dc9947d00.jpeg",
|
||||
"https://rav.shishnet.org/3737e6629146ba78fb4a84284357802a.gif",
|
||||
"https://rav.shishnet.org/c6bbc472ba1ec7012620ac0c1b35491a.gif",
|
||||
);
|
||||
|
||||
// X elements per page for pagination
|
||||
$config["perpage"]["titles"] = 25;
|
||||
$config["perpage"]["chapters"] = 36;
|
||||
|
||||
$config["path"]["sleek"] = "/software/SleekDB";
|
||||
$config["path"]["parsedown"] = "/software";
|
||||
$config["path"]["langs"] = "/library/langs";
|
||||
$config["path"]["htmlpurifier"] = "/software/HTMLPurifier";
|
||||
$config["path"]["smarty"] = "/software/Smarty";
|
||||
$config["path"]["plugins"] = "/library/plugins";
|
||||
$config["path"]["phpmailer"] = "/software/PHPMailer";
|
||||
$config["path"]["imagescrambler"] = "/software";
|
||||
|
||||
// Diese Software nutzt Smarty als Template-Engine. Dokumentation: https://smarty-php.github.io/smarty/
|
||||
$config["smarty"]["template"] = "/library/themes";
|
||||
$config["smarty"]["config"] = "/software/Smarty/config";
|
||||
$config["smarty"]["compile"] = "/software/Smarty/compile";
|
||||
$config["smarty"]["cache"] = "/software/Smarty/cache";
|
15
custom/format.json
Normal file
@ -0,0 +1,15 @@
|
||||
[
|
||||
"4-Koma",
|
||||
"Adaption",
|
||||
"Anthology",
|
||||
"Award Winning",
|
||||
"Doujinshi",
|
||||
"Fan Colored",
|
||||
"Fan Color",
|
||||
"Full Color",
|
||||
"Long Strip",
|
||||
"Official Colored",
|
||||
"Oneshot",
|
||||
"User Created",
|
||||
"Web Comic"
|
||||
]
|
27
custom/genre.json
Normal file
@ -0,0 +1,27 @@
|
||||
[
|
||||
"Action",
|
||||
"Adventure",
|
||||
"Boys' Love",
|
||||
"Comedy",
|
||||
"Crime",
|
||||
"Drama",
|
||||
"Fantasy",
|
||||
"Girls' Love",
|
||||
"Historical",
|
||||
"Horror",
|
||||
"Isekai",
|
||||
"Magical Girls",
|
||||
"Mecha",
|
||||
"Medical",
|
||||
"Mystery",
|
||||
"Philosophical",
|
||||
"Psychological",
|
||||
"Romance",
|
||||
"Sci-Fi",
|
||||
"Slice of Life",
|
||||
"Sports",
|
||||
"Superhero",
|
||||
"Thriller",
|
||||
"Tragedy",
|
||||
"Wuxia"
|
||||
]
|
39
custom/theme.json
Normal file
@ -0,0 +1,39 @@
|
||||
[
|
||||
"Aliens",
|
||||
"Animals",
|
||||
"Cooking",
|
||||
"Crossdressing",
|
||||
"Delinquents",
|
||||
"Demons",
|
||||
"Genderswap",
|
||||
"Ghosts",
|
||||
"Gyaru",
|
||||
"Harem",
|
||||
"Incest",
|
||||
"Loli",
|
||||
"Mafia",
|
||||
"Magic",
|
||||
"Martial Arts",
|
||||
"Military",
|
||||
"Monster Girls",
|
||||
"Monsters",
|
||||
"Music",
|
||||
"Ninja",
|
||||
"Office Workers",
|
||||
"Police",
|
||||
"Post-Apocalyptic",
|
||||
"Reincarnation",
|
||||
"Reverse Harem",
|
||||
"Samurai",
|
||||
"School Life",
|
||||
"Shota",
|
||||
"Supernatural",
|
||||
"Survival",
|
||||
"Time Travel",
|
||||
"Traditional Games",
|
||||
"Vampires",
|
||||
"Video Games",
|
||||
"Villainess",
|
||||
"Virtual Reality",
|
||||
"Zombies"
|
||||
]
|
22
custom/upload_langs.json
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
[
|
||||
"gb",
|
||||
"English",
|
||||
"🇬🇧"
|
||||
],
|
||||
[
|
||||
"de",
|
||||
"German",
|
||||
"🇩🇪"
|
||||
],
|
||||
[
|
||||
"pt",
|
||||
"Portuguese",
|
||||
"🇵🇹"
|
||||
],
|
||||
[
|
||||
"fr",
|
||||
"French",
|
||||
"🇫🇷"
|
||||
]
|
||||
]
|
5
custom/warnings.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
"Gore",
|
||||
"Sexual Violence",
|
||||
"Smut"
|
||||
]
|
396
funky.php
Normal file
@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
function clean($data)
|
||||
{
|
||||
// This function is used, to completely sanitize user-input and make any form of scripts harmless and displayable
|
||||
$data = htmlspecialchars($data);
|
||||
$data = strip_tags($data);
|
||||
$data = stripslashes($data);
|
||||
$data = trim($data);
|
||||
$data = str_replace("'", "\'", $data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
function cat($title)
|
||||
{
|
||||
// This function is used, to make all titles readable for the URL and links
|
||||
return preg_replace('/[^A-Za-z0-9\-_,.]/', '', str_replace("&", "et", str_replace(' ', '-', strtolower($title))));
|
||||
}
|
||||
|
||||
function namba($data)
|
||||
{
|
||||
return preg_replace("/[^0-9.]/i", "", str_replace(",", ".", $data));
|
||||
}
|
||||
|
||||
function now()
|
||||
{
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
function jd($text)
|
||||
{
|
||||
return json_decode($text, true);
|
||||
}
|
||||
|
||||
function je($text)
|
||||
{
|
||||
return json_encode($text, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
function ps($path)
|
||||
{
|
||||
return str_replace('/', DIRECTORY_SEPARATOR, $path);
|
||||
}
|
||||
|
||||
function genUuid()
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0C2f) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0x2Aff),
|
||||
mt_rand(0, 0xffD3),
|
||||
mt_rand(0, 0xff4B)
|
||||
);
|
||||
}
|
||||
|
||||
function genToken()
|
||||
{
|
||||
return md5(rand());
|
||||
}
|
||||
|
||||
function formatBytes($size, $precision = 2)
|
||||
{
|
||||
$base = log($size, 1024);
|
||||
$suffixes = array('B', 'KB', 'MB', 'GB', 'TB');
|
||||
|
||||
// return round(pow(1024, $base - floor($base)), $precision) . ' ' . $suffixes[floor($base)];
|
||||
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
|
||||
}
|
||||
|
||||
function doLog($action, bool $success, $value = null, $user = null)
|
||||
{
|
||||
require "config.php";
|
||||
if ($config["logs"]) {
|
||||
require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php");
|
||||
$db = new \SleekDB\Store("logs", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]); // Logs
|
||||
if (empty($action)) return false;
|
||||
if (!empty($user) && !is_numeric($user)) return false;
|
||||
$data = array(
|
||||
"action" => clean($action),
|
||||
"success" => $success,
|
||||
"value" => clean($value),
|
||||
"user" => $user,
|
||||
"ip" => clean($_SERVER["REMOTE_ADDR"]),
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db->insert($data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function visit()
|
||||
{
|
||||
require "config.php";
|
||||
require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php");
|
||||
$db = new \SleekDB\Store("visitLogs", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]); // Logs
|
||||
if (empty($db->findOneBy(["ip", "=", clean($_SERVER["REMOTE_ADDR"])]))) {
|
||||
$data = array(
|
||||
"ip" => clean($_SERVER["REMOTE_ADDR"]),
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db->insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
function titleVisit($title)
|
||||
{
|
||||
require "config.php";
|
||||
require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php");
|
||||
$db = new \SleekDB\Store("titleViews", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]); // Logs
|
||||
if (empty($db->findOneBy([["ip", "=", clean($_SERVER["REMOTE_ADDR"])], "AND", ["title.id", "=", $title["id"]]]))) {
|
||||
$data = array(
|
||||
"title" => $title,
|
||||
"ip" => clean($_SERVER["REMOTE_ADDR"]),
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db->insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
function chapterVisit($chapter)
|
||||
{
|
||||
require "config.php";
|
||||
require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php");
|
||||
$db = new \SleekDB\Store("chapterViews", ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]); // Logs
|
||||
if (empty($db->findOneBy([["ip", "=", clean($_SERVER["REMOTE_ADDR"])], "AND", ["chapter.id", "=", $chapter["id"]]]))) {
|
||||
$data = array(
|
||||
"chapter" => $chapter,
|
||||
"ip" => clean($_SERVER["REMOTE_ADDR"]),
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db->insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
function hCaptcha($response)
|
||||
{
|
||||
require "config.php";
|
||||
$data = array(
|
||||
"secret" => $config["captcha"]["hcaptcha"]["secret"],
|
||||
"response" => $response
|
||||
);
|
||||
$verify = curl_init();
|
||||
curl_setopt($verify, CURLOPT_URL, "https://hcaptcha.com/siteverify");
|
||||
curl_setopt($verify, CURLOPT_POST, true);
|
||||
curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));
|
||||
curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
|
||||
$responseData = json_decode(curl_exec($verify));
|
||||
return $responseData->success ? true : false;
|
||||
}
|
||||
|
||||
function valCustom($file)
|
||||
{
|
||||
require "config.php";
|
||||
require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php");
|
||||
$db = new \SleekDB\Store("custom_" . $file, ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$file = file_exists(ps(__DIR__ . "/custom/{$file}.json")) ? file_get_contents(ps(__DIR__ . "/custom/{$file}.json")) : "";
|
||||
$data = jd($file);
|
||||
$_data = $db->findAll();
|
||||
if (!empty($data)) {
|
||||
sort($data);
|
||||
foreach ($data as $dat) {
|
||||
$dta = array(
|
||||
"name" => $dat,
|
||||
"timestamp" => now()
|
||||
);
|
||||
if (empty($db->findOneBy(["name", "=", $dat]))) $db->insert($dta);
|
||||
}
|
||||
}
|
||||
if (!empty($_data)) {
|
||||
sort($_data);
|
||||
foreach ($_data as $dat) {
|
||||
if (!in_array($dat["name"], $data)) {
|
||||
$db->deleteBy(["name", "=", $dat["name"]]);
|
||||
}
|
||||
}
|
||||
return $_data;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function valCLang($file)
|
||||
{
|
||||
require "config.php";
|
||||
require_once ps(__DIR__ . $config["path"]["sleek"] . "/Store.php");
|
||||
$db = new \SleekDB\Store("custom_" . $file, ps(__DIR__ . $config["db"]["sleek"]["dir"]), $config["db"]["sleek"]["config"]);
|
||||
$file = file_exists(ps(__DIR__ . "/custom/{$file}.json")) ? file_get_contents(ps(__DIR__ . "/custom/{$file}.json")) : "";
|
||||
$data = jd($file);
|
||||
$_data = $db->findAll();
|
||||
if (!empty($_data)) {
|
||||
sort($_data);
|
||||
$langs = array_column($data, 0);
|
||||
$langs = array_reverse($langs);
|
||||
foreach ($_data as $dat) {
|
||||
$found = false;
|
||||
foreach ($langs as $l) {
|
||||
if ($dat["code"] == $l) $found = true;
|
||||
}
|
||||
if (!$found) $db->deleteBy(["code", "=", $dat["code"]]);
|
||||
}
|
||||
}
|
||||
if (!empty($data)) {
|
||||
sort($data);
|
||||
foreach ($data as $dat) {
|
||||
$dta = array(
|
||||
"code" => $dat[0],
|
||||
"name" => $dat[1],
|
||||
"flag" => $dat[2],
|
||||
"timestamp" => now()
|
||||
);
|
||||
if (empty($db->findOneBy(["code", "=", $dat[0]]))) $db->insert($dta);
|
||||
}
|
||||
}
|
||||
return !empty($_data) ? $_data : true;
|
||||
}
|
||||
|
||||
function getTag($file, $id)
|
||||
{
|
||||
$tags = valCustom($file);
|
||||
foreach ($tags as $tg) {
|
||||
if ($tg["id"] == $id) return $tg;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function rmrf($dir)
|
||||
{
|
||||
foreach (glob($dir) as $file) {
|
||||
if (is_dir($file)) {
|
||||
rmrf("$file/*");
|
||||
rmdir($file);
|
||||
} else {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function valImage($location, $file)
|
||||
{
|
||||
// Image?
|
||||
$check = getimagesize($location);
|
||||
if ($check == false) return "Fake image.";
|
||||
// In array?
|
||||
$filetypes = array("jpg", "jpeg", "png", "gif", "webp");
|
||||
if (!in_array(strtolower($file), $filetypes)) return "Invalid Image format.";
|
||||
// Yes!
|
||||
return true;
|
||||
}
|
||||
|
||||
function unzip($file, $tmp, $target)
|
||||
{
|
||||
$zip = new ZipArchive;
|
||||
$res = $zip->open($file);
|
||||
if ($res !== true) die("Something!");
|
||||
|
||||
// extract it to the path we determined above
|
||||
if (file_exists($tmp)) rmrf($tmp);
|
||||
if (file_exists($target)) rmrf($target);
|
||||
mkdir($tmp, 0755, true);
|
||||
mkdir($target, 0755, true);
|
||||
$zip->extractTo($tmp);
|
||||
$files = glob("$tmp/*.{jpg,jpeg,webp,gif,png}", GLOB_BRACE);
|
||||
if (empty($files)) return "No images in ZIP!";
|
||||
foreach ($files as $f) {
|
||||
$check = getimagesize($f);
|
||||
if ($check == false) {
|
||||
return "An error occured.";
|
||||
rmrf($tmp);
|
||||
}
|
||||
}
|
||||
sort($files, SORT_STRING);
|
||||
$c = 1;
|
||||
foreach ($files as $_file) {
|
||||
rename($_file, ps($target . "/{$c}." . strtolower(pathinfo($_file, PATHINFO_EXTENSION))));
|
||||
$c++;
|
||||
}
|
||||
$zip->close();
|
||||
rmrf($tmp);
|
||||
unlink($file);
|
||||
return "success";
|
||||
}
|
||||
|
||||
function formatChapterTitle($volume, $chapter, $type = "short")
|
||||
{
|
||||
switch ($type) {
|
||||
case "full":
|
||||
$type = "full";
|
||||
break;
|
||||
case "short":
|
||||
default:
|
||||
$type = "short";
|
||||
}
|
||||
$out = "";
|
||||
if (substr($chapter, -2) == ".00") {
|
||||
$chapter = substr($chapter, 0, -3);
|
||||
} elseif (substr($chapter, -1) == "0") {
|
||||
$chapter = substr($chapter, 0, -1);
|
||||
}
|
||||
if ($type == "short") {
|
||||
if (!empty($volume)) $out .= "Vol." . $volume . " ";
|
||||
$out .= "Ch." . $chapter;
|
||||
}
|
||||
if ($type == "full") {
|
||||
if (!empty($volume)) $out .= "Volume " . $volume . " ";
|
||||
$out .= "Chapter " . $chapter;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
function formatDate($date, $full = false)
|
||||
{
|
||||
$date = clean($date);
|
||||
|
||||
$s = $date;
|
||||
$date = strtotime($s);
|
||||
if ($full == false) {
|
||||
return date('d. M Y', $date);
|
||||
} else {
|
||||
return date('d. M Y H:m:i', $date);
|
||||
}
|
||||
}
|
||||
|
||||
function getUserLang($logged = false, $userlang = "")
|
||||
{
|
||||
require "config.php";
|
||||
$userlang = $logged ? $userlang : cat($_COOKIE[cat($config["title"]) . "_lang"] ?? $config["default"]["lang"]);
|
||||
if (!file_exists(ps(__DIR__ . $config["path"]["langs"] . "/{$userlang}.php")))
|
||||
$userlang = $config["default"]["lang"];
|
||||
return $userlang;
|
||||
}
|
||||
|
||||
function getUserTheme($logged = false, $usertheme = "")
|
||||
{
|
||||
require "config.php";
|
||||
$usertheme = $logged ? $usertheme : cat($_COOKIE[cat($config["title"]) . "_theme"] ?? $config["default"]["theme"]);
|
||||
if (!file_exists(ps(__DIR__ . $config["smarty"]["template"] . "/{$usertheme}/info.php")))
|
||||
$usertheme = $config["default"]["theme"];
|
||||
return $usertheme;
|
||||
}
|
||||
|
||||
function getPrefLang()
|
||||
{
|
||||
require "config.php";
|
||||
return isset($_COOKIE[cat($config["title"]) . "_preflang"]) && !empty($_COOKIE[cat($config["title"]) . "_preflang"]) ? cat($_COOKIE[cat($config["title"]) . "_preflang"]) : $config["default"]["lang"];
|
||||
}
|
||||
|
||||
function timeAgo($datetime, $full = false)
|
||||
{
|
||||
// Thx - https://stackoverflow.com/questions/1416697/converting-timestamp-to-time-ago-in-php-e-g-1-day-ago-2-days-ago
|
||||
$now = new DateTime;
|
||||
$ago = new DateTime($datetime);
|
||||
$diff = $now->diff($ago);
|
||||
|
||||
$diff->w = floor($diff->d / 7);
|
||||
$diff->d -= $diff->w * 7;
|
||||
|
||||
$string = array(
|
||||
'y' => 'year',
|
||||
'm' => 'month',
|
||||
'w' => 'week',
|
||||
'd' => 'day',
|
||||
'h' => 'hour',
|
||||
'i' => 'min',
|
||||
's' => 'second',
|
||||
);
|
||||
foreach ($string as $k => &$v) {
|
||||
if ($diff->$k) {
|
||||
$v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
|
||||
} else {
|
||||
unset($string[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$full) $string = array_slice($string, 0, 1);
|
||||
return $string ? implode(', ', $string) . ' ago' : 'just now';
|
||||
}
|
||||
|
||||
// Only available in PHP 8.x which I don't like at all
|
||||
if (!function_exists("str_contains")) {
|
||||
function str_contains($haystack, $needle)
|
||||
{
|
||||
return $needle !== "" && mb_strpos($haystack, $needle) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
function shorten($text, $maxlength = 25)
|
||||
{
|
||||
// This function is used, to display only a certain amount of characters
|
||||
if (strlen($text) > $maxlength)
|
||||
return substr($text, 0, $maxlength) . "...";
|
||||
return $text;
|
||||
}
|
10
library/langs/en.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
$lang = [
|
||||
"info" => [
|
||||
"name" => "English",
|
||||
"author" => "Saintly & S-VHS",
|
||||
"website" => "https://h33t.moe",
|
||||
"updated" => "30.03.2023"
|
||||
]
|
||||
];
|
14
library/plugins/disabled/getRel.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/* ************************************************************ *
|
||||
* Name: Get Rel *
|
||||
* Author: Saintly *
|
||||
* Website: https://h33t.moe *
|
||||
* Last Updated: 13.02.2023 *
|
||||
* ------------------------------------------------------------ *
|
||||
* This plugin is used to get if rel isset per get request and *
|
||||
* if it is "rel", it assigns it to smarty. *
|
||||
* ************************************************************ */
|
||||
|
||||
if (isset($_GET["rel"]) && !empty($_GET["rel"]))
|
||||
$smarty->assign("rel", clean($_GET["rel"]));
|
1
library/plugins/disabled/keep.txt
Normal file
@ -0,0 +1 @@
|
||||
Keep this, GitHub!
|
17
library/plugins/enabled/daisyTheme.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/* ************************************************************ *
|
||||
* Name: Daisy Theme *
|
||||
* Author: Saintly *
|
||||
* Website: https://h33t.moe *
|
||||
* Last Updated: 28.01.2023 *
|
||||
* ------------------------------------------------------------ *
|
||||
* This plugin gets the cookie "{prefix}_daisytheme" and pushes *
|
||||
* it to Smarty as a variable. *
|
||||
* ************************************************************ */
|
||||
|
||||
$daisytheme = cat($_COOKIE[cat($config["title"]) . "_daisytheme"] ?? "light");
|
||||
if (isset($smarty)) {
|
||||
$smarty->assign("daisytheme", $daisytheme);
|
||||
unset($daisytheme);
|
||||
}
|
20
library/plugins/enabled/logoImage.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/* ************************************************************ *
|
||||
* Name: Logo Image *
|
||||
* Author: Saintly *
|
||||
* Website: https://h33t.moe *
|
||||
* Last Updated: 28.01.2023 *
|
||||
* ------------------------------------------------------------ *
|
||||
* This plugin checks if the logo in the theme folder exists *
|
||||
* and pushes it to Smarty. If it doesn't exist, it will assign *
|
||||
* and empty value to "logoImage". *
|
||||
* ************************************************************ */
|
||||
|
||||
$usertheme = getUserTheme($logged, $user["theme"] ?? "");
|
||||
$logoImage = file_exists(ps(__DIR__ . "/../../../public/assets/{$usertheme}/logo.png")) ? "assets/{$usertheme}/logo.png" : "";
|
||||
if (isset($smarty)) {
|
||||
$smarty->assign("logoImage", $logoImage);
|
||||
unset($logoImage);
|
||||
unset($usertheme);
|
||||
}
|
25
library/plugins/enabled/readChapters.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/* ************************************************************ *
|
||||
* Name: Read Chapters *
|
||||
* Author: Saintly *
|
||||
* Website: https://h33t.moe *
|
||||
* Last Updated: 24.02.2023 *
|
||||
* ------------------------------------------------------------ *
|
||||
* Gets all read chapters from Database and puts the into an *
|
||||
* array which gets pushed to Smarty. *
|
||||
* ************************************************************ */
|
||||
|
||||
if ($logged) {
|
||||
$readChapters = $db["readChapters"]->findBy(["user", "==", $user["id"]]);
|
||||
$_readChapters = array();
|
||||
foreach ($readChapters as $key => $ch) {
|
||||
array_push($_readChapters, $ch["chapter"]);
|
||||
}
|
||||
if (isset($smarty))
|
||||
$smarty->assign("readChapters", $_readChapters);
|
||||
|
||||
unset($ch);
|
||||
unset($_readChapters);
|
||||
unset($readChapters);
|
||||
}
|
32
library/plugins/enabled/simpleAlert.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/* ************************************************************ *
|
||||
* Name: Simple Alert *
|
||||
* Author: Saintly *
|
||||
* Website: https://h33t.moe *
|
||||
* Last Updated: 28.01.2023 *
|
||||
* ------------------------------------------------------------ *
|
||||
* This plugin gets the latest Alert and assigns it to Smarty. *
|
||||
* ************************************************************ */
|
||||
|
||||
/*
|
||||
// Alert structure:
|
||||
$data = array(
|
||||
"type" => "info", // "", "info", "success", "warning", "error"
|
||||
"content" => "Welcome to {$config["title"]}! To use all the functions such as commenting or Bookmarking, please create a free acount!",
|
||||
"timestamp" => now()
|
||||
);
|
||||
// Database Insert
|
||||
$db["alerts"]->insert($data);
|
||||
*/
|
||||
$topAlert = $db["alerts"]->findAll(["id" => "DESC"], 1);
|
||||
if (!empty($topAlert)) $topAlert = $topAlert[0];
|
||||
$readAlert = !empty($topAlert) ? ($logged ? (!empty($db["alertReads"]->findOneBy([["user", "=", $user["id"]], "AND", ["alert", "=", $topAlert["id"]]])) ? true : false) : false) : false;
|
||||
|
||||
if (isset($smarty)) {
|
||||
$smarty->assign("topAlert", $topAlert);
|
||||
$smarty->assign("readAlert", $readAlert);
|
||||
|
||||
unset($topAlert);
|
||||
unset($readAlert);
|
||||
}
|
4
library/plugins/enabled/started.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
if (!file_exists(ps(__DIR__ . "/../../secrets/started.txt"))) die("started.txt is missing. did you install the software properly?");
|
||||
$started = file_get_contents(ps(__DIR__ . "/../../secrets/started.txt"));
|
26
library/plugins/enabled/userLangs.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/* ************************************************************ *
|
||||
* Name: User Langs *
|
||||
* Author: Saintly *
|
||||
* Website: https://h33t.moe *
|
||||
* Last Updated: 28.01.2023 *
|
||||
* ------------------------------------------------------------ *
|
||||
* This Plugin is used to get all the languages and put *
|
||||
* them into an array and push that to Smarty. *
|
||||
* ************************************************************ */
|
||||
|
||||
$userlangs = glob(ps(__DIR__ . "/../../langs/*.php"));
|
||||
$_userlangs = array();
|
||||
foreach ($userlangs as $userlang) {
|
||||
unset($lang);
|
||||
require_once $userlang;
|
||||
$lang["info"]["code"] = substr(basename($userlang), 0, -4);
|
||||
array_push($_userlangs, $lang["info"]);
|
||||
}
|
||||
if (isset($smarty)) {
|
||||
$smarty->assign("userlangs", $_userlangs);
|
||||
unset($lang);
|
||||
// unset($userlangs);
|
||||
unset($_userlangs);
|
||||
}
|
6
library/secrets/README.txt
Normal file
@ -0,0 +1,6 @@
|
||||
DO NOT EDIT ANY OF THE SECRETS (pwease)!
|
||||
|
||||
THIS WILL NOT ONLY BREAK THE SOFTWARE
|
||||
BUT ALSO THE API - and I don't want that.
|
||||
|
||||
- thanks, Saintly
|
16
library/themes/nucleus/info.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
$theme = [
|
||||
"name" => "Nucleus",
|
||||
"author" => "Saintly2k, S-VHS and Kaligula",
|
||||
"website" => "https://h33t.moe/",
|
||||
"plugins" => [
|
||||
"daisyTheme",
|
||||
"logoImage",
|
||||
"readChapters",
|
||||
"simpleAlert",
|
||||
"started",
|
||||
"userLangs"
|
||||
],
|
||||
"version" => "2023-03-30"
|
||||
];
|
3
library/themes/nucleus/pages/chapter.tpl
Normal file
@ -0,0 +1,3 @@
|
||||
{foreach from=$images item=item key=key name=name}
|
||||
<img src="data/chapters/{$chapter.id}/{basename($item)}" alt="Page {$key + 1}">
|
||||
{/foreach}
|
126
library/themes/nucleus/pages/index.tpl
Normal file
@ -0,0 +1,126 @@
|
||||
<div class="p-4 border shadow-md rounded-xl my-4">
|
||||
<div class="mb-4">
|
||||
<h1 class="font-bold text-2xl">
|
||||
<a href="titles.php" class="link">
|
||||
Recently Updated Titles
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
{if !empty($recentlyUpdated)}
|
||||
<div class="carousel w-full rounded-xl border">
|
||||
{foreach from=$recentlyUpdated item=item key=key name=name}
|
||||
<div id="titleSlider_{$key}" class=" text-black carousel-item w-full rounded-xl backdrop-blur-sm overflow-auto"
|
||||
style="background-image: url(data/covers/{$item.title.id}.png); background-size: cover; background-position: center;">
|
||||
<div
|
||||
class="w-full backdrop-blur-md bg-white/30 grid grid-cols-3 md:grid-cols-6 gap-2 md:gap-4 flex items-center">
|
||||
<div class="col-span-1 pl-4 py-4">
|
||||
<a href="title.php?id={$item.title.id}">
|
||||
<img src="data/covers/{$item.title.id}.png" class="rounded-xl w-full border shadow-md">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-span-2 md:col-span-5 pr-4">
|
||||
<h1 class="font-bold text-2xl">
|
||||
<a href="title.php?id={$item.title.id}"><span class="link">{$item.title.title}</span></a>
|
||||
<div
|
||||
class="badge shadow-md badge-{if $item.title.sstatus == 1}info{elseif $item.title.sstatus == 2}success{elseif $item.title.sstatus == 3}warning{elseif $item.title.sstatus == 4}success-content{else}error{/if}">
|
||||
{if $item.title.sstatus == 1}Planned{elseif $item.title.sstatus == 2}Ongoing{elseif $item.title.sstatus == 3}Hiatus{elseif $item.title.sstatus == 4}Completed{else}Cancelled{/if}
|
||||
</div>
|
||||
</h1>
|
||||
<p class="text-sm mb-2">{$item.title.alts}</p>
|
||||
<div class="auto space-y-2 md:hidden">
|
||||
{$item.title.summary2}
|
||||
</div>
|
||||
<div class="auto space-y-1 hidden md:block">
|
||||
{$item.title.summary1}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
<div class="flex justify-center w-full pt-4 gap-2 btn-group">
|
||||
{foreach from=$recentlyUpdated item=item key=key name=name}
|
||||
<a href="#titleSlider_{$key}" class="carlink btn btn-xs md:btn-sm {if $key == 0}btn-active{/if}"
|
||||
onclick="switchCarLink(event);" rel="mad">{$key + 1}</a>
|
||||
{/foreach}
|
||||
</div>
|
||||
{else}
|
||||
<p>There are no Titles at the Moment!</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Chapters Div -->
|
||||
<div class="p-4 border shadow-md rounded-xl my-4">
|
||||
<h1 class="mb-4 font-bold text-2xl">
|
||||
<a href="releases.php" class="link">
|
||||
Latest Releases
|
||||
</a>
|
||||
</h1>
|
||||
{if !empty($chapters)}
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-4">
|
||||
{foreach from=$chapters item=item key=key name=name}
|
||||
<div class="w-full grid grid-cols-3 gap-2 border rounded-xl shadow-md">
|
||||
<div class="col-span-1 p-2">
|
||||
<a href="title.php?id={$item.title.id}">
|
||||
<img src="data/covers/{$item.title.id}.png" alt="Cover" class="w-full rounded-md border shadow-md">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-span-2 py-2 pr-2">
|
||||
<p class="font-bold text-md">
|
||||
<a href="title.php?id={$item.title.id}" class="link" title="{$item.title.title}">
|
||||
<span class="sm:hidden">
|
||||
{shorten($item.title.title, 10)}
|
||||
</span>
|
||||
<span class="hidden sm:block md:hidden">
|
||||
{shorten($item.title.title, 21)}
|
||||
</span>
|
||||
<span class="hidden md:block lg:hidden">
|
||||
{shorten($item.title.title, 17)}
|
||||
</span>
|
||||
<span class="hidden lg:block xl:hidden">
|
||||
{shorten($item.title.title, 12)}
|
||||
</span>
|
||||
<span class="hidden xl:block 2xl:hidden">
|
||||
{shorten($item.title.title, 13)}
|
||||
</span>
|
||||
<span class="hidden 2xl:block">
|
||||
{shorten($item.title.title, 17)}
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div class="text-sm">
|
||||
<p>
|
||||
<a href="chapter.php?id={$item.id}" class="flex items-center gap-2 link"
|
||||
title="{formatChapterTitle($item.volume, $item.number, "full")}">
|
||||
<img src="https://flagcdn.com/16x12/{$item.language.1}.png" alt="{$item.language.2}"
|
||||
class="h-3">
|
||||
{formatChapterTitle($item.volume, $item.number)}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="profile.php?id={$item.user.id}"
|
||||
class="link flex items-center text-center md:text-left">
|
||||
<img src="{$item.user.avatar}" class="rounded-full h-5">
|
||||
<span class="ml-1">
|
||||
{$item.user.username}
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
{timeAgo($item.timestamp)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
<div class="col-span-2 md:col-span-3 lg:col-span-5 xl:col-span-6">
|
||||
<a href="releases.php?page=2" class="btn w-full">
|
||||
More
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{else}
|
||||
<p>There are no Chapters on this page!</p>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- /Chapters Div -->
|
86
library/themes/nucleus/pages/releases.tpl
Normal file
@ -0,0 +1,86 @@
|
||||
<!-- Chapters Div -->
|
||||
<div class="p-4 border shadow-md rounded-xl my-4">
|
||||
<h1 class="mb-4 font-bold text-2xl">
|
||||
Releases - Page {$page}
|
||||
</h1>
|
||||
{if !empty($chapters)}
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-4">
|
||||
{foreach from=$chapters item=item key=key name=name}
|
||||
<div class="w-full grid grid-cols-3 gap-2 border rounded-xl shadow-md">
|
||||
<div class="col-span-1 p-2">
|
||||
<a href="title.php?id={$item.title.id}">
|
||||
<img src="data/covers/{$item.title.id}.png" alt="Cover" class="w-full rounded-md border shadow-md">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-span-2 py-2 pr-2">
|
||||
<p class="font-bold text-md">
|
||||
<a href="title.php?id={$item.title.id}" class="link" title="{$item.title.title}">
|
||||
<span class="md:hidden">
|
||||
{shorten($item.title.title, 10)}
|
||||
</span>
|
||||
<span class="hidden md:block lg:hidden">
|
||||
{shorten($item.title.title, 18)}
|
||||
</span>
|
||||
<span class="hidden lg:block xl:hidden">
|
||||
{shorten($item.title.title, 13)}
|
||||
</span>
|
||||
<span class="hidden xl:block 2xl:hidden">
|
||||
{shorten($item.title.title, 14)}
|
||||
</span>
|
||||
<span class="hidden 2xl:block">
|
||||
{shorten($item.title.title, 17)}
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div class="text-sm">
|
||||
<p>
|
||||
<a href="chapter.php?id={$item.id}" class="flex items-center gap-2 link" title="{formatChapterTitle($item.volume, $item.number, "full")}">
|
||||
<img src="https://flagcdn.com/16x12/{$item.language.1}.png" alt="{$item.language.2}"
|
||||
class="h-3">
|
||||
{formatChapterTitle($item.volume, $item.number)}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="profile.php?id={$item.user.id}"
|
||||
class="link flex items-center text-center md:text-left">
|
||||
<img src="{$item.user.avatar}" class="rounded-full h-5">
|
||||
<span class="ml-1">
|
||||
{$item.user.username}
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
{timeAgo($item.timestamp)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
{else}
|
||||
<p>There are no Chapters on this page!</p>
|
||||
{/if}
|
||||
|
||||
{if !empty($pagis)}
|
||||
<!-- Pagination Div -->
|
||||
<div class="btn-group mx-auto pt-4">
|
||||
{if $page > 1}
|
||||
<a href="?page=1" class="btn btn-sm md:btn-md shadow-md">«</a>
|
||||
<a href="?page={$page - 1}" class="btn btn-sm md:btn-md shadow-md">‹</a>
|
||||
{/if}
|
||||
{foreach from=$pagis item=item key=key name=name}
|
||||
{if $page != $item}
|
||||
<a href="?page={$item}" class="btn btn-sm md:btn-md shadow-md">{$item}</a>
|
||||
{else}
|
||||
<span class="btn btn-active btn-sm md:btn-md shadow-md">{$item}</span>
|
||||
{/if}
|
||||
{/foreach}
|
||||
{if $page < $totalPages}
|
||||
<a href="?page={$page + 1}" class="btn btn-sm md:btn-md shadow-md">›</a>
|
||||
<a href="?page={$totalPages}" class="btn btn-sm md:btn-md shadow-md">»</a>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- /Pagination Div -->
|
||||
{/if}
|
||||
</div>
|
||||
<!-- /Chapters Div -->
|
746
library/themes/nucleus/pages/title.tpl
Normal file
@ -0,0 +1,746 @@
|
||||
<!-- Title Div -->
|
||||
<div class="p-4 border shadow-md rounded-xl my-4">
|
||||
<div class="mb-4 md:mb-0 md:hidden">
|
||||
<h1 class="font-bold text-2xl">
|
||||
{$title.title}
|
||||
<div
|
||||
class="badge shadow-md badge-{if $title.sstatus == 1}info{elseif $title.sstatus == 2}success{elseif $title.sstatus == 3}warning{elseif $title.sstatus == 4}success-content{else}error{/if}">
|
||||
{if $title.sstatus == 1}Planned{elseif $title.sstatus == 2}Ongoing{elseif $title.sstatus == 3}Hiatus{elseif $title.sstatus == 4}Completed{else}Cancelled{/if}
|
||||
</div>
|
||||
{if $logged && $user.level >= 75}
|
||||
<button class="btn btn-sm shadow-md" onclick="toggleCheck('editTitleModal');">Edit Title</button>
|
||||
{/if}
|
||||
</h1>
|
||||
<p>{$title.alts}</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<a href="data/covers/{$title.id}.png" target="_blank">
|
||||
<img src="data/covers/{$title.id}.png" alt="Cover" class="w-full rounded-xl shadow-md">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-span-1 md:col-span-3">
|
||||
<div class="hidden md:block md:mb-4">
|
||||
<h1 class="font-bold text-2xl">
|
||||
{$title.title}
|
||||
<div
|
||||
class="badge shadow-md badge-{if $title.sstatus == 1}info{elseif $title.sstatus == 2}success{elseif $title.sstatus == 3}warning{elseif $title.sstatus == 4}success-content{else}error{/if}">
|
||||
{if $title.sstatus == 1}Planned{elseif $title.sstatus == 2}Ongoing{elseif $title.sstatus == 3}Hiatus{elseif $title.sstatus == 4}Completed{else}Cancelled{/if}
|
||||
</div>
|
||||
{if $logged && $user.level >= 75}
|
||||
<button class="btn btn-sm shadow-md" onclick="toggleCheck('editTitleModal');">Edit Title</button>
|
||||
{/if}
|
||||
</h1>
|
||||
<p>{$title.alts}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="underline font-bold text-xl mb-2">Details</h2>
|
||||
<p class="mb-1">
|
||||
<span class="font-bold">Author/s:</span>
|
||||
{if !empty($title.authors)}
|
||||
{foreach from=$title.authors item=item key=key name=name}
|
||||
<a href="titles.php?search={trim($item)}">
|
||||
<span class="badge hover:badge-info p-3 shadow-md">
|
||||
{trim($item)}
|
||||
</span>
|
||||
</a>
|
||||
{/foreach}
|
||||
{else}
|
||||
Unknown
|
||||
{/if}
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<span class="font-bold">Artist/s:</span>
|
||||
{if !empty($title.artists)}
|
||||
{foreach from=$title.artists item=item key=key name=name}
|
||||
<a href="titles.php?search={trim($item)}">
|
||||
<span class="badge hover:badge-info p-3 shadow-md">
|
||||
{trim($item)}
|
||||
</span>
|
||||
</a>
|
||||
{/foreach}
|
||||
{else}
|
||||
Unknown
|
||||
{/if}
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<span class="font-bold">Release Year:</span>
|
||||
{if !empty($title.releaseYear)}
|
||||
{$title.releaseYear}
|
||||
{else}
|
||||
Unknown
|
||||
{/if}
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<span class="font-bold">Completion Year:</span>
|
||||
{if !empty($title.completionYear)}
|
||||
{$title.completionYear}
|
||||
{else}
|
||||
Unknown
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<h2 class="underline font-bold text-xl mb-2">Tags</h2>
|
||||
<div class="mb-1 grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p><b>Formats</b></p>
|
||||
{if !empty($title.tags.formats)}
|
||||
{foreach from=$title.tags.formats item=item key=key name=name}
|
||||
<a href="titles.php?search={trim($item.name)}">
|
||||
<span class="badge hover:badge-info p-3 mb-1 border border-black shadow-md">
|
||||
{trim($item.name)}
|
||||
</span>
|
||||
</a>
|
||||
{/foreach}
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<p><b>Warnings</b></p>
|
||||
{if !empty($title.tags.warnings)}
|
||||
{foreach from=$title.tags.warnings item=item key=key name=name}
|
||||
<a href="titles.php?search={trim($item.name)}">
|
||||
<span class="badge hover:badge-info p-3 mb-1 border border-black shadow-md">
|
||||
{trim($item.name)}
|
||||
</span>
|
||||
</a>
|
||||
{/foreach}
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<p><b>Themes</b></p>
|
||||
{if !empty($title.tags.themes)}
|
||||
{foreach from=$title.tags.themes item=item key=key name=name}
|
||||
<a href="titles.php?search={trim($item.name)}">
|
||||
<span class="badge hover:badge-info p-3 mb-1 border border-black shadow-md">
|
||||
{trim($item.name)}
|
||||
</span>
|
||||
</a>
|
||||
{/foreach}
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<p><b>Genres</b></p>
|
||||
{if !empty($title.tags.genres)}
|
||||
{foreach from=$title.tags.genres item=item key=key name=name}
|
||||
<a href="titles.php?search={trim($item.name)}">
|
||||
<span class="badge hover:badge-info p-3 mb-1 border border-black shadow-md">
|
||||
{trim($item.name)}
|
||||
</span>
|
||||
</a>
|
||||
{/foreach}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{if !empty($title.summary)}
|
||||
<div class="md:col-span-4">
|
||||
<h2 class="underline font-bold text-xl mb-2">Summary</h2>
|
||||
<div class="auto space-y-2 md:space-y-1">
|
||||
{$title.summary}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Title Div -->
|
||||
|
||||
<!-- Chapters Div -->
|
||||
<div class="p-4 border shadow-md rounded-xl my-4">
|
||||
<h1 class="text-xl font-bold">
|
||||
<span class="underline">Chapters</span>
|
||||
(<span id="chapterCount">{$chapterCount}</span>)
|
||||
{if $logged && $user.level >= 75}
|
||||
<label for="addChapterModal" class="btn btn-sm shadow-md">
|
||||
Add Chapter
|
||||
</label>
|
||||
{/if}
|
||||
</h1>
|
||||
|
||||
{if !empty($chapterLangs.0.language.chapters)}
|
||||
<div class="tabs tabs-boxed mt-4 rounded-xl shadow-md border gap-1">
|
||||
{foreach from=$chapterLangs item=item key=key name=name}
|
||||
{if count($chapterLangs) > 1}
|
||||
<button class="tab tablink border rounded-md {if $preflang == $item.language.1}tab-active{/if}"
|
||||
onclick="switchTab(event, 'chTab_{$item.language.1}'); setCookie('preflang','{$item.language.1}');">
|
||||
{else}
|
||||
<button class="tab tablink tab-active rounded-md">
|
||||
{/if}
|
||||
<img src="https://flagcdn.com/16x12/{$item.language.1}.png" alt="{$item.language.2}" class="h-3">
|
||||
<span class="hidden md:block md:ml-1">
|
||||
{$item.language.0}
|
||||
</span>
|
||||
<span class="badge ml-1 mr-0">{$item.language.count}</span>
|
||||
</button>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<!-- Chapter Tabs -->
|
||||
<div class="tabcontents mt-4">
|
||||
{foreach from=$chapterLangs item=_lang key=lkey name=lname}
|
||||
{if count($chapterLangs) > 1}
|
||||
<div class="tabcontent {if $preflang == $_lang.language.1}active{else}hidden{/if}"
|
||||
id="chTab_{$_lang.language.1}">
|
||||
{/if}
|
||||
<!-- Chapter Table -->
|
||||
<div class="overflow-x-auto border shadow-md rounded-xl">
|
||||
<table class="table table-compact w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">
|
||||
<span class="md:hidden">#</span>
|
||||
<span class="hidden md:inline">Chapter</span>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<span class="md:hidden">𝕋</span>
|
||||
<span class="hidden md:inline">Title</span>
|
||||
</th>
|
||||
<th class="text-center">
|
||||
<span class="md:hidden">@</span>
|
||||
<span class="hidden md:inline">User</span>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<span class="md:hidden">⟲</span>
|
||||
<span class="hidden md:inline">Date</span>
|
||||
</th>
|
||||
{if $logged && $user.level >= 75}
|
||||
<th></th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach from=$_lang.language.chapters item=item key=key name=name}
|
||||
<tr class="hover">
|
||||
<th>
|
||||
<label class="swap swap-flip">
|
||||
<input type="checkbox" onchange="toggleRead({$item.id});" id="readBox{$item.id}"
|
||||
{if $logged && in_array($item.id, $readChapters)}checked{/if}>
|
||||
<div class="swap-on">✅</div>
|
||||
<div class="swap-off">❌</div>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<a href="chapter.php?id={$item.id}" class="link">
|
||||
<span class="md:hidden">
|
||||
{formatChapterTitle($item.volume, $item.number, "short")}
|
||||
</span>
|
||||
<span class="hidden md:inline">
|
||||
{formatChapterTitle($item.volume, $item.number, "full")}
|
||||
</span>
|
||||
</a>
|
||||
<label class="btn btn-xs" for="editChapterModal{$item.id}">
|
||||
Edit Chapter
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<a href="chapter.php?id={$item.id}" class="link">
|
||||
{$item.name}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="profile.php?id={$item.user.id}"
|
||||
class="link flex items-center text-center md:text-left">
|
||||
<img src="{$item.user.avatar}" class="rounded-full h-6">
|
||||
<span class="hidden md:inline md:ml-1">
|
||||
{$item.user.username}
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="md:hidden">
|
||||
{formatDate($item.timestamp)}
|
||||
</span>
|
||||
<span class="hidden md:inline">
|
||||
{formatDate($item.timestamp, true)}
|
||||
</span>
|
||||
</td>
|
||||
{if $logged && $user.level >= 75}
|
||||
<td class="text-center">
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{if $logged && $user.level >= 75}
|
||||
<input type="checkbox" id="editChapterModal{$item.id}" class="modal-toggle">
|
||||
<div class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Edit Chapter</h3>
|
||||
<form method="POST" name="editChapterForm{$item.id}" id="editChapterForm{$item.id}"
|
||||
enctype="multipart/form-data">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<!-- Chapter Data-->
|
||||
<input name="id" value="{$title.id}" hidden>
|
||||
<input name="cid" value="{$item.id}" hidden>
|
||||
<input type="file" id="zip" name="zip{$item.id}"
|
||||
class="border border-black file-input w-full" title="File">
|
||||
<input type="number" step=".01" placeholder="Chapter" name="number"
|
||||
class="input w-full border border-black" value="{$item.number}"
|
||||
title="Chapter">
|
||||
<input type="number" placeholder="Volume" name="volume" value="{$item.volume}"
|
||||
class="input w-full border border-black" title="Volume">
|
||||
<input type="text" placeholder="Chapter Title" name="title"
|
||||
class="input w-full border border-black" value="{$item.name}" title="Title">
|
||||
<select name="language" class="select w-full border border-black"
|
||||
title="Language">
|
||||
<option disabled selected>Select chapter language...</option>
|
||||
{foreach from=$upl_langs item=_item key=_key name=_name}
|
||||
<option value="{$_item.name},{$_item.code},{$_item.flag}"
|
||||
{if $item.language.1 == $_item.code}selected{/if}>
|
||||
{$_item.flag} {$_item.name}
|
||||
</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
<progress class="progress hidden" max="100"
|
||||
id="addChapterLoadingBar{$item.id}"></progress>
|
||||
<!-- /Chapter Data -->
|
||||
|
||||
<!-- Submit, Error and Close -->
|
||||
<button class="btn btn-primary btn-block btn-outline"
|
||||
type="submit">Edit!</button>
|
||||
<div class="text-center hidden text-error" id="editchapter{$item.id}-result">
|
||||
</div>
|
||||
<!-- /Submit, Error and Close -->
|
||||
</div>
|
||||
</form>
|
||||
<button onclick="toggleCheck('editChapterModal{$item.id}');"
|
||||
id="editChapterModal{$item.id}Close"
|
||||
class="btn btn-error btn-block mt-4">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#editChapterForm{$item.id}").submit(function(e) {
|
||||
let loading = document.getElementById("addChapterLoadingBar{$item.id}");
|
||||
let text = document.getElementById("editchapter{$item.id}-result");
|
||||
loading.classList.remove("hidden");
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
var percentComplete = ((evt.loaded / evt.total) * 100);
|
||||
loading.setAttribute("value", percentComplete);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: "POST",
|
||||
url: "ajax\\chapters\\edit.php",
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData: false,
|
||||
beforeSend: function() {
|
||||
loading.setAttribute("value", 0);
|
||||
text.classList.add("hidden");
|
||||
text.innerHTML = "";
|
||||
loading.classList.remove("progress-error");
|
||||
loading.classList.remove("progress-success");
|
||||
},
|
||||
success: function(data) {
|
||||
if (data != "success") {
|
||||
loading.classList.add("progress-error");
|
||||
text.classList.remove("hidden");
|
||||
text.innerHTML = "<b>Error:</b> " + data;
|
||||
} else {
|
||||
loading.classList.add("progress-success");
|
||||
|
||||
// Now make the close button reload
|
||||
document.getElementById("editChapterModal{$item.id}Close").setAttribute("onclick", "location.reload()");
|
||||
document.getElementById("editChapterModal{$item.id}Close").setAttribute("for", "");
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
{/if}
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- /Chapter Table -->
|
||||
{if count($chapterLangs) > 1}
|
||||
</div>
|
||||
{/if}
|
||||
{/foreach}
|
||||
</div>
|
||||
<!-- /Chapter Tabs -->
|
||||
{else}
|
||||
<p class="mt-4">There are no chapters at the moment!</p>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- /Chapters Div -->
|
||||
|
||||
<!-- Comments Div -->
|
||||
<div class="p-4 border shadow-md rounded-xl my-4">
|
||||
<h1 class="text-xl font-bold">
|
||||
<span class="underline">Comments</span>
|
||||
(<span id="commentsCount">{$commentsCount}</span>)
|
||||
</h1>
|
||||
{if $logged && $user.level >= 50}
|
||||
{* <div class="w-full my-4">
|
||||
<div class="iconBar border border-black rounded-t-xl">
|
||||
<div class="m-1">
|
||||
<button class="btn btn-xs" onclick="insertTag('ca', '**', '**', '-2');">
|
||||
<b>
|
||||
<span class="sm:hidden">
|
||||
B
|
||||
</span>
|
||||
<span class="hidden sm:inline">
|
||||
Bold
|
||||
</span>
|
||||
</b>
|
||||
</button>
|
||||
<button class="btn btn-xs" onclick="insertTag('ca', '*', '*', '-1');">
|
||||
<i>
|
||||
<span class="sm:hidden">
|
||||
I
|
||||
</span>
|
||||
<span class="hidden sm:inline">
|
||||
Italic
|
||||
</span>
|
||||
</i>
|
||||
</button>
|
||||
<button class="btn btn-xs" onclick="insertTag('ca', '\n> ', '', '-4');">
|
||||
<code>
|
||||
<span class="sm:hidden">
|
||||
> Q
|
||||
</span>
|
||||
<span class="hidden sm:inline">
|
||||
Quote
|
||||
</span>
|
||||
</code>
|
||||
</button>
|
||||
<button class="btn btn-xs" onclick="insertTag('ca', '`', '`', '-1');">
|
||||
<code>
|
||||
<span class="sm:hidden">
|
||||
C
|
||||
</span>
|
||||
<span class="hidden sm:inline">
|
||||
Code
|
||||
</span>
|
||||
</code>
|
||||
</button>
|
||||
<button class="btn btn-xs" onclick="insertTag('ca', '[', '](https://)', '-1');">
|
||||
<span class="sm:hidden">
|
||||
URL
|
||||
</span>
|
||||
<span class="hidden sm:inline">
|
||||
Link
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn btn-xs" onclick="insertTag('ca', '![Image]', '()', '-9');">
|
||||
<span class="sm:hidden">
|
||||
IMG
|
||||
</span>
|
||||
<span class="hidden sm:inline">
|
||||
Image
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn btn-xs" onclick="insertTag('ca', '\n- ', '', '-4');">
|
||||
<span class="sm:hidden">
|
||||
-
|
||||
</span>
|
||||
<span class="hidden sm:inline">
|
||||
List
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" name="addComment" id="addCommentForm">
|
||||
<textarea class="textarea textarea-bordered textarea-sm p-2 w-full rounded-none m-0" id="ca"></textarea>
|
||||
<div class="m-0 -mt-1">
|
||||
<button type="submit"
|
||||
class="btn btn-success btn-bordered btn-sm btn-block rounded-none rounded-b-xl mt-0 -mt-10">
|
||||
Publish Comment
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div> *}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- /Comments Div -->
|
||||
|
||||
{if $logged && $user.level >= 75}
|
||||
<input type="checkbox" id="editTitleModal" class="modal-toggle">
|
||||
<div class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Edit Title</h3>
|
||||
<form method="POST" name="editTitle" id="editTitleForm">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<!-- Cover Image -->
|
||||
<input type="file" id="cover" class="border border-black file-input w-full" title="Cover">
|
||||
<img src="data/covers/{$title.id}.png" alt="Cover" id="imgpreview"
|
||||
class="rounded-xl w-full border shadow-sm">
|
||||
<!-- /Cover Image -->
|
||||
|
||||
<!-- Title Data-->
|
||||
<input name="id" value="{$title.id}" hidden>
|
||||
<input name="cover" value="{$title.cover}" hidden>
|
||||
<input type="text" value="{$title.title}" placeholder="Title" name="title"
|
||||
class="input w-full border border-black" title="Title">
|
||||
<input type="text" value="{$title.alts}" placeholder="Alternate Names" name="alts"
|
||||
class="input w-full border border-black" title="Alternate Names">
|
||||
<label for="tagsModal" class="btn">Select Tags</label>
|
||||
<input type="text" value="{$title.authors2}" placeholder="Author/s (Seperate by comma)" name="authors"
|
||||
class="input w-full border border-black" title="Author/s (Seperate by comma)">
|
||||
<input type="text" value="{$title.artists2}" placeholder="Artist/s (Seperate by comma)" name="artists"
|
||||
class="input w-full border border-black" title="Artist/s (Seperate by comma)">
|
||||
<input type="text" value="{$title.lang}" placeholder="Original Language" name="olang"
|
||||
class="input w-full border border-black" title="Original Language">
|
||||
<select name="ostatus" class="select w-full border border-black" title="Original Work Status">
|
||||
<option disabled>Select original work status...</option>
|
||||
<option value="1" {if $title.status == 1}selected{/if}>Announced</option>
|
||||
<option value="2" {if $title.status == 2}selected{/if}>Releasing</option>
|
||||
<option value="3" {if $title.status == 3}selected{/if}>Hiatus</option>
|
||||
<option value="4" {if $title.status == 4}selected{/if}>Completed</option>
|
||||
<option value="5" {if $title.status == 5}selected{/if}>Cancelled</option>
|
||||
</select>
|
||||
<select name="sstatus" class="select w-full border border-black" title="Scanlation Status">
|
||||
<option disabled>Select scanlation status...</option>
|
||||
<option value="1" {if $title.sstatus == 1}selected{/if}>Planned</option>
|
||||
<option value="2" {if $title.sstatus == 2}selected{/if}>Ongoing</option>
|
||||
<option value="3" {if $title.sstatus == 3}selected{/if}>Hiatus</option>
|
||||
<option value="4" {if $title.sstatus == 4}selected{/if}>Completed</option>
|
||||
<option value="5" {if $title.sstatus == 5}selected{/if}>Cancelled</option>
|
||||
</select>
|
||||
<input type="number" value="{$title.releaseYear}" placeholder="Year of Release" name="release"
|
||||
class="input w-full border border-black" title="Year of Release">
|
||||
<input type="number" value="{$title.completionYear}" placeholder="Year of Completion" name="completion"
|
||||
class="input w-full border border-black" title="Year of Completion">
|
||||
<textarea name="summary" class="textarea w-full border border-black" title="Summary"
|
||||
placeholder="Summary">{$title.summary2}</textarea>
|
||||
<!-- /Title Data -->
|
||||
|
||||
<!-- Tags Modal -->
|
||||
<input type="checkbox" id="tagsModal" class="modal-toggle">
|
||||
<div class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Select Tags</h3>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Format/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_format item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="format[]" value="{$item.id}"
|
||||
{if in_array($item.id, $title.tags._formats)}checked{/if}> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Warning/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_warnings item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="warning[]" value="{$item.id}"
|
||||
{if in_array($item.id, $title.tags._warnings)}checked{/if}> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Theme/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_theme item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="theme[]" value="{$item.id}"
|
||||
{if in_array($item.id, $title.tags._themes)}checked{/if}> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Genre/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_genre item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="genre[]" value="{$item.id}"
|
||||
{if in_array($item.id, $title.tags._genres)}checked{/if}> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<label for="tagsModal" class="btn btn-success">Close</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Tags Modal -->
|
||||
|
||||
<!-- Submit, Error and Close -->
|
||||
<button class="btn btn-primary btn-block btn-outline" type="submit">Edit!</button>
|
||||
<div class="text-center hidden text-error" id="edittitle-result"></div>
|
||||
<label for="editTitleModal" class="btn btn-error btn-block">Cancel</label>
|
||||
<!-- /Submit, Error and Close -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<input type="checkbox" id="addChapterModal" class="modal-toggle">
|
||||
<div class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Add Chapter</h3>
|
||||
<form method="POST" name="addChapter" id="addChapterForm" enctype="multipart/form-data">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<!-- Chapter Data-->
|
||||
<input name="id" value="{$title.id}" hidden>
|
||||
<input type="file" id="zip" name="zip" class="border border-black file-input w-full">
|
||||
<input type="number" step=".01" id="chapterNumber" placeholder="Chapter" name="number"
|
||||
class="input w-full border border-black" title="Chapter">
|
||||
<input type="number" id="volumeNumber" placeholder="Volume" name="volume"
|
||||
class="input w-full border border-black" title="Volume">
|
||||
<input type="text" id="chTitle" placeholder="Chapter Title" name="title"
|
||||
class="input w-full border border-black" title="Title">
|
||||
<select name="language" class="select w-full border border-black" title="Language">
|
||||
<option disabled selected>Select chapter language...</option>
|
||||
{foreach from=$upl_langs item=item key=key name=name}
|
||||
<option value="{$item.name},{$item.code},{$item.flag}">
|
||||
{$item.flag} {$item.name}
|
||||
</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
<progress class="progress hidden" max="100" id="addChapterLoadingBar"></progress>
|
||||
<!-- /Chapter Data -->
|
||||
|
||||
<!-- Submit, Error and Close -->
|
||||
<button class="btn btn-primary btn-block btn-outline" type="submit">Add!</button>
|
||||
<div class="text-center hidden text-error" id="addchapter-result"></div>
|
||||
<!-- /Submit, Error and Close -->
|
||||
</div>
|
||||
</form>
|
||||
<button onclick="toggleCheck('addChapterModal');" id="addChapterModalClose"
|
||||
class="btn btn-error btn-block mt-4">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$("#cover").change(function(e) {
|
||||
e.preventDefault();
|
||||
var fd = new FormData();
|
||||
var files = $("#cover")[0].files;
|
||||
if (files.length > 0) {
|
||||
fd.append("cover", files[0]);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\images\\tmp.php",
|
||||
data: fd,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(msg) {
|
||||
let result = JSON.parse(msg);
|
||||
if (result.s == true) {
|
||||
let preview = document.getElementById("imgpreview");
|
||||
//preview.classList.remove("hidden");
|
||||
preview.src = "data/tmp/" + result.msg;
|
||||
document.querySelector("input[name='cover']").value =
|
||||
result.msg;
|
||||
} else {
|
||||
alert(result.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#cover").change(function(e) {
|
||||
var allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/webp"];
|
||||
var file = this.files[0];
|
||||
var fileType = file.type;
|
||||
if (!allowedTypes.includes(fileType)) {
|
||||
alert("Upload only supports JPG, JPEG, PNG and WEBP!");
|
||||
$("#cover").val("");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$("#editTitleForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\titles\\edit.php",
|
||||
data: $(this).serialize(),
|
||||
success: function(data) {
|
||||
let result = JSON.parse(data);
|
||||
if (result.s == true) {
|
||||
//let text = document.getElementById("addtitle-result");
|
||||
//text.classList.add("hidden");
|
||||
window.location.replace("title.php?id=" + result.msg);
|
||||
// Redirect to Title?
|
||||
} else {
|
||||
let text = document.getElementById("edittitle-result");
|
||||
text.classList.remove("hidden");
|
||||
text.innerHTML = "<b>Error:</b> " + result.msg;
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#addChapterForm").submit(function(e) {
|
||||
let loading = document.getElementById("addChapterLoadingBar");
|
||||
let text = document.getElementById("addchapter-result");
|
||||
loading.classList.remove("hidden");
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
var percentComplete = ((evt.loaded / evt.total) * 100);
|
||||
loading.setAttribute("value", percentComplete);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: "POST",
|
||||
url: "ajax\\chapters\\add.php",
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData: false,
|
||||
beforeSend: function() {
|
||||
loading.setAttribute("value", 0);
|
||||
text.classList.add("hidden");
|
||||
text.innerHTML = "";
|
||||
loading.classList.remove("progress-error");
|
||||
loading.classList.remove("progress-success");
|
||||
},
|
||||
success: function(data) {
|
||||
if (data != "success") {
|
||||
loading.classList.add("progress-error");
|
||||
text.classList.remove("hidden");
|
||||
text.innerHTML = "<b>Error:</b> " + data;
|
||||
} else {
|
||||
loading.classList.add("progress-success");
|
||||
let chIn = document.getElementById("chapterNumber");
|
||||
let volIn = document.getElementById("volumeNumber");
|
||||
let allChs = document.getElementById("chapterCount");
|
||||
let chNum = (Number(chIn.value) + 1).toFixed(2);
|
||||
let volNum = Number(volIn.value);
|
||||
let chNos = Number(allChs.innerHTML) + 1;
|
||||
|
||||
// Reset Form
|
||||
document.getElementById("chTitle").value = "";
|
||||
document.getElementById("zip").value = "";
|
||||
|
||||
// Set all values for next chapter
|
||||
chIn.value = chNum;
|
||||
volIn.value = volNum;
|
||||
allChs.innerHTML = chNos;
|
||||
|
||||
// Now make the close button reload
|
||||
document.getElementById("addChapterModalClose").setAttribute("onclick",
|
||||
"location.reload()");
|
||||
document.getElementById("addChapterModalClose").setAttribute("for", "");
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
{/if}
|
235
library/themes/nucleus/pages/titles.tpl
Normal file
@ -0,0 +1,235 @@
|
||||
<!-- Titles Div -->
|
||||
<div class="p-4 border shadow-md rounded-xl my-4">
|
||||
<h1 class="mb-4 font-bold text-2xl">
|
||||
Titles - Page {$page}
|
||||
{if $logged && $user.level >= 75}
|
||||
<a href="#/" class="btn btn-sm" onclick="toggleCheck('addTitleModal');">Add Title</a>
|
||||
{/if}
|
||||
</h1>
|
||||
{if !empty($titles)}
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
|
||||
{foreach from=$titles item=item key=key name=name}
|
||||
<div>
|
||||
<div class="card card-compact w-full bg-base-200 shadow-lg border">
|
||||
<figure>
|
||||
<a href="title.php?id={$item.id}">
|
||||
<img src="data/covers/{$item.id}.png" alt="Cover">
|
||||
</a>
|
||||
</figure>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title flex">
|
||||
<a href="title.php?id={$item.id}" class="link">
|
||||
{$item.title}
|
||||
</a>
|
||||
</h2>
|
||||
<div class="card-actions justify-end">
|
||||
<span
|
||||
class="badge shadow-md badge-{if $item.sstatus == 1}info{elseif $item.sstatus == 2}success{elseif $item.sstatus == 3}warning{elseif $item.sstatus == 4}success-content{else}error{/if}">
|
||||
{if $item.sstatus == 1}Planned{elseif $item.sstatus == 2}Ongoing{elseif $item.sstatus == 3}Hiatus{elseif $item.sstatus == 4}Completed{else}Cancelled{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
{else}
|
||||
<p>There are no Titles on this page!</p>
|
||||
{/if}
|
||||
|
||||
{if !empty($pagis)}
|
||||
<div class="btn-group mx-auto pt-4">
|
||||
{if $page > 1}
|
||||
<a href="?page=1" class="btn btn-sm md:btn-md shadow-md">«</a>
|
||||
<a href="?page={$page - 1}" class="btn btn-sm md:btn-md shadow-md">‹</a>
|
||||
{/if}
|
||||
{foreach from=$pagis item=item key=key name=name}
|
||||
{if $page != $item}
|
||||
<a href="?page={$item}" class="btn btn-sm md:btn-md shadow-md">{$item}</a>
|
||||
{else}
|
||||
<span class="btn btn-active btn-sm md:btn-md shadow-md">{$item}</span>
|
||||
{/if}
|
||||
{/foreach}
|
||||
{if $page < $totalPages}
|
||||
<a href="?page={$page + 1}" class="btn btn-sm md:btn-md shadow-md">›</a>
|
||||
<a href="?page={$totalPages}" class="btn btn-sm md:btn-md shadow-md">»</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- /Titles Div -->
|
||||
|
||||
{if $logged && $user.level >= 75}
|
||||
<input type="checkbox" id="addTitleModal" class="modal-toggle">
|
||||
<div class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Add Title</h3>
|
||||
<form method="POST" name="addTitle" id="addTitleForm">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<!-- Cover Image -->
|
||||
<input type="file" id="cover" class="border border-black file-input w-full">
|
||||
<img src="" alt="Cover" id="imgpreview" class="rounded-xl hidden w-full border shadow-sm">
|
||||
<!-- /Cover Image -->
|
||||
|
||||
<!-- Title Data-->
|
||||
<input name="cover" hidden>
|
||||
<input type="text" placeholder="Title" name="title" class="input w-full border border-black">
|
||||
<input type="text" placeholder="Alternate Names" name="alts" class="input w-full border border-black">
|
||||
<label for="tagsModal" class="btn">Select Tags</label>
|
||||
<input type="text" placeholder="Author/s (Seperate by comma)" name="authors"
|
||||
class="input w-full border border-black">
|
||||
<input type="text" placeholder="Artist/s (Seperate by comma)" name="artists"
|
||||
class="input w-full border border-black">
|
||||
<input type="text" placeholder="Original Language" name="olang"
|
||||
class="input w-full border border-black">
|
||||
<select name="ostatus" class="select w-full border border-black">
|
||||
<option disabled selected>Select original work status...</option>
|
||||
<option value="1">Announced</option>
|
||||
<option value="2">Releasing</option>
|
||||
<option value="3">Hiatus</option>
|
||||
<option value="4">Completed</option>
|
||||
<option value="5">Cancelled</option>
|
||||
</select>
|
||||
<select name="sstatus" class="select w-full border border-black">
|
||||
<option disabled selected>Select scanlation status...</option>
|
||||
<option value="1">Planned</option>
|
||||
<option value="2">Ongoing</option>
|
||||
<option value="3">Hiatus</option>
|
||||
<option value="4">Completed</option>
|
||||
<option value="5">Cancelled</option>
|
||||
</select>
|
||||
<input type="number" placeholder="Year of Release" name="release"
|
||||
class="input w-full border border-black">
|
||||
<input type="number" placeholder="Year of Completion" name="completion"
|
||||
class="input w-full border border-black">
|
||||
<textarea name="summary" class="textarea w-full border border-black" placeholder="Summary"></textarea>
|
||||
<!-- /Title Data -->
|
||||
|
||||
<!-- Tags Modal -->
|
||||
<input type="checkbox" id="tagsModal" class="modal-toggle">
|
||||
<div class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Select Tags</h3>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Format/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_format item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="format[]" value="{$item.id}"> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Warning/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_warnings item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="warning[]" value="{$item.id}"> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Theme/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_theme item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="theme[]" value="{$item.id}"> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-xl text-left">Genre/s</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-1">
|
||||
{foreach from=$upl_genre item=item key=key name=name}
|
||||
<label>
|
||||
<input type="checkbox" name="genre[]" value="{$item.id}"> {$item.name}
|
||||
</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
<label for="tagsModal" class="btn btn-success">Close</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Tags Modal -->
|
||||
|
||||
<!-- Submit, Error and Close -->
|
||||
<button class="btn btn-primary btn-block btn-outline" type="submit">Add!</button>
|
||||
<div class="text-center hidden text-error" id="addtitle-result"></div>
|
||||
<label for="addTitleModal" class="btn btn-error btn-block">Cancel</label>
|
||||
<!-- /Submit, Error and Close -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$("#cover").change(function(e) {
|
||||
e.preventDefault();
|
||||
var fd = new FormData();
|
||||
var files = $("#cover")[0].files;
|
||||
if (files.length > 0) {
|
||||
fd.append("cover", files[0]);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\images\\tmp.php",
|
||||
data: fd,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function(msg) {
|
||||
let result = JSON.parse(msg);
|
||||
if (result.s == true) {
|
||||
let preview = document.getElementById("imgpreview");
|
||||
preview.classList.remove("hidden");
|
||||
preview.src = "data/tmp/" + result.msg;
|
||||
document.querySelector("input[name='cover']").value =
|
||||
result.msg;
|
||||
} else {
|
||||
alert(result.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#cover").change(function(e) {
|
||||
var allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/webp"];
|
||||
var file = this.files[0];
|
||||
var fileType = file.type;
|
||||
if (!allowedTypes.includes(fileType)) {
|
||||
alert("Upload only supports JPG, JPEG, PNG and WEBP!");
|
||||
$("#cover").val("");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$("#addTitleForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\titles\\add.php",
|
||||
data: $(this).serialize(),
|
||||
success: function(data) {
|
||||
let result = JSON.parse(data);
|
||||
if (result.s == true) {
|
||||
//let text = document.getElementById("addtitle-result");
|
||||
//text.classList.add("hidden");
|
||||
window.location.replace("title.php?id=" + result.msg);
|
||||
// Redirect to Title?
|
||||
} else {
|
||||
let text = document.getElementById("addtitle-result");
|
||||
text.classList.remove("hidden");
|
||||
text.innerHTML = "<b>Error:</b> " + result.msg;
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
{/if}
|
41
library/themes/nucleus/parts/alert.tpl
Normal file
@ -0,0 +1,41 @@
|
||||
{if !empty($topAlert) && !$readAlert}
|
||||
<div class="alert alert-{$topAlert.type} shadow-md" id="topAlert">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
class="stroke-current flex-shrink-0 w-6 h-6">
|
||||
{if $topAlert.type == "info"}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
{elseif $topAlert.type == "success"}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
{elseif $topAlert.type == "warning"}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
{else}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
{/if}
|
||||
</svg>
|
||||
<span><b>{ucfirst($topAlert.type)}:</b> {$topAlert.content}</span>
|
||||
</div>
|
||||
{if $logged}
|
||||
<div class="flex-none m-0 p-0">
|
||||
<button class="btn btn-sm m-0 mikuLink" id="dismissAlert">Dismiss</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $logged && $user.level == 49}
|
||||
<div class="alert alert-info shadow-md" id="topAlert">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
class="stroke-current flex-shrink-0 w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span><b>Info:</b> Activate your account through the Email we sent you to use all functions of {$config.title}!</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
322
library/themes/nucleus/parts/footer.tpl
Normal file
@ -0,0 +1,322 @@
|
||||
</div>
|
||||
<!-- Container End -->
|
||||
|
||||
{if (isset($rel) && $rel != 'tab') || !isset($rel)}
|
||||
{if !$logged}
|
||||
<input type="checkbox" id="loginModal" class="modal-toggle">
|
||||
<div class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Login</h3>
|
||||
<form method="POST" name="login" id="loginForm">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<input type="text" placeholder="Username" name="username" class="input w-full border border-black">
|
||||
<input type="password" placeholder="Password" name="password" class="input w-full border border-black">
|
||||
</div>
|
||||
<div class=" mt-4">
|
||||
<button class="btn btn-primary btn-block btn-outline" type="submit">Log me in!</button>
|
||||
</div>
|
||||
<div class="mt-4 text-error text-center hidden" id="login-result"></div>
|
||||
<div class="pt-4 grid grid-cols-2 gap-4">
|
||||
<label onclick="toggleCheck('loginModal'); toggleCheck('signupModal');" class="btn btn-block">To
|
||||
Signup</label>
|
||||
<label for="loginModal" class="btn btn-error">Cancel</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="signupModal" class="modal-toggle">
|
||||
<div class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">Signup</h3>
|
||||
<form method="POST" name="signup" id="signupForm">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<input type="text" placeholder="Username" name="username" class="input w-full border border-black">
|
||||
<input type="password" placeholder="Password" name="password" class="input w-full border border-black">
|
||||
<input type="password" placeholder="Repeat Password" name="password2"
|
||||
class="input w-full border border-black">
|
||||
<input type="email" placeholder="Email (for account-activation and recovery)" name="email"
|
||||
class="input w-full border border-black">
|
||||
<input type="email" placeholder="Repeat Email" name="email2" class="input w-full border border-black">
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary btn-block btn-outline" type="submit">Create my account!</button>
|
||||
</div>
|
||||
<div class="mt-2 text-error text-center hidden" id="signup-result"></div>
|
||||
<div class="pt-4 grid grid-cols-2 gap-4">
|
||||
<label onclick="toggleCheck('loginModal'); toggleCheck('signupModal');" class="btn btn-block">To
|
||||
Login</label>
|
||||
<label for="signupModal" class="btn btn-error">Cancel</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{else}
|
||||
<input type="checkbox" id="userSettingsModal" class="modal-toggle">
|
||||
<div class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-2xl text-center mb-4 underline">User Settings</h3>
|
||||
<form method="POST" name="userSettings" id="userSettingsForm">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<input type="text" placeholder="Avatar URL" name="avatar" class="input w-full border border-black"
|
||||
value="{$user.avatar}">
|
||||
<select class="select w-full border border-black" name="theme">
|
||||
{foreach from=$config.themes item=item key=key name=name}
|
||||
<option value="{$key}">Theme: {$item}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary btn-block btn-outline" type="submit">Save settings!</button>
|
||||
</div>
|
||||
<div class="mt-2 text-center hidden" id="usersettings-result"></div>
|
||||
<div class="pt-4">
|
||||
<label for="userSettingsModal" class="btn btn-error btn-block">Close</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<script>
|
||||
function toggleCheck(id) {
|
||||
let box = document.getElementById(id);
|
||||
box.checked = !box.checked;
|
||||
}
|
||||
|
||||
{if !$logged}
|
||||
$("#loginForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\account\\login.php",
|
||||
data: $(this).serialize(),
|
||||
success: function(data) {
|
||||
if (data == "success") {
|
||||
location.reload();
|
||||
} else {
|
||||
let text = document.getElementById("login-result");
|
||||
text.classList.remove("hidden");
|
||||
text.innerHTML = "<b>Error:</b> " + data;
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#signupForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\account\\signup.php",
|
||||
data: $(this).serialize(),
|
||||
success: function(data) {
|
||||
if (data == "success") {
|
||||
location.reload();
|
||||
} else {
|
||||
let text = document.getElementById("signup-result");
|
||||
text.classList.remove("hidden");
|
||||
text.innerHTML = "<b>Error:</b> " + data;
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
{/if}
|
||||
|
||||
{if $logged}
|
||||
$("#userSettingsForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\account\\settings.php",
|
||||
data: $(this).serialize(),
|
||||
success: function(data) {
|
||||
if (data == "success") {
|
||||
location.reload();
|
||||
} else {
|
||||
let text = document.getElementById("usersettings-result");
|
||||
text.classList.remove("hidden");
|
||||
text.innerHTML = "<b>Error:</b> " + data;
|
||||
text.classList.add("text-error");
|
||||
text.classList.remove("text-success");
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#logoutButton").click(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\account\\logout.php",
|
||||
data: $(this).serialize(),
|
||||
success: function(data) {
|
||||
if (data == "success") {
|
||||
location.reload();
|
||||
} else {
|
||||
alert("Error: " + data);
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#dismissAlert").click(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\alerts\\dismiss.php",
|
||||
success: function(data) {
|
||||
if (data == "success") {
|
||||
let div = document.getElementById("topAlert");
|
||||
div.classList.add("hidden");
|
||||
} else {
|
||||
alert("Error: " + data);
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
{/if}
|
||||
|
||||
function setCookie(name, value) {
|
||||
Cookies.set("{cat($config.title)}_" + name, value, { expires: 999 })
|
||||
}
|
||||
|
||||
function updateUserLang(lang) {
|
||||
{if $logged}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\account\\updateLang.php",
|
||||
data: {
|
||||
newLang: lang
|
||||
},
|
||||
success: function(data) {
|
||||
if (data == "success") {
|
||||
location.reload();
|
||||
} else {
|
||||
alert("Error: " + data);
|
||||
}
|
||||
}
|
||||
});
|
||||
{else}
|
||||
location.reload();
|
||||
{/if}
|
||||
}
|
||||
|
||||
function toggleRead(id) {
|
||||
let box = event.srcElement.id;
|
||||
{if !$logged}
|
||||
alert("Error: Login to use this function!");
|
||||
toggleCheck(box);
|
||||
{else}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax\\chapters\\read.php",
|
||||
data: {
|
||||
user: {$user.id},
|
||||
chapter: id
|
||||
},
|
||||
success: function(data) {
|
||||
if (data != "success") {
|
||||
alert("Error: " + data);
|
||||
toggleCheck(box);
|
||||
}
|
||||
}
|
||||
});
|
||||
{/if}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Theme Modal -->
|
||||
<input type="checkbox" id="themeModal" class="modal-toggle">
|
||||
<div class="modal">
|
||||
<div class="modal-box w-11/12 max-w-5xl">
|
||||
<h3 class="font-bold text-lg mb-4">Change Design</h3>
|
||||
{$daisyThemes = array("light", "dark", "cupcake", "bumblebee", "emerald", "corporate", "synthwave", "retro", "cyberpunk", "valentine", "halloween", "garden", "forest", "aqua", "lofi", "pastel", "fantasy", "wireframe", "black", "luxury", "dracula", "cmyk", "autumn", "business", "acid", "lemonade", "night", "coffee", "winter")}
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-1 sm:gap-2 md:gap-4">
|
||||
{foreach from=$daisyThemes item=item key=key name=name}
|
||||
<div class="col-span-1 border rounded-xl p-1 md:p-2 cursor-pointer bg-base-100" data-theme="{$item}"
|
||||
onclick="setCookie('daisytheme','{$item}');assignTheme('{cat($config.title)}_');">
|
||||
<div class="grid grid-cols-5 grid-rows-2">
|
||||
<div class="bg-base-200 col-start-1 row-span-1 row-start-1 rounded-tl-md"></div>
|
||||
<div class="bg-base-300 col-start-1 row-start-2 rounded-bl-md"></div>
|
||||
<div class="col-span-4 col-start-2 row-span-2 row-start-1 ml-2">
|
||||
<span class="font-bold">{$item}</span>
|
||||
<div class="w-full grid grid-cols-4 gap-1 md:gap-2 mt-1 md:mt-2 text-sm">
|
||||
<button class="btn btn-sm btn-primary">A</button>
|
||||
<button class="btn btn-sm btn-secondary">A</button>
|
||||
<button class="btn btn-sm btn-accent">A</button>
|
||||
<button class="btn btn-sm btn-neutral">A</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
<label for="themeModal" class="mt-4 md:mt-0 btn col-span-2 md:col-span-5">Close</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Theme Modal -->
|
||||
|
||||
<!-- Language Modal -->
|
||||
<input type="checkbox" id="langModal" class="modal-toggle">
|
||||
<div class="modal">
|
||||
<div class="modal-box w-11/12 max-w-5xl">
|
||||
<h3 class="font-bold text-lg mb-4">Change Language</h3>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-1 sm:gap-2 md:gap-4">
|
||||
{foreach from=$userlangs item=item key=key name=name}
|
||||
<div class="col-span-1 border rounded-xl text-center p-1 md:p-2 cursor-pointer bg-base-100"
|
||||
onclick="setCookie('userlang','{$item.code}');updateUserLang('{$item.code}');">
|
||||
<p class="font-bold text-xl">{$item.name}</p>
|
||||
<p>
|
||||
by
|
||||
{if !empty($item.website)}
|
||||
<a href="{$item.website}" target="_blank" class="link">
|
||||
{/if}
|
||||
{$item.author}
|
||||
{if !empty($item.website)}
|
||||
</a>
|
||||
{/if}
|
||||
</p>
|
||||
<p class="italic text-sm">Updated: {$item.updated}</p>
|
||||
</div>
|
||||
{/foreach}
|
||||
<label for="langModal" class="mt-4 md:mt-0 btn col-span-2 md:col-span-4">Close</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Language Modal -->
|
||||
|
||||
<footer class="footer footer-center p-6 bg-base-300 text-base-content">
|
||||
<div>
|
||||
<p>
|
||||
Copyright © {date("Y")} <a href="{$config.url}" class="link">{$config.title}</a>
|
||||
<span class="hidden md:inline">|</span>
|
||||
<br class="md:hidden">
|
||||
Running <a href="https://github.com/saintly2k/FoOlSlideX" class="link" target="_blank">FoOlSlideX</a>
|
||||
<span class="badge badge-info">{$version}</span>
|
||||
<br class="md:hidden">
|
||||
by <a href="https://github.com/saintly2k" class="link" target="_blank">Saintly2k</a> and
|
||||
<a href="https://github.com/saintly2k/FoOlSlideX/graphs/contributors" class="link" target="_blank">The
|
||||
Gang</a>.
|
||||
<span class="hidden md:inline">|</span>
|
||||
<br class="md:hidden">
|
||||
<label for="themeModal" class="link">Change Design</label> or
|
||||
<label for="langModal" class="link">Change Language</label>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!--<script src="assets/page.js"></script>-->
|
||||
<script src="assets/nucleus/tabs.js"></script>
|
||||
<script src="assets/nucleus/tags.js"></script>
|
||||
<script src="assets/nucleus/autotheme.js"></script>
|
||||
<script src="assets/nucleus/carousel.js"></script>
|
||||
<script src="assets/js.cookie.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{/if}
|
42
library/themes/nucleus/parts/header.tpl
Normal file
@ -0,0 +1,42 @@
|
||||
{if (isset($rel) && $rel != 'tab') || !isset($rel)}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{$userlang}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>{$pagetitle}</title>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@2.47.0/dist/full.css" rel="stylesheet" type="text/css">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="assets/favicon/site.webmanifest">
|
||||
<style>
|
||||
.link {
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dotted;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
text-decoration-style: solid;
|
||||
}
|
||||
|
||||
.auto a {
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dotted;
|
||||
}
|
||||
|
||||
.auto a:hover {
|
||||
text-decoration-style: solid;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-theme="{$daisytheme}" onload="assignTheme('{cat($config.title)}_');" class="min-h-screen">
|
||||
|
||||
{include file="../parts/menu.tpl"}
|
||||
{/if}
|
65
library/themes/nucleus/parts/menu.tpl
Normal file
@ -0,0 +1,65 @@
|
||||
<input hidden readonly type="text" value="{$config.title}" id="siteName">
|
||||
<input hidden readonly type="text" value="{$config.divider}" id="siteDivider">
|
||||
|
||||
<div class="navbar bg-base-300 text-base-content">
|
||||
<div class="navbar-start">
|
||||
<div class="dropdown">
|
||||
<label tabindex="0" class="btn btn-ghost lg:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" />
|
||||
</svg>
|
||||
</label>
|
||||
<ul tabindex="0"
|
||||
class="menu menu-compact border dropdown-content mt-3 p-2 shadow-md bg-base-100 rounded-box w-52">
|
||||
<li><a href="index.php" rel="tab" ttl="Home">Home</a></li>
|
||||
<li><a href="releases.php" rel="tab" ttl="Releases - Page 1">Releases</a></li>
|
||||
<li><a href="titles.php" rel="tab" ttl="Titles - Page 1">Titles</a></li>
|
||||
<li><a href="bookmarks.php" rel="tab" ttl="Bookmarks - Reading - Page 1">Bookmarks</a></li>
|
||||
<li><a href="members.php" rel="tab" ttl="Members - Page 1">Members</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="{$config.url}" class="btn btn-ghost normal-case text-xl">
|
||||
{if !empty($logoImage)}
|
||||
<img src="{$logoImage}" alt="Logo" class="h-10">
|
||||
{else}
|
||||
{$config.title}
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-center hidden lg:flex">
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
<li><a href="index.php" rel="tab" ttl="Home">Home</a></li>
|
||||
<li><a href="releases.php" rel="tab" ttl="Releases - Page 1">Releases</a></li>
|
||||
<li><a href="titles.php" rel="tab" ttl="Titles - Page 1">Titles</a></li>
|
||||
<li><a href="bookmarks.php" rel="tab" ttl="Bookmarks - Reading - Page 1">Bookmarks</a></li>
|
||||
<li><a href="members.php" rel="tab" ttl="Members - Page 1">Members</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="btn btn-ghost btn-circle avatar">
|
||||
<div class="w-10 rounded-full">
|
||||
<img src="{if $logged}{$user.avatar}{else}https://rav.h33t.moe/avatar.php?name=rileinc{/if}"
|
||||
alt="Avatar">
|
||||
</div>
|
||||
</label>
|
||||
<ul tabindex="0"
|
||||
class="mt-3 p-2 shadow-md menu menu-compact dropdown-content bg-base-100 border rounded-box w-52">
|
||||
{if $logged}
|
||||
<li><a href="profile.php?id={$user.id}" rel="tab"
|
||||
ttl="Profile of {$user.username} - Uploads - Page 1">Profile</a></li>
|
||||
<li><label for="userSettingsModal">Settings</label></li>
|
||||
<li><button id="logoutButton">Logout</button></li>
|
||||
{else}
|
||||
<li><label for="loginModal">Login</label></li>
|
||||
<li><label for="signupModal">Signup</label></li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Container Begin -->
|
||||
<div class="container mx-auto my-4 px-2 md:px-0" id="content">
|
||||
{include file="../parts/alert.tpl"}
|
10
public/.htaccess
Normal file
@ -0,0 +1,10 @@
|
||||
Options +FollowSymLinks
|
||||
RewriteEngine On
|
||||
Options -Indexes
|
||||
|
||||
# If yours are too low set by the server? the second should be larger than the first
|
||||
# php_value post_max_size 500M
|
||||
# php_value upload_max_filesize 510M
|
||||
|
||||
ErrorDocument 404 /fsx/public/error.php
|
||||
ErrorDocument 403 /fsx/public/error.php
|
19
public/activate.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
require "../autoload.php";
|
||||
|
||||
if (!$logged) header("Location: index.php") && die("Not even logged in.");
|
||||
if ($logged && $user["level"] >= 50) header("Location: index.php") && die("Already activated.");
|
||||
if (!isset($_GET["token"]) || empty($_GET["token"])) header("Location: index.php") && die("No token given.");
|
||||
|
||||
$token = clean($_GET["token"] ?? "");
|
||||
|
||||
if (empty($token)) header("Location: index.php") && die("Empty token.");
|
||||
|
||||
$check = $db["activation"]->findOneBy(["token", "==", $token]);
|
||||
if (empty($check)) header("Location: index.php") && die("Invalid token.");
|
||||
$data = array(
|
||||
"level" => 50
|
||||
);
|
||||
$db["users"]->updateById($user["id"], $data);
|
||||
header("Location: index.php");
|
38
public/ajax/account/login.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if ($logged) die("You're already logged in!");
|
||||
|
||||
$username = clean($_POST["username"] ?? "");
|
||||
$password = clean($_POST["password"] ?? "");
|
||||
|
||||
if ($config["captcha"]["enabled"]) {
|
||||
if ($config["captcha"]["type"] == "hcaptcha") {
|
||||
if (!hCaptcha($_POST['h-captcha-response'] ?? "")) die("Captcha is wrong!");
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($password)) die("Password is empty!");
|
||||
if (empty($username)) die("Username is empty!");
|
||||
if (strlen($password) < 8) die("Password needs to be at least 8 characters long!");
|
||||
if (strlen($password) > 64) die("Password can only be 64 characters long!");
|
||||
if (strlen($username) < 3) die("Username needs to be at least 3 characters long!");
|
||||
if (strlen($username) > 20) die("Username can only be 20 characters long!");
|
||||
|
||||
$check = $db["users"]->findOneBy(["username", "=", $username]);
|
||||
if (empty($check)) doLog("login", false, "user not found") && die("User not found!");
|
||||
if (!password_verify($password, $check["password"])) doLog("login", false, "invalid password") && die("Wrong Password!");
|
||||
if ($check["banned"]) doLog("login", false, "banned", $check["id"]) && die("Banned! Reason: " . $check["bannedReason"]);
|
||||
|
||||
// Everything is right!
|
||||
$token = genToken();
|
||||
$session = array(
|
||||
"user" => $check["id"],
|
||||
"token" => $token,
|
||||
"ip" => $_SERVER["REMOTE_ADDR"]
|
||||
);
|
||||
$db["sessions"]->insert($session);
|
||||
setcookie(cat($config["title"]) . "_session", $token, time() + 606024 * 9999, "/");
|
||||
doLog("login", true, null, $check["id"]);
|
||||
die("success");
|
9
public/ajax/account/logout.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("You're not logged in!");
|
||||
|
||||
doLog("logout", true, null, $user["id"]);
|
||||
$db["sessions"]->deleteById($session["id"]);
|
||||
die("success");
|
18
public/ajax/account/settings.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("You're not logged in!");
|
||||
|
||||
$avatar = clean($_POST["avatar"] ?? "");
|
||||
$theme = clean($_POST["theme"] ?? "");
|
||||
|
||||
if (empty($avatar)) $avatar = $config["default"]["avatar"];
|
||||
if (empty($theme)) $theme = $config["default"]["theme"];
|
||||
|
||||
$data = array(
|
||||
"avatar" => $avatar,
|
||||
"theme" => $theme,
|
||||
);
|
||||
$db["users"]->updateById($user["id"], $data);
|
||||
die("success");
|
89
public/ajax/account/signup.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if ($logged) die("You're already logged in!");
|
||||
|
||||
$username = clean($_POST["username"] ?? "");
|
||||
$password = clean($_POST["password"] ?? "");
|
||||
$password2 = clean($_POST["password2"] ?? "");
|
||||
$email = clean($_POST["email"] ?? "");
|
||||
$email2 = clean($_POST["email2"] ?? "");
|
||||
|
||||
if ($config["captcha"]["enabled"]) {
|
||||
if ($config["captcha"]["type"] == "hcaptcha") {
|
||||
if (!hCaptcha($_POST['h-captcha-response'] ?? "")) die("Captcha is wrong!");
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($username)) die("Username is empty!");
|
||||
if (empty($password)) die("Password is empty!");
|
||||
if (empty($password2)) die("You need to repeat your password!");
|
||||
if (empty($email)) die("You need to enter an email!");
|
||||
if (empty($email2)) die("You need to confirm your email!");
|
||||
if (strlen($username) < 3) die("Username needs to be at least 3 characters long!");
|
||||
if (strlen($username) > 20) die("Username can only be 20 characters long!");
|
||||
if (strlen($password) < 8 || strlen($password) > 64) die("Password needs to be at leats 8 characters long!");
|
||||
if ($password != $password2) die("Passwords don't match!");
|
||||
if (strlen($email) < 6 || strlen($email) > 320) die("Email needs to be between 6 and 320 characters!");
|
||||
if (strlen($email2) < 6 || strlen($email2) > 320) die("Email needs to be between 6 and 320 characters!");
|
||||
if ($email != $email2) die("Emails don't match!");
|
||||
|
||||
$check = $db["users"]->findOneBy(["email", "=", $email]);
|
||||
if (!empty($check)) doLog("signup", false, "email already in use: " . $email) && die("Email already in use!");
|
||||
$check = $db["users"]->findOneBy(["username", "=", $username]);
|
||||
if (!empty($check)) doLog("signup", false, "username already in use: " . $username) && die("Username already in use!");
|
||||
$password = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
if ($config["activation"]) {
|
||||
$token = genToken();
|
||||
$activationLink = $config["url"] . "activate.php?token=" . $token;
|
||||
|
||||
try {
|
||||
$mailer->setFrom($config["email"], $config["title"] . " Staff", 0);
|
||||
$mailer->addAddress($email, $config["title"] . " Reader");
|
||||
$mailer->isHTML(true);
|
||||
$mailer->Subject = "Activate your account at " . $config["title"];
|
||||
$mailer->Body = "<b>Hello, {$username}</b><br>You just created an account on {$config["title"]}.<br>Please use the link below to activate it and gain access to all of the features:<br>{$activationLink}<br><br>Regards,<br>The {$config["title"]} Staff";
|
||||
$mailer->AltBody = "Hello {$username}, you just created an account on {$config["title"]}. Please activate it with this link to fully access our site: {$activationLink} - Best regards, the {$config["title"]} Staff.";
|
||||
$mailer->send();
|
||||
} catch (Exception $e) {
|
||||
die("Email died: {$mailer->ErrorInfo}");
|
||||
}
|
||||
$data = array(
|
||||
"token" => $token,
|
||||
"user" => $check
|
||||
);
|
||||
$db["activation"]->insert($data);
|
||||
$level = 49;
|
||||
} else {
|
||||
$level = 50;
|
||||
}
|
||||
|
||||
if (empty($db["users"]->findAll(null, 1))) $level = 100;
|
||||
|
||||
$data = array(
|
||||
"username" => $username,
|
||||
"password" => $password,
|
||||
"email" => $email,
|
||||
"avatar" => $config["default"]["avatar"],
|
||||
"level" => $level,
|
||||
"theme" => $usertheme,
|
||||
"lang" => $userlang,
|
||||
"banned" => false,
|
||||
"bannedReason" => null,
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db["users"]->insert($data);
|
||||
$token = genToken();
|
||||
$check = $db["users"]->findOneBy(["username", "=", $username]);
|
||||
$session = array(
|
||||
"user" => $check["id"],
|
||||
"token" => $token,
|
||||
"ip" => $_SERVER["REMOTE_ADDR"]
|
||||
);
|
||||
$db["sessions"]->insert($session);
|
||||
setcookie(cat($config["title"]) . "_session", $token, time() + 606024 * 9999, "/");
|
||||
|
||||
doLog("signup", true, null, $check["id"]);
|
||||
die("success");
|
12
public/ajax/account/updateLang.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("Not logged in.");
|
||||
|
||||
$newLang = cat($_POST["newLang"]);
|
||||
$data = array(
|
||||
"lang" => $newLang
|
||||
);
|
||||
$db["users"]->updateById($user["id"], $data);
|
||||
die("success");
|
24
public/ajax/alerts/dismiss.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("You're not logged in!");
|
||||
|
||||
$topAlert = $db["alerts"]->findAll(["id" => "DESC"], 1)[0];
|
||||
if (!empty($topAlert)) {
|
||||
$readAlert = !empty($db["alerts"]->findOneBy([["user", "=", $user["id"]], "AND", ["alert", "=", $topAlert["id"]]])) ? true : false;
|
||||
|
||||
if (!$readAlert) {
|
||||
$data = array(
|
||||
"user" => $user["id"],
|
||||
"alert" => $topAlert["id"],
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db["alertReads"]->insert($data);
|
||||
die("success");
|
||||
} else {
|
||||
die("Already dismissed!");
|
||||
}
|
||||
} else {
|
||||
die("No alert to dismiss!");
|
||||
}
|
51
public/ajax/chapters/add.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("Not logged in!");
|
||||
if ($user["level"] < 75) die("Missing permission!");
|
||||
|
||||
if (!isset($_POST["id"]) || empty($_POST["id"])) die("Missing ID!");
|
||||
if (!is_numeric($_POST["id"])) die("Invalid ID!");
|
||||
|
||||
(int) $id = cat($_POST["id"]);
|
||||
$title = $db["titles"]->findById($id);
|
||||
|
||||
if (empty($title)) die("Title does not exist!");
|
||||
|
||||
$number = cat($_POST["number"] ?? 1);
|
||||
$volume = cat($_POST["volume"] ?? null);
|
||||
$name = clean($_POST["title"] ?? "");
|
||||
$language = clean($_POST["language"] ?? "");
|
||||
|
||||
if (!isset($_FILES["zip"]) || empty($_FILES["zip"]["tmp_name"])) die("Missing ZIP file!");
|
||||
if (empty($number)) die("Invalid or empty chapter number!");
|
||||
if (empty($language)) die("Invalid Language!");
|
||||
$language = explode(",", $language);
|
||||
if (empty($language[0]) || empty($language[1])) die("Invalid Language!");
|
||||
|
||||
$mime = mime_content_type($_FILES["zip"]["tmp_name"]);
|
||||
$type = strtolower(pathinfo($_FILES["zip"]["name"], PATHINFO_EXTENSION));
|
||||
|
||||
if (empty($_FILES["zip"]["tmp_name"])) die("Invalid File!");
|
||||
if ($mime != "application/zip" || $type != "zip") die("Only ZIP is supported!");
|
||||
if ($_FILES["zip"]["size"] > $config["mfs"]["chapter"]) die(je(["s" => false, "msg" => "ZIP too large. Cannot exceed " . formatBytes($config["mfs"]["chapter"]) . "!"]));
|
||||
|
||||
$cid = $db["chapters"]->getLastInsertedId() + 1;
|
||||
|
||||
if (file_exists(ps(__DIR__ . "/../../data/chapters/{$cid}"))) rmrf(ps(__DIR__ . "/../../data/chapters/{$cid}"));
|
||||
$result = unzip($_FILES["zip"]["tmp_name"], ps(__DIR__ . "/../../data/tmp/{$cid}"), ps(__DIR__ . "/../../data/chapters/{$cid}"));
|
||||
if ($result !== "success") die($result);
|
||||
|
||||
$data = array(
|
||||
"number" => namba($number),
|
||||
"volume" => namba($volume),
|
||||
"name" => $name,
|
||||
"language" => $language,
|
||||
"title" => $title,
|
||||
"user" => $user,
|
||||
"lastEdited" => now(),
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db["chapters"]->insert($data);
|
||||
die("success");
|
56
public/ajax/chapters/edit.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("Not logged in!");
|
||||
if ($user["level"] < 75) die("Missing permission!");
|
||||
|
||||
if (!isset($_POST["id"]) || empty($_POST["id"])) die("Missing ID!");
|
||||
if (!isset($_POST["cid"]) || empty($_POST["cid"]) || !is_numeric($_POST["cid"])) die("Invalid Chapter ID!");
|
||||
if (!is_numeric($_POST["id"])) die("Invalid ID!");
|
||||
|
||||
$id = cat($_POST["id"]);
|
||||
$cid = cat($_POST["cid"]);
|
||||
$title = $db["titles"]->findById($id);
|
||||
|
||||
if (empty($title)) die("Title does not exist!");
|
||||
|
||||
$number = cat($_POST["number"] ?? 1);
|
||||
$volume = cat($_POST["volume"] ?? null);
|
||||
$name = clean($_POST["title"] ?? "");
|
||||
$language = clean($_POST["language"] ?? "");
|
||||
|
||||
if (empty($number)) die("Invalid or empty chapter number!");
|
||||
if (empty($language)) die("Invalid Language!");
|
||||
$language = explode(",", $language);
|
||||
if (empty($language[0]) || empty($language[1])) die("Invalid Language!");
|
||||
|
||||
$zip = false;
|
||||
if (!isset($_FILES["zip"]) && !empty($_FILES["zip"]["tmp_name"])) {
|
||||
$zip = true;
|
||||
$mime = mime_content_type($_FILES["zip"]["tmp_name"]);
|
||||
$type = strtolower(pathinfo($_FILES["zip"]["name"], PATHINFO_EXTENSION));
|
||||
|
||||
if (empty($_FILES["zip"]["tmp_name"])) die("Invalid File!");
|
||||
if ($mime != "application/zip" || $type != "zip") die("Only ZIP is supported!");
|
||||
if ($_FILES["zip"]["size"] > $config["mfs"]["chapter"]) die(je(["s" => false, "msg" => "ZIP too large. Cannot exceed " . formatBytes($config["mfs"]["chapter"]) . "!"]));
|
||||
}
|
||||
|
||||
if ($zip) {
|
||||
$result = unzip($_FILES["zip"]["tmp_name"], ps(__DIR__ . "/../../data/tmp/{$cid}"), ps(__DIR__ . "/../../data/chapters/{$cid}"));
|
||||
if ($result !== "success") die($result);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
"number" => namba($number),
|
||||
"volume" => namba($volume),
|
||||
"name" => $name,
|
||||
"language" => $language,
|
||||
"title" => $title,
|
||||
"user" => $user,
|
||||
"lastEdited" => now(),
|
||||
"timestamp" => now()
|
||||
);
|
||||
|
||||
$db["chapters"]->updateById($cid, $data);
|
||||
die("success");
|
26
public/ajax/chapters/read.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("Not logged in!");
|
||||
if ($user["level"] < 75) die("Missing permission!");
|
||||
|
||||
if (!isset($_POST["user"]) || empty($_POST["user"]) || !is_numeric($_POST["user"])) die("Invalid User!");
|
||||
if (!isset($_POST["chapter"]) || empty($_POST["chapter"]) || !is_numeric($_POST["chapter"])) die("Invalid Chapter!");
|
||||
|
||||
$uid = cat($_POST["user"] ?? 0);
|
||||
$cid = cat($_POST["chapter"] ?? 0);
|
||||
|
||||
$check = $db["readChapters"]->findOneBy(["chapter", "==", $cid]);
|
||||
if (empty($check)) {
|
||||
$data = array(
|
||||
"user" => $uid,
|
||||
"chapter" => $cid,
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db["readChapters"]->insert($data);
|
||||
} else {
|
||||
$db["readChapters"]->deleteById($check["id"]);
|
||||
}
|
||||
|
||||
die("success");
|
28
public/ajax/images/tmp.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die(je([false, "You're not logged in!"]));
|
||||
|
||||
if (!isset($_FILES["cover"]["name"])) die(je(["s" => false, "msg" => "No image to upload!"]));
|
||||
|
||||
// Upload image on select
|
||||
if (!file_exists("../../data/tmp")) mkdir("../../data/tmp", 0777, true); // Create TMP folder if not exists
|
||||
$string = genUuid();
|
||||
$target_file = "../../data/tmp/" . $string . "-" . basename($_FILES["cover"]["name"]);
|
||||
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
|
||||
$target_file = "../../data/tmp/" . $string . "." . $imageFileType;
|
||||
$output_file = $string . "." . $imageFileType;
|
||||
|
||||
// Check if image file is a actual image or fake image
|
||||
$check = getimagesize($_FILES["cover"]["tmp_name"]);
|
||||
if ($check == false) die(je(["s" => false, "msg" => "Invalid Image!"]));
|
||||
|
||||
// Check file size
|
||||
if ($_FILES["cover"]["size"] > $config["mfs"]["cover"]) die(je(["s" => false, "msg" => "Cover too large. Cannot exceed " . formatBytes($config["mfs"]["cover"]) . "!"]));
|
||||
|
||||
// Allow certain file formats
|
||||
if ($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" && $imageFileType != "webp") die(je(["s" => false, "msg" => "Invalid Image! Please use JPEG, PNG or WEBP."]));
|
||||
|
||||
if (!move_uploaded_file($_FILES["cover"]["tmp_name"], $target_file)) die(je(["s" => false, "msg" => "Something went wrong..."]));
|
||||
die(je(["s" => true, "msg" => $output_file]));
|
129
public/ajax/titles/add.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("You're not logged in!");
|
||||
if ($user["level"] < 75) die("Missing permissions to do this!");
|
||||
|
||||
$cover = clean($_POST["cover"] ?? "");
|
||||
$title = clean($_POST["title"] ?? "");
|
||||
$alts = clean($_POST["alts"] ?? "");
|
||||
$authors = clean($_POST["authors"] ?? "");
|
||||
$artists = clean($_POST["artists"] ?? "");
|
||||
$olang = clean($_POST["olang"] ?? "");
|
||||
$ostatus = clean($_POST["ostatus"] ?? "");
|
||||
$sstatus = clean($_POST["sstatus"] ?? "");
|
||||
$release = clean($_POST["release"] ?? "");
|
||||
$completion = clean($_POST["completion"] ?? "");
|
||||
$summary = clean($_POST["summary"] ?? "");
|
||||
|
||||
$formats = $_POST["format"] ?? array();
|
||||
$warnings = $_POST["warning"] ?? array();
|
||||
$themes = $_POST["theme"] ?? array();
|
||||
$genres = $_POST["genre"] ?? array();
|
||||
|
||||
if (empty($cover)) die(je(["s" => false, "msg" => "Cover is required!"]));
|
||||
if (empty($title)) die(je(["s" => false, "msg" => "Title is required!"]));
|
||||
switch ($ostatus) {
|
||||
case 1:
|
||||
break;
|
||||
case 3:
|
||||
break;
|
||||
case 4:
|
||||
break;
|
||||
case 5:
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
$ostatus = 2;
|
||||
}
|
||||
switch ($sstatus) {
|
||||
case 1:
|
||||
break;
|
||||
case 3:
|
||||
break;
|
||||
case 4:
|
||||
break;
|
||||
case 5:
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
$sstatus = 2;
|
||||
}
|
||||
if (empty($olang)) die(je(["s" => false, "msg" => "You need to enter an original language."]));
|
||||
if (!empty($release)) {
|
||||
if (!is_numeric($release)) die(je(["s" => false, "msg" => "You messed with the Release Year!"]));
|
||||
if (strlen($release) != 4) die(je(["s" => false, "msg" => "Release years tend to be four digits long."]));
|
||||
}
|
||||
if (!empty($completion)) {
|
||||
if (!is_numeric($completion)) die(je(["s" => false, "msg" => "You messed with the Completion Year!"]));
|
||||
if (strlen($completion) != 4) die(je(["s" => false, "msg" => "Completion years tend to be four digits long."]));
|
||||
}
|
||||
|
||||
if (!is_array($formats)) die(je(["s" => false, "msg" => "Formats should be an array."]));
|
||||
if (!is_array($warnings)) die(je(["s" => false, "msg" => "Warnings should be an array."]));
|
||||
if (!is_array($themes)) die(je(["s" => false, "msg" => "Themes should be an array."]));
|
||||
if (!is_array($genres)) die(je(["s" => false, "msg" => "Genres should be an array."]));
|
||||
|
||||
if (!empty($formats)) {
|
||||
$_formats = array();
|
||||
foreach ($formats as $format) {
|
||||
if (!empty($format)) array_push($_formats, trim(cat($format)));
|
||||
}
|
||||
$formats = $_formats;
|
||||
}
|
||||
if (!empty($warnings)) {
|
||||
$_warnings = array();
|
||||
foreach ($warnings as $warning) {
|
||||
if (!empty($warning)) array_push($_warnings, trim(cat($warning)));
|
||||
}
|
||||
$warnings = $_warnings;
|
||||
}
|
||||
if (!empty($themes)) {
|
||||
$_themes = array();
|
||||
foreach ($themes as $theme) {
|
||||
if (!empty($theme)) array_push($_themes, trim(cat($theme)));
|
||||
}
|
||||
$themes = $_themes;
|
||||
}
|
||||
if (!empty($genres)) {
|
||||
$_genres = array();
|
||||
foreach ($genres as $genre) {
|
||||
if (!empty($genre)) array_push($_genres, trim(cat($genre)));
|
||||
}
|
||||
$genres = $_genres;
|
||||
}
|
||||
|
||||
// So far, everything is correct
|
||||
if (!empty($db["titles"]->findOneBy(["title", "=", $title]))) die(je(["s" => false, "msg" => "Title already exists!"]));
|
||||
|
||||
//die(je(["s" => false, "msg" => $cover]));
|
||||
if (!file_exists("../../data/covers")) mkdir("../../data/covers", 0777, true);
|
||||
if (file_exists("../../data/tmp/" . $cover)) rename("../../data/tmp/" . $cover, "../../data/covers/" . ($db["titles"]->getLastInsertedId() + 1) . ".png");
|
||||
|
||||
$data = array(
|
||||
"title" => $title,
|
||||
"alts" => $alts,
|
||||
"authors" => $authors,
|
||||
"artists" => $artists,
|
||||
"lang" => $olang,
|
||||
"status" => $ostatus,
|
||||
"sstatus" => $sstatus,
|
||||
"releaseYear" => $release,
|
||||
"completionYear" => $completion,
|
||||
"summary" => $summary,
|
||||
"tags" => array(
|
||||
"formats" => $formats,
|
||||
"warnings" => $warnings,
|
||||
"themes" => $themes,
|
||||
"genres" => $genres
|
||||
),
|
||||
"creator" => $user,
|
||||
"lastEdited" => now(),
|
||||
"timestamp" => now()
|
||||
);
|
||||
$db["titles"]->insert($data);
|
||||
$title = $db["titles"]->findOneBy(["title", "=", $title]);
|
||||
|
||||
// Everything is right, return true and title ID
|
||||
die(je(["s" => true, "msg" => $title["id"]]));
|
133
public/ajax/titles/edit.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
require "../../../autoload.php";
|
||||
|
||||
if (!$logged) die("You're not logged in!");
|
||||
if ($user["level"] < 75) die("Missing permissions to do this!");
|
||||
if (!isset($_POST["id"]) || empty($_POST["id"])) die("Empty ID.");
|
||||
if (!is_numeric($_POST["id"])) die("Invalid ID.");
|
||||
|
||||
$id = clean($_POST["id"] ?? "");
|
||||
|
||||
$title = $db["titles"]->findById($id);
|
||||
if (empty($title)) die(je(["s" => false, "msg" => "Title not found!"]));
|
||||
|
||||
$cover = clean($_POST["cover"] ?? "");
|
||||
$name = clean($_POST["title"] ?? "");
|
||||
$alts = clean($_POST["alts"] ?? "");
|
||||
$authors = clean($_POST["authors"] ?? "");
|
||||
$artists = clean($_POST["artists"] ?? "");
|
||||
$olang = clean($_POST["olang"] ?? "");
|
||||
$ostatus = clean($_POST["ostatus"] ?? "");
|
||||
$sstatus = clean($_POST["sstatus"] ?? "");
|
||||
$release = clean($_POST["release"] ?? "");
|
||||
$completion = clean($_POST["completion"] ?? "");
|
||||
$summary = clean($_POST["summary"] ?? "");
|
||||
|
||||
$formats = $_POST["format"] ?? array();
|
||||
$warnings = $_POST["warning"] ?? array();
|
||||
$themes = $_POST["theme"] ?? array();
|
||||
$genres = $_POST["genre"] ?? array();
|
||||
|
||||
if (empty($cover)) die(je(["s" => false, "msg" => "Cover is required!"]));
|
||||
if (empty($name)) die(je(["s" => false, "msg" => "Title is required!"]));
|
||||
switch ($ostatus) {
|
||||
case 1:
|
||||
break;
|
||||
case 3:
|
||||
break;
|
||||
case 4:
|
||||
break;
|
||||
case 5:
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
$ostatus = 2;
|
||||
}
|
||||
switch ($sstatus) {
|
||||
case 1:
|
||||
break;
|
||||
case 3:
|
||||
break;
|
||||
case 4:
|
||||
break;
|
||||
case 5:
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
$sstatus = 2;
|
||||
}
|
||||
if (empty($olang)) die(je(["s" => false, "msg" => "You need to enter an original language."]));
|
||||
if (!empty($release)) {
|
||||
if (!is_numeric($release)) die(je(["s" => false, "msg" => "You messed with the Release Year!"]));
|
||||
if (strlen($release) != 4) die(je(["s" => false, "msg" => "Release years tend to be four digits long."]));
|
||||
}
|
||||
if (!empty($completion)) {
|
||||
if (!is_numeric($completion)) die(je(["s" => false, "msg" => "You messed with the Completion Year!"]));
|
||||
if (strlen($completion) != 4) die(je(["s" => false, "msg" => "Completion years tend to be four digits long."]));
|
||||
}
|
||||
|
||||
if (!is_array($formats)) die(je(["s" => false, "msg" => "Formats should be an array."]));
|
||||
if (!is_array($warnings)) die(je(["s" => false, "msg" => "Warnings should be an array."]));
|
||||
if (!is_array($themes)) die(je(["s" => false, "msg" => "Themes should be an array."]));
|
||||
if (!is_array($genres)) die(je(["s" => false, "msg" => "Genres should be an array."]));
|
||||
|
||||
if (!empty($formats)) {
|
||||
$_formats = array();
|
||||
foreach ($formats as $format) {
|
||||
if (!empty($format)) array_push($_formats, trim(cat($format)));
|
||||
}
|
||||
$formats = $_formats;
|
||||
}
|
||||
if (!empty($warnings)) {
|
||||
$_warnings = array();
|
||||
foreach ($warnings as $warning) {
|
||||
if (!empty($warning)) array_push($_warnings, trim(cat($warning)));
|
||||
}
|
||||
$warnings = $_warnings;
|
||||
}
|
||||
if (!empty($themes)) {
|
||||
$_themes = array();
|
||||
foreach ($themes as $theme) {
|
||||
if (!empty($theme)) array_push($_themes, trim(cat($theme)));
|
||||
}
|
||||
$themes = $_themes;
|
||||
}
|
||||
if (!empty($genres)) {
|
||||
$_genres = array();
|
||||
foreach ($genres as $genre) {
|
||||
if (!empty($genre)) array_push($_genres, trim(cat($genre)));
|
||||
}
|
||||
$genres = $_genres;
|
||||
}
|
||||
|
||||
// So far, everything is correct
|
||||
if ($title != $title["title"])
|
||||
if (!empty($db["titles"]->findOneBy(["title", "=", $title]))) die(je(["s" => false, "msg" => "Title already exists!"]));
|
||||
|
||||
if (!file_exists("../../data/covers")) mkdir("../../data/covers", 0777, true);
|
||||
if (file_exists("../../data/tmp/" . $cover)) rename("../../data/tmp/" . $cover, "../../data/covers/" . $title["id"] . ".png");
|
||||
|
||||
$data = array(
|
||||
"title" => $name,
|
||||
"alts" => $alts,
|
||||
"authors" => $authors,
|
||||
"artists" => $artists,
|
||||
"lang" => $olang,
|
||||
"status" => $ostatus,
|
||||
"sstatus" => $sstatus,
|
||||
"releaseYear" => $release,
|
||||
"completionYear" => $completion,
|
||||
"summary" => $summary,
|
||||
"tags" => array(
|
||||
"formats" => $formats,
|
||||
"warnings" => $warnings,
|
||||
"themes" => $themes,
|
||||
"genres" => $genres
|
||||
),
|
||||
"lastEdited" => now()
|
||||
);
|
||||
$db["titles"]->updateById($title["id"], $data);
|
||||
|
||||
// Everything is right, return true and title ID
|
||||
die(je(["s" => true, "msg" => $title["id"]]));
|
88
public/api.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
require "../autoload.php";
|
||||
header("Content-Type: application/json");
|
||||
|
||||
if (isset($_GET["isOnline"])) die(je(true));
|
||||
if (isset($_GET["getAnalytics"])) {
|
||||
if (!$config["shareAnonymousAnalytics"]) die(je(["code" => 503, "message" => "Analytics are disabled."]));
|
||||
if (!isset($_GET["key"]) || empty($_GET["key"])) die(je(["code" => 401, "message" => "Key not supplied."]));
|
||||
$key = clean($_GET["key"]);
|
||||
if ($key != file_get_contents(ps(__DIR__ . "/../library/secrets/key.txt"))) die(je(["code" => 498, "message" => "Invalid key."]));
|
||||
$data = array(
|
||||
"code" => 201,
|
||||
"version" => file_get_contents(ps(__DIR__ . "/../version.txt")),
|
||||
"config" => array(
|
||||
"title" => $config["title"],
|
||||
"divider" => $config["divider"],
|
||||
"slogan" => $config["slogan"],
|
||||
"logs" => $config["logs"],
|
||||
"debug" => $config["debug"],
|
||||
"activation" => $config["activation"],
|
||||
"url" => $config["url"],
|
||||
"db" => array(
|
||||
"type" => $config["db"]["type"],
|
||||
"sleek" => array(
|
||||
"dir" => $config["db"]["sleek"]["dir"],
|
||||
"config" => $config["db"]["sleek"]["config"]
|
||||
)
|
||||
),
|
||||
"mfs" => array(
|
||||
"cover" => $config["mfs"]["cover"],
|
||||
"chapter" => $config["mfs"]["chapter"]
|
||||
),
|
||||
"default" => array(
|
||||
"theme" => $config["default"]["theme"],
|
||||
"lang" => $config["default"]["lang"],
|
||||
"avatar" => $config["default"]["avatar"]
|
||||
),
|
||||
"captcha" => array(
|
||||
"enabled" => $config["captcha"]["enabled"],
|
||||
"type" => $config["captcha"]["type"]
|
||||
),
|
||||
"themes" => $config["themes"],
|
||||
"langs" => $config["langs"],
|
||||
"avatars" => $config["avatars"],
|
||||
"perpage" => array(
|
||||
"titles" => $config["perpage"]["titles"],
|
||||
"chapters" => $config["perpage"]["chapters"],
|
||||
)
|
||||
),
|
||||
"statistics" => array(
|
||||
"users" => $db["users"]->count(),
|
||||
"titles" => $db["titles"]->count(),
|
||||
"comments" => array(
|
||||
"chapters" => $db["chapterComments"]->count(),
|
||||
"titles" => $db["titleComments"]->count(),
|
||||
"profiles" => $db["profileComments"]->count(),
|
||||
"total" => ($db["chapterComments"]->count() + $db["titleComments"]->count() + $db["profileComments"]->count())
|
||||
),
|
||||
"views" => array(
|
||||
"chapters" => $db["chapterViews"]->count(),
|
||||
"titles" => $db["titleViews"]->count(),
|
||||
"profiles" => $db["profileViews"]->count(),
|
||||
"total" => ($db["chapterViews"]->count() + $db["titleViews"]->count() + $db["profileViews"]->count()),
|
||||
"visits" => $db["visitLogs"]->count()
|
||||
),
|
||||
),
|
||||
"website" => array(
|
||||
"onlineSince" => $started ?? "file deleted or plugin disabled",
|
||||
"checks" => [
|
||||
now() => $started ?? "file deleted or plugin disabled"
|
||||
]
|
||||
),
|
||||
"server" => array(
|
||||
"self" => $_SERVER["PHP_SELF"],
|
||||
"server" => $_SERVER["SERVER_NAME"],
|
||||
"host" => $_SERVER["HTTP_HOST"],
|
||||
"agent" => $_SERVER["HTTP_USER_AGENT"],
|
||||
"script" => $_SERVER["SCRIPT_NAME"],
|
||||
"gateway" => $_SERVER["GATEWAY_INTERFACE"],
|
||||
"software" => $_SERVER["SERVER_SOFTWARE"],
|
||||
"protocol" => $_SERVER["SERVER_PROTOCOL"],
|
||||
"time" => $_SERVER["REQUEST_TIME"]
|
||||
),
|
||||
"timestamp" => now()
|
||||
);
|
||||
die(je($data));
|
||||
}
|
6
public/assets/favicon/about.txt
Normal file
@ -0,0 +1,6 @@
|
||||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: ABeeZee
|
||||
- Font Author: Copyright 2011 The ABeeZee Project Authors (https://github.com/googlefonts/abeezee) with Reserved Font Name ABeeZee
|
||||
- Font Source: http://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
BIN
public/assets/favicon/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
public/assets/favicon/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
public/assets/favicon/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
public/assets/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 494 B |
BIN
public/assets/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 984 B |
BIN
public/assets/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
1
public/assets/favicon/site.webmanifest
Normal file
@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
147
public/assets/js.cookie.js
Normal file
@ -0,0 +1,147 @@
|
||||
/*! js-cookie v3.0.1 | MIT */
|
||||
;
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = global || self, (function () {
|
||||
var current = global.Cookies;
|
||||
var exports = global.Cookies = factory();
|
||||
exports.noConflict = function () { global.Cookies = current; return exports; };
|
||||
}()));
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
/* eslint-disable no-var */
|
||||
function assign (target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i];
|
||||
for (var key in source) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
/* eslint-enable no-var */
|
||||
|
||||
/* eslint-disable no-var */
|
||||
var defaultConverter = {
|
||||
read: function (value) {
|
||||
if (value[0] === '"') {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
|
||||
},
|
||||
write: function (value) {
|
||||
return encodeURIComponent(value).replace(
|
||||
/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,
|
||||
decodeURIComponent
|
||||
)
|
||||
}
|
||||
};
|
||||
/* eslint-enable no-var */
|
||||
|
||||
/* eslint-disable no-var */
|
||||
|
||||
function init (converter, defaultAttributes) {
|
||||
function set (key, value, attributes) {
|
||||
if (typeof document === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
attributes = assign({}, defaultAttributes, attributes);
|
||||
|
||||
if (typeof attributes.expires === 'number') {
|
||||
attributes.expires = new Date(Date.now() + attributes.expires * 864e5);
|
||||
}
|
||||
if (attributes.expires) {
|
||||
attributes.expires = attributes.expires.toUTCString();
|
||||
}
|
||||
|
||||
key = encodeURIComponent(key)
|
||||
.replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
|
||||
.replace(/[()]/g, escape);
|
||||
|
||||
var stringifiedAttributes = '';
|
||||
for (var attributeName in attributes) {
|
||||
if (!attributes[attributeName]) {
|
||||
continue
|
||||
}
|
||||
|
||||
stringifiedAttributes += '; ' + attributeName;
|
||||
|
||||
if (attributes[attributeName] === true) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Considers RFC 6265 section 5.2:
|
||||
// ...
|
||||
// 3. If the remaining unparsed-attributes contains a %x3B (";")
|
||||
// character:
|
||||
// Consume the characters of the unparsed-attributes up to,
|
||||
// not including, the first %x3B (";") character.
|
||||
// ...
|
||||
stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];
|
||||
}
|
||||
|
||||
return (document.cookie =
|
||||
key + '=' + converter.write(value, key) + stringifiedAttributes)
|
||||
}
|
||||
|
||||
function get (key) {
|
||||
if (typeof document === 'undefined' || (arguments.length && !key)) {
|
||||
return
|
||||
}
|
||||
|
||||
// To prevent the for loop in the first place assign an empty array
|
||||
// in case there are no cookies at all.
|
||||
var cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||
var jar = {};
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var parts = cookies[i].split('=');
|
||||
var value = parts.slice(1).join('=');
|
||||
|
||||
try {
|
||||
var foundKey = decodeURIComponent(parts[0]);
|
||||
jar[foundKey] = converter.read(value, foundKey);
|
||||
|
||||
if (key === foundKey) {
|
||||
break
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return key ? jar[key] : jar
|
||||
}
|
||||
|
||||
return Object.create(
|
||||
{
|
||||
set: set,
|
||||
get: get,
|
||||
remove: function (key, attributes) {
|
||||
set(
|
||||
key,
|
||||
'',
|
||||
assign({}, attributes, {
|
||||
expires: -1
|
||||
})
|
||||
);
|
||||
},
|
||||
withAttributes: function (attributes) {
|
||||
return init(this.converter, assign({}, this.attributes, attributes))
|
||||
},
|
||||
withConverter: function (converter) {
|
||||
return init(assign({}, this.converter, converter), this.attributes)
|
||||
}
|
||||
},
|
||||
{
|
||||
attributes: { value: Object.freeze(defaultAttributes) },
|
||||
converter: { value: Object.freeze(converter) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var api = init(defaultConverter, { path: '/' });
|
||||
/* eslint-enable no-var */
|
||||
|
||||
return api;
|
||||
|
||||
})));
|
2
public/assets/miku_cursor/CREDITS.txt
Normal file
@ -0,0 +1,2 @@
|
||||
By: MIMI DESTINO
|
||||
Source: https://www.rw-designer.com/cursor-set/miku-hatsune-md
|
BIN
public/assets/miku_cursor/busy.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
public/assets/miku_cursor/diagonal-1.png
Normal file
After Width: | Height: | Size: 922 B |
BIN
public/assets/miku_cursor/diagonal-2.png
Normal file
After Width: | Height: | Size: 967 B |
BIN
public/assets/miku_cursor/handwriting.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/miku_cursor/help.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/miku_cursor/link.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/miku_cursor/move.png
Normal file
After Width: | Height: | Size: 922 B |
BIN
public/assets/miku_cursor/pos-select.png
Normal file
After Width: | Height: | Size: 410 B |
BIN
public/assets/miku_cursor/resize-h.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/assets/miku_cursor/resize-v.png
Normal file
After Width: | Height: | Size: 745 B |
BIN
public/assets/miku_cursor/select-alt.png
Normal file
After Width: | Height: | Size: 642 B |
BIN
public/assets/miku_cursor/select-alt2.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/miku_cursor/select.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/miku_cursor/text.png
Normal file
After Width: | Height: | Size: 218 B |
BIN
public/assets/miku_cursor/unavailable.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/assets/miku_cursor/working.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
4
public/assets/nucleus/autotheme.js
Normal file
@ -0,0 +1,4 @@
|
||||
function assignTheme(prefix) {
|
||||
let theme = Cookies.get(prefix + "daisytheme");
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
}
|
8
public/assets/nucleus/carousel.js
Normal file
@ -0,0 +1,8 @@
|
||||
function switchCarLink(evt,) {
|
||||
let carlinks, i;
|
||||
carlinks = document.getElementsByClassName("carlink");
|
||||
for (i = 0; i < carlinks.length; i++) {
|
||||
carlinks[i].classList.remove("btn-active");
|
||||
}
|
||||
evt.currentTarget.classList.add("btn-active");
|
||||
}
|
BIN
public/assets/nucleus/logo.png
Normal file
After Width: | Height: | Size: 45 KiB |
14
public/assets/nucleus/tabs.js
Normal file
@ -0,0 +1,14 @@
|
||||
function switchTab(evt, tab) {
|
||||
let i, tabcontent, tablinks;
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].classList.add("hidden");
|
||||
}
|
||||
tablinks = document.getElementsByClassName("tablink");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].classList.remove("tab-active");
|
||||
}
|
||||
document.getElementById(tab).classList.remove("hidden");
|
||||
document.getElementById(tab).classList.add("block");
|
||||
evt.currentTarget.classList.add("tab-active");
|
||||
}
|
55
public/assets/nucleus/tags.js
Normal file
@ -0,0 +1,55 @@
|
||||
function insertTag(id, toInsert, toInsertAfter, cursorWhere) {
|
||||
let target = document.getElementById(id);
|
||||
if (target.setRangeText) {
|
||||
target.setRangeText(toInsert + toInsertAfter);
|
||||
target.focus();
|
||||
setCaretToPos(target, Number(doGetCaretPosition(target) - cursorWhere));
|
||||
} else {
|
||||
target.focus();
|
||||
document.execCommand("insertText", false /*no UI*/, toInsert + toInsertAfter);
|
||||
target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectionRange(input, selectionStart, selectionEnd) {
|
||||
if (input.setSelectionRange) {
|
||||
input.focus();
|
||||
input.setSelectionRange(selectionStart, selectionEnd);
|
||||
}
|
||||
else if (input.createTextRange) {
|
||||
let range = input.createTextRange();
|
||||
range.collapse(true);
|
||||
range.moveEnd('character', selectionEnd);
|
||||
range.moveStart('character', selectionStart);
|
||||
range.select();
|
||||
}
|
||||
}
|
||||
|
||||
function setCaretToPos(input, pos) {
|
||||
setSelectionRange(input, pos, pos);
|
||||
}
|
||||
|
||||
/*
|
||||
** Returns the caret (cursor) position of the specified text field (oField).
|
||||
** Return value range is 0-oField.value.length.
|
||||
*/
|
||||
function doGetCaretPosition(oField) {
|
||||
// Initialize
|
||||
let iCaretPos = 0;
|
||||
// IE Support
|
||||
if (document.selection) {
|
||||
// Set focus on the element
|
||||
oField.focus();
|
||||
// To get cursor position, get empty selection range
|
||||
let oSel = document.selection.createRange();
|
||||
// Move selection start to 0 position
|
||||
oSel.moveStart('character', -oField.value.length);
|
||||
// The caret position is selection length
|
||||
iCaretPos = oSel.text.length;
|
||||
}
|
||||
// Firefox support
|
||||
else if (oField.selectionStart || oField.selectionStart == '0')
|
||||
iCaretPos = oField.selectionDirection == 'backward' ? oField.selectionStart : oField.selectionEnd;
|
||||
// Return results
|
||||
return iCaretPos;
|
||||
}
|
BIN
public/assets/other/kofi_button_blue.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
public/assets/other/kofi_s_tag_white.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
public/assets/other/paypal-donate-button.png
Normal file
After Width: | Height: | Size: 10 KiB |
24
public/assets/page.js
Normal file
@ -0,0 +1,24 @@
|
||||
$(function () {
|
||||
$("a[rel='tab']").click(function (e) {
|
||||
let pageurl = $(this).attr('href');
|
||||
$.ajax({
|
||||
url: pageurl + '?rel=tab', success: function (data) {
|
||||
$('#content').html(data);
|
||||
}
|
||||
});
|
||||
if (pageurl != window.location) {
|
||||
window.history.pushState({ path: pageurl }, '', pageurl);
|
||||
}
|
||||
document.title = $(this).attr("ttl") + " " + $("#siteDivider").val() + " " + $("#siteName").val(); $('html, body')
|
||||
.animate({ scrollTop: 0 }, 'fast');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
$(window).bind('popstate', function () {
|
||||
$.ajax({
|
||||
url: location.pathname + '?rel=tab', success: function (data) {
|
||||
$('#content').html(data);
|
||||
}
|
||||
});
|
||||
});
|
28
public/chapter.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
require "../autoload.php";
|
||||
|
||||
if (!isset($_GET["id"]) || empty($_GET["id"])) header("Location: index.php") && die("No or empty ID.");
|
||||
if (!is_numeric($_GET["id"])) header("Location: index.php") && die("Invalid ID.");
|
||||
$id = cat($_GET["id"]);
|
||||
$chapter = $db["chapters"]->findById($id);
|
||||
$title = $db["titles"]->findById($chapter["title"]["id"]);
|
||||
if (empty($title)) header("Location: titles.php") && die("Title not found.");
|
||||
if (empty($chapter)) header("Location: title.php?id={$title["id"]}") && die("Chapter not found.");
|
||||
|
||||
$images = glob(ps(__DIR__ . "/data/chapters/{$id}/*.{jpg,png,jpeg,webp,gif}"), GLOB_BRACE);
|
||||
natsort($images);
|
||||
|
||||
$comments = $db["chapterComments"]->findBy(["chapter.id", "=", $title["id"]], ["id" => "DESC"]);
|
||||
|
||||
chapterVisit($chapter);
|
||||
|
||||
$smarty->assign("commentsCount", count($comments));
|
||||
$smarty->assign("title", $title);
|
||||
$smarty->assign("chapter", $chapter);
|
||||
$smarty->assign("images", $images);
|
||||
$smarty->assign("pagetitle", "Read " . $title["title"] . " " . formatChapterTitle($chapter["volume"], $chapter["number"]) . " (Chapter) " . $config["divider"] . " " . $config["title"]);
|
||||
|
||||
$smarty->display("parts/header.tpl");
|
||||
$smarty->display("pages/chapter.tpl");
|
||||
$smarty->display("parts/footer.tpl");
|
BIN
public/data/chapters/1/1.jpg
Normal file
After Width: | Height: | Size: 368 KiB |
BIN
public/data/chapters/1/2.jpg
Normal file
After Width: | Height: | Size: 267 KiB |
BIN
public/data/chapters/1/3.jpg
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
public/data/chapters/1/4.jpg
Normal file
After Width: | Height: | Size: 658 KiB |
BIN
public/data/chapters/1/5.jpg
Normal file
After Width: | Height: | Size: 291 KiB |
BIN
public/data/chapters/1/6.png
Normal file
After Width: | Height: | Size: 6.7 MiB |
BIN
public/data/chapters/1/7.jpg
Normal file
After Width: | Height: | Size: 222 KiB |
BIN
public/data/chapters/1/8.png
Normal file
After Width: | Height: | Size: 13 MiB |
BIN
public/data/chapters/1/9.png
Normal file
After Width: | Height: | Size: 3.4 MiB |
BIN
public/data/chapters/10/1.jpg
Normal file
After Width: | Height: | Size: 281 KiB |
BIN
public/data/chapters/10/10.jpg
Normal file
After Width: | Height: | Size: 302 KiB |
BIN
public/data/chapters/10/11.jpg
Normal file
After Width: | Height: | Size: 302 KiB |
BIN
public/data/chapters/10/12.jpg
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
public/data/chapters/10/13.jpg
Normal file
After Width: | Height: | Size: 376 KiB |
BIN
public/data/chapters/10/14.jpg
Normal file
After Width: | Height: | Size: 323 KiB |
BIN
public/data/chapters/10/15.jpg
Normal file
After Width: | Height: | Size: 337 KiB |
BIN
public/data/chapters/10/16.jpg
Normal file
After Width: | Height: | Size: 298 KiB |
BIN
public/data/chapters/10/17.jpg
Normal file
After Width: | Height: | Size: 291 KiB |
BIN
public/data/chapters/10/18.jpg
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
public/data/chapters/10/19.jpg
Normal file
After Width: | Height: | Size: 356 KiB |
BIN
public/data/chapters/10/2.jpg
Normal file
After Width: | Height: | Size: 239 KiB |