This commit is contained in:
saintly2k 2023-03-30 23:00:50 +02:00
parent cdb4e3b5eb
commit 068303b34b
968 changed files with 85519 additions and 0 deletions

97
autoload.php Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,22 @@
[
[
"gb",
"English",
"🇬🇧"
],
[
"de",
"German",
"🇩🇪"
],
[
"pt",
"Portuguese",
"🇵🇹"
],
[
"fr",
"French",
"🇫🇷"
]
]

5
custom/warnings.json Normal file
View File

@ -0,0 +1,5 @@
[
"Gore",
"Sexual Violence",
"Smut"
]

396
funky.php Normal file
View 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
View File

@ -0,0 +1,10 @@
<?php
$lang = [
"info" => [
"name" => "English",
"author" => "Saintly & S-VHS",
"website" => "https://h33t.moe",
"updated" => "30.03.2023"
]
];

View 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"]));

View File

@ -0,0 +1 @@
Keep this, GitHub!

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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"));

View 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);
}

View 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

View 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"
];

View 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}

View 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 -->

View 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 -->

View 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">&#65283;</span>
<span class="hidden md:inline">Chapter</span>
</th>
<th class="text-center">
<span class="md:hidden">&#120139;</span>
<span class="hidden md:inline">Title</span>
</th>
<th class="text-center">
<span class="md:hidden">&#64;</span>
<span class="hidden md:inline">User</span>
</th>
<th class="text-right">
<span class="md:hidden">&#10226;</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">&#9989;</div>
<div class="swap-off">&#10060;</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}

View 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}

View 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}

View 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}

View 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}

View 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
View 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
View 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");

View 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");

View 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");

View 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");

View 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");

View 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");

View 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!");
}

View 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");

View 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");

View 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");

View 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
View 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
View 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
View 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));
}

View 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))

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View 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
View 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;
})));

View File

@ -0,0 +1,2 @@
By: MIMI DESTINO
Source: https://www.rw-designer.com/cursor-set/miku-hatsune-md

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,4 @@
function assignTheme(prefix) {
let theme = Cookies.get(prefix + "daisytheme");
document.body.setAttribute("data-theme", theme);
}

View 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");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View 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");
}

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

24
public/assets/page.js Normal file
View 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
View 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");

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Some files were not shown because too many files have changed in this diff Show More