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, domain: domain,
authorization: authorization).singleOutput() authorization: authorization).singleOutput()
_ = try await Mastodon.API.Statuses.editHistory(
forStatusID: statusID,
session: session,
domain: domain,
authorization: authorization
).singleOutput()
return response return response
} }
} }

View File

@ -179,7 +179,7 @@ extension Mastodon.API.Media {
extension Mastodon.API.Media { extension Mastodon.API.Media {
static func updateMediaEndpointURL(domain: String, attachmentID: Mastodon.Entity.Attachment.ID) -> URL { 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 /// Update attachment

View File

@ -106,12 +106,41 @@ extension Mastodon.API.Statuses {
} }
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 struct EditStatusQuery: Codable, PutQuery {
public let status: String? public let status: String?
public let mediaIDs: [String]? public let mediaIDs: [String]?
public let pollOptions: [String]? public let mediaAttributes: [MediaAttributes]?
public let pollExpiresIn: Int? public let poll: Poll?
public let pollMultipleAnswers: Bool?
public let sensitive: Bool? public let sensitive: Bool?
public let spoilerText: String? public let spoilerText: String?
public let visibility: Mastodon.Entity.Status.Visibility? public let visibility: Mastodon.Entity.Status.Visibility?
@ -120,9 +149,8 @@ extension Mastodon.API.Statuses {
public init( public init(
status: String?, status: String?,
mediaIDs: [String]?, mediaIDs: [String]?,
pollOptions: [String]?, mediaAttributes: [MediaAttributes]? = nil,
pollExpiresIn: Int?, poll: Poll?,
pollMultipleAnswers: Bool?,
sensitive: Bool?, sensitive: Bool?,
spoilerText: String?, spoilerText: String?,
visibility: Mastodon.Entity.Status.Visibility?, visibility: Mastodon.Entity.Status.Visibility?,
@ -130,37 +158,23 @@ extension Mastodon.API.Statuses {
) { ) {
self.status = status self.status = status
self.mediaIDs = mediaIDs self.mediaIDs = mediaIDs
self.pollOptions = pollOptions self.mediaAttributes = mediaAttributes
self.pollExpiresIn = pollExpiresIn self.poll = poll
self.pollMultipleAnswers = pollMultipleAnswers
self.sensitive = sensitive self.sensitive = sensitive
self.spoilerText = spoilerText self.spoilerText = spoilerText
self.visibility = visibility self.visibility = visibility
self.language = language self.language = language
} }
var contentType: String? { enum CodingKeys: String, CodingKey {
return Self.multipartContentType() case status
} case mediaIDs = "media_ids"
case mediaAttributes = "media_attributes"
var body: Data? { case poll
var data = Data() case sensitive
case spoilerText = "spoiler_text"
status.flatMap { data.append(Data.multipart(key: "status", value: $0)) } case visibility
for mediaID in mediaIDs ?? [] { case language
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
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -115,8 +115,8 @@ extension MastodonEditStatusPublisher: StatusPublisher {
guard case let AttachmentViewModel.Input.mastodonAssetUrl(_, attachmentId) = attachmentViewModel.input else { guard case let AttachmentViewModel.Input.mastodonAssetUrl(_, attachmentId) = attachmentViewModel.input else {
throw AppError.badRequest throw AppError.badRequest
} }
attachmentIDs.append(attachmentId) attachmentIDs.append(attachmentId)
break
case let .uploadedMastodonAttachment(attachment): case let .uploadedMastodonAttachment(attachment):
attachmentIDs.append(attachment.id) attachmentIDs.append(attachment.id)
@ -157,12 +157,21 @@ extension MastodonEditStatusPublisher: StatusPublisher {
return self.pollExpireConfigurationOption.seconds 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( let query = Mastodon.API.Statuses.EditStatusQuery(
status: content, status: content,
mediaIDs: attachmentIDs.isEmpty ? nil : attachmentIDs, mediaIDs: attachmentIDs.isEmpty ? nil : attachmentIDs,
pollOptions: pollOptions, mediaAttributes: mediaAttributes,
pollExpiresIn: pollExpiresIn, poll: poll,
pollMultipleAnswers: pollMultipleConfigurationOption,
sensitive: isMediaSensitive, sensitive: isMediaSensitive,
spoilerText: isContentWarningComposing ? contentWarning : nil, spoilerText: isContentWarningComposing ? contentWarning : nil,
visibility: visibility, visibility: visibility,

View File

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