]> git.amazingca.dev Git - autumn/todo/commitdiff
Add sync server
authorAmazingca <autumn@amazingca.dev>
Thu, 2 Jul 2026 07:00:32 +0000 (01:00 -0600)
committerAmazingca <autumn@amazingca.dev>
Thu, 2 Jul 2026 07:00:32 +0000 (01:00 -0600)
app/settings.html
app/settings.js
app/style.css

index 49c3ad194f96125f2fb3bae15aa22ca4e93590e3..267a1edba255cae74a7d039cd72d42c3ea446c77 100644 (file)
           <label class="red"><input type="radio" class="themes_select" name="theme" value="red"></label>
           <label class="purple"><input type="radio" class="themes_select" name="theme" value="purple"></label>
         </div>
+        <h3>sync server</h3>
+        <div class="form">
+          <div>
+            <p>server url</p>
+            <input type="text" id="serverAddress">
+          </div>
+          <div>
+            <p>passphrase</p>
+            <input type="password" id="serverPassphrase">
+          </div>
+          <button id="checkConnection">Save</button>
+        </div>
+        <br>
+        <div id="ws-options" class="hidden">
+          <button id="pushToServer">Push</button>
+          <button id="pullFromServer">Pull</button>
+        </div>
         <h3>override css</h3>
         <textarea id="override_css"></textarea>
       </div>
index e487faead7181bb20a2d6cd9ae37ba657f67be72..8f3b8f14df3819e76b2a970213753307bcc95808 100644 (file)
@@ -8,6 +8,12 @@ var theme = {
   css: "",
   showCompleted: false
 };
+var server = {
+  address: "",
+  passphrase: ""
+};
+
+const wsOptions = document.getElementById("ws-options");
 
 if (localStorage.getItem("todos")) {
 
@@ -24,9 +30,19 @@ if (localStorage.getItem("theme")) {
   theme = JSON.parse(localStorage.getItem("theme"));
 }
 
+const serverAddressElement = document.getElementById("serverAddress");
+
+if (localStorage.getItem("server")) {
+  
+  server = JSON.parse(localStorage.getItem("server"));
+
+  serverAddressElement.value = server.address;
+  wsOptions.classList.remove("hidden");
+}
+
 const exportDataText = document.getElementById("exportData_text");
 
-document.getElementById("exportData").addEventListener("click", async (event) => {
+const encodeApplicationData = () => {
 
   const dataExport = {
     todos: todos,
@@ -34,7 +50,12 @@ document.getElementById("exportData").addEventListener("click", async (event) =>
     theme: theme
   };
 
-  const exportURL = window.location.href.replace("settings", "").replace(".html", "") + "index.html#" + btoa(encodeURIComponent(JSON.stringify(dataExport)));
+  return btoa(encodeURIComponent(JSON.stringify(dataExport)));
+}
+
+document.getElementById("exportData").addEventListener("click", async (event) => {
+
+  const exportURL = window.location.href.replace("settings", "").replace(".html", "") + "index.html#" + encodeApplicationData();
 
   if (!window.navigator.userAgent.includes("Firefox") && !/(Linux|Windows|Mac)/g.test(window.navigator.platform)) {
     
@@ -64,9 +85,15 @@ document.getElementById("exportData").addEventListener("click", async (event) =>
   }
 });
 
-const backupData = () => {
+const backupData = (full = false) => {
+
+  if (full == true) {
+    localStorage.setItem("todos", JSON.stringify(todos));
+    localStorage.setItem("archived", JSON.stringify(archived));
+  }
 
   localStorage.setItem("theme", JSON.stringify(theme));
+  localStorage.setItem("server", JSON.stringify(server));
 }
 
 const override_css = document.getElementById("override_css");
@@ -172,3 +199,109 @@ const updateTheme = (force = false) => {
 }
 
 updateTheme();
+
+const checkConnection = document.getElementById("checkConnection");
+
+checkConnection.addEventListener("click", async () => {
+  
+  const serverAddress = serverAddressElement.value;
+  const serverPassphrase = document.getElementById("serverPassphrase").value;
+
+  const reply = await fetch(serverAddress + "/auth", {
+    headers: {
+      "Authorization": `Basic ${serverPassphrase}`
+    }    
+  });
+
+  if (reply.status == 200) {
+
+    server = {
+      address: serverAddress,
+      passphrase: serverPassphrase
+    };
+
+    backupData();
+    
+    checkConnection.innerHTML = "Successfully connected!";
+
+    setTimeout(() => {
+      checkConnection.innerHTML = "Save";
+    }, 2000);
+
+    wsOptions.classList.remove("hidden");
+  } else {
+
+    checkConnection.innerHTML = "Server or credentials provided not valid!";
+
+    setTimeout(() => {
+      checkConnection.innerHTML = "Save";
+    }, 2000);
+    
+    wsOptions.classList.add("hidden");
+  }
+});
+
+const pushToServer = document.getElementById("pushToServer");
+const pullFromServer = document.getElementById("pullFromServer");
+
+pushToServer.addEventListener("click", async () => {
+
+  const req = await fetch(server.address + "/push", {
+    method: "POST",
+    headers: {
+      "Authorization": `Basic ${server.passphrase}`,
+      "Data": JSON.stringify({
+        todos: todos,
+        archived: archived,
+        theme: theme
+      })
+    }
+  });
+
+  if (req.status == 200) {
+
+    pushToServer.innerHTML = "Success!";
+
+    setTimeout(() => {
+      pushToServer.innerHTML = "Push";
+    }, 2000);
+  } else {
+
+    window.alert(JSON.stringify(req));
+  }
+});
+
+pullFromServer.addEventListener("click", async () => {
+
+  const req = await fetch(server.address + "/pull", {
+    headers: {
+      "Authorization": `Basic ${server.passphrase}`
+    }
+  });
+
+  if (req.status == 200) {
+
+    try {
+
+      ({todos, archived, theme} = JSON.parse(await req.text())); // using parenthesis around this is required because we're doing object literal destructuring without a declaration
+
+      backupData(true);
+      updateHeaders();
+      updateTheme();
+    } catch (e) {
+
+      console.error(e);
+
+      window.alert("The data pulled from the server was not valid!");
+    }
+
+    pullFromServer.innerHTML = "Success!";
+
+    setTimeout(() => {
+      pullFromServer.innerHTML = "Pull";
+    }, 2000);
+  } else {
+
+    window.alert(JSON.stringify(req));
+  }
+})
index 51c0768d2e38c34af964b2f0f88e7141cfb7e48c..7ed7934fc7fb36f0d9145a5b9d7a4ee6e5f30620 100644 (file)
@@ -266,6 +266,53 @@ div:has(> #createTodo) {
   }
 }
 
+.form {
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+  & input[type="text"], & input[type="password"] {
+    border: unset;
+    border-radius: var(--border-radius-inner);
+    background-color: var(--background-primary);
+    font-family: "Maple Mono";
+    padding: 0.25rem;
+    &:focus-visible {
+      outline: unset;
+    }
+  }
+  & > div {
+    background-color: var(--background-secondary);
+    border-radius: var(--border-radius-inner);
+    padding: 0.5rem;
+    display: flex;
+    flex-direction: column;
+    gap: 0.5rem;
+    p {
+      margin: 0;
+    }
+  }
+  & > button {
+    border-radius: var(--border-radius-inner);
+  }
+  & > :first-child {
+    border-top-left-radius: var(--border-radius-outer);
+    border-top-right-radius: var(--border-radius-outer);
+  }
+  & > :last-child {
+    border-bottom-left-radius: var(--border-radius-outer);
+    border-bottom-right-radius: var(--border-radius-outer);
+  }
+}
+
+#ws-options {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.5rem;
+  & > button {
+    flex: 1;
+  }
+}
+
 #override_css {
   border-radius: 0.5rem;
   resize: vertical;