Finish the wonderland: System told truthfully, in eight tabs instead of ten
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// The manual's table of contents, with the titles read from the manual itself.
|
||||
//
|
||||
// Chapter files are numbered so their order is their filename, and each one
|
||||
// begins with its own title as a first-level heading. Reading that heading from
|
||||
// the file rather than repeating it in QML means a chapter cannot be renamed in
|
||||
// one place and not the other -- which is what had already happened by the time
|
||||
// anybody compared the two. There is deliberately no `label:` beside a filename
|
||||
// below; if one appears, the drift is back.
|
||||
//
|
||||
// Two pages need this list: the reader, and About's Manual card. Neither owns
|
||||
// it, so it lives here and both instantiate it. This is the one place the
|
||||
// chapter files are named.
|
||||
//
|
||||
// The chapters live beside the shell in manual/, reached through
|
||||
// Quickshell.shellDir: that resolves whether or not the repository is where it
|
||||
// usually is, and a path walked upward out of the shell directory does not.
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQml
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Display order is filename order.
|
||||
readonly property var chapters: [
|
||||
{ file: "01-coming-from-another-desktop.md" },
|
||||
{ file: "02-the-keyboard.md" },
|
||||
{ file: "03-windows-and-workspaces.md" },
|
||||
{ file: "04-when-something-breaks.md" },
|
||||
{ file: "05-making-it-yours.md" }
|
||||
]
|
||||
|
||||
// file -> first heading, filled in as each file is read.
|
||||
property var headings: ({})
|
||||
|
||||
// [{ file, title }]. A chapter whose file has not been read yet -- or
|
||||
// cannot be -- falls back to a title made out of its filename, so the list
|
||||
// is never blank and never a row of paths.
|
||||
readonly property var titled: root.chapters.map(chapter => ({
|
||||
file: chapter.file,
|
||||
title: String(root.headings[chapter.file] ?? "") !== ""
|
||||
? root.headings[chapter.file]
|
||||
: root.titleFromFilename(chapter.file)
|
||||
}))
|
||||
|
||||
function titleFromFilename(file: string): string {
|
||||
const stem = file.replace(/^\d+-/, "").replace(/\.md$/, "").replace(/-/g, " ");
|
||||
return stem.charAt(0).toUpperCase() + stem.slice(1);
|
||||
}
|
||||
|
||||
// The first level-one heading, which every chapter opens with.
|
||||
function headingOf(text: string): string {
|
||||
for (const line of String(text ?? "").split("\n")) {
|
||||
const match = /^#\s+(.+?)\s*$/.exec(line);
|
||||
if (match)
|
||||
return match[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Reassigned rather than mutated: a property change on a plain object does
|
||||
// not fire, and `titled` would keep answering with the old headings.
|
||||
function absorb(file: string, text: string): void {
|
||||
const next = Object.assign({}, root.headings);
|
||||
next[file] = root.headingOf(text);
|
||||
root.headings = next;
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
model: root.chapters
|
||||
|
||||
delegate: FileView {
|
||||
required property var modelData
|
||||
|
||||
path: Quickshell.shellDir + "/manual/" + modelData.file
|
||||
printErrors: false
|
||||
onLoaded: root.absorb(modelData.file, this.text())
|
||||
onLoadFailed: root.absorb(modelData.file, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user