oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1import CoreTransferable
2import Foundation
3import RastrilloApple
4import SwiftUI
5import UniformTypeIdentifiers
6#if os(macOS)
7import PDFKit
8#else
9import UIKit
10#endif
11
12struct ExportedDocument: Transferable {
13 let url: URL
14 let isPDF: Bool
15 let suggestedName: String
16 static var transferRepresentation: some TransferRepresentation {
17 FileRepresentation(exportedContentType: .pdf) { document in
18 SentTransferredFile(document.url, allowAccessingOriginalFile: false)
19 }.exportingCondition { $0.isPDF }.suggestedFileName { $0.suggestedName }
20 FileRepresentation(exportedContentType: UTType(filenameExtension: "md") ?? .plainText) { document in
21 SentTransferredFile(document.url, allowAccessingOriginalFile: false)
22 }.exportingCondition { !$0.isPDF }.suggestedFileName { $0.suggestedName }
23 }
24}
25@MainActor struct DocumentExportControls: View {
26 let client: NativeHTTP
27 let sqid: String
28 let title: String
29 let prepare: () async -> Bool
30 @Binding var request: String?
31 @State private var document: ExportedDocument?
32 @State private var exporting = false
33 @State private var working = false
34 @State private var active = true
35 @State private var type = UTType.pdf
36 @State private var failed = false
37 init(client: NativeHTTP, sqid: String, title: String, prepare: @escaping () async -> Bool, request: Binding<String?> = .constant(nil)) {
38 self.client = client; self.sqid = sqid; self.title = title; self.prepare = prepare; self._request = request
39 }
40 var body: some View {
41 Menu {
42 Button { Task { await download(pdf: true, printDocument: false) } } label: { Text("docs.export.pdf.label", bundle: DocsResources.bundle) }
43 Button { Task { await download(pdf: false, printDocument: false) } } label: { Text("docs.export.markdown.label", bundle: DocsResources.bundle) }
44 Button { Task { await download(pdf: true, printDocument: true) } } label: { Text("docs.editor.menu.file.print", bundle: DocsResources.bundle) }
45 } label: { Image(systemName: "square.and.arrow.up") }
46 .onChange(of: request) { _, value in
47 guard let value else { return }
48 request = nil
49 Task { await download(pdf: value != "markdown", printDocument: value == "print") }
50 }
51 .disabled(working || exporting)
52 .accessibilityLabel(Text("native.export", bundle: DocsResources.bundle))
53 .fileExporter(isPresented: $exporting, item: document, contentTypes: [type], defaultFilename: title, onCompletion: { result in
54 if case .failure = result { failed = true }
55 cleanup()
56 }, onCancellation: { cleanup() })
57 .onAppear { active = true }
58 .onDisappear { active = false; if !exporting && !working { cleanup() } }
59 .alert(Text("docs.error.generic", bundle: DocsResources.bundle), isPresented: $failed) {
60 Button(role: .cancel) {} label: { Text("native.close", bundle: DocsResources.bundle) }
61 }
62 }
63 static func filename(title: String, extension suffix: String) -> String {
64 let invalid = CharacterSet.controlCharacters.union(CharacterSet(charactersIn: "/\\:"))
65 let cleaned = title.components(separatedBy: invalid).joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines)
66 var base = ""
67 for character in cleaned {
68 guard base.utf8.count + String(character).utf8.count <= 180 else { break }
69 base.append(character)
70 }
71 if base.isEmpty || base == "." || base == ".." { base = String(localized: "docs.document.untitled", bundle: DocsResources.bundle) }
72 return base.lowercased().hasSuffix("." + suffix) ? base : base + "." + suffix
73 }
74 private func cleanup() {
75 if let document { try? FileManager.default.removeItem(at: document.url.deletingLastPathComponent()) }
76 document = nil
77 }
78 private func download(pdf: Bool, printDocument: Bool) async {
79 guard !working, !exporting else { return }
80 working = true
81 defer { working = false }
82 guard await prepare(), active else { return }
83 let directory = FileManager.default.temporaryDirectory
84 .appendingPathComponent("oficina-docs-export-" + UUID().uuidString, isDirectory: true)
85 let filename = Self.filename(title: title, extension: pdf ? "pdf" : "md")
86 let destination = directory.appendingPathComponent(filename)
87 do {
88 try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: [.posixPermissions: 0o700])
89 try await client.download("/d/\(sqid)/export.\(pdf ? "pdf" : "md")", to: destination)
90 guard active else { try? FileManager.default.removeItem(at: directory); return }
91 if printDocument {
92 #if os(macOS)
93 defer { try? FileManager.default.removeItem(at: directory) }
94 guard let pdf = PDFDocument(url: destination), let operation = pdf.printOperation(for: NSPrintInfo.shared, scalingMode: .pageScaleDownToFit, autoRotate: true) else { throw CRDTReplica.Failure.rejected }
95 operation.run()
96 #else
97 let controller = UIPrintInteractionController.shared
98 controller.printingItem = destination
99 let presented = controller.present(animated: true) { _, _, _ in
100 try? FileManager.default.removeItem(at: directory)
101 }
102 if !presented { try? FileManager.default.removeItem(at: directory); failed = true }
103 #endif
104 } else {
105 type = pdf ? .pdf : (UTType(filenameExtension: "md") ?? .plainText)
106 document = ExportedDocument(url: destination, isPDF: pdf, suggestedName: filename)
107 exporting = true
108 }
109 } catch {
110 try? FileManager.default.removeItem(at: directory)
111 failed = true
112 }
113 }
114}
115