// The manual. // // Panama's docs/ directory is engineering artifacts: design specs, plans, an // upstream ledger. None of it is written for the person using the desktop, // and the person using the desktop is the one with questions. // // So the manual is prose, in chapters, rendered here rather than opened in a // browser. Keeping it in Settings means it is reachable from the same place // as everything else it talks about, and a link to a settings page can just // be a settings page -- see onLinkActivated below, where that stopped being a // figure of speech. // // The chapter list and the chapter titles both come from ManualChapters, which // reads each file's own first heading. This page used to carry a hand-written // copy of those titles under a comment claiming they were read from the files. // // One Text per chapter, never the whole manual at once: Text has an implicit // texture size limit, and a document long enough to hit it fails by going // blank rather than by complaining. import Quickshell import Quickshell.Io import QtQuick import qs.config import qs.services SettingsPage { id: root title: "Manual" lede: "How this desktop works, for the person using it." readonly property var chapters: contents.titled property int current: 0 // A deep link picks the chapter once and then lets go, so arriving at a // chapter does not pin you to it. Same consume-once idiom ShellState uses // for settings sections. Component.onCompleted: { const section = ShellState.takeSettingsSection(); for (let index = 0; index < root.chapters.length; index++) { if (root.chapters[index].file.indexOf(section) >= 0 && section !== "") { root.current = index; break; } } } ManualChapters { id: contents } SettingsTabs { tabs: root.chapters.map((chapter, index) => ({ value: String(index), label: chapter.title })) current: String(root.current) onSelected: value => root.current = parseInt(value, 10) } SettingsCard { FileView { id: chapterFile path: Quickshell.shellDir + "/manual/" + root.chapters[root.current].file printErrors: false onLoaded: body.text = this.text() onLoadFailed: body.text = "This chapter could not be read.\n\nIt should be at `" + chapterFile.path + "`." } Text { id: body width: parent.width textFormat: Text.MarkdownText wrapMode: Text.WordWrap color: Theme.fgDim linkColor: Theme.accent font.family: Theme.fontFamily font.pixelSize: Theme.fontSize // Markdown's own line height is tight for a wall of prose at this // size; a document read once needs to be easy on the first pass. lineHeight: 1.35 // A link that names a settings page opens that page instead of a // browser, which is the whole reason the manual lives inside // Settings. Anything else -- a project URL, a wiki -- still leaves // the desktop. // // The scheme is Panama's own, so a chapter can never accidentally // hand an http link to openSettings, and an unknown page cannot be // smuggled in either: SettingsRoutes.resolve turns anything it does // not recognize into Home rather than a blank loader. A fragment, // where there is one, is the section within the page. onLinkActivated: link => { const target = String(link ?? ""); const scheme = "panama://settings/"; if (target.indexOf(scheme) !== 0) { Qt.openUrlExternally(target); return; } const rest = target.slice(scheme.length); const hash = rest.indexOf("#"); if (hash < 0) ShellState.openSettings(rest); else ShellState.openSettingsSection(rest.slice(0, hash), rest.slice(hash + 1)); } } } }