mirror of
https://github.com/dsec-hub/dsec-notebook.git
synced 2026-09-22 07:24:27 +00:00
edited admin units dashboard
This commit is contained in:
parent
2ff5b92bbc
commit
830a04893a
14 changed files with 3743 additions and 36 deletions
98
dev-dist/sw.js
Normal file
98
dev-dist/sw.js
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If the loader is already loaded, just stop.
|
||||||
|
if (!self.define) {
|
||||||
|
let registry = {};
|
||||||
|
|
||||||
|
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
||||||
|
// In both cases, it's safe to use a global var because those functions are synchronous.
|
||||||
|
let nextDefineUri;
|
||||||
|
|
||||||
|
const singleRequire = (uri, parentUri) => {
|
||||||
|
uri = new URL(uri + ".js", parentUri).href;
|
||||||
|
return (
|
||||||
|
registry[uri] ||
|
||||||
|
new Promise((resolve) => {
|
||||||
|
if ("document" in self) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = uri;
|
||||||
|
script.onload = resolve;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
} else {
|
||||||
|
nextDefineUri = uri;
|
||||||
|
importScripts(uri);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.then(() => {
|
||||||
|
let promise = registry[uri];
|
||||||
|
if (!promise) {
|
||||||
|
throw new Error(`Module ${uri} didn’t register its module`);
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.define = (depsNames, factory) => {
|
||||||
|
const uri =
|
||||||
|
nextDefineUri ||
|
||||||
|
("document" in self ? document.currentScript.src : "") ||
|
||||||
|
location.href;
|
||||||
|
if (registry[uri]) {
|
||||||
|
// Module is already loading or loaded.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let exports = {};
|
||||||
|
const require = (depUri) => singleRequire(depUri, uri);
|
||||||
|
const specialDeps = {
|
||||||
|
module: { uri },
|
||||||
|
exports,
|
||||||
|
require,
|
||||||
|
};
|
||||||
|
registry[uri] = Promise.all(
|
||||||
|
depsNames.map((depName) => specialDeps[depName] || require(depName)),
|
||||||
|
).then((deps) => {
|
||||||
|
factory(...deps);
|
||||||
|
return exports;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
define(["./workbox-7e5eb42b"], function (workbox) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
self.skipWaiting();
|
||||||
|
workbox.clientsClaim();
|
||||||
|
/**
|
||||||
|
* The precacheAndRoute() method efficiently caches and responds to
|
||||||
|
* requests for URLs in the manifest.
|
||||||
|
* See https://goo.gl/S9QRab
|
||||||
|
*/
|
||||||
|
workbox.precacheAndRoute(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
url: "/",
|
||||||
|
revision: "0.agglep2qr98",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
workbox.cleanupOutdatedCaches();
|
||||||
|
workbox.registerRoute(
|
||||||
|
new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/"), {
|
||||||
|
allowlist: [/^\/$/],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
3545
dev-dist/workbox-7e5eb42b.js
Normal file
3545
dev-dist/workbox-7e5eb42b.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -255,24 +255,27 @@ function topicsGetAll(db: Db) {
|
||||||
// ---- units ----
|
// ---- units ----
|
||||||
|
|
||||||
function unitsGetByCode(db: Db, args: { code: string }) {
|
function unitsGetByCode(db: Db, args: { code: string }) {
|
||||||
|
const code = args.code.toUpperCase();
|
||||||
return (
|
return (
|
||||||
db
|
db
|
||||||
.prepare("SELECT id AS _id, code, name, description FROM units WHERE code = ?")
|
.prepare(
|
||||||
.get(args.code.toUpperCase()) ?? null
|
"SELECT id AS _id, code, code2, name, description FROM units WHERE code = ? OR code2 = ?",
|
||||||
|
)
|
||||||
|
.get(code, code) ?? null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unitsGetAll(db: Db) {
|
function unitsGetAll(db: Db) {
|
||||||
return db
|
return db
|
||||||
.prepare("SELECT id AS _id, code, name, description FROM units ORDER BY code ASC")
|
.prepare("SELECT id AS _id, code, code2, name, description FROM units ORDER BY code ASC")
|
||||||
.all();
|
.all();
|
||||||
}
|
}
|
||||||
|
|
||||||
function unitsCreateCustom(db: Db, args: { code: string; name: string }) {
|
function unitsCreateCustom(db: Db, args: { code: string; name: string }) {
|
||||||
const code = args.code.toUpperCase();
|
const code = args.code.toUpperCase();
|
||||||
const existing = db.prepare("SELECT id AS _id FROM units WHERE code = ?").get(code) as
|
const existing = db
|
||||||
| { _id: string }
|
.prepare("SELECT id AS _id FROM units WHERE code = ? OR code2 = ?")
|
||||||
| undefined;
|
.get(code, code) as { _id: string } | undefined;
|
||||||
if (existing) return existing._id;
|
if (existing) return existing._id;
|
||||||
|
|
||||||
const id = newId();
|
const id = newId();
|
||||||
|
|
@ -550,7 +553,7 @@ function getNoteWithDetails(db: Db, args: { id: string }) {
|
||||||
.prepare("SELECT id AS _id, name, slug, description FROM topics WHERE id = ?")
|
.prepare("SELECT id AS _id, name, slug, description FROM topics WHERE id = ?")
|
||||||
.get(note.topicId);
|
.get(note.topicId);
|
||||||
const unit = db
|
const unit = db
|
||||||
.prepare("SELECT id AS _id, code, name, description FROM units WHERE id = ?")
|
.prepare("SELECT id AS _id, code, code2, name, description FROM units WHERE id = ?")
|
||||||
.get(note.unitId);
|
.get(note.unitId);
|
||||||
const comments = commentsListByNote(db, { noteId: note._id });
|
const comments = commentsListByNote(db, { noteId: note._id });
|
||||||
|
|
||||||
|
|
@ -567,7 +570,7 @@ function getQuestionWithDetails(db: Db, args: { id: string }) {
|
||||||
.prepare("SELECT id AS _id, name, slug, description FROM topics WHERE id = ?")
|
.prepare("SELECT id AS _id, name, slug, description FROM topics WHERE id = ?")
|
||||||
.get(question.topicId);
|
.get(question.topicId);
|
||||||
const unit = db
|
const unit = db
|
||||||
.prepare("SELECT id AS _id, code, name, description FROM units WHERE id = ?")
|
.prepare("SELECT id AS _id, code, code2, name, description FROM units WHERE id = ?")
|
||||||
.get(question.unitId);
|
.get(question.unitId);
|
||||||
const answers = commentsListByQuestion(db, { questionId: question._id });
|
const answers = commentsListByQuestion(db, { questionId: question._id });
|
||||||
|
|
||||||
|
|
@ -635,35 +638,51 @@ function adminCompleteSetup(db: Db, args: { email: string; code: string }) {
|
||||||
|
|
||||||
function adminUnitsSave(
|
function adminUnitsSave(
|
||||||
db: Db,
|
db: Db,
|
||||||
args: { token: string; id?: string; code: string; name: string; description?: string },
|
args: {
|
||||||
|
token: string;
|
||||||
|
id?: string;
|
||||||
|
code: string;
|
||||||
|
code2?: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
requireAdmin(db, args.token);
|
requireAdmin(db, args.token);
|
||||||
const code = (args.code ?? "").trim().toUpperCase();
|
const code = (args.code ?? "").trim().toUpperCase();
|
||||||
const name = (args.name ?? "").trim();
|
const name = (args.name ?? "").trim();
|
||||||
if (!code || !name) throw new Error("Code and name are required");
|
if (!code || !name) throw new Error("Code and name are required");
|
||||||
|
|
||||||
|
const code2 = (args.code2 ?? "").trim().toUpperCase() || null;
|
||||||
|
if (code2 && code2 === code)
|
||||||
|
throw new Error("The second code must differ from the primary code");
|
||||||
|
|
||||||
const duplicate = db
|
const duplicate = db
|
||||||
.prepare("SELECT id AS _id FROM units WHERE code = ? AND id != ?")
|
.prepare("SELECT id AS _id FROM units WHERE (code = ? OR code2 = ?) AND id != ?")
|
||||||
.get(code, args.id ?? "") as { _id: string } | undefined;
|
.get(code, code, args.id ?? "") as { _id: string } | undefined;
|
||||||
if (duplicate) throw new Error("A unit with this code already exists");
|
if (duplicate) throw new Error("A unit with this code already exists");
|
||||||
|
|
||||||
|
if (code2) {
|
||||||
|
const duplicate2 = db
|
||||||
|
.prepare("SELECT id AS _id FROM units WHERE (code = ? OR code2 = ?) AND id != ?")
|
||||||
|
.get(code2, code2, args.id ?? "") as { _id: string } | undefined;
|
||||||
|
if (duplicate2) throw new Error("A unit with this second code already exists");
|
||||||
|
}
|
||||||
|
|
||||||
const description = args.description?.trim() || null;
|
const description = args.description?.trim() || null;
|
||||||
if (args.id) {
|
if (args.id) {
|
||||||
const existing = db.prepare("SELECT id AS _id FROM units WHERE id = ?").get(args.id);
|
const existing = db.prepare("SELECT id AS _id FROM units WHERE id = ?").get(args.id);
|
||||||
if (!existing) throw new Error("Unit not found");
|
if (!existing) throw new Error("Unit not found");
|
||||||
db.prepare("UPDATE units SET code = ?, name = ?, description = ? WHERE id = ?").run(
|
db.prepare(
|
||||||
code,
|
"UPDATE units SET code = ?, code2 = ?, name = ?, description = ? WHERE id = ?",
|
||||||
name,
|
).run(code, code2, name, description, args.id);
|
||||||
description,
|
|
||||||
args.id,
|
|
||||||
);
|
|
||||||
return args.id;
|
return args.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = newId();
|
const id = newId();
|
||||||
db.prepare("INSERT INTO units (id, code, name, description) VALUES (?, ?, ?, ?)").run(
|
db.prepare("INSERT INTO units (id, code, code2, name, description) VALUES (?, ?, ?, ?, ?)").run(
|
||||||
id,
|
id,
|
||||||
code,
|
code,
|
||||||
|
code2,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
);
|
);
|
||||||
|
|
@ -797,7 +816,8 @@ function adminNotesList(db: Db, args: { token: string }) {
|
||||||
n.updatedAt,
|
n.updatedAt,
|
||||||
n.voteCount,
|
n.voteCount,
|
||||||
n.commentCount,
|
n.commentCount,
|
||||||
u.code AS unitCode
|
u.code AS unitCode,
|
||||||
|
u.code2 AS unitCode2
|
||||||
FROM notes n
|
FROM notes n
|
||||||
LEFT JOIN units u ON u.id = n.unitId
|
LEFT JOIN units u ON u.id = n.unitId
|
||||||
ORDER BY n.createdAt DESC`,
|
ORDER BY n.createdAt DESC`,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ function createSchema(database: DatabaseSync) {
|
||||||
CREATE TABLE IF NOT EXISTS units (
|
CREATE TABLE IF NOT EXISTS units (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
code TEXT NOT NULL UNIQUE,
|
code TEXT NOT NULL UNIQUE,
|
||||||
|
code2 TEXT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT
|
description TEXT
|
||||||
);
|
);
|
||||||
|
|
@ -214,6 +215,11 @@ function migrate(database: DatabaseSync) {
|
||||||
if (!columns.some((c) => c.name === "role")) {
|
if (!columns.some((c) => c.name === "role")) {
|
||||||
database.exec("ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user'");
|
database.exec("ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unitColumns = database.prepare("PRAGMA table_info(units)").all() as { name: string }[];
|
||||||
|
if (!unitColumns.some((c) => c.name === "code2")) {
|
||||||
|
database.exec("ALTER TABLE units ADD COLUMN code2 TEXT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function seed(database: DatabaseSync) {
|
function seed(database: DatabaseSync) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ export type TopicDoc = Doc<"topics"> & {
|
||||||
|
|
||||||
export type UnitDoc = Doc<"units"> & {
|
export type UnitDoc = Doc<"units"> & {
|
||||||
code: string;
|
code: string;
|
||||||
|
code2?: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
class="group border-rule flex items-baseline justify-between gap-4 border-b px-5 py-3"
|
class="group border-rule flex items-baseline justify-between gap-4 border-b px-5 py-3"
|
||||||
>
|
>
|
||||||
<span class="text-ink group-hover:text-primary font-serif text-lg"
|
<span class="text-ink group-hover:text-primary font-serif text-lg"
|
||||||
>{unit.code}</span
|
>{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}</span
|
||||||
>
|
>
|
||||||
<span class="text-muted truncate text-sm">{unit.name}</span>
|
<span class="text-muted truncate text-sm">{unit.name}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
authorName: string;
|
authorName: string;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
unitCode?: string;
|
unitCode?: string;
|
||||||
|
unitCode2?: string;
|
||||||
voteCount: number;
|
voteCount: number;
|
||||||
commentCount: number;
|
commentCount: number;
|
||||||
};
|
};
|
||||||
|
|
@ -53,7 +54,7 @@
|
||||||
let pageError = $state("");
|
let pageError = $state("");
|
||||||
|
|
||||||
// Unit editor
|
// Unit editor
|
||||||
let unitForm = $state({ id: "", code: "", name: "", description: "" });
|
let unitForm = $state({ id: "", code: "", code2: "", name: "", description: "" });
|
||||||
let unitBusy = $state(false);
|
let unitBusy = $state(false);
|
||||||
let unitError = $state("");
|
let unitError = $state("");
|
||||||
let unitSuccess = $state("");
|
let unitSuccess = $state("");
|
||||||
|
|
@ -163,6 +164,7 @@
|
||||||
unitForm = {
|
unitForm = {
|
||||||
id: unit._id,
|
id: unit._id,
|
||||||
code: unit.code,
|
code: unit.code,
|
||||||
|
code2: unit.code2 ?? "",
|
||||||
name: unit.name,
|
name: unit.name,
|
||||||
description: unit.description ?? "",
|
description: unit.description ?? "",
|
||||||
};
|
};
|
||||||
|
|
@ -171,7 +173,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetUnitForm() {
|
function resetUnitForm() {
|
||||||
unitForm = { id: "", code: "", name: "", description: "" };
|
unitForm = { id: "", code: "", code2: "", name: "", description: "" };
|
||||||
unitError = "";
|
unitError = "";
|
||||||
unitSuccess = "";
|
unitSuccess = "";
|
||||||
}
|
}
|
||||||
|
|
@ -186,6 +188,7 @@
|
||||||
token: adminToken(),
|
token: adminToken(),
|
||||||
id: unitForm.id || undefined,
|
id: unitForm.id || undefined,
|
||||||
code: unitForm.code,
|
code: unitForm.code,
|
||||||
|
code2: unitForm.code2,
|
||||||
name: unitForm.name,
|
name: unitForm.name,
|
||||||
description: unitForm.description,
|
description: unitForm.description,
|
||||||
});
|
});
|
||||||
|
|
@ -534,7 +537,9 @@
|
||||||
class="border-rule flex items-center justify-between gap-4 border-b py-3"
|
class="border-rule flex items-center justify-between gap-4 border-b py-3"
|
||||||
>
|
>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="text-ink font-medium">{unit.code}</p>
|
<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>
|
||||||
<div class="flex shrink-0 gap-3">
|
<div class="flex shrink-0 gap-3">
|
||||||
|
|
@ -558,7 +563,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onsubmit={saveUnit} class="border-rule h-fit space-y-5 border p-5">
|
<form
|
||||||
|
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>
|
<div>
|
||||||
<label for="unit-code" class="kicker mb-2 block">Code</label>
|
<label for="unit-code" class="kicker mb-2 block">Code</label>
|
||||||
|
|
@ -571,6 +579,18 @@
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="unit-code2" class="kicker mb-2 block"
|
||||||
|
>Second code (optional)</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="unit-code2"
|
||||||
|
type="text"
|
||||||
|
bind:value={unitForm.code2}
|
||||||
|
placeholder="e.g., SIT103"
|
||||||
|
class="field"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="unit-name" class="kicker mb-2 block">Name</label>
|
<label for="unit-name" class="kicker mb-2 block">Name</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -747,7 +767,10 @@
|
||||||
<div class="min-w-0">
|
<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">
|
<p class="text-muted mt-1 truncate text-sm">
|
||||||
{note.unitCode ?? "—"} · {note.authorName} · {timeAgo(
|
{note.unitCode
|
||||||
|
? note.unitCode +
|
||||||
|
(note.unitCode2 ? ` / ${note.unitCode2}` : "")
|
||||||
|
: "—"} · {note.authorName} · {timeAgo(
|
||||||
note.createdAt,
|
note.createdAt,
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
class="chip {selectedUnitId === unit._id ? 'chip-active' : ''}"
|
class="chip {selectedUnitId === unit._id ? 'chip-active' : ''}"
|
||||||
onclick={() => (selectedUnitId = unit._id)}
|
onclick={() => (selectedUnitId = unit._id)}
|
||||||
>
|
>
|
||||||
{unit.code}
|
{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -85,7 +85,9 @@
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href="/notes/{note._id}"
|
href="/notes/{note._id}"
|
||||||
title={note.title}
|
title={note.title}
|
||||||
unitCode={note.unit?.code}
|
unitCode={note.unit
|
||||||
|
? note.unit.code + (note.unit.code2 ? ` / ${note.unit.code2}` : "")
|
||||||
|
: undefined}
|
||||||
meta="{note.authorName} · {timeAgo(
|
meta="{note.authorName} · {timeAgo(
|
||||||
note.createdAt,
|
note.createdAt,
|
||||||
)} · {note.commentCount} comment{note.commentCount === 1 ? '' : 's'}"
|
)} · {note.commentCount} comment{note.commentCount === 1 ? '' : 's'}"
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,9 @@
|
||||||
<VoteStack count={note.voteCount} targetType="note" targetId={note._id} />
|
<VoteStack count={note.voteCount} targetType="note" targetId={note._id} />
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="kicker">
|
<p class="kicker">
|
||||||
{#if unit}{unit.code}{/if}{#if unit && topic}
|
{#if unit}{unit.code}{unit.code2
|
||||||
|
? ` / ${unit.code2}`
|
||||||
|
: ""}{/if}{#if unit && topic}
|
||||||
·
|
·
|
||||||
{/if}{#if topic}{topic.name}{/if}
|
{/if}{#if topic}{topic.name}{/if}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,9 @@
|
||||||
<select id="unit" bind:value={selectedUnitId} class="field">
|
<select id="unit" bind:value={selectedUnitId} class="field">
|
||||||
<option value="">Select a unit...</option>
|
<option value="">Select a unit...</option>
|
||||||
{#each units as unit}
|
{#each units as unit}
|
||||||
<option value={unit._id}>{unit.code} — {unit.name}</option>
|
<option value={unit._id}
|
||||||
|
>{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""} — {unit.name}</option
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,9 @@
|
||||||
<select id="unit" bind:value={selectedUnitId} class="field">
|
<select id="unit" bind:value={selectedUnitId} class="field">
|
||||||
<option value="">Select a unit...</option>
|
<option value="">Select a unit...</option>
|
||||||
{#each units as unit}
|
{#each units as unit}
|
||||||
<option value={unit._id}>{unit.code} — {unit.name}</option>
|
<option value={unit._id}
|
||||||
|
>{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""} — {unit.name}</option
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
class="chip {selectedUnitId === unit._id ? 'chip-active' : ''}"
|
class="chip {selectedUnitId === unit._id ? 'chip-active' : ''}"
|
||||||
onclick={() => (selectedUnitId = unit._id)}
|
onclick={() => (selectedUnitId = unit._id)}
|
||||||
>
|
>
|
||||||
{unit.code}
|
{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -90,7 +90,9 @@
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href="/questions/{question._id}"
|
href="/questions/{question._id}"
|
||||||
title={question.title}
|
title={question.title}
|
||||||
unitCode={question.unit?.code}
|
unitCode={question.unit
|
||||||
|
? question.unit.code + (question.unit.code2 ? ` / ${question.unit.code2}` : "")
|
||||||
|
: undefined}
|
||||||
meta="{question.authorName} · {timeAgo(
|
meta="{question.authorName} · {timeAgo(
|
||||||
question.createdAt,
|
question.createdAt,
|
||||||
)} · {question.answerCount} answer{question.answerCount === 1 ? '' : 's'}"
|
)} · {question.answerCount} answer{question.answerCount === 1 ? '' : 's'}"
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,9 @@
|
||||||
<VoteStack count={question.voteCount} targetType="question" targetId={question._id} />
|
<VoteStack count={question.voteCount} targetType="question" targetId={question._id} />
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="kicker">
|
<p class="kicker">
|
||||||
{#if unit}{unit.code}{/if}{#if unit && topic}
|
{#if unit}{unit.code}{unit.code2
|
||||||
|
? ` / ${unit.code2}`
|
||||||
|
: ""}{/if}{#if unit && topic}
|
||||||
·
|
·
|
||||||
{/if}{#if topic}{topic.name}{/if}
|
{/if}{#if topic}{topic.name}{/if}
|
||||||
{#if question.solved}
|
{#if question.solved}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,9 @@
|
||||||
<p class="kicker py-16">Loading</p>
|
<p class="kicker py-16">Loading</p>
|
||||||
{:else if unit}
|
{:else if unit}
|
||||||
<p class="kicker"><a href="/" class="hover:text-primary">Home</a> · Unit</p>
|
<p class="kicker"><a href="/" class="hover:text-primary">Home</a> · Unit</p>
|
||||||
<h1 class="text-ink mt-2 font-serif text-4xl font-medium">{unit.code}</h1>
|
<h1 class="text-ink mt-2 font-serif text-4xl font-medium">
|
||||||
|
{unit.code}{unit.code2 ? ` / ${unit.code2}` : ""}
|
||||||
|
</h1>
|
||||||
<p class="text-muted mt-1 text-[15px]">{unit.name}</p>
|
<p class="text-muted mt-1 text-[15px]">{unit.name}</p>
|
||||||
{#if unit.description}
|
{#if unit.description}
|
||||||
<p class="text-muted mt-2 text-sm">{unit.description}</p>
|
<p class="text-muted mt-2 text-sm">{unit.description}</p>
|
||||||
|
|
@ -48,7 +50,7 @@
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href="/notes/{note._id}"
|
href="/notes/{note._id}"
|
||||||
title={note.title}
|
title={note.title}
|
||||||
unitCode={unit.code}
|
unitCode={unit.code + (unit.code2 ? ` / ${unit.code2}` : "")}
|
||||||
meta="{note.authorName} · {timeAgo(
|
meta="{note.authorName} · {timeAgo(
|
||||||
note.createdAt,
|
note.createdAt,
|
||||||
)} · {note.commentCount} comment{note.commentCount === 1 ? '' : 's'}"
|
)} · {note.commentCount} comment{note.commentCount === 1 ? '' : 's'}"
|
||||||
|
|
@ -65,7 +67,7 @@
|
||||||
<FeedRow
|
<FeedRow
|
||||||
href="/questions/{question._id}"
|
href="/questions/{question._id}"
|
||||||
title={question.title}
|
title={question.title}
|
||||||
unitCode={unit.code}
|
unitCode={unit.code + (unit.code2 ? ` / ${unit.code2}` : "")}
|
||||||
meta="{question.authorName} · {timeAgo(
|
meta="{question.authorName} · {timeAgo(
|
||||||
question.createdAt,
|
question.createdAt,
|
||||||
)} · {question.answerCount} answer{question.answerCount === 1 ? '' : 's'}"
|
)} · {question.answerCount} answer{question.answerCount === 1 ? '' : 's'}"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue