user spaces
This commit is contained in:
@@ -273,6 +273,23 @@ body {
|
||||
.note-meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||||
.note-tags { color: var(--primary); font-size: 12px; margin-top: 4px; }
|
||||
|
||||
.note-common { border-left: 3px solid var(--secondary); }
|
||||
.common-badge {
|
||||
display: inline-block;
|
||||
background: color-mix(in srgb, var(--secondary) 18%, transparent);
|
||||
color: var(--secondary);
|
||||
border-radius: 10px;
|
||||
padding: 0 7px;
|
||||
font-size: 11px;
|
||||
margin-right: 2px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
.common-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); cursor: pointer; }
|
||||
.common-toggle input { accent-color: var(--secondary); cursor: pointer; }
|
||||
|
||||
.tag-cloud { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.tag-chip {
|
||||
background: color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
|
||||
@@ -6,7 +6,7 @@ const root = document.getElementById("root");
|
||||
const turndownService = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
||||
marked.setOptions({ gfm: true, breaks: false });
|
||||
|
||||
const state = { user: null, local: true, authed: true, editor: null, noteId: null, view: "home", space: null, userNames: null };
|
||||
const state = { user: null, local: true, authed: true, editor: null, noteId: null, view: "home", space: null, userNames: null, commonSpace: "common", isCommon: false };
|
||||
|
||||
const THEMES = [
|
||||
{ id: "light", label: "Light", dark: false, dots: ["#f5f7fb", "#ffffff", "#3b82f6", "#8b5cf6"] },
|
||||
@@ -157,6 +157,7 @@ async function boot() {
|
||||
try {
|
||||
const me = await api("GET", "/api/me");
|
||||
state.user = me.user; state.local = me.local; state.authed = me.authed;
|
||||
state.commonSpace = me.common_space || "common";
|
||||
} catch { return; }
|
||||
if (!state.local && !state.authed) { showLogin(); return; }
|
||||
showHome();
|
||||
@@ -230,7 +231,7 @@ function showHelp() {
|
||||
el("p", { html: 'Use the toolbar to format text with <b>bold</b>, <i>italic</i>, headings, quotes, code, and horizontal rules. The editor also accepts standard Markdown shortcuts as you type.' }),
|
||||
|
||||
el("h2", { text: "Notes & sharing" }),
|
||||
el("p", { html: 'Notes are plain Markdown files. A note lives in a <i>space</i> (a user), chosen with the dropdown at the top of the editor. Changing the space moves the note to that user.' }),
|
||||
el("p", { html: 'Notes are plain Markdown files. A note lives in a <i>space</i> (a user), chosen with the dropdown at the top of the editor. Tick the <b>common</b> checkbox to save it to the shared space everyone can see. Changing the space moves the note to that user.' }),
|
||||
el("p", { html: 'Use <b>Attach</b> to upload a file and insert a link to it. <b>History</b> shows every change, and <b>Delete</b> soft-deletes a note.' }),
|
||||
]),
|
||||
]);
|
||||
@@ -366,6 +367,7 @@ async function populateUsers() {
|
||||
const names = users.map((u) => u.name);
|
||||
state.userNames = names;
|
||||
for (const n of names) {
|
||||
if (n === state.commonSpace) continue;
|
||||
sel.appendChild(el("option", { value: n, text: n }));
|
||||
}
|
||||
sel.value = state.user;
|
||||
@@ -402,9 +404,13 @@ function noteList(notes) {
|
||||
const ul = el("ul", { class: "note-list" });
|
||||
for (const n of notes) {
|
||||
const tags = (n.tags || []).map((t) => `#${t}`).join(" ");
|
||||
ul.appendChild(el("li", { class: "note-item", onclick: () => openNote(n.id) }, [
|
||||
const isCommon = n.username === state.commonSpace;
|
||||
const meta = el("div", { class: "note-meta" });
|
||||
if (isCommon) meta.appendChild(el("span", { class: "common-badge", text: "common" }));
|
||||
meta.appendChild(document.createTextNode(`${n.username} · ${relTime(n.updated_at)}`));
|
||||
ul.appendChild(el("li", { class: isCommon ? "note-item note-common" : "note-item", onclick: () => openNote(n.id) }, [
|
||||
el("div", { class: "note-title", text: n.title || "Untitled" }),
|
||||
el("div", { class: "note-meta", text: `${n.username} · ${relTime(n.updated_at)}` }),
|
||||
meta,
|
||||
el("div", { class: "note-tags", text: tags }),
|
||||
]));
|
||||
}
|
||||
@@ -466,13 +472,21 @@ function renderEditor(body, owner) {
|
||||
const del = state.noteId ? el("button", { class: "btn danger", text: "Delete", onclick: deleteNote }) : null;
|
||||
const hist = state.noteId ? el("button", { class: "btn ghost", text: "History", onclick: showHistory }) : null;
|
||||
|
||||
const realUsers = (state.userNames || [state.user]).filter((n) => n !== state.commonSpace);
|
||||
const spaceSel = el("select", { id: "space-select", class: "user-select", onchange: (e) => { state.space = e.target.value; } });
|
||||
for (const n of (state.userNames || [state.user])) {
|
||||
for (const n of realUsers) {
|
||||
spaceSel.appendChild(el("option", { value: n, text: n }));
|
||||
}
|
||||
const initialSpace = owner || state.space || state.user;
|
||||
spaceSel.value = initialSpace;
|
||||
state.space = initialSpace;
|
||||
state.isCommon = owner === state.commonSpace;
|
||||
state.space = owner && owner !== state.commonSpace ? owner : (state.user || realUsers[0]);
|
||||
spaceSel.value = state.space;
|
||||
|
||||
const commonCb = el("input", { type: "checkbox", id: "common-check" });
|
||||
commonCb.checked = state.isCommon;
|
||||
commonCb.addEventListener("change", () => { state.isCommon = commonCb.checked; });
|
||||
const commonLabel = el("label", { class: "common-toggle", title: "Save to the shared common space" }, [
|
||||
commonCb, el("span", { text: "common" }),
|
||||
]);
|
||||
|
||||
const toolbar = el("div", { class: "toolbar" }, [
|
||||
toolbarBtn("B", "bold"), toolbarBtn("I", "italic"),
|
||||
@@ -494,7 +508,7 @@ function renderEditor(body, owner) {
|
||||
const attach = el("button", { class: "btn", text: "Attach", onclick: () => fileInput.click() });
|
||||
|
||||
const wrap = el("div", { class: "editor-wrap" }, [
|
||||
el("div", { class: "editor-topbar" }, [back, spaceSel, el("div", { class: "spacer" }), attach, hist, del, save]),
|
||||
el("div", { class: "editor-topbar" }, [back, spaceSel, el("div", { class: "spacer" }), attach, hist, del, commonLabel, save]),
|
||||
toolbar,
|
||||
editorArea,
|
||||
hint,
|
||||
@@ -538,13 +552,14 @@ function runToolbar(action) {
|
||||
async function persistNote() {
|
||||
const html = state.editor.getHTML();
|
||||
const body = turndownService.turndown(html);
|
||||
const username = state.isCommon ? state.commonSpace : state.space;
|
||||
const status = document.querySelector(".status");
|
||||
try {
|
||||
let note;
|
||||
if (state.noteId) {
|
||||
note = await api("PUT", "/api/notes/" + state.noteId, { body, username: state.space });
|
||||
note = await api("PUT", "/api/notes/" + state.noteId, { body, username });
|
||||
} else {
|
||||
note = await api("POST", "/api/notes", { body, username: state.space });
|
||||
note = await api("POST", "/api/notes", { body, username });
|
||||
state.noteId = note.id;
|
||||
}
|
||||
state.editor.commands.setContent(marked.parse(note.body));
|
||||
@@ -574,7 +589,7 @@ async function uploadAttachment(input) {
|
||||
if (!file) return;
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
fd.append("username", state.space);
|
||||
fd.append("username", state.isCommon ? state.commonSpace : state.space);
|
||||
const status = document.querySelector(".status");
|
||||
try {
|
||||
const resp = await fetch("/api/attachments", { method: "POST", body: fd });
|
||||
|
||||
Reference in New Issue
Block a user