Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10671260
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Subscribers
None
View Options
diff --git a/Shared/ErrorHandling/ErrorConstants.swift b/Shared/ErrorHandling/ErrorConstants.swift
index 467b858..1b66594 100644
--- a/Shared/ErrorHandling/ErrorConstants.swift
+++ b/Shared/ErrorHandling/ErrorConstants.swift
@@ -1,116 +1,140 @@
import Foundation
// MARK: - Network Errors
enum NetworkError: Error {
case noConnectionError
}
extension NetworkError: LocalizedError {
public var errorDescription: String? {
switch self {
case .noConnectionError:
return NSLocalizedString(
"There is no internet connection at the moment. Please reconnect or try again later.",
comment: ""
)
}
}
}
// MARK: - Keychain Errors
enum KeychainError: Error {
case couldNotStoreAccessToken
case couldNotPurgeAccessToken
}
extension KeychainError: LocalizedError {
public var errorDescription: String? {
switch self {
case .couldNotStoreAccessToken:
return NSLocalizedString("There was a problem storing your access token in the Keychain.", comment: "")
case .couldNotPurgeAccessToken:
return NSLocalizedString("Something went wrong purging the token from the Keychain.", comment: "")
}
}
}
// MARK: - Account Errors
enum AccountError: Error {
case invalidPassword
case usernameNotFound
case serverNotFound
case invalidServerURL
case couldNotSaveTokenToKeychain
case couldNotFetchTokenFromKeychain
case couldNotDeleteTokenFromKeychain
case unknownLoginError
case genericAuthError
}
extension AccountError: LocalizedError {
public var errorDescription: String? {
switch self {
case .serverNotFound:
return NSLocalizedString(
"The server could not be found. Please check the information you've entered and try again.",
comment: ""
)
case .invalidPassword:
return NSLocalizedString(
"Invalid password. Please check that you've entered your password correctly and try logging in again.",
comment: ""
)
case .usernameNotFound:
return NSLocalizedString(
"Username not found. Did you use your email address by mistake?",
comment: ""
)
case .invalidServerURL:
return NSLocalizedString(
"Please enter a valid instance domain name. It should look like \"https://example.com\" or \"write.as\".", // swiftlint:disable:this line_length
comment: ""
)
case .couldNotSaveTokenToKeychain:
return NSLocalizedString(
"There was a problem trying to save your access token to the device, please try logging in again.",
comment: ""
)
case .couldNotFetchTokenFromKeychain:
return NSLocalizedString(
"There was a problem trying to fetch your access token from the device, please try logging in again.",
comment: ""
)
case .couldNotDeleteTokenFromKeychain:
return NSLocalizedString(
"There was a problem trying to delete your access token from the device, please try logging out again.",
comment: ""
)
case .genericAuthError:
return NSLocalizedString("Something went wrong, please try logging in again.", comment: "")
case .unknownLoginError:
- return NSLocalizedString("An unknown error occurred while trying to login", comment: "")
+ return NSLocalizedString("An unknown error occurred while trying to login.", comment: "")
}
}
}
// MARK: - Local Store Errors
enum LocalStoreError: Error {
+ case couldNotFetchCollections
case couldNotFetchPosts(String)
+ case couldNotPurgePublishedPosts
}
extension LocalStoreError: LocalizedError {
public var errorDescription: String? {
switch self {
+ case .couldNotFetchCollections:
+ return NSLocalizedString("Failed to fetch blogs from local store.", comment: "")
case .couldNotFetchPosts(let postFilter):
if postFilter.isEmpty {
- return NSLocalizedString("Failed to fetch posts from local store", comment: "")
+ return NSLocalizedString("Failed to fetch posts from local store.", comment: "")
} else {
- return NSLocalizedString("Failed to fetch \(postFilter) posts from local store", comment: "")
+ return NSLocalizedString("Failed to fetch \(postFilter) posts from local store.", comment: "")
}
+ case .couldNotPurgePublishedPosts:
+ return NSLocalizedString("Failed to purge published posts from local store.", comment: "")
+ }
+ }
+}
+
+// MARK: - Application Errors
+
+enum AppError: Error {
+ case couldNotGetLoggedInClient
+ case couldNotGetPostId
+}
+
+extension AppError: LocalizedError {
+ public var errorDescription: String? {
+ switch self {
+ case .couldNotGetLoggedInClient:
+ return NSLocalizedString("Something went wrong trying to access the WriteFreely client.", comment: "")
+ case .couldNotGetPostId:
+ return NSLocalizedString("Something went wrong trying to get the post's unique ID.", comment: "")
}
}
}
diff --git a/Shared/Extensions/WriteFreelyModel+API.swift b/Shared/Extensions/WriteFreelyModel+API.swift
index 01edabc..55282b1 100644
--- a/Shared/Extensions/WriteFreelyModel+API.swift
+++ b/Shared/Extensions/WriteFreelyModel+API.swift
@@ -1,164 +1,173 @@
import Foundation
import WriteFreely
extension WriteFreelyModel {
func login(to server: URL, as username: String, password: String) {
if !hasNetworkConnection {
isPresentingNetworkErrorAlert = true
return
}
let secureProtocolPrefix = "https://"
let insecureProtocolPrefix = "http://"
var serverString = server.absoluteString
// If there's neither an http or https prefix, prepend "https://" to the server string.
if !(serverString.hasPrefix(secureProtocolPrefix) || serverString.hasPrefix(insecureProtocolPrefix)) {
serverString = secureProtocolPrefix + serverString
}
// If the server string is prefixed with http, upgrade to https before attempting to login.
if serverString.hasPrefix(insecureProtocolPrefix) {
serverString = serverString.replacingOccurrences(of: insecureProtocolPrefix, with: secureProtocolPrefix)
}
isLoggingIn = true
var serverURL = URL(string: serverString)!
if !serverURL.path.isEmpty {
serverURL.deleteLastPathComponent()
}
account.server = serverURL.absoluteString
client = WFClient(for: serverURL)
client?.login(username: username, password: password, completion: loginHandler)
}
func logout() {
if !hasNetworkConnection {
DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true }
return
}
guard let loggedInClient = client else {
do {
try purgeTokenFromKeychain(username: account.username, server: account.server)
account.logout()
} catch {
- fatalError("Failed to log out persisted state")
+ self.currentError = KeychainError.couldNotPurgeAccessToken
}
return
}
loggedInClient.logout(completion: logoutHandler)
}
func fetchUserCollections() {
if !hasNetworkConnection {
DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true }
return
}
guard let loggedInClient = client else {
- fatalError("Could not get logged in client")
+ self.currentError = AppError.couldNotGetLoggedInClient
+ return
}
// We're starting the network request.
DispatchQueue.main.async {
self.isProcessingRequest = true
}
loggedInClient.getUserCollections(completion: fetchUserCollectionsHandler)
}
func fetchUserPosts() {
if !hasNetworkConnection {
DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true }
return
}
guard let loggedInClient = client else {
- fatalError("Could not get logged in client")
+ self.currentError = AppError.couldNotGetLoggedInClient
+ return
}
// We're starting the network request.
DispatchQueue.main.async {
self.isProcessingRequest = true
}
loggedInClient.getPosts(completion: fetchUserPostsHandler)
}
func publish(post: WFAPost) {
postToUpdate = nil
if !hasNetworkConnection {
DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true }
return
}
guard let loggedInClient = client else {
- fatalError("Could not get logged in client")
+ self.currentError = AppError.couldNotGetLoggedInClient
+ return
}
// We're starting the network request.
DispatchQueue.main.async {
self.isProcessingRequest = true
}
if post.language == nil {
if let languageCode = Locale.current.languageCode {
post.language = languageCode
post.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
}
}
var wfPost = WFPost(
body: post.body,
title: post.title.isEmpty ? "" : post.title,
appearance: post.appearance,
language: post.language,
rtl: post.rtl,
createdDate: post.status == PostStatus.local.rawValue ? Date() : post.createdDate
)
if let existingPostId = post.postId {
// This is an existing post.
postToUpdate = post
wfPost.postId = post.postId
loggedInClient.updatePost(
postId: existingPostId,
updatedPost: wfPost,
completion: publishHandler
)
} else {
// This is a new local draft.
loggedInClient.createPost(
post: wfPost, in: post.collectionAlias, completion: publishHandler
)
}
}
func updateFromServer(post: WFAPost) {
if !hasNetworkConnection {
DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true }
return
}
guard let loggedInClient = client else {
- fatalError("Could not get logged in client")
+ self.currentError = AppError.couldNotGetLoggedInClient
+ return
}
guard let postId = post.postId else {
- fatalError("Could not get post ID")
+ self.currentError = AppError.couldNotGetPostId
+ return
}
// We're starting the network request.
DispatchQueue.main.async {
self.selectedPost = post
self.isProcessingRequest = true
}
loggedInClient.getPost(byId: postId, completion: updateFromServerHandler)
}
func move(post: WFAPost, from oldCollection: WFACollection?, to newCollection: WFACollection?) {
if !hasNetworkConnection {
DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true }
return
}
- guard let loggedInClient = client,
- let postId = post.postId else {
- fatalError("Could not get post ID")
- }
+ guard let loggedInClient = client else {
+ self.currentError = AppError.couldNotGetLoggedInClient
+ return
+ }
+ guard let postId = post.postId else {
+ self.currentError = AppError.couldNotGetPostId
+ return
+ }
// We're starting the network request.
DispatchQueue.main.async {
self.isProcessingRequest = true
}
selectedPost = post
post.collectionAlias = newCollection?.alias
loggedInClient.movePost(postId: postId, to: newCollection?.alias, completion: movePostHandler)
}
}
diff --git a/Shared/PostCollection/CollectionListModel.swift b/Shared/PostCollection/CollectionListModel.swift
index 8beae45..b2e6be3 100644
--- a/Shared/PostCollection/CollectionListModel.swift
+++ b/Shared/PostCollection/CollectionListModel.swift
@@ -1,40 +1,40 @@
import SwiftUI
import CoreData
class CollectionListModel: NSObject, ObservableObject {
@Published var list: [WFACollection] = []
private let collectionsController: NSFetchedResultsController<WFACollection>
- init(managedObjectContext: NSManagedObjectContext) {
+ init(managedObjectContext: NSManagedObjectContext) throws {
collectionsController = NSFetchedResultsController(fetchRequest: WFACollection.collectionsFetchRequest,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil)
super.init()
collectionsController.delegate = self
do {
try collectionsController.performFetch()
list = collectionsController.fetchedObjects ?? []
} catch {
- fatalError("Failed to fetch collections!")
+ throw LocalStoreError.couldNotFetchCollections
}
}
}
extension CollectionListModel: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let collections = controller.fetchedObjects as? [WFACollection] else { return }
self.list = collections
}
}
extension WFACollection {
static var collectionsFetchRequest: NSFetchRequest<WFACollection> {
let request: NSFetchRequest<WFACollection> = WFACollection.createFetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)]
return request
}
}
diff --git a/Shared/PostList/PostListModel.swift b/Shared/PostList/PostListModel.swift
index 4cf62b0..86c6b9e 100644
--- a/Shared/PostList/PostListModel.swift
+++ b/Shared/PostList/PostListModel.swift
@@ -1,126 +1,126 @@
import SwiftUI
import CoreData
class PostListModel: ObservableObject {
func remove(_ post: WFAPost) {
withAnimation {
LocalStorageManager.standard.container.viewContext.delete(post)
LocalStorageManager.standard.saveContext()
}
}
- func purgePublishedPosts() {
+ func purgePublishedPosts() throws {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "WFAPost")
fetchRequest.predicate = NSPredicate(format: "status != %i", 0)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try LocalStorageManager.standard.container.viewContext.executeAndMergeChanges(using: deleteRequest)
} catch {
- fatalError("Error: Failed to purge cached posts.")
+ throw LocalStoreError.couldNotPurgePublishedPosts
}
}
func getBodyPreview(of post: WFAPost) -> String {
var elidedPostBody: String = ""
// Strip any markdown from the post body.
let strippedPostBody = stripMarkdown(from: post.body)
// Extract lede from post.
elidedPostBody = extractLede(from: strippedPostBody)
return elidedPostBody
}
}
private extension PostListModel {
func stripMarkdown(from string: String) -> String {
var strippedString = string
strippedString = stripHeadingOctothorpes(from: strippedString)
strippedString = stripImages(from: strippedString, keepAltText: true)
return strippedString
}
func stripHeadingOctothorpes(from string: String) -> String {
let newLines = CharacterSet.newlines
var processedComponents: [String] = []
let components = string.components(separatedBy: newLines)
for component in components {
if component.isEmpty {
continue
}
var newString = component
while newString.first == "#" {
newString.removeFirst()
}
if newString.hasPrefix(" ") {
newString.removeFirst()
}
processedComponents.append(newString)
}
let headinglessString = processedComponents.joined(separator: "\n\n")
return headinglessString
}
func stripImages(from string: String, keepAltText: Bool = false) -> String {
let pattern = #"!\[[\"]?(.*?)[\"|]?\]\(.*?\)"#
var processedComponents: [String] = []
let components = string.components(separatedBy: .newlines)
for component in components {
if component.isEmpty { continue }
var processedString: String = component
if keepAltText {
let regex = try? NSRegularExpression(pattern: pattern, options: [])
if let matches = regex?.matches(
in: component, options: [], range: NSRange(location: 0, length: component.utf16.count)
) {
for match in matches {
if let range = Range(match.range(at: 1), in: component) {
processedString = "\(component[range])"
}
}
}
} else {
let range = component.startIndex..<component.endIndex
processedString = component.replacingOccurrences(
of: pattern,
with: "",
options: .regularExpression,
range: range
)
}
if processedString.isEmpty { continue }
processedComponents.append(processedString)
}
return processedComponents.joined(separator: "\n\n")
}
func extractLede(from string: String) -> String {
let truncatedString = string.prefix(80)
let terminatingPunctuation = ".。?"
let terminatingCharacters = CharacterSet(charactersIn: terminatingPunctuation).union(.newlines)
var lede: String = ""
let sentences = truncatedString.components(separatedBy: terminatingCharacters)
if let firstSentence = (sentences.filter { !$0.isEmpty }).first {
if truncatedString.count > firstSentence.count {
if terminatingPunctuation.contains(truncatedString[firstSentence.endIndex]) {
lede = String(truncatedString[...firstSentence.endIndex])
} else {
lede = firstSentence
}
} else if truncatedString.count == firstSentence.count {
if string.count > 80 {
if let endOfStringIndex = truncatedString.lastIndex(of: " ") {
lede = truncatedString[..<endOfStringIndex] + "…"
}
} else {
lede = firstSentence
}
}
}
return lede
}
}
diff --git a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist
index 155f2da..33a3444 100644
--- a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -1,24 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>ActionExtension-iOS.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
- <integer>0</integer>
+ <integer>1</integer>
</dict>
<key>WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
- <integer>1</integer>
+ <integer>2</integer>
</dict>
<key>WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
- <integer>2</integer>
+ <integer>0</integer>
</dict>
</dict>
</dict>
</plist>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, May 17, 9:47 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3241280
Attached To
rWFSUI WriteFreely SwiftUI
Event Timeline
Log In to Comment