add browse and home pages

This commit is contained in:
trinkey 2024-02-26 17:33:35 -05:00
parent 12310e3b83
commit 28589f8a7b
14 changed files with 273 additions and 23 deletions

View file

@ -6,3 +6,5 @@ it's like pronouns.page but i made it
### todo ### todo
* social links * social links
* sexuality/gender flags * sexuality/gender flags
* browse people and allow public pages
* home page

View file

@ -4,6 +4,7 @@ SAVING_DIRECTORY = "./save/"
UPGRADE_TO_HTTPS = False UPGRADE_TO_HTTPS = False
import hashlib import hashlib
import random
import shutil import shutil
import flask import flask
import json import json
@ -14,6 +15,7 @@ from flask import request, redirect
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.url_map.strict_slashes = False
def validate_color(color: str) -> bool: def validate_color(color: str) -> bool:
if len(color) != 7 or color[0] != "#": if len(color) != 7 or color[0] != "#":
@ -64,16 +66,55 @@ def ensure_file(path: str, *, default_value: str="", folder: bool=False) -> None
f.close() f.close()
def escape_html(string: str) -> str: def escape_html(string: str) -> str:
return string.replace("&", "&amp;").replace("<", "&lt;").replace("\"", "&quo;") return string.replace("&", "&amp;").replace("<", "&lt;").replace("\"", "&quot;")
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: def generate_token(username: str, passhash: str) -> str:
return sha(sha(f"{username}:{passhash}") + "among us in real life, sus, sus") return sha(sha(f"{username}:{passhash}") + "among us in real life, sus, sus")
def list_public(
sort: str="alphabetical",
page: int=0,
limit: int=25
) -> dict:
# Sort: "alphabetical", "random"
# Page: for "alphabetical", page for next. 0 is first page, 1 is second...
x = json.loads(open(f"{SAVING_DIRECTORY}public/list.json", "r").read())
output = {
"end": True,
"list": []
}
if sort == "alphabetical":
x = x[limit * page::]
output["end"] = len(x) <= limit
for i in x[:limit:]:
q = json.loads(open(f"{SAVING_DIRECTORY}{i}.json", "r").read())
output["list"].append({
"colors": q["colors"],
"display_name": q["display_name"],
"bio": q["description"],
"username": i
})
elif sort == "random":
random.shuffle(x)
for i in x[:limit:]:
q = json.loads(open(f"{SAVING_DIRECTORY}{i}.json", "r").read())
output["list"].append({
"colors": q["colors"],
"display_name": q["display_name"],
"bio": q["description"],
"username": i
})
return output
def create_file_serve(file) -> Callable:
x = lambda property=None: flask.send_file(f"{CONTENT_DIRECTORY}{file}")
x.__name__ = file
return x
def create_folder_serve(directory) -> Callable: def create_folder_serve(directory) -> Callable:
x = lambda file: flask.send_from_directory(f"{CONTENT_DIRECTORY}{directory}", file) x = lambda file: flask.send_from_directory(f"{CONTENT_DIRECTORY}{directory}", file)
x.__name__ = directory x.__name__ = directory
@ -138,7 +179,7 @@ def api_account_signup():
except KeyError: except KeyError:
flask.abort(400) flask.abort(400)
if len(x["username"]) > 24 or len(x["username"]) < 1: if len(x["username"]) > 24 or len(username) < 1:
flask.abort(400) flask.abort(400)
if len(passhash) != 64: if len(passhash) != 64:
@ -240,8 +281,6 @@ def api_account_self():
flask.abort(404) flask.abort(404)
def api_save(): def api_save():
# TODO - ADD SORTING
username = open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read() username = open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read()
x = json.loads(request.data) x = json.loads(request.data)
@ -253,6 +292,19 @@ def api_save():
if "description" in x and len(x["description"]) < 512: if "description" in x and len(x["description"]) < 512:
user_data["description"] = x["description"] user_data["description"] = x["description"]
if "public" in x:
user_data["public"] = bool(x["public"])
public_list = json.loads(open(f"{SAVING_DIRECTORY}public/list.json", "r").read())
if user_data["public"] and username not in public_list:
public_list.append(username)
elif not user_data["public"] and username in public_list:
public_list.remove(username)
f = open(f"{SAVING_DIRECTORY}public/list.json", "w")
f.write(json.dumps(sorted(public_list)))
f.close()
if "colors" in x: if "colors" in x:
if "accent" in x["colors"] and validate_color(x["colors"]["accent"]): if "accent" in x["colors"] and validate_color(x["colors"]["accent"]):
user_data["colors"]["accent"] = x["colors"]["accent"] user_data["colors"]["accent"] = x["colors"]["accent"]
@ -304,18 +356,54 @@ def api_save():
return "200 OK" return "200 OK"
def u_(username): def api_browse():
return flask.send_file(f"{CONTENT_DIRECTORY}user.html") return return_dynamic_content_type(
json.dumps(list_public(request.args.get("sort"), int(request.args.get("page")) if "page" in request.args else 0)),
"application/json"
)
def home():
if "token" not in request.cookies:
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
token = request.cookies['token']
if len(token) != 64:
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
for i in token:
if i not in "abcdef0123456789":
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
try:
username = open(f"{SAVING_DIRECTORY}tokens/{token}.txt", "r").read()
user_info = json.loads(open(f"{SAVING_DIRECTORY}{username}.json", "r").read())
except FileNotFoundError:
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
x = open(f"{CONTENT_DIRECTORY}home.html", "r").read()
x = x.replace("{{USERNAME}}", username)
x = x.replace("{{DISPL_NAME}}", user_info["display_name"])
x = x.replace("{{PUBLIC}}", "true" if "public" in user_info and user_info["public"] else "false")
x = x.replace("{{TOTAL}}", str(len(json.loads(open(f"{SAVING_DIRECTORY}public/list.json", "r").read()))))
return return_dynamic_content_type(x, "text/html")
ensure_file(SAVING_DIRECTORY, folder=True) ensure_file(SAVING_DIRECTORY, folder=True)
ensure_file(f"{SAVING_DIRECTORY}tokens/", folder=True) ensure_file(f"{SAVING_DIRECTORY}tokens/", folder=True)
ensure_file(f"{SAVING_DIRECTORY}public", folder=True)
ensure_file(f"{SAVING_DIRECTORY}public/list.json", default_value="[]")
app.route("/")(create_file_serve("index.html")) app.route("/")(create_file_serve("index.html"))
app.route("/login")(create_file_serve("login.html")) app.route("/login")(create_file_serve("login.html"))
app.route("/signup")(create_file_serve("signup.html")) app.route("/signup")(create_file_serve("signup.html"))
app.route("/logout")(create_file_serve("logout.html")) app.route("/logout")(create_file_serve("logout.html"))
app.route("/browse")(create_file_serve("browse.html"))
app.route("/editor")(create_file_serve("editor.html")) app.route("/editor")(create_file_serve("editor.html"))
app.route("/u/<path:username>")(u_) app.route("/u/<path:property>")(create_file_serve("user.html"))
app.route("/home")(home)
app.route("/js/<path:file>")(create_folder_serve("js")) app.route("/js/<path:file>")(create_folder_serve("js"))
app.route("/css/<path:file>")(create_folder_serve("css")) app.route("/css/<path:file>")(create_folder_serve("css"))
@ -324,6 +412,9 @@ 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/info/<path:user>", methods=["GET"])(api_account_info_)
app.route("/api/account/self", methods=["GET"])(api_account_self) app.route("/api/account/self", methods=["GET"])(api_account_self)
app.route("/api/save", methods=["PATCH"])(api_save) app.route("/api/save", methods=["PATCH"])(api_save)
app.route("/api/browse", methods=["GET"])(api_browse)
app.errorhandler(404)(create_file_serve("404.html"))
if UPGRADE_TO_HTTPS: if UPGRADE_TO_HTTPS:
app.wsgi_app = ProxyFix(app.wsgi_app) app.wsgi_app = ProxyFix(app.wsgi_app)

