Initial commit

This commit is contained in:
2021-07-02 15:30:45 +02:00
parent 26aec639e5
commit d49db34a1c
26 changed files with 1166 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
# Windows thumbnails
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Volatile files
backups/*
profiles/*
# Files that are not appropriate for git
created.txt
description.txt
title.txt
anti_doxx.json
# Sensitive information
api_key.php
local_secret.php
account.php
+635
View File
@@ -0,0 +1,635 @@
<?php
session_start();
// error reporting
error_reporting(E_ALL);
ini_set('display_errors', 1);
// libs
require "template_engine.php";
// globals
$session_username_key = "todo-username";
$session_password_key = "todo-password";
$session_register_notice_key = "todo-register-notice";
$get_register_key = "reg";
$profile_template_path = "new_profile.json";
$profile_extension = ".json";
$profiles_path = "profiles/";
$backup_path = "backups/";
$model = array();
// setup
if (!file_exists("backups"))
{
mkdir("backups");
}
if (!file_exists("profiles"))
{
mkdir("profiles");
}
// post
if (!empty($_POST["intent"]))
{
if ($_POST["intent"] == "login" && !empty($_POST["uname"]) && !empty($_POST["pwd"]))
{
$username = $_POST["uname"];
$password = $_POST["pwd"];
$_SESSION[$session_username_key] = $username;
$_SESSION[$session_password_key] = $password;
redirect("./");
exit(0);
}
else if ($_POST["intent"] == "register" && !empty($_POST["uname"]) && !empty($_POST["pwd"]) && !empty($_POST["pwd2"]))
{
unset($_SESSION[$session_register_notice_key]);
$username = $_POST["uname"];
$password = $_POST["pwd"];
$password2 = $_POST["pwd2"];
$valid = false;
if (preg_match("/^[A-Za-z0-9]{3,16}$/", $username) === 1)
{
$valid = true;
}
$profile_file = $profiles_path . strtolower($username) . $profile_extension;
if (!$valid)
{
$_SESSION[$session_register_notice_key] = "Error: Invalid username.";
redirect("./?{$get_register_key}");
}
else if (file_exists($profile_file))
{
$_SESSION[$session_register_notice_key] = "Error: Username is already taken.";
redirect("./?{$get_register_key}");
}
else if (preg_match("/.{8,265}/", $password) !== 1)
{
$_SESSION[$session_register_notice_key] = "Error: Invalid password.";
redirect("./?{$get_register_key}");
}
else if ($password != $password2)
{
$_SESSION[$session_register_notice_key] = "Error: Passwords don't match.";
redirect("./?{$get_register_key}");
}
else
{
$new_profile = json_decode(file_get_contents($profile_template_path), true);
$new_profile["file"] = $profile_file;
$new_profile["username"] = $username;
$new_profile["password"] = $password;
file_put_contents($profile_file, json_encode($new_profile));
$_SESSION[$session_username_key] = $username;
$_SESSION[$session_password_key] = $password;
redirect("./");
}
exit(0);
}
}
if (isset($_POST["logout"]) || isset($_GET["logout"]))
{
unset($_SESSION[$session_username_key]);
unset($_SESSION[$session_password_key]);
redirect("./");
exit(0);
}
// session
if (isset($_GET[$get_register_key]))
{
$notice = "";
if (!empty($_SESSION[$session_register_notice_key]))
{
$notice = $_SESSION[$session_register_notice_key];
}
echo register_form($notice);
exit(0);
}
else if (!empty($_SESSION[$session_username_key]))
{
$username = $_SESSION[$session_username_key];
$password = "";
if (!empty($_SESSION[$session_password_key])) $password = $_SESSION[$session_password_key];
$profiles_whitelist = array();
$glob = glob($profiles_path . "*{$profile_extension}");
foreach ($glob as $key => $profile)
{
$profiles_whitelist[$key] = strtolower(pathinfo($profile)["filename"]);
}
if (!in_array(strtolower($username), $profiles_whitelist))
{
echo login_form("Error: username not found.");
exit(0);
}
$index = array_search(strtolower($username), $profiles_whitelist);
$raw_json = file_get_contents($glob[$index]);
$json = json_decode($raw_json, true);
if ($json["username"] !== $username)
{
echo login_form("Error: wrong username.");
exit(0);
}
if ($json["password"] !== $password)
{
echo login_form("Error: wrong password.");
exit(0);
}
if ($json["username"] === $username && $json["password"] === $password) // just to be safe, the if is here
{
$model = $json;
}
}
else
{
echo login_form();
exit(0);
}
// actual
if (isset($_POST["backup"]) || isset($_GET["backup"]))
{
$time_part = "-" . time();
$backup_filename = $backup_path . strtolower($model["username"]) . $time_part . $profile_extension;
file_put_contents($backup_filename, json_encode($model));
redirect("./");
exit(0);
}
else if (!empty($_POST["restore"]) || !empty($_GET["restore"]))
{
$timestamp = 0;
if (!empty($_POST["restore"]))
{
$timestamp = $_POST["restore"];
}
else if (!empty($_GET["restore"]))
{
$timestamp = $_GET["restore"];
}
$backup_prefix = $backup_path . strtolower($model["username"]) . "-";
$backup_filename = $backup_prefix . strval($timestamp) . $profile_extension;
$backups = glob($backup_prefix . "*" . $profile_extension);
if (in_array($backup_filename, $backups) && file_exists($backup_filename))
{
$backup_json = json_decode(file_get_contents($backup_filename), true);
$model["list"] = $backup_json["list"];
}
$file = $model["file"];
$raw_json = json_encode($model);
file_put_contents($file, $raw_json);
redirect("./");
exit(0);
}
else if (isset($_POST["delete-restore"]) || isset($_GET["delete-restore"]))
{
$timestamp = 0;
if (!empty($_POST["delete-restore"]))
{
$timestamp = $_POST["delete-restore"];
}
else if (!empty($_GET["delete-restore"]))
{
$timestamp = $_GET["delete-restore"];
}
$backup_prefix = $backup_path . strtolower($model["username"]) . "-";
$backup_filename = $backup_prefix . strval($timestamp) . $profile_extension;
$backup_new_filename = $backup_path . "deleted-" . strtolower($model["username"]) . "-" . strval($timestamp) . $profile_extension;
$backups = glob($backup_prefix . "*" . $profile_extension);
if (in_array($backup_filename, $backups) && file_exists($backup_filename))
{
rename($backup_filename, $backup_new_filename);
}
redirect("./?restore-list");
exit(0);
}
else if (isset($_POST["restore-list"]) || isset($_GET["restore-list"]))
{
$backups = array();
$backup_prefix = $backup_path . strtolower($model["username"]) . "-";
$glob = glob($backup_prefix . "*" . $profile_extension);
natcasesort($glob);
$glob = array_reverse($glob);
foreach ($glob as $backup)
{
$timestamp_and_ext = str_replace($backup_prefix, "", $backup);
$timestamp = intval(str_replace($profile_extension, "", $timestamp_and_ext));
$datestamp = date("r", $timestamp);
$backups[] = array(
"timestamp" => $timestamp,
"date" => $datestamp,
"file" => $backup,
);
}
echo restore_form($backups);
exit(0);
}
else if (!empty($_POST["action"]))
{
$action = $_POST["action"];
$modified = false;
if ($action === "add")
{
if (!empty($_POST["todo_title"]))
{
$title = $_POST["todo_title"];
$description = "";
if (!empty($_POST["todo_description"]))
{
$description = $_POST["todo_description"];
}
$deadline = "";
if (!empty($_POST["todo_deadline"]))
{
$deadline = strtotime($_POST["todo_deadline"]);
}
$category = "";
if (!empty($_POST["todo_category"]))
{
$category = $_POST["todo_category"];
}
$now = time();
if (empty($model["list"]))
{
$model["list"] = array();
}
$model["list"][] = array(
"title" => $title,
"description" => $description,
"deadline" => $deadline,
"category" => $category,
"created" => $now,
"modified" => $now,
);
$modified = true;
}
}
else if ($action === "remove")
{
if (isset($_POST["todo_item_id"]))
{
$id = $_POST["todo_item_id"];
if (isset($model["list"][$id]))
{
unset($model["list"][$id]);
$model["list"] = array_values($model["list"]); // renumber
$modified = true;
}
}
}
else if ($action === "delay" && !empty($_POST["delay_by"]))
{
$delay_by = $_POST["delay_by"];
if (isset($_POST["todo_item_id"]))
{
$id = $_POST["todo_item_id"];
if (isset($model["list"][$id]) && !empty($model["list"][$id]["deadline"]))
{
$now = time();
$deadline = strtotime("@".$model["list"][$id]["deadline"]);
$deadline_new = strtotime($delay_by, $deadline);
$model["list"][$id]["deadline"] = $deadline_new;
$model["list"][$id]["modified"] = $now;
$modified = true;
}
}
}
if ($modified)
{
if (!empty($model["file"]) && file_exists($model["file"]))
{
$file = $model["file"];
$raw_json = json_encode($model);
file_put_contents($file, $raw_json);
redirect("./");
exit(0);
}
else
{
login_form("Internal error!");
exit(0);
exit(0);
exit(0);
exit(0);
}
}
}
else
{
echo todo_list($model);
}
// renderers
function todo_list(array $model = []) : string
{
$list = array();
if (!empty($model["list"]))
{
$list = $model["list"];
}
$te = new template_engine();
$te->set_block("TITLE", $model["username"] . "'s to-do List");
$te->append_block_template("CONTENT", "TODO_LIST");
$te->append_block_template("CONTENT", "ADD_FORM");
$te->append_block_template("CONTENT", "NAVBAR");
$categories = array();
$now = time();
$now_dayth = intval(date("z", $now));
if (!empty($list))
{
foreach ($list as $key => $item)
{
if (!empty($item["category"]))
{
$category = strtolower($item["category"]);
$category_name = $item["category"];
$category_basis = "custom";
}
else if (!empty($item["deadline"]))
{
$deadline = $item["deadline"];
$ymd = date("y-m-d", $deadline);
$day = date("l", $deadline);
$dayth = intval(date("z", $deadline));
$category = $ymd;
if ($dayth - $now_dayth == 0)
{
$category_name = "Today ({$day})";
}
else if ($dayth - $now_dayth == 1)
{
$category_name = "Tomorrow ({$day})";
}
else if ($dayth - $now_dayth == -1)
{
$category_name = "Yesterday ({$day})";
}
else if ($dayth - $now_dayth < -7)
{
$category_name = "{$day} ({$ymd})";
}
else if ($dayth - $now_dayth < 0)
{
$category_name = "Last " . $day;
}
else if ($dayth - $now_dayth < 7)
{
$category_name = $day;
}
else if ($dayth - $now_dayth < 14)
{
$category_name = "Next " . $day;
}
else
{
$category_name = "{$day} ({$ymd})";
}
$category_basis = "date";
}
else
{
$category = "uncategorized";
$category_name = "Uncategorized";
$category_basis = "default";
}
if (empty($categories[$category]))
{
$categories[$category] = array(
"title" => $category_name,
"basis" => $category_basis,
"list" => array(),
);
}
$categories[$category]["list"][$key] = $item;
}
}
if (!empty($categories))
{
$te->set_block("TODO_ITEMS", "");
}
$te->set_block("DATALIST_AUTOFILLS", "");
ksort($categories);
$titles = array();
foreach ($categories as $category_key => $category)
{
$category_name = $category["title"];
$category_basis = $category["basis"];
$te->set_block("TODO_CATEGORY_ITEMS", "");
if ($category_basis == "custom")
{
$te->append_argumented_block("DATALIST_AUTOFILLS", "DATALIST_AUTOFILL", [
"DATALIST_AUTOFILL_DATA" => $category_name,
]);
}
foreach ($category["list"] as $key => $item)
{
$titles[] = $item["title"];
if (empty($item["description"]))
{
$te->set_block_template("TODO_ITEM_SUMMARY", "TODO_ITEM_SUMMARY_NODESC");
}
else
{
$te->set_block_template("TODO_ITEM_SUMMARY", "TODO_ITEM_SUMMARY_DESC");
}
$te->append_argumented_block("TODO_CATEGORY_ITEMS", "TODO_ITEM", [
"TODO_ITEM_ID" => $key,
"TODO_ITEM_TITLE" => $item["title"],
"TODO_ITEM_DESCRIPTION" => $item["description"],
]);
}
$te->append_argumented_block("TODO_ITEMS", "TODO_CATEGORY", [
"TODO_CATEGORY_ID" => $category_key,
"TODO_CATEGORY_TITLE" => $category_name,
]);
}
$te->append_argumented_block("DATALISTS", "DATALIST", [
"DATALIST_ID" => "todo_categories",
]);
$te->set_block("DATALIST_AUTOFILLS", "");
foreach (array_unique($titles) as $title)
{
$te->append_argumented_block("DATALIST_AUTOFILLS", "DATALIST_AUTOFILL", [
"DATALIST_AUTOFILL_DATA" => $title,
]);
}
$te->append_argumented_block("DATALISTS", "DATALIST", [
"DATALIST_ID" => "todo_titles",
]);
// $te->append_block("TODO_ITEMS", "<pre><details open><summary>dump</summary>".print_r($model, true)."</details></pre>");
// $te->append_block("TODO_ITEMS", "<pre><details open><summary>dump</summary>".print_r($categories, true)."</details></pre>");
return $te->get_html();
exit(0);
}
function restore_form(array $backups = array()) : string
{
$te = new template_engine();
$number = count($backups);
foreach($backups as $key => $backup)
{
$title = "#" . $backup["timestamp"] . ": " . $backup["date"];
$te->append_argumented_block("RESTORE_ITEMS", "RESTORE_ITEM", [
"RESTORE_ITEM_TIMESTAMP" => $backup["timestamp"],
"RESTORE_ITEM_TITLE" => $title,
]);
}
$te->set_block("TITLE", "Zovguran: To-Do");
$te->append_block_template("CONTENT", "RESTORE_CONTAINER");
return $te->get_html();
exit(0);
}
function login_form(string $notice = "") : string
{
$te = new template_engine();
$te->set_block("TITLE", "Zovguran: To-Do");
$te->set_block("LOGIN_NOTICE", $notice);
$te->append_block_template("CONTENT", "LOGIN_FORM");
return $te->get_html();
exit(0);
}
function register_form(string $notice = "") : string
{
$te = new template_engine();
$te->set_block("TITLE", "Sign Up");
$te->set_block("REGISTER_NOTICE", $notice);
$te->append_block_template("CONTENT", "REGISTER_FORM");
return $te->get_html();
exit(0);
}
function redirect($path)
{
header('Location: ' . $path);
exit(0); // TERMINATE!
}
// function reference($model)
// {
// $te = new template_engine();
// $te->get_block_names();
// $te->set_block("TITLE", $model->get_title());
// $te->set_block("PREVIEW_WIDTH", $model->get_preview_width());
// $te->set_block("PREVIEW_HEIGHT", $model->get_preview_height());
// $te->set_block("PREVIEW_WIDTH_BORDER", $model->get_preview_width() + site_data::$border_size*2);
// $te->set_block("PREVIEW_HEIGHT_BORDER", $model->get_preview_height() + site_data::$border_size*2);
// foreach ($model->get_border_colors() as $info)
// {
// $te->append_argumented_block("EXTRA_STYLES", "BORDER_COLOR_STYLE", [
// "BORDER_CLASS" => $info["id"],
// "BORDER_COLOR" => $info["color"]
// ]);
// }
// if ($model->get_mode() == "image" || $model->get_mode() == "collection_image")
// {
// $te->set_block_template("PREVIEW_MAIN_CLASS", "PREVIEW_SINGLE_CLASS");
// }
// if ($model->has_previews())
// {
// $te->append_block_template("CONTENT", "PREVIEW_CONTAINER");
// foreach ($model->get_contents() as $preview)
// {
// if (!isset($preview["class"])) $preview["class"] = ""; // should not be here
// $te->append_argumented_block("PREVIEWS", "PREVIEW", [
// "PREVIEW_LINK" => $preview["link"],
// "PREVIEW_CLASS" => $preview["class"],
// "PREVIEW_IMAGE" => $preview["image"]
// ]);
// }
// }
// else
// {
// $te->append_block_template("CONTENT", "ITEM_CONTAINER");
// foreach ($model->get_contents() as $item_id => $item)
// {
// if (isset($item["text"])) $text = $item["text"];
// else if (isset($item["id"])) $text = $item["id"];
// else $text = $item_id;
// $te->append_argumented_block("ITEMS", "ITEM", [
// "ITEM_LINK" => $item["link"],
// "ITEM_TEXT" => $text
// ]);
// }
// }
// $link_categories = $model->get_navbar_links();
// ksort($link_categories); // sort
// foreach ($link_categories as $key => $link_category)
// {
// foreach ($link_category as $key2 => $link)
// {
// if ($link["id"] == null) $link["id"] = $key . "-" . $key2;
// if ($key == "hidden") $class = "hidden";
// else $class = "";
// $te->append_argumented_block("NAVBAR_ITEMS", "NAVBAR_LINK", [
// "NAVBAR_LINK_TEXT" => $link["text"],
// "NAVBAR_LINK_LINK" => $link["link"],
// "NAVBAR_LINK_CLASS" => $class,
// "NAVBAR_LINK_ID" => $link["id"]
// ]);
// }
// if ($key != array_key_last($link_categories))
// $te->append_block_template("NAVBAR_ITEMS", "NAVBAR_SPACER");
// }
// echo $te->get_html();
// }
+6
View File
@@ -0,0 +1,6 @@
{
"file":"FILE_PATH",
"username":"USERNAME",
"password":"PASSWORD",
"list":[]
}
+140
View File
@@ -0,0 +1,140 @@
<?php
class template_engine
{
public static $base_block = "BASE";
public $blocks = array();
public $templates = array();
private $block_pattern = '/\w*/';
private $block_pattern_chars = '[\w,_,-]*';
private $block_pattern_prefix = '{{ ';
private $block_pattern_suffix = ' }}';
private $source_files = array();
private $template_folder = "./templates/";
private function init_templates(array $source_files) : void
{
foreach ($source_files as $name => $source)
{
$this->templates[$name] = file_get_contents($source);
}
}
private function discover_templates(string $template_folder) : void
{
foreach (glob($template_folder."*") as $file)
{
$info = pathinfo($file);
$template_id = strtoupper(str_replace("-", "_", $info["filename"]));
$this->source_files[$template_id] = $file;
}
}
public function __construct()
{
$this->block_pattern = '/' . $this->block_pattern_prefix . $this->block_pattern_chars . $this->block_pattern_suffix . '/';
$this->discover_templates($this->template_folder);
$this->init_templates($this->source_files);
$this->new();
}
public function new() : void
{
$this->blocks = array();
foreach ($this->templates as $key => $template)
{
$this->blocks[$key] = $template;
}
// $this->blocks[self::$base_block] = $this->templates[self::$base_block];
}
public function get_block_names() : array
{
$all_matches = array();
foreach ($this->blocks as $block)
{
$matches = array();
preg_match_all($this->block_pattern, $block, $matches);
$all_matches = array_merge($all_matches, $matches[0]);
}
$block_names = array();
foreach (array_unique($all_matches) as $match)
{
$block_name = str_replace([$this->block_pattern_prefix, $this->block_pattern_suffix], "", $match);
$block_names[$block_name] = array_key_exists($block_name, $this->blocks) ? true : false;
}
return $block_names;
}
public function set_block_template(string $block, string $template) : void
{
$this->blocks[$block] = $this->templates[$template];
}
public function set_block(string $block, string $content) : void
{
$this->blocks[$block] = $content;
}
public function append_block(string $block, string $content) : void
{
if (empty($this->blocks[$block])) $this->blocks[$block] = "";
$this->blocks[$block] .= $content;
}
public function append_block_template(string $block, string $template) : void
{
if (empty($this->blocks[$block])) $this->blocks[$block] = "";
$this->blocks[$block] .= $this->templates[$template];
}
public function append_built_block(string $block, string $block_name) : void
{
$this->append_block($block, $this->build_block($block_name));
}
public function append_argumented_block(string $block, string $block_name, array $args) : void
{
$this->append_block($block, $this->build_argumented_block($block_name, $args));
}
public function build_argumented_block(string $block, array $args) : string
{
foreach ($args as $key => $value) $this->set_block($key, $value);
return $this->build_block($block);
}
public function build_block(string $block) : string
{
$matches = array();
$html = $this->blocks[$block];
$from = array();
$to = array();
foreach ($this->get_block_names() as $block_name => $has_block)
{
$from[] = $this->block_pattern_prefix . $block_name . $this->block_pattern_suffix;
if ($has_block) $to[] = $this->blocks[$block_name];
else $to[] = "";
}
while (preg_match_all($this->block_pattern, $html) > 0)
{
$html = str_replace($from, $to, $html);
}
return $html;
}
public function get_raw_html() : string
{
return $this->build_block(self::$base_block);
}
public function get_html() : string
{
$strip = [ "\t" ];
return str_replace($strip, "", $this->get_raw_html());
}
}
+37
View File
@@ -0,0 +1,37 @@
<div class="todo-add-container">
<details>
<summary>New</summary>
<form action="./" method="POST" autocomplete="off">
<input type="hidden" name="action" value="add" />
<div class="todo-add-div">
<p>{{ ADD_NOTICE }}</p>
</div>
<div class="todo-add-div">
<label class="label" for="todo_title">Title</label>
<input id="todo_title" type="text" list="todo_titles" name="todo_title" required />
</div>
<div class="todo-add-div">
<label class="label" for="todo_description">Description</label>
<input id="todo_description" type="text" name="todo_description" />
</div>
<div class="todo-add-div">
<label class="label" for="todo_deadline">Deadline</label>
<input id="todo_deadline" type="date" name="todo_deadline" /><!--time-local-->
<i>(optional)</i>
</div>
<div class="todo-add-div">
<label class="label" for="todo_category">Category</label>
<input id="todo_category" type="text" list="todo_categories" name="todo_category" />
<i>(optional)</i>
</div>
<div class="todo-add-div">
<input class="todo-add-button" type="submit" value="Add" />
</div>
</form>
</details>
</div>
+1
View File
@@ -0,0 +1 @@
<html><head>{{ HEAD }}</head><body id="body">{{ BODY }}</body></html>
+3
View File
@@ -0,0 +1,3 @@
{{ HEADER }}
{{ CONTENT }}
{{ FOOTER }}
+1
View File
@@ -0,0 +1 @@
<option value="{{ DATALIST_AUTOFILL_DATA }}" />
+3
View File
@@ -0,0 +1,3 @@
<datalist id="{{ DATALIST_ID }}">
{{ DATALIST_AUTOFILLS }}
</datalist>
+3
View File
@@ -0,0 +1,3 @@
<div class="footer" id="footer">
{{ DATALISTS }}
</div>
+6
View File
@@ -0,0 +1,6 @@
<title>Zvg: {{ TITLE }}</title>
<style>{{ STYLE }}</style>
<script>{{ SCRIPT }}</script>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="icon" type="image/png" sizes="64x64" href="/_src/favicon/64.png">
<link rel="icon" type="image/png" sizes="32x32" href="/_src/favicon/32.png">
+3
View File
@@ -0,0 +1,3 @@
<div class="header" id="header">
<h1>{{ TITLE }}</h1>
</div>
+20
View File
@@ -0,0 +1,20 @@
<div class="login-container">
<form action="./" method="POST">
<input type="hidden" name="intent" value="login" />
<div class="login-div">
<p>{{ LOGIN_NOTICE }}</p>
</div>
<div class="login-div">
<label class="label" for="uname">Username</label>
<input class="login-input" id="uname" type="text" name="uname" required />
</div>
<div class="login-div">
<label class="label" for="pwd">Password</label>
<input class="login-input" id="pwd" type="password" autocomplete="off" name="pwd" required />
</div>
<div class="login-div">
<input class="login-button" type="submit" value="Log in" />
</div>
</form>
<p>Don't have an account? <a href="./?reg">Sign up!</a></p>
</div>
+16
View File
@@ -0,0 +1,16 @@
<div class="navbar">
<form action="./" method="GET" class="navbar-form">
<input type="hidden" name="backup" value="1">
<!-- Backing up actually works but there is no restore function yet... -->
<!-- <input type="submit" value="Back up"> -->
<input type="submit" value="Back up">
</form>
<form action="./" method="GET" class="navbar-form">
<input type="hidden" name="restore-list" value="1">
<input type="submit" value="Restore">
</form>
<form action="./" method="GET" class="navbar-form">
<input type="hidden" name="logout" value="1">
<input type="submit" value="Log out">
</form>
</div>
+24
View File
@@ -0,0 +1,24 @@
<div class="login-container">
<form action="./" method="POST">
<input type="hidden" name="intent" value="register" />
<div class="login-div">
<p>{{ REGISTER_NOTICE }}</p>
</div>
<div class="login-div">
<label class="label" for="uname">Username</label>
<input class="login-input" id="uname" type="text" name="uname" required />
</div>
<div class="login-div">
<label class="label" for="pwd">Password</label>
<input class="login-input" id="pwd" type="password" autocomplete="off" name="pwd" required />
</div>
<div class="login-div">
<label class="label" for="pwd2">Password (Again)</label>
<input class="login-input" id="pwd2" type="password" autocomplete="off" name="pwd2" required />
</div>
<div class="login-div">
<input class="login-button" type="submit" value="Sign Up" />
</div>
</form>
<p>Have an account? <a href="./?">Log in!</a></p>
</div>
+6
View File
@@ -0,0 +1,6 @@
<div class="navbar">
<form action="./" method="GET" class="navbar-form">
<input type="submit" value="Cancel">
</form>
</div>
<div class="todo-list">{{ RESTORE_ITEMS }}</div>
+17
View File
@@ -0,0 +1,17 @@
<div class="todo-item" id="todo-item-{{ RESTORE_ITEM_TIMESTAMP }}">
<details>
<summary>{{ RESTORE_ITEM_TITLE }}</summary>
<form action="./" method="POST">
<input type="hidden" name="restore" value="{{ RESTORE_ITEM_TIMESTAMP }}" />
<input type="submit" value="Restore" />
</form>
<div style="display:inline-block;width:1em;"></div>
<form action="./" method="POST">
<input type="hidden" name="delete-restore" value="{{ RESTORE_ITEM_TIMESTAMP }}" />
<input type="submit" value="Delete" />
</form>
</details>
</div>
+3
View File
@@ -0,0 +1,3 @@
<div class="todo-item" id="todo-item-{{ RESTORE_ITEM_TIMESTAMP }}">
<summary>WARNING: Restoring a backup will overwrite your current list.</summary>
</div>
+16
View File
@@ -0,0 +1,16 @@
document.addEventListener("DOMContentLoaded", function(){
// Fetch all the details element.
const details = document.querySelectorAll("details");
// Add the onclick listeners.
details.forEach((targetDetail) => {
targetDetail.addEventListener("click", () => {
// Close all the details that are not targetDetail.
details.forEach((detail) => {
if (detail !== targetDetail) {
detail.removeAttribute("open");
}
});
});
});
});
+153
View File
@@ -0,0 +1,153 @@
html {
font-family: 'Roboto', sans-serif;
background-color: #111111;
color: #ffffff;
text-align: center;
}
a:link, a:hover, a:visited, a:active {
color: DeepPink;
text-decoration: none;
}
body {
max-width: 800px;
margin: 30px auto;
text-align: left;
}
.header {
text-align: center;
}
.login-container form {
width: inherit;
margin: inherit;
}
.todo-category {
margin: 5px;
padding: 10px;
border: 1px dashed #222222;
}
.todo-category .todo-item {
margin-left: 10px;
}
h3 {
margin: 0;
padding: 0;
}
.todo-item form, .navbar-form {
margin: 0;
padding: 0;
display: inline-block;
}
.todo-item form input, .navbar-form input {
border: 2px solid #333333;
padding: 5px;
margin: 0;
background-color: transparent;
color: white;
border-radius: 4px;
}
.todo-item form input:focus, .navbar-form input:focus {
outline: none;
}
.todo-item form input:active, .navbar-form input:active {
background-color: #222222;
}
summary, details[open] {
padding: 0.5em;
margin: 0;
}
summary {
border: 1px solid transparent;
}
details[open] summary {
margin: -0.5em;
margin-bottom: 0.7em;
border: none;
}
details[open] {
border: 1px solid #222222;
border-radius: 0 0 10px 10px;
overflow: hidden;
}
summary:focus {
outline: none;
background-color: #333333;
}
.todo-add-div, .todo-add-container {
margin: 20px;
}
.todo-add-div input[type="text"],
.todo-add-div input[type="datetime-local"],
.todo-add-div input[type="date"]
{
width: 30em;
}
.todo-add-div label {
display: inline-block;
width: 6em;
text-align: right;
margin-right: 5px;
}
.todo-add-button {
border: none;
background-color: Green;
color: White;
padding: 5px;
border-radius: 3px;
width: 100%;
}
.todo-add-button:active {
background-color: Lime;
color: DimGrey;
}
.navbar {
text-align: right;
}
/* LOGIN */
.login-input {
width: 100%;
border: 1px solid LightPink;
background-color: transparent;
padding: 5px;
border-radius: 3px;
color: white;
}
.login-button {
border: none;
background-color: DeepPink;
padding: 5px;
border-radius: 3px;
width: 100%;
}
.login-button:active {
background-color: MediumVioletRed;
}
.login-div {
margin: 20px;
}
.login-container {
width: fit-content;
margin: auto;
}
.login-container p {
text-align: center;
}
+4
View File
@@ -0,0 +1,4 @@
<div class="todo-category" id="todo-category-{{ TODO_CATEGORY_ID }}">
<h3>{{ TODO_CATEGORY_TITLE }}</h3>
{{ TODO_CATEGORY_ITEMS }}
</div>
+1
View File
@@ -0,0 +1 @@
<b>{{ TODO_ITEM_TITLE }}:</b> {{ TODO_ITEM_DESCRIPTION }}
+1
View File
@@ -0,0 +1 @@
<b>{{ TODO_ITEM_TITLE }}</b>
+45
View File
@@ -0,0 +1,45 @@
<div class="todo-item" id="todo-item-{{ TODO_ITEM_ID }}">
<details>
<summary>{{ TODO_ITEM_SUMMARY }}</summary>
<form action="./" method="POST">
<input type="hidden" name="action" value="delay" />
<input type="hidden" name="delay_by" value="-1 week" />
<input type="hidden" name="todo_item_id" value="{{ TODO_ITEM_ID }}" />
<input type="submit" value="-1 week" />
</form>
<form action="./" method="POST">
<input type="hidden" name="action" value="delay" />
<input type="hidden" name="delay_by" value="-1 day" />
<input type="hidden" name="todo_item_id" value="{{ TODO_ITEM_ID }}" />
<input type="submit" value="-1 day" />
</form>
<div style="display:inline-block;width:1em;"></div>
<form action="./" method="POST">
<input type="hidden" name="action" value="delay" />
<input type="hidden" name="delay_by" value="+1 day" />
<input type="hidden" name="todo_item_id" value="{{ TODO_ITEM_ID }}" />
<input type="submit" value="+1 day" />
</form>
<form action="./" method="POST">
<input type="hidden" name="action" value="delay" />
<input type="hidden" name="delay_by" value="+1 week" />
<input type="hidden" name="todo_item_id" value="{{ TODO_ITEM_ID }}" />
<input type="submit" value="+1 week" />
</form>
<div style="display:inline-block;width:1em;"></div>
<div style="display:inline-block;width:1em;"></div>
<form action="./" method="POST">
<input type="hidden" name="action" value="remove" />
<input type="hidden" name="todo_item_id" value="{{ TODO_ITEM_ID }}" />
<input type="submit" value="Remove" />
</form>
</details>
</div>
+1
View File
@@ -0,0 +1 @@
Nothing here yet...
+1
View File
@@ -0,0 +1 @@
<div class="todo-list">{{ TODO_ITEMS }}</div>