Cybrkyd's Git Repositories

jottings - commit: 4ce53d5

commit 4ce53d5df95da2fe6d36a0b444f7471a38b8efb850d4611ef1089dfcb4dc496d
author Cybrkyd <git@cybrkyd.com> 2026-06-13 11:44:31 +0100
committer Cybrkyd <git@cybrkyd.com> 2026-06-13 11:44:31 +0100

Commit Message

app.js

📊 Diffstat

resources/js/app.js 372
1 files changed, 372 insertions(+), 0 deletions(-)

Diff

diff --git a/resources/js/app.js b/resources/js/app.js
new file mode 100644
index 0000000..0c98bca
--- /dev/null
+++ b/resources/js/app.js
@@ -0,0 +1,372 @@
+ // Storage paths
+ const DATA_FILE = 'notes.json';
+ const METADATA_FILE = 'notes.meta.json';
+
+ // Global state
+ let currentDocId = null;
+ let currentDocName = null;
+ let autoSaveTimer = null;
+
+ // Helper: write to user's home directory
+ async function getDataPath(filename) {
+ const home = await Neutralino.os.getEnv('HOME');
+ const appDir = `${home}/.local/share/notes`;
+
+ // Create directories one level at a time
+ const localDir = `${home}/.local`;
+ const shareDir = `${home}/.local/share`;
+
+ try {
+ await Neutralino.filesystem.createDirectory(localDir);
+ } catch(e) {}
+
+ try {
+ await Neutralino.filesystem.createDirectory(shareDir);
+ } catch(e) {}
+
+ try {
+ await Neutralino.filesystem.createDirectory(appDir);
+ } catch(e) {}
+
+ return `${appDir}/${filename}`;
+ }
+
+ // Read all notes from JSON file
+ async function readNotes() {
+ try {
+ const path = await getDataPath(DATA_FILE);
+ const content = await Neutralino.filesystem.readFile(path);
+ return JSON.parse(content);
+ } catch (e) {
+ if (e.code === 'NE_FS_NOPATHE' || e.code === 'NE_FS_FILRDER') {
+ return [];
+ }
+ throw e;
+ }
+ }
+
+ // Write notes to JSON file
+ async function writeNotes(notes) {
+ const path = await getDataPath(DATA_FILE);
+ await Neutralino.filesystem.writeFile(path, JSON.stringify(notes, null, 2));
+ }
+
+ // Read metadata (nextId)
+ async function readMeta() {
+ try {
+ const path = await getDataPath(METADATA_FILE);
+ const content = await Neutralino.filesystem.readFile(path);
+ return JSON.parse(content);
+ } catch (e) {
+ if (e.code === 'NE_FS_NOPATHE' || e.code === 'NE_FS_FILRDER') {
+ return { nextId: 1 };
+ }
+ throw e;
+ }
+ }
+
+ // Write metadata
+ async function writeMeta(meta) {
+ const path = await getDataPath(METADATA_FILE);
+ await Neutralino.filesystem.writeFile(path, JSON.stringify(meta, null, 2));
+ }
+
+ // Get next available ID
+ async function getNextId() {
+ const meta = await readMeta();
+ return meta.nextId;
+ }
+
+ // Increment and save next ID
+ async function incrementNextId(usedId) {
+ const meta = { nextId: usedId + 1 };
+ await writeMeta(meta);
+ }
+
+ // Load all documents and render list
+ async function loadDocuments() {
+ let notes;
+ try {
+ notes = await readNotes();
+ } catch (e) {
+ console.error("Could not load notes:", e);
+ return;
+ }
+
+ const list = document.getElementById("docList");
+ list.innerHTML = "";
+
+ // Reverse to show newest first (matches original)
+ notes.reverse();
+
+ notes.forEach(doc => {
+ const div = document.createElement("div");
+ div.className = "doc-single-line" + (doc.id === currentDocId ? " active-doc" : "");
+
+ const line1 = document.createElement("div");
+ line1.className = "doc-line1";
+ line1.textContent = doc.name;
+
+ // Format created date for display (from ISO to local readable)
+ let displayDate = doc.created;
+ if (doc.created) {
+ const date = new Date(doc.created);
+ displayDate = date.toLocaleString('en-GB');
+ }
+ const line2 = document.createElement("div");
+ line2.className = "doc-line1";
+ line2.textContent = displayDate;
+
+ const line3 = document.createElement("div");
+ line3.className = "doc-line2";
+
+ const loadBtn = document.createElement("button");
+ loadBtn.textContent = "Load";
+ loadBtn.className = "small-link-btn";
+
+ const deleteBtn = document.createElement("button");
+ deleteBtn.textContent = "Delete";
+ deleteBtn.className = "small-link-btn";
+
+ const renameBtn = document.createElement("button");
+ renameBtn.textContent = "Rename";
+ renameBtn.className = "small-link-btn";
+
+ loadBtn.onclick = async function() {
+ try {
+ const notes = await readNotes();
+ const note = notes.find(n => n.id === doc.id);
+ if (note) {
+ document.getElementById("editor").value = note.content;
+ updateCounts(note.content);
+ currentDocId = doc.id;
+ currentDocName = doc.name;
+ loadDocuments();
+ updateStatus();
+ }
+ } catch (e) {
+ alert("Could not load note: " + e.message);
+ }
+ };
+
+ deleteBtn.onclick = function() { deleteDocument(doc.id); };
+ renameBtn.onclick = function() { renameDocument(doc.id, doc.name); };
+
+ line3.appendChild(loadBtn);
+ line3.appendChild(deleteBtn);
+ line3.appendChild(renameBtn);
+
+ div.appendChild(line1);
+ div.appendChild(line2);
+ div.appendChild(line3);
+ list.appendChild(div);
+ });
+ }
+
+ // Save document (create or update)
+ async function saveDocument(text) {
+ try {
+ let notes = await readNotes();
+
+ if (currentDocId !== null) {
+ // Update existing note
+ const index = notes.findIndex(n => n.id === currentDocId);
+ if (index !== -1) {
+ notes[index].content = text;
+ notes[index].updated = new Date().toISOString();
+ await writeNotes(notes);
+ }
+ } else {
+ // Create new note
+ const newId = await getNextId();
+ await incrementNextId(newId);
+
+ const now = new Date().toISOString();
+ const newNote = {
+ id: newId,
+ name: `Note ${newId}`,
+ content: text,
+ created: now,
+ updated: now
+ };
+ notes.push(newNote);
+ await writeNotes(notes);
+
+ currentDocId = newId;
+ currentDocName = newNote.name;
+ }
+
+ await loadDocuments();
+ updateStatus();
+ } catch (e) {
+ alert("Save failed: " + e.message);
+ }
+ }
+
+ // Delete document
+ async function deleteDocument(id) {
+ if (!confirm("Delete this document?")) return;
+
+ try {
+ let notes = await readNotes();
+ notes = notes.filter(n => n.id !== id);
+ await writeNotes(notes);
+
+ if (currentDocId === id) {
+ currentDocId = null;
+ currentDocName = null;
+ document.getElementById("editor").value = "";
+ updateCounts("");
+ updateStatus();
+ }
+
+ await loadDocuments();
+ } catch (e) {
+ alert("Delete failed: " + e.message);
+ }
+ }
+
+ // Rename document
+ async function renameDocument(id, currentName) {
+ const newName = prompt("Enter a new name for this note:", currentName);
+ if (newName === null) return;
+ const trimmed = newName.trim();
+ if (!trimmed) { alert("Name cannot be empty"); return; }
+
+ try {
+ let notes = await readNotes();
+ const note = notes.find(n => n.id === id);
+ if (note) {
+ note.name = trimmed;
+ await writeNotes(notes);
+ }
+
+ if (id === currentDocId) {
+ currentDocName = trimmed;
+ updateStatus();
+ }
+
+ await loadDocuments();
+ } catch (e) {
+ alert("Rename failed: " + e.message);
+ }
+ }
+
+ // Export current note as TXT file
+ async function exportDocument() {
+ const text = document.getElementById("editor").value;
+ if (!text.trim()) {
+ document.getElementById("docStatus").textContent = "Nothing to export";
+ return;
+ }
+
+ const defaultName = `${currentDocName}.txt`;
+
+ try {
+ const filename = await Neutralino.os.showSaveDialog('Export Note', {
+ defaultPath: defaultName
+ });
+
+ if (!filename) {
+ document.getElementById("docStatus").textContent = "Export cancelled";
+ return;
+ }
+
+ await Neutralino.filesystem.writeFile(filename, text);
+
+ const shortName = filename.split('/').pop();
+ document.getElementById("docStatus").textContent = `Exported ${shortName}`;
+
+ setTimeout(() => {
+ if (currentDocId !== null) {
+ document.getElementById("docStatus").textContent = `Editing: ${currentDocName}`;
+ } else {
+ document.getElementById("docStatus").textContent = "New Note";
+ }
+ }, 5000);
+
+ } catch (err) {
+ document.getElementById("docStatus").textContent = `Export error: ${err.message}`;
+ }
+ }
+
+ // Update word and character counts
+ function updateCounts(text) {
+ const characters = text.length;
+ const words = text.trim().split(/\s+/).filter(Boolean).length;
+ const formatter = new Intl.NumberFormat('en-GB');
+
+ document.getElementById("charCount").textContent =
+ `${formatter.format(characters)} character${characters !== 1 ? "s" : ""}`;
+ document.getElementById("wordCount").textContent =
+ `${formatter.format(words)} word${words !== 1 ? "s" : ""}`;
+ }
+
+ // Update status bar text
+ function updateStatus() {
+ const status = document.getElementById("docStatus");
+ if (currentDocId !== null) {
+ status.textContent = `Editing: ${currentDocName}`;
+ } else {
+ status.textContent = "New Note";
+ }
+ }
+
+ // Event Listeners
+ document.getElementById("saveBtn").onclick = function() {
+ const text = document.getElementById("editor").value.trim();
+ if (!text) { alert("Enter some text first"); return; }
+ saveDocument(text);
+ };
+
+ document.getElementById("newBtn").onclick = function() {
+ document.getElementById("editor").value = "";
+ updateCounts("");
+ currentDocId = null;
+ currentDocName = null;
+ loadDocuments();
+ updateStatus();
+ };
+
+ document.getElementById("exportBtn").onclick = exportDocument;
+
+ // Column toggle
+ const savedColumn = document.getElementById("savedColumn");
+ const toggleBtn = document.getElementById("toggleBtn");
+ toggleBtn.addEventListener("click", function() {
+ savedColumn.classList.toggle("collapsed");
+ toggleBtn.textContent = savedColumn.classList.contains("collapsed") ? "+" : "−";
+ });
+
+ // Auto-save on input (existing notes only)
+ document.getElementById("editor").addEventListener("input", function() {
+ updateCounts(this.value);
+
+ // Auto-save only if we are editing an existing note
+ if (currentDocId === null) return;
+
+ clearTimeout(autoSaveTimer);
+ autoSaveTimer = setTimeout(async function() {
+ const text = document.getElementById("editor").value.trim();
+ if (!text) return;
+
+ try {
+ let notes = await readNotes();
+ const index = notes.findIndex(n => n.id === currentDocId);
+ if (index !== -1) {
+ notes[index].content = text;
+ notes[index].updated = new Date().toISOString();
+ await writeNotes(notes);
+ document.getElementById("docStatus").textContent =
+ `${currentDocName} auto-saved at ${new Date().toLocaleTimeString()}`;
+ }
+ } catch (e) {
+ document.getElementById("docStatus").textContent = "Auto-save failed: " + e.message;
+ }
+ }, 3000);
+ });
+
+ // Initialize app
+ Neutralino.init();
+ loadDocuments();
+ updateStatus();