mirror of
https://github.com/zoldar/jenot.git
synced 2026-01-03 14:32:54 +00:00
160 lines
4.8 KiB
JavaScript
160 lines
4.8 KiB
JavaScript
import "./service-worker-init.js";
|
|
import { renderText } from "./dom.js";
|
|
import { SyncedNoteStore } from "./synced-store.js";
|
|
import {
|
|
notificationsAvailable,
|
|
authorizeNotifications,
|
|
notificationsEnabled,
|
|
sendNotification,
|
|
} from "./notifications.js";
|
|
import "./components.js";
|
|
|
|
const URL_PARAMS = new URLSearchParams(window.location.search);
|
|
|
|
// Cookie presence determines login state
|
|
const isLoggedIn = !!document.cookie
|
|
.split("; ")
|
|
.find((row) => row.startsWith("jenot_pub="));
|
|
|
|
// Notes storage configuration.
|
|
// The storage is a combination of IndexedDB + network sync.
|
|
// Network sync is only enabled is user is logged in.
|
|
const Notes = new SyncedNoteStore("jenot-app", "notes", isLoggedIn && "/");
|
|
|
|
// Reset metadata to force full sync
|
|
if (URL_PARAMS.has("reset-meta")) {
|
|
history.replaceState(
|
|
null,
|
|
"",
|
|
location.href.replace("&reset-meta", "").replace("?reset-meta", ""),
|
|
);
|
|
await Notes.setMeta({ lastSync: null });
|
|
}
|
|
|
|
// Very rudimentary periodic sync. It will be refactored into a more real-time
|
|
// solution using either websocket of long-polling, so that server can notify about
|
|
// new data to sync.
|
|
const sync = async () => {
|
|
await Notes.sync();
|
|
Notes.saveStorage();
|
|
};
|
|
|
|
if (isLoggedIn) {
|
|
setInterval(sync, 5000);
|
|
}
|
|
|
|
// New account provisioning and login/logout actions
|
|
|
|
const newAccountForm = document.querySelector("#new-account-form");
|
|
const loginLink = document.querySelector("#login-link");
|
|
const logoutForm = document.querySelector("#logout-form");
|
|
|
|
if (!isLoggedIn) {
|
|
loginLink.classList.remove("hidden");
|
|
newAccountForm.classList.remove("hidden");
|
|
} else {
|
|
logoutForm.classList.remove("hidden");
|
|
}
|
|
|
|
// Notifications API test - to be reused for push notifications later on
|
|
|
|
if (notificationsAvailable()) {
|
|
document.querySelector("#notifications-pane").classList.remove("hidden");
|
|
const notificationsButton = document.querySelector("#enable-notifications");
|
|
const notificationsTestButton = document.querySelector("#test-notifications");
|
|
|
|
if (!notificationsEnabled()) {
|
|
notificationsButton.classList.remove("hidden");
|
|
notificationsButton.addEventListener("click", () => {
|
|
authorizeNotifications(() => notificationsButton.classList.add("hidden"));
|
|
});
|
|
}
|
|
|
|
notificationsTestButton.addEventListener("click", () => {
|
|
setTimeout(() => {
|
|
sendNotification("reminder", "This is a test reminder!");
|
|
}, 8000);
|
|
});
|
|
}
|
|
|
|
// There are two note-form component instances - one for
|
|
// composing new notes and another one for editing existing notes.
|
|
|
|
const newNote = document.querySelector("#new-note");
|
|
const editNote = document.querySelector("#edit-note");
|
|
|
|
// Each save event originating from storage triggers a re-render
|
|
// of notes list.
|
|
Notes.addEventListener("save", render.bind(this));
|
|
|
|
// Initial notes render and initial sync.
|
|
render();
|
|
if (isLoggedIn) {
|
|
sync();
|
|
}
|
|
|
|
// note-form component specific event handlers
|
|
newNote.addEventListener("addNote", async (e) => {
|
|
await Notes.add(e.detail);
|
|
Notes.saveStorage();
|
|
});
|
|
|
|
editNote.addEventListener("updateNote", async (e) => {
|
|
newNote.classList.remove("hidden");
|
|
editNote.classList.add("hidden");
|
|
await Notes.update(e.detail);
|
|
Notes.saveStorage();
|
|
});
|
|
|
|
editNote.addEventListener("deleteNote", async (e) => {
|
|
newNote.classList.remove("hidden");
|
|
editNote.classList.add("hidden");
|
|
await Notes.remove(e.detail);
|
|
Notes.saveStorage();
|
|
});
|
|
|
|
// All notes are currently re-rendered on each storage
|
|
// update. The routine will be optimized to replace only
|
|
// nodes that actually changed - most likely based on unique
|
|
// note IDs associated with block elements.
|
|
|
|
async function render() {
|
|
const notes = await Notes.all();
|
|
const notesContainer = document.querySelector("#notes");
|
|
notesContainer.replaceChildren();
|
|
|
|
notes.forEach((note) => {
|
|
const container = document.createElement("div");
|
|
container.id = note.id;
|
|
container.classList.add("note");
|
|
container.classList.add("readonly");
|
|
|
|
if (note.type === "note") {
|
|
container.replaceChildren(...renderText(note.content));
|
|
} else if (note.type === "tasklist") {
|
|
const list = document.createElement("ul");
|
|
|
|
note.content.forEach((task) => {
|
|
const item = document.createElement("li");
|
|
const check = document.createElement("p");
|
|
check.textContent = task.checked ? "☑" : "☐";
|
|
item.appendChild(check);
|
|
const itemContent = document.createElement("p");
|
|
itemContent.replaceChildren(...renderText(task.content));
|
|
item.appendChild(itemContent);
|
|
list.append(item);
|
|
});
|
|
|
|
container.appendChild(list);
|
|
}
|
|
|
|
notesContainer.appendChild(container);
|
|
|
|
container.addEventListener("click", async (e) => {
|
|
newNote.classList.add("hidden");
|
|
editNote.classList.remove("hidden");
|
|
const note = await Notes.get(container.id);
|
|
editNote.load(note);
|
|
});
|
|
});
|
|
}
|