Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Mac/Base.lproj/UnifiedWindow.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,25 @@
<outlet property="urlLabel" destination="Dim-ed-Dcz" id="8fY-oo-cGT"/>
</connections>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="lMN-Bl-G9Q" userLabel="Find Bar Container View">
<rect key="frame" x="0.0" y="300" width="730" height="0.0"/>
<constraints>
<constraint firstAttribute="height" id="C1e-J5-baX"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="xI5-lx-RD8" firstAttribute="leading" secondItem="cJ9-6s-66u" secondAttribute="leading" constant="6" id="5vz-ys-CAo"/>
<constraint firstItem="lMN-Bl-G9Q" firstAttribute="top" secondItem="cJ9-6s-66u" secondAttribute="top" placeholder="YES" id="Qyj-r4-LOb"/>
<constraint firstItem="lMN-Bl-G9Q" firstAttribute="leading" secondItem="cJ9-6s-66u" secondAttribute="leading" id="fjq-7Z-e0q"/>
<constraint firstAttribute="trailing" secondItem="lMN-Bl-G9Q" secondAttribute="trailing" id="oth-5X-F9R"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xI5-lx-RD8" secondAttribute="trailing" constant="6" id="pbD-LN-Gk1"/>
<constraint firstAttribute="bottom" secondItem="xI5-lx-RD8" secondAttribute="bottom" constant="2" id="zsv-B0-ChW"/>
</constraints>
<connections>
<outlet property="detailStatusBarView" destination="xI5-lx-RD8" id="yIZ-aP-fKF"/>
<outlet property="findBarContainerView" destination="lMN-Bl-G9Q" id="zMP-iV-uSN"/>
<outlet property="findBarHeightConstraint" destination="C1e-J5-baX" id="Q4g-LU-JWK"/>
</connections>
</customView>
<connections>
Expand Down
60 changes: 58 additions & 2 deletions Mac/MainWindow/Detail/DetailContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import AppKit

final class DetailContainerView: NSView {
final class DetailContainerView: NSView, @MainActor NSTextFinderBarContainer {

@IBOutlet var detailStatusBarView: DetailStatusBarView!

Expand All @@ -29,7 +29,12 @@ 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
}
Expand All @@ -41,4 +46,55 @@ final class DetailContainerView: NSView {
let r = dirtyRect.intersection(bounds)
r.fill()
}

// 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
}
}

// Notify the web view so it can update its obscuredContentInsets
if let webView = contentView as? DetailWebView {
webView.isFindBarVisible = isFindBarVisible
}
}
}

func findBarViewDidChangeHeight() {
if let height = findBarView?.frame.size.height {
findBarHeightConstraint.constant = height + 1.0
findBarContainerView.layoutSubtreeIfNeeded()
findBarView?.setFrameOrigin(NSPoint.zero)
}
}

}
39 changes: 39 additions & 0 deletions Mac/MainWindow/Detail/DetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ final class DetailViewController: NSViewController, WKUIDelegate {
}
statusBarView.mouseoverLink = nil
containerView.contentView = webview
resetTextFinder()
}
}

Expand Down Expand Up @@ -81,6 +82,8 @@ final class DetailViewController: NSViewController, WKUIDelegate {
case .search:
detailStateForSearch = state
}

resetTextFinder()
}

func showDetail(for mode: TimelineSourceMode) {
Expand Down Expand Up @@ -115,6 +118,42 @@ final class DetailViewController: NSViewController, WKUIDelegate {
}
window.makeFirstResponderUnlessDescendantIsFirstResponder(currentWebViewController.webView)
}

// MARK: State Restoration

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)
}
}

var canFindInCurrentArticle: Bool {
currentWebViewController.canFindInArticle
}
}

// MARK: - DetailWebViewControllerDelegate
Expand Down
21 changes: 20 additions & 1 deletion Mac/MainWindow/Detail/DetailWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ final class DetailWebView: WKWebView {
weak var keyboardDelegate: KeyboardDelegate?
private var isObservingResizeNotifications = false

/// When the find bar is visible, the web view is pushed below it and no longer
/// extends under the toolbar, so we should not set obscuredContentInsets.
var isFindBarVisible = false {
didSet {
if isFindBarVisible != oldValue {
updateObscuredContentInsets()
}
}
}

private static let estimatedToolbarHeight: CGFloat = 52 // Height of macOS 26.2 icon-only toolbar
private var toolbarHeight: CGFloat {
guard let window,
Expand Down Expand Up @@ -99,6 +109,12 @@ final class DetailWebView: WKWebView {
super.viewDidEndLiveResize()
evaluateJavaScript("document.body.style.overflow = 'visible';", completionHandler: nil)
}

// MARK: NSTextFinderClient

// Returning false here prevents the "Replace" checkbox from appearing in the find bar
override var isEditable: Bool { return false }

}

// MARK: - Private
Expand Down Expand Up @@ -132,7 +148,10 @@ private extension DetailWebView {
}

func updateObscuredContentInsets() {
let updatedObscuredContentInsets = NSEdgeInsets(top: toolbarHeight, left: 0, bottom: 0, right: 0)
// When the find bar is visible, the web view is constrained below it and no longer
// extends under the toolbar, so we don't need to account for toolbar obscuring.
let topInset = isFindBarVisible ? 0 : toolbarHeight
let updatedObscuredContentInsets = NSEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0)
if obscuredContentInsets != updatedObscuredContentInsets {
obscuredContentInsets = updatedObscuredContentInsets
}
Expand Down
19 changes: 19 additions & 0 deletions Mac/MainWindow/Detail/DetailWebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,25 @@ final class DetailWebViewController: NSViewController {
override func scrollPageUp(_ sender: Any?) {
webView.scrollPageUp(sender)
}

// MARK: State Restoration

func saveState(to state: inout [AnyHashable : Any]) {
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
Expand Down
8 changes: 8 additions & 0 deletions Mac/MainWindow/MainWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ final class MainWindowController: NSWindowController, NSUserInterfaceValidations
return validateToggleReadArticles(item)
}

if item.action == #selector(performFindPanelAction(_:)) {
return self.detailViewController?.canFindInCurrentArticle ?? false
}

return true
}

Expand Down Expand Up @@ -550,6 +554,10 @@ final class MainWindowController: NSWindowController, NSUserInterfaceValidations
timelineContainerViewController?.toggleReadFilter()
}

@IBAction func performFindPanelAction(_ sender: Any?) {
self.detailViewController?.performFindPanelAction(sender)
}

@objc func selectArticleTheme(_ menuItem: NSMenuItem) {
ArticleThemesManager.shared.currentThemeName = menuItem.title
}
Expand Down