krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Commit 1adb6b1169

1adb6b1169d778b9e39305deec352ec7a9a05d10

parent: 414cb44451

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-07 00:25 UTC

iOS: export through the share sheet

The reader's toolbar and the editor's More menu export the file as HTML or
Markdown, written when the share sheet takes it: to another app, copied, or
saved with Save to Files. The formats that run through Emacs stay on the Mac.

Layout: unified · split

Sources/OrgstarMobile/EditorScreen.swift +1
@@ -53,6 +53,7 @@ struct EditorScreen: View {
53 Menu("More", systemImage: "ellipsis.circle") { 53 Menu("More", systemImage: "ellipsis.circle") {
54 Button("Commands", systemImage: "command") { sheet = .palette } 54 Button("Commands", systemImage: "command") { sheet = .palette }
55 Button("Clock In", systemImage: "stopwatch") { editor?.run("app.clock.in") } 55 Button("Clock In", systemImage: "stopwatch") { editor?.run("app.clock.in") }
56 if let text = session.document?.text, session.path == path { ExportMenu(path: path, text: text) }
56 Button("Sync Conflict Copies", systemImage: "doc.on.doc") { sheet = .syncCopies } 57 Button("Sync Conflict Copies", systemImage: "doc.on.doc") { sheet = .syncCopies }
57 Button("Recovered Versions", systemImage: "clock.arrow.circlepath") { sheet = .recovery } 58 Button("Recovered Versions", systemImage: "clock.arrow.circlepath") { sheet = .recovery }
58 } 59 }
Sources/OrgstarMobile/ExportMenu.swift added +61
@@ -0,0 +1,61 @@
1#if os(iOS)
2import CoreTransferable
3import OrgApp
4import OrgCore
5import SwiftUI
6import UniformTypeIdentifiers
7
8/// A file exported as HTML or Markdown, written when the share sheet takes it.
9struct OrgExport: Transferable {
10 enum Format { case html, markdown }
11
12 let text: String
13 let path: String
14 let format: Format
15 let settings: OrgSettings
16
17 var fileName: String {
18 let base = ((path as NSString).lastPathComponent as NSString).deletingPathExtension
19 return base + (format == .html ? ".html" : ".md")
20 }
21
22 static var transferRepresentation: some TransferRepresentation {
23 FileRepresentation(exportedContentType: .html) { export in
24 try export.write()
25 }
26 .exportingCondition { $0.format == .html }
27 FileRepresentation(exportedContentType: UTType(filenameExtension: "md") ?? .plainText) { export in
28 try export.write()
29 }
30 .exportingCondition { $0.format == .markdown }
31 }
32
33 private func write() throws -> SentTransferredFile {
34 let base = ((path as NSString).lastPathComponent as NSString).deletingPathExtension
35 let content = switch format {
36 case .html: HTMLExport.page(text, fallbackTitle: base, settings: settings, path: path)
37 case .markdown: MarkdownExport.document(text, settings: settings)
38 }
39 let folder = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
40 try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
41 let url = folder.appendingPathComponent(fileName)
42 try content.write(to: url, atomically: true, encoding: .utf8)
43 return SentTransferredFile(url)
44 }
45}
46
47/// Sharing the file as HTML or Markdown: to another app, or saved with Save to Files.
48struct ExportMenu: View {
49 let path: String
50 let text: String
51
52 var body: some View {
53 Menu("Export", systemImage: "square.and.arrow.up") {
54 ForEach([(OrgExport.Format.html, "HTML"), (.markdown, "Markdown")], id: \.1) { format, name in
55 let export = OrgExport(text: text, path: path, format: format, settings: OrgPreferences.orgSettings())
56 ShareLink(item: export, preview: SharePreview(export.fileName)) { Text(name) }
57 }
58 }
59 }
60}
61#endif
Sources/OrgstarMobile/ReaderScreen.swift +1
@@ -67,6 +67,7 @@ struct ReaderScreen: View {
67 ToolbarItemGroup(placement: .primaryAction) { 67 ToolbarItemGroup(placement: .primaryAction) {
68 Button("Outline", systemImage: "list.bullet.indent") { showingOutline = true }.disabled(outline.isEmpty) 68 Button("Outline", systemImage: "list.bullet.indent") { showingOutline = true }.disabled(outline.isEmpty)
69 if let path = session.path { 69 if let path = session.path {
70 if let text = session.document?.text { ExportMenu(path: path, text: text) }
70 NavigationLink(value: EditorRoute(path: path, offset: nil)) { Label("Edit", systemImage: "square.and.pencil") } 71 NavigationLink(value: EditorRoute(path: path, offset: nil)) { Label("Edit", systemImage: "square.and.pencil") }
71 } 72 }
72 } 73 }