mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-26 12:08:27 +00:00
fix: resolve duplicate resolution shortcuts, update freeze, and process id flashing
- Fix keyboard shortcut collision causing main window to intercept Enter/Escape keys when duplicate resolution sheet is open - Fix UI freeze when checking release notes for an update by parsing HTML asynchronously - Improve Sparkle changelog formatting by converting HTML tags to clean Markdown - Change internal Process ID status message to Starting... when queueing downloads - Fix EXC_BREAKPOINT crash on app launch by prioritizing Bundle.main over Bundle.module - Update CHANGELOG.md for 0.6.2 release
This commit is contained in:
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.6.2] - 2026-06-08
|
||||
|
||||
### Fixes
|
||||
- Fix keyboard shortcut collision that caused the main window to intercept Enter/Escape keys when the duplicate resolution sheet was open.
|
||||
- Fix UI freeze when checking release notes for an update by parsing HTML asynchronously on a background thread.
|
||||
- Improve Sparkle changelog formatting by converting HTML tags to clean Markdown instead of stripping them into an unreadable block of text.
|
||||
- Change the internal `Process xxxxx` status message to a cleaner `Starting...` message when queueing a new download.
|
||||
- Fix `EXC_BREAKPOINT` crash on app launch in production builds by prioritizing `Bundle.main` over `Bundle.module` when accessing resources.
|
||||
|
||||
## [0.6.1] - 2026-06-08
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -312,7 +312,7 @@ struct AddDownloadsView: View {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
.keyboardShortcut(.cancelAction)
|
||||
.keyboardShortcut(showingDuplicates ? nil : .cancelAction)
|
||||
|
||||
Button("Add to Queue") {
|
||||
addDownloads(start: false)
|
||||
@@ -324,7 +324,7 @@ struct AddDownloadsView: View {
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(!canAddDownloads)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.keyboardShortcut(showingDuplicates ? nil : .defaultAction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -602,7 +602,7 @@ final class DownloadController: ObservableObject {
|
||||
update(item.id) {
|
||||
$0.rpcPort = handle.rpcPort
|
||||
$0.rpcSecret = handle.rpcSecret
|
||||
$0.message = "Process \(handle.processIdentifier)"
|
||||
$0.message = "Starting..."
|
||||
}
|
||||
saveDownloads()
|
||||
applySpeedLimitsToActiveDownloads()
|
||||
|
||||
@@ -31,11 +31,19 @@ final class MediaEngineManager: ObservableObject {
|
||||
}
|
||||
|
||||
func binaryPath(for addon: AddonType) -> URL? {
|
||||
for bundle in [Bundle.main, Bundle.module] {
|
||||
if let bundled = bundle.url(forResource: addon.binaryName, withExtension: nil),
|
||||
if let bundled = Bundle.main.url(forResource: addon.binaryName, withExtension: nil),
|
||||
FileManager.default.isExecutableFile(atPath: bundled.path) {
|
||||
return bundled
|
||||
}
|
||||
|
||||
// Prevent fatalError crash: avoid accessing Bundle.module if running in a packaged app.
|
||||
if Bundle.main.bundleURL.pathExtension.lowercased() != "app" {
|
||||
#if SWIFT_PACKAGE
|
||||
if let bundled = Bundle.module.url(forResource: addon.binaryName, withExtension: nil),
|
||||
FileManager.default.isExecutableFile(atPath: bundled.path) {
|
||||
return bundled
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -32,19 +32,53 @@ class InlineUpdateUserDriver: NSObject, SPUUserDriver {
|
||||
}
|
||||
|
||||
func showUpdateReleaseNotes(with downloadData: SPUDownloadData) {
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
if let htmlString = String(data: downloadData.data, encoding: .utf8) {
|
||||
self.updater?.releaseNotes = self.stripHTML(htmlString)
|
||||
let parsedText = self.fastHTMLToMarkdown(htmlString)
|
||||
DispatchQueue.main.async {
|
||||
self.updater?.releaseNotes = parsedText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stripHTML(_ string: String) -> String {
|
||||
guard let data = string.data(using: .utf8) else { return string }
|
||||
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) {
|
||||
return attributedString.string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
nonisolated private func fastHTMLToMarkdown(_ html: String) -> String {
|
||||
var text = html
|
||||
text = text.replacingOccurrences(of: "<br>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<br/>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<br />", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</p>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<li>", with: "- ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</li>", with: "\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h1>", with: "# ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h1>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h2>", with: "## ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h2>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<h3>", with: "### ", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</h3>", with: "\n\n", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<b>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</b>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "<strong>", with: "**", options: .caseInsensitive)
|
||||
text = text.replacingOccurrences(of: "</strong>", with: "**", options: .caseInsensitive)
|
||||
|
||||
if let regex = try? NSRegularExpression(pattern: "<[^>]+>", options: .caseInsensitive) {
|
||||
let range = NSRange(location: 0, length: text.utf16.count)
|
||||
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "")
|
||||
}
|
||||
return string
|
||||
|
||||
text = text.replacingOccurrences(of: " ", with: " ")
|
||||
text = text.replacingOccurrences(of: "&", with: "&")
|
||||
text = text.replacingOccurrences(of: "<", with: "<")
|
||||
text = text.replacingOccurrences(of: ">", with: ">")
|
||||
text = text.replacingOccurrences(of: """, with: "\"")
|
||||
text = text.replacingOccurrences(of: "'", with: "'")
|
||||
|
||||
if let regex = try? NSRegularExpression(pattern: "\\n{3,}", options: []) {
|
||||
let range = NSRange(location: 0, length: text.utf16.count)
|
||||
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "\n\n")
|
||||
}
|
||||
|
||||
return text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
func showUpdateReleaseNotesFailedToDownloadWithError(_ error: Error) {
|
||||
|
||||
Reference in New Issue
Block a user