Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

199 wiersze
7.7 KiB

  1. import SwiftUI
  2. import MobileCoreServices
  3. import UniformTypeIdentifiers
  4. import WriteFreely
  5. enum WFActionExtensionError: Error {
  6. case userCancelledRequest
  7. case couldNotParseInputItems
  8. }
  9. struct ContentView: View {
  10. @Environment(\.extensionContext) private var extensionContext: NSExtensionContext!
  11. @Environment(\.managedObjectContext) private var managedObjectContext
  12. @AppStorage(WFDefaults.defaultFontIntegerKey, store: UserDefaults.shared) var fontIndex: Int = 0
  13. @FetchRequest(
  14. entity: WFACollection.entity(),
  15. sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)]
  16. ) var collections: FetchedResults<WFACollection>
  17. @State private var draftTitle: String = ""
  18. @State private var draftText: String = ""
  19. @State private var isShowingAlert: Bool = false
  20. @State private var selectedBlog: WFACollection?
  21. private var draftsCollectionName: String {
  22. guard UserDefaults.shared.string(forKey: WFDefaults.serverStringKey) == "https://write.as" else {
  23. return "Drafts"
  24. }
  25. return "Anonymous"
  26. }
  27. private var controls: some View {
  28. HStack {
  29. Group {
  30. Button(
  31. action: { extensionContext.cancelRequest(withError: WFActionExtensionError.userCancelledRequest) },
  32. label: { Image(systemName: "xmark.circle").imageScale(.large) }
  33. )
  34. .accessibilityLabel(Text("Cancel"))
  35. Spacer()
  36. Button(
  37. action: {
  38. savePostToCollection(collection: selectedBlog, title: draftTitle, body: draftText)
  39. extensionContext.completeRequest(returningItems: nil, completionHandler: nil)
  40. },
  41. label: { Image(systemName: "square.and.arrow.down").imageScale(.large) }
  42. )
  43. .accessibilityLabel(Text("Create new draft"))
  44. }
  45. .padding()
  46. }
  47. }
  48. var body: some View {
  49. VStack {
  50. controls
  51. Form {
  52. Section(header: Text("Title")) {
  53. switch fontIndex {
  54. case 1:
  55. TextField("Draft Title", text: $draftTitle).font(.custom("OpenSans-Regular", size: 26))
  56. case 2:
  57. TextField("Draft Title", text: $draftTitle).font(.custom("Hack-Regular", size: 26))
  58. default:
  59. TextField("Draft Title", text: $draftTitle).font(.custom("Lora", size: 26))
  60. }
  61. }
  62. Section(header: Text("Content")) {
  63. switch fontIndex {
  64. case 1:
  65. TextEditor(text: $draftText).font(.custom("OpenSans-Regular", size: 17))
  66. case 2:
  67. TextEditor(text: $draftText).font(.custom("Hack-Regular", size: 17))
  68. default:
  69. TextEditor(text: $draftText).font(.custom("Lora", size: 17))
  70. }
  71. }
  72. Section(header: Text("Save To")) {
  73. Button(action: {
  74. self.selectedBlog = nil
  75. }, label: {
  76. HStack {
  77. Text(draftsCollectionName)
  78. .foregroundColor(selectedBlog == nil ? .primary : .secondary)
  79. Spacer()
  80. if selectedBlog == nil {
  81. Image(systemName: "checkmark")
  82. }
  83. }
  84. })
  85. ForEach(collections, id: \.self) { collection in
  86. Button(action: {
  87. self.selectedBlog = collection
  88. }, label: {
  89. HStack {
  90. Text(collection.title)
  91. .foregroundColor(selectedBlog == collection ? .primary : .secondary)
  92. Spacer()
  93. if selectedBlog == collection {
  94. Image(systemName: "checkmark")
  95. }
  96. }
  97. })
  98. }
  99. }
  100. }
  101. .padding(.bottom, 24)
  102. }
  103. .alert(isPresented: $isShowingAlert, content: {
  104. Alert(
  105. title: Text("Something Went Wrong"),
  106. message: Text("WriteFreely can't create a draft with the data received."),
  107. dismissButton: .default(Text("OK"), action: {
  108. extensionContext.cancelRequest(withError: WFActionExtensionError.couldNotParseInputItems)
  109. }))
  110. })
  111. .onAppear {
  112. do {
  113. try getPageDataFromExtensionContext()
  114. } catch {
  115. self.isShowingAlert = true
  116. }
  117. }
  118. }
  119. private func savePostToCollection(collection: WFACollection?, title: String, body: String) {
  120. let post = WFAPost(context: managedObjectContext)
  121. post.createdDate = Date()
  122. post.title = title
  123. post.body = body
  124. post.status = PostStatus.local.rawValue
  125. post.collectionAlias = collection?.alias
  126. switch fontIndex {
  127. case 1:
  128. post.appearance = "sans"
  129. case 2:
  130. post.appearance = "wrap"
  131. default:
  132. post.appearance = "serif"
  133. }
  134. if let languageCode = Locale.current.languageCode {
  135. post.language = languageCode
  136. post.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
  137. }
  138. LocalStorageManager.standard.saveContext()
  139. }
  140. private func getPageDataFromExtensionContext() throws {
  141. if let inputItem = extensionContext.inputItems.first as? NSExtensionItem {
  142. if let itemProvider = inputItem.attachments?.first {
  143. let typeIdentifier: String
  144. if #available(iOS 15, *) {
  145. typeIdentifier = UTType.propertyList.identifier
  146. } else {
  147. typeIdentifier = kUTTypePropertyList as String
  148. }
  149. itemProvider.loadItem(forTypeIdentifier: typeIdentifier) { (dict, error) in
  150. if error != nil {
  151. self.isShowingAlert = true
  152. }
  153. guard let itemDict = dict as? NSDictionary else {
  154. return
  155. }
  156. guard let jsValues = itemDict[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else {
  157. return
  158. }
  159. let pageTitle = jsValues["title"] as? String ?? ""
  160. let pageURL = jsValues["URL"] as? String ?? ""
  161. let pageSelectedText = jsValues["selection"] as? String ?? ""
  162. if pageSelectedText.isEmpty {
  163. // If there's no selected text, create a Markdown link to the webpage.
  164. self.draftText = "[\(pageTitle)](\(pageURL))"
  165. } else {
  166. // If there is selected text, create a Markdown blockquote with the selection
  167. // and add a Markdown link to the webpage.
  168. self.draftText = """
  169. > \(pageSelectedText)
  170. Via: [\(pageTitle)](\(pageURL))
  171. """
  172. }
  173. }
  174. } else {
  175. throw WFActionExtensionError.couldNotParseInputItems
  176. }
  177. } else {
  178. throw WFActionExtensionError.couldNotParseInputItems
  179. }
  180. }
  181. }