1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
import QtQuick
import "Toml.js" as T
// One config.toml's draft, the store qtmaildir's Setting and BoolSetting
// edit. ponytail: a list entry holding a space cannot be entered here; edit
// the file. Fields hand it text; it writes the type of the key's default, the
// type sb-builder checks on load: a list is words split on spaces, a number
// is a number, a switch is true or false.
QtObject {
id: st
// The file as last read or written, the draft's start, and the draft.
property string text: ""
property string base: ""
property string draft: ""
readonly property bool dirty: st.draft !== st.base
// {section: {key: value}}, top-level keys under "".
property var defaults: ({})
property var invalid: ({})
readonly property bool valid: Object.keys(st.invalid).length === 0
property string error: ""
// Assigned only when the set changes, as Qtm does: a Repeater bound to a
// fresh array per keystroke rebuilds the row being typed in.
property var manifests: []
// The helper takes a [manifests] table whole, in place of its default
// one, so with none in the file the default rows are the ones in effect
// and are listed; the first edit writes them all before its own.
readonly property bool manifestsDefault: !T.sections(st.draft).includes("manifests")
onDraftChanged: {
const k = st.manifestsDefault ? Object.keys(st.fallback("manifests") ?? {})
: T.entries(st.draft, "manifests").map(e => e.key);
if (JSON.stringify(k) !== JSON.stringify(st.manifests)) st.manifests = k;
}
function seedManifests(section) {
if (section !== "manifests" || !st.manifestsDefault) return;
const d = st.fallback("manifests") ?? {};
for (const k in d) st.draft = T.set(st.draft, "manifests", k, d[k]);
}
function fallback(section, key) { return key === undefined ? st.defaults[section] : (st.defaults[section] || {})[key]; }
function show(v) { return v === undefined ? undefined : Array.isArray(v) ? v.join(" ") : String(v); }
function placeholder(section, key) { return st.show(st.fallback(section, key)) ?? ""; }
function get(section, key) {
const v = T.get(st.draft, section, key);
// A listed default manifest shows its URL, so editing starts from it.
return st.show(v === undefined && section === "manifests" && st.manifestsDefault ? st.fallback(section, key) : v);
}
// The effective value, typed: the file's, else the default.
function value(section, key) { return T.get(st.draft, section, key) ?? st.fallback(section, key); }
function entries(section) { return T.entries(st.draft, section); }
function put(section, key, v) {
st.seedManifests(section);
try { st.draft = T.set(st.draft, section, key, v); } catch (e) {
st.error = e.message;
st.markInvalid(section + "/" + key, true); // hold Save back
}
}
// A field's text; null or "" unsets.
function set(section, key, t) {
// An empty manifest URL is a row still being typed: unsetting would
// remove it. Mark it invalid instead; Setting marks the id valid just
// before calling set, so this mark, made after, is the one that stays.
if (section === "manifests" && t === "") return st.markInvalid(section + "/" + key, true);
if (t === null || t === "") return st.put(section, key, null);
const d = st.fallback(section, key);
st.put(section, key, Array.isArray(d) ? t.split(/\s+/).filter(x => x !== "")
: typeof d === "number" ? Number(t)
: typeof d === "boolean" ? t === "true" : t);
}
function remove(section, key) {
st.seedManifests(section);
st.draft = T.remove(st.draft, section, key);
}
function markInvalid(id, bad) {
if (!!st.invalid[id] === bad) return;
const next = Object.assign({}, st.invalid);
if (bad) next[id] = true; else delete next[id];
st.invalid = next;
}
// The file as read: a clean draft follows it, a dirty one is kept and
// its save is refused.
function loaded(t) {
st.text = t;
if (!st.dirty) { st.base = t; st.draft = t; }
}
function discard() {
st.error = "";
st.invalid = ({});
st.base = st.text;
st.draft = st.text;
}
}
|