share funxtions
This commit is contained in:
@@ -615,3 +615,92 @@ dialog::backdrop { background: rgba(4,6,9,.82); }
|
||||
.room-user > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.chat-message a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }
|
||||
.chat-message a:hover { filter: brightness(1.15); }
|
||||
|
||||
/* Sharing panel */
|
||||
.share-panel { display: grid; gap: 18px; }
|
||||
.share-panel-head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }
|
||||
.share-panel-head h3, .share-section h4 { margin:0; }
|
||||
.share-panel-head p { margin:4px 0 0; color:var(--muted); }
|
||||
.share-section { display:grid; gap:10px; padding:14px; border:1px solid var(--border); border-radius:10px; background:var(--surface); }
|
||||
.share-form-grid { display:grid; grid-template-columns:minmax(220px,1fr) minmax(140px,auto) auto; gap:10px; align-items:end; }
|
||||
.share-form-grid label { display:grid; gap:5px; }
|
||||
.share-link-form { grid-template-columns:minmax(140px,auto) minmax(110px,140px) auto auto; }
|
||||
.share-forever { display:flex !important; align-items:center; gap:7px !important; min-height:38px; white-space:nowrap; }
|
||||
.share-forever input { width:auto; }
|
||||
.share-list { display:grid; gap:8px; }
|
||||
.share-list-row { display:grid; grid-template-columns:minmax(180px,1fr) auto auto; gap:8px; align-items:center; padding:9px 0; border-top:1px solid var(--border); }
|
||||
.share-list-row:first-child { border-top:0; }
|
||||
.share-list-row strong, .share-list-row small { display:block; overflow-wrap:anywhere; }
|
||||
.share-list-row small { color:var(--muted); margin-top:2px; }
|
||||
.share-role { color:var(--muted); font-size:.82rem; }
|
||||
.share-link-row { grid-template-columns:minmax(170px,1fr) minmax(130px,auto) 100px auto auto auto; }
|
||||
.share-link-row input, .share-link-row select { min-width:0; }
|
||||
.share-empty { margin:0; color:var(--muted); }
|
||||
@media (max-width:800px) {
|
||||
.share-form-grid, .share-link-form, .share-list-row, .share-link-row { grid-template-columns:1fr; align-items:stretch; }
|
||||
.share-panel-head { flex-direction:column; }
|
||||
.share-panel-head > button { align-self:flex-end; }
|
||||
}
|
||||
|
||||
/* Share dialog */
|
||||
.share-dialog { width: min(900px, calc(100% - 28px)); max-width: 900px; max-height: min(88dvh, 860px); padding: 0; overflow: hidden; }
|
||||
.share-dialog::backdrop { background: rgb(0 0 0 / .68); backdrop-filter: blur(3px); }
|
||||
.share-dialog .share-panel { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; max-height: min(88dvh, 860px); gap: 0; }
|
||||
.share-dialog .share-panel-head { padding: 20px 22px 16px; border-bottom: 1px solid var(--border); background: var(--surface); }
|
||||
.share-dialog .share-panel-head strong { color: var(--text); }
|
||||
.share-dialog-body { display: grid; gap: 16px; padding: 18px 22px 22px; overflow-y: auto; background: color-mix(in srgb, var(--surface) 92%, black); }
|
||||
.share-dialog-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 64px; padding: 12px 22px; border-top: 1px solid var(--border); background: var(--surface); }
|
||||
.share-dialog-footer .form-message { margin: 0; }
|
||||
.dialog-close { width: 34px; height: 34px; flex: 0 0 auto; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); color: var(--muted); font-size: 1.35rem; line-height: 1; cursor: pointer; }
|
||||
.dialog-close:hover { color: var(--text); border-color: var(--border-strong); }
|
||||
.share-section { padding: 16px; background: var(--surface); }
|
||||
.share-section-head h4 { margin: 0; }
|
||||
.share-section-head p { margin: 4px 0 2px; color: var(--muted); font-size: .82rem; }
|
||||
.share-form-grid label > span:not(.sr-only), .share-link-row label > span:not(.sr-only) { color: var(--muted); font-size: .76rem; }
|
||||
.share-dialog select { width: 100%; min-height: 42px; padding: 0 38px 0 12px; border: 1px solid var(--border-strong); border-radius: 8px; background-color: #10141a; color: var(--text); appearance: none; background-image: linear-gradient(45deg, transparent 50%, #9aa3b2 50%), linear-gradient(135deg, #9aa3b2 50%, transparent 50%); background-position: calc(100% - 16px) 17px, calc(100% - 11px) 17px; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; cursor: pointer; }
|
||||
.share-dialog select:hover { border-color: #596273; }
|
||||
.share-dialog select:focus { border-color: #7567db; box-shadow: 0 0 0 3px rgb(117 103 219 / .18); outline: none; }
|
||||
.share-dialog input[type="text"], .share-dialog input[type="number"] { min-height: 42px; border-radius: 8px; padding: 0 12px; }
|
||||
.share-hours-field { display: grid; grid-template-columns: minmax(70px, 1fr) auto; align-items: center; min-height: 42px; border: 1px solid var(--border-strong); border-radius: 8px; background: #10141a; overflow: hidden; }
|
||||
.share-hours-field:focus-within { border-color: #7567db; box-shadow: 0 0 0 3px rgb(117 103 219 / .18); }
|
||||
.share-hours-field input { width: 100%; min-width: 0; border: 0 !important; background: transparent; box-shadow: none !important; outline: 0 !important; }
|
||||
.share-hours-field span { padding: 0 11px; color: var(--muted); font-size: .76rem; }
|
||||
.share-list-row { min-height: 58px; padding: 10px 0; }
|
||||
.share-list-identity { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
||||
.share-avatar { display: grid; place-items: center; width: 34px; height: 34px; flex: 0 0 auto; border-radius: 50%; background: color-mix(in srgb, var(--accent) 20%, var(--surface-2)); color: #d8d3ff; font-weight: 700; }
|
||||
.share-list-identity > div, .share-link-info { min-width: 0; }
|
||||
.share-row-actions { display: flex; gap: 7px; justify-content: flex-end; }
|
||||
.compact-button { min-height: 36px; padding: 7px 11px; }
|
||||
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
||||
@media (max-width: 800px) {
|
||||
.share-dialog { width: calc(100% - 16px); max-height: calc(100dvh - 16px); }
|
||||
.share-dialog .share-panel { max-height: calc(100dvh - 16px); }
|
||||
.share-dialog .share-panel-head, .share-dialog-body, .share-dialog-footer { padding-left: 14px; padding-right: 14px; }
|
||||
.share-dialog-footer { align-items: stretch; flex-direction: column; }
|
||||
.share-dialog-footer > button { align-self: flex-end; }
|
||||
.share-row-actions { justify-content: stretch; }
|
||||
.share-row-actions button { flex: 1; }
|
||||
}
|
||||
|
||||
/* Refined sharing dialog and shared-resource attribution. */
|
||||
.share-dialog {
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 18px;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 28px 90px rgb(0 0 0 / .58);
|
||||
}
|
||||
.share-dialog .share-panel { overflow: hidden; border-radius: inherit; }
|
||||
.share-dialog .share-panel-head { border-radius: 18px 18px 0 0; }
|
||||
.share-dialog-footer { border-radius: 0 0 18px 18px; }
|
||||
.share-dialog-body { scrollbar-gutter: stable; }
|
||||
.share-section { border-radius: 12px; box-shadow: inset 0 1px 0 rgb(255 255 255 / .025); }
|
||||
.resource-copy { min-width: 0; }
|
||||
.resource-title-line { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-width: 0; }
|
||||
.resource-title-line a { min-width: 0; }
|
||||
.resource-shared-badge { display: inline-flex; align-items: center; min-height: 22px; padding: 3px 8px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 999px; background: color-mix(in srgb, var(--accent) 12%, transparent); color: color-mix(in srgb, var(--accent) 70%, white); font-size: .68rem; font-weight: 650; line-height: 1.2; }
|
||||
.resource-row--shared { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); background: color-mix(in srgb, var(--accent) 4%, transparent); }
|
||||
@media (max-width: 640px) {
|
||||
.share-dialog, .share-dialog .share-panel, .share-dialog .share-panel-head, .share-dialog-footer { border-radius: 14px; }
|
||||
.share-dialog .share-panel-head { border-radius: 14px 14px 0 0; }
|
||||
.share-dialog-footer { border-radius: 0 0 14px 14px; }
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export async function api(path, options = {}) {
|
||||
if (!response.ok) {
|
||||
const defaults = { 400: "Invalid request.", 401: "Authentication required.", 403: "Access denied.", 404: "The requested resource was not found.", 405: "This operation is not allowed.", 409: "The requested change conflicts with existing data.", 413: "The uploaded data is too large.", 429: "Too many requests. Try again later.", 500: "Server error. Try again later.", 503: "Service temporarily unavailable." };
|
||||
const requestError = new Error(data.error || defaults[response.status] || `Request failed (${response.status}).`);
|
||||
requestError.status = response.status;
|
||||
logWarn("api.failed", { method: options.method || "GET", path, status: response.status, message: requestError.message });
|
||||
throw requestError;
|
||||
}
|
||||
|
||||
+67
-3
@@ -94,6 +94,9 @@ const resourcesList = document.querySelector("#resources-list");
|
||||
const resourcesError = document.querySelector("#resources-error");
|
||||
|
||||
function authHeaders() { const token = getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
||||
function escapeHtml(value) { const node=document.createElement("div"); node.textContent=String(value ?? ""); return node.innerHTML; }
|
||||
function shareExpiry(hours, forever) { if (forever) return null; const value=Number(hours); if (!Number.isFinite(value) || value <= 0 || value > 87600) throw new Error("Enter a validity between 1 and 87600 hours."); return new Date(Date.now()+value*3600000).toISOString(); }
|
||||
function formatShareExpiry(value) { if (!value) return "Never expires"; const date=new Date(value); return Number.isNaN(date.getTime()) ? value : `Expires ${date.toLocaleString()}`; }
|
||||
async function loadResources() {
|
||||
resourcesError.textContent = ""; resourcesList.innerHTML = "<p>Loading…</p>";
|
||||
try {
|
||||
@@ -103,7 +106,10 @@ async function loadResources() {
|
||||
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 sharedLabel = !item.owned ? `<span class="resource-shared-badge">Shared by ${escapeHtml(item.shared_by || "another user")}</span>` : "";
|
||||
const permissionLabel = item.permission === "rw" ? "Read and write" : "Read only";
|
||||
row.classList.toggle("resource-row--shared", !Boolean(item.owned));
|
||||
row.innerHTML = `<div class="resource-main"><div class="resource-copy"><div class="resource-title-line"><a href="${item.url}">${escapeHtml(item.title)}</a>${sharedLabel}</div><small>${item.kind === "workspace" ? "Workspace" : "Note"}${item.private ? " · private" : ""}${!item.owned ? ` · ${permissionLabel}` : item.protected ? " · password protected" : ""}</small></div><div class="resource-actions">${item.owned ? `<button type="button" data-privacy>${item.private ? "Make public" : "Make private"}</button><button type="button" data-share>Share</button><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 = ""; };
|
||||
@@ -114,7 +120,65 @@ async function loadResources() {
|
||||
message.textContent = text;
|
||||
};
|
||||
|
||||
row.querySelector("[data-password]").addEventListener("click", () => {
|
||||
row.querySelector("[data-privacy]")?.addEventListener("click", async () => {
|
||||
try { await api("/api/auth/resources/privacy", {method:"POST",headers:authHeaders(),body:JSON.stringify({kind:item.kind,slug:item.slug,private:!Boolean(item.private)})}); await loadResources(); }
|
||||
catch(e){ resourcesError.textContent=e.message; }
|
||||
});
|
||||
row.querySelector("[data-share]")?.addEventListener("click", async () => {
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.className = "app-dialog share-dialog";
|
||||
dialog.innerHTML = `<div class="share-panel">
|
||||
<div class="share-panel-head"><div><h3>Share access</h3><p>Manage registered users and direct access links for <strong>${escapeHtml(item.title)}</strong>.</p></div><button class="dialog-close" type="button" data-cancel aria-label="Close share dialog">×</button></div>
|
||||
<div class="share-dialog-body">
|
||||
<section class="share-section"><div class="share-section-head"><div><h4>People with access</h4><p>Invite existing Rustpad users by e-mail.</p></div></div><form data-user-share-form class="share-form-grid">
|
||||
<label class="share-emails"><span>E-mail addresses</span><input name="emails" type="text" placeholder="alice@example.com, bob@example.com" autocomplete="off"></label>
|
||||
<label><span>Permission</span><select name="permission"><option value="ro">Read only</option><option value="rw">Read and write</option></select></label>
|
||||
<button class="primary-button" type="submit">Grant access</button>
|
||||
</form><div data-user-list class="share-list"></div></section>
|
||||
<section class="share-section"><div class="share-section-head"><div><h4>Direct links</h4><p>Create links for people without an account or manage existing links.</p></div></div><form data-link-form class="share-form-grid share-link-form">
|
||||
<label><span>Permission</span><select name="permission"><option value="ro">Read only</option><option value="rw">Read and write</option></select></label>
|
||||
<label><span>Valid for</span><div class="share-hours-field"><input name="hours" type="number" min="1" max="87600" value="24" inputmode="numeric"><span>hours</span></div></label>
|
||||
<label class="share-forever"><input name="forever" type="checkbox"><span>Never expires</span></label>
|
||||
<button class="primary-button" type="submit">Create link</button>
|
||||
</form><div data-link-list class="share-list"></div></section>
|
||||
</div>
|
||||
<div class="share-dialog-footer"><p class="form-message resource-inline-message" data-inline-message role="status"></p><button class="secondary-button" type="button" data-done>Done</button></div>
|
||||
</div>`;
|
||||
document.body.append(dialog);
|
||||
const closeDialog=()=>dialog.close();
|
||||
dialog.addEventListener("close",()=>dialog.remove(),{once:true});
|
||||
dialog.addEventListener("click",event=>{if(event.target===dialog) closeDialog();});
|
||||
dialog.querySelector("[data-cancel]").addEventListener("click",closeDialog);
|
||||
dialog.querySelector("[data-done]").addEventListener("click",closeDialog);
|
||||
const setDialogMessage = (text, type = "") => {
|
||||
const message = dialog.querySelector("[data-inline-message]");
|
||||
message.className = `form-message resource-inline-message ${type}`.trim();
|
||||
message.textContent = text;
|
||||
};
|
||||
const userForm=dialog.querySelector("[data-user-share-form]");
|
||||
const linkForm=dialog.querySelector("[data-link-form]");
|
||||
const userList=dialog.querySelector("[data-user-list]");
|
||||
const linkList=dialog.querySelector("[data-link-list]");
|
||||
const syncForever=()=>{linkForm.hours.disabled=linkForm.forever.checked;};
|
||||
linkForm.forever.addEventListener("change",syncForever); syncForever();
|
||||
const refresh=async()=>{
|
||||
const d=await api(`/api/auth/resources/sharing?kind=${encodeURIComponent(item.kind)}&slug=${encodeURIComponent(item.slug)}`,{headers:authHeaders()});
|
||||
userList.innerHTML=d.users.length?d.users.map(u=>`<div class="share-list-row"><div class="share-list-identity"><span class="share-avatar">${escapeHtml((u.nickname || u.email || "?").slice(0,1).toUpperCase())}</span><div><strong>${escapeHtml(u.nickname)}</strong><small>${escapeHtml(u.email)}</small></div></div><span class="share-role">${u.permission === "rw" ? "Read and write" : "Read only"}</span><button class="secondary-button compact-button" type="button" data-remove-user="${escapeHtml(u.email)}">Remove</button></div>`).join(""):'<p class="share-empty">No users have access.</p>';
|
||||
linkList.innerHTML=d.links.length?d.links.map(link=>`<form class="share-list-row share-link-row" data-link-token="${escapeHtml(link.token)}"><div class="share-link-info"><strong>Link ${escapeHtml(link.token.slice(0,8))}…</strong><small>${escapeHtml(formatShareExpiry(link.expires_at))}</small></div><label><span class="sr-only">Permission</span><select name="permission" aria-label="Link permission"><option value="ro" ${link.permission==="ro"?"selected":""}>Read only</option><option value="rw" ${link.permission==="rw"?"selected":""}>Read and write</option></select></label><label><span class="sr-only">Validity in hours</span><div class="share-hours-field"><input name="hours" type="number" min="1" max="87600" value="24" aria-label="New validity in hours"><span>h</span></div></label><label class="share-forever"><input name="forever" type="checkbox" ${link.expires_at?"":"checked"}><span>Never</span></label><div class="share-row-actions"><button class="secondary-button compact-button" type="submit">Update</button><button class="danger-button compact-button" type="button" data-revoke-link>Revoke</button></div></form>`).join(""):'<p class="share-empty">No active links.</p>';
|
||||
userList.querySelectorAll("[data-remove-user]").forEach(button=>button.addEventListener("click",async()=>{try{button.disabled=true;await api("/api/auth/resources/sharing",{method:"DELETE",headers:authHeaders(),body:JSON.stringify({kind:item.kind,slug:item.slug,email:button.dataset.removeUser})});setDialogMessage("Access removed.","success");await refresh();}catch(err){setDialogMessage(err.message,"error");button.disabled=false;}}));
|
||||
linkList.querySelectorAll("[data-link-token]").forEach(linkRow=>{
|
||||
const forever=linkRow.elements.forever, hours=linkRow.elements.hours; const sync=()=>{hours.disabled=forever.checked;}; forever.addEventListener("change",sync); sync();
|
||||
linkRow.addEventListener("submit",async event=>{event.preventDefault();try{const expires_at=shareExpiry(hours.value,forever.checked);await api("/api/auth/resources/share-links",{method:"PUT",headers:authHeaders(),body:JSON.stringify({kind:item.kind,slug:item.slug,token:linkRow.dataset.linkToken,permission:linkRow.elements.permission.value,expires_at})});setDialogMessage("Link updated.","success");await refresh();}catch(err){setDialogMessage(err.message,"error");}});
|
||||
linkRow.querySelector("[data-revoke-link]").addEventListener("click",async()=>{try{await api("/api/auth/resources/share-links",{method:"DELETE",headers:authHeaders(),body:JSON.stringify({kind:item.kind,slug:item.slug,token:linkRow.dataset.linkToken})});setDialogMessage("Link revoked.","success");await refresh();}catch(err){setDialogMessage(err.message,"error");}});
|
||||
});
|
||||
};
|
||||
userForm.addEventListener("submit",async event=>{event.preventDefault();try{await api("/api/auth/resources/sharing",{method:"POST",headers:authHeaders(),body:JSON.stringify({kind:item.kind,slug:item.slug,emails:userForm.emails.value,permission:userForm.permission.value})});userForm.emails.value="";setDialogMessage("Access granted.","success");await refresh();}catch(err){setDialogMessage(err.message,"error");}});
|
||||
linkForm.addEventListener("submit",async event=>{event.preventDefault();try{const expires_at=shareExpiry(linkForm.hours.value,linkForm.forever.checked);const result=await api("/api/auth/resources/share-links",{method:"POST",headers:authHeaders(),body:JSON.stringify({kind:item.kind,slug:item.slug,permission:linkForm.permission.value,expires_at})});const absolute=new URL(result.url,location.origin).href;await navigator.clipboard.writeText(absolute);setDialogMessage("Link created and copied.","success");await refresh();}catch(err){setDialogMessage(err.message,"error");}});
|
||||
dialog.showModal();
|
||||
try { await refresh(); } catch(err) { setDialogMessage(err.message,"error"); }
|
||||
});
|
||||
|
||||
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");
|
||||
@@ -138,7 +202,7 @@ async function loadResources() {
|
||||
input.focus();
|
||||
});
|
||||
|
||||
row.querySelector("[data-delete]").addEventListener("click", () => {
|
||||
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);
|
||||
|
||||
+2
-1
@@ -18,7 +18,8 @@ const modeToggle=document.querySelector("#mode-toggle"), passwordDialog=document
|
||||
const roomDetails=document.querySelector("#room-details"), roomUsers=document.querySelector("#room-users"), roomCount=document.querySelector("#room-count"), socketLatency=document.querySelector("#socket-latency"), chatMessages=document.querySelector("#chat-messages"), chatForm=document.querySelector("#chat-form"), chatInput=document.querySelector("#chat-input"), chatUnread=document.querySelector("#chat-unread");
|
||||
let unreadChat=0;
|
||||
const compactToggle=document.querySelector("#compact-toggle"), publicTaskUpdates=document.querySelector("#public-task-updates"), fontFamily=document.querySelector("#font-family"), fontSize=document.querySelector("#font-size"), currentUser=document.querySelector("#current-user"), userColorPicker=document.querySelector("#user-color-picker");
|
||||
let accessToken=getAccessToken("workspace",workspaceSlug), password="", nickname=getNickname(), info, socket, saveTimer, applyingRemote=false, uiState=readEditorState(), owners=[];
|
||||
const shareToken=new URLSearchParams(location.search).get("share");if(shareToken)setAccessToken("workspace",workspaceSlug,shareToken);
|
||||
let accessToken=shareToken||getAuthToken()||getAccessToken("workspace",workspaceSlug), password="", nickname=getNickname(), info, socket, saveTimer, applyingRemote=false, uiState=readEditorState(), owners=[];
|
||||
const lineToggle=document.querySelector("#line-numbers-toggle"); lineToggle.checked=localStorage.getItem("rustpad:line-numbers")!=="off";
|
||||
compactToggle.checked=localStorage.getItem("rustpad:compact")!=="off";
|
||||
fontFamily.value=localStorage.getItem("rustpad:font-family")||"mono";
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ const modeToggle=document.querySelector("#mode-toggle"), passwordDialog=document
|
||||
const roomDetails=document.querySelector("#room-details"), roomUsers=document.querySelector("#room-users"), roomCount=document.querySelector("#room-count"), socketLatency=document.querySelector("#socket-latency"), chatMessages=document.querySelector("#chat-messages"), chatForm=document.querySelector("#chat-form"), chatInput=document.querySelector("#chat-input"), chatUnread=document.querySelector("#chat-unread");
|
||||
let unreadChat=0;
|
||||
const compactToggle=document.querySelector("#compact-toggle"), publicTaskUpdates=document.querySelector("#public-task-updates"), fontFamily=document.querySelector("#font-family"), fontSize=document.querySelector("#font-size"), currentUser=document.querySelector("#current-user"), userColorPicker=document.querySelector("#user-color-picker");
|
||||
let accessToken=getAccessToken("pad",slug), password="", nickname=getNickname(), info, socket, saveTimer, applyingRemote=false, uiState=readEditorState(), owners=[];
|
||||
const shareToken=new URLSearchParams(location.search).get("share");if(shareToken)setAccessToken("pad",slug,shareToken);
|
||||
let accessToken=shareToken||getAuthToken()||getAccessToken("pad",slug), password="", nickname=getNickname(), info, socket, saveTimer, applyingRemote=false, uiState=readEditorState(), owners=[];
|
||||
const lineToggle=document.querySelector("#line-numbers-toggle"); lineToggle.checked=localStorage.getItem("rustpad:line-numbers")!=="off";
|
||||
compactToggle.checked=localStorage.getItem("rustpad:compact")!=="off";
|
||||
fontFamily.value=localStorage.getItem("rustpad:font-family")||"mono";
|
||||
|
||||
+10
-3
@@ -3,13 +3,15 @@ installGlobalDiagnostics();
|
||||
|
||||
import { api } from "@rustpad/api";
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { getNickname, getAccessToken, setAccessToken } from "@rustpad/session";
|
||||
import { getNickname, getAccessToken, getAuthToken, setAccessToken } from "@rustpad/session";
|
||||
import { askConfirm } from "./modal.js";
|
||||
|
||||
const parts = location.pathname.split("/").filter(Boolean);
|
||||
const slug = parts[1];
|
||||
let info;
|
||||
let accessToken = getAccessToken("workspace", slug);
|
||||
const shareToken=new URLSearchParams(location.search).get("share");
|
||||
let accessToken=shareToken||getAuthToken()||getAccessToken("workspace",slug);
|
||||
if(shareToken)setAccessToken("workspace",slug,shareToken);
|
||||
const dialog = document.querySelector("#password-dialog");
|
||||
const notesList = document.querySelector("#notes-list");
|
||||
const notesViewKey = `rustpad:workspace:${slug}:notes-view`;
|
||||
@@ -89,6 +91,8 @@ async function openWorkspace() {
|
||||
if (info?.protected || e.message.toLowerCase().includes("password")) {
|
||||
document.querySelector("#password-error").textContent = e.message;
|
||||
if (!dialog.open) dialog.showModal();
|
||||
} else if (e.status === 403) {
|
||||
location.replace("/errors/private-workspace");
|
||||
} else document.querySelector("#workspace-error").textContent = e.message;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +102,10 @@ async function init() {
|
||||
document.querySelector("#workspace-title").textContent = info.title;
|
||||
document.querySelector("#workspace-url").textContent = location.pathname;
|
||||
if (info.protected && !accessToken) dialog.showModal(); else openWorkspace();
|
||||
} catch (e) { document.querySelector("#workspace-error").textContent = e.message; }
|
||||
} catch (e) {
|
||||
if (e.status === 403) location.replace("/errors/private-workspace");
|
||||
else document.querySelector("#workspace-error").textContent = e.message;
|
||||
}
|
||||
}
|
||||
document.querySelector("#password-form").addEventListener("submit", async e => {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user