accounts and email
This commit is contained in:
@@ -276,6 +276,25 @@ public class MenuBarController: NSObject, ObservableObject, NSWindowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadDataResolvingDuplicates(_ data: Data, filename: String) async throws -> UploadResult {
|
||||
var duplicateAction: UploadDuplicateAction?
|
||||
|
||||
while true {
|
||||
do {
|
||||
return try await uploadManager.uploadData(
|
||||
data,
|
||||
filename: filename,
|
||||
duplicateAction: duplicateAction
|
||||
)
|
||||
} catch let error as APIError where error.isUploadConflict {
|
||||
guard let action = promptForUploadDuplicate(error, filename: filename) else {
|
||||
throw UploadCancelledError()
|
||||
}
|
||||
duplicateAction = action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func refreshRecentUploads() async {
|
||||
guard !isLoadingRecentUploads else { return }
|
||||
|
||||
@@ -290,6 +309,15 @@ public class MenuBarController: NSObject, ObservableObject, NSWindowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public func createNote() {
|
||||
switch config.effectiveNewNoteAction {
|
||||
case .clipboard:
|
||||
createNoteFromClipboard()
|
||||
case .editor:
|
||||
createNoteInEditor()
|
||||
}
|
||||
}
|
||||
|
||||
public func createNoteFromClipboard() {
|
||||
guard let text = NSPasteboard.general.string(forType: .string) else {
|
||||
syncStatus = "Clipboard is empty"
|
||||
@@ -305,6 +333,97 @@ public class MenuBarController: NSObject, ObservableObject, NSWindowDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createNoteInEditor() {
|
||||
let draftURL = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("cairnquire-\(UUID().uuidString)")
|
||||
.appendingPathExtension("md")
|
||||
let completionURL = draftURL.appendingPathExtension("done")
|
||||
|
||||
do {
|
||||
try Data().write(to: draftURL, options: .atomic)
|
||||
try launchTerminalEditor(draftURL: draftURL, completionURL: completionURL)
|
||||
syncStatus = "Waiting for $EDITOR to exit"
|
||||
} catch {
|
||||
try? FileManager.default.removeItem(at: draftURL)
|
||||
syncStatus = "Failed to open $EDITOR: \(error.localizedDescription)"
|
||||
return
|
||||
}
|
||||
|
||||
Task { [weak self] in
|
||||
await self?.uploadEditorDraft(draftURL: draftURL, completionURL: completionURL)
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadEditorDraft(draftURL: URL, completionURL: URL) async {
|
||||
defer {
|
||||
try? FileManager.default.removeItem(at: draftURL)
|
||||
try? FileManager.default.removeItem(at: completionURL)
|
||||
}
|
||||
|
||||
while !FileManager.default.fileExists(atPath: completionURL.path) {
|
||||
try? await Task.sleep(for: .milliseconds(500))
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: draftURL)
|
||||
guard !data.isEmpty else {
|
||||
syncStatus = "Editor note was empty; nothing uploaded"
|
||||
return
|
||||
}
|
||||
|
||||
let filename = Self.editorNoteFilename()
|
||||
let result = try await uploadDataResolvingDuplicates(data, filename: filename)
|
||||
syncStatus = "Uploaded \(result.filename)"
|
||||
clipboardNotice = ClipboardNotice(filename: result.filename)
|
||||
await refreshRecentUploads()
|
||||
} catch {
|
||||
syncStatus = "Failed to upload editor note: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
private func launchTerminalEditor(draftURL: URL, completionURL: URL) throws {
|
||||
let draftPath = Self.shellQuote(draftURL.path)
|
||||
let completionPath = Self.shellQuote(completionURL.path)
|
||||
let shellCommand = "trap '/usr/bin/touch \(completionPath)' EXIT; editor=\"${EDITOR:-${VISUAL:-vi}}\"; eval \"$editor \(draftPath)\""
|
||||
let script = "tell application \"Terminal\" to do script \"\(Self.appleScriptEscape(shellCommand))\""
|
||||
|
||||
let process = Process()
|
||||
let errorPipe = Pipe()
|
||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
|
||||
process.arguments = ["-e", "tell application \"Terminal\" to activate", "-e", script]
|
||||
process.standardError = errorPipe
|
||||
try process.run()
|
||||
process.waitUntilExit()
|
||||
|
||||
guard process.terminationStatus == 0 else {
|
||||
let data = errorPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let message = String(data: data, encoding: .utf8)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
throw NSError(
|
||||
domain: "CairnquireSync",
|
||||
code: Int(process.terminationStatus),
|
||||
userInfo: [NSLocalizedDescriptionKey: message ?? "Terminal could not be opened"]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private static func shellQuote(_ value: String) -> String {
|
||||
"'" + value.replacingOccurrences(of: "'", with: "'\\''") + "'"
|
||||
}
|
||||
|
||||
private static func appleScriptEscape(_ value: String) -> String {
|
||||
value
|
||||
.replacingOccurrences(of: "\\", with: "\\\\")
|
||||
.replacingOccurrences(of: "\"", with: "\\\"")
|
||||
}
|
||||
|
||||
private static func editorNoteFilename() -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.dateFormat = "yyyy-MM-dd HH-mm-ss"
|
||||
return "Note \(formatter.string(from: Date())).md"
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuBarController: FolderWatcherDelegate {
|
||||
|
||||
Reference in New Issue
Block a user