mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge branch 'feature/fix-mac-deprecation-warnings'
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import WebKit
|
||||
@preconcurrency import WebKit
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import Articles
|
||||
|
||||
@@ -69,7 +69,7 @@ private extension NNW3ImportController {
|
||||
panel.canChooseDirectories = false
|
||||
panel.resolvesAliases = true
|
||||
panel.directoryURL = NNW3ImportController.defaultFileURL
|
||||
panel.allowedFileTypes = ["plist"]
|
||||
panel.allowedContentTypes = [.propertyList]
|
||||
panel.allowsOtherFileTypes = false
|
||||
panel.accessoryView = accessoryViewController.view
|
||||
panel.isAccessoryViewDisclosed = true
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import AppKit
|
||||
import Account
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class ExportOPMLWindowController: NSWindowController {
|
||||
|
||||
@@ -75,7 +76,7 @@ class ExportOPMLWindowController: NSWindowController {
|
||||
func exportOPML(account: Account) {
|
||||
|
||||
let panel = NSSavePanel()
|
||||
panel.allowedFileTypes = ["opml"]
|
||||
panel.allowedContentTypes = [UTType.opml]
|
||||
panel.allowsOtherFileTypes = false
|
||||
panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML")
|
||||
panel.title = NSLocalizedString("Export OPML", comment: "Export OPML")
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import AppKit
|
||||
import Account
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class ImportOPMLWindowController: NSWindowController {
|
||||
|
||||
@@ -85,7 +86,7 @@ class ImportOPMLWindowController: NSWindowController {
|
||||
panel.allowsMultipleSelection = false
|
||||
panel.canChooseDirectories = false
|
||||
panel.resolvesAliases = true
|
||||
panel.allowedFileTypes = ["opml", "xml"]
|
||||
panel.allowedContentTypes = [UTType.opml, UTType.xml]
|
||||
panel.allowsOtherFileTypes = false
|
||||
|
||||
panel.beginSheetModal(for: hostWindow!) { modalResult in
|
||||
|
||||
@@ -84,7 +84,7 @@ struct PasteboardFolder: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Folder: PasteboardWriterOwner {
|
||||
extension Folder: @retroactive PasteboardWriterOwner {
|
||||
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return FolderPasteboardWriter(folder: self)
|
||||
|
||||
@@ -146,7 +146,7 @@ struct PasteboardWebFeed: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
extension WebFeed: PasteboardWriterOwner {
|
||||
extension WebFeed: @retroactive PasteboardWriterOwner {
|
||||
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return WebFeedPasteboardWriter(webFeed: self)
|
||||
|
||||
@@ -10,7 +10,7 @@ import AppKit
|
||||
import Articles
|
||||
import RSCore
|
||||
|
||||
extension Article: PasteboardWriterOwner {
|
||||
extension Article: @retroactive PasteboardWriterOwner {
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return ArticlePasteboardWriter(article: self)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ struct TimelineCellAppearance: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
extension NSEdgeInsets: Equatable {
|
||||
extension NSEdgeInsets: @retroactive Equatable {
|
||||
|
||||
public static func ==(lhs: NSEdgeInsets, rhs: NSEdgeInsets) -> Bool {
|
||||
return lhs.left == rhs.left && lhs.top == rhs.top && lhs.right == rhs.right && lhs.bottom == rhs.bottom
|
||||
|
||||
23
Mac/MainWindow/Timeline/NSSharingService+Extension.h
Normal file
23
Mac/MainWindow/Timeline/NSSharingService+Extension.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// NSObject+NSSharingService_RSCore.h
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 11/3/24.
|
||||
//
|
||||
|
||||
@import AppKit;
|
||||
|
||||
@interface NSSharingService (NoDeprecationWarning)
|
||||
|
||||
// The only way to create custom UI — a Share menu, for instance —
|
||||
// is to use the unfortunately deprecated
|
||||
// +[NSSharingService sharingServicesForItems:].
|
||||
// This cover method allows us to not generate a warning.
|
||||
//
|
||||
// We know it’s deprecated, and we don’t want to be bugged
|
||||
// about it every time we build. (If anyone from Apple
|
||||
// is reading this — a replacement would be very welcome!)
|
||||
|
||||
+ (NSArray *)sharingServicesForItems_noDeprecationWarning:(NSArray *)items;
|
||||
|
||||
@end
|
||||
22
Mac/MainWindow/Timeline/NSSharingService+Extension.m
Normal file
22
Mac/MainWindow/Timeline/NSSharingService+Extension.m
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// NSSharingService+Extension.m
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 11/3/24.
|
||||
//
|
||||
|
||||
#import "NSSharingService+Extension.h"
|
||||
|
||||
@implementation NSSharingService (NoDeprecationWarning)
|
||||
|
||||
+ (NSArray *)sharingServicesForItems_noDeprecationWarning:(NSArray *)items {
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
return [NSSharingService sharingServicesForItems:items];
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -200,7 +200,7 @@ private extension TimelineViewController {
|
||||
|
||||
let sortedArticles = articles.sortedByDate(.orderedAscending)
|
||||
let items = sortedArticles.map { ArticlePasteboardWriter(article: $0) }
|
||||
let standardServices = NSSharingService.sharingServices(forItems: items)
|
||||
let standardServices = NSSharingService.sharingServices(forItems_noDeprecationWarning: items) as? [NSSharingService] ?? [NSSharingService]()
|
||||
let customServices = SharingServicePickerDelegate.customSharingServices(for: items)
|
||||
let services = standardServices + customServices
|
||||
if services.isEmpty {
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
//
|
||||
|
||||
#import "WKPreferencesPrivate.h"
|
||||
#import "NSSharingService+Extension.h"
|
||||
|
||||
@@ -222,7 +222,7 @@ struct AddAccountsView: View {
|
||||
.padding(.top, 8)
|
||||
|
||||
HStack {
|
||||
ForEach(0..<chunkedWebAccounts().count, content: { chunk in
|
||||
ForEach(0..<chunkedWebAccounts().count, id: \.self, content: { chunk in
|
||||
VStack {
|
||||
Picker(selection: $selectedAccount, label: Text(""), content: {
|
||||
ForEach(chunkedWebAccounts()[chunk], id: \.self, content: { account in
|
||||
|
||||
@@ -10,6 +10,7 @@ import AppKit
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import UserNotifications
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
final class GeneralPreferencesViewController: NSViewController {
|
||||
|
||||
@@ -125,7 +126,7 @@ private extension GeneralPreferencesViewController {
|
||||
let item = NSMenuItem(title: name, action: nil, keyEquivalent: "")
|
||||
item.representedObject = browser.bundleIdentifier
|
||||
|
||||
let icon = browser.icon ?? NSWorkspace.shared.icon(forFileType: kUTTypeApplicationBundle as String)
|
||||
let icon = browser.icon ?? NSWorkspace.shared.icon(for: UTType.applicationBundle)
|
||||
icon.size = NSSize(width: 16.0, height: 16.0)
|
||||
item.image = browser.icon
|
||||
menu.addItem(item)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -10,7 +10,7 @@ import AppKit
|
||||
|
||||
//let keypadEnter: unichar = 3
|
||||
|
||||
@objc public protocol KeyboardDelegate: class {
|
||||
@objc public protocol KeyboardDelegate: AnyObject {
|
||||
|
||||
// Return true if handled.
|
||||
func keydown(_: NSEvent, in view: NSView) -> Bool
|
||||
|
||||
@@ -128,8 +128,11 @@ public class RSAppMovementMonitor: NSObject {
|
||||
func relaunchFromURL(_ appURL: URL) {
|
||||
// Relaunching is best achieved by requesting that the system launch the app
|
||||
// at the given URL with the "new instance" option to prevent it simply reactivating us.
|
||||
let _ = try? NSWorkspace.shared.launchApplication(at: appURL, options: .newInstance, configuration: [:])
|
||||
NSApp.terminate(self)
|
||||
let configuration = NSWorkspace.OpenConfiguration()
|
||||
configuration.createsNewApplicationInstance = true
|
||||
NSWorkspace.shared.openApplication(at: appURL, configuration: configuration) { _, _ in
|
||||
NSApp.terminate(self)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultHandler() {
|
||||
|
||||
@@ -63,7 +63,7 @@ public final class UserApp {
|
||||
path = bundleURL.path
|
||||
}
|
||||
else {
|
||||
path = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleID)
|
||||
path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID)?.path
|
||||
}
|
||||
if icon == nil, let path = path {
|
||||
icon = NSWorkspace.shared.icon(forFile: path)
|
||||
@@ -71,7 +71,7 @@ public final class UserApp {
|
||||
return
|
||||
}
|
||||
|
||||
path = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleID)
|
||||
path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID)?.path
|
||||
if let path = path {
|
||||
if icon == nil {
|
||||
icon = NSWorkspace.shared.icon(forFile: path)
|
||||
@@ -84,7 +84,7 @@ public final class UserApp {
|
||||
}
|
||||
}
|
||||
|
||||
public func launchIfNeeded() -> Bool {
|
||||
public func launchIfNeeded() async -> Bool {
|
||||
|
||||
// Return true if already running.
|
||||
// Return true if not running and successfully gets launched.
|
||||
@@ -99,20 +99,29 @@ public final class UserApp {
|
||||
}
|
||||
|
||||
let url = URL(fileURLWithPath: path)
|
||||
if let app = try? NSWorkspace.shared.launchApplication(at: url, options: [.withErrorPresentation], configuration: [:]) {
|
||||
runningApplication = app
|
||||
if app.isFinishedLaunching {
|
||||
return true
|
||||
}
|
||||
Thread.sleep(forTimeInterval: 1.0) // Give the app time to launch. This is ugly.
|
||||
if app.isFinishedLaunching {
|
||||
return true
|
||||
}
|
||||
Thread.sleep(forTimeInterval: 1.0) // Give it some *more* time.
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
do {
|
||||
let configuration = NSWorkspace.OpenConfiguration()
|
||||
configuration.promptsUserIfNeeded = true
|
||||
|
||||
let app = try await NSWorkspace.shared.openApplication(at: url, configuration: configuration)
|
||||
runningApplication = app
|
||||
|
||||
if app.isFinishedLaunching {
|
||||
return true
|
||||
}
|
||||
|
||||
try? await Task.sleep(for: .seconds(1)) // Give the app time to launch. This is ugly.
|
||||
if app.isFinishedLaunching {
|
||||
return true
|
||||
}
|
||||
|
||||
try? await Task.sleep(for: .seconds(1)) // Give it some *more* time.
|
||||
return true
|
||||
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func bringToFront() -> Bool {
|
||||
|
||||
@@ -26,13 +26,13 @@ public enum CloudKitZoneError: LocalizedError {
|
||||
}
|
||||
}
|
||||
|
||||
public protocol CloudKitZoneDelegate: class {
|
||||
public protocol CloudKitZoneDelegate: AnyObject {
|
||||
func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void);
|
||||
}
|
||||
|
||||
public typealias CloudKitRecordKey = (recordType: CKRecord.RecordType, recordID: CKRecord.ID)
|
||||
|
||||
public protocol CloudKitZone: class {
|
||||
public protocol CloudKitZone: AnyObject {
|
||||
|
||||
static var qualityOfService: QualityOfService { get }
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import Foundation
|
||||
/// When it’s canceled, it should do its best to stop
|
||||
/// doing whatever it’s doing. However, it should not
|
||||
/// leave data in an inconsistent state.
|
||||
public protocol MainThreadOperation: class {
|
||||
public protocol MainThreadOperation: AnyObject {
|
||||
|
||||
// These three properties are set by MainThreadOperationQueue. Don’t set them.
|
||||
var isCanceled: Bool { get set } // Check this at appropriate times in case the operation has been canceled.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol MainThreadOperationDelegate: class {
|
||||
public protocol MainThreadOperationDelegate: AnyObject {
|
||||
func operationDidComplete(_ operation: MainThreadOperation)
|
||||
func cancelOperation(_ operation: MainThreadOperation)
|
||||
func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol UndoableCommand: class {
|
||||
public protocol UndoableCommand: AnyObject {
|
||||
|
||||
var undoActionName: String { get }
|
||||
var redoActionName: String { get }
|
||||
@@ -39,7 +39,7 @@ extension UndoableCommand {
|
||||
|
||||
// Useful for view controllers.
|
||||
|
||||
public protocol UndoableCommandRunner: class {
|
||||
public protocol UndoableCommandRunner: AnyObject {
|
||||
|
||||
var undoableCommands: [UndoableCommand] { get set }
|
||||
var undoManager: UndoManager? { get }
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol TreeControllerDelegate: class {
|
||||
|
||||
public protocol TreeControllerDelegate: AnyObject {
|
||||
|
||||
func treeController(treeController: TreeController, childNodesFor: Node) -> [Node]?
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ let package = Package(
|
||||
dependencies: [
|
||||
"RSParser",
|
||||
"RSCore"
|
||||
]
|
||||
//swiftSettings: [.unsafeFlags(["-warnings-as-errors"])]
|
||||
],
|
||||
swiftSettings: [.unsafeFlags(["-warnings-as-errors"])]
|
||||
),
|
||||
.testTarget(
|
||||
name: "RSWebTests",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
public class MacWebBrowser {
|
||||
|
||||
@@ -18,34 +19,28 @@ public class MacWebBrowser {
|
||||
return false
|
||||
}
|
||||
|
||||
if (inBackground) {
|
||||
do {
|
||||
try NSWorkspace.shared.open(preparedURL, options: [.withoutActivation], configuration: [:])
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
if inBackground {
|
||||
|
||||
let configuration = NSWorkspace.OpenConfiguration()
|
||||
configuration.activates = false
|
||||
NSWorkspace.shared.open(url, configuration: configuration, completionHandler: nil)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
return NSWorkspace.shared.open(preparedURL)
|
||||
}
|
||||
|
||||
/// Returns an array of the browsers installed on the system, sorted by name.
|
||||
///
|
||||
/// "Browsers" are applications that can both handle `https` URLs, and display HTML documents.
|
||||
public class func sortedBrowsers() -> [MacWebBrowser] {
|
||||
guard let httpsIDs = LSCopyAllHandlersForURLScheme("https" as CFString)?.takeRetainedValue() as? [String] else {
|
||||
return []
|
||||
}
|
||||
public static func sortedBrowsers() -> [MacWebBrowser] {
|
||||
|
||||
guard let htmlIDs = LSCopyAllRoleHandlersForContentType(kUTTypeHTML, .viewer)?.takeRetainedValue() as? [String] else {
|
||||
return []
|
||||
}
|
||||
let httpsAppURLs = NSWorkspace.shared.urlsForApplications(toOpen: URL(string: "https://apple.com/")!)
|
||||
let htmlAppURLs = NSWorkspace.shared.urlsForApplications(toOpen: UTType.html)
|
||||
let browserAppURLs = Set(httpsAppURLs).intersection(Set(htmlAppURLs))
|
||||
|
||||
let browserIDs = Set(httpsIDs).intersection(Set(htmlIDs))
|
||||
|
||||
return browserIDs.compactMap { MacWebBrowser(bundleIdentifier: $0) }.sorted {
|
||||
return browserAppURLs.compactMap { MacWebBrowser(url: $0) }.sorted {
|
||||
if let leftName = $0.name, let rightName = $1.name {
|
||||
return leftName < rightName
|
||||
}
|
||||
@@ -127,15 +122,25 @@ public class MacWebBrowser {
|
||||
/// - url: The URL to open.
|
||||
/// - inBackground: If `true`, attempt to load the URL without bringing the browser to the foreground.
|
||||
@discardableResult public func openURL(_ url: URL, inBackground: Bool = false) -> Bool {
|
||||
|
||||
// TODO: make this function async.
|
||||
|
||||
guard let preparedURL = url.preparedForOpeningInBrowser() else {
|
||||
return false
|
||||
}
|
||||
|
||||
let options: NSWorkspace.LaunchOptions = inBackground ? [.withoutActivation] : []
|
||||
Task { @MainActor in
|
||||
|
||||
return NSWorkspace.shared.open([preparedURL], withAppBundleIdentifier: self.bundleIdentifier, options: options, additionalEventParamDescriptor: nil, launchIdentifiers: nil)
|
||||
let configuration = NSWorkspace.OpenConfiguration()
|
||||
if inBackground {
|
||||
configuration.activates = false
|
||||
}
|
||||
|
||||
NSWorkspace.shared.open([preparedURL], withApplicationAt: self.url, configuration: configuration, completionHandler: nil)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MacWebBrowser: CustomDebugStringConvertible {
|
||||
|
||||
@@ -915,7 +915,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1240;
|
||||
LastUpgradeCheck = 1610;
|
||||
LastUpgradeCheck = 1620;
|
||||
ORGANIZATIONNAME = "Ranchero Software";
|
||||
TargetAttributes = {
|
||||
176813F22564BB2C00D98635 = {
|
||||
@@ -959,6 +959,7 @@
|
||||
849C645F1ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
LastSwiftMigration = 1620;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.HardenedRuntime = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -29,11 +29,17 @@ final class SendToMarsEditCommand: SendToCommand {
|
||||
guard let article = (object as? ArticlePasteboardWriter)?.article else {
|
||||
return
|
||||
}
|
||||
guard let app = appToUse(), app.launchIfNeeded(), app.bringToFront() else {
|
||||
guard let app = appToUse() else {
|
||||
return
|
||||
}
|
||||
|
||||
send(article, to: app)
|
||||
Task {
|
||||
guard await app.launchIfNeeded(), app.bringToFront() else {
|
||||
return
|
||||
}
|
||||
|
||||
send(article, to: app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,24 +37,27 @@ final class SendToMicroBlogCommand: SendToCommand {
|
||||
guard let article = (object as? ArticlePasteboardWriter)?.article else {
|
||||
return
|
||||
}
|
||||
guard microBlogApp.launchIfNeeded(), microBlogApp.bringToFront() else {
|
||||
return
|
||||
|
||||
Task {
|
||||
guard await microBlogApp.launchIfNeeded(), microBlogApp.bringToFront() else {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: get text from contentHTML or contentText if no title and no selectedText.
|
||||
// TODO: consider selectedText.
|
||||
|
||||
let s = article.attributionString + article.linkString
|
||||
|
||||
let urlQueryDictionary = ["text": s]
|
||||
guard let urlQueryString = urlQueryDictionary.urlQueryString else {
|
||||
return
|
||||
}
|
||||
guard let url = URL(string: "microblog://post?" + urlQueryString) else {
|
||||
return
|
||||
}
|
||||
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
|
||||
// TODO: get text from contentHTML or contentText if no title and no selectedText.
|
||||
// TODO: consider selectedText.
|
||||
|
||||
let s = article.attributionString + article.linkString
|
||||
|
||||
let urlQueryDictionary = ["text": s]
|
||||
guard let urlQueryString = urlQueryDictionary.urlQueryString else {
|
||||
return
|
||||
}
|
||||
guard let url = URL(string: "microblog://post?" + urlQueryString) else {
|
||||
return
|
||||
}
|
||||
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user