From d5c9473528b34a50ceb82cede013db807dda3ea8 Mon Sep 17 00:00:00 2001
From: CMK <cirno.mainasuk@gmail.com>
Date: Wed, 14 Apr 2021 15:59:29 +0800
Subject: [PATCH] feat: implement reply status entry and update query of API

---
 .../CoreData.xcdatamodel/contents             |  7 ++--
 CoreDataStack/Entity/Mention.swift            |  7 +++-
 .../Section/ComposeStatusSection.swift        | 12 +++++++
 Mastodon/Helper/MastodonField.swift           |  2 +-
 ...Provider+StatusTableViewCellDelegate.swift |  4 +++
 .../StatusProvider/StatusProviderFacade.swift | 32 ++++++++++++++++++-
 .../StatusTableViewControllerAspect.swift     |  2 +-
 .../Scene/Compose/ComposeViewController.swift |  2 +-
 .../ComposeViewModel+PublishState.swift       | 11 +++++++
 Mastodon/Scene/Compose/ComposeViewModel.swift | 31 +++++++++++++++++-
 .../HashtagTimelineViewController.swift       |  4 +++
 .../Favorite/FavoriteViewController.swift     |  4 +++
 .../TableviewCell/StatusTableViewCell.swift   | 11 ++++---
 .../Scene/Thread/ThreadViewController.swift   |  7 ++--
 .../CoreData/APIService+CoreData+Status.swift |  4 +--
 .../API/Mastodon+API+Statuses.swift           |  5 ++-
 16 files changed, 126 insertions(+), 19 deletions(-)

diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
index 5ed4021a7..eb095669a 100644
--- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
+++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D75" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
     <entity name="Application" representedClassName=".Application" syncable="YES">
         <attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
         <attribute name="name" attributeType="String"/>
@@ -115,6 +115,7 @@
         <attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
         <attribute name="id" attributeType="String"/>
         <attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
+        <attribute name="index" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
         <attribute name="url" attributeType="String"/>
         <attribute name="username" attributeType="String"/>
         <relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="mentions" inverseEntity="Status"/>
@@ -209,7 +210,7 @@
         <element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
         <element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/>
         <element name="MastodonUser" positionX="0" positionY="0" width="128" height="659"/>
-        <element name="Mention" positionX="0" positionY="0" width="128" height="134"/>
+        <element name="Mention" positionX="0" positionY="0" width="128" height="149"/>
         <element name="Poll" positionX="0" positionY="0" width="128" height="194"/>
         <element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
         <element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
@@ -217,4 +218,4 @@
         <element name="Status" positionX="0" positionY="0" width="128" height="569"/>
         <element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
     </elements>
-</model>
+</model>
\ No newline at end of file
diff --git a/CoreDataStack/Entity/Mention.swift b/CoreDataStack/Entity/Mention.swift
index 9559ea5d5..864ca4948 100644
--- a/CoreDataStack/Entity/Mention.swift
+++ b/CoreDataStack/Entity/Mention.swift
@@ -10,6 +10,9 @@ import Foundation
 
 public final class Mention: NSManagedObject {
     public typealias ID = UUID
+    
+    @NSManaged public private(set) var index: NSNumber
+    
     @NSManaged public private(set) var identifier: ID
     @NSManaged public private(set) var id: String
     @NSManaged public private(set) var createAt: Date
@@ -32,9 +35,11 @@ public extension Mention {
     @discardableResult
     static func insert(
         into context: NSManagedObjectContext,
-        property: Property
+        property: Property,
+        index: Int
     ) -> Mention {
         let mention: Mention = context.insertObject()
+        mention.index = NSNumber(value: index)
         mention.id = property.id
         mention.username = property.username
         mention.acct = property.acct
diff --git a/Mastodon/Diffiable/Section/ComposeStatusSection.swift b/Mastodon/Diffiable/Section/ComposeStatusSection.swift
index c8a8bc180..4b0b5aa57 100644
--- a/Mastodon/Diffiable/Section/ComposeStatusSection.swift
+++ b/Mastodon/Diffiable/Section/ComposeStatusSection.swift
@@ -57,7 +57,19 @@ extension ComposeStatusSection {
                         return
                     }
                     let status = replyTo.reblog ?? replyTo
+                    
+                    // set avatar
                     cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
+                    // set name username
+                    cell.statusView.nameLabel.text = {
+                        let author = status.author
+                        return author.displayName.isEmpty ? author.username : author.displayName
+                    }()
+                    cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
+                    // set text
+                    cell.statusView.activeTextLabel.configure(content: status.content)
+                    // set date
+                    cell.statusView.dateLabel.text = status.createdAt.shortTimeAgoSinceNow
                 }
                 return cell
             case .input(let replyToStatusObjectID, let attribute):
diff --git a/Mastodon/Helper/MastodonField.swift b/Mastodon/Helper/MastodonField.swift
index e828602e4..5f652b32c 100644
--- a/Mastodon/Helper/MastodonField.swift
+++ b/Mastodon/Helper/MastodonField.swift
@@ -11,7 +11,7 @@ import ActiveLabel
 enum MastodonField {
     
     static func parse(field string: String) -> ParseResult {
-        let mentionMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.]+)?)")
+        let mentionMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?)")
         let hashtagMatches = string.matches(pattern: "(?:#([^\\s.]+))")
         let urlMatches = string.matches(pattern: "(?i)https?://\\S+(?:/|\\b)")
         
diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift
index 2983a6f96..25322e216 100644
--- a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift
+++ b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift
@@ -33,6 +33,10 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
 // MARK: - ActionToolbarContainerDelegate
 extension StatusTableViewCellDelegate where Self: StatusProvider {
     
+    func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, replyButtonDidPressed sender: UIButton) {
+        StatusProviderFacade.responseToStatusReplyAction(provider: self, cell: cell)
+    }
+    
     func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, reblogButtonDidPressed sender: UIButton) {
         StatusProviderFacade.responseToStatusReblogAction(provider: self, cell: cell)
     }
diff --git a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift
index 6db861ec6..0e26614c5 100644
--- a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift
+++ b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift
@@ -277,7 +277,6 @@ extension StatusProviderFacade {
 }
 
 extension StatusProviderFacade {
- 
     
     static func responseToStatusReblogAction(provider: StatusProvider) {
         _responseToStatusReblogAction(
@@ -385,6 +384,37 @@ extension StatusProviderFacade {
 
 }
 
+extension StatusProviderFacade {
+    
+    static func responseToStatusReplyAction(provider: StatusProvider) {
+        _responseToStatusReplyAction(
+            provider: provider,
+            status: provider.status()
+        )
+    }
+    
+    static func responseToStatusReplyAction(provider: StatusProvider, cell: UITableViewCell) {
+        _responseToStatusReplyAction(
+            provider: provider,
+            status: provider.status(for: cell, indexPath: nil)
+        )
+    }
+    
+    private static func _responseToStatusReplyAction(provider: StatusProvider, status: Future<Status?, Never>) {
+        status
+            .sink { [weak provider] status in
+                guard let provider = provider else { return }
+                guard let status = status?.reblog ?? status else { return }
+                
+                let composeViewModel = ComposeViewModel(context: provider.context, composeKind: .reply(repliedToStatusObjectID: status.objectID))
+                provider.coordinator.present(scene: .compose(viewModel: composeViewModel), from: provider, transition: .modal(animated: true, completion: nil))
+            }
+            .store(in: &provider.context.disposeBag)
+        
+    }
+    
+}
+
 extension StatusProviderFacade {
     enum Target {
         case primary        // original status
diff --git a/Mastodon/Protocol/StatusTableViewControllerAspect.swift b/Mastodon/Protocol/StatusTableViewControllerAspect.swift
index 77b1e17ba..f96998ea6 100644
--- a/Mastodon/Protocol/StatusTableViewControllerAspect.swift
+++ b/Mastodon/Protocol/StatusTableViewControllerAspect.swift
@@ -10,7 +10,7 @@ import AVKit
 
 //   Check List                     Last Updated
 // - HomeViewController:                2021/4/13
-// - FavoriteViewController:            2021/4/8
+// - FavoriteViewController:            2021/4/14
 // - HashtagTimelineViewController:     2021/4/8
 // - UserTimelineViewController:        2021/4/13
 // - ThreadViewController:              2021/4/13
diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift
index e68be7295..29b8850b9 100644
--- a/Mastodon/Scene/Compose/ComposeViewController.swift
+++ b/Mastodon/Scene/Compose/ComposeViewController.swift
@@ -548,7 +548,7 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
             os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update: %s", ((#file as NSString).lastPathComponent), #line, #function, string)
 
             let stringRange = NSRange(location: 0, length: string.length)
-            let highlightMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.]+)?|#([^\\s.]+))")
+            let highlightMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))")
             // accept ^\B: or \s: but not accept \B: to force user input a space to make emoji take effect
             // precondition :\B with following space 
             let emojiMatches = string.matches(pattern: "(?:(^\\B:|\\s:)([a-zA-Z0-9_]+)(:\\B(?=\\s)))")
diff --git a/Mastodon/Scene/Compose/ComposeViewModel+PublishState.swift b/Mastodon/Scene/Compose/ComposeViewModel+PublishState.swift
index c3e903812..fd3f5bce0 100644
--- a/Mastodon/Scene/Compose/ComposeViewModel+PublishState.swift
+++ b/Mastodon/Scene/Compose/ComposeViewModel+PublishState.swift
@@ -8,6 +8,7 @@
 import os.log
 import Foundation
 import Combine
+import CoreDataStack
 import GameplayKit
 import MastodonSDK
 
@@ -64,6 +65,15 @@ extension ComposeViewModel.PublishState {
                 guard viewModel.isPollComposing.value else { return nil }
                 return viewModel.pollExpiresOptionAttribute.expiresOption.value.seconds
             }()
+            let inReplyToID: Mastodon.Entity.Status.ID? = {
+                guard case let .reply(repliedToStatusObjectID) = viewModel.composeKind else { return nil }
+                var id: Mastodon.Entity.Status.ID?
+                viewModel.context.managedObjectContext.performAndWait {
+                    guard let replyTo = viewModel.context.managedObjectContext.object(with: repliedToStatusObjectID) as? Status else { return }
+                    id = replyTo.id
+                }
+                return id
+            }()
             let sensitive: Bool = viewModel.isContentWarningComposing.value
             let spoilerText: String? = {
                 let text = viewModel.composeStatusAttribute.contentWarningContent.value.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -105,6 +115,7 @@ extension ComposeViewModel.PublishState {
                         mediaIDs: mediaIDs.isEmpty ? nil : mediaIDs,
                         pollOptions: pollOptions,
                         pollExpiresIn: pollExpiresIn,
+                        inReplyToID: inReplyToID,
                         sensitive: sensitive,
                         spoilerText: spoilerText,
                         visibility: visibility
diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift
index 1043d8bec..56efd2529 100644
--- a/Mastodon/Scene/Compose/ComposeViewModel.swift
+++ b/Mastodon/Scene/Compose/ComposeViewModel.swift
@@ -87,7 +87,36 @@ final class ComposeViewModel {
         self.activeAuthentication = CurrentValueSubject(context.authenticationService.activeMastodonAuthentication.value)
         self.activeAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
         // end init
-        if case let .hashtag(text) = composeKind {
+        if case let .reply(repliedToStatusObjectID) = composeKind {
+            context.managedObjectContext.performAndWait {
+                guard let status = context.managedObjectContext.object(with: repliedToStatusObjectID) as? Status else { return }
+                let composeAuthor: MastodonUser? = {
+                    guard let objectID = self.activeAuthentication.value?.user.objectID else { return nil }
+                    guard let author = context.managedObjectContext.object(with: objectID) as? MastodonUser else { return nil }
+                    return author
+                }()
+                
+                var mentionAccts: [String] = []
+                if composeAuthor?.id != status.author.id {
+                    mentionAccts.append("@" + status.author.acct)
+                }
+                let mentions = (status.mentions ?? Set())
+                    .sorted(by: { $0.index.intValue < $1.index.intValue })
+                    .filter { $0.id != composeAuthor?.id }
+                for mention in mentions {
+                    mentionAccts.append("@" + mention.acct)
+                }
+                for acct in mentionAccts {
+                    UITextChecker.learnWord(acct)
+                }
+                
+                let initialComposeContent = mentionAccts.joined(separator: " ")
+                let preInsertedContent: String? = initialComposeContent.isEmpty ? nil : initialComposeContent + " "
+                self.preInsertedContent = preInsertedContent
+                self.composeStatusAttribute.composeContent.value = preInsertedContent
+            }
+            
+        } else if case let .hashtag(text) = composeKind {
             let initialComposeContent = "#" + text
             UITextChecker.learnWord(initialComposeContent)
             let preInsertedContent = initialComposeContent + " "
diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift
index c9bf87410..4b45d6638 100644
--- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift
+++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift
@@ -218,6 +218,10 @@ extension HashtagTimelineViewController: UITableViewDelegate {
     func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
         aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
     }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        aspectTableView(tableView, didSelectRowAt: indexPath)
+    }
 }
 
 // MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift
index a175ae348..8205d5a2e 100644
--- a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift
+++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift
@@ -114,6 +114,10 @@ extension FavoriteViewController: UITableViewDelegate {
         aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
     }
     
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        aspectTableView(tableView, didSelectRowAt: indexPath)
+    }
+    
 }
 
 // MARK: - UITableViewDataSourcePrefetching
diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift
index 39916741e..10fcdca3c 100644
--- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift
+++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift
@@ -32,6 +32,7 @@ protocol StatusTableViewCellDelegate: class {
     func statusTableViewCell(_ cell: StatusTableViewCell, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
     func statusTableViewCell(_ cell: StatusTableViewCell, playerViewControllerDidPressed playerViewController: AVPlayerViewController)
     
+    func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, replyButtonDidPressed sender: UIButton)
     func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, reblogButtonDidPressed sender: UIButton)
     func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
     
@@ -302,19 +303,21 @@ extension StatusTableViewCell: MosaicImageViewContainerDelegate {
 
 // MARK: - ActionToolbarContainerDelegate
 extension StatusTableViewCell: ActionToolbarContainerDelegate {
+    
     func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) {
-        
+        delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, replyButtonDidPressed: sender)
     }
+    
     func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, reblogButtonDidPressed sender: UIButton) {
         delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, reblogButtonDidPressed: sender)
     }
