diff --git a/crates/ork-server/src/p9.rs b/crates/ork-server/src/p9.rs index 3b32bb2..499df7b 100644 --- a/crates/ork-server/src/p9.rs +++ b/crates/ork-server/src/p9.rs @@ -506,8 +506,13 @@ impl Server { let mut fids = self.fids.write().unwrap(); if let Some(f) = fids.remove(&fid) { - // Flush write buffer if not rdwr mode - if !f.write_buf.is_empty() && f.node.rdwr_fn.is_none() { + // Flush the write buffer on clunk for non-rdwr files. A writable + // open with an empty buffer is a legitimate "set to empty" (the + // client opened with OTRUNC and wrote nothing, e.g. clearing a + // field), so flush whenever the fid was opened for writing — not + // only when the buffer is non-empty. + let opened_for_write = f.open_mode.map(|m| m.is_writable()).unwrap_or(false); + if f.node.rdwr_fn.is_none() && (opened_for_write || !f.write_buf.is_empty()) { if let Err(e) = f.node.write(&f.write_buf) { return self.error_response(tag, &e.to_string()); } diff --git a/ui/qml/OutlineView.qml b/ui/qml/OutlineView.qml index 8cd4ed7..296a955 100644 --- a/ui/qml/OutlineView.qml +++ b/ui/qml/OutlineView.qml @@ -26,6 +26,10 @@ ListView { property var dropInfo: null // {valid, level, parentId, parentTitle, beforeId} property real dropIndicatorY: 0 // content-y of the insertion bar property int dropGapAbove: -2 // visible row above the gap (-1 = top) + // Row whose grip has been "armed" by a press-and-hold, so its DragHandler + // may start a structural drag. This gates the drag behind a hold so a + // quick press-drag still scrolls the ListView (Flickable) instead. + property int dragArmed: -1 // Recompute the drop target from a pointer position in content coords. function updateDrop(contentX, contentY) { @@ -517,33 +521,54 @@ ListView { Layout.fillWidth: true spacing: 8 - // Drag grip: drag a section (and its subtree) to restructure. - // A live indicator shows the resolved drop target; releasing - // commits a refile. Horizontal position chooses the depth, - // vertical position chooses the gap. A DragHandler is used - // (not a MouseArea) so the enclosing ListView cannot steal the - // drag for flicking. - Label { + // Drag grip (left gutter): press-and-hold to arm, then drag to + // restructure the section (and its subtree). A live indicator + // shows the resolved drop target; releasing commits a refile. + // Horizontal position chooses the depth, vertical the gap. + // + // Gating the drag behind a hold resolves the conflict with the + // ListView (a Flickable): a quick press-drag scrolls the list; + // a hold arms the grip so its DragHandler takes the grab. + Rectangle { id: gripLabel - text: "⠿" - color: theme.placeholderTextColor - // A slightly larger hit target than the bare glyph. - Layout.preferredWidth: outline.baseFontSize + 6 - horizontalAlignment: Text.AlignHCenter - font.pointSize: outline.baseFontSize - 1 - opacity: gripHover.hovered || outline.dragRow === del.index ? 1.0 : 0.3 + Layout.preferredWidth: outline.baseFontSize + 8 + Layout.preferredHeight: outline.baseFontSize + 8 + radius: 3 + readonly property bool armed: outline.dragArmed === del.index + || outline.dragRow === del.index + color: armed ? theme.highlightColor + : (gripHover.hovered ? theme.alternateBaseColor : "transparent") + + Label { + anchors.centerIn: parent + text: "⠿" + color: gripLabel.armed ? theme.highlightedTextColor + : theme.placeholderTextColor + font.pointSize: outline.baseFontSize - 1 + opacity: gripHover.hovered || gripLabel.armed ? 1.0 : 0.4 + } HoverHandler { id: gripHover - cursorShape: Qt.SizeAllCursor + cursorShape: gripLabel.armed ? Qt.SizeAllCursor : Qt.OpenHandCursor } + // Press-and-hold to arm the drag without moving. + TapHandler { + id: gripHold + longPressThreshold: 0.35 // seconds + onLongPressed: outline.dragArmed = del.index + // A plain tap (no hold) shouldn't arm anything. + onTapped: outline.dragArmed = -1 + } + + // The structural drag itself. Only grabs once armed, so the + // Flickable keeps scrolling for un-armed press-drags. DragHandler { id: gripDrag - target: null // we track, we don't move the item + target: null // track only; don't move the item + enabled: gripLabel.armed dragThreshold: 3 - // Take the grab and refuse to yield it to the ListView - // (a Flickable), which would otherwise steal the drag. grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType @@ -552,6 +577,7 @@ ListView { outline.dragRow = del.index } else { outline.commitDrop() + outline.dragArmed = -1 } } onCentroidChanged: {