diff --git a/apple/TablePro/Core/Menu/AboutPanelCredits.swift b/apple/TablePro/Core/Menu/AboutPanelCredits.swift deleted file mode 100644 index 8492b87d06..0000000000 --- a/apple/TablePro/Core/Menu/AboutPanelCredits.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// AboutPanelCredits.swift -// TablePro -// - -import AppKit -import os - -/// The About panel's credits: the project links, then the license holders who asked to be thanked -/// by name. -enum AboutPanelCredits { - static func attributedString(supporters: [String] = SupporterList.load()) -> NSAttributedString { - let style: [NSAttributedString.Key: Any] = [ - .font: NSFont.systemFont(ofSize: 11), - .foregroundColor: NSColor.secondaryLabelColor - ] - let credits = NSMutableAttributedString() - let links: [(String, String)] = [ - ("Website", MainMenuLink.website), - ("GitHub", MainMenuLink.repository), - (String(localized: "Documentation"), MainMenuLink.documentation), - (String(localized: "Sponsor"), SupportLinks.sponsors(.aboutPanel).absoluteString) - ] - for (index, link) in links.enumerated() { - if index > 0 { - credits.append(NSAttributedString(string: " | ", attributes: style)) - } - let attributed = NSMutableAttributedString(string: link.0, attributes: style) - if let url = URL(string: link.1) { - attributed.addAttribute(.link, value: url, range: NSRange(location: 0, length: attributed.length)) - } - credits.append(attributed) - } - - if !supporters.isEmpty { - let heading = String(localized: "Thanks to these license holders") - credits.append(NSAttributedString(string: "\n\n\(heading)\n", attributes: style)) - credits.append(NSAttributedString(string: supporters.joined(separator: ", "), attributes: style)) - } - - let centered = NSMutableParagraphStyle() - centered.alignment = .center - credits.addAttribute(.paragraphStyle, value: centered, range: NSRange(location: 0, length: credits.length)) - return credits - } -} - -/// License holders who asked, by email, to be named in the About panel. -/// -/// `Supporters.json` in the app's resources is the only copy of the list. A name goes in, or comes -/// out, through a pull request, and only after its owner said yes, using the spelling they gave. -enum SupporterList { - static let resourceName = "Supporters" - - private struct File: Decodable { - let supporters: [String] - } - - private static let logger = Logger(subsystem: "com.TablePro", category: "SupporterList") - - static func load(from bundle: Bundle = .main) -> [String] { - guard let url = bundle.url(forResource: resourceName, withExtension: "json") else { - logger.error("Supporters.json is missing from the app bundle") - return [] - } - do { - return try decode(Data(contentsOf: url)) - } catch { - logger.error("Could not read Supporters.json: \(error.publicLogShape, privacy: .public)") - return [] - } - } - - static func decode(_ data: Data) throws -> [String] { - try JSONDecoder().decode(File.self, from: data).supporters - .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } - .filter { !$0.isEmpty } - } -} diff --git a/apple/TablePro/Core/Menu/AppDelegate+MainMenuActions.swift b/apple/TablePro/Core/Menu/AppDelegate+MainMenuActions.swift index 7f81715475..352d9e93a3 100644 --- a/apple/TablePro/Core/Menu/AppDelegate+MainMenuActions.swift +++ b/apple/TablePro/Core/Menu/AppDelegate+MainMenuActions.swift @@ -156,3 +156,35 @@ enum MainMenuLink { static let repository = "https://github.com/TableProApp/TablePro" static let issues = "https://github.com/TableProApp/TablePro/issues" } + +/// The About panel's credits line, kept out of the delegate so the menu action stays +/// a single call. +enum AboutPanelCredits { + static func attributedString() -> NSAttributedString { + let style: [NSAttributedString.Key: Any] = [ + .font: NSFont.systemFont(ofSize: 11), + .foregroundColor: NSColor.secondaryLabelColor + ] + let credits = NSMutableAttributedString() + let links: [(String, String)] = [ + ("Website", MainMenuLink.website), + ("GitHub", MainMenuLink.repository), + (String(localized: "Documentation"), MainMenuLink.documentation), + (String(localized: "Sponsor"), SupportLinks.sponsors(.aboutPanel).absoluteString) + ] + for (index, link) in links.enumerated() { + if index > 0 { + credits.append(NSAttributedString(string: " | ", attributes: style)) + } + let attributed = NSMutableAttributedString(string: link.0, attributes: style) + if let url = URL(string: link.1) { + attributed.addAttribute(.link, value: url, range: NSRange(location: 0, length: attributed.length)) + } + credits.append(attributed) + } + let centered = NSMutableParagraphStyle() + centered.alignment = .center + credits.addAttribute(.paragraphStyle, value: centered, range: NSRange(location: 0, length: credits.length)) + return credits + } +} diff --git a/apple/TablePro/Resources/Localizable.xcstrings b/apple/TablePro/Resources/Localizable.xcstrings index 2400d04c20..7712f107eb 100644 --- a/apple/TablePro/Resources/Localizable.xcstrings +++ b/apple/TablePro/Resources/Localizable.xcstrings @@ -182092,46 +182092,6 @@ } } }, - "Thanks to these license holders" : { - "localizations" : { - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Merci à ces détenteurs de licence" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "라이선스를 구매해 주신 분들께 감사드립니다" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bu lisans sahiplerine teşekkürler" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cảm ơn những người đã mua giấy phép" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "感谢这些许可证持有者" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "感謝這些授權持有者" - } - } - } - }, "Thanks. Your license is what pays for the next release." : { "localizations" : { "fr" : { diff --git a/apple/TablePro/Resources/Supporters.json b/apple/TablePro/Resources/Supporters.json deleted file mode 100644 index 75d509d60e..0000000000 --- a/apple/TablePro/Resources/Supporters.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "supporters": [] -} diff --git a/apple/TableProTests/Core/Menu/AboutPanelCreditsTests.swift b/apple/TableProTests/Core/Menu/AboutPanelCreditsTests.swift deleted file mode 100644 index 0dbdad099f..0000000000 --- a/apple/TableProTests/Core/Menu/AboutPanelCreditsTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// AboutPanelCreditsTests.swift -// TableProTests -// - -import Foundation -@testable import TablePro -import Testing - -@MainActor -struct AboutPanelCreditsTests { - @Test("Named license holders follow the project links") - func listsSupporters() throws { - let credits = AboutPanelCredits.attributedString(supporters: ["Ada Lovelace", "Grace Hopper"]).string - - #expect(credits.contains("Thanks to these license holders")) - #expect(credits.contains("Ada Lovelace, Grace Hopper")) - let links = try #require(credits.range(of: "GitHub")) - let names = try #require(credits.range(of: "Ada Lovelace")) - #expect(links.lowerBound < names.lowerBound) - } - - @Test("With nobody listed, the credits stay the project links alone") - func emptyListAddsNothing() { - let credits = AboutPanelCredits.attributedString(supporters: []).string - - #expect(!credits.contains("Thanks to these license holders")) - #expect(!credits.contains("\n")) - } - - @Test("Blank entries and stray spaces in the list never reach the panel") - func decodeTrimsAndDropsBlanks() throws { - let data = Data(#"{"supporters": [" Ada Lovelace ", "", " ", "Grace Hopper"]}"#.utf8) - - #expect(try SupporterList.decode(data) == ["Ada Lovelace", "Grace Hopper"]) - } - - @Test("The list the app ships decodes") - func bundledListDecodes() throws { - let url = try #require(Bundle.main.url(forResource: SupporterList.resourceName, withExtension: "json")) - - _ = try SupporterList.decode(Data(contentsOf: url)) - } -} diff --git a/apple/changelog.d/3923.added.md b/apple/changelog.d/3923.added.md deleted file mode 100644 index 09d17d7b1c..0000000000 --- a/apple/changelog.d/3923.added.md +++ /dev/null @@ -1 +0,0 @@ -The About panel thanks, by name, license holders who asked to be listed.