fixes and my notes function
This commit is contained in:
@@ -524,3 +524,28 @@ dialog::backdrop { background: rgba(4,6,9,.82); }
|
||||
[hidden] { display: none !important; }
|
||||
.identity-panel #auth-panel:not([hidden]) { margin-top: 4px; }
|
||||
.identity-panel #nickname:disabled { opacity: .55; cursor: not-allowed; }
|
||||
|
||||
#resources-dialog { width: min(760px, calc(100% - 28px)); max-width: 760px; }
|
||||
.resources-panel { position: relative; box-sizing: border-box; width: 100%; min-width: 0; max-height: 80vh; overflow-x: hidden; overflow-y: auto; }
|
||||
.resources-panel__header { min-width: 0; padding-right: 38px; }
|
||||
.resources-panel__header h2 { margin: 0 0 7px; overflow-wrap: anywhere; }
|
||||
.resources-panel__header p { margin: 0; overflow-wrap: anywhere; }
|
||||
.resources-list { display: grid; min-width: 0; gap: 10px; margin-top: 18px; }
|
||||
.resource-row { display:flex; min-width:0; justify-content:space-between; gap:16px; align-items:center; padding:12px; border:1px solid var(--border); border-radius:10px; }
|
||||
.resource-row > :first-child { min-width: 0; }
|
||||
.resource-row a { display: block; max-width: 100%; font-weight:700; overflow-wrap:anywhere; word-break:break-word; }
|
||||
.resource-row small { display:block; max-width:100%; margin-top:4px; color:var(--muted-2); overflow-wrap:anywhere; }
|
||||
.resource-actions { display:flex; flex:0 0 auto; gap:8px; flex-wrap:wrap; }
|
||||
.resource-actions button { padding:7px 10px; }
|
||||
@media (max-width:640px){.resource-row{align-items:flex-start;flex-direction:column}.resource-actions{width:100%}}
|
||||
|
||||
.resource-main { display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; }
|
||||
.resource-inline { width: 100%; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
|
||||
.resource-password-form label { display: grid; gap: 6px; font-size: .8rem; }
|
||||
.resource-password-form input { width: 100%; }
|
||||
.resource-inline-help { margin: 6px 0 0; color: var(--muted); font-size: .75rem; }
|
||||
.resource-inline-message { margin: 8px 0 0; }
|
||||
.resource-inline-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
|
||||
.resource-delete-confirm > p:first-child { margin: 0; }
|
||||
@media (max-width: 640px) { .resource-main { align-items: flex-start; flex-direction: column; } .resource-actions { width: 100%; } }
|
||||
.resource-row { align-items: stretch; flex-direction: column; }
|
||||
|
||||
+13
-1
@@ -67,6 +67,7 @@
|
||||
</div>
|
||||
<div id="footer-account-user" class="home-footer__account" hidden>
|
||||
<span id="footer-user-label" class="home-footer__user"></span>
|
||||
<button id="footer-resources" class="footer-action footer-action--primary" type="button">My notes</button>
|
||||
<button id="footer-logout" class="footer-action" type="button">Log out</button>
|
||||
</div>
|
||||
<span class="home-footer__author">Author: <a href="https://www.linuxiarz.pl" rel="author noopener">@linuxiarz.pl</a></span>
|
||||
@@ -81,7 +82,7 @@
|
||||
<p id="identity-copy" class="dialog-copy"></p>
|
||||
</header>
|
||||
<div class="identity-fields">
|
||||
<label>Nickname<input id="nickname" maxlength="40" autocomplete="off" data-bwignore="true" placeholder="Your nickname"></label>
|
||||
<label>Nickname<input id="nickname" name="nickname" maxlength="40" autocomplete="nickname" placeholder="Your nickname"></label>
|
||||
<label id="auth-email-field">E-mail<input id="auth-email" name="username" type="email" maxlength="320" autocomplete="username" required placeholder="you@example.com"></label>
|
||||
<label>Password<input id="auth-password" name="password" type="password" minlength="8" maxlength="128" autocomplete="current-password" required placeholder="At least 8 characters"></label>
|
||||
</div>
|
||||
@@ -94,5 +95,16 @@
|
||||
<p id="identity-error" class="form-message" role="status"></p>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="resources-dialog" class="app-dialog">
|
||||
<div class="dialog-panel resources-panel">
|
||||
<button id="close-resources" class="modal-close" type="button" aria-label="Close dialog">×</button>
|
||||
<header class="resources-panel__header">
|
||||
<h2>My notes and workspaces</h2>
|
||||
<p class="dialog-copy">Items created while signed in are assigned to your account.</p>
|
||||
</header>
|
||||
<div id="resources-list" class="resources-list"></div>
|
||||
<p id="resources-error" class="form-message error" role="alert"></p>
|
||||
</div>
|
||||
</dialog>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -49,11 +49,14 @@ export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }
|
||||
switchMode.textContent = registering ? "Already registered? Log in" : "Create an account";
|
||||
resetButton.hidden = resetting || registering;
|
||||
backButton.hidden = !resetting;
|
||||
form.autocomplete = resetting ? "off" : "on";
|
||||
nickname.autocomplete = "nickname";
|
||||
email.autocomplete = "username";
|
||||
password.autocomplete = registering ? "new-password" : "current-password";
|
||||
message.textContent = "";
|
||||
|
||||
queueMicrotask(() => {
|
||||
email.focus();
|
||||
(registering ? nickname : email).focus();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+76
-2
@@ -2,6 +2,7 @@ import { installGlobalDiagnostics, logInfo } from "./logger.js";
|
||||
installGlobalDiagnostics();
|
||||
|
||||
import { bindIdentityDialog, handleResetToken, logoutCurrentSession, validateCurrentSession } from "./auth-ui.js";
|
||||
import { getAuthToken } from "@rustpad/session";
|
||||
import { api } from "@rustpad/api";
|
||||
|
||||
function slugify(value, fallback) {
|
||||
@@ -48,7 +49,7 @@ document.querySelector("#pad-form").addEventListener("submit", async (event) =>
|
||||
try {
|
||||
const payload = { name: name.value.trim() };
|
||||
if (password.value) payload.password = password.value;
|
||||
const result = await api("/api/pads", { method: "POST", body: JSON.stringify(payload) });
|
||||
const result = await api("/api/pads", { method: "POST", headers: authHeaders(), body: JSON.stringify(payload) });
|
||||
if (password.value) sessionStorage.setItem(`rustpad:pad:${result.slug}:password`, password.value);
|
||||
window.location.assign(`${result.url}?view=split&mode=markdown`);
|
||||
} catch (requestError) {
|
||||
@@ -69,7 +70,7 @@ document.querySelector("#workspace-form").addEventListener("submit", async (even
|
||||
try {
|
||||
const payload = { name: name.value.trim() };
|
||||
if (password.value) payload.password = password.value;
|
||||
const result = await api("/api/workspaces", { method: "POST", body: JSON.stringify(payload) });
|
||||
const result = await api("/api/workspaces", { method: "POST", headers: authHeaders(), body: JSON.stringify(payload) });
|
||||
if (password.value) sessionStorage.setItem(`rustpad:workspace:${result.slug}:password`, password.value);
|
||||
window.location.assign(result.url);
|
||||
} catch (requestError) {
|
||||
@@ -87,6 +88,76 @@ const userAccount = document.querySelector("#footer-account-user");
|
||||
const userLabel = document.querySelector("#footer-user-label");
|
||||
const registerLink = document.querySelector("#footer-register");
|
||||
const registrationEnabled = document.body.dataset.registrationEnabled === "true";
|
||||
const resourcesDialog = document.querySelector("#resources-dialog");
|
||||
const resourcesList = document.querySelector("#resources-list");
|
||||
const resourcesError = document.querySelector("#resources-error");
|
||||
|
||||
function authHeaders() { const token = getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
||||
async function loadResources() {
|
||||
resourcesError.textContent = ""; resourcesList.innerHTML = "<p>Loading…</p>";
|
||||
try {
|
||||
const data = await api("/api/auth/resources", { headers: authHeaders() });
|
||||
const items = [...data.workspaces.map(item => ({...item, kind:"workspace", url:`/w/${item.slug}`})), ...data.pads.map(item => ({...item, kind:"pad", url:`/p/${item.slug}`}))];
|
||||
resourcesList.innerHTML = items.length ? "" : "<p>No assigned items yet.</p>";
|
||||
for (const item of items) {
|
||||
const row = document.createElement("article");
|
||||
row.className = "resource-row";
|
||||
row.innerHTML = `<div class="resource-main"><div><a href="${item.url}">${item.title}</a><small>${item.kind === "workspace" ? "Workspace" : "Note"}${item.protected ? " · password protected" : ""}</small></div><div class="resource-actions"><button type="button" data-password>Change password</button><button type="button" data-delete>Delete</button></div></div><div class="resource-inline" data-inline hidden></div>`;
|
||||
|
||||
const inline = row.querySelector("[data-inline]");
|
||||
const closeInline = () => { inline.hidden = true; inline.innerHTML = ""; };
|
||||
const setInlineMessage = (text, type = "") => {
|
||||
const message = inline.querySelector("[data-inline-message]");
|
||||
if (!message) return;
|
||||
message.className = `form-message resource-inline-message ${type}`.trim();
|
||||
message.textContent = text;
|
||||
};
|
||||
|
||||
row.querySelector("[data-password]").addEventListener("click", () => {
|
||||
inline.hidden = false;
|
||||
inline.innerHTML = `<form class="resource-password-form" autocomplete="off"><label>New password<input name="resource-password" type="password" minlength="8" maxlength="128" autocomplete="new-password" data-bwignore="true" placeholder="Minimum 8 characters"></label><p class="resource-inline-help">Leave empty to remove password protection.</p><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="primary-button" type="submit">Save</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></form>`;
|
||||
const form = inline.querySelector("form");
|
||||
const input = form.querySelector("input");
|
||||
form.querySelector("[data-cancel]").addEventListener("click", closeInline);
|
||||
form.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const password = input.value;
|
||||
if (password && password.length < 8) { setInlineMessage("Password must contain at least 8 characters.", "error"); return; }
|
||||
const submit = form.querySelector('[type="submit"]');
|
||||
submit.disabled = true;
|
||||
setInlineMessage("");
|
||||
try {
|
||||
await api("/api/auth/resources", { method:"PUT", headers:authHeaders(), body:JSON.stringify({kind:item.kind, slug:item.slug, password}) });
|
||||
await loadResources();
|
||||
} catch(e) {
|
||||
setInlineMessage(e.message, "error");
|
||||
submit.disabled = false;
|
||||
}
|
||||
});
|
||||
input.focus();
|
||||
});
|
||||
|
||||
row.querySelector("[data-delete]").addEventListener("click", () => {
|
||||
inline.hidden = false;
|
||||
inline.innerHTML = `<div class="resource-delete-confirm"><p>Delete “${item.title}” permanently?</p><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="danger-button" type="button" data-confirm-delete>Delete</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></div>`;
|
||||
inline.querySelector("[data-cancel]").addEventListener("click", closeInline);
|
||||
inline.querySelector("[data-confirm-delete]").addEventListener("click", async event => {
|
||||
event.currentTarget.disabled = true;
|
||||
setInlineMessage("");
|
||||
try {
|
||||
await api("/api/auth/resources", { method:"DELETE", headers:authHeaders(), body:JSON.stringify({kind:item.kind, slug:item.slug}) });
|
||||
await loadResources();
|
||||
} catch(e) {
|
||||
setInlineMessage(e.message, "error");
|
||||
event.currentTarget.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
resourcesList.append(row);
|
||||
}
|
||||
} catch(e) { resourcesList.innerHTML=""; resourcesError.textContent=e.message; }
|
||||
}
|
||||
|
||||
|
||||
function renderAccount(session) {
|
||||
guestAccount.hidden = Boolean(session);
|
||||
@@ -109,6 +180,9 @@ if (identityDialog) {
|
||||
authDialog.setMode("register");
|
||||
identityDialog.showModal();
|
||||
});
|
||||
document.querySelector("#footer-resources")?.addEventListener("click", async () => { resourcesDialog.showModal(); await loadResources(); });
|
||||
document.querySelector("#close-resources")?.addEventListener("click", () => resourcesDialog.close());
|
||||
resourcesDialog?.addEventListener("click", (event) => { if (event.target === resourcesDialog) resourcesDialog.close(); });
|
||||
document.querySelector("#footer-logout")?.addEventListener("click", async () => {
|
||||
await logoutCurrentSession();
|
||||
renderAccount(null);
|
||||
|
||||
Reference in New Issue
Block a user