Initial commit
This commit is contained in:
commit
39881ccf6a
16 changed files with 1043 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
save/
|
||||||
|
.vscode/
|
321
_server.py
Normal file
321
_server.py
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
import hashlib
|
||||||
|
import shutil
|
||||||
|
import flask
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
from typing import Union, Callable
|
||||||
|
|
||||||
|
CONTENT_DIRECTORY = "./public/"
|
||||||
|
SAVING_DIRECTORY = "./save/"
|
||||||
|
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
def sort_list(l: list[list[str]]) -> list[list[str]]:
|
||||||
|
output = []
|
||||||
|
for i in l:
|
||||||
|
if i[0] and i[1] in ["1", "2", "3", "4"]:
|
||||||
|
output.append(i)
|
||||||
|
|
||||||
|
return sorted(output, key=lambda x: {"1": "d", "2": "c", "3": "b", "4": "a"}[x[1]] + x[0])
|
||||||
|
|
||||||
|
def return_dynamic_content_type(content: Union[str, bytes], content_type: str="text/html") -> flask.Response:
|
||||||
|
response = flask.make_response(content)
|
||||||
|
response.headers["Content-Type"] = content_type
|
||||||
|
return response
|
||||||
|
|
||||||
|
def sha(string: Union[str, bytes]) -> str:
|
||||||
|
if type(string) == str:
|
||||||
|
return hashlib.sha256(str.encode(string)).hexdigest()
|
||||||
|
elif type(string) == bytes:
|
||||||
|
return hashlib.sha256(string).hexdigest()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def ensure_file(path: str, *, default_value: str="", folder: bool=False) -> None:
|
||||||
|
if os.path.exists(path):
|
||||||
|
if folder and not os.path.isdir(path):
|
||||||
|
os.remove(path)
|
||||||
|
os.makedirs(path)
|
||||||
|
elif not folder and os.path.isdir(path):
|
||||||
|
shutil.rmtree(path, ignore_errors=True)
|
||||||
|
f = open(path, "w")
|
||||||
|
f.write(default_value)
|
||||||
|
f.close()
|
||||||
|
else:
|
||||||
|
if folder:
|
||||||
|
os.makedirs(path)
|
||||||
|
else:
|
||||||
|
f = open(path, "w")
|
||||||
|
f.write(default_value)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def escape_html(string: str) -> str:
|
||||||
|
return string.replace("&", "&").replace("<", "<").replace("\"", "&quo;")
|
||||||
|
|
||||||
|
def create_file_serve(file) -> Callable:
|
||||||
|
x = lambda: flask.send_file(f"{CONTENT_DIRECTORY}{file}")
|
||||||
|
x.__name__ = file
|
||||||
|
return x
|
||||||
|
|
||||||
|
def generate_token(username: str, passhash: str) -> str:
|
||||||
|
return sha(sha(f"{username}:{passhash}") + "among us in real life, sus, sus")
|
||||||
|
|
||||||
|
def create_folder_serve(directory) -> Callable:
|
||||||
|
x = lambda file: flask.send_from_directory(f"{CONTENT_DIRECTORY}{directory}", file)
|
||||||
|
x.__name__ = directory
|
||||||
|
return x
|
||||||
|
|
||||||
|
def api_account_login():
|
||||||
|
try:
|
||||||
|
x = json.loads(request.data)
|
||||||
|
username = x["username"].replace(" ", "").lower()
|
||||||
|
passhash = x["password"]
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
flask.abort(400)
|
||||||
|
except KeyError:
|
||||||
|
flask.abort(400)
|
||||||
|
|
||||||
|
if len(username) > 24 or len(username) < 1:
|
||||||
|
flask.abort(400)
|
||||||
|
|
||||||
|
for i in username:
|
||||||
|
if i not in "abcdefghijklmnopqrstuvwxyz0123456789_-":
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": False,
|
||||||
|
"reason": "User doesn't exist."
|
||||||
|
}), "application/json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
open(f"{SAVING_DIRECTORY}{username}.json", "r")
|
||||||
|
except FileNotFoundError:
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": False,
|
||||||
|
"reason": "User doesn't exist."
|
||||||
|
}), "application/json")
|
||||||
|
|
||||||
|
token = generate_token(username, passhash)
|
||||||
|
|
||||||
|
try:
|
||||||
|
enforced_username = open(f"{SAVING_DIRECTORY}tokens/{token}.txt", "r").read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": False,
|
||||||
|
"reason": "Invalid password"
|
||||||
|
}), "application/json")
|
||||||
|
|
||||||
|
if enforced_username != username:
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": False,
|
||||||
|
"reason": "Invalid password"
|
||||||
|
}), "application/json")
|
||||||
|
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": True,
|
||||||
|
"token": token
|
||||||
|
}), "application/json")
|
||||||
|
|
||||||
|
def api_account_signup():
|
||||||
|
try:
|
||||||
|
x = json.loads(request.data)
|
||||||
|
username = x["username"].replace(" ", "")
|
||||||
|
passhash = x["password"]
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
flask.abort(400)
|
||||||
|
except KeyError:
|
||||||
|
flask.abort(400)
|
||||||
|
|
||||||
|
if len(username) > 24 or len(username) < 1:
|
||||||
|
flask.abort(400)
|
||||||
|
|
||||||
|
if len(passhash) != 64:
|
||||||
|
flask.abort(400)
|
||||||
|
|
||||||
|
for i in passhash:
|
||||||
|
if i not in "abcdef0123456789":
|
||||||
|
flask.abort(400)
|
||||||
|
|
||||||
|
for i in username:
|
||||||
|
if i not in "abcdefghijklmnopqrstuvwxyz0123456789_-":
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": False,
|
||||||
|
"reason": "Username can only contain a-z, 0-9, underscores, and hyphens."
|
||||||
|
}), "application/json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
open(f"{SAVING_DIRECTORY}{username}.json", "r")
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": False,
|
||||||
|
"reason": "Username taken."
|
||||||
|
}), "application/json")
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
token = generate_token(username, passhash)
|
||||||
|
ensure_file(f"{SAVING_DIRECTORY}tokens/{token}.txt", default_value=username)
|
||||||
|
ensure_file(f"{SAVING_DIRECTORY}{username}.json", default_value=json.dumps({
|
||||||
|
"username": username,
|
||||||
|
"display_name": username,
|
||||||
|
"description": "",
|
||||||
|
"colors": {
|
||||||
|
"accent": "#ff0000",
|
||||||
|
"text": "#ffffff",
|
||||||
|
"background": "#111122"
|
||||||
|
},
|
||||||
|
"names": [
|
||||||
|
[username, "4"]
|
||||||
|
],
|
||||||
|
"pronouns": [
|
||||||
|
["he/him", "3"],
|
||||||
|
["it/its", "3"],
|
||||||
|
["she/her", "3"],
|
||||||
|
["they/them", "3"]
|
||||||
|
],
|
||||||
|
"honorifics": [
|
||||||
|
["ma'am", "3"],
|
||||||
|
["madam", "3"],
|
||||||
|
["mir", "3"],
|
||||||
|
["mr.", "3"],
|
||||||
|
["ms.", "3"],
|
||||||
|
["mx.", "3"],
|
||||||
|
["sai", "3"],
|
||||||
|
["shazam", "3"],
|
||||||
|
["sir", "3"],
|
||||||
|
["zam", "3"]
|
||||||
|
],
|
||||||
|
"compliments": [
|
||||||
|
["cute", "3"],
|
||||||
|
["handsome", "3"],
|
||||||
|
["hot", "3"],
|
||||||
|
["pretty", "3"],
|
||||||
|
["sexy", "3"]
|
||||||
|
], "relationship": [
|
||||||
|
["beloved", "3"],
|
||||||
|
["boyfriend", "3"],
|
||||||
|
["darling", "3"],
|
||||||
|
["enbyfriend", "3"],
|
||||||
|
["friend", "3"],
|
||||||
|
["girlfriend", "3"],
|
||||||
|
["husband", "3"],
|
||||||
|
["partner", "3"],
|
||||||
|
["wife", "3"]
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
|
||||||
|
return return_dynamic_content_type(json.dumps({
|
||||||
|
"valid": True,
|
||||||
|
"token": token
|
||||||
|
}))
|
||||||
|
|
||||||
|
def api_account_info_(user):
|
||||||
|
try:
|
||||||
|
return return_dynamic_content_type(
|
||||||
|
open(f"{SAVING_DIRECTORY}{user}.json").read(),
|
||||||
|
"application/json"
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
flask.abort(404)
|
||||||
|
|
||||||
|
def api_account_self():
|
||||||
|
try:
|
||||||
|
return return_dynamic_content_type(
|
||||||
|
open(SAVING_DIRECTORY + open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read() + ".json", 'r').read(),
|
||||||
|
"application/json"
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
flask.abort(404)
|
||||||
|
|
||||||
|
def api_save():
|
||||||
|
# TODO - ADD SORTING
|
||||||
|
|
||||||
|
username = open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read()
|
||||||
|
x = json.loads(request.data)
|
||||||
|
|
||||||
|
user_data = json.loads(open(f"{SAVING_DIRECTORY}{username}.json", "r").read())
|
||||||
|
|
||||||
|
if "display_name" in x and len(x["display_name"]) < 64 and len(x["display_name"]) > 0:
|
||||||
|
user_data["display_name"] = x["display_name"]
|
||||||
|
|
||||||
|
if "description" in x and len(x["description"]) < 512:
|
||||||
|
user_data["description"] = x["description"]
|
||||||
|
|
||||||
|
if "colors" in x:
|
||||||
|
if "accent" in x["colors"]:
|
||||||
|
user_data["colors"]["accent"] = x["colors"]["accent"]
|
||||||
|
|
||||||
|
if "background" in x["colors"]:
|
||||||
|
user_data["colors"]["background"] = x["colors"]["background"]
|
||||||
|
|
||||||
|
if "text" in x["colors"]:
|
||||||
|
user_data["colors"]["text"] = x["colors"]["text"]
|
||||||
|
|
||||||
|
if "names" in x:
|
||||||
|
names = []
|
||||||
|
for i in x["names"]:
|
||||||
|
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
|
||||||
|
names.append(i)
|
||||||
|
user_data["names"] = sort_list(names)
|
||||||
|
|
||||||
|
if "pronouns" in x:
|
||||||
|
pronouns = []
|
||||||
|
for i in x["pronouns"]:
|
||||||
|
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
|
||||||
|
pronouns.append(i)
|
||||||
|
user_data["pronouns"] = sort_list(pronouns)
|
||||||
|
|
||||||
|
if "honorifics" in x:
|
||||||
|
honorifics = []
|
||||||
|
for i in x["honorifics"]:
|
||||||
|
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
|
||||||
|
honorifics.append(i)
|
||||||
|
user_data["honorifics"] = sort_list(honorifics)
|
||||||
|
|
||||||
|
if "compliments" in x:
|
||||||
|
compliments = []
|
||||||
|
for i in x["compliments"]:
|
||||||
|
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
|
||||||
|
compliments.append(i)
|
||||||
|
user_data["compliments"] = sort_list(compliments)
|
||||||
|
|
||||||
|
if "relationship" in x:
|
||||||
|
relationship = []
|
||||||
|
for i in x["relationship"]:
|
||||||
|
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
|
||||||
|
relationship.append(i)
|
||||||
|
user_data["relationship"] = sort_list(relationship)
|
||||||
|
|
||||||
|
f = open(f"{SAVING_DIRECTORY}{username}.json", "w")
|
||||||
|
f.write(json.dumps(user_data))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
return "200 OK"
|
||||||
|
|
||||||
|
def u_(username):
|
||||||
|
return flask.send_file(f"{CONTENT_DIRECTORY}user.html")
|
||||||
|
|
||||||
|
# levels:
|
||||||
|
# 4 - love
|
||||||
|
# 3 - good
|
||||||
|
# 2 - okay
|
||||||
|
# 1 - bad
|
||||||
|
|
||||||
|
ensure_file(SAVING_DIRECTORY, folder=True)
|
||||||
|
ensure_file(f"{SAVING_DIRECTORY}tokens/", folder=True)
|
||||||
|
|
||||||
|
app.route("/")(create_file_serve("index.html"))
|
||||||
|
app.route("/login")(create_file_serve("login.html"))
|
||||||
|
app.route("/signup")(create_file_serve("signup.html"))
|
||||||
|
app.route("/logout")(create_file_serve("logout.html"))
|
||||||
|
app.route("/editor")(create_file_serve("editor.html"))
|
||||||
|
app.route("/u/<path:username>")(u_)
|
||||||
|
app.route("/js/<path:file>")(create_folder_serve("js"))
|
||||||
|
app.route("/css/<path:file>")(create_folder_serve("css"))
|
||||||
|
|
||||||
|
app.route("/api/account/login", methods=["POST"])(api_account_login)
|
||||||
|
app.route("/api/account/signup", methods=["POST"])(api_account_signup)
|
||||||
|
app.route("/api/account/info/<path:user>", methods=["GET"])(api_account_info_)
|
||||||
|
app.route("/api/account/self", methods=["GET"])(api_account_self)
|
||||||
|
app.route("/api/save", methods=["PATCH"])(api_save)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True, port=8080)
|
132
public/css/base.css
Normal file
132
public/css/base.css
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'FontAwesome Solid';
|
||||||
|
src: url('https://site-assets.fontawesome.com/releases/v6.5.1/webfonts/fa-solid-900.woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'FontAwesome Solid';
|
||||||
|
src: url('https://site-assets.fontawesome.com/releases/v6.5.1/webfonts/fa-solid-900.woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-s {
|
||||||
|
font-family: 'FontAwesome Solid', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-r {
|
||||||
|
font-family: 'FontAwesome Regular', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--text: #aaaaaa;
|
||||||
|
--background: #333333;
|
||||||
|
--primary: #ffffff;
|
||||||
|
--secondary: #666666;
|
||||||
|
--accent: #444444;
|
||||||
|
|
||||||
|
--text-low-opacity: #aaaaaa88;
|
||||||
|
--background-low-opacity: #33333333;
|
||||||
|
--primary-low-opacity: #88888833;
|
||||||
|
--secondary-low-opacity: #66666633;
|
||||||
|
--accent-low-opacity: #44444466;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: var(--accent-low-opacity);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: 'Poppins', "FontAwesome Regular", 'Arial';
|
||||||
|
padding: 5vh 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1025px) {
|
||||||
|
body { font-size: 16px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 565px) {
|
||||||
|
body { font-size: 16px; padding: 5vh 3vw; }
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: var(--secondary-low-opacity);
|
||||||
|
color: var(--primary);
|
||||||
|
border: none;
|
||||||
|
padding: 5px 7px;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition-property: color;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled { opacity: 60%; pointer-events: none; }
|
||||||
|
button:active { scale: 95%; }
|
||||||
|
|
||||||
|
input, textarea, option, select{
|
||||||
|
background-color: var(--secondary-low-opacity);
|
||||||
|
color: var(--primary);
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 5px 7px;
|
||||||
|
margin: 3px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea, select {
|
||||||
|
font-family: 'Poppins';
|
||||||
|
}
|
||||||
|
|
||||||
|
option, select {
|
||||||
|
background-color: var(--secondary-low-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea { background-color: var(--secondary-low-opacity); }
|
||||||
|
textarea:disabled { opacity: 70%; pointer-events: none; }
|
||||||
|
|
||||||
|
a:link { color: var(--primary); text-decoration: none; }
|
||||||
|
a:visited { color: var(--primary); text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
a::-moz-selection { text-decoration-color: var(--text); }
|
||||||
|
a::selection { -webkit-text-decoration-color: var(--text); text-decoration-color: var(--text); }
|
||||||
|
|
||||||
|
i { opacity: 80%; }
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:not(svg) { color: var(--text); }
|
||||||
|
.background:not(svg) { color: var(--background); }
|
||||||
|
.primary:not(svg) { color: var(--primary); }
|
||||||
|
.secondary:not(svg) { color: var(--secondary); }
|
||||||
|
.accent:not(svg) { color: var(--accent); }
|
||||||
|
|
||||||
|
svg.text { fill: var(--text); }
|
||||||
|
svg.background { fill: var(--background); }
|
||||||
|
svg.primary { fill: var(--primary); }
|
||||||
|
svg.secondary { fill: var(--secondary); }
|
||||||
|
svg.accent { fill: var(--accent); }
|
||||||
|
|
||||||
|
.left { text-align: left; }
|
||||||
|
.center { text-align: center; }
|
||||||
|
.right { text-align: right; }
|
||||||
|
|
||||||
|
.invis { opacity: 0%; }
|
||||||
|
.hidden { display: none; }
|
||||||
|
.no-underline { text-decoration: none !important; }
|
12
public/css/editor.css
Normal file
12
public/css/editor.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
input:not([type]), input[type="text"] {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input-display-name {
|
||||||
|
width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 30em;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
26
public/css/page.css
Normal file
26
public/css/page.css
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
svg {
|
||||||
|
width: 0.9em;
|
||||||
|
height: 0.9em;
|
||||||
|
display: inline-block;
|
||||||
|
fill: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#word-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
row-gap: 5em;
|
||||||
|
column-gap: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.added {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: left;
|
||||||
|
max-width: 16em;
|
||||||
|
white-space: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.added h2 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
19
public/editor.html
Normal file
19
public/editor.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Editor</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
<link rel="stylesheet" href="/css/page.css">
|
||||||
|
<link rel="stylesheet" href="/css/editor.css">
|
||||||
|
|
||||||
|
<script src="/js/base.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script async src="/js/editor.js"></script>
|
||||||
|
<a href="/logout">Log Out</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
public/index.html
Normal file
25
public/index.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>InfoPage</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
<script src="/js/base.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if (localStorage.getItem("token")) {
|
||||||
|
setCookie("token", localStorage.getItem("token"));
|
||||||
|
window.location.href = "/editor";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>InfoPage</h1>
|
||||||
|
To share information about yourself<br><br>
|
||||||
|
<a href="/signup">Sign up</a> -
|
||||||
|
<a href="/login">Log in</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
100
public/js/base.js
Normal file
100
public/js/base.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
const dom = (id) => (document.getElementById(id));
|
||||||
|
|
||||||
|
function setCookie(name, value) {
|
||||||
|
let date = new Date();
|
||||||
|
date.setTime(date.getTime() + (356 * 24 * 60 * 60 * 1000));
|
||||||
|
document.cookie = `${name}=${value};Path=/;Expires=${date.toUTCString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eraseCookie(name) {
|
||||||
|
document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256(ascii) {
|
||||||
|
function rightRotate(value, amount) {
|
||||||
|
return (value >>> amount) | (value << (32 - amount));
|
||||||
|
};
|
||||||
|
|
||||||
|
let maxWord = Math.pow(2, 32);
|
||||||
|
let i, j;
|
||||||
|
let result = '';
|
||||||
|
|
||||||
|
let words = [];
|
||||||
|
let asciiBitLength = ascii["length"]*8;
|
||||||
|
|
||||||
|
let hash = sha256.h = sha256.h || [];
|
||||||
|
let k = sha256.k = sha256.k || [];
|
||||||
|
let primeCounter = k["length"];
|
||||||
|
|
||||||
|
let isComposite = {};
|
||||||
|
for (let candidate = 2; primeCounter < 64; candidate++) {
|
||||||
|
if (!isComposite[candidate]) {
|
||||||
|
for (i = 0; i < 313; i += candidate) {
|
||||||
|
isComposite[i] = candidate;
|
||||||
|
}
|
||||||
|
hash[primeCounter] = (Math.pow(candidate, .5) * maxWord) | 0;
|
||||||
|
k[primeCounter++] = (Math.pow(candidate, 1 / 3) * maxWord) | 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ascii += '\x80'
|
||||||
|
while (ascii["length"]%64 - 56) ascii += '\x00'
|
||||||
|
for (i = 0; i < ascii["length"]; i++) {
|
||||||
|
j = ascii.charCodeAt(i);
|
||||||
|
if (j >> 8) return;
|
||||||
|
words[i >> 2] |= j << ((3 - i) % 4) * 8;
|
||||||
|
}
|
||||||
|
words[words["length"]] = ((asciiBitLength / maxWord) | 0);
|
||||||
|
words[words["length"]] = (asciiBitLength)
|
||||||
|
|
||||||
|
for (j = 0; j < words["length"];) {
|
||||||
|
let w = words.slice(j, j += 16);
|
||||||
|
let oldHash = hash;
|
||||||
|
hash = hash.slice(0, 8);
|
||||||
|
|
||||||
|
let w15, a, temp1, temp2;
|
||||||
|
|
||||||
|
for (i = 0; i < 64; i++) {
|
||||||
|
w15 = w[i - 15], w2 = w[i - 2];
|
||||||
|
a = hash[0], e = hash[4];
|
||||||
|
temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e & hash[5])^((~e) & hash[6])) + k[i] + (w[i] = (i < 16) ? w[i] : (w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) + w[i - 7] + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))) | 0);
|
||||||
|
temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));
|
||||||
|
hash = [(temp1 + temp2) | 0].concat(hash);
|
||||||
|
hash[4] = (hash[4] + temp1) | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
hash[i] = (hash[i] + oldHash[i]) | 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
for (j = 3; j + 1; j--) {
|
||||||
|
let b = (hash[i] >> (j * 8)) & 255;
|
||||||
|
result += ((b < 16) ? 0 : '') + b.toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function escapeHTML(text, forInput=false) {
|
||||||
|
if (forInput) {
|
||||||
|
return text.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll("\"", """)
|
||||||
|
.replaceAll("\n", " ");
|
||||||
|
}
|
||||||
|
return text.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll("\"", """);
|
||||||
|
}
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
1: '<svg style="fill: var(--text-low-opacity);" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M323.8 477.2c-38.2 10.9-78.1-11.2-89-49.4l-5.7-20c-3.7-13-10.4-25-19.5-35l-51.3-56.4c-8.9-9.8-8.2-25 1.6-33.9s25-8.2 33.9 1.6l51.3 56.4c14.1 15.5 24.4 34 30.1 54.1l5.7 20c3.6 12.7 16.9 20.1 29.7 16.5s20.1-16.9 16.5-29.7l-5.7-20c-5.7-19.9-14.7-38.7-26.6-55.5-5.2-7.3-5.8-16.9-1.7-24.9s12.3-13 21.3-13H448c8.8 0 16-7.2 16-16 0-6.8-4.3-12.7-10.4-15-7.4-2.8-13-9-14.9-16.7s.1-15.8 5.3-21.7c2.5-2.8 4-6.5 4-10.6 0-7.8-5.6-14.3-13-15.7-8.2-1.6-15.1-7.3-18-15.2s-1.6-16.7 3.6-23.3c2.1-2.7 3.4-6.1 3.4-9.9 0-6.7-4.2-12.6-10.2-14.9-11.5-4.5-17.7-16.9-14.4-28.8.4-1.3.6-2.8.6-4.3 0-8.8-7.2-16-16-16h-97.5c-12.6 0-25 3.7-35.5 10.7l-61.7 41.1c-11 7.4-25.9 4.4-33.3-6.7s-4.4-25.9 6.7-33.3l61.7-41.1c18.4-12.3 40-18.8 62.1-18.8H384c34.7 0 62.9 27.6 64 62 14.6 11.7 24 29.7 24 50 0 4.5-.5 8.8-1.3 13 15.4 11.7 25.3 30.2 25.3 51 0 6.5-1 12.8-2.8 18.7 11.6 11.8 18.8 27.8 18.8 45.5 0 35.3-28.6 64-64 64h-92.3c4.7 10.4 8.7 21.2 11.8 32.2l5.7 20c10.9 38.2-11.2 78.1-49.4 89zM32 384c-17.7 0-32-14.3-32-32V128c0-17.7 14.3-32 32-32h64c17.7 0 32 14.3 32 32v224c0 17.7-14.3 32-32 32H32z"/></svg>',
|
||||||
|
2: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M100.5 176c-29 0-52.5 23.5-52.5 52.5V320c0 13.3-10.7 24-24 24S0 333.3 0 320v-91.5C0 173 45 128 100.5 128c29.6 0 57.6 13 76.7 35.6l130.2 153.8c10 11.8 24.6 18.6 40.1 18.6 29 0 52.5-23.5 52.5-52.5V192c0-13.3 10.7-24 24-24s24 10.7 24 24v91.5C448 339 403 384 347.5 384c-29.6 0-57.6-13-76.7-35.6L140.6 194.6c-10-11.8-24.6-18.6-40.1-18.6z"/></svg>',
|
||||||
|
3: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M323.8 34.8c-38.2-10.9-78.1 11.2-89 49.4l-5.7 20c-3.7 13-10.4 25-19.5 35l-51.3 56.4c-8.9 9.8-8.2 25 1.6 33.9s25 8.2 33.9-1.6l51.3-56.4c14.1-15.5 24.4-34 30.1-54.1l5.7-20c3.6-12.7 16.9-20.1 29.7-16.5s20.1 16.9 16.5 29.7l-5.7 20c-5.7 19.9-14.7 38.7-26.6 55.5-5.2 7.3-5.8 16.9-1.7 24.9s12.3 13 21.3 13H448c8.8 0 16 7.2 16 16 0 6.8-4.3 12.7-10.4 15-7.4 2.8-13 9-14.9 16.7s.1 15.8 5.3 21.7c2.5 2.8 4 6.5 4 10.6 0 7.8-5.6 14.3-13 15.7-8.2 1.6-15.1 7.3-18 15.2s-1.6 16.7 3.6 23.3c2.1 2.7 3.4 6.1 3.4 9.9 0 6.7-4.2 12.6-10.2 14.9-11.5 4.5-17.7 16.9-14.4 28.8.4 1.3.6 2.8.6 4.3 0 8.8-7.2 16-16 16h-97.5c-12.6 0-25-3.7-35.5-10.7l-61.7-41.1c-11-7.4-25.9-4.4-33.3 6.7s-4.4 25.9 6.7 33.3l61.7 41.1c18.4 12.3 40 18.8 62.1 18.8H384c34.7 0 62.9-27.6 64-62 14.6-11.7 24-29.7 24-50 0-4.5-.5-8.8-1.3-13 15.4-11.7 25.3-30.2 25.3-51 0-6.5-1-12.8-2.8-18.7 11.6-11.8 18.8-27.8 18.8-45.5 0-35.3-28.6-64-64-64h-92.3c4.7-10.4 8.7-21.2 11.8-32.2l5.7-20c10.9-38.2-11.2-78.1-49.4-89zM32 192c-17.7 0-32 14.3-32 32v224c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32H32z"/></svg>',
|
||||||
|
4: '<svg class="accent" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m47.6 300.4 180.7 168.7c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9l180.7-168.7c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141-45.6-7.6-92 7.3-124.6 39.9l-12 12-1-12c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg>',
|
||||||
|
add: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"/></svg>',
|
||||||
|
x: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>',
|
||||||
|
arrow: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h306.7L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>'
|
||||||
|
}
|
159
public/js/editor.js
Normal file
159
public/js/editor.js
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
function log(str) {
|
||||||
|
c++;
|
||||||
|
document.getElementById("log").innerText = str;
|
||||||
|
setTimeout(
|
||||||
|
function() {
|
||||||
|
--c;
|
||||||
|
if (!c) {
|
||||||
|
document.getElementById("log").innerText = " ";
|
||||||
|
}
|
||||||
|
}, 3000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToOutput(starting, json, key, title) {
|
||||||
|
starting += `<div class="added" style="text-align: center;"><div style="text-align: left; margin-bottom: 10px;" id='${key}'><h2>${title}</h2>`; // Title
|
||||||
|
|
||||||
|
for (let i = 0; i < json[key].length; i++) {
|
||||||
|
starting += `
|
||||||
|
<div id="${key}-${i}" data-id="${i}">
|
||||||
|
<select data-select2>
|
||||||
|
<option value="4"${json[key][i][1] == 4 ?" selected" : ""}>great</option>
|
||||||
|
<option value="3"${json[key][i][1] == 3 ?" selected" : ""}>good</option>
|
||||||
|
<option value="2"${json[key][i][1] == 2 ?" selected" : ""}>fine</option>
|
||||||
|
<option value="1"${json[key][i][1] == 1 ?" selected" : ""}>bad</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input value="${escapeHTML(json[key][i][0], true)}" maxlength="48">
|
||||||
|
<svg onclick="dom('${key}-${i}').remove()">${icons.x}</svg>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
starting += `</div><button onclick="add_input('${key}');">Add</button></div>`;
|
||||||
|
return starting;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_input(key) {
|
||||||
|
let x = document.createElement("div")
|
||||||
|
|
||||||
|
let q = [...document.querySelectorAll(`#${key} div[id^="${key}-"]`)];
|
||||||
|
let i = Number(q[q.length - 1].dataset.id) + 1;
|
||||||
|
|
||||||
|
x.id = `${key}-${i}`;
|
||||||
|
x.setAttribute("data-id", i);
|
||||||
|
x.innerHTML = `
|
||||||
|
<select data-select2>
|
||||||
|
<option value="4">great</option>
|
||||||
|
<option value="3" selected>good</option>
|
||||||
|
<option value="2">fine</option>
|
||||||
|
<option value="1">bad</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input maxlength="48">
|
||||||
|
<svg onclick="dom('${x.id}').remove()">${icons.x}</svg>`;
|
||||||
|
|
||||||
|
dom(key).append(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateColors() {
|
||||||
|
document.body.setAttribute("style", `--background: ${colors.background}; --background-low-opacity: ${colors.background}33; --accent: ${colors.accent}; --accent-low-opacity: ${colors.accent}66; --text: ${colors.text}; --text-low-opacity: ${colors.text}88;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_list(key) {
|
||||||
|
let output = [];
|
||||||
|
[...document.querySelectorAll(`#${key} div[id^="${key}-"]`)].forEach((val, index) => {
|
||||||
|
output.push([val.querySelector("input").value, val.querySelector("select").value]);
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStorage.getItem("token")) {
|
||||||
|
setCookie("token", localStorage.getItem("token"));
|
||||||
|
} else {
|
||||||
|
window.location.href = "/logout";
|
||||||
|
}
|
||||||
|
|
||||||
|
let colors, c;
|
||||||
|
|
||||||
|
fetch("/api/account/self", {
|
||||||
|
"method": "GET"
|
||||||
|
}).then((response) => (response.json()))
|
||||||
|
.then((json) => {
|
||||||
|
colors = json.colors;
|
||||||
|
updateColors();
|
||||||
|
|
||||||
|
let x = document.createElement("div");
|
||||||
|
let inner = `
|
||||||
|
<h2><input maxlength="64" placeholder="Display name..." id="input-display-name" value="${escapeHTML(json.display_name, true)}"></h2>
|
||||||
|
<div><textarea maxlength="512" placeholder="About me..." id="input-description">${escapeHTML(json.description)}</textarea></div>
|
||||||
|
<div>Text color: <input id="input-col-text" type="color" value="${colors.text}"></div>
|
||||||
|
<div>Background color: <input id="input-col-background" type="color" value="${colors.background}"></div>
|
||||||
|
<div>Accent color: <input id="input-col-accent" type="color" value="${colors.accent}"></div><br>
|
||||||
|
<a target="_blank" href="/u/${json.username}"><button>Preview</button></a> <button id="save">Save</button><div id="log"> </div>
|
||||||
|
<div id="word-container">
|
||||||
|
`;
|
||||||
|
|
||||||
|
inner = addToOutput(inner, json, "names", "Names");
|
||||||
|
inner = addToOutput(inner, json, "pronouns", "Pronouns");
|
||||||
|
inner = addToOutput(inner, json, "honorifics", "Honorifics");
|
||||||
|
inner = addToOutput(inner, json, "compliments", "Compliments");
|
||||||
|
inner = addToOutput(inner, json, "relationship", "Relationship<br>Descriptions");
|
||||||
|
|
||||||
|
inner += "</div>";
|
||||||
|
|
||||||
|
x.id = "container";
|
||||||
|
x.innerHTML = inner;
|
||||||
|
document.body.append(x);
|
||||||
|
|
||||||
|
dom("input-description").addEventListener("input", function() {
|
||||||
|
let cursorPosition = this.selectionStart;
|
||||||
|
if (this.value.indexOf("\n") !== -1) {
|
||||||
|
--cursorPosition;
|
||||||
|
}
|
||||||
|
this.value = this.value.replaceAll("\n", "").replaceAll("\r", "");
|
||||||
|
this.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom("save").addEventListener("click", function() {
|
||||||
|
fetch("/api/save", {
|
||||||
|
"method": "PATCH",
|
||||||
|
"body": JSON.stringify({
|
||||||
|
colors: colors,
|
||||||
|
display_name: dom("input-display-name").value,
|
||||||
|
description: dom("input-description").value,
|
||||||
|
names: get_list("names"),
|
||||||
|
pronouns: get_list("pronouns"),
|
||||||
|
honorifics: get_list("honorifics"),
|
||||||
|
compliments: get_list("compliments"),
|
||||||
|
relationship: get_list("relationship")
|
||||||
|
})
|
||||||
|
}).then((response) => (response.text()))
|
||||||
|
.then((text) => {
|
||||||
|
if (text === "200 OK") {
|
||||||
|
log("Saved!");
|
||||||
|
} else {
|
||||||
|
log("Something went wrong when saving!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log("Something went wrong when saving!");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
dom("input-col-text").addEventListener("input", function() {
|
||||||
|
colors.text = this.value;
|
||||||
|
updateColors();
|
||||||
|
});
|
||||||
|
|
||||||
|
dom("input-col-accent").addEventListener("input", function() {
|
||||||
|
colors.accent = this.value;
|
||||||
|
updateColors();
|
||||||
|
});
|
||||||
|
|
||||||
|
dom("input-col-background").addEventListener("input", function() {
|
||||||
|
colors.background = this.value;
|
||||||
|
updateColors();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
window.location.href = "/logout";
|
||||||
|
});
|
54
public/js/login.js
Normal file
54
public/js/login.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
let inc = 0, req = 0;
|
||||||
|
|
||||||
|
showlog = (str, time=3000) => {
|
||||||
|
inc++;
|
||||||
|
dom("error").innerText = str;
|
||||||
|
setTimeout(() => { req++; if (req == inc) { dom("error").innerText = ""; }}, time);
|
||||||
|
};
|
||||||
|
|
||||||
|
dom("toggle-password").addEventListener("click", function() {
|
||||||
|
if (dom("password").getAttribute("type") == "password") {
|
||||||
|
dom("password").setAttribute("type", "text");
|
||||||
|
} else {
|
||||||
|
dom("password").setAttribute("type", "password");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dom("submit").addEventListener("click", function() {
|
||||||
|
this.setAttribute("disabled", "");
|
||||||
|
username = dom("username").value;
|
||||||
|
password = sha256(dom("password").value)
|
||||||
|
fetch("/api/account/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"username": username,
|
||||||
|
"password": password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 429) {
|
||||||
|
dom("post").removeAttribute("disabled");
|
||||||
|
dom("post-text").removeAttribute("disabled");
|
||||||
|
showlog("You are being ratelimited! Try again in a few moments...");
|
||||||
|
} else {
|
||||||
|
response.json().then((json) => {
|
||||||
|
if (json.valid) {
|
||||||
|
setCookie("token", json.token);
|
||||||
|
localStorage.setItem("token", json.token);
|
||||||
|
window.location.href = "/editor";
|
||||||
|
} else {
|
||||||
|
dom("submit").removeAttribute("disabled");
|
||||||
|
showlog(`Unable to login! Reason: ${json.reason}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
dom("submit").removeAttribute("disabled");
|
||||||
|
showlog("Something went wrong! Try again in a few moments...");
|
||||||
|
throw(err);
|
||||||
|
});
|
||||||
|
});
|
54
public/js/signup.js
Normal file
54
public/js/signup.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
let inc = 0, req = 0;
|
||||||
|
|
||||||
|
showlog = (str, time=3000) => {
|
||||||
|
inc++;
|
||||||
|
dom("error").innerText = str;
|
||||||
|
setTimeout(() => { req++; if (req == inc) { dom("error").innerText = ""; }}, time);
|
||||||
|
};
|
||||||
|
|
||||||
|
dom("toggle-password").addEventListener("click", function() {
|
||||||
|
if (dom("password").getAttribute("type") == "password") {
|
||||||
|
dom("password").setAttribute("type", "text");
|
||||||
|
} else {
|
||||||
|
dom("password").setAttribute("type", "password");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dom("submit").addEventListener("click", function() {
|
||||||
|
this.setAttribute("disabled", "");
|
||||||
|
username = dom("username").value;
|
||||||
|
password = sha256(dom("password").value)
|
||||||
|
fetch("/api/account/signup", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"username": username,
|
||||||
|
"password": password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 429) {
|
||||||
|
dom("post").removeAttribute("disabled");
|
||||||
|
dom("post-text").removeAttribute("disabled");
|
||||||
|
showlog("You are being ratelimited! Try again in a few moments...");
|
||||||
|
} else {
|
||||||
|
response.json().then((json) => {
|
||||||
|
if (json.valid) {
|
||||||
|
setCookie("token", json.token);
|
||||||
|
localStorage.setItem("token", json.token);
|
||||||
|
window.location.href = "/editor";
|
||||||
|
} else {
|
||||||
|
dom("submit").removeAttribute("disabled");
|
||||||
|
showlog(`Unable to create account! Reason: ${json.reason}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
dom("submit").removeAttribute("disabled");
|
||||||
|
showlog("Something went wrong! Try again in a few moments...");
|
||||||
|
throw(err);
|
||||||
|
});
|
||||||
|
});
|
43
public/js/user.js
Normal file
43
public/js/user.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
function addToOutput(starting, json, key, title) {
|
||||||
|
starting += `<div class="added" id='${key}'><h2>${title}</h2>`;
|
||||||
|
for (let i = 0; i < json[key].length; i++) {
|
||||||
|
starting += `<div ${
|
||||||
|
json[key][i][1] === 4 ? "class='accent'" :
|
||||||
|
json[key][i][1] === 1 ? "style='color: var(--text-low-opacity);'" : ""
|
||||||
|
}>${icons[json[key][i][1]]} ${escapeHTML(json[key][i][0])}</div>`;
|
||||||
|
}
|
||||||
|
starting += "</div>";
|
||||||
|
return starting;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x2 = window.location.href.split("?")[0].split("/");
|
||||||
|
|
||||||
|
fetch("/api/account/info/" + x2[x2.length - 1], {
|
||||||
|
"method": "GET"
|
||||||
|
}).then((response) => (response.json()))
|
||||||
|
.then((json) => {
|
||||||
|
document.body.setAttribute("style", `--background: ${json.colors.background}; --background-low-opacity: ${json.colors.background}33; --accent: ${json.colors.accent}; --accent-low-opacity: ${json.colors.accent}66; --text: ${json.colors.text}; --text-low-opacity: ${json.colors.text}88;`);
|
||||||
|
let x = document.createElement("div");
|
||||||
|
let inner = `
|
||||||
|
<h1 id="name">${escapeHTML(json.display_name)}</h1>
|
||||||
|
<div id="description">${escapeHTML(json.description)}</div>
|
||||||
|
<div id="word-container">
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.title = `${json.display_name} (@${x2[x2.length - 1]})`;
|
||||||
|
|
||||||
|
inner = addToOutput(inner, json, "names", "Names");
|
||||||
|
inner = addToOutput(inner, json, "pronouns", "Pronouns");
|
||||||
|
inner = addToOutput(inner, json, "honorifics", "Honorifics");
|
||||||
|
inner = addToOutput(inner, json, "compliments", "Compliments");
|
||||||
|
inner = addToOutput(inner, json, "relationship", "Relationship<br>Descriptions");
|
||||||
|
|
||||||
|
inner += "</div>";
|
||||||
|
|
||||||
|
x.id = "container";
|
||||||
|
x.innerHTML = inner;
|
||||||
|
document.body.append(x);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
document.body.innerHTML = "<h1>User not found!</h1><a href=\"/signup\">Sign up</a> - <a href=\"/login\">Log in</a>";
|
||||||
|
});
|
30
public/login.html
Normal file
30
public/login.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Log In</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
<script src="/js/base.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if (localStorage.getItem("token")) {
|
||||||
|
setCookie("token", localStorage.getItem("token"));
|
||||||
|
window.location.href = "/editor";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Log In</h1>
|
||||||
|
<input id="username" maxlength="24" placeholder="Username..."><br>
|
||||||
|
<input id="password" placeholder="Password..." type="password"><br><br>
|
||||||
|
<button id="submit">Log In</button><br><br>
|
||||||
|
<button id="toggle-password">Show/Hide Password</button><br><br>
|
||||||
|
<a href="/signup">Sign up instead...</a>
|
||||||
|
<div id="error"></div>
|
||||||
|
|
||||||
|
<script src="/js/login.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
20
public/logout.html
Normal file
20
public/logout.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Log In</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
<script src="/js/base.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
Click <a href="/">here</a> if you aren't redirected shortly...
|
||||||
|
<script>
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
eraseCookie("token");
|
||||||
|
window.location.href = "/";
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
30
public/signup.html
Normal file
30
public/signup.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Sign Up</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
<script src="/js/base.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if (localStorage.getItem("token")) {
|
||||||
|
setCookie("token", localStorage.getItem("token"));
|
||||||
|
window.location.href = "/editor";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Sign Up</h1>
|
||||||
|
<input id="username" maxlength="24" placeholder="Username..."><br>
|
||||||
|
<input id="password" placeholder="Password..." type="password"><br><br>
|
||||||
|
<button id="submit">Sign Up</button><br><br>
|
||||||
|
<button id="toggle-password">Show/Hide Password</button><br><br>
|
||||||
|
<a href="/login">Log in instead...</a>
|
||||||
|
<div id="error"></div>
|
||||||
|
|
||||||
|
<script src="/js/signup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
16
public/user.html
Normal file
16
public/user.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>sussy among us imposter</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
<link rel="stylesheet" href="/css/page.css">
|
||||||
|
<script src="/js/base.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="/js/user.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue