Files
ad_fun/chessstars/internal/web/templates/courses.html
T
2026-07-15 13:29:08 +03:00

163 lines
5.6 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<title>Courses - ChessStars</title>
{{template "head" .}}
</head>
<body>
{{template "nav" .}}
<main>
<div class="panel">
<h2>Publish a course</h2>
<label>Title<br><input id="c-title" size="40"></label><br><br>
<label>Description<br><textarea id="c-desc" rows="2" cols="40"></textarea></label><br><br>
<label>Visibility
<select id="c-vis">
<option value="private">private (only you)</option>
<option value="public">public</option>
</select>
</label><br><br>
<button id="c-create">Create course</button>
<p class="muted" id="c-msg"></p>
</div>
<div class="panel">
<h2>Public courses</h2>
<div id="public-list"></div>
</div>
<div class="panel">
<h2>My courses</h2>
<div id="my-list" class="muted">log in to see your courses</div>
</div>
<div class="panel" id="detail-panel" style="display:none">
<h2 id="detail-title"></h2>
<p class="muted" id="detail-meta"></p>
<h3>Chapters</h3>
<div id="chapter-list"></div>
<div id="add-chapter-box" style="display:none">
<h3>Add chapter</h3>
<label>Title<br><input id="ch-title" size="40"></label><br><br>
<label>Annotation<br><textarea id="ch-annotation" rows="2" cols="40"></textarea></label><br><br>
<label>Solution note (private)<br><textarea id="ch-solution" rows="2" cols="40"></textarea></label><br><br>
<button id="ch-add">Add chapter</button>
</div>
<div id="chapter-detail" class="panel" style="display:none"></div>
</div>
</main>
<script>
let currentCourseID = null;
function courseCard(c) {
const div = document.createElement("div");
div.className = "course-card";
div.innerHTML = "<b>" + c.title + "</b> by " + (c.author_name || c.AuthorName || "?") +
" <span class='muted'>(" + c.visibility + ")</span><br>" +
"<span class='muted'>" + (c.description || "") + "</span><br>" +
"<a href='#' data-id='" + c.id + "'>Open</a>";
div.querySelector("a").addEventListener("click", (e) => {
e.preventDefault();
openCourse(c.id);
});
return div;
}
async function loadPublic() {
const res = await api.get("/api/courses");
const list = document.getElementById("public-list");
list.innerHTML = "";
(res.courses || []).forEach((c) => list.appendChild(courseCard(c)));
}
async function loadMine() {
const res = await api.get("/api/me/courses");
const list = document.getElementById("my-list");
if (!res.ok) {
list.textContent = "log in to see your courses";
return;
}
list.innerHTML = "";
(res.courses || []).forEach((c) => list.appendChild(courseCard(c)));
}
async function openCourse(id) {
currentCourseID = id;
const res = await api.get("/api/courses/" + id);
document.getElementById("detail-panel").style.display = "block";
document.getElementById("chapter-detail").style.display = "none";
document.getElementById("detail-title").textContent = res.title || "(not found)";
document.getElementById("detail-meta").textContent =
"by " + res.author + " - " + res.visibility;
const list = document.getElementById("chapter-list");
list.innerHTML = "";
(res.chapters || []).forEach((ch) => {
const a = document.createElement("a");
a.href = "#";
a.textContent = ch.ordinal + ". " + ch.title;
a.style.display = "block";
a.addEventListener("click", (e) => {
e.preventDefault();
openChapter(id, ch.id);
});
list.appendChild(a);
});
const me = await api.get("/api/me");
document.getElementById("add-chapter-box").style.display = me.username ? "block" : "none";
}
async function openChapter(courseID, chapterID) {
const res = await api.get("/api/courses/" + courseID + "/chapters/" + chapterID);
const box = document.getElementById("chapter-detail");
box.style.display = "block";
box.innerHTML = "<h4>" + res.title + "</h4><p>" + (res.annotation || "") + "</p>" +
"<p class='muted'>moves: " + (res.moves ? res.moves.length : 0) + "</p>" +
"<p><b>Solution:</b> " + (res.solution_note || "(none)") + "</p>";
}
document.getElementById("c-create").addEventListener("click", async () => {
const res = await api.post("/api/courses", {
title: document.getElementById("c-title").value,
description: document.getElementById("c-desc").value,
visibility: document.getElementById("c-vis").value,
variant: 0,
start_fen: "",
rule_flags: 0,
});
const msg = document.getElementById("c-msg");
if (res.ok) {
msg.textContent = "created course #" + res.id;
loadPublic();
loadMine();
} else {
msg.textContent = res.error || "could not create course";
}
});
document.getElementById("ch-add").addEventListener("click", async () => {
if (!currentCourseID) return;
const res = await api.post("/api/courses/" + currentCourseID + "/chapters", {
ordinal: 1,
title: document.getElementById("ch-title").value,
annotation: document.getElementById("ch-annotation").value,
moves: [],
solution_note: document.getElementById("ch-solution").value,
});
if (res.ok) {
openCourse(currentCourseID);
} else {
alert(res.error || "could not add chapter");
}
});
loadPublic();
loadMine();
</script>
</body>
</html>