From 6f4ddf15b12c02aef07b82d85408e8f512435676 Mon Sep 17 00:00:00 2001 From: Steven Spungin Date: Sun, 27 Sep 2026 09:34:09 -0400 Subject: [PATCH] fix: release the Text file lock when leaving the editor with the back button Leaving a Markdown file with the back button popped NCViewerDirectEditing without running Text's close(), so the editing session was never closed and the file stayed "Locked by Text" on the server. Text's own close button saves, closes the session (releasing the lock) and then posts "close". For the Text editor, route the back button through that close button and let the existing "close" message pop the view, with a 5 s fallback so navigation can never get stuck. Fixes nextcloud/text#8858 Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Steven Spungin --- .../NCViewerDirectEditing.swift | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift index 367583b844..572c566371 100644 --- a/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift +++ b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift @@ -24,6 +24,7 @@ final class NCViewerDirectEditing: UIViewController, WKNavigationDelegate, WKScr let database = NCManageDatabase.shared let global = NCGlobal.shared var items: [UIBarButtonItem] = [] + private var isClosingTextEditor = false @MainActor var session: NCSession.Session { @@ -90,6 +91,19 @@ final class NCViewerDirectEditing: UIViewController, WKNavigationDelegate, WKScr navigationItem.trailingItemGroups = [group] navigationItem.leftBarButtonItems = nil + // Text releases its lock on the file only when its own close runs (the in-page close + // button). Leaving with the system back button skipped that and left the file locked + // for everyone else, so for Text the back button runs the same close first. + if editor == global.editorText { + navigationItem.hidesBackButton = true + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: UIImage(systemName: "chevron.backward"), + style: .plain, + target: self, + action: #selector(closeTextEditor) + ) + } + // Prevent back navigation gesture of iOS >= 26 as that can cause unintended swipe backs if #available(iOS 26.0, *) { navigationController?.interactiveContentPopGestureRecognizer?.isEnabled = false @@ -205,6 +219,42 @@ final class NCViewerDirectEditing: UIViewController, WKNavigationDelegate, WKScr navigationController?.popViewController(animated: true) } + /// Leaves the Text editor the same way its own close button does: Text saves, closes its + /// session (which releases the file lock) and then posts "close", which calls viewUnload(). + @objc private func closeTextEditor() { + guard !isClosingTextEditor else { + return + } + isClosingTextEditor = true + + let pressClose = """ + (function () { + const button = document.querySelector('#direct-editor .icon-close'); + if (!button) { return false; } + button.click(); + return true; + })(); + """ + webView.evaluateJavaScript(pressClose) { [weak self] result, _ in + guard let self else { + return + } + // The editor has not loaded yet: there is no session to close. + guard (result as? Bool) == true else { + self.viewUnload() + return + } + // Text waits up to 2 seconds for a save. Never leave the user stuck if "close" + // does not arrive. + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in + guard let self, self.navigationController?.topViewController === self else { + return + } + self.viewUnload() + } + } + } + // MARK: - NotificationCenter @objc func keyboardDidShow(notification: Notification) {