53
public/browse.html Normal file
View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<title>Browse - InfoPage</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/browse.css">
<script src="/js/base.js"></script>
</head>
<body>
<h1>Browse</h1>
<a href="/home">Return home</a><br><br>
<button onclick="load(true)">Refresh</button>
<div id="middle"></div>
<button hidden id="more" onclick="load(false)">Load more...</button>
<script>
let next = 0;
function load(fromStart) {
if (fromStart) { next = 0; }
fetch(`/api/browse?sort=alphabetical&page=${next}`, {
"method": "GET"
}).then((request) => (request.json()))
.then((json) => {
if (json.end) {
dom("more").setAttribute("hidden", "");
} else {
dom("more").removeAttribute("hidden");
}
output = "";
for (const profile of json.list) {
output += `
<div style="--text: ${profile.colors.text}; --background: ${profile.colors.background}; --accent: ${profile.colors.accent};">
<div onclick="window.location.href = '/u/${profile.username}'" class="user-entry">
<p>${escapeHTML(profile.display_name)} (@${profile.username})</p>
${escapeHTML(profile.bio)}
</div>
</div>`;
}
dom("middle").innerHTML = output;
});
}
load();
</script>
</body>
</html>

20
public/css/browse.css Normal file
View file

@ -0,0 +1,20 @@
.user-entry {
width: 30em;
margin: 10px auto;
border: 2px var(--accent) solid;
padding: 6px;
border-radius: 4px;
background-color: var(--background);
color: var(--text);
cursor: pointer;
}
p {
font-size: 1.2em;
}
@media screen and (max-width: 40em) {
.user-entry {
width: 80vw;
}
}

17
public/css/home.css Normal file
View file

@ -0,0 +1,17 @@
.home-container {
width: 20em;
padding: 10px 0;
margin: 0 auto;
border: 2px var(--secondary) solid;
padding: 6px;
border-radius: 4px;
background-color: var(--accent);
}
.home-container[onclick] {
cursor: pointer;
}
p {
font-size: 1.2em;
}

View file

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>Editor</title> <title>Editor - InfoPage</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">

63
public/home.html Normal file
View file

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>Home - InfoPage</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/home.css">
<script src="/js/base.js"></script>
<script>
if (localStorage.getItem("token")) {
setCookie("token", localStorage.getItem("token"));
} else {
window.location.href = "/logout";
}
let public = eval("{{PUBLIC}}");
</script>
</head>
<body>
<h1>Welcome, {{DISPL_NAME}}!</h1>
<div onclick="window.location.href = '/editor'" class="home-container">
<p>Edit your profile</p>
{{DISPL_NAME}} (@{{USERNAME}})<br>
</div>
<br>
<div onclick="toggleVisibility()" class="home-container">
<p>Toggle visibility</p>
Visibility: <span id="public-thing"></span>Public
</div>
<br>
<div onclick="window.location.href = '/browse'" class="home-container">
<p>Browse other profiles</p>
Total public profiles: <span id="total">{{TOTAL}}</span>
</div>
<br>
<div onclick="window.location.href = '/u/{{USERNAME}}'" class="home-container">
<p>Preview your profile</p>
Shortened url:<br>
https://infopg.web.app/u/{{USERNAME}}
</div>
<script>
dom("public-thing").innerText = public ? "" : "Not "
function toggleVisibility() {
public = !public;
dom("public-thing").innerText = public ? "" : "Not ";
dom("total").innerText = +dom("total").innerText + (public ? 1 : -1);
fetch( "/api/save", {
"method": "PATCH",
"body": JSON.stringify({
"public": public
})
});
}
</script>
</body>
</html>

View file

@ -11,7 +11,7 @@
<script> <script>
if (localStorage.getItem("token")) { if (localStorage.getItem("token")) {
setCookie("token", localStorage.getItem("token")); setCookie("token", localStorage.getItem("token"));
window.location.href = "/editor"; window.location.href = "/home";
} }
</script> </script>
</head> </head>

View file

@ -55,7 +55,7 @@ function add_input(key) {
} }
function updateColors() { 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;`); document.body.setAttribute("style", `--primary: ${colors.text}; --secondary-low-opacity: ${colors.text}22; --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) { function get_list(key) {
@ -87,7 +87,9 @@ fetch("/api/account/self", {
<div><textarea maxlength="512" placeholder="About me..." id="input-description">${escapeHTML(json.description)}</textarea></div> <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>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>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> <div>Accent color: <input id="input-col-accent" type="color" value="${colors.accent}"></div>
<div>Public: <input id="public" type="checkbox" ${json.public ? "checked" : ""}></div><br>
<a target="_blank" href="/home"><button>Home</button></a>
<a target="_blank" href="/u/${json.username}"><button>Preview</button></a> <a target="_blank" href="/u/${json.username}"><button>Preview</button></a>
<button onclick="navigator.clipboard.writeText('https://infopg.web.app/u/trinkey'); log('Copied!');">Share</button> <button onclick="navigator.clipboard.writeText('https://infopg.web.app/u/trinkey'); log('Copied!');">Share</button>
<button id="save">Save</button><div id="log"> </div> <button id="save">Save</button><div id="log"> </div>
@ -126,7 +128,8 @@ fetch("/api/account/self", {
pronouns: get_list("pronouns"), pronouns: get_list("pronouns"),
honorifics: get_list("honorifics"), honorifics: get_list("honorifics"),
compliments: get_list("compliments"), compliments: get_list("compliments"),
relationship: get_list("relationship") relationship: get_list("relationship"),
public: dom("public").checked
}) })
}).then((response) => (response.text())) }).then((response) => (response.text()))
.then((text) => { .then((text) => {

View file

@ -39,5 +39,6 @@ fetch("/api/account/info/" + x2[x2.length - 1].toLowerCase(), {
document.body.append(x); document.body.append(x);
}) })
.catch((err) => { .catch((err) => {
document.title = "User not found - InfoPage"
document.body.innerHTML = "<h1>User not found!</h1><a href=\"/signup\">Sign up</a> - <a href=\"/login\">Log in</a>"; document.body.innerHTML = "<h1>User not found!</h1><a href=\"/signup\">Sign up</a> - <a href=\"/login\">Log in</a>";
}); });

View file

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>Log In</title> <title>Log In - InfoPage</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
@ -11,7 +11,7 @@
<script> <script>
if (localStorage.getItem("token")) { if (localStorage.getItem("token")) {
setCookie("token", localStorage.getItem("token")); setCookie("token", localStorage.getItem("token"));
window.location.href = "/editor"; window.location.href = "/home";
} }
</script> </script>
</head> </head>

View file

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>Log In</title> <title>Log out - InfoPage</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">

View file

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>Sign Up</title> <title>Sign Up - InfoPage</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
@ -11,7 +11,7 @@
<script> <script>
if (localStorage.getItem("token")) { if (localStorage.getItem("token")) {
setCookie("token", localStorage.getItem("token")); setCookie("token", localStorage.getItem("token"));
window.location.href = "/editor"; window.location.href = "/home";
} }
</script> </script>
</head> </head>

View file

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>User not found!</title> <title>Loading user...</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">