mirror of
https://github.com/dsec-hub/dsec-notebook.git
synced 2026-09-22 07:24:27 +00:00
change title from Notebook to DSEC Notebook
This commit is contained in:
parent
c135b017fb
commit
80833ad8f4
11 changed files with 227 additions and 132 deletions
|
|
@ -111,7 +111,11 @@
|
|||
passwordError = "";
|
||||
passwordMessage = "";
|
||||
try {
|
||||
await mutation("users:changePassword", { token, currentPassword, newPassword });
|
||||
await mutation("users:changePassword", {
|
||||
token,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
});
|
||||
currentPassword = "";
|
||||
newPassword = "";
|
||||
confirmPassword = "";
|
||||
|
|
@ -131,7 +135,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Account — Notebook</title>
|
||||
<title>Account — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
|
|
@ -202,8 +206,12 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{#if profileError}<p class="text-primary text-sm">{profileError}</p>{/if}
|
||||
{#if profileMessage}<p class="text-secondary text-sm">{profileMessage}</p>{/if}
|
||||
{#if profileError}<p class="text-primary text-sm">
|
||||
{profileError}
|
||||
</p>{/if}
|
||||
{#if profileMessage}<p class="text-secondary text-sm">
|
||||
{profileMessage}
|
||||
</p>{/if}
|
||||
<button type="submit" disabled={profileSaving} class="btn-primary">
|
||||
{profileSaving ? "Saving..." : "Save profile"}
|
||||
</button>
|
||||
|
|
@ -250,8 +258,12 @@
|
|||
required
|
||||
/>
|
||||
</div>
|
||||
{#if passwordError}<p class="text-primary text-sm">{passwordError}</p>{/if}
|
||||
{#if passwordMessage}<p class="text-secondary text-sm">{passwordMessage}</p>{/if}
|
||||
{#if passwordError}<p class="text-primary text-sm">
|
||||
{passwordError}
|
||||
</p>{/if}
|
||||
{#if passwordMessage}<p class="text-secondary text-sm">
|
||||
{passwordMessage}
|
||||
</p>{/if}
|
||||
<button type="submit" disabled={passwordSaving} class="btn-primary">
|
||||
{passwordSaving ? "Changing..." : "Change password"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@
|
|||
type WeekStat = { weekStart: number; notes: number; questions: number };
|
||||
type Stats = {
|
||||
weeks: WeekStat[];
|
||||
totals: { notes: number; questions: number; users: number; units: number };
|
||||
totals: {
|
||||
notes: number;
|
||||
questions: number;
|
||||
users: number;
|
||||
units: number;
|
||||
};
|
||||
};
|
||||
|
||||
type AdminUser = UserDoc & { noteCount: number; questionCount: number };
|
||||
|
|
@ -54,18 +59,33 @@
|
|||
let pageError = $state("");
|
||||
|
||||
// Unit editor
|
||||
let unitForm = $state({ id: "", code: "", code2: "", name: "", description: "" });
|
||||
let unitForm = $state({
|
||||
id: "",
|
||||
code: "",
|
||||
code2: "",
|
||||
name: "",
|
||||
description: "",
|
||||
});
|
||||
let unitBusy = $state(false);
|
||||
let unitError = $state("");
|
||||
let unitSuccess = $state("");
|
||||
|
||||
// Account editor
|
||||
let userForm = $state<{ id: string; email: string; name: string; role: string } | null>(null);
|
||||
let userForm = $state<{
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
role: string;
|
||||
} | null>(null);
|
||||
let userBusy = $state(false);
|
||||
let userError = $state("");
|
||||
|
||||
// Note editor
|
||||
let noteForm = $state<{ id: string; title: string; content: string } | null>(null);
|
||||
let noteForm = $state<{
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
} | null>(null);
|
||||
let noteBusy = $state(false);
|
||||
let noteError = $state("");
|
||||
|
||||
|
|
@ -207,7 +227,10 @@
|
|||
unitError = "";
|
||||
unitSuccess = "";
|
||||
try {
|
||||
await mutation("admin:unitsDelete", { token: adminToken(), id: unit._id });
|
||||
await mutation("admin:unitsDelete", {
|
||||
token: adminToken(),
|
||||
id: unit._id,
|
||||
});
|
||||
units = (await query("units:getAll")) as UnitDoc[];
|
||||
} catch (err: any) {
|
||||
unitError = err.message ?? "Failed to delete unit";
|
||||
|
|
@ -217,7 +240,12 @@
|
|||
// ---- accounts ----
|
||||
|
||||
function startEditUser(user: AdminUser) {
|
||||
userForm = { id: user._id, email: user.email, name: user.name, role: user.role };
|
||||
userForm = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
};
|
||||
userError = "";
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +267,9 @@
|
|||
name: userForm.name,
|
||||
role: userForm.role,
|
||||
});
|
||||
users = (await query("admin:usersList", { token: adminToken() })) as AdminUser[];
|
||||
users = (await query("admin:usersList", {
|
||||
token: adminToken(),
|
||||
})) as AdminUser[];
|
||||
resetUserForm();
|
||||
} catch (err: any) {
|
||||
userError = err.message ?? "Failed to update account";
|
||||
|
|
@ -258,7 +288,9 @@
|
|||
name: user.name,
|
||||
role: user.role === "admin" ? "user" : "admin",
|
||||
});
|
||||
users = (await query("admin:usersList", { token: adminToken() })) as AdminUser[];
|
||||
users = (await query("admin:usersList", {
|
||||
token: adminToken(),
|
||||
})) as AdminUser[];
|
||||
} catch (err: any) {
|
||||
userError = err.message ?? "Failed to change role";
|
||||
}
|
||||
|
|
@ -268,8 +300,13 @@
|
|||
if (!confirm(`Delete ${user.email} and all of their content?`)) return;
|
||||
userError = "";
|
||||
try {
|
||||
await mutation("admin:usersDelete", { token: adminToken(), id: user._id });
|
||||
users = (await query("admin:usersList", { token: adminToken() })) as AdminUser[];
|
||||
await mutation("admin:usersDelete", {
|
||||
token: adminToken(),
|
||||
id: user._id,
|
||||
});
|
||||
users = (await query("admin:usersList", {
|
||||
token: adminToken(),
|
||||
})) as AdminUser[];
|
||||
} catch (err: any) {
|
||||
userError = err.message ?? "Failed to delete account";
|
||||
}
|
||||
|
|
@ -299,7 +336,9 @@
|
|||
title: noteForm.title,
|
||||
content: noteForm.content,
|
||||
});
|
||||
notes = (await query("admin:notesList", { token: adminToken() })) as AdminNote[];
|
||||
notes = (await query("admin:notesList", {
|
||||
token: adminToken(),
|
||||
})) as AdminNote[];
|
||||
resetNoteForm();
|
||||
} catch (err: any) {
|
||||
noteError = err.message ?? "Failed to update note";
|
||||
|
|
@ -312,8 +351,13 @@
|
|||
if (!confirm(`Delete note "${note.title}"?`)) return;
|
||||
noteError = "";
|
||||
try {
|
||||
await mutation("admin:notesDelete", { token: adminToken(), id: note._id });
|
||||
notes = (await query("admin:notesList", { token: adminToken() })) as AdminNote[];
|
||||
await mutation("admin:notesDelete", {
|
||||
token: adminToken(),
|
||||
id: note._id,
|
||||
});
|
||||
notes = (await query("admin:notesList", {
|
||||
token: adminToken(),
|
||||
})) as AdminNote[];
|
||||
} catch (err: any) {
|
||||
noteError = err.message ?? "Failed to delete note";
|
||||
}
|
||||
|
|
@ -321,7 +365,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Admin — Notebook</title>
|
||||
<title>Admin — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
|
|
@ -473,25 +517,35 @@
|
|||
<div class="grid grid-cols-2 gap-4 py-8 lg:grid-cols-4">
|
||||
<div class="border-rule border p-5">
|
||||
<p class="kicker">Notes</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">{stats.totals.notes}</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">
|
||||
{stats.totals.notes}
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-rule border p-5">
|
||||
<p class="kicker">Questions</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">{stats.totals.questions}</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">
|
||||
{stats.totals.questions}
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-rule border p-5">
|
||||
<p class="kicker">Accounts</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">{stats.totals.users}</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">
|
||||
{stats.totals.users}
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-rule border p-5">
|
||||
<p class="kicker">Units</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">{stats.totals.units}</p>
|
||||
<p class="text-ink mt-2 font-serif text-4xl">
|
||||
{stats.totals.units}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-rule border-t pt-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="kicker">Posts — last {stats.weeks.length} weeks</p>
|
||||
<p class="kicker">
|
||||
Posts — last {stats.weeks.length} weeks
|
||||
</p>
|
||||
<div class="flex gap-4 text-[11px] tracking-[0.14em] uppercase">
|
||||
<span class="text-ink"><span class="text-primary">■</span> Notes</span>
|
||||
<span class="text-ink"
|
||||
|
|
@ -540,7 +594,9 @@
|
|||
<p class="text-ink font-medium">
|
||||
{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}
|
||||
</p>
|
||||
<p class="text-muted truncate text-sm">{unit.name}</p>
|
||||
<p class="text-muted truncate text-sm">
|
||||
{unit.name}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 gap-3">
|
||||
<button
|
||||
|
|
@ -567,7 +623,9 @@
|
|||
onsubmit={saveUnit}
|
||||
class="border-rule sticky top-5 h-fit space-y-5 border p-5"
|
||||
>
|
||||
<p class="kicker">{unitForm.id ? "Edit unit" : "Add unit"}</p>
|
||||
<p class="kicker">
|
||||
{unitForm.id ? "Edit unit" : "Add unit"}
|
||||
</p>
|
||||
<div>
|
||||
<label for="unit-code" class="kicker mb-2 block">Code</label>
|
||||
<input
|
||||
|
|
@ -643,8 +701,12 @@
|
|||
<div class="border-rule border-b py-3">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<p class="text-ink font-medium">{user.name}</p>
|
||||
<p class="text-muted truncate text-sm">{user.email}</p>
|
||||
<p class="text-ink font-medium">
|
||||
{user.name}
|
||||
</p>
|
||||
<p class="text-muted truncate text-sm">
|
||||
{user.email}
|
||||
</p>
|
||||
</div>
|
||||
<span class="chip {user.role === 'admin' ? 'chip-active' : ''}">
|
||||
{user.role}
|
||||
|
|
@ -765,7 +827,9 @@
|
|||
<div class="border-rule border-b py-3">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<p class="text-ink font-medium">{note.title}</p>
|
||||
<p class="text-ink font-medium">
|
||||
{note.title}
|
||||
</p>
|
||||
<p class="text-muted mt-1 truncate text-sm">
|
||||
{note.unitCode
|
||||
? note.unitCode +
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Sign in — Notebook</title>
|
||||
<title>Sign in — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page flex min-h-[calc(100vh-220px)] items-center">
|
||||
|
|
@ -302,8 +302,7 @@
|
|||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if !sent}
|
||||
{:else if !sent}
|
||||
<form onsubmit={handleSubmit} class="border-rule space-y-5 border-t pt-8">
|
||||
<div>
|
||||
<label for="email" class="kicker mb-2 block">Deakin email</label>
|
||||
|
|
@ -340,8 +339,8 @@
|
|||
{:else}
|
||||
<form onsubmit={handleSubmit} class="border-rule space-y-5 border-t pt-8">
|
||||
<p class="text-muted text-sm">
|
||||
We sent a 6-digit code to <span class="text-ink">{email}</span>. It expires
|
||||
in 10 minutes.
|
||||
We sent a 6-digit code to <span class="text-ink">{email}</span>. It expires in
|
||||
10 minutes.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
|
|
@ -380,15 +379,10 @@
|
|||
{loading ? "Resetting..." : "Reset password"}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={goBack}
|
||||
class="text-muted hover:text-ink text-sm"
|
||||
>
|
||||
<button type="button" onclick={goBack} class="text-muted hover:text-ink text-sm">
|
||||
Use a different email
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@
|
|||
onMount(async () => {
|
||||
const [n, u] = await Promise.all([query("notes:list", {}), query("units:getAll")]);
|
||||
const unitMap = new Map((u as UnitDoc[]).map((unit) => [unit._id, unit]));
|
||||
notes = (n as NoteDoc[]).map((note) => ({ ...note, unit: unitMap.get(note.unitId) }));
|
||||
notes = (n as NoteDoc[]).map((note) => ({
|
||||
...note,
|
||||
unit: unitMap.get(note.unitId),
|
||||
}));
|
||||
units = u as UnitDoc[];
|
||||
loading = false;
|
||||
});
|
||||
|
|
@ -43,7 +46,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Notes — Notebook</title>
|
||||
<title>Notes — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Post a note — Notebook</title>
|
||||
<title>Post a note — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window onbeforeunload={handleBeforeUnload} />
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Ask a question — Notebook</title>
|
||||
<title>Ask a question — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window onbeforeunload={handleBeforeUnload} />
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Questions — Notebook</title>
|
||||
<title>Questions — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
searchQuery = new URL(window.location.href).searchParams.get("q") ?? "";
|
||||
if (searchQuery) {
|
||||
ranQuery = true;
|
||||
results = (await query("search:all", { query: searchQuery })) as SearchResult[];
|
||||
results = (await query("search:all", {
|
||||
query: searchQuery,
|
||||
})) as SearchResult[];
|
||||
}
|
||||
loading = false;
|
||||
});
|
||||
|
|
@ -25,7 +27,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{searchQuery ? `Search: ${searchQuery}` : "Search"} — Notebook</title>
|
||||
<title>{searchQuery ? `Search: ${searchQuery}` : "Search"} — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
|
|
@ -110,7 +112,9 @@
|
|||
{/if}
|
||||
{:else}
|
||||
{#if unitResults.length === 0}
|
||||
<p class="text-muted text-sm">No results found for “{searchQuery}”.</p>
|
||||
<p class="text-muted text-sm">
|
||||
No results found for “{searchQuery}”.
|
||||
</p>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -40,15 +40,19 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{topic?.name ?? "Topic"} — Notebook</title>
|
||||
<title>{topic?.name ?? "Topic"} — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
{#if loading}
|
||||
<p class="kicker py-16">Loading</p>
|
||||
{:else if topic}
|
||||
<p class="kicker"><a href="/" class="hover:text-primary">Home</a> · Topic</p>
|
||||
<h1 class="text-ink mt-2 font-serif text-4xl font-medium">{topic.name}</h1>
|
||||
<p class="kicker">
|
||||
<a href="/" class="hover:text-primary">Home</a> · Topic
|
||||
</p>
|
||||
<h1 class="text-ink mt-2 font-serif text-4xl font-medium">
|
||||
{topic.name}
|
||||
</h1>
|
||||
{#if topic.description}
|
||||
<p class="text-muted mt-2 text-[15px]">{topic.description}</p>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Units — Notebook</title>
|
||||
<title>Units — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
|
|
@ -47,7 +47,9 @@
|
|||
{:else if units.length === 0}
|
||||
<p class="text-muted py-16 text-sm">No units yet.</p>
|
||||
{:else if visible.length === 0}
|
||||
<p class="text-muted py-16 text-sm">No units match “{searchQuery.trim()}”.</p>
|
||||
<p class="text-muted py-16 text-sm">
|
||||
No units match “{searchQuery.trim()}”.
|
||||
</p>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 gap-3 pt-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{#each visible as unit (unit._id)}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@
|
|||
tab = "posts";
|
||||
|
||||
void (async () => {
|
||||
const result = (await query("users:getPublicProfile", { id })) as Profile | null;
|
||||
const result = (await query("users:getPublicProfile", {
|
||||
id,
|
||||
})) as Profile | null;
|
||||
if (cancelled) return;
|
||||
profile = result;
|
||||
loading = false;
|
||||
|
|
@ -60,7 +62,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{profile?.name ?? "Profile"} — Notebook</title>
|
||||
<title>{profile?.name ?? "Profile"} — DSEC Notebook</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
|
|
@ -71,7 +73,9 @@
|
|||
<Avatar src={profile.avatarUrl} name={profile.name} size="xl" />
|
||||
<div>
|
||||
<p class="kicker">Contributor</p>
|
||||
<h1 class="text-ink mt-2 font-serif text-4xl font-medium">{profile.name}</h1>
|
||||
<h1 class="text-ink mt-2 font-serif text-4xl font-medium">
|
||||
{profile.name}
|
||||
</h1>
|
||||
<p class="text-muted mt-2 text-sm">
|
||||
Member since {new Date(profile._creationTime).toLocaleDateString(undefined, {
|
||||
month: "long",
|
||||
|
|
@ -83,7 +87,9 @@
|
|||
|
||||
<section class="border-rule mt-10 grid grid-cols-2 border-y sm:grid-cols-4">
|
||||
<div class="border-rule border-r px-4 py-5 first:pl-0">
|
||||
<p class="text-ink font-serif text-3xl">{profile.totalContributions}</p>
|
||||
<p class="text-ink font-serif text-3xl">
|
||||
{profile.totalContributions}
|
||||
</p>
|
||||
<p class="kicker text-muted mt-1">Contributions</p>
|
||||
</div>
|
||||
<div class="border-rule border-r px-4 py-5">
|
||||
|
|
@ -91,11 +97,15 @@
|
|||
<p class="kicker text-muted mt-1">Notes</p>
|
||||
</div>
|
||||
<div class="border-rule border-r px-4 py-5">
|
||||
<p class="text-ink font-serif text-3xl">{profile.questionCount}</p>
|
||||
<p class="text-ink font-serif text-3xl">
|
||||
{profile.questionCount}
|
||||
</p>
|
||||
<p class="kicker text-muted mt-1">Questions</p>
|
||||
</div>
|
||||
<div class="px-4 py-5">
|
||||
<p class="text-ink font-serif text-3xl">{profile.commentCount}</p>
|
||||
<p class="text-ink font-serif text-3xl">
|
||||
{profile.commentCount}
|
||||
</p>
|
||||
<p class="kicker text-muted mt-1">Comments</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -161,7 +171,9 @@
|
|||
<p class="text-ink mt-2 text-sm leading-relaxed">
|
||||
{previewMarkdown(comment.content)}
|
||||
</p>
|
||||
<p class="text-faint mt-1 text-xs">{timeAgo(comment.createdAt)}</p>
|
||||
<p class="text-faint mt-1 text-xs">
|
||||
{timeAgo(comment.createdAt)}
|
||||
</p>
|
||||
</article>
|
||||
{/each}
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Reference in a new issue