235 lines
8.1 KiB
Swift
235 lines
8.1 KiB
Swift
//
|
|
// Draft.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 8/18/20.
|
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import Pachyderm
|
|
|
|
class Draft: Codable, ObservableObject {
|
|
let id: UUID
|
|
var lastModified: Date
|
|
|
|
@Published var accountID: String
|
|
@Published var text: String
|
|
@Published var contentWarningEnabled: Bool
|
|
@Published var contentWarning: String
|
|
@Published var attachments: [CompositionAttachment]
|
|
@Published var inReplyToID: String?
|
|
@Published var visibility: Status.Visibility
|
|
@Published var poll: Poll?
|
|
@Published var localOnly: Bool
|
|
|
|
var initialText: String
|
|
|
|
var hasContent: Bool {
|
|
(!text.isEmpty && text != initialText) ||
|
|
(contentWarningEnabled && !contentWarning.isEmpty) ||
|
|
attachments.count > 0 ||
|
|
poll?.hasContent == true
|
|
}
|
|
|
|
init(accountID: String) {
|
|
self.id = UUID()
|
|
self.lastModified = Date()
|
|
|
|
self.accountID = accountID
|
|
self.text = ""
|
|
self.contentWarningEnabled = false
|
|
self.contentWarning = ""
|
|
self.attachments = []
|
|
self.inReplyToID = nil
|
|
self.visibility = Preferences.shared.defaultPostVisibility
|
|
self.poll = nil
|
|
self.localOnly = false
|
|
|
|
self.initialText = ""
|
|
}
|
|
|
|
required init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.id = try container.decode(UUID.self, forKey: .id)
|
|
self.lastModified = try container.decode(Date.self, forKey: .lastModified)
|
|
|
|
self.accountID = try container.decode(String.self, forKey: .accountID)
|
|
self.text = try container.decode(String.self, forKey: .text)
|
|
self.contentWarningEnabled = try container.decode(Bool.self, forKey: .contentWarningEnabled)
|
|
self.contentWarning = try container.decode(String.self, forKey: .contentWarning)
|
|
self.attachments = try container.decode([CompositionAttachment].self, forKey: .attachments)
|
|
self.inReplyToID = try container.decode(String?.self, forKey: .inReplyToID)
|
|
self.visibility = try container.decode(Status.Visibility.self, forKey: .visibility)
|
|
self.poll = try container.decode(Poll.self, forKey: .poll)
|
|
self.localOnly = try container.decodeIfPresent(Bool.self, forKey: .localOnly) ?? false
|
|
|
|
self.initialText = try container.decode(String.self, forKey: .initialText)
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(id, forKey: .id)
|
|
try container.encode(lastModified, forKey: .lastModified)
|
|
|
|
try container.encode(accountID, forKey: .accountID)
|
|
try container.encode(text, forKey: .text)
|
|
try container.encode(contentWarningEnabled, forKey: .contentWarningEnabled)
|
|
try container.encode(contentWarning, forKey: .contentWarning)
|
|
try container.encode(attachments, forKey: .attachments)
|
|
try container.encode(inReplyToID, forKey: .inReplyToID)
|
|
try container.encode(visibility, forKey: .visibility)
|
|
try container.encode(poll, forKey: .poll)
|
|
try container.encode(localOnly, forKey: .localOnly)
|
|
|
|
try container.encode(initialText, forKey: .initialText)
|
|
}
|
|
|
|
func textForPosting(on instance: InstanceFeatures) -> String {
|
|
var text = self.text
|
|
// when using dictation, iOS sometimes leaves a U+FFFC OBJECT REPLACEMENT CHARACTER behind in the text,
|
|
// which we want to strip out before actually posting the status
|
|
text = text.replacingOccurrences(of: "\u{fffc}", with: "")
|
|
|
|
if localOnly && instance.instanceType == .glitch {
|
|
text += " 👁"
|
|
}
|
|
|
|
return text
|
|
}
|
|
}
|
|
|
|
extension Draft: Equatable {
|
|
static func ==(lhs: Draft, rhs: Draft) -> Bool {
|
|
return lhs.id == rhs.id
|
|
}
|
|
}
|
|
|
|
extension Draft {
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case lastModified
|
|
|
|
case accountID
|
|
case text
|
|
case contentWarningEnabled
|
|
case contentWarning
|
|
case attachments
|
|
case inReplyToID
|
|
case visibility
|
|
case poll
|
|
case localOnly
|
|
|
|
case initialText
|
|
}
|
|
}
|
|
|
|
extension Draft {
|
|
class Poll: Codable, ObservableObject {
|
|
@Published var options: [Option]
|
|
@Published var multiple: Bool
|
|
@Published var duration: TimeInterval
|
|
|
|
var hasContent: Bool {
|
|
options.contains { !$0.text.isEmpty }
|
|
}
|
|
|
|
init() {
|
|
self.options = [Option(""), Option("")]
|
|
self.multiple = false
|
|
self.duration = 24 * 60 * 60 // 1 day
|
|
}
|
|
|
|
required init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.options = try container.decode([Option].self, forKey: .options)
|
|
self.multiple = try container.decode(Bool.self, forKey: .multiple)
|
|
self.duration = try container.decode(TimeInterval.self, forKey: .duration)
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(options, forKey: .options)
|
|
try container.encode(multiple, forKey: .multiple)
|
|
try container.encode(duration, forKey: .duration)
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case options
|
|
case multiple
|
|
case duration
|
|
}
|
|
|
|
class Option: Identifiable, Codable, ObservableObject {
|
|
let id = UUID()
|
|
@Published var text: String
|
|
|
|
init(_ text: String) {
|
|
self.text = text
|
|
}
|
|
|
|
required init(from decoder: Decoder) throws {
|
|
self.text = try decoder.singleValueContainer().decode(String.self)
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension MastodonController {
|
|
|
|
func createDraft(inReplyToID: String? = nil, mentioningAcct: String? = nil) -> Draft {
|
|
var acctsToMention = [String]()
|
|
|
|
var visibility = Preferences.shared.defaultPostVisibility
|
|
var contentWarning = ""
|
|
|
|
if let inReplyToID = inReplyToID,
|
|
let inReplyTo = persistentContainer.status(for: inReplyToID) {
|
|
acctsToMention.append(inReplyTo.account.acct)
|
|
acctsToMention.append(contentsOf: inReplyTo.mentions.map(\.acct))
|
|
visibility = inReplyTo.visibility
|
|
|
|
if !inReplyTo.spoilerText.isEmpty {
|
|
switch Preferences.shared.contentWarningCopyMode {
|
|
case .doNotCopy:
|
|
break
|
|
case .asIs:
|
|
contentWarning = inReplyTo.spoilerText
|
|
case .prependRe:
|
|
if inReplyTo.spoilerText.lowercased().starts(with: "re:") {
|
|
contentWarning = inReplyTo.spoilerText
|
|
} else {
|
|
contentWarning = "re: \(inReplyTo.spoilerText)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let mentioningAcct = mentioningAcct {
|
|
acctsToMention.append(mentioningAcct)
|
|
}
|
|
if let ownAccount = self.account {
|
|
acctsToMention.removeAll(where: { $0 == ownAccount.acct })
|
|
}
|
|
acctsToMention = acctsToMention.uniques()
|
|
|
|
let draft = Draft(accountID: accountInfo!.id)
|
|
draft.inReplyToID = inReplyToID
|
|
draft.text = acctsToMention.map { "@\($0) " }.joined()
|
|
draft.initialText = draft.text
|
|
draft.visibility = visibility
|
|
draft.contentWarning = contentWarning
|
|
draft.contentWarningEnabled = !contentWarning.isEmpty
|
|
|
|
DraftsManager.shared.add(draft)
|
|
return draft
|
|
}
|
|
|
|
}
|