Initial import: KeepNote service (dcntrlized-auth consumer)
This commit is contained in:
+173
@@ -0,0 +1,173 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Notes</title>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<script src="./cookies.js"></script>
|
||||
<script src="./jwt.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>KeepNote</h1>
|
||||
<div id="toolbar">
|
||||
<button id="logout">Log out</button>
|
||||
</div>
|
||||
<form id="create-note">
|
||||
<input name="title" type="text" placeholder="title" required />
|
||||
<input name="content" type="text" placeholder="content" />
|
||||
<button>Add note</button>
|
||||
</form>
|
||||
<div id="error"></div>
|
||||
<ul id="notes"></ul>
|
||||
<script>
|
||||
const API = "http://127.0.0.1:8081";
|
||||
const errorBox = document.getElementById("error");
|
||||
let editingId = null;
|
||||
|
||||
function token() {
|
||||
return cookieEater("accessToken");
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
errorBox.textContent = msg;
|
||||
}
|
||||
|
||||
async function api(path, options) {
|
||||
options = options || {};
|
||||
options.headers = Object.assign({ "X-Access-Token": token() }, options.headers || {});
|
||||
const res = await fetch(`${API}${path}`, options);
|
||||
if (res.status === 401) {
|
||||
window.location.replace("./auth.html");
|
||||
throw new Error("unauthorized");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function renderNote(note) {
|
||||
const li = document.createElement("li");
|
||||
if (editingId === note.id) {
|
||||
const form = document.createElement("form");
|
||||
form.className = "edit-note";
|
||||
const title = document.createElement("input");
|
||||
title.name = "title";
|
||||
title.value = note.title || "";
|
||||
const content = document.createElement("input");
|
||||
content.name = "content";
|
||||
content.value = note.content || "";
|
||||
const tags = document.createElement("input");
|
||||
tags.name = "tags";
|
||||
tags.value = (note.tags || []).join(", ");
|
||||
tags.placeholder = "tags (comma separated)";
|
||||
const save = document.createElement("button");
|
||||
save.textContent = "Save";
|
||||
const cancel = document.createElement("button");
|
||||
cancel.type = "button";
|
||||
cancel.textContent = "Cancel";
|
||||
cancel.addEventListener("click", () => { editingId = null; loadNotes(); });
|
||||
form.append(title, content, tags, save, cancel);
|
||||
form.addEventListener("submit", (event) => updateNote(event, note.id, title, content, tags));
|
||||
li.appendChild(form);
|
||||
return li;
|
||||
}
|
||||
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = note.title || "(untitled)";
|
||||
li.appendChild(title);
|
||||
if (note.content) {
|
||||
li.appendChild(document.createTextNode(" - " + note.content));
|
||||
}
|
||||
for (const tag of note.tags || []) {
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "tag";
|
||||
badge.textContent = tag;
|
||||
li.appendChild(badge);
|
||||
}
|
||||
const edit = document.createElement("button");
|
||||
edit.textContent = "Edit";
|
||||
edit.addEventListener("click", () => { editingId = note.id; loadNotes(); });
|
||||
li.appendChild(edit);
|
||||
const del = document.createElement("button");
|
||||
del.textContent = "Delete";
|
||||
del.addEventListener("click", () => deleteNote(note.id));
|
||||
li.appendChild(del);
|
||||
return li;
|
||||
}
|
||||
|
||||
async function loadNotes() {
|
||||
const res = await api("/action/getNotes");
|
||||
if (!res.ok) {
|
||||
showError(`failed to load notes: ${res.status} ${await res.text()}`);
|
||||
return;
|
||||
}
|
||||
const notes = await res.json();
|
||||
const list = document.getElementById("notes");
|
||||
list.innerHTML = "";
|
||||
for (const note of notes) {
|
||||
list.appendChild(renderNote(note));
|
||||
}
|
||||
}
|
||||
|
||||
async function createNote(event) {
|
||||
event.preventDefault();
|
||||
showError("");
|
||||
const data = Object.fromEntries(new FormData(event.target).entries());
|
||||
const res = await api("/action/writeNote", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ payload: data })
|
||||
});
|
||||
if (!res.ok) {
|
||||
showError(`failed to add note: ${res.status} ${await res.text()}`);
|
||||
return;
|
||||
}
|
||||
event.target.reset();
|
||||
await loadNotes();
|
||||
}
|
||||
|
||||
async function updateNote(event, id, titleInput, contentInput, tagsInput) {
|
||||
event.preventDefault();
|
||||
showError("");
|
||||
const tags = tagsInput.value.split(",").map((t) => t.trim()).filter((t) => t !== "");
|
||||
const res = await api("/action/updateNote", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ payload: { id: id, title: titleInput.value, content: contentInput.value, tags: tags } })
|
||||
});
|
||||
if (!res.ok) {
|
||||
showError(`failed to update note: ${res.status} ${await res.text()}`);
|
||||
return;
|
||||
}
|
||||
editingId = null;
|
||||
await loadNotes();
|
||||
}
|
||||
|
||||
async function deleteNote(id) {
|
||||
showError("");
|
||||
const res = await api("/action/deleteNote", {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ payload: { id: id } })
|
||||
});
|
||||
if (!res.ok) {
|
||||
showError(`failed to delete note: ${res.status} ${await res.text()}`);
|
||||
return;
|
||||
}
|
||||
await loadNotes();
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await api("/auth/logout");
|
||||
} catch (e) { /* ignore */ }
|
||||
document.cookie = "accessToken=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/";
|
||||
window.location.replace("./auth.html");
|
||||
}
|
||||
|
||||
document.getElementById("create-note").addEventListener("submit", createNote);
|
||||
document.getElementById("logout").addEventListener("click", logout);
|
||||
|
||||
loadNotes();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user