stuff
This commit is contained in:
parent
ec90d4a8d7
commit
c3a1ce74af
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,7 +1,10 @@
|
||||||
users.txt
|
users.txt
|
||||||
|
access.log
|
||||||
.venv
|
.venv
|
||||||
venv
|
venv
|
||||||
env
|
env
|
||||||
.env
|
.env
|
||||||
notes
|
notes
|
||||||
log.log
|
log.log
|
||||||
|
__pycache__
|
||||||
|
error.log
|
||||||
|
|
48
app.py
48
app.py
|
@ -108,7 +108,53 @@ def api(note):
|
||||||
def serve_note(note):
|
def serve_note(note):
|
||||||
if not get_user():
|
if not get_user():
|
||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
return render_template("note.html", note=note)
|
return render_template("note.html", note=note, username=get_user())
|
||||||
|
|
||||||
|
@app.route("/public/<username>")
|
||||||
|
@app.route("/board")
|
||||||
|
def serve_note_nologin(username: str = ""):
|
||||||
|
return render_template("note.html", note=username, username=get_user())
|
||||||
|
|
||||||
|
@app.route("/publicapi/<username>", methods=["GET", "POST"])
|
||||||
|
def public_board(username):
|
||||||
|
user = get_user()
|
||||||
|
path = os.path.join(DATA_DIR, username.replace("/", "_"), "__public.txt")
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return "", 200
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return f.read(), 200
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
if user != username:
|
||||||
|
return "Forbidden", 403
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or "content" not in data:
|
||||||
|
return "Bad request", 400
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(data["content"])
|
||||||
|
return jsonify({"status": "saved"})
|
||||||
|
|
||||||
|
@app.route("/boardapi", methods=["GET", "POST"])
|
||||||
|
def shared_board():
|
||||||
|
path = os.path.join(DATA_DIR, "__shared_board.txt")
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return "", 200
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return f.read(), 200
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or "content" not in data:
|
||||||
|
return "Bad request", 400
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(data["content"])
|
||||||
|
return jsonify({"status": "saved"})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def serve_root():
|
def serve_root():
|
||||||
|
|
|
@ -1,31 +1,63 @@
|
||||||
const textarea = document.getElementById("editor");
|
const textarea = document.getElementById("editor");
|
||||||
const status = document.getElementById("status");
|
const status = document.getElementById("status");
|
||||||
const error = document.getElementById("error");
|
const error = document.getElementById("error");
|
||||||
|
const currentUser = document.getElementById("username").innerHTML;
|
||||||
|
const path = window.location.pathname;
|
||||||
|
|
||||||
fetch("/api/" + noteName)
|
// === ROUTE PARSING ===
|
||||||
|
let mode = "private";
|
||||||
|
let fetchUrl = "";
|
||||||
|
let editable = true;
|
||||||
|
let last = "";
|
||||||
|
|
||||||
|
if (path.startsWith("/public/")) {
|
||||||
|
const username = path.split("/")[2];
|
||||||
|
fetchUrl = "/publicapi/" + username;
|
||||||
|
editable = (currentUser === username);
|
||||||
|
mode = "public";
|
||||||
|
} else if (path === "/board") {
|
||||||
|
fetchUrl = "/boardapi";
|
||||||
|
mode = "shared";
|
||||||
|
editable = true;
|
||||||
|
} else if (path.startsWith("/n/") || path.startsWith("/notes/")) {
|
||||||
|
const note = path.split("/").pop();
|
||||||
|
fetchUrl = "/api/" + note;
|
||||||
|
editable = true;
|
||||||
|
} else {
|
||||||
|
error.textContent = "Unsupported path: " + path;
|
||||||
|
textarea.disabled = true;
|
||||||
|
throw new Error("Unknown path: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!editable) textarea.disabled = true;
|
||||||
|
|
||||||
|
// === FETCH INITIAL CONTENT ===
|
||||||
|
fetch(fetchUrl)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (!res.ok) throw new Error("Failed to load note. Status: " + res.status);
|
if (!res.ok) throw new Error("Failed to load. Status: " + res.status);
|
||||||
return res.text();
|
return res.text();
|
||||||
})
|
})
|
||||||
.then(text => {
|
.then(text => {
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
|
last = text;
|
||||||
status.textContent = "Loaded";
|
status.textContent = "Loaded";
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
error.textContent = "Error loading note: " + err.message;
|
error.textContent = "Error loading content: " + err.message;
|
||||||
status.textContent = "Load failed";
|
status.textContent = "Load failed";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// === AUTOSAVE LOGIC ===
|
||||||
let timeout;
|
let timeout;
|
||||||
let last = "";
|
|
||||||
|
|
||||||
textarea.addEventListener("input", () => {
|
textarea.addEventListener("input", () => {
|
||||||
|
if (!editable) return;
|
||||||
status.textContent = "Typing...";
|
status.textContent = "Typing...";
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
const text = textarea.value;
|
const text = textarea.value;
|
||||||
if (text !== last) {
|
if (text !== last) {
|
||||||
fetch("/api/" + noteName, {
|
fetch(fetchUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ content: text })
|
body: JSON.stringify({ content: text })
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<div id="username" style="display: none;">{{ username }}</div>
|
||||||
<title> {{ note }} </title>
|
<title> {{ note }} </title>
|
||||||
<style>
|
<style>
|
||||||
textarea { width: 100%; height: 90vh; font-family: monospace; font-size: 1em; }
|
textarea { width: 100%; height: 90vh; font-family: monospace; font-size: 1em; }
|
||||||
|
|
Loading…
Reference in a new issue