Merge pull request #1212 from mastodon/1138-edit-caption

Edit Caption
This commit is contained in:
Nathan Mattes 2024-01-23 12:51:34 +01:00 committed by GitHub
commit c0c795e473
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 82 additions and 61 deletions

View File

@ -50,13 +50,6 @@ extension APIService {
domain: domain,
authorization: authorization).singleOutput()
_ = try await Mastodon.API.Statuses.editHistory(
forStatusID: statusID,
session: session,
domain: domain,
authorization: authorization
).singleOutput()
return response
}
}

View File

@ -179,7 +179,7 @@ extension Mastodon.API.Media {
extension Mastodon.API.Media {
static func updateMediaEndpointURL(domain: String, attachmentID: Mastodon.Entity.Attachment.ID) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("media").appendingPathComponent(attachmentID)
Mastodon.API.endpointURL(domain: domain).appendingPathComponent("media").appendingPathComponent(attachmentID)
}
/// Update attachment

View File

@ -106,12 +106,41 @@ extension Mastodon.API.Statuses {
}
extension Mastodon.API.Statuses {
public struct MediaAttributes: Codable {
let id: String
let description: String?
//TODO: Add focus at some point
public init(id: String, description: String?) {
self.id = id
self.description = description
}
}
public struct Poll: Codable {
public let options: [String]?
public let expiresIn: Int?
public let multipleAnswers: Bool?
public init(options: [String]?, expiresIn: Int?, multipleAnswers: Bool?) {
self.options = options
self.expiresIn = expiresIn
self.multipleAnswers = multipleAnswers
}
enum CodingKeys: String, CodingKey {
case options
case expiresIn = "expires_in"
case multipleAnswers = "multiple_answers"
}
}
public struct EditStatusQuery: Codable, PutQuery {
public let status: String?
public let mediaIDs: [String]?
public let pollOptions: [String]?
public let pollExpiresIn: Int?
public let pollMultipleAnswers: Bool?
public let mediaAttributes: [MediaAttributes]?
public let poll: Poll?
public let sensitive: Bool?
public let spoilerText: String?
public let visibility: Mastodon.Entity.Status.Visibility?
@ -120,9 +149,8 @@ extension Mastodon.API.Statuses {
public init(
status: String?,
mediaIDs: [String]?,
pollOptions: [String]?,
pollExpiresIn: Int?,
pollMultipleAnswers: Bool?,
mediaAttributes: [MediaAttributes]? = nil,
poll: Poll?,
sensitive: Bool?,
spoilerText: String?,
visibility: Mastodon.Entity.Status.Visibility?,
@ -130,37 +158,23 @@ extension Mastodon.API.Statuses {
) {
self.status = status
self.mediaIDs = mediaIDs
self.pollOptions = pollOptions
self.pollExpiresIn = pollExpiresIn
self.pollMultipleAnswers = pollMultipleAnswers
self.mediaAttributes = mediaAttributes
self.poll = poll
self.sensitive = sensitive
self.spoilerText = spoilerText
self.visibility = visibility
self.language = language
}
var contentType: String? {
return Self.multipartContentType()
}
var body: Data? {
var data = Data()
status.flatMap { data.append(Data.multipart(key: "status", value: $0)) }
for mediaID in mediaIDs ?? [] {
data.append(Data.multipart(key: "media_ids[]", value: mediaID))
}
for pollOption in pollOptions ?? [] {
data.append(Data.multipart(key: "poll[options][]", value: pollOption))
}
pollExpiresIn.flatMap { data.append(Data.multipart(key: "poll[expires_in]", value: $0)) }
sensitive.flatMap { data.append(Data.multipart(key: "sensitive", value: $0)) }
spoilerText.flatMap { data.append(Data.multipart(key: "spoiler_text", value: $0)) }
visibility.flatMap { data.append(Data.multipart(key: "visibility", value: $0.rawValue)) }
language.flatMap { data.append(Data.multipart(key: "language", value: $0)) }
data.append(Data.multipartEnd())
return data
enum CodingKeys: String, CodingKey {
case status
case mediaIDs = "media_ids"
case mediaAttributes = "media_attributes"
case poll
case sensitive
case spoilerText = "spoiler_text"
case visibility
case language
}
}
}

View File

@ -120,15 +120,12 @@ extension AttachmentViewModel {
}
let attachment = output.asAttachment
let query = Mastodon.API.Media.UploadMediaQuery(
file: attachment,
thumbnail: nil,
description: {
let caption = caption.trimmingCharacters(in: .whitespacesAndNewlines)
return caption.isEmpty ? nil : caption
}(),
focus: nil // TODO:
description: caption.trimmingCharacters(in: .whitespacesAndNewlines),
focus: nil
)
// upload + N * check upload

View File

@ -47,6 +47,7 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
public let sizeLimit: SizeLimit
@Published var caption = ""
@Published public private(set) var isCaptionEditable = true
let isEditing: Bool
// output
@Published public private(set) var output: Output?
@ -75,15 +76,20 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
authContext: AuthContext,
input: Input,
sizeLimit: SizeLimit,
delegate: AttachmentViewModelDelegate
delegate: AttachmentViewModelDelegate,
isEditing: Bool = false,
caption: String? = nil
) {
self.api = api
self.authContext = authContext
self.input = input
self.sizeLimit = sizeLimit
self.delegate = delegate
self.isEditing = isEditing
self.caption = caption ?? ""
super.init()
// end init
Timer.publish(every: 1.0 / 60.0, on: .main, in: .common) // 60 FPS
.autoconnect()
@ -134,7 +140,9 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
switch input {
case .mastodonAssetUrl:
self.isCaptionEditable = false
if self.isEditing == false {
self.isCaptionEditable = false
}
self.uploadState = .finish
self.output = output
self.uploadResult = .exists
@ -258,7 +266,7 @@ extension AttachmentViewModel {
public enum Input: Hashable {
case image(UIImage)
case url(URL)
case mastodonAssetUrl(URL, String)
case mastodonAssetUrl(url: URL, attachmentId: String)
case pickerResult(PHPickerResult)
case itemProvider(NSItemProvider)
}
@ -321,4 +329,5 @@ extension AttachmentViewModel {
func update(uploadResult: UploadResult) {
self.uploadResult = uploadResult
}
}

View File

@ -265,14 +265,16 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
self.isVisibilityButtonEnabled = false
self.attachmentViewModels = status.entity.mastodonAttachments.compactMap {
guard let assetURL = $0.assetURL, let url = URL(string: assetURL) else { return nil }
let attachmentViewModel = AttachmentViewModel(
api: context.apiService,
authContext: authContext,
input: .mastodonAssetUrl(url, $0.id),
input: .mastodonAssetUrl(url: url, attachmentId: $0.id),
sizeLimit: sizeLimit,
delegate: self
delegate: self,
isEditing: true,
caption: $0.altDescription
)
attachmentViewModel.caption = $0.altDescription ?? ""
return attachmentViewModel
}
}

View File

@ -115,8 +115,8 @@ extension MastodonEditStatusPublisher: StatusPublisher {
guard case let AttachmentViewModel.Input.mastodonAssetUrl(_, attachmentId) = attachmentViewModel.input else {
throw AppError.badRequest
}
attachmentIDs.append(attachmentId)
break
case let .uploadedMastodonAttachment(attachment):
attachmentIDs.append(attachment.id)
@ -157,12 +157,21 @@ extension MastodonEditStatusPublisher: StatusPublisher {
return self.pollExpireConfigurationOption.seconds
}()
let poll = Mastodon.API.Statuses.Poll(options: pollOptions, expiresIn: pollExpiresIn, multipleAnswers: self.pollMultipleConfigurationOption)
let mediaAttributes: [Mastodon.API.Statuses.MediaAttributes] = attachmentViewModels.compactMap {
if case let .mastodonAssetUrl(url: _, attachmentId: attachmentId) = $0.input {
return Mastodon.API.Statuses.MediaAttributes(id: attachmentId, description: $0.caption)
} else {
return nil
}
}
let query = Mastodon.API.Statuses.EditStatusQuery(
status: content,
mediaIDs: attachmentIDs.isEmpty ? nil : attachmentIDs,
pollOptions: pollOptions,
pollExpiresIn: pollExpiresIn,
pollMultipleAnswers: pollMultipleConfigurationOption,
mediaAttributes: mediaAttributes,
poll: poll,
sensitive: isMediaSensitive,
spoilerText: isContentWarningComposing ? contentWarning : nil,
visibility: visibility,

View File

@ -124,17 +124,14 @@ extension MastodonStatusPublisher: StatusPublisher {
break
case let .uploadedMastodonAttachment(attachment):
attachmentIDs.append(attachment.id)
let caption = attachmentViewModel.caption
guard !caption.isEmpty else { continue }
_ = try await api.updateMedia(
domain: authContext.mastodonAuthenticationBox.domain,
attachmentID: attachment.id,
query: .init(
file: nil,
thumbnail: nil,
description: caption,
description: attachmentViewModel.caption,
focus: nil
),
mastodonAuthenticationBox: authContext.mastodonAuthenticationBox