mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-01 19:46:56 +01:00
Remove Twitter integration. Fixes #3842
This commit is contained in:
parent
06910b1e58
commit
6c1e1ea1c6
@ -1,70 +0,0 @@
|
||||
//
|
||||
// TwitterEntities.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol TwitterEntity {
|
||||
var indices: [Int]? { get }
|
||||
func renderAsHTML() -> String
|
||||
}
|
||||
|
||||
extension TwitterEntity {
|
||||
|
||||
var startIndex: Int {
|
||||
if let indices = indices, indices.count > 0 {
|
||||
return indices[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var endIndex: Int {
|
||||
if let indices = indices, indices.count > 1 {
|
||||
return indices[1]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct TwitterEntities: Codable {
|
||||
|
||||
let hashtags: [TwitterHashtag]?
|
||||
let urls: [TwitterURL]?
|
||||
let userMentions: [TwitterMention]?
|
||||
let symbols: [TwitterSymbol]?
|
||||
let media: [TwitterMedia]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case hashtags = "hashtags"
|
||||
case urls = "urls"
|
||||
case userMentions = "user_mentions"
|
||||
case symbols = "symbols"
|
||||
case media = "media"
|
||||
}
|
||||
|
||||
func combineAndSort() -> [TwitterEntity] {
|
||||
var entities = [TwitterEntity]()
|
||||
if let hashtags = hashtags {
|
||||
entities.append(contentsOf: hashtags)
|
||||
}
|
||||
if let urls = urls {
|
||||
entities.append(contentsOf: urls)
|
||||
}
|
||||
if let userMentions = userMentions {
|
||||
entities.append(contentsOf: userMentions)
|
||||
}
|
||||
if let symbols = symbols {
|
||||
entities.append(contentsOf: symbols)
|
||||
}
|
||||
if let media = media {
|
||||
entities.append(contentsOf: media)
|
||||
}
|
||||
return entities.sorted(by: { $0.startIndex < $1.startIndex })
|
||||
}
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
//
|
||||
// TwitterExtendedEntities.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterExtendedEntities: Codable {
|
||||
|
||||
let medias: [TwitterExtendedMedia]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case medias = "media"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String {
|
||||
var html = String()
|
||||
if let medias = medias {
|
||||
for media in medias {
|
||||
html += media.renderAsHTML()
|
||||
}
|
||||
}
|
||||
return html
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
//
|
||||
// TwitterExtendedMedia.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterExtendedMedia: Codable {
|
||||
|
||||
let idStr: String?
|
||||
let indices: [Int]?
|
||||
let mediaURL: String?
|
||||
let httpsMediaURL: String?
|
||||
let url: String?
|
||||
let displayURL: String?
|
||||
let type: String?
|
||||
let video: TwitterVideo?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case idStr = "idStr"
|
||||
case indices = "indices"
|
||||
case mediaURL = "media_url"
|
||||
case httpsMediaURL = "media_url_https"
|
||||
case url = "url"
|
||||
case displayURL = "display_url"
|
||||
case type = "type"
|
||||
case video = "video_info"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String {
|
||||
var html = String()
|
||||
|
||||
switch type {
|
||||
case "photo":
|
||||
html += renderPhotoAsHTML()
|
||||
case "video":
|
||||
html += renderVideoAsHTML()
|
||||
case "animated_gif":
|
||||
html += renderAnimatedGIFAsHTML()
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension TwitterExtendedMedia {
|
||||
|
||||
func renderPhotoAsHTML() -> String {
|
||||
if let httpsMediaURL = httpsMediaURL {
|
||||
return "<figure><img src=\"\(httpsMediaURL)\"></figure>"
|
||||
}
|
||||
if let mediaURL = mediaURL {
|
||||
return "<figure><img src=\"\(mediaURL)\"></figure>"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func renderVideoAsHTML() -> String {
|
||||
guard let bestVariantURL = findBestVariant()?.url else { return "" }
|
||||
|
||||
var html = "<video "
|
||||
|
||||
if let httpsMediaURL = httpsMediaURL {
|
||||
html += "poster=\"\(httpsMediaURL)\" "
|
||||
} else if let mediaURL = mediaURL {
|
||||
html += "poster=\"\(mediaURL)\" "
|
||||
}
|
||||
|
||||
html += "src=\"\(bestVariantURL)\"></video>"
|
||||
return html
|
||||
}
|
||||
|
||||
func renderAnimatedGIFAsHTML() -> String {
|
||||
guard let bestVariantURL = findBestVariant()?.url else { return "" }
|
||||
|
||||
var html = "<video class=\"nnwAnimatedGIF\" "
|
||||
|
||||
if let httpsMediaURL = httpsMediaURL {
|
||||
html += "poster=\"\(httpsMediaURL)\" "
|
||||
} else if let mediaURL = mediaURL {
|
||||
html += "poster=\"\(mediaURL)\" "
|
||||
}
|
||||
|
||||
html += "src=\"\(bestVariantURL)\" autoplay muted loop></video>"
|
||||
return html
|
||||
}
|
||||
|
||||
func findBestVariant() -> TwitterVideo.Variant? {
|
||||
var best: TwitterVideo.Variant? = nil
|
||||
if let variants = video?.variants {
|
||||
for variant in variants {
|
||||
if let currentBest = best {
|
||||
if variant.bitrate ?? 0 > currentBest.bitrate ?? 0 {
|
||||
best = variant
|
||||
}
|
||||
} else {
|
||||
best = variant
|
||||
}
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
}
|
@ -1,485 +0,0 @@
|
||||
//
|
||||
// TwitterFeedProvider.swift
|
||||
// FeedProvider
|
||||
//
|
||||
// Created by Maurice Parker on 4/7/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Secrets
|
||||
import OAuthSwift
|
||||
import RSParser
|
||||
import RSWeb
|
||||
|
||||
public enum TwitterFeedProviderError: LocalizedError {
|
||||
case rateLimitExceeded
|
||||
case screenNameNotFound
|
||||
case unknown
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .rateLimitExceeded:
|
||||
return NSLocalizedString("Twitter API rate limit has been exceeded. Please wait a short time and try again.", comment: "Rate Limit")
|
||||
case .screenNameNotFound:
|
||||
return NSLocalizedString("Unable to determine screen name.", comment: "Screen name")
|
||||
case .unknown:
|
||||
return NSLocalizedString("An unknown Twitter Feed Provider error has occurred.", comment: "Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum TwitterFeedType: Int {
|
||||
case homeTimeline = 0
|
||||
case mentions = 1
|
||||
case screenName = 2
|
||||
case search = 3
|
||||
}
|
||||
|
||||
public final class TwitterFeedProvider: FeedProvider {
|
||||
|
||||
private static let homeURL = "https://www.twitter.com"
|
||||
private static let iconURL = "https://abs.twimg.com/favicons/twitter.ico"
|
||||
private static let server = "api.twitter.com"
|
||||
private static let apiBase = "https://api.twitter.com/1.1/"
|
||||
private static let userAgentHeaders = UserAgent.headers() as! [String: String]
|
||||
private static let dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
|
||||
|
||||
private static let userPaths = ["/", "/home", "/notifications"]
|
||||
private static let reservedPaths = ["/search", "/explore", "/messages", "/i", "/compose", "/notifications/mentions"]
|
||||
|
||||
private var parsingQueue = DispatchQueue(label: "TwitterFeedProvider parse queue")
|
||||
|
||||
public var screenName: String
|
||||
|
||||
private var oauthToken: String
|
||||
private var oauthTokenSecret: String
|
||||
|
||||
private var client: OAuthSwiftClient
|
||||
|
||||
private var rateLimitRemaining: Int?
|
||||
private var rateLimitReset: Date?
|
||||
|
||||
public init?(tokenSuccess: OAuthSwift.TokenSuccess) {
|
||||
guard let screenName = tokenSuccess.parameters["screen_name"] as? String else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.screenName = screenName
|
||||
self.oauthToken = tokenSuccess.credential.oauthToken
|
||||
self.oauthTokenSecret = tokenSuccess.credential.oauthTokenSecret
|
||||
|
||||
let tokenCredentials = Credentials(type: .oauthAccessToken, username: screenName, secret: oauthToken)
|
||||
try? CredentialsManager.storeCredentials(tokenCredentials, server: Self.server)
|
||||
|
||||
let tokenSecretCredentials = Credentials(type: .oauthAccessTokenSecret, username: screenName, secret: oauthTokenSecret)
|
||||
try? CredentialsManager.storeCredentials(tokenSecretCredentials, server: Self.server)
|
||||
|
||||
client = OAuthSwiftClient(consumerKey: SecretsManager.provider.twitterConsumerKey,
|
||||
consumerSecret: SecretsManager.provider.twitterConsumerSecret,
|
||||
oauthToken: oauthToken,
|
||||
oauthTokenSecret: oauthTokenSecret,
|
||||
version: .oauth1)
|
||||
}
|
||||
|
||||
public init?(screenName: String) {
|
||||
self.screenName = screenName
|
||||
|
||||
guard let tokenCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessToken, server: Self.server, username: screenName),
|
||||
let tokenSecretCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessTokenSecret, server: Self.server, username: screenName) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.oauthToken = tokenCredentials.secret
|
||||
self.oauthTokenSecret = tokenSecretCredentials.secret
|
||||
|
||||
client = OAuthSwiftClient(consumerKey: SecretsManager.provider.twitterConsumerKey,
|
||||
consumerSecret: SecretsManager.provider.twitterConsumerSecret,
|
||||
oauthToken: oauthToken,
|
||||
oauthTokenSecret: oauthTokenSecret,
|
||||
version: .oauth1)
|
||||
}
|
||||
|
||||
public func ability(_ urlComponents: URLComponents) -> FeedProviderAbility {
|
||||
guard urlComponents.host?.hasSuffix("twitter.com") ?? false else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let username = urlComponents.user {
|
||||
if username == screenName {
|
||||
return .owner
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
return .available
|
||||
}
|
||||
|
||||
public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
if let screenName = deriveScreenName(urlComponents) {
|
||||
retrieveUser(screenName: screenName) { result in
|
||||
switch result {
|
||||
case .success(let user):
|
||||
if let avatarURL = user.avatarURL {
|
||||
completion(.success(avatarURL))
|
||||
} else {
|
||||
completion(.failure(TwitterFeedProviderError.screenNameNotFound))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completion(.success(Self.iconURL))
|
||||
}
|
||||
}
|
||||
|
||||
public func metaData(_ urlComponents: URLComponents, completion: @escaping (Result<FeedProviderFeedMetaData, Error>) -> Void) {
|
||||
switch urlComponents.path {
|
||||
|
||||
case "", "/", "/home":
|
||||
let name = NSLocalizedString("Twitter Timeline", comment: "Twitter Timeline")
|
||||
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
|
||||
|
||||
case "/notifications/mentions":
|
||||
let name = NSLocalizedString("Twitter Mentions", comment: "Twitter Mentions")
|
||||
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
|
||||
|
||||
case "/search":
|
||||
if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value {
|
||||
let localized = NSLocalizedString("Twitter Search: %@", comment: "Twitter Search")
|
||||
let name = NSString.localizedStringWithFormat(localized as NSString, query) as String
|
||||
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
|
||||
} else {
|
||||
let name = NSLocalizedString("Twitter Search", comment: "Twitter Search")
|
||||
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
|
||||
}
|
||||
|
||||
default:
|
||||
if let hashtag = deriveHashtag(urlComponents) {
|
||||
completion(.success(FeedProviderFeedMetaData(name: "#\(hashtag)", homePageURL: Self.homeURL)))
|
||||
} else if let listID = deriveListID(urlComponents) {
|
||||
retrieveList(listID: listID) { result in
|
||||
switch result {
|
||||
case .success(let list):
|
||||
if let userName = list.name {
|
||||
var urlComponents = URLComponents(string: Self.homeURL)
|
||||
urlComponents?.path = "/i/lists/\(listID)"
|
||||
completion(.success(FeedProviderFeedMetaData(name: userName, homePageURL: urlComponents?.url?.absoluteString)))
|
||||
} else {
|
||||
completion(.failure(TwitterFeedProviderError.screenNameNotFound))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
} else if let screenName = deriveScreenName(urlComponents) {
|
||||
retrieveUser(screenName: screenName) { result in
|
||||
switch result {
|
||||
case .success(let user):
|
||||
if let userName = user.name {
|
||||
var urlComponents = URLComponents(string: Self.homeURL)
|
||||
urlComponents?.path = "/\(screenName)"
|
||||
completion(.success(FeedProviderFeedMetaData(name: userName, homePageURL: urlComponents?.url?.absoluteString)))
|
||||
} else {
|
||||
completion(.failure(TwitterFeedProviderError.screenNameNotFound))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completion(.failure(TwitterFeedProviderError.unknown))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public func refresh(_ webFeed: WebFeed, completion: @escaping (Result<Set<ParsedItem>, Error>) -> Void) {
|
||||
guard let urlComponents = URLComponents(string: webFeed.url) else {
|
||||
completion(.failure(TwitterFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
let api: String
|
||||
var parameters = [String: Any]()
|
||||
var isSearch = false
|
||||
|
||||
parameters["count"] = 20
|
||||
|
||||
switch urlComponents.path {
|
||||
case "", "/", "/home":
|
||||
parameters["count"] = 100
|
||||
if let sinceToken = webFeed.sinceToken, let sinceID = Int(sinceToken) {
|
||||
parameters["since_id"] = sinceID
|
||||
}
|
||||
api = "statuses/home_timeline.json"
|
||||
case "/notifications/mentions":
|
||||
api = "statuses/mentions_timeline.json"
|
||||
case "/search":
|
||||
api = "search/tweets.json"
|
||||
if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value {
|
||||
parameters["q"] = query
|
||||
}
|
||||
isSearch = true
|
||||
default:
|
||||
if let hashtag = deriveHashtag(urlComponents) {
|
||||
api = "search/tweets.json"
|
||||
parameters["q"] = "#\(hashtag)"
|
||||
isSearch = true
|
||||
} else if let listID = deriveListID(urlComponents) {
|
||||
api = "lists/statuses.json"
|
||||
parameters["list_id"] = listID
|
||||
} else if let screenName = deriveScreenName(urlComponents) {
|
||||
api = "statuses/user_timeline.json"
|
||||
parameters["exclude_replies"] = true
|
||||
parameters["screen_name"] = screenName
|
||||
} else {
|
||||
completion(.failure(TwitterFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
retrieveTweets(api: api, parameters: parameters, isSearch: isSearch) { result in
|
||||
switch result {
|
||||
case .success(let statuses):
|
||||
if let sinceID = statuses.first?.idStr {
|
||||
webFeed.sinceToken = sinceID
|
||||
}
|
||||
self.parsingQueue.async {
|
||||
let parsedItems = self.makeParsedItems(webFeed.url, statuses)
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(parsedItems))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func buildURL(_ type: TwitterFeedType, username: String?, screenName: String?, searchField: String?) -> URL? {
|
||||
var components = URLComponents()
|
||||
components.scheme = "https"
|
||||
components.host = "twitter.com"
|
||||
|
||||
switch type {
|
||||
case .homeTimeline:
|
||||
guard let username = username else {
|
||||
return nil
|
||||
}
|
||||
components.user = username
|
||||
case .mentions:
|
||||
guard let username = username else {
|
||||
return nil
|
||||
}
|
||||
components.user = username
|
||||
components.path = "/notifications/mentions"
|
||||
case .screenName:
|
||||
guard let screenName = screenName else {
|
||||
return nil
|
||||
}
|
||||
components.path = "/\(screenName)"
|
||||
case .search:
|
||||
guard let searchField = searchField else {
|
||||
return nil
|
||||
}
|
||||
components.path = "/search"
|
||||
components.queryItems = [URLQueryItem(name: "q", value: searchField)]
|
||||
}
|
||||
|
||||
return components.url
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: OAuth1SwiftProvider
|
||||
|
||||
extension TwitterFeedProvider: OAuth1SwiftProvider {
|
||||
|
||||
public static var callbackURL: URL {
|
||||
return URL(string: "netnewswire://")!
|
||||
}
|
||||
|
||||
public static var oauth1Swift: OAuth1Swift {
|
||||
return OAuth1Swift(
|
||||
consumerKey: SecretsManager.provider.twitterConsumerKey,
|
||||
consumerSecret: SecretsManager.provider.twitterConsumerSecret,
|
||||
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
|
||||
authorizeUrl: "https://api.twitter.com/oauth/authorize",
|
||||
accessTokenUrl: "https://api.twitter.com/oauth/access_token"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private extension TwitterFeedProvider {
|
||||
|
||||
func deriveHashtag(_ urlComponents: URLComponents) -> String? {
|
||||
let path = urlComponents.path
|
||||
if path.starts(with: "/hashtag/"), let startIndex = path.index(path.startIndex, offsetBy: 9, limitedBy: path.endIndex), startIndex < path.endIndex {
|
||||
return String(path[startIndex..<path.endIndex])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deriveScreenName(_ urlComponents: URLComponents) -> String? {
|
||||
let path = urlComponents.path
|
||||
guard !Self.reservedPaths.contains(path) else { return nil }
|
||||
|
||||
if path.isEmpty || Self.userPaths.contains(path) {
|
||||
return screenName
|
||||
} else {
|
||||
return String(path.suffix(from: path.index(path.startIndex, offsetBy: 1)))
|
||||
}
|
||||
}
|
||||
|
||||
func deriveListID(_ urlComponents: URLComponents) -> String? {
|
||||
let path = urlComponents.path
|
||||
guard path.starts(with: "/i/lists/") else { return nil }
|
||||
return String(path.suffix(from: path.index(path.startIndex, offsetBy: 9)))
|
||||
}
|
||||
|
||||
func retrieveUser(screenName: String, completion: @escaping (Result<TwitterUser, Error>) -> Void) {
|
||||
let url = "\(Self.apiBase)users/show.json"
|
||||
let parameters = ["screen_name": screenName]
|
||||
|
||||
client.get(url, parameters: parameters, headers: Self.userAgentHeaders) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let user = try decoder.decode(TwitterUser.self, from: response.data)
|
||||
completion(.success(user))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveList(listID: String, completion: @escaping (Result<TwitterList, Error>) -> Void) {
|
||||
let url = "\(Self.apiBase)lists/show.json"
|
||||
let parameters = ["list_id": listID]
|
||||
|
||||
client.get(url, parameters: parameters, headers: Self.userAgentHeaders) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let collection = try decoder.decode(TwitterList.self, from: response.data)
|
||||
completion(.success(collection))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveTweets(api: String, parameters: [String: Any], isSearch: Bool, completion: @escaping (Result<[TwitterStatus], Error>) -> Void) {
|
||||
let url = "\(Self.apiBase)\(api)"
|
||||
var expandedParameters = parameters
|
||||
expandedParameters["tweet_mode"] = "extended"
|
||||
|
||||
if let remaining = rateLimitRemaining, let reset = rateLimitReset, remaining < 1 && reset > Date() {
|
||||
completion(.failure(TwitterFeedProviderError.rateLimitExceeded))
|
||||
return
|
||||
}
|
||||
|
||||
client.get(url, parameters: expandedParameters, headers: Self.userAgentHeaders) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale.init(identifier: "en_US_POSIX")
|
||||
dateFormatter.dateFormat = Self.dateFormat
|
||||
decoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
if let remaining = response.response.value(forHTTPHeaderField: "x-rate-limit-remaining") {
|
||||
self.rateLimitRemaining = Int(remaining) ?? 0
|
||||
}
|
||||
if let reset = response.response.value(forHTTPHeaderField: "x-rate-limit-reset") {
|
||||
self.rateLimitReset = Date(timeIntervalSince1970: Double(reset) ?? 0)
|
||||
}
|
||||
|
||||
self.parsingQueue.async {
|
||||
do {
|
||||
let tweets: [TwitterStatus]
|
||||
if isSearch {
|
||||
let searchResult = try decoder.decode(TwitterSearchResult.self, from: response.data)
|
||||
if let statuses = searchResult.statuses {
|
||||
tweets = statuses
|
||||
} else {
|
||||
tweets = [TwitterStatus]()
|
||||
}
|
||||
} else {
|
||||
tweets = try decoder.decode([TwitterStatus].self, from: response.data)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(tweets))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
if error.errorCode == -11 {
|
||||
// Eat these errors. They are old or invalid URL requests.
|
||||
completion(.success([TwitterStatus]()))
|
||||
} else {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeParsedItems(_ webFeedURL: String, _ statuses: [TwitterStatus]) -> Set<ParsedItem> {
|
||||
var parsedItems = Set<ParsedItem>()
|
||||
|
||||
for status in statuses {
|
||||
guard let idStr = status.idStr, let statusURL = status.url else { continue }
|
||||
|
||||
let parsedItem = ParsedItem(syncServiceID: nil,
|
||||
uniqueID: idStr,
|
||||
feedURL: webFeedURL,
|
||||
url: statusURL,
|
||||
externalURL: nil,
|
||||
title: nil,
|
||||
language: nil,
|
||||
contentHTML: status.renderAsHTML(),
|
||||
contentText: status.renderAsText(),
|
||||
summary: nil,
|
||||
imageURL: nil,
|
||||
bannerImageURL: nil,
|
||||
datePublished: status.createdAt,
|
||||
dateModified: nil,
|
||||
authors: makeParsedAuthors(status.user),
|
||||
tags: nil,
|
||||
attachments: nil)
|
||||
parsedItems.insert(parsedItem)
|
||||
}
|
||||
|
||||
return parsedItems
|
||||
}
|
||||
|
||||
func makeUserURL(_ screenName: String) -> String {
|
||||
return "https://twitter.com/\(screenName)"
|
||||
}
|
||||
|
||||
func makeParsedAuthors(_ user: TwitterUser?) -> Set<ParsedAuthor>? {
|
||||
guard let user = user else { return nil }
|
||||
return Set([ParsedAuthor(name: user.name, url: user.url, avatarURL: user.avatarURL, emailAddress: nil)])
|
||||
}
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
//
|
||||
// TwitterHashtag.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterHashtag: Codable, TwitterEntity {
|
||||
|
||||
let text: String?
|
||||
let indices: [Int]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case text = "text"
|
||||
case indices = "indices"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String {
|
||||
var html = String()
|
||||
if let text = text {
|
||||
html += "<a href=\"https://twitter.com/search?q=%23\(text)\">#\(text)</a>"
|
||||
}
|
||||
return html
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
//
|
||||
// TwitterList.swift
|
||||
//
|
||||
//
|
||||
// Created by Maurice Parker on 8/14/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterList: Codable {
|
||||
|
||||
let name: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name = "name"
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// TwitterMedia.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/20/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterMedia: Codable, TwitterEntity {
|
||||
|
||||
let indices: [Int]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case indices = "indices"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String {
|
||||
return String()
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// TwitterMention.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterMention: Codable, TwitterEntity {
|
||||
|
||||
let name: String?
|
||||
let indices: [Int]?
|
||||
let screenName: String?
|
||||
let idStr: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name = "url"
|
||||
case indices = "indices"
|
||||
case screenName = "screen_name"
|
||||
case idStr = "idStr"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String {
|
||||
var html = String()
|
||||
if let screenName = screenName {
|
||||
html += "<a href=\"https://twitter.com/\(screenName)\">@\(screenName)</a>"
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
//
|
||||
// TwitterSearchResult.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterSearchResult: Codable {
|
||||
|
||||
let statuses: [TwitterStatus]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case statuses = "statuses"
|
||||
}
|
||||
}
|
||||
|
@ -1,169 +0,0 @@
|
||||
//
|
||||
// TwitterStatus.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/16/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class TwitterStatus: Codable {
|
||||
|
||||
let createdAt: Date?
|
||||
let idStr: String?
|
||||
let fullText: String?
|
||||
let displayTextRange: [Int]?
|
||||
let user: TwitterUser?
|
||||
let truncated: Bool?
|
||||
let retweeted: Bool?
|
||||
let retweetedStatus: TwitterStatus?
|
||||
let quotedStatus: TwitterStatus?
|
||||
let entities: TwitterEntities?
|
||||
let extendedEntities: TwitterExtendedEntities?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case createdAt = "created_at"
|
||||
case idStr = "id_str"
|
||||
case fullText = "full_text"
|
||||
case displayTextRange = "display_text_range"
|
||||
case user = "user"
|
||||
case truncated = "truncated"
|
||||
case retweeted = "retweeted"
|
||||
case retweetedStatus = "retweeted_status"
|
||||
case quotedStatus = "quoted_status"
|
||||
case entities = "entities"
|
||||
case extendedEntities = "extended_entities"
|
||||
}
|
||||
|
||||
var url: String? {
|
||||
guard let userURL = user?.url, let idStr = idStr else { return nil }
|
||||
return "\(userURL)/status/\(idStr)"
|
||||
}
|
||||
|
||||
func renderAsText() -> String? {
|
||||
let statusToRender = retweetedStatus != nil ? retweetedStatus! : self
|
||||
return statusToRender.displayText
|
||||
}
|
||||
|
||||
func renderAsHTML(topLevel: Bool = true) -> String {
|
||||
if let retweetedStatus = retweetedStatus {
|
||||
return renderAsRetweetHTML(retweetedStatus)
|
||||
}
|
||||
if let quotedStatus = quotedStatus {
|
||||
return renderAsQuoteHTML(quotedStatus, topLevel: topLevel)
|
||||
}
|
||||
return renderAsOriginalHTML(topLevel: topLevel)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension TwitterStatus {
|
||||
|
||||
var displayText: String? {
|
||||
if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1,
|
||||
let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex),
|
||||
let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) {
|
||||
return String(text[startIndex..<endIndex])
|
||||
} else {
|
||||
return fullText
|
||||
}
|
||||
}
|
||||
|
||||
var displayHTML: String? {
|
||||
if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, let entities = entities?.combineAndSort() {
|
||||
|
||||
let displayStartIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex) ?? text.startIndex
|
||||
let displayEndIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) ?? text.endIndex
|
||||
|
||||
var html = String()
|
||||
var prevIndex = displayStartIndex
|
||||
var unicodeScalarOffset = 0
|
||||
|
||||
for entity in entities {
|
||||
|
||||
// The twitter indices are messed up by characters with more than one scalar, we are going to adjust for that here.
|
||||
let endIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) ?? text.endIndex
|
||||
if prevIndex < endIndex {
|
||||
let characters = String(text[prevIndex..<endIndex])
|
||||
for character in characters {
|
||||
unicodeScalarOffset += character.unicodeScalars.count - 1
|
||||
}
|
||||
}
|
||||
|
||||
let offsetStartIndex = unicodeScalarOffset < entity.startIndex ? entity.startIndex - unicodeScalarOffset : entity.startIndex
|
||||
let offsetEndIndex = unicodeScalarOffset < entity.endIndex ? entity.endIndex - unicodeScalarOffset : entity.endIndex
|
||||
|
||||
let entityStartIndex = text.index(text.startIndex, offsetBy: offsetStartIndex, limitedBy: text.endIndex) ?? text.startIndex
|
||||
let entityEndIndex = text.index(text.startIndex, offsetBy: offsetEndIndex, limitedBy: text.endIndex) ?? text.endIndex
|
||||
|
||||
if prevIndex < entityStartIndex {
|
||||
html += String(text[prevIndex..<entityStartIndex]).replacingOccurrences(of: "\n", with: "<br>")
|
||||
}
|
||||
|
||||
// We drop off any URL which is just pointing to the quoted status. It is redundant.
|
||||
if let twitterURL = entity as? TwitterURL, let expandedURL = twitterURL.expandedURL, let quotedURL = quotedStatus?.url {
|
||||
if expandedURL.caseInsensitiveCompare(quotedURL) != .orderedSame {
|
||||
html += entity.renderAsHTML()
|
||||
}
|
||||
} else {
|
||||
html += entity.renderAsHTML()
|
||||
}
|
||||
|
||||
prevIndex = entityEndIndex
|
||||
|
||||
}
|
||||
|
||||
if prevIndex < displayEndIndex {
|
||||
html += String(text[prevIndex..<displayEndIndex]).replacingOccurrences(of: "\n", with: "<br>")
|
||||
}
|
||||
|
||||
return html
|
||||
} else {
|
||||
return displayText
|
||||
}
|
||||
}
|
||||
|
||||
func renderAsTweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String {
|
||||
var html = "<div>\(status.displayHTML ?? "")</div>"
|
||||
|
||||
if !topLevel, let createdAt = status.createdAt, let url = status.url {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .short
|
||||
html += "<a href=\"\(url)\" class=\"twitterTimestamp\">\(dateFormatter.string(from: createdAt))</a>"
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
func renderAsOriginalHTML(topLevel: Bool) -> String {
|
||||
var html = renderAsTweetHTML(self, topLevel: topLevel)
|
||||
html += extendedEntities?.renderAsHTML() ?? ""
|
||||
return html
|
||||
}
|
||||
|
||||
func renderAsRetweetHTML(_ status: TwitterStatus) -> String {
|
||||
var html = "<blockquote>"
|
||||
if let userHTML = status.user?.renderAsHTML() {
|
||||
html += userHTML
|
||||
}
|
||||
html += status.renderAsHTML(topLevel: false)
|
||||
html += "</blockquote>"
|
||||
return html
|
||||
}
|
||||
|
||||
func renderAsQuoteHTML(_ quotedStatus: TwitterStatus, topLevel: Bool) -> String {
|
||||
var html = String()
|
||||
html += renderAsTweetHTML(self, topLevel: topLevel)
|
||||
html += extendedEntities?.renderAsHTML() ?? ""
|
||||
html += "<blockquote>"
|
||||
if let userHTML = quotedStatus.user?.renderAsHTML() {
|
||||
html += userHTML
|
||||
}
|
||||
html += quotedStatus.renderAsHTML(topLevel: false)
|
||||
html += "</blockquote>"
|
||||
return html
|
||||
}
|
||||
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
//
|
||||
// TwitterSymbol.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterSymbol: Codable, TwitterEntity {
|
||||
|
||||
let text: String?
|
||||
let indices: [Int]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case text = "text"
|
||||
case indices = "indices"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String {
|
||||
var html = String()
|
||||
if let text = text {
|
||||
html += "<a href=\"https://twitter.com/search?q=%24\(text)\">$\(text)</a>"
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// TwitterURL.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterURL: Codable, TwitterEntity {
|
||||
|
||||
let url: String?
|
||||
let indices: [Int]?
|
||||
let displayURL: String?
|
||||
let expandedURL: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "url"
|
||||
case indices = "indices"
|
||||
case displayURL = "display_url"
|
||||
case expandedURL = "expanded_url"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String {
|
||||
var html = String()
|
||||
if let expandedURL = expandedURL, let displayURL = displayURL {
|
||||
html += "<a href=\"\(expandedURL)\">\(displayURL)</a>"
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
//
|
||||
// TwitterUser.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/16/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TwitterUser: Codable {
|
||||
|
||||
let name: String?
|
||||
let screenName: String?
|
||||
let avatarURL: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name = "name"
|
||||
case screenName = "screen_name"
|
||||
case avatarURL = "profile_image_url_https"
|
||||
}
|
||||
|
||||
var url: String {
|
||||
return "https://twitter.com/\(screenName ?? "")"
|
||||
}
|
||||
|
||||
func renderAsHTML() -> String? {
|
||||
var html = String()
|
||||
html += "<div><a href=\"\(url)\">"
|
||||
if let avatarURL = avatarURL {
|
||||
html += "<img class=\"twitterAvatar nnw-nozoom\" src=\"\(avatarURL)\">"
|
||||
}
|
||||
html += "<div class=\"twitterUsername\">"
|
||||
if let name = name {
|
||||
html += " \(name)"
|
||||
}
|
||||
html += "<br><span class=\"twitterScreenName\">"
|
||||
if let screenName = screenName {
|
||||
html += " @\(screenName)"
|
||||
}
|
||||
html += "</span></div></a></div>"
|
||||
return html
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
//
|
||||
// TwitterVideoInfo.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 4/18/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
struct TwitterVideo: Codable {
|
||||
|
||||
let variants: [Variant]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case variants = "variants"
|
||||
}
|
||||
|
||||
struct Variant: Codable {
|
||||
|
||||
let bitrate: Int?
|
||||
let contentType: String?
|
||||
let url: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case bitrate = "bitrate"
|
||||
case contentType = "content_type"
|
||||
case url = "url"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -244,17 +244,13 @@ public final class WebFeed: Feed, Renamable, Hashable {
|
||||
// MARK: - NotificationDisplayName
|
||||
public var notificationDisplayName: String {
|
||||
#if os(macOS)
|
||||
if self.url.contains("twitter.com") {
|
||||
return NSLocalizedString("Show notifications for new tweets", comment: "notifyNameDisplay / Twitter")
|
||||
} else if self.url.contains("www.reddit.com") {
|
||||
if self.url.contains("www.reddit.com") {
|
||||
return NSLocalizedString("Show notifications for new posts", comment: "notifyNameDisplay / Reddit")
|
||||
} else {
|
||||
return NSLocalizedString("Show notifications for new articles", comment: "notifyNameDisplay / Default")
|
||||
}
|
||||
#else
|
||||
if self.url.contains("twitter.com") {
|
||||
return NSLocalizedString("Notify about new tweets", comment: "notifyNameDisplay / Twitter")
|
||||
} else if self.url.contains("www.reddit.com") {
|
||||
if self.url.contains("www.reddit.com") {
|
||||
return NSLocalizedString("Notify about new posts", comment: "notifyNameDisplay / Reddit")
|
||||
} else {
|
||||
return NSLocalizedString("Notify about new articles", comment: "notifyNameDisplay / Default")
|
||||
|
@ -14,8 +14,6 @@ struct FeedlyTestSecrets: SecretsProvider {
|
||||
var mercuryClientSecret = ""
|
||||
var feedlyClientId = ""
|
||||
var feedlyClientSecret = ""
|
||||
var twitterConsumerKey = ""
|
||||
var twitterConsumerSecret = ""
|
||||
var redditConsumerKey = ""
|
||||
var inoreaderAppId = ""
|
||||
var inoreaderAppKey = ""
|
||||
|
@ -91,10 +91,6 @@ struct AppAssets {
|
||||
return RSImage(named: "extensionPointReddit")!
|
||||
}()
|
||||
|
||||
static var extensionPointTwitter: RSImage = {
|
||||
return RSImage(named: "extensionPointTwitter")!
|
||||
}()
|
||||
|
||||
static var faviconTemplateImage: RSImage = {
|
||||
return RSImage(named: "faviconTemplateImage")!
|
||||
}()
|
||||
|
@ -478,13 +478,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
return ExtensionPointManager.shared.isRedditEnabled
|
||||
}
|
||||
|
||||
if item.action == #selector(showAddTwitterFeedWindow(_:)) {
|
||||
guard !isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager.shared.isTwitterEnabled else {
|
||||
return false
|
||||
}
|
||||
return ExtensionPointManager.shared.isTwitterEnabled
|
||||
}
|
||||
|
||||
#if !MAC_APP_STORE
|
||||
if item.action == #selector(toggleWebInspectorEnabled(_:)) {
|
||||
(item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off
|
||||
@ -564,12 +557,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
addFeedController?.showAddFeedSheet(.redditFeed)
|
||||
}
|
||||
|
||||
@IBAction func showAddTwitterFeedWindow(_ sender: Any?) {
|
||||
createAndShowMainWindowIfNecessary()
|
||||
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
|
||||
addFeedController?.showAddFeedSheet(.twitterFeed)
|
||||
}
|
||||
|
||||
@IBAction func showAddFolderWindow(_ sender: Any?) {
|
||||
createAndShowMainWindowIfNecessary()
|
||||
showAddFolderSheetOnWindow(mainWindowController!.window!)
|
||||
|
@ -1,200 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17154" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17154"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AddTwitterFeedWindowController" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="accountLabel" destination="Acr-Ig-NVG" id="1gD-BE-CjH"/>
|
||||
<outlet property="accountPopupButton" destination="X1H-Vv-1CJ" id="I0k-bb-XcU"/>
|
||||
<outlet property="addButton" destination="dtI-Hu-rFb" id="D11-zR-dWH"/>
|
||||
<outlet property="folderPopupButton" destination="6vt-DL-mVR" id="98M-xt-ZYU"/>
|
||||
<outlet property="nameTextField" destination="TzV-3k-fXd" id="h4h-5v-4cY"/>
|
||||
<outlet property="screenSearchTextField" destination="cEh-Wt-f5D" id="bnp-Zp-1fe"/>
|
||||
<outlet property="typeDescriptionLabel" destination="f4Z-B8-HHm" id="jZ2-gz-Zr2"/>
|
||||
<outlet property="typePopupButton" destination="j18-w8-wsH" id="KFC-K4-0tG"/>
|
||||
<outlet property="window" destination="QvC-M9-y7g" id="7rH-S2-LF4"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Add Twitter Feed" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="306" height="216"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<view key="contentView" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="306" height="216"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hXq-IS-19x">
|
||||
<rect key="frame" x="119" y="13" width="88" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Dop-HC-6Q9">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
Gw
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="cancel:" target="-2" id="tcT-tt-t99"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dtI-Hu-rFb">
|
||||
<rect key="frame" x="205" y="13" width="88" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Add" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6NK-Ql-drk">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="addFeed:" target="-2" id="Ilv-Un-eDp"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ddC-6D-Tvd">
|
||||
<rect key="frame" x="40" y="177" width="41" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Type:" id="qto-IO-a1j">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="j18-w8-wsH">
|
||||
<rect key="frame" x="85" y="172" width="204" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Home Timeline" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="uE6-1a-w5g" id="bad-PM-uqO">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="Ibj-Uy-KK7">
|
||||
<items>
|
||||
<menuItem title="Home Timeline" state="on" id="uE6-1a-w5g"/>
|
||||
<menuItem title="Mentions" tag="1" id="177-F8-Esj"/>
|
||||
<menuItem title="Screen Name" tag="2" id="DBZ-RV-FfV"/>
|
||||
<menuItem title="Search" tag="3" id="0gG-oY-8yR"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="selectedType:" target="-2" id="eAs-So-odx"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acr-Ig-NVG">
|
||||
<rect key="frame" x="18" y="146" width="63" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Account:" id="LFf-JL-Ahl">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="X1H-Vv-1CJ">
|
||||
<rect key="frame" x="85" y="141" width="204" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="@username" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tfk-aQ-RKg" id="HPE-P1-Hje">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="TmQ-5T-oaz">
|
||||
<items>
|
||||
<menuItem title="@username" state="on" id="Tfk-aQ-RKg"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cEh-Wt-f5D">
|
||||
<rect key="frame" x="87" y="144" width="199" height="21"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="NLJ-ih-hZ8">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-2" id="hNy-Li-bjr"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="f4Z-B8-HHm">
|
||||
<rect key="frame" x="85" y="122" width="203" height="14"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" title="Label" usesSingleLineMode="YES" id="5AA-um-oEb">
|
||||
<font key="font" metaFont="menu" size="11"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sM9-DX-M0c">
|
||||
<rect key="frame" x="35" y="94" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Name:" id="8ca-Qp-BkT">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TzV-3k-fXd" userLabel="Name Text Field">
|
||||
<rect key="frame" x="87" y="91" width="199" height="21"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Optional" drawsBackground="YES" usesSingleLineMode="YES" id="pLP-pL-5R5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-oD-vzR">
|
||||
<rect key="frame" x="31" y="61" width="50" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Folder:" id="Kwx-7B-CIu">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6vt-DL-mVR" userLabel="Folder Popup">
|
||||
<rect key="frame" x="85" y="57" width="204" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tLJ-zY-CcZ" id="0cM-5q-Snl">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="OpL-Uf-woJ">
|
||||
<items>
|
||||
<menuItem title="Item 1" state="on" id="tLJ-zY-CcZ"/>
|
||||
<menuItem title="Item 2" id="APc-af-7Um"/>
|
||||
<menuItem title="Item 3" id="j09-9b-bGs"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dNV-oD-vzR" firstAttribute="baseline" secondItem="6vt-DL-mVR" secondAttribute="baseline" id="14b-jN-4Y6"/>
|
||||
<constraint firstItem="X1H-Vv-1CJ" firstAttribute="firstBaseline" secondItem="Acr-Ig-NVG" secondAttribute="firstBaseline" id="3Cl-Bw-Pcy"/>
|
||||
<constraint firstItem="X1H-Vv-1CJ" firstAttribute="top" secondItem="j18-w8-wsH" secondAttribute="bottom" constant="10" id="48A-2f-2Wq"/>
|
||||
<constraint firstAttribute="bottom" secondItem="dtI-Hu-rFb" secondAttribute="bottom" constant="20" symbolic="YES" id="6ac-2K-RnD"/>
|
||||
<constraint firstItem="cEh-Wt-f5D" firstAttribute="leading" secondItem="j18-w8-wsH" secondAttribute="leading" id="73d-zR-g8z"/>
|
||||
<constraint firstItem="TzV-3k-fXd" firstAttribute="leading" secondItem="cEh-Wt-f5D" secondAttribute="leading" id="Ap9-Ln-amq"/>
|
||||
<constraint firstAttribute="trailing" secondItem="X1H-Vv-1CJ" secondAttribute="trailing" constant="20" id="Boa-Qw-dIK"/>
|
||||
<constraint firstItem="TzV-3k-fXd" firstAttribute="leading" secondItem="sM9-DX-M0c" secondAttribute="trailing" constant="8" id="Ebw-Fa-w9o"/>
|
||||
<constraint firstItem="TzV-3k-fXd" firstAttribute="top" secondItem="f4Z-B8-HHm" secondAttribute="bottom" constant="10" id="Elk-Gm-e4i"/>
|
||||
<constraint firstItem="X1H-Vv-1CJ" firstAttribute="leading" secondItem="Acr-Ig-NVG" secondAttribute="trailing" constant="8" id="HwM-IS-kMa"/>
|
||||
<constraint firstItem="dtI-Hu-rFb" firstAttribute="width" secondItem="hXq-IS-19x" secondAttribute="width" id="J80-aG-OjE"/>
|
||||
<constraint firstItem="sM9-DX-M0c" firstAttribute="baseline" secondItem="TzV-3k-fXd" secondAttribute="baseline" id="K9a-t8-khQ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="f4Z-B8-HHm" secondAttribute="trailing" constant="20" id="POl-uX-qpn"/>
|
||||
<constraint firstItem="f4Z-B8-HHm" firstAttribute="leading" secondItem="j18-w8-wsH" secondAttribute="leading" id="RbK-fc-c6E"/>
|
||||
<constraint firstItem="hXq-IS-19x" firstAttribute="centerY" secondItem="dtI-Hu-rFb" secondAttribute="centerY" id="Sgq-Cy-rII"/>
|
||||
<constraint firstItem="6vt-DL-mVR" firstAttribute="top" secondItem="TzV-3k-fXd" secondAttribute="bottom" constant="10" id="Sjo-Bv-alZ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TzV-3k-fXd" secondAttribute="trailing" constant="20" symbolic="YES" id="V1s-JA-hA8"/>
|
||||
<constraint firstItem="6vt-DL-mVR" firstAttribute="leading" secondItem="dNV-oD-vzR" secondAttribute="trailing" constant="8" id="WNy-vn-p8M"/>
|
||||
<constraint firstItem="f4Z-B8-HHm" firstAttribute="top" secondItem="cEh-Wt-f5D" secondAttribute="bottom" constant="8" id="WiN-GE-aPh"/>
|
||||
<constraint firstAttribute="trailing" secondItem="cEh-Wt-f5D" secondAttribute="trailing" constant="20" id="ZSt-ga-a8N"/>
|
||||
<constraint firstItem="dtI-Hu-rFb" firstAttribute="leading" secondItem="hXq-IS-19x" secondAttribute="trailing" constant="12" symbolic="YES" id="ahD-oU-iFu"/>
|
||||
<constraint firstItem="Acr-Ig-NVG" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="dhv-D0-aPe"/>
|
||||
<constraint firstAttribute="trailing" secondItem="j18-w8-wsH" secondAttribute="trailing" constant="20" id="eQ9-hw-PXg"/>
|
||||
<constraint firstItem="j18-w8-wsH" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" symbolic="YES" id="fK6-IW-NhJ"/>
|
||||
<constraint firstItem="j18-w8-wsH" firstAttribute="leading" secondItem="X1H-Vv-1CJ" secondAttribute="leading" id="gSv-gG-TLd"/>
|
||||
<constraint firstItem="6vt-DL-mVR" firstAttribute="leading" secondItem="TzV-3k-fXd" secondAttribute="leading" id="hMP-wG-fsP"/>
|
||||
<constraint firstItem="cEh-Wt-f5D" firstAttribute="top" secondItem="j18-w8-wsH" secondAttribute="bottom" constant="10" id="hxS-Z9-dWU"/>
|
||||
<constraint firstItem="j18-w8-wsH" firstAttribute="firstBaseline" secondItem="ddC-6D-Tvd" secondAttribute="firstBaseline" id="iz7-4p-NWj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="dtI-Hu-rFb" secondAttribute="trailing" constant="20" symbolic="YES" id="kEo-af-SUe"/>
|
||||
<constraint firstItem="j18-w8-wsH" firstAttribute="leading" secondItem="ddC-6D-Tvd" secondAttribute="trailing" constant="8" id="n9D-4Y-HXk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6vt-DL-mVR" secondAttribute="trailing" constant="20" id="suO-dd-E0b"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="102" y="-768"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19162"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
@ -80,12 +80,6 @@
|
||||
<action selector="showAddRedditFeedWindow:" target="Ady-hI-5gd" id="Irh-Rw-mFK"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="New Twitter Feed…" id="ki4-7l-tM6">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="showAddTwitterFeedWindow:" target="Ady-hI-5gd" id="dZR-aU-O52"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="New Folder…" keyEquivalent="N" id="wkh-LX-Xp1">
|
||||
<connections>
|
||||
<action selector="showAddFolderWindow:" target="Ady-hI-5gd" id="GIi-wc-uYk"/>
|
||||
|
@ -48,9 +48,6 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
||||
case .redditFeed:
|
||||
addFeedWindowController = AddRedditFeedWindowController(folderTreeController: folderTreeController,
|
||||
delegate: self)
|
||||
case .twitterFeed:
|
||||
addFeedWindowController = AddTwitterFeedWindowController(folderTreeController: folderTreeController,
|
||||
delegate: self)
|
||||
}
|
||||
|
||||
addFeedWindowController!.runSheetOnWindow(hostWindow)
|
||||
|
@ -12,7 +12,6 @@ import Account
|
||||
enum AddFeedWindowControllerType {
|
||||
case webFeed
|
||||
case redditFeed
|
||||
case twitterFeed
|
||||
}
|
||||
|
||||
protocol AddFeedWindowControllerDelegate: AnyObject {
|
||||
|
@ -1,194 +0,0 @@
|
||||
//
|
||||
// AddTwitterFeedWindowController.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 4/21/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import RSCore
|
||||
import RSTree
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowController {
|
||||
|
||||
@IBOutlet weak var typePopupButton: NSPopUpButton!
|
||||
@IBOutlet weak var typeDescriptionLabel: NSTextField!
|
||||
|
||||
@IBOutlet weak var accountLabel: NSTextField!
|
||||
@IBOutlet weak var accountPopupButton: NSPopUpButton!
|
||||
@IBOutlet weak var screenSearchTextField: NSTextField!
|
||||
|
||||
@IBOutlet var nameTextField: NSTextField!
|
||||
@IBOutlet var addButton: NSButton!
|
||||
@IBOutlet var folderPopupButton: NSPopUpButton!
|
||||
|
||||
private weak var delegate: AddFeedWindowControllerDelegate?
|
||||
private var folderTreeController: TreeController!
|
||||
|
||||
private var userEnteredScreenSearch: String? {
|
||||
var s = screenSearchTextField.stringValue
|
||||
s = s.collapsingWhitespace
|
||||
if s.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
private var userEnteredTitle: String? {
|
||||
var s = nameTextField.stringValue
|
||||
s = s.collapsingWhitespace
|
||||
if s.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var hostWindow: NSWindow!
|
||||
|
||||
convenience init(folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) {
|
||||
self.init(windowNibName: NSNib.Name("AddTwitterFeedSheet"))
|
||||
self.folderTreeController = folderTreeController
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
func runSheetOnWindow(_ hostWindow: NSWindow) {
|
||||
hostWindow.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in
|
||||
}
|
||||
}
|
||||
|
||||
override func windowDidLoad() {
|
||||
|
||||
let accountMenu = NSMenu()
|
||||
for feedProvider in ExtensionPointManager.shared.activeFeedProviders {
|
||||
if let twitterFeedProvider = feedProvider as? TwitterFeedProvider {
|
||||
let accountMenuItem = NSMenuItem()
|
||||
accountMenuItem.title = "@\(twitterFeedProvider.screenName)"
|
||||
accountMenu.addItem(accountMenuItem)
|
||||
}
|
||||
}
|
||||
accountPopupButton.menu = accountMenu
|
||||
|
||||
folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode, restrictToSpecialAccounts: true)
|
||||
|
||||
if let container = AddWebFeedDefaultContainer.defaultContainer {
|
||||
if let folder = container as? Folder, let account = folder.account {
|
||||
FolderTreeMenu.select(account: account, folder: folder, in: folderPopupButton)
|
||||
} else {
|
||||
if let account = container as? Account {
|
||||
FolderTreeMenu.select(account: account, folder: nil, in: folderPopupButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateUI()
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction func selectedType(_ sender: Any) {
|
||||
screenSearchTextField.stringValue = ""
|
||||
updateUI()
|
||||
}
|
||||
|
||||
@IBAction func cancel(_ sender: Any?) {
|
||||
cancelSheet()
|
||||
}
|
||||
|
||||
@IBAction func addFeed(_ sender: Any?) {
|
||||
guard let type = TwitterFeedType(rawValue: typePopupButton.selectedItem?.tag ?? 0),
|
||||
let atUsername = accountPopupButton.selectedItem?.title else { return }
|
||||
|
||||
let username = String(atUsername[atUsername.index(atUsername.startIndex, offsetBy: 1)..<atUsername.endIndex])
|
||||
|
||||
var screenSearch = userEnteredScreenSearch
|
||||
if let screenName = screenSearch, type == .screenName && screenName.starts(with: "@") {
|
||||
screenSearch = String(screenName[screenName.index(screenName.startIndex, offsetBy: 1)..<screenName.endIndex])
|
||||
}
|
||||
|
||||
guard let url = TwitterFeedProvider.buildURL(type, username: username, screenName: screenSearch, searchField: screenSearch) else { return }
|
||||
|
||||
let container = selectedContainer()!
|
||||
AddWebFeedDefaultContainer.saveDefaultContainer(container)
|
||||
delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: container)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AddTwitterFeedWindowController: NSTextFieldDelegate {
|
||||
|
||||
func controlTextDidChange(_ obj: Notification) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension AddTwitterFeedWindowController {
|
||||
|
||||
private func updateUI() {
|
||||
|
||||
switch typePopupButton.selectedItem?.tag ?? 0 {
|
||||
case 0:
|
||||
|
||||
accountLabel.isHidden = false
|
||||
accountPopupButton.isHidden = false
|
||||
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets from everyone you follow", comment: "Home Timeline")
|
||||
screenSearchTextField.isHidden = true
|
||||
addButton.isEnabled = true
|
||||
|
||||
case 1:
|
||||
|
||||
accountLabel.isHidden = false
|
||||
accountPopupButton.isHidden = false
|
||||
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets mentioning you", comment: "Mentions")
|
||||
screenSearchTextField.isHidden = true
|
||||
addButton.isEnabled = true
|
||||
|
||||
case 2:
|
||||
|
||||
accountLabel.isHidden = true
|
||||
accountPopupButton.isHidden = true
|
||||
|
||||
var screenSearch = userEnteredScreenSearch
|
||||
if screenSearch != nil {
|
||||
if let screenName = screenSearch, screenName.starts(with: "@") {
|
||||
screenSearch = String(screenName[screenName.index(screenName.startIndex, offsetBy: 1)..<screenName.endIndex])
|
||||
}
|
||||
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets from @\(screenSearch!)", comment: "Home Timeline")
|
||||
} else {
|
||||
typeDescriptionLabel.stringValue = ""
|
||||
}
|
||||
|
||||
screenSearchTextField.placeholderString = NSLocalizedString("@name", comment: "@name")
|
||||
screenSearchTextField.isHidden = false
|
||||
addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty
|
||||
|
||||
default:
|
||||
|
||||
accountLabel.isHidden = true
|
||||
accountPopupButton.isHidden = true
|
||||
|
||||
if !screenSearchTextField.stringValue.isEmpty {
|
||||
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets that contain \(screenSearchTextField.stringValue)", comment: "Home Timeline")
|
||||
} else {
|
||||
typeDescriptionLabel.stringValue = ""
|
||||
}
|
||||
|
||||
screenSearchTextField.placeholderString = NSLocalizedString("Search Term or #hashtag", comment: "Search Term")
|
||||
screenSearchTextField.isHidden = false
|
||||
addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func cancelSheet() {
|
||||
delegate?.addFeedWindowControllerUserDidCancel(self)
|
||||
}
|
||||
|
||||
func selectedContainer() -> Container? {
|
||||
return folderPopupButton.selectedItem?.representedObject as? Container
|
||||
}
|
||||
}
|
@ -1446,11 +1446,6 @@ private extension MainWindowController {
|
||||
newRedditFeedItem.action = Selector(("showAddRedditFeedWindow:"))
|
||||
menu.addItem(newRedditFeedItem)
|
||||
|
||||
let newTwitterFeedItem = NSMenuItem()
|
||||
newTwitterFeedItem.title = NSLocalizedString("New Twitter Feed…", comment: "New Twitter Feed")
|
||||
newTwitterFeedItem.action = Selector(("showAddTwitterFeedWindow:"))
|
||||
menu.addItem(newTwitterFeedItem)
|
||||
|
||||
let newFolderFeedItem = NSMenuItem()
|
||||
newFolderFeedItem.title = NSLocalizedString("New Folder…", comment: "New Folder")
|
||||
newFolderFeedItem.action = Selector(("showAddFolderWindow:"))
|
||||
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "twitter24x24.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "twitter48x48.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB |
@ -213,10 +213,6 @@
|
||||
5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; };
|
||||
5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */; };
|
||||
5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */; };
|
||||
514A89A2244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */; };
|
||||
514A89A3244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */; };
|
||||
514A89A5244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; };
|
||||
514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; };
|
||||
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; };
|
||||
514C16CE24D2E63F009A3AFA /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16CD24D2E63F009A3AFA /* Account */; };
|
||||
514C16DE24D2EF15009A3AFA /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16DD24D2EF15009A3AFA /* RSTree */; };
|
||||
@ -225,8 +221,6 @@
|
||||
5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
|
||||
515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; };
|
||||
515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; };
|
||||
515A5107243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; };
|
||||
515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; };
|
||||
515A5148243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; };
|
||||
515A5149243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; };
|
||||
515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */; };
|
||||
@ -237,7 +231,6 @@
|
||||
515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; };
|
||||
515A517B243E90260089E588 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; };
|
||||
515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; };
|
||||
515A5180243E90260089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; };
|
||||
515A5181243E90260089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; };
|
||||
515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; };
|
||||
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
|
||||
@ -1233,11 +1226,8 @@
|
||||
5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = "<group>"; };
|
||||
5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MasterTimelineTitleView.xib; sourceTree = "<group>"; };
|
||||
5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineTitleView.swift; sourceTree = "<group>"; };
|
||||
514A8980244FD63F0085E65D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddTwitterFeedSheet.xib; sourceTree = SOURCE_ROOT; };
|
||||
514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AddTwitterFeedWindowController.swift; path = AddFeed/AddTwitterFeedWindowController.swift; sourceTree = "<group>"; };
|
||||
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = "<group>"; };
|
||||
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = "<group>"; };
|
||||
515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
|
||||
515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointEnableWindowController.swift; sourceTree = "<group>"; };
|
||||
515A516D243E7F950089E588 /* ExtensionPointDetail.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointDetail.xib; sourceTree = "<group>"; };
|
||||
515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointDetailViewController.swift; sourceTree = "<group>"; };
|
||||
@ -1841,7 +1831,6 @@
|
||||
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */,
|
||||
84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */,
|
||||
84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */,
|
||||
515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */,
|
||||
5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */,
|
||||
);
|
||||
path = ExtensionPoints;
|
||||
@ -2380,8 +2369,6 @@
|
||||
51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */,
|
||||
51333D392468615D00EB5C91 /* AddRedditFeedSheet.xib */,
|
||||
51333D1524685D2E00EB5C91 /* AddRedditFeedWindowController.swift */,
|
||||
514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */,
|
||||
514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */,
|
||||
848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */,
|
||||
849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */,
|
||||
51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */,
|
||||
@ -3386,7 +3373,6 @@
|
||||
65ED405C235DEF6C0081F399 /* ImportOPMLSheet.xib in Resources */,
|
||||
51DEE81926FBFF84006DAA56 /* Promenade.nnwtheme in Resources */,
|
||||
65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */,
|
||||
514A89A3244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */,
|
||||
51D0214726ED617100FF2E0F /* core.css in Resources */,
|
||||
5103A9F5242258C600410853 /* AccountsAddCloudKit.xib in Resources */,
|
||||
65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */,
|
||||
@ -3513,7 +3499,6 @@
|
||||
49F40DF82335B71000552BF4 /* newsfoot.js in Resources */,
|
||||
51333D3B2468615D00EB5C91 /* AddRedditFeedSheet.xib in Resources */,
|
||||
BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */,
|
||||
514A89A2244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */,
|
||||
5103A9982421643300410853 /* blank.html in Resources */,
|
||||
515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */,
|
||||
84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */,
|
||||
@ -3935,7 +3920,6 @@
|
||||
65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||
518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */,
|
||||
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */,
|
||||
514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */,
|
||||
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */,
|
||||
653813182680E152007A082C /* AccountType+Helpers.swift in Sources */,
|
||||
65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */,
|
||||
@ -4008,7 +3992,6 @@
|
||||
65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */,
|
||||
65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */,
|
||||
65ED402A235DEF6C0081F399 /* AddWebFeedWindowController.swift in Sources */,
|
||||
515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */,
|
||||
65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */,
|
||||
65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */,
|
||||
178A9F9E2549449F00AB7E9D /* AddAccountsView.swift in Sources */,
|
||||
@ -4179,7 +4162,6 @@
|
||||
512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */,
|
||||
5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */,
|
||||
51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */,
|
||||
515A5180243E90260089E588 /* TwitterFeedProvider-Extensions.swift in Sources */,
|
||||
51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */,
|
||||
51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */,
|
||||
51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */,
|
||||
@ -4269,7 +4251,6 @@
|
||||
849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */,
|
||||
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */,
|
||||
849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */,
|
||||
514A89A5244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */,
|
||||
849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */,
|
||||
5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */,
|
||||
8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */,
|
||||
@ -4335,7 +4316,6 @@
|
||||
5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */,
|
||||
84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */,
|
||||
845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */,
|
||||
515A5107243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */,
|
||||
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */,
|
||||
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */,
|
||||
511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */,
|
||||
@ -4547,14 +4527,6 @@
|
||||
name = MainInterface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
514A8980244FD63F0085E65D /* Base */,
|
||||
);
|
||||
name = AddTwitterFeedSheet.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
51C0307F257D815A00609262 /* UnifiedWindow.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -13,8 +13,6 @@ public protocol SecretsProvider {
|
||||
var mercuryClientSecret: String { get }
|
||||
var feedlyClientId: String { get }
|
||||
var feedlyClientSecret: String { get }
|
||||
var twitterConsumerKey: String { get }
|
||||
var twitterConsumerSecret: String { get }
|
||||
var redditConsumerKey: String { get }
|
||||
var inoreaderAppId: String { get }
|
||||
var inoreaderAppKey: String { get }
|
||||
|
@ -281,30 +281,6 @@ blockquote {
|
||||
border-top: 1px solid var(--header-table-border-color);
|
||||
}
|
||||
|
||||
/* Twitter */
|
||||
|
||||
.twitterAvatar {
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
height: 1.7em;
|
||||
width: 1.7em;
|
||||
}
|
||||
|
||||
.twitterUsername {
|
||||
line-height: 1.2;
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.twitterScreenName {
|
||||
font-size: 66%;
|
||||
}
|
||||
|
||||
.twitterTimestamp {
|
||||
font-size: 66%;
|
||||
}
|
||||
|
||||
/* Newsfoot theme for light mode (default) */
|
||||
.newsfoot-footnote-popover {
|
||||
background: #ccc;
|
||||
|
@ -15,7 +15,6 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
case marsEdit
|
||||
case microblog
|
||||
#endif
|
||||
case twitter(String)
|
||||
case reddit(String)
|
||||
|
||||
var extensionPointType: ExtensionPoint.Type {
|
||||
@ -26,8 +25,6 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
case .microblog:
|
||||
return SendToMicroBlogCommand.self
|
||||
#endif
|
||||
case .twitter:
|
||||
return TwitterFeedProvider.self
|
||||
case .reddit:
|
||||
return RedditFeedProvider.self
|
||||
}
|
||||
@ -45,11 +42,6 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
"type": "microblog"
|
||||
]
|
||||
#endif
|
||||
case .twitter(let screenName):
|
||||
return [
|
||||
"type": "twitter",
|
||||
"screenName": screenName
|
||||
]
|
||||
case .reddit(let username):
|
||||
return [
|
||||
"type": "reddit",
|
||||
@ -68,9 +60,6 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
case "microblog":
|
||||
self = ExtensionPointIdentifer.microblog
|
||||
#endif
|
||||
case "twitter":
|
||||
guard let screenName = userInfo["screenName"] as? String else { return nil }
|
||||
self = ExtensionPointIdentifer.twitter(screenName)
|
||||
case "reddit":
|
||||
guard let username = userInfo["username"] as? String else { return nil }
|
||||
self = ExtensionPointIdentifer.reddit(username)
|
||||
@ -87,9 +76,6 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
case .microblog:
|
||||
hasher.combine("microblog")
|
||||
#endif
|
||||
case .twitter(let screenName):
|
||||
hasher.combine("twitter")
|
||||
hasher.combine(screenName)
|
||||
case .reddit(let username):
|
||||
hasher.combine("reddit")
|
||||
hasher.combine(username)
|
||||
|
@ -69,16 +69,12 @@ final class ExtensionPointManager: FeedProviderManagerDelegate {
|
||||
return activeExtensionPoints.values.compactMap({ return $0 as? FeedProvider })
|
||||
}
|
||||
|
||||
var isTwitterEnabled: Bool {
|
||||
return activeExtensionPoints.values.contains(where: { $0 is TwitterFeedProvider })
|
||||
}
|
||||
|
||||
var isRedditEnabled: Bool {
|
||||
return activeExtensionPoints.values.contains(where: { $0 is RedditFeedProvider })
|
||||
}
|
||||
|
||||
init() {
|
||||
possibleExtensionPointTypes = [TwitterFeedProvider.self, RedditFeedProvider.self]
|
||||
possibleExtensionPointTypes = [RedditFeedProvider.self]
|
||||
loadExtensionPoints()
|
||||
}
|
||||
|
||||
@ -121,12 +117,6 @@ private extension ExtensionPointManager {
|
||||
|
||||
func extensionPoint(for extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess?, completion: @escaping (Result<ExtensionPoint, Error>) -> Void) {
|
||||
switch extensionPointType {
|
||||
case is TwitterFeedProvider.Type:
|
||||
if let tokenSuccess = tokenSuccess, let twitter = TwitterFeedProvider(tokenSuccess: tokenSuccess) {
|
||||
completion(.success(twitter))
|
||||
} else {
|
||||
completion(.failure(ExtensionPointManagerError.unableToCreate))
|
||||
}
|
||||
case is RedditFeedProvider.Type:
|
||||
if let tokenSuccess = tokenSuccess {
|
||||
RedditFeedProvider.create(tokenSuccess: tokenSuccess) { result in
|
||||
@ -147,8 +137,6 @@ private extension ExtensionPointManager {
|
||||
|
||||
func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint? {
|
||||
switch extensionPointID {
|
||||
case .twitter(let screenName):
|
||||
return TwitterFeedProvider(screenName: screenName)
|
||||
case .reddit(let username):
|
||||
return RedditFeedProvider(username: username)
|
||||
#if os(macOS)
|
||||
|
@ -2,7 +2,7 @@
|
||||
%{
|
||||
import os
|
||||
|
||||
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'TWITTER_CONSUMER_KEY', 'TWITTER_CONSUMER_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
|
||||
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
|
||||
|
||||
def chunks(seq, size):
|
||||
return (seq[i:(i + size)] for i in range(0, len(seq), size))
|
||||
|
@ -20,7 +20,7 @@ RSDatabase uses FMDB for SQLite persistence.
|
||||
Required by OAuthSwift as a testing dependency. Not shipped in NNW.
|
||||
|
||||
## [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift)
|
||||
Our Reddit and Twitter use the OAuth framework to authenticate with the services
|
||||
Our Reddit integration uses the OAuth framework to authenticate with the services
|
||||
and then service requests to them.
|
||||
|
||||
## [PLCrashReporter](https://github.com/microsoft/plcrashreporter)
|
||||
|
Loading…
x
Reference in New Issue
Block a user