Merge pull request #133 from tootsuite/feature/postDelete
feat: Add post delete action entry for user posts
This commit is contained in:
commit
191c488921
|
@ -87,7 +87,7 @@
|
||||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="userID" attributeType="String"/>
|
<attribute name="userID" attributeType="String"/>
|
||||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
|
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
|
||||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
|
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="inNotifications" inverseEntity="Status"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
<uniquenessConstraint>
|
<uniquenessConstraint>
|
||||||
<constraint value="id"/>
|
<constraint value="id"/>
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="votersCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="votersCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="votesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="votesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<relationship name="options" toMany="YES" deletionRule="Nullify" destinationEntity="PollOption" inverseName="poll" inverseEntity="PollOption"/>
|
<relationship name="options" toMany="YES" deletionRule="Cascade" destinationEntity="PollOption" inverseName="poll" inverseEntity="PollOption"/>
|
||||||
<relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="poll" inverseEntity="Status"/>
|
<relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="poll" inverseEntity="Status"/>
|
||||||
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePolls" inverseEntity="MastodonUser"/>
|
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePolls" inverseEntity="MastodonUser"/>
|
||||||
</entity>
|
</entity>
|
||||||
|
@ -218,14 +218,15 @@
|
||||||
<relationship name="bookmarkedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
<relationship name="bookmarkedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="emojis" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Emoji" inverseName="status" inverseEntity="Emoji"/>
|
<relationship name="emojis" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Emoji" inverseName="status" inverseEntity="Emoji"/>
|
||||||
<relationship name="favouritedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
<relationship name="favouritedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="homeTimelineIndexes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="HomeTimelineIndex" inverseName="status" inverseEntity="HomeTimelineIndex"/>
|
<relationship name="homeTimelineIndexes" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="HomeTimelineIndex" inverseName="status" inverseEntity="HomeTimelineIndex"/>
|
||||||
|
<relationship name="inNotifications" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MastodonNotification" inverseName="status" inverseEntity="MastodonNotification"/>
|
||||||
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="status" inverseEntity="Attachment"/>
|
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="status" inverseEntity="Attachment"/>
|
||||||
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Mention" inverseName="status" inverseEntity="Mention"/>
|
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Mention" inverseName="status" inverseEntity="Mention"/>
|
||||||
<relationship name="mutedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
<relationship name="mutedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedStatus" inverseEntity="MastodonUser"/>
|
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedStatus" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="status" inverseEntity="Poll"/>
|
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="status" inverseEntity="Poll"/>
|
||||||
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>
|
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>
|
||||||
<relationship name="reblogFrom" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
<relationship name="reblogFrom" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
||||||
<relationship name="rebloggedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
<relationship name="rebloggedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="replyFrom" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="replyTo" inverseEntity="Status"/>
|
<relationship name="replyFrom" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="replyTo" inverseEntity="Status"/>
|
||||||
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
||||||
|
@ -279,7 +280,7 @@
|
||||||
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
|
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
|
||||||
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="104"/>
|
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="104"/>
|
||||||
<element name="Setting" positionX="72" positionY="162" width="128" height="119"/>
|
<element name="Setting" positionX="72" positionY="162" width="128" height="119"/>
|
||||||
<element name="Status" positionX="0" positionY="0" width="128" height="584"/>
|
<element name="Status" positionX="0" positionY="0" width="128" height="599"/>
|
||||||
<element name="Subscription" positionX="81" positionY="171" width="128" height="179"/>
|
<element name="Subscription" positionX="81" positionY="171" width="128" height="179"/>
|
||||||
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="14"/>
|
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="14"/>
|
||||||
<element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
|
<element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
|
||||||
|
|
|
@ -60,6 +60,8 @@ public final class Status: NSManagedObject {
|
||||||
@NSManaged public private(set) var mediaAttachments: Set<Attachment>?
|
@NSManaged public private(set) var mediaAttachments: Set<Attachment>?
|
||||||
@NSManaged public private(set) var replyFrom: Set<Status>?
|
@NSManaged public private(set) var replyFrom: Set<Status>?
|
||||||
|
|
||||||
|
@NSManaged public private(set) var inNotifications: Set<MastodonNotification>?
|
||||||
|
|
||||||
@NSManaged public private(set) var updatedAt: Date
|
@NSManaged public private(set) var updatedAt: Date
|
||||||
@NSManaged public private(set) var deletedAt: Date?
|
@NSManaged public private(set) var deletedAt: Date?
|
||||||
@NSManaged public private(set) var revealedAt: Date?
|
@NSManaged public private(set) var revealedAt: Date?
|
||||||
|
|
|
@ -29,12 +29,16 @@
|
||||||
"confirm": "Sign Out"
|
"confirm": "Sign Out"
|
||||||
},
|
},
|
||||||
"block_domain": {
|
"block_domain": {
|
||||||
"message": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
"title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||||
"block_entire_domain": "Block entire domain"
|
"block_entire_domain": "Block entire domain"
|
||||||
},
|
},
|
||||||
"save_photo_failure": {
|
"save_photo_failure": {
|
||||||
"title": "Save Photo Failure",
|
"title": "Save Photo Failure",
|
||||||
"message": "Please enable photo libaray access permission to save photo."
|
"message": "Please enable photo libaray access permission to save photo."
|
||||||
|
},
|
||||||
|
"delete_post": {
|
||||||
|
"title": "Are you sure you want to delete this post?",
|
||||||
|
"delete": "Delete"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
|
@ -67,7 +71,8 @@
|
||||||
"report_user": "Report %s",
|
"report_user": "Report %s",
|
||||||
"block_domain": "Block %s",
|
"block_domain": "Block %s",
|
||||||
"unblock_domain": "Unblock %s",
|
"unblock_domain": "Unblock %s",
|
||||||
"settings": "Settings"
|
"settings": "Settings",
|
||||||
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"user_reblogged": "%s reblogged",
|
"user_reblogged": "%s reblogged",
|
||||||
|
|
|
@ -935,7 +935,6 @@
|
||||||
DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = "<group>"; };
|
DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = "<group>"; };
|
||||||
DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerResultLoader.swift; sourceTree = "<group>"; };
|
DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerResultLoader.swift; sourceTree = "<group>"; };
|
||||||
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = "<group>"; };
|
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = "<group>"; };
|
||||||
DB9A488326034BD7008B817C /* APIService+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status.swift"; sourceTree = "<group>"; };
|
|
||||||
DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+PublishState.swift"; sourceTree = "<group>"; };
|
DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+PublishState.swift"; sourceTree = "<group>"; };
|
||||||
DB9A488F26035963008B817C /* APIService+Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Media.swift"; sourceTree = "<group>"; };
|
DB9A488F26035963008B817C /* APIService+Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Media.swift"; sourceTree = "<group>"; };
|
||||||
DB9A48952603685D008B817C /* MastodonAttachmentService+UploadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAttachmentService+UploadState.swift"; sourceTree = "<group>"; };
|
DB9A48952603685D008B817C /* MastodonAttachmentService+UploadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAttachmentService+UploadState.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1735,7 +1734,6 @@
|
||||||
DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */,
|
DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */,
|
||||||
DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */,
|
DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */,
|
||||||
DB49A62A25FF36C700B98345 /* APIService+CustomEmoji.swift */,
|
DB49A62A25FF36C700B98345 /* APIService+CustomEmoji.swift */,
|
||||||
DB9A488326034BD7008B817C /* APIService+Status.swift */,
|
|
||||||
2D61254C262547C200299647 /* APIService+Notification.swift */,
|
2D61254C262547C200299647 /* APIService+Notification.swift */,
|
||||||
DB9A488F26035963008B817C /* APIService+Media.swift */,
|
DB9A488F26035963008B817C /* APIService+Media.swift */,
|
||||||
2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */,
|
2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */,
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
|
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "bbc4bc4def7eb05a7ba8e1219f80ee9be327334e",
|
"revision": "15d199e84677303a7004ed2c5ecaa1a90f3863f8",
|
||||||
"version": "6.2.1"
|
"version": "6.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -143,6 +143,21 @@ extension StatusSection {
|
||||||
requestUserID: String,
|
requestUserID: String,
|
||||||
statusItemAttribute: Item.StatusAttribute
|
statusItemAttribute: Item.StatusAttribute
|
||||||
) {
|
) {
|
||||||
|
// safely cancel the listenser when deleted
|
||||||
|
ManagedObjectObserver.observe(object: status.reblog ?? status)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { _ in
|
||||||
|
// do nothing
|
||||||
|
} receiveValue: { [weak cell] change in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
guard let changeType = change.changeType else { return }
|
||||||
|
if case .delete = changeType {
|
||||||
|
cell.disposeBag.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
|
|
||||||
// set header
|
// set header
|
||||||
StatusSection.configureHeader(cell: cell, status: status)
|
StatusSection.configureHeader(cell: cell, status: status)
|
||||||
ManagedObjectObserver.observe(object: status)
|
ManagedObjectObserver.observe(object: status)
|
||||||
|
@ -781,7 +796,6 @@ extension StatusSection {
|
||||||
}
|
}
|
||||||
let author = status.authorForUserProvider
|
let author = status.authorForUserProvider
|
||||||
let isMyself = authenticationBox.userID == author.id
|
let isMyself = authenticationBox.userID == author.id
|
||||||
let canReport = !isMyself
|
|
||||||
let isInSameDomain = authenticationBox.domain == author.domainFromAcct
|
let isInSameDomain = authenticationBox.domain == author.domainFromAcct
|
||||||
let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
||||||
let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
||||||
|
|
|
@ -16,7 +16,7 @@ extension Status.Property {
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
uri: entity.uri,
|
uri: entity.uri,
|
||||||
createdAt: entity.createdAt,
|
createdAt: entity.createdAt,
|
||||||
content: entity.content,
|
content: entity.content!,
|
||||||
visibility: entity.visibility?.rawValue,
|
visibility: entity.visibility?.rawValue,
|
||||||
sensitive: entity.sensitive ?? false,
|
sensitive: entity.sensitive ?? false,
|
||||||
spoilerText: entity.spoilerText,
|
spoilerText: entity.spoilerText,
|
||||||
|
|
|
@ -17,8 +17,8 @@ internal enum L10n {
|
||||||
/// Block entire domain
|
/// Block entire domain
|
||||||
internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain")
|
internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain")
|
||||||
/// Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.
|
/// Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.
|
||||||
internal static func message(_ p1: Any) -> String {
|
internal static func title(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Message", String(describing: p1))
|
return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Title", String(describing: p1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Common {
|
internal enum Common {
|
||||||
|
@ -27,6 +27,12 @@ internal enum L10n {
|
||||||
/// Please try again later.
|
/// Please try again later.
|
||||||
internal static let pleaseTryAgainLater = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgainLater")
|
internal static let pleaseTryAgainLater = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgainLater")
|
||||||
}
|
}
|
||||||
|
internal enum DeletePost {
|
||||||
|
/// Delete
|
||||||
|
internal static let delete = L10n.tr("Localizable", "Common.Alerts.DeletePost.Delete")
|
||||||
|
/// Are you sure you want to delete this post?
|
||||||
|
internal static let title = L10n.tr("Localizable", "Common.Alerts.DeletePost.Title")
|
||||||
|
}
|
||||||
internal enum DiscardPostContent {
|
internal enum DiscardPostContent {
|
||||||
/// Confirm discard composed post content.
|
/// Confirm discard composed post content.
|
||||||
internal static let message = L10n.tr("Localizable", "Common.Alerts.DiscardPostContent.Message")
|
internal static let message = L10n.tr("Localizable", "Common.Alerts.DiscardPostContent.Message")
|
||||||
|
@ -84,6 +90,8 @@ internal enum L10n {
|
||||||
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
|
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
|
||||||
/// Continue
|
/// Continue
|
||||||
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
|
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
|
||||||
|
/// Delete
|
||||||
|
internal static let delete = L10n.tr("Localizable", "Common.Controls.Actions.Delete")
|
||||||
/// Discard
|
/// Discard
|
||||||
internal static let discard = L10n.tr("Localizable", "Common.Controls.Actions.Discard")
|
internal static let discard = L10n.tr("Localizable", "Common.Controls.Actions.Discard")
|
||||||
/// Done
|
/// Done
|
||||||
|
|
|
@ -252,7 +252,7 @@ extension UserProviderFacade {
|
||||||
} else {
|
} else {
|
||||||
let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
|
let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
|
||||||
guard let provider = provider else { return }
|
guard let provider = provider else { return }
|
||||||
let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domainFromAcct), preferredStyle: .alert)
|
let alertController = UIAlertController(title: L10n.Common.Alerts.BlockDomain.title(mastodonUser.domainFromAcct), message: nil, preferredStyle: .alert)
|
||||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
|
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
|
||||||
}
|
}
|
||||||
alertController.addAction(cancelAction)
|
alertController.addAction(cancelAction)
|
||||||
|
@ -300,6 +300,35 @@ extension UserProviderFacade {
|
||||||
children.append(shareAction)
|
children.append(shareAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let status = shareStatus, isMyself {
|
||||||
|
let deleteAction = UIAction(title: L10n.Common.Controls.Actions.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) {
|
||||||
|
[weak provider] _ in
|
||||||
|
guard let provider = provider else { return }
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: L10n.Common.Alerts.DeletePost.title, message: nil, preferredStyle: .alert)
|
||||||
|
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
|
||||||
|
}
|
||||||
|
alertController.addAction(cancelAction)
|
||||||
|
let deleteAction = UIAlertAction(title: L10n.Common.Alerts.DeletePost.delete, style: .destructive) { _ in
|
||||||
|
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||||
|
provider.context.apiService.deleteStatus(domain: activeMastodonAuthenticationBox.domain,
|
||||||
|
statusID: status.id,
|
||||||
|
authorizationBox: activeMastodonAuthenticationBox
|
||||||
|
)
|
||||||
|
.sink { _ in
|
||||||
|
// do nothing
|
||||||
|
} receiveValue: { _ in
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
.store(in: &provider.context.disposeBag)
|
||||||
|
}
|
||||||
|
alertController.addAction(deleteAction)
|
||||||
|
provider.present(alertController, animated: true, completion: nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
children.append(deleteAction)
|
||||||
|
}
|
||||||
|
|
||||||
return UIMenu(title: "", options: [], children: children)
|
return UIMenu(title: "", options: [], children: children)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
"Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain";
|
"Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain";
|
||||||
"Common.Alerts.BlockDomain.Message" = "Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.";
|
"Common.Alerts.BlockDomain.Title" = "Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.";
|
||||||
"Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
|
"Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
|
||||||
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
|
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
|
||||||
|
"Common.Alerts.DeletePost.Delete" = "Delete";
|
||||||
|
"Common.Alerts.DeletePost.Title" = "Are you sure you want to delete this post?";
|
||||||
"Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content.";
|
"Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content.";
|
||||||
"Common.Alerts.DiscardPostContent.Title" = "Discard Publish";
|
"Common.Alerts.DiscardPostContent.Title" = "Discard Publish";
|
||||||
"Common.Alerts.PublishPostFailure.Message" = "Failed to publish the post.
|
"Common.Alerts.PublishPostFailure.Message" = "Failed to publish the post.
|
||||||
|
@ -22,6 +24,7 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Actions.Cancel" = "Cancel";
|
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||||
"Common.Controls.Actions.Confirm" = "Confirm";
|
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||||
"Common.Controls.Actions.Continue" = "Continue";
|
"Common.Controls.Actions.Continue" = "Continue";
|
||||||
|
"Common.Controls.Actions.Delete" = "Delete";
|
||||||
"Common.Controls.Actions.Discard" = "Discard";
|
"Common.Controls.Actions.Discard" = "Discard";
|
||||||
"Common.Controls.Actions.Done" = "Done";
|
"Common.Controls.Actions.Done" = "Done";
|
||||||
"Common.Controls.Actions.Edit" = "Edit";
|
"Common.Controls.Actions.Edit" = "Edit";
|
||||||
|
|
|
@ -88,4 +88,50 @@ extension APIService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteStatus(
|
||||||
|
domain: String,
|
||||||
|
statusID: Mastodon.Entity.Status.ID,
|
||||||
|
authorizationBox: AuthenticationService.MastodonAuthenticationBox
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
|
let authorization = authorizationBox.userAuthorization
|
||||||
|
let query = Mastodon.API.Statuses.DeleteStatusQuery(id: statusID)
|
||||||
|
return Mastodon.API.Statuses.deleteStatus(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
query: query,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> in
|
||||||
|
return self.backgroundManagedObjectContext.performChanges{
|
||||||
|
// fetch old Status
|
||||||
|
let oldStatus: Status? = {
|
||||||
|
let request = Status.sortedFetchRequest
|
||||||
|
request.predicate = Status.predicate(domain: domain, id: response.value.id)
|
||||||
|
request.fetchLimit = 1
|
||||||
|
request.returnsObjectsAsFaults = false
|
||||||
|
do {
|
||||||
|
return try self.backgroundManagedObjectContext.fetch(request).first
|
||||||
|
} catch {
|
||||||
|
assertionFailure(error.localizedDescription)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if let status = oldStatus {
|
||||||
|
self.backgroundManagedObjectContext.delete(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setFailureType(to: Error.self)
|
||||||
|
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Status> in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
return response
|
||||||
|
case .failure(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Combine
|
||||||
|
|
||||||
extension Mastodon.API.Statuses {
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
static func viewStatusEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
static func statusEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||||
let pathComponent = "statuses/" + statusID
|
let pathComponent = "statuses/" + statusID
|
||||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ extension Mastodon.API.Statuses {
|
||||||
authorization: Mastodon.API.OAuth.Authorization?
|
authorization: Mastodon.API.OAuth.Authorization?
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
let request = Mastodon.API.get(
|
let request = Mastodon.API.get(
|
||||||
url: viewStatusEndpointURL(domain: domain, statusID: statusID),
|
url: statusEndpointURL(domain: domain, statusID: statusID),
|
||||||
query: nil,
|
query: nil,
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
)
|
)
|
||||||
|
@ -150,6 +150,54 @@ extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
|
/// Delete status
|
||||||
|
///
|
||||||
|
/// Delete one of your own statuses.
|
||||||
|
///
|
||||||
|
/// - Since: 0.0.0
|
||||||
|
/// - Version: 3.3.0
|
||||||
|
/// # Last Update
|
||||||
|
/// 2021/5/7
|
||||||
|
/// # Reference
|
||||||
|
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||||
|
/// - Parameters:
|
||||||
|
/// - session: `URLSession`
|
||||||
|
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||||
|
/// - query: `DeleteStatusQuery`
|
||||||
|
/// - authorization: User token
|
||||||
|
/// - Returns: `AnyPublisher` contains `Status` nested in the response
|
||||||
|
public static func deleteStatus(
|
||||||
|
session: URLSession,
|
||||||
|
domain: String,
|
||||||
|
query: DeleteStatusQuery,
|
||||||
|
authorization: Mastodon.API.OAuth.Authorization?
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
|
let request = Mastodon.API.delete(
|
||||||
|
url: statusEndpointURL(domain: domain, statusID: query.id),
|
||||||
|
query: query,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
return session.dataTaskPublisher(for: request)
|
||||||
|
.tryMap { data, response in
|
||||||
|
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)
|
||||||
|
return Mastodon.Response.Content(value: value, response: response)
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DeleteStatusQuery: Codable, DeleteQuery {
|
||||||
|
public let id: Mastodon.Entity.Status.ID
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: Mastodon.Entity.Status.ID
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Mastodon.API.Statuses {
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
static func statusContextEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
static func statusContextEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||||
|
|
|
@ -26,7 +26,7 @@ extension Mastodon.Entity {
|
||||||
public let uri: String
|
public let uri: String
|
||||||
public let createdAt: Date
|
public let createdAt: Date
|
||||||
public let account: Account
|
public let account: Account
|
||||||
public let content: String
|
public let content: String? // will be optional when delete status
|
||||||
|
|
||||||
public let visibility: Visibility?
|
public let visibility: Visibility?
|
||||||
public let sensitive: Bool?
|
public let sensitive: Bool?
|
||||||
|
|
Loading…
Reference in New Issue