+    
     func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) {
         delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, likeButtonDidPressed: sender)
     }
-    func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, bookmarkButtonDidPressed sender: UIButton) {
-        
-    }
+    
     func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) {
         
     }
+    
 }
diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift
index 43c40025e..db7c76a75 100644
--- a/Mastodon/Scene/Thread/ThreadViewController.swift
+++ b/Mastodon/Scene/Thread/ThreadViewController.swift
@@ -88,8 +88,6 @@ extension ThreadViewController {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         
-        // force readable layout frame update
-        tableView.reloadData()
         aspectViewWillAppear(animated)
     }
     
@@ -104,7 +102,10 @@ extension ThreadViewController {
 extension ThreadViewController {
     @objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
         os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
-        
+        guard let rootItem = viewModel.rootItem.value,
+              case let .root(statusObjectID, _) = rootItem else { return }
+        let composeViewModel = ComposeViewModel(context: context, composeKind: .reply(repliedToStatusObjectID: statusObjectID))
+        coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
     }
 }
 
diff --git a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift
index a05574b6b..328fa2305 100644
--- a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift
+++ b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Status.swift
@@ -86,8 +86,8 @@ extension APIService.CoreData {
                 let object = Poll.insert(into: managedObjectContext, property: Poll.Property(id: poll.id, expiresAt: poll.expiresAt, expired: poll.expired, multiple: poll.multiple, votesCount: poll.votesCount, votersCount: poll.votersCount, networkDate: networkDate), votedBy: votedBy, options: options)
                 return object
             }
-            let metions = entity.mentions?.compactMap { mention -> Mention in
-                Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url))
+            let metions = entity.mentions?.enumerated().compactMap { index, mention -> Mention in
+                Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url), index: index)
             }
             let emojis = entity.emojis?.compactMap { emoji -> Emoji in
                 Emoji.insert(into: managedObjectContext, property: Emoji.Property(shortcode: emoji.shortcode, url: emoji.url, staticURL: emoji.staticURL, visibleInPicker: emoji.visibleInPicker, category: emoji.category))
diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses.swift
index ae5d5e670..bb5a4abfc 100644
--- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses.swift
+++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses.swift
@@ -98,6 +98,7 @@ extension Mastodon.API.Statuses {
         public let mediaIDs: [String]?
         public let pollOptions: [String]?
         public let pollExpiresIn: Int?
+        public let inReplyToID: Mastodon.Entity.Status.ID?
         public let sensitive: Bool?
         public let spoilerText: String?
         public let visibility: Mastodon.Entity.Status.Visibility?
@@ -107,6 +108,7 @@ extension Mastodon.API.Statuses {
             mediaIDs: [String]?,
             pollOptions: [String]?,
             pollExpiresIn: Int?,
+            inReplyToID: Mastodon.Entity.Status.ID?,
             sensitive: Bool?,
             spoilerText: String?,
             visibility: Mastodon.Entity.Status.Visibility?
@@ -115,10 +117,10 @@ extension Mastodon.API.Statuses {
             self.mediaIDs = mediaIDs
             self.pollOptions = pollOptions
             self.pollExpiresIn = pollExpiresIn
+            self.inReplyToID = inReplyToID
             self.sensitive = sensitive
             self.spoilerText = spoilerText
             self.visibility = visibility
-            
         }
         
         var contentType: String? {
@@ -136,6 +138,7 @@ extension Mastodon.API.Statuses {
                 data.append(Data.multipart(key: "poll[options][]", value: pollOption))
             }
             pollExpiresIn.flatMap { data.append(Data.multipart(key: "poll[expires_in]", value: $0)) }
+            inReplyToID.flatMap { data.append(Data.multipart(key: "in_reply_to_id", 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)) }