From f200f7007272129bf59fef3e0b0e0a19f5bd362c Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 16 Oct 2026 11:29:09 +0200 Subject: feat(git): add keys per section with a + beside each heading The single Add line at the bottom took a full section.key and left the user to type the section right. Each heading now carries a + that opens a key and value row under it; git config --add writes the key into that existing section, among its neighbours, rather than at the end of the file. The typed text lives on the page, not in the fields, so a failed add keeps it across the reload that rebuilds every row. A new section is made in the editor. Co-Authored-By: Claude Opus 5.5 --- desktop/modules/git/GitPage.qml | 229 ++++++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 101 deletions(-) (limited to 'desktop/modules/git/GitPage.qml') diff --git a/desktop/modules/git/GitPage.qml b/desktop/modules/git/GitPage.qml index c91f050..6edf085 100644 --- a/desktop/modules/git/GitPage.qml +++ b/desktop/modules/git/GitPage.qml @@ -36,6 +36,20 @@ Column { // the page is closed. Not named `gm`, the module's id (it would bind to itself). required property QtObject owner + // The section whose add row is open, and what is typed in it. The text + // lives here rather than in the fields because every reload rebuilds the + // rows, and a failed add should keep what was typed. + property string adding: "" + property string newKey: "" + property string newValue: "" + onAddingChanged: { newKey = ""; newValue = ""; errors = ({}); } + + function add() { + if (page.newKey.trim() === "") return; + page.write(["add", page.adding + "." + page.newKey.trim(), page.newValue], "add", + () => page.adding = ""); + } + function tag(e) { return e.name + "=" + e.value; } // One Process per call, so reads and writes never share one (a running @@ -98,6 +112,94 @@ Column { font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } + // The add row under a section's heading: key and value. Enter in the key + // moves to the value, Enter there adds, Escape closes the row. + Component { + id: addRow + Column { + spacing: 4 + + Item { + width: parent.width + height: 34 + + Rectangle { + id: keyBox + anchors.left: parent.left + width: parent.width * 0.32 + height: parent.height + radius: 8 + color: Theme.surface + TextInput { + id: keyIn + anchors { fill: parent; leftMargin: 10; rightMargin: 10 } + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + text: page.newKey + onTextEdited: page.newKey = text + onAccepted: valIn.forceActiveFocus() + Keys.onEscapePressed: page.adding = "" + Component.onCompleted: forceActiveFocus() + Text { + anchors.verticalCenter: parent.verticalCenter + visible: parent.text === "" + text: "key" + color: Theme.subtext + font: parent.font + } + } + } + + Rectangle { + anchors { left: keyBox.right; leftMargin: 8; right: addBtn.left; rightMargin: 8 } + height: parent.height + radius: 8 + color: Theme.surface + TextInput { + id: valIn + anchors { fill: parent; leftMargin: 10; rightMargin: 10 } + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + text: page.newValue + onTextEdited: page.newValue = text + onAccepted: page.add() + Keys.onEscapePressed: page.adding = "" + Text { + anchors.verticalCenter: parent.verticalCenter + visible: parent.text === "" + text: "value" + color: Theme.subtext + font: parent.font + } + } + } + + Button { + id: addBtn + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + text: "Add" + enabled: page.newKey.trim() !== "" + onClicked: page.add() + } + } + + Text { + width: parent.width + visible: !!page.errors.add + text: page.errors.add ?? "" + wrapMode: Text.Wrap + color: Theme.red + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + } + } + Repeater { model: page.entries @@ -110,14 +212,33 @@ Column { spacing: 4 enabled: !page.busy - // A section's heading, above its first row. - Text { + // A section's heading, above its first row, with + to add a key + // to it. git config --add writes into the existing section, so + // the new key lands with its neighbours in the file. + Item { + id: heading visible: row.index === 0 || page.entries[row.index - 1].section !== row.modelData.section - topPadding: 10 - bottomPadding: 2 - text: row.modelData.section - font { family: Theme.fontFamily; pixelSize: Theme.fontSize; bold: true } - color: Theme.text + width: parent.width + height: 40 + + Text { + anchors { left: parent.left; bottom: parent.bottom; bottomMargin: 6 } + text: row.modelData.section + font { family: Theme.fontFamily; pixelSize: Theme.fontSize; bold: true } + color: Theme.text + } + Button { + anchors { right: parent.right; bottom: parent.bottom; bottomMargin: 2 } + text: "+" + onClicked: page.adding = page.adding === row.modelData.section ? "" : row.modelData.section + } + } + + Loader { + width: parent.width + active: heading.visible && page.adding === row.modelData.section + visible: active + sourceComponent: addRow } // An Item with anchors, not a Row: a Row sizes to its children @@ -183,98 +304,4 @@ Column { } } } - - // Add: section.key and a value. The fields clear only when git took it. - Text { - topPadding: 10 - bottomPadding: 2 - text: "Add" - font { family: Theme.fontFamily; pixelSize: Theme.fontSize; bold: true } - color: Theme.text - } - - Item { - width: parent.width - height: 34 - enabled: !page.busy - - Rectangle { - id: newKeyBox - anchors { left: parent.left } - width: parent.width * 0.32 - height: parent.height - radius: 8 - color: Theme.surface - TextInput { - id: newKey - anchors { fill: parent; leftMargin: 10; rightMargin: 10 } - verticalAlignment: TextInput.AlignVCenter - clip: true - selectByMouse: true - color: Theme.text - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } - // Typed text is cleared first, as a row's edit is undone; - // an empty field lets Escape through, to leave the page. - Keys.onEscapePressed: event => { - if (text !== "") text = ""; - else event.accepted = false; - } - Text { - anchors.verticalCenter: parent.verticalCenter - visible: parent.text === "" - text: "section.key" - color: Theme.subtext - font: parent.font - } - } - } - - Rectangle { - anchors { left: newKeyBox.right; leftMargin: 8; right: addBtn.left; rightMargin: 8 } - height: parent.height - radius: 8 - color: Theme.surface - TextInput { - id: newValue - anchors { fill: parent; leftMargin: 10; rightMargin: 10 } - verticalAlignment: TextInput.AlignVCenter - clip: true - selectByMouse: true - color: Theme.text - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } - // Typed text is cleared first, as a row's edit is undone; - // an empty field lets Escape through, to leave the page. - Keys.onEscapePressed: event => { - if (text !== "") text = ""; - else event.accepted = false; - } - onAccepted: if (addBtn.enabled) addBtn.clicked() - Text { - anchors.verticalCenter: parent.verticalCenter - visible: parent.text === "" - text: "value" - color: Theme.subtext - font: parent.font - } - } - } - - Button { - id: addBtn - anchors { right: parent.right; verticalCenter: parent.verticalCenter } - text: "Add" - enabled: newKey.text.trim() !== "" - onClicked: page.write(["add", newKey.text.trim(), newValue.text], "add", - () => { newKey.text = ""; newValue.text = ""; }) - } - } - - Text { - width: parent.width - visible: !!page.errors.add - text: page.errors.add ?? "" - wrapMode: Text.Wrap - color: Theme.red - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } - } } -- cgit v1.2.3