diff --git a/Mac/Base.lproj/MainWindow.storyboard b/Mac/Base.lproj/MainWindow.storyboard
index 2a537adbc..a816f6226 100644
--- a/Mac/Base.lproj/MainWindow.storyboard
+++ b/Mac/Base.lproj/MainWindow.storyboard
@@ -364,14 +364,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Mac/MainWindow/Detail/DetailContainerView.swift b/Mac/MainWindow/Detail/DetailContainerView.swift
index ffc330b37..7e6ae710a 100644
--- a/Mac/MainWindow/Detail/DetailContainerView.swift
+++ b/Mac/MainWindow/Detail/DetailContainerView.swift
@@ -29,10 +29,61 @@ final class DetailContainerView: NSView {
if let contentView = contentView {
contentView.translatesAutoresizingMaskIntoConstraints = false
addSubview(contentView, positioned: .below, relativeTo: detailStatusBarView)
- let constraints = constraintsToMakeSubViewFullSize(contentView)
+
+ // Constrain the content view to fill the available space on all sides except the top, which we'll constrain to the find bar
+ var constraints = constraintsToMakeSubViewFullSize(contentView).filter { $0.firstAttribute != .top }
+
+ constraints.append(findBarContainerView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor))
+ constraints.append(findBarContainerView.bottomAnchor.constraint(equalTo: contentView.topAnchor))
NSLayoutConstraint.activate(constraints)
contentViewConstraints = constraints
}
}
}
+
+ // MARK: NSTextFinderBarContainer
+
+ @IBOutlet var findBarContainerView: NSView!
+ @IBOutlet var findBarHeightConstraint: NSLayoutConstraint!
+
+
+ public var findBarView: NSView? = nil {
+ didSet {
+ oldValue?.removeFromSuperview()
+ }
+ }
+
+ public var isFindBarVisible = false {
+ didSet {
+ // It seems AppKit assumes the findBarView will be removed from its superview when it's
+ // not being shown, so we have to fulfill that expectation in addition to hiding the stack view
+ // container we embed it in.
+ if
+ self.isFindBarVisible,
+ let view = findBarView
+ {
+ view.layoutSubtreeIfNeeded()
+ view.frame.origin = NSZeroPoint
+ view.frame.size.width = self.findBarContainerView.bounds.width
+ findBarContainerView.frame = view.bounds
+ findBarHeightConstraint.constant = view.frame.size.height + 1.0
+ findBarContainerView.addSubview(view)
+ }
+ else {
+ if let view = findBarView {
+ view.removeFromSuperview()
+ findBarHeightConstraint.constant = 0
+ }
+ }
+ }
+ }
+
+ func findBarViewDidChangeHeight() {
+ if let height = findBarView?.frame.size.height {
+ findBarHeightConstraint.constant = height + 1.0
+ findBarContainerView.layoutSubtreeIfNeeded()
+ findBarView?.setFrameOrigin(NSPoint.zero)
+ }
+ }
+
}
diff --git a/Mac/MainWindow/Detail/DetailViewController.swift b/Mac/MainWindow/Detail/DetailViewController.swift
index f8758a852..bcfd2533a 100644
--- a/Mac/MainWindow/Detail/DetailViewController.swift
+++ b/Mac/MainWindow/Detail/DetailViewController.swift
@@ -41,6 +41,7 @@ final class DetailViewController: NSViewController, WKUIDelegate {
}
statusBarView.mouseoverLink = nil
containerView.contentView = webview
+ resetTextFinder()
}
}
@@ -52,6 +53,7 @@ final class DetailViewController: NSViewController, WKUIDelegate {
func setState(_ state: DetailState, mode: TimelineSourceMode) {
webViewController(for: mode).state = state
+ resetTextFinder()
}
func showDetail(for mode: TimelineSourceMode) {
@@ -92,7 +94,33 @@ final class DetailViewController: NSViewController, WKUIDelegate {
func saveState(to state: inout [AnyHashable : Any]) {
currentWebViewController.saveState(to: &state)
}
-
+
+ // MARK: Find in Article
+
+ private var didLoadTextFinder = false
+ lazy private var textFinder: NSTextFinder = {
+ let finder = NSTextFinder()
+ finder.isIncrementalSearchingEnabled = true
+ finder.incrementalSearchingShouldDimContentView = false
+ finder.client = self.currentWebViewController.webView
+ finder.findBarContainer = self.containerView
+ didLoadTextFinder = true
+ return finder
+ }()
+
+ private func resetTextFinder() {
+ if didLoadTextFinder {
+ self.textFinder.performAction(.hideFindInterface)
+ self.textFinder.client = currentWebViewController.webView
+ }
+ }
+
+ @IBAction func performFindPanelAction(_ sender: Any?) {
+ if let menuItem = sender as? NSMenuItem, let findAction = NSTextFinder.Action(rawValue: menuItem.tag) {
+ self.textFinder.performAction(findAction)
+ }
+ }
+
}
// MARK: - DetailWebViewControllerDelegate
diff --git a/Mac/MainWindow/Detail/DetailWebView.swift b/Mac/MainWindow/Detail/DetailWebView.swift
index 9c7a618b9..f66555500 100644
--- a/Mac/MainWindow/Detail/DetailWebView.swift
+++ b/Mac/MainWindow/Detail/DetailWebView.swift
@@ -99,6 +99,12 @@ final class DetailWebView: WKWebView {
window!.setFrame(frame, display: false)
}
}
+
+ // MARK: NSTextFinderClient
+
+ // Returning false here prevents the "Replace" checkbox from appearing in the find bar
+ override var isEditable: Bool { return false }
+
}
// MARK: - Private
diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift
index 0c3465f03..6d669cf5f 100644
--- a/Mac/MainWindow/Detail/DetailWebViewController.swift
+++ b/Mac/MainWindow/Detail/DetailWebViewController.swift
@@ -186,7 +186,18 @@ final class DetailWebViewController: NSViewController {
state[UserInfoKey.isShowingExtractedArticle] = isShowingExtractedArticle
state[UserInfoKey.articleWindowScrollY] = windowScrollY
}
-
+
+ // MARK: Find in Article
+
+ var canFindInArticle: Bool {
+ switch state {
+ case .article(_, _), .extracted(_, _, _):
+ return true
+ default:
+ return false
+ }
+ }
+
}
// MARK: - WKScriptMessageHandler
diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift
index 10320b8c5..17aba7c5b 100644
--- a/Mac/MainWindow/MainWindowController.swift
+++ b/Mac/MainWindow/MainWindowController.swift
@@ -276,6 +276,9 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
return true
}
+ if item.action == #selector(performFindPanelAction(_:)) {
+ return self.detailViewController?.currentWebViewController.canFindInArticle ?? false
+ }
return true
}
@@ -536,6 +539,10 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
@IBAction func toggleReadArticlesFilter(_ sender: Any?) {
timelineContainerViewController?.toggleReadFilter()
}
+
+ @IBAction func performFindPanelAction(_ sender: Any?) {
+ self.detailViewController?.performFindPanelAction(sender)
+ }
@objc func selectArticleTheme(_ menuItem: NSMenuItem) {
ArticleThemesManager.shared.currentThemeName = menuItem.title