Rename project from TopDeck to Canon

- Module github.com/cdnutter/canon, binary/command cmd/canon, Docker
  image and compose project "canon"
- Environment variables now use the CANON_ prefix
- Default site title is "<Your name>'s Canon" (or "Canon" with no name);
  a Site title set in Settings still overrides it
- Existing topdeck.db (and WAL files) is renamed to canon.db on start
- README: upgrade notes, including moving the old compose volume

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-24 17:22:17 -07:00
1 parent 58a29b7339
commit 3923444b3b
26 files changed
+215 -120

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
TopDeck. Colors and fonts come from themes.css.
Canon. Colors and fonts come from themes.css.
========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* TopDeck theme presets.
* Canon theme presets.
*
* Every color on the site comes from these variables, so a custom theme only
* needs to override them (Settings → Appearance → Custom CSS), e.g.
+7 -4
View File
@@ -70,7 +70,10 @@ function profileSection() {
});
const ownerName = h("input", { maxlength: 100, value: settings.ownerName, placeholder: "Your name" });
const siteTitle = h("input", { maxlength: 100, value: settings.siteTitle, placeholder: "TopDeck" });
// Mirrors DefaultTitle in render.go.
const defaultTitle = (name) => (name.trim() ? `${name.trim()}'s Canon` : "Canon");
const siteTitle = h("input", { maxlength: 100, value: settings.siteTitle, placeholder: defaultTitle(settings.ownerName) });
ownerName.addEventListener("input", () => { siteTitle.placeholder = defaultTitle(ownerName.value); });
const tagline = h("input", { maxlength: 300, value: settings.tagline, placeholder: "A line about you or your taste" });
const preview = h("input", { type: "number", min: 0, max: 50, value: settings.previewCount, style: { maxWidth: "120px" } });
const notes = h("input", { type: "checkbox", checked: settings.showNotes });
@@ -78,7 +81,7 @@ function profileSection() {
const form = h("form", { class: "stack" },
h("div", { class: "avatar-edit" }, avatarBox, h("div", { class: "row-btns", style: { display: "flex", gap: "6px", flexWrap: "wrap" } }, uploadBtn, removeBtn, file)),
h("div", { class: "row" }, field("Your name", ownerName, "Shown as the page heading."), field("Site title", siteTitle, "Shown in the header and browser tab.")),
h("div", { class: "row" }, field("Your name", ownerName, "Shown as the page heading."), field("Site title", siteTitle, "Shown in the header and browser tab. Leave blank to use “Your name's Canon”.")),
field("Tagline", tagline),
field("Entries per list on the home page", preview, "Visitors see the full list on each list's page. 0 shows only the heading."),
h("label", { class: "check" }, notes, "Show my notes to visitors"),
@@ -93,7 +96,7 @@ function profileSection() {
if (ok) {
renderAvatar();
const brand = document.querySelector(".brand-name");
if (brand) brand.textContent = settings.siteTitle || "TopDeck";
if (brand) brand.textContent = settings.siteTitleShown;
}
});
renderAvatar();
@@ -181,7 +184,7 @@ function secretInput(key, label, hintHTML) {
disabled: st.locked,
});
const hint = h("span", { class: "field-hint" });
if (st.locked) hint.textContent = `Set by the ${"TOPDECK_" + key.toUpperCase()} environment variable.`;
if (st.locked) hint.textContent = `Set by the ${"CANON_" + key.toUpperCase()} environment variable.`;
else if (hintHTML) hint.append(...hintHTML);
const wrap = field(label, input);
wrap.append(hint);
+2 -2
View File
@@ -1,4 +1,4 @@
// Shared helpers for TopDeck's pages. No build step, no dependencies.
// Shared helpers for Canon's pages. No build step, no dependencies.
/** Create an element: h("button", { class: "btn", onclick }, "Save") */
export function h(tag, attrs = {}, ...children) {
@@ -53,7 +53,7 @@ export class APIError extends Error {
/** Call the admin JSON API. Throws APIError with a human message. */
export async function api(method, path, body) {
const opts = { method, headers: { "X-TopDeck": "1" }, credentials: "same-origin" };
const opts = { method, headers: { "X-Canon": "1" }, credentials: "same-origin" };
if (body instanceof FormData) {
opts.body = body;
} else if (body !== undefined) {
+1 -1
View File
@@ -75,7 +75,7 @@ document.addEventListener("click", (e) => {
// Grid / list toggle on list pages, remembered per list in this browser.
const toggle = document.querySelector("[data-layout-toggle]");
if (toggle) {
const key = "topdeck:layout:" + toggle.dataset.key;
const key = "canon:layout:" + toggle.dataset.key;
const apply = (layout) => {
for (const { ol } of lists) {
ol.classList.toggle("items--grid", layout === "grid");
+1 -1
View File
@@ -39,7 +39,7 @@
<div class="container footer-inner">
<span>{{if .Site.OwnerName}}Curated by {{.Site.OwnerName}}{{else}}{{.Site.Title}}{{end}}</span>
<span class="footer-sep" aria-hidden="true">·</span>
<span>Powered by <a href="https://git.iamchrisama.com/cdnutter/topdeck" rel="noopener">TopDeck</a></span>
<span>Powered by <a href="https://git.iamchrisama.com/cdnutter/canon" rel="noopener">Canon</a></span>
</div>
</footer>
{{block "scripts" .}}{{end}}
+2 -2
View File
@@ -2,14 +2,14 @@
{{define "content"}}
<section class="auth-card">
<div class="auth-mark">{{icon "deck"}}</div>
<h1>Welcome to TopDeck</h1>
<h1>Welcome to Canon</h1>
<p class="muted">Create the owner account. Only you can sign in; everyone else just sees your lists.</p>
{{with .Data.Error}}<p class="alert alert--error" role="alert">{{icon "alert"}}{{.}}</p>{{end}}
<form method="post" action="/setup" class="stack">
<label class="field">
<span class="field-label">Setup code</span>
<input name="code" required autocomplete="one-time-code" placeholder="XXXX-XXXX" spellcheck="false" autocapitalize="characters">
<span class="field-hint">Printed in the server log when TopDeck starts. Run <code>docker compose logs topdeck</code> to see it.</span>
<span class="field-hint">Printed in the server log when Canon starts. Run <code>docker compose logs canon</code> to see it.</span>
</label>
<label class="field">
<span class="field-label">Username</span>