v2
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// Shared helpers used by app.js, explore.js, and share.js.
|
||||
|
||||
window.$ = (sel, root = document) => root.querySelector(sel);
|
||||
|
||||
window.api = {
|
||||
async req(method, path, body) {
|
||||
const res = await fetch(path, {
|
||||
method,
|
||||
headers: body ? { "Content-Type": "application/json" } : undefined,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const data = res.status === 204 ? null : await res.json().catch(() => null);
|
||||
if (!res.ok) {
|
||||
const e = new Error((data && data.error) || "Request failed");
|
||||
e.status = res.status;
|
||||
throw e;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
window.escapeHtml = (s) =>
|
||||
String(s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
|
||||
// SQLite stores UTC datetimes as "YYYY-MM-DD HH:MM:SS"; parse as UTC.
|
||||
window.parseUTC = (s) => new Date(s.replace(" ", "T") + "Z");
|
||||
|
||||
window.relativeTime = (s) => {
|
||||
const then = parseUTC(s);
|
||||
const diff = (Date.now() - then.getTime()) / 1000;
|
||||
if (diff < 45) return "just now";
|
||||
if (diff < 90) return "a minute ago";
|
||||
if (diff < 3600) return `${Math.round(diff / 60)} min ago`;
|
||||
if (diff < 7200) return "an hour ago";
|
||||
if (diff < 86400) return `${Math.round(diff / 3600)} hours ago`;
|
||||
if (diff < 172800) return "yesterday";
|
||||
if (diff < 604800) return `${Math.round(diff / 86400)} days ago`;
|
||||
return then.toLocaleDateString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
window.fullTime = (s) =>
|
||||
parseUTC(s).toLocaleString(undefined, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
let toastTimer;
|
||||
window.toast = (msg, isError = false) => {
|
||||
const el = $("#toast");
|
||||
if (!el) return;
|
||||
el.textContent = msg;
|
||||
el.classList.toggle("error", isError);
|
||||
el.classList.add("show");
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => el.classList.remove("show"), 2600);
|
||||
};
|
||||
|
||||
// Render a list of notes into a container element (read-only unless onDelete given).
|
||||
window.renderNotesInto = (container, notes, onDelete) => {
|
||||
if (!container) return;
|
||||
if (!notes.length) {
|
||||
container.innerHTML =
|
||||
'<div class="empty" style="padding:24px">No notes yet.</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = notes
|
||||
.map(
|
||||
(n) => `
|
||||
<div class="note">
|
||||
<div class="avatar">📝</div>
|
||||
<div class="note-body">
|
||||
<div class="text">${escapeHtml(n.body)}</div>
|
||||
<div class="note-time">
|
||||
<span title="${escapeHtml(fullTime(n.created_at))}">${relativeTime(
|
||||
n.created_at
|
||||
)}</span>
|
||||
${onDelete ? `<span class="del" data-note="${n.id}">delete</span>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
)
|
||||
.join("");
|
||||
if (onDelete) {
|
||||
container.querySelectorAll(".del").forEach((el) =>
|
||||
el.addEventListener("click", () => onDelete(Number(el.dataset.note)))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Provider sign-in buttons (used by login gate).
|
||||
window.providerButton = (p) => {
|
||||
const icons = {
|
||||
github:
|
||||
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.3.8-.6v-2c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0C17.3 4.7 18.3 5 18.3 5c.6 1.6.2 2.8.1 3.1.8.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5z"/></svg>',
|
||||
google:
|
||||
'<svg viewBox="0 0 24 24"><path fill="#4285F4" d="M22.5 12.2c0-.8-.1-1.4-.2-2.1H12v3.9h6c-.1 1-.8 2.5-2.2 3.5v2.9h3.5c2.1-1.9 3.2-4.7 3.2-8.1z"/><path fill="#34A853" d="M12 23c2.9 0 5.4-1 7.2-2.6l-3.5-2.9c-1 .7-2.2 1.1-3.7 1.1-2.8 0-5.2-1.9-6.1-4.5H2.3v2.9C4.1 20.6 7.8 23 12 23z"/><path fill="#FBBC05" d="M5.9 14.1c-.2-.7-.4-1.4-.4-2.1s.1-1.4.4-2.1V7H2.3C1.5 8.5 1 10.2 1 12s.5 3.5 1.3 5l3.6-2.9z"/><path fill="#EA4335" d="M12 5.4c1.6 0 2.9.5 4 1.6l3-3C17.4 2.1 14.9 1 12 1 7.8 1 4.1 3.4 2.3 7l3.6 2.9C6.8 7.3 9.2 5.4 12 5.4z"/></svg>',
|
||||
};
|
||||
return `<button class="provider-btn" data-provider="${p.id}">${
|
||||
icons[p.id] || ""
|
||||
}<span>Continue with ${escapeHtml(p.label)}</span></button>`;
|
||||
};
|
||||
Reference in New Issue
Block a user