diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8bd5feb863..e80c0f9be6 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -304,7 +304,7 @@ F72CA05C2F5051DB002E2F06 /* AlertActionBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72CA05B2F5051DB002E2F06 /* AlertActionBannerView.swift */; }; F72CD63A25C19EBF00F46F9A /* NCAutoUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */; }; F72D1007210B6882009C96B7 /* NCPushNotificationEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = F72D1005210B6882009C96B7 /* NCPushNotificationEncryption.m */; }; - F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */; }; + F72D404923D2082500A97FD0 /* NCViewerDirectEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72D404823D2082500A97FD0 /* NCViewerDirectEditing.swift */; }; F72D7EB7263B1207000B3DFC /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = F72D7EB6263B1207000B3DFC /* MarkdownKit */; }; F72DA9B425F53E4E00B87DB1 /* SwiftRichString in Frameworks */ = {isa = PBXBuildFile; productRef = F72DA9B325F53E4E00B87DB1 /* SwiftRichString */; }; F72EA95228B7BA2A00C88F0C /* DashboardWidgetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72EA95128B7BA2A00C88F0C /* DashboardWidgetProvider.swift */; }; @@ -339,7 +339,7 @@ F7386E482DA90E0F009A00F6 /* NCAppVersionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7386E452DA90E02009A00F6 /* NCAppVersionManager.swift */; }; F73BC74F2F23811E003170C2 /* WarningBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7DF7B3E2F1A2EE400514020 /* WarningBannerView.swift */; }; F73CB3B222E072A000AD728E /* NCShareHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F73CB3B122E072A000AD728E /* NCShareHeaderView.xib */; }; - F73D11FA253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F73D11F9253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard */; }; + F73D11FA253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F73D11F9253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard */; }; F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */; }; F73EF7A82B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */; }; F73EF7A92B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */; }; @@ -430,6 +430,7 @@ F758B45A212C564000515F55 /* NCScan.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F758B457212C564000515F55 /* NCScan.storyboard */; }; F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45D212C569C00515F55 /* NCScanCell.swift */; }; F758B460212C56A400515F55 /* NCScan.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45F212C56A400515F55 /* NCScan.swift */; }; + F75A60552FB4493A00F8247E /* NCDirectEditorAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A60542FB4493A00F8247E /* NCDirectEditorAdapter.swift */; }; F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; }; F75A9EE723796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; }; F75C0C4823D1FAE300163CC8 /* NCRichWorkspaceCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75C0C4723D1FAE300163CC8 /* NCRichWorkspaceCommon.swift */; }; @@ -1370,7 +1371,7 @@ F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAutoUpload.swift; sourceTree = ""; }; F72D1005210B6882009C96B7 /* NCPushNotificationEncryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCPushNotificationEncryption.m; sourceTree = ""; }; F72D1006210B6882009C96B7 /* NCPushNotificationEncryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCPushNotificationEncryption.h; sourceTree = ""; }; - F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerNextcloudText.swift; sourceTree = ""; }; + F72D404823D2082500A97FD0 /* NCViewerDirectEditing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerDirectEditing.swift; sourceTree = ""; }; F72EA95128B7BA2A00C88F0C /* DashboardWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardWidgetProvider.swift; sourceTree = ""; }; F72EA95328B7BABA00C88F0C /* FilesWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesWidgetProvider.swift; sourceTree = ""; }; F72EA95728B7BC4F00C88F0C /* FilesData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesData.swift; sourceTree = ""; }; @@ -1397,7 +1398,7 @@ F7386E452DA90E02009A00F6 /* NCAppVersionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAppVersionManager.swift; sourceTree = ""; }; F73CB3B122E072A000AD728E /* NCShareHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCShareHeaderView.xib; sourceTree = ""; }; F73CB5771ED46807005F2A5A /* NCBridgeSwift.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCBridgeSwift.h; sourceTree = ""; }; - F73D11F9253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCViewerNextcloudText.storyboard; sourceTree = ""; }; + F73D11F9253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCViewerDirectEditing.storyboard; sourceTree = ""; }; F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Comments.swift"; sourceTree = ""; }; F73EF7B62B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+ExternalSites.swift"; sourceTree = ""; }; F73EF7BE2B02250B0087E6E9 /* NCManageDatabase+GPS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+GPS.swift"; sourceTree = ""; }; @@ -1435,6 +1436,7 @@ F758B457212C564000515F55 /* NCScan.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCScan.storyboard; sourceTree = ""; }; F758B45D212C569C00515F55 /* NCScanCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScanCell.swift; sourceTree = ""; }; F758B45F212C56A400515F55 /* NCScan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScan.swift; sourceTree = ""; }; + F75A60542FB4493A00F8247E /* NCDirectEditorAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCDirectEditorAdapter.swift; sourceTree = ""; }; F75A9EE523796C6F0044CFCE /* NCNetworking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCNetworking.swift; sourceTree = ""; }; F75B91E21ECAE17800199C96 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; F75B91F71ECAE26300199C96 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; @@ -2421,13 +2423,14 @@ path = Offline; sourceTree = ""; }; - F73D11FF253C5F5400DF9BEC /* NCViewerNextcloudText */ = { + F73D11FF253C5F5400DF9BEC /* NCViewerDirectEditing */ = { isa = PBXGroup; children = ( - F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */, - F73D11F9253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard */, + F75A60542FB4493A00F8247E /* NCDirectEditorAdapter.swift */, + F72D404823D2082500A97FD0 /* NCViewerDirectEditing.swift */, + F73D11F9253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard */, ); - path = NCViewerNextcloudText; + path = NCViewerDirectEditing; sourceTree = ""; }; F74D3DB81BAC1941000BAE4B /* Networking */ = { @@ -2782,7 +2785,7 @@ F79018B1240962C7007C9B6D /* NCViewerMedia */, F723986A253C9C0E00257F49 /* NCViewerQuickLook */, F76D3CEF2428B3DD005DFA87 /* NCViewerPDF */, - F73D11FF253C5F5400DF9BEC /* NCViewerNextcloudText */, + F73D11FF253C5F5400DF9BEC /* NCViewerDirectEditing */, F7239861253C95D500257F49 /* NCViewerRichdocument */, ); path = Viewer; @@ -3992,7 +3995,7 @@ F704B5E32430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard in Resources */, F7EDE509262DA9D600414FE6 /* NCSelectCommandViewSelect.xib in Resources */, F732D23327CF8AED000B0F1B /* NCPlayerToolBar.xib in Resources */, - F73D11FA253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard in Resources */, + F73D11FA253C5F4800DF9BEC /* NCViewerDirectEditing.storyboard in Resources */, F7EDE51B262DD0C400414FE6 /* NCSelectCommandViewCopyMove.xib in Resources */, F7FF2CB12842159500EBB7A1 /* NCSectionHeader.xib in Resources */, F7D1612023CF19E30039EBBF /* NCViewerRichWorkspace.storyboard in Resources */, @@ -4544,7 +4547,7 @@ F7D60CAF2C941ACB008FBFDD /* NCMediaPinchGesture.swift in Sources */, F71916142E2901FB00E13E96 /* NCNetworking+Upload.swift in Sources */, F704B5E92430C0B800632F5F /* NCCreateFormUploadConflictCell.swift in Sources */, - F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */, + F72D404923D2082500A97FD0 /* NCViewerDirectEditing.swift in Sources */, AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */, F77BB746289984CA0090FC19 /* UIViewController+Extension.swift in Sources */, F700510522DF6A89003A3356 /* NCShare.swift in Sources */, @@ -4784,6 +4787,7 @@ F7D61EA82EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */, F79FFB262A97C24A0055EEA4 /* NCNetworkingE2EEMarkFolder.swift in Sources */, F70D8D8124A4A9BF000A5756 /* NCNetworkingProcess.swift in Sources */, + F75A60552FB4493A00F8247E /* NCDirectEditorAdapter.swift in Sources */, F3A0479A2BD2668800658E7B /* NCAssistantTaskDetail.swift in Sources */, F71D2FB72E09BBD700B751CC /* NCAutoUploadModel.swift in Sources */, F38F71252B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift in Sources */, @@ -6132,8 +6136,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - kind = exactVersion; - version = 7.3.1; + branch = main; + kind = branch; }; }; F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = { diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index bf9a829c4b..2e44150c79 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -259,9 +259,8 @@ extension tableMetadata { directEditingEditors.isEmpty { // RichDocument: Collabora return true - } else if directEditingEditors.contains("nextcloud text") || directEditingEditors.contains("onlyoffice") { - // DirectEditing: Nextcloud Text - OnlyOffice - return true + } else if !directEditingEditors.isEmpty { + return true } return false } @@ -282,12 +281,8 @@ extension tableMetadata { guard (classFile == NKTypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { return false } - let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } - - if editors.contains("nextcloud text") || editors.contains("onlyoffice") { - return true - } - return false + let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType) + return !editors.isEmpty } var isPDF: Bool { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift index 2733be4b87..279de6f3a5 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later import UIKit +import UniformTypeIdentifiers import NextcloudKit import RealmSwift import LucidBanner @@ -70,7 +71,14 @@ extension NCCollectionViewCommon: UIEditMenuInteractionDelegate { for (index, items) in UIPasteboard.general.items.enumerated() { for item in items { let capabilities = await NKCapabilities.shared.getCapabilities(for: session.account) - let results = NKFilePropertyResolver().resolve(inUTI: item.key, capabilities: capabilities) + let identifier = item.key + let resolvedType = UTType(mimeType: identifier) ?? UTType(identifier) + let resolvedMimeType = resolvedType?.preferredMIMEType ?? identifier + let resolvedExtension = resolvedType?.preferredFilenameExtension ?? "" + let results = NKFilePropertyResolver().resolve(mimeType: resolvedMimeType, + fileExtension: resolvedExtension, + typeIdentifier: resolvedType?.identifier ?? identifier, + capabilities: capabilities) guard let data = UIPasteboard.general.data(forPasteboardType: item.key, inItemSet: IndexSet([index]))?.first else { diff --git a/iOSClient/Main/Create/NCCreate.swift b/iOSClient/Main/Create/NCCreate.swift index d4042e2e92..5db2c8818e 100644 --- a/iOSClient/Main/Create/NCCreate.swift +++ b/iOSClient/Main/Create/NCCreate.swift @@ -25,12 +25,8 @@ class NCCreate: NSObject { var options = NKRequestOptions() let serverUrl = controller.currentServerUrl() - if let creatorId, editorId == "text" || editorId == "onlyoffice" { - if editorId == "onlyoffice" { - options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentOnlyOffice()) - } else if editorId == "text" { - options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentNCText()) - } + if let creatorId, let adapter = NCDirectEditorAdapter.resolve(from: [editorId]) { + options = NKRequestOptions(customUserAgent: adapter.userAgent(utility)) let results = await NextcloudKit.shared.textCreateFileAsync(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateId, account: account, options: options) { task in Task { let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, @@ -94,13 +90,8 @@ class NCCreate: NSObject { var selectedTemplate = NKEditorTemplate() var ext: String = "" - if editorId == "text" || editorId == "onlyoffice" { - var options = NKRequestOptions() - if editorId == "onlyoffice" { - options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentOnlyOffice()) - } else if editorId == "text" { - options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentNCText()) - } + if let adapter = NCDirectEditorAdapter.resolve(from: [editorId]) { + let options = NKRequestOptions(customUserAgent: adapter.userAgent(NCUtility())) let results = await NextcloudKit.shared.textGetListOfTemplatesAsync(account: account, options: options) { task in Task { @@ -128,15 +119,7 @@ class NCCreate: NSObject { if templates.isEmpty { var temp = NKEditorTemplate() temp.identifier = "" - if editorId == "text" { - temp.ext = "md" - } else if editorId == "onlyoffice" && templateId == "document" { - temp.ext = "docx" - } else if editorId == "onlyoffice" && templateId == "spreadsheet" { - temp.ext = "xlsx" - } else if editorId == "onlyoffice" && templateId == "presentation" { - temp.ext = "pptx" - } + temp.ext = adapter.defaultExt(templateId) temp.name = "Empty" temp.preview = "" templates.append(temp) diff --git a/iOSClient/Main/NCMainNavigationController.swift b/iOSClient/Main/NCMainNavigationController.swift index beb91f757e..efc12f1e7f 100644 --- a/iOSClient/Main/NCMainNavigationController.swift +++ b/iOSClient/Main/NCMainNavigationController.swift @@ -294,7 +294,7 @@ class NCMainNavigationController: UINavigationController, UINavigationController !(topViewController is NCViewerMediaPage), !(topViewController is NCViewerPDF), !(topViewController is NCViewerRichDocument), - !(topViewController is NCViewerNextcloudText) + !(topViewController is NCViewerDirectEditing) else { return } diff --git a/iOSClient/Menu/NCContextMenuPlus.swift b/iOSClient/Menu/NCContextMenuPlus.swift index 2d1cdefdbb..0782aed87d 100644 --- a/iOSClient/Menu/NCContextMenuPlus.swift +++ b/iOSClient/Menu/NCContextMenuPlus.swift @@ -8,6 +8,13 @@ import NextcloudKit @MainActor class NCContextMenuPlus: NSObject { + struct CreatorMenuInfo { + let titleKey: String + let templateId: String + let icon: String + let sortOrder: Int + } + let menuToolbar: UIToolbar? let controller: NCMainTabBarController? @@ -20,6 +27,19 @@ class NCContextMenuPlus: NSObject { self.controller = controller } + nonisolated static func menuInfo(for ext: String) -> CreatorMenuInfo? { + switch ext.lowercased() { + case "docx": + return CreatorMenuInfo(titleKey: "_create_new_document_", templateId: "document", icon: "doc.text", sortOrder: 0) + case "xlsx": + return CreatorMenuInfo(titleKey: "_create_new_spreadsheet_", templateId: "spreadsheet", icon: "tablecells", sortOrder: 1) + case "pptx": + return CreatorMenuInfo(titleKey: "_create_new_presentation_", templateId: "presentation", icon: "play.rectangle", sortOrder: 2) + default: + return nil + } + } + func create(session: NCSession.Session) async { guard let controller, let menuToolbar else { return @@ -38,7 +58,7 @@ class NCContextMenuPlus: NSObject { var menuActionElement: [UIMenuElement] = [] var menuE2EEElement: [UIMenuElement] = [] var menuTextElement: [UIMenuElement] = [] - var menuOnlyOfficeElement: [UIMenuElement] = [] + var menuDirectEditingElement: [UIMenuElement] = [] var menuRichDocumentElement: [UIMenuElement] = [] // ------------------------------- ACTION @@ -214,59 +234,53 @@ class NCContextMenuPlus: NSObject { }) } - // ------------------------------- ONLY OFFICE + // ------------------------------- DIRECT EDITING CREATORS (onlyoffice, eurooffice, …) - if let creator = capabilities.directEditingCreators.first(where: { $0.editor == "onlyoffice" && $0.identifier == "onlyoffice_docx"}) { - menuOnlyOfficeElement.append(UIAction(title: NSLocalizedString("_create_new_document_", comment: ""), - image: utility.loadImage(named: "doc.text", colors: [NCBrandColor.shared.documentIconColor])) { _ in - Task { @MainActor in - let createDocument = NCCreate() - let templates = await createDocument.getTemplate(editorId: "onlyoffice", templateId: "document", account: session.account) - let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + templates.ext, account: session.account, serverUrl: serverUrl) - let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session) + let creatorsByEditor = Dictionary(grouping: capabilities.directEditingCreators, by: \.editor) + for editorId in creatorsByEditor.keys.sorted() { + guard NCDirectEditorAdapter.resolve(from: [editorId]) != nil, + editorId != "text" else { continue } - await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: "onlyoffice", creatorId: creator.identifier, templateId: templates.selectedTemplate.identifier, account: session.account) + let sortedCreators = creatorsByEditor[editorId]! + .compactMap { creator -> (NKEditorDetailsCreator, CreatorMenuInfo)? in + guard let info = NCContextMenuPlus.menuInfo(for: creator.ext) else { return nil } + return (creator, info) } - }) - } - - if let creator = capabilities.directEditingCreators.first(where: { $0.editor == "onlyoffice" && $0.identifier == "onlyoffice_xlsx"}) { - menuOnlyOfficeElement.append(UIAction(title: NSLocalizedString("_create_new_spreadsheet_", comment: ""), - image: utility.loadImage(named: "tablecells", colors: [NCBrandColor.shared.spreadsheetIconColor])) { _ in - Task { @MainActor in - let createDocument = NCCreate() - let templates = await createDocument.getTemplate(editorId: "onlyoffice", templateId: "spreadsheet", account: session.account) - let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + templates.ext, account: session.account, serverUrl: serverUrl) - let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session) - - await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: "onlyoffice", creatorId: creator.identifier, templateId: templates.selectedTemplate.identifier, account: session.account) - } - - }) - } - - if let creator = capabilities.directEditingCreators.first(where: { $0.editor == "onlyoffice" && $0.identifier == "onlyoffice_pptx"}) { - menuOnlyOfficeElement.append(UIAction(title: NSLocalizedString("_create_new_presentation_", comment: ""), - image: utility.loadImage(named: "play.rectangle", colors: [NCBrandColor.shared.presentationIconColor])) { _ in - Task { @MainActor in - let createDocument = NCCreate() - let templates = await createDocument.getTemplate(editorId: "onlyoffice", templateId: "presentation", account: session.account) - let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + templates.ext, account: session.account, serverUrl: serverUrl) - let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session) - - await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: "onlyoffice", creatorId: creator.identifier, templateId: templates.selectedTemplate.identifier, account: session.account) - } - }) + .sorted { $0.1.sortOrder < $1.1.sortOrder } + + for (creator, info) in sortedCreators { + menuDirectEditingElement.append(UIAction( + title: NSLocalizedString(info.titleKey, comment: ""), + image: utility.loadImage(named: info.icon, colors: [info.iconColor]) + ) { _ in + Task { @MainActor in + let createDocument = NCCreate() + let fileExt: String + let templateIdentifier: String + if creator.templates { + let result = await createDocument.getTemplate(editorId: editorId, templateId: info.templateId, account: session.account) + fileExt = result.ext + templateIdentifier = result.selectedTemplate.identifier + } else { + fileExt = creator.ext + templateIdentifier = "" + } + let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + "." + fileExt, account: session.account, serverUrl: serverUrl) + let fileNamePath = utilityFileSystem.getRelativeFilePath(String(describing: fileName), serverUrl: serverUrl, session: session) + await createDocument.createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: editorId, creatorId: creator.identifier, templateId: templateIdentifier, account: session.account) + } + }) + } } } let menuAction = UIMenu(title: "", options: .displayInline, children: menuActionElement) let menuText = UIMenu(title: "", options: .displayInline, children: menuTextElement) let menuE2EE = UIMenu(title: "", options: .displayInline, children: menuE2EEElement) - let menuOnlyOffice = UIMenu(title: "", options: .displayInline, children: menuOnlyOfficeElement) + let menuDirectEditing = UIMenu(title: "", options: .displayInline, children: menuDirectEditingElement) let menuRichDocument = UIMenu(title: "", options: .displayInline, children: menuRichDocumentElement) - let plusMenu = UIMenu(children: [menuAction, menuE2EE, menuText, menuRichDocument, menuOnlyOffice]) + let plusMenu = UIMenu(children: [menuAction, menuE2EE, menuText, menuRichDocument, menuDirectEditing]) let config = UIImage.SymbolConfiguration(pointSize: 25, weight: .thin) let plusImage = UIImage(systemName: "plus.circle.fill", withConfiguration: config) @@ -342,3 +356,14 @@ class NCContextMenuPlus: NSObject { } } } + +@MainActor +extension NCContextMenuPlus.CreatorMenuInfo { + var iconColor: UIColor { + switch templateId { + case "spreadsheet": return NCBrandColor.shared.spreadsheetIconColor + case "presentation": return NCBrandColor.shared.presentationIconColor + default: return NCBrandColor.shared.documentIconColor + } + } +} diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index d427cc5c86..4c4f747681 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -33,6 +33,7 @@ extension NCUtility { case NKTypeIconFile.txt.rawValue: image = UIImage(systemName: "doc.text", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2])) case NKTypeIconFile.url.rawValue: image = UIImage(systemName: "network", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2])) case NKTypeIconFile.xls.rawValue: image = UIImage(systemName: "tablecells", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.spreadsheetIconColor])) + case NKTypeIconFile.draw.rawValue: image = UIImage(systemName: "pencil.and.scribble", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2])) default: image = UIImage(systemName: "doc", withConfiguration: UIImage.SymbolConfiguration(weight: .thin))?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [NCBrandColor.shared.iconImageColor2])) } } diff --git a/iOSClient/Utility/NCUtility.swift b/iOSClient/Utility/NCUtility.swift index 0a36f11662..e64b90735c 100644 --- a/iOSClient/Utility/NCUtility.swift +++ b/iOSClient/Utility/NCUtility.swift @@ -39,32 +39,32 @@ final class NCUtility: NSObject, Sendable { } func editorsDirectEditing(account: String, contentType: String) -> [String] { - var names: [String] = [] + var identifiers: [String] = [] let capabilities = NCNetworking.shared.capabilities[account] capabilities?.directEditingEditors.forEach { editor in editor.mimetypes.forEach { mimetype in if mimetype == contentType { - names.append(editor.name) + identifiers.append(editor.identifier) } // HARDCODE // https://github.com/nextcloud/text/issues/913 if mimetype == "text/markdown" && contentType == "text/x-markdown" { - names.append(editor.name) + identifiers.append(editor.identifier) } if contentType == "text/html" { - names.append(editor.name) + identifiers.append(editor.identifier) } } editor.optionalMimetypes.forEach { mimetype in if mimetype == contentType { - names.append(editor.name) + identifiers.append(editor.identifier) } } } - return Array(Set(names)) + return Array(Set(identifiers)) } func getCustomUserAgentNCText() -> String { diff --git a/iOSClient/Viewer/NCViewer.swift b/iOSClient/Viewer/NCViewer.swift index 6689927a39..353787937d 100644 --- a/iOSClient/Viewer/NCViewer.swift +++ b/iOSClient/Viewer/NCViewer.swift @@ -58,6 +58,7 @@ class NCViewer: NSObject { // DOCUMENTS else if metadata.classFile == NKTypeClassFile.document.rawValue, !NCUtilityFileSystem().isDirectoryE2EE(serverUrl: metadata.serverUrl, urlBase: session.urlBase, userId: session.userId, account: session.account) { + // PDF if metadata.isPDF { let vc = UIStoryboard(name: "NCViewerPDF", bundle: nil).instantiateInitialViewController() as? NCViewerPDF @@ -68,16 +69,26 @@ class NCViewer: NSObject { return vc } - // RichDocument: Collabora - if metadata.isAvailableRichDocumentEditorView { + + // DirectEditing + if metadata.isAvailableDirectEditingEditorView { + let editors = utility.editorsDirectEditing(account: metadata.account, contentType: metadata.contentType).map { $0.lowercased() } + guard let editorAdapter = NCDirectEditorAdapter.resolve(from: editors) else { + self.QLPreview(metadata: metadata, delegate: delegate) + return nil + } + let editor = editorAdapter.apiKey + let editorViewController = editorAdapter.viewControllerEditor + let options = NKRequestOptions(customUserAgent: editorAdapter.userAgent(utility)) if metadata.url.isEmpty { + let fileNamePath = utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session) NCActivityIndicator.shared.start(backgroundView: delegate?.view) - let results = await NextcloudKit.shared.createUrlRichdocumentsAsync(fileID: metadata.fileId, account: metadata.account) { task in + let results = await NextcloudKit.shared.textOpenFileAsync(fileNamePath: fileNamePath, editor: editor, account: metadata.account, options: options) { task in Task { let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: metadata.fileId, - name: "createUrlRichdocuments") + path: fileNamePath, + name: "textOpenFile") await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } } @@ -89,19 +100,20 @@ class NCViewer: NSObject { return nil } - let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument + let vc = UIStoryboard(name: "NCViewerDirectEditing", bundle: nil).instantiateInitialViewController() as? NCViewerDirectEditing vc?.metadata = metadata + vc?.editor = editorViewController vc?.link = url vc?.imageIcon = image vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView) return vc - } else { - let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument + let vc = UIStoryboard(name: "NCViewerDirectEditing", bundle: nil).instantiateInitialViewController() as? NCViewerDirectEditing vc?.metadata = metadata + vc?.editor = editorViewController vc?.link = metadata.url vc?.imageIcon = image vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView) @@ -109,30 +121,16 @@ class NCViewer: NSObject { return vc } } - // DirectEditing: Nextcloud Text - OnlyOffice - if metadata.isAvailableDirectEditingEditorView { - var options = NKRequestOptions() - var editor = "" - var editorViewController = "" - let editors = utility.editorsDirectEditing(account: metadata.account, contentType: metadata.contentType).map { $0.lowercased() } - if editors.contains("nextcloud text") { - editor = "text" - editorViewController = "nextcloud text" - options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) - } else if editors.contains("onlyoffice") { - editor = "onlyoffice" - editorViewController = "onlyoffice" - options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) - } - if metadata.url.isEmpty { - let fileNamePath = utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session) + // RichDocument: Collabora + if metadata.isAvailableRichDocumentEditorView { + if metadata.url.isEmpty { NCActivityIndicator.shared.start(backgroundView: delegate?.view) - let results = await NextcloudKit.shared.textOpenFileAsync(fileNamePath: fileNamePath, editor: editor, account: metadata.account, options: options) { task in + let results = await NextcloudKit.shared.createUrlRichdocumentsAsync(fileID: metadata.fileId, account: metadata.account) { task in Task { let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: metadata.account, - path: fileNamePath, - name: "textOpenFile") + path: metadata.fileId, + name: "createUrlRichdocuments") await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } } @@ -144,33 +142,31 @@ class NCViewer: NSObject { return nil } - let vc = UIStoryboard(name: "NCViewerNextcloudText", bundle: nil).instantiateInitialViewController() as? NCViewerNextcloudText + let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument vc?.metadata = metadata - vc?.editor = editorViewController vc?.link = url vc?.imageIcon = image vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView) return vc + } else { - let vc = UIStoryboard(name: "NCViewerNextcloudText", bundle: nil).instantiateInitialViewController() as? NCViewerNextcloudText + let vc = UIStoryboard(name: "NCViewerRichdocument", bundle: nil).instantiateInitialViewController() as? NCViewerRichDocument vc?.metadata = metadata - vc?.editor = editorViewController vc?.link = metadata.url vc?.imageIcon = image vc?.navigationItem.setBidiSafeTitle(metadata.fileNameView) return vc } - } else { - self.QLPreview(metadata: metadata, delegate: delegate) } - } else { - self.QLPreview(metadata: metadata, delegate: delegate) } + // iOS QL-Preview + self.QLPreview(metadata: metadata, delegate: delegate) + return nil } diff --git a/iOSClient/Viewer/NCViewerDirectEditing/NCDirectEditorAdapter.swift b/iOSClient/Viewer/NCViewerDirectEditing/NCDirectEditorAdapter.swift new file mode 100644 index 0000000000..15cc9a33ac --- /dev/null +++ b/iOSClient/Viewer/NCViewerDirectEditing/NCDirectEditorAdapter.swift @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +struct NCDirectEditorAdapter { + /// Editor ID passed to the textOpenFile API. + let apiKey: String + /// Value set on NCViewerNextcloudText.editor — controls user agent and JS behaviour. + let viewControllerEditor: String + /// Resolves the custom user agent string via NCUtility. + let userAgent: (NCUtility) -> String + /// Returns the fallback file extension for a given templateId when the template API returns no templates. + let defaultExt: (_ templateId: String) -> String + + /// Lookup an adapter for the first matching editor ID in the provided list. + /// The list should already be lowercased. + static func resolve(from editors: [String]) -> NCDirectEditorAdapter? { + editors.lazy.compactMap { registry[$0.lowercased()] }.first + } + + // MARK: - Registry + + private static func officeDefaultExt(_ templateId: String) -> String { + switch templateId { + case "spreadsheet": return "xlsx" + case "presentation": return "pptx" + default: return "docx" + } + } + + private static let registry: [String: NCDirectEditorAdapter] = [ + "text": NCDirectEditorAdapter( + apiKey: "text", + viewControllerEditor: "nextcloud text", + userAgent: { $0.getCustomUserAgentNCText() }, + defaultExt: { _ in "md" } + ), + "onlyoffice": NCDirectEditorAdapter( + apiKey: "onlyoffice", + viewControllerEditor: "onlyoffice", + userAgent: { $0.getCustomUserAgentOnlyOffice() }, + defaultExt: officeDefaultExt + ), + "eurooffice": NCDirectEditorAdapter( + apiKey: "eurooffice", + viewControllerEditor: "onlyoffice", + userAgent: { $0.getCustomUserAgentOnlyOffice() }, + defaultExt: officeDefaultExt + ), + "whiteboard": NCDirectEditorAdapter( + apiKey: "whiteboard", + viewControllerEditor: "onlyoffice", + userAgent: { $0.getCustomUserAgentOnlyOffice() }, + defaultExt: { _ in "whiteboard" } + ) + ] +} diff --git a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.storyboard b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.storyboard similarity index 91% rename from iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.storyboard rename to iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.storyboard index 959209506b..e90c459511 100644 --- a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.storyboard +++ b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.storyboard @@ -1,17 +1,17 @@ - + - + - + - + diff --git a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift similarity index 98% rename from iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift rename to iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift index 577f55ea76..90fef2de6a 100644 --- a/iOSClient/Viewer/NCViewerNextcloudText/NCViewerNextcloudText.swift +++ b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift @@ -6,7 +6,7 @@ import UIKit import NextcloudKit @preconcurrency import WebKit -class NCViewerNextcloudText: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate { +class NCViewerDirectEditing: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate { var webView = WKWebView() var bottomConstraint: NSLayoutConstraint? var link: String = "" @@ -223,7 +223,7 @@ class NCViewerNextcloudText: UIViewController, WKNavigationDelegate, WKScriptMes } } -extension NCViewerNextcloudText: UINavigationControllerDelegate { +extension NCViewerDirectEditing: UINavigationControllerDelegate { override func didMove(toParent parent: UIViewController?) { super.didMove(toParent: parent) @@ -237,7 +237,7 @@ extension NCViewerNextcloudText: UINavigationControllerDelegate { } } -extension NCViewerNextcloudText: NCTransferDelegate { +extension NCViewerDirectEditing: NCTransferDelegate { func transferReloadData(serverUrl: String?) { } func transferReloadDataSource(serverUrl: String?, requestData: Bool, status: Int?) { }