Skip to content

[WIP] Search Matches Navigation #842

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Draft
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
16 changes: 16 additions & 0 deletions Simplenote.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@
A6672FBF25C7F77000090DE3 /* NoteBodyExcerptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6672FBD25C7F77000090DE3 /* NoteBodyExcerptTests.swift */; };
A667305425C9751B00090DE3 /* SearchMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A667305325C9751B00090DE3 /* SearchMapView.swift */; };
A6BBDA3325501398005C8343 /* SPTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6BBDA3225501398005C8343 /* SPTextView.swift */; };
A6FFE3AF25CAC75400F937A5 /* SearchMatchesBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6FFE3AD25CAC75400F937A5 /* SearchMatchesBarViewController.swift */; };
A6FFE3B025CAC75400F937A5 /* SearchMatchesBarViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A6FFE3AE25CAC75400F937A5 /* SearchMatchesBarViewController.xib */; };
B50099322421218B0037A431 /* NSTextViewSimplenoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50099312421218B0037A431 /* NSTextViewSimplenoteTests.swift */; };
B5009937242130F70037A431 /* UnicodeScalar+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5009936242130F70037A431 /* UnicodeScalar+Simplenote.swift */; };
B5009938242130F70037A431 /* UnicodeScalar+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5009936242130F70037A431 /* UnicodeScalar+Simplenote.swift */; };
Expand Down Expand Up @@ -555,6 +557,8 @@
A6672FBD25C7F77000090DE3 /* NoteBodyExcerptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteBodyExcerptTests.swift; sourceTree = "<group>"; };
A667305325C9751B00090DE3 /* SearchMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchMapView.swift; sourceTree = "<group>"; };
A6BBDA3225501398005C8343 /* SPTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPTextView.swift; sourceTree = "<group>"; };
A6FFE3AD25CAC75400F937A5 /* SearchMatchesBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchMatchesBarViewController.swift; sourceTree = "<group>"; };
A6FFE3AE25CAC75400F937A5 /* SearchMatchesBarViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SearchMatchesBarViewController.xib; sourceTree = "<group>"; };
B50099312421218B0037A431 /* NSTextViewSimplenoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTextViewSimplenoteTests.swift; sourceTree = "<group>"; };
B5009936242130F70037A431 /* UnicodeScalar+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnicodeScalar+Simplenote.swift"; sourceTree = "<group>"; };
B5009939242131410037A431 /* UnicodeScalarSimplenoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnicodeScalarSimplenoteTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1094,6 +1098,15 @@
name = config;
sourceTree = "<group>";
};
A6FFE39F25CABD6800F937A5 /* SearchMatchesBar */ = {
isa = PBXGroup;
children = (
A6FFE3AD25CAC75400F937A5 /* SearchMatchesBarViewController.swift */,
A6FFE3AE25CAC75400F937A5 /* SearchMatchesBarViewController.xib */,
);
name = SearchMatchesBar;
sourceTree = "<group>";
};
B501AAD22437E52D0084CDA3 /* Constants */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1442,6 +1455,7 @@
B5C7DD4B243E697F00BEE354 /* ViewControllers */ = {
isa = PBXGroup;
children = (
A6FFE39F25CABD6800F937A5 /* SearchMatchesBar */,
B53BF19E24AC1F9500938C34 /* Editor */,
B5C63352251E727300C8BF46 /* Interlinks */,
B55C313124A5339300B23B3F /* Metrics */,
Expand Down Expand Up @@ -1696,6 +1710,7 @@
37AE49C91FFEBB0B00FCB165 /* markdown-dark.css in Resources */,
B5686D0E1AEAE6D7009F9E20 /* Images.xcassets in Resources */,
B58117D625B9D5F500927E0C /* AccountVerificationViewController.xib in Resources */,
A6FFE3B025CAC75400F937A5 /* SearchMatchesBarViewController.xib in Resources */,
B5C7DD43243E4A8E00BEE354 /* CollaborateViewController.xib in Resources */,
B5A89195231ECB3C0007EDCB /* LICENSE in Resources */,
46F0E66717A3300B005BB4D1 /* Localizable.strings in Resources */,
Expand Down Expand Up @@ -2008,6 +2023,7 @@
B58CE26524F9AE870079C04B /* NSStoryboard+Simplenote.swift in Sources */,
3700E97621C1E390004771C9 /* SPTextAttachment.swift in Sources */,
B5E0862F2448E58C00DEF476 /* NSImageName+Simplenote.swift in Sources */,
A6FFE3AF25CAC75400F937A5 /* SearchMatchesBarViewController.swift in Sources */,
B54F9A6724D0B21D00BCF754 /* NSNotification+Simplenote.swift in Sources */,
B505068D25A3DF4E008A8E10 /* SortBarView.swift in Sources */,
3712FC8E1FE1ACAA008544AC /* Theme.swift in Sources */,
Expand Down
74 changes: 47 additions & 27 deletions Simplenote/NoteEditorViewController+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,44 @@ extension NoteEditorViewController {
clipView.contentInsets.top = SplitItemMetrics.editorContentTopInset
scrollView.scrollerInsets.top = SplitItemMetrics.editorScrollerTopInset
}

@objc
func setupSearchMatchesBar() {
let viewController = SearchMatchesBarViewController()
addChild(viewController)
viewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(viewController.view)

NSLayoutConstraint.activate([
viewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
viewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
viewController.view.topAnchor.constraint(equalTo: toolbarView.bottomAnchor),
viewController.view.heightAnchor.constraint(equalToConstant: 28),
])

viewController.onCompletion = { [weak self] in
// self?.toolbarView.endSearch()
}

searchMatchesBarViewController = viewController
}

@objc
func setupSearchMap() {
let searchMapView = SearchMapView()
searchMapView.translatesAutoresizingMaskIntoConstraints = false

view.addSubview(searchMapView)

NSLayoutConstraint.activate([
searchMapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
searchMapView.widthAnchor.constraint(equalToConstant: EditorMetrics.searchMapWidth),
searchMapView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
searchMapView.topAnchor.constraint(equalTo: toolbarView.bottomAnchor)
])

self.searchMapView = searchMapView
}
}


Expand Down Expand Up @@ -238,6 +276,8 @@ extension NoteEditorViewController {
if let note = note {
storage.refreshStyle(markdownEnabled: note.markdown)
}

searchMatchesBarViewController?.refreshStyle()
}

/// Refreshes the Toolbar's Inner State
Expand Down Expand Up @@ -913,39 +953,19 @@ extension NoteEditorViewController {
private func updateKeywordsHighlight() {
let ranges = highlightedRanges

noteEditor.highlightedRanges = ranges
searchMatchesBarViewController?.setup(with: ranges.count, onChange: { [weak self] (index) in
let range = ranges[index]
self?.noteEditor.scrollRangeToVisible(range)
self?.noteEditor.showFindIndicator(for: range)
})
searchMatchesBarViewController?.view.isHidden = ranges.isEmpty

createSearchMapViewIfNeeded()
noteEditor.highlightedRanges = ranges
searchMapView?.update(with: noteEditor.relativeLocationsForText(in: ranges))
}
}


// MARK: - Search Map
//
extension NoteEditorViewController {
private func createSearchMapViewIfNeeded() {
guard searchMapView == nil else {
return
}

let searchMapView = SearchMapView()
searchMapView.translatesAutoresizingMaskIntoConstraints = false

view.addSubview(searchMapView)

NSLayoutConstraint.activate([
searchMapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
searchMapView.widthAnchor.constraint(equalToConstant: EditorMetrics.searchMapWidth),
searchMapView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
searchMapView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: SplitItemMetrics.editorScrollerTopInset)
])

self.searchMapView = searchMapView
}
}


// MARK: - EditorMetrics
//
private enum EditorMetrics {
Expand Down
3 changes: 2 additions & 1 deletion Simplenote/NoteEditorViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
@class ToolbarView;
@class SPTextView;
@class SearchMapView;
@class SearchQuery;
@class SearchMatchesBarViewController;

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -75,6 +75,7 @@ typedef NS_ENUM(NSInteger, NoteFontSize) {
@property (nonatomic, weak) id<EditorControllerNoteActionsDelegate> noteActionsDelegate;
@property (nonatomic, weak) id<EditorControllerTagActionsDelegate> tagActionsDelegate;
@property (nonatomic, strong, nullable) SearchMapView *searchMapView;
@property (nonatomic, strong, nullable) SearchMatchesBarViewController *searchMatchesBarViewController;

// TODO: Switch NSObject >> SearchQuery. ObjC compiler isn't picking up the Swift Package =(
@property (nonatomic, strong, nullable) NSObject *searchQuery;
Expand Down
2 changes: 2 additions & 0 deletions Simplenote/NoteEditorViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ - (void)viewDidLoad
[self setupScrollView];
[self setupStatusImageView];
[self setupTagsField];
[self setupSearchMap];
[self setupSearchMatchesBar];

// Preload Markdown Preview
self.markdownViewController = [MarkdownViewController new];
Expand Down
114 changes: 114 additions & 0 deletions Simplenote/SearchMatchesBarViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Cocoa

// MARK: - SearchMatchesBarViewController
//
class SearchMatchesBarViewController: NSViewController {

@IBOutlet private weak var textLabel: NSTextField!
@IBOutlet private weak var navigationControl: NSSegmentedControl!
@IBOutlet private weak var doneButton: NSButton! {
didSet {
doneButton.title = Localization.doneButton
}
}

private var total: Int = 0

///
private var current: Int = Constants.defaultCurrentValue {
didSet {
if oldValue != current, current >= 0 {
onChange?(current)
}

update()
}
}

private var onChange: ((_ current: Int) -> Void)?

/// Callback will be invoked when user presses "done" button
///
var onCompletion: (() -> Void)?

override func viewDidLoad() {
super.viewDidLoad()
refreshStyle()
update()
}

/// Setup with total number of matches and "on change" callback
///
func setup(with total: Int, onChange: @escaping (_ current: Int) -> Void) {
self.onChange = onChange
self.total = total
current = Constants.defaultCurrentValue
}

private func update() {
textLabel.stringValue = Localization.matches(with: total)

navigationControl.setEnabled(current - 1 >= 0 && total > 0, forSegment: Constants.backButtonIndex)
navigationControl.setEnabled(current + 1 < total && total > 0, forSegment: Constants.forwardButtonIndex)
}
}


// MARK: - Style
//
extension SearchMatchesBarViewController {
func refreshStyle() {
textLabel.textColor = .simplenoteSecondaryTextColor
doneButton.contentTintColor = .simplenoteActionButtonTintColor
}
}


// MARK: - Actions
//
private extension SearchMatchesBarViewController {
@IBAction func handlePressOnDoneButton(_ sender: Any) {
onCompletion?()
}

@IBAction func handlePressOnNavigationControl(_ sender: Any) {
var newCurrent = current
if navigationControl.selectedSegment == Constants.backButtonIndex {
newCurrent -= 1
} else {
newCurrent += 1
}

newCurrent = min(newCurrent, total - 1)
newCurrent = max(newCurrent, 0)

current = newCurrent
}
}


// MARK: - Constants
//
private struct Constants {
static let backButtonIndex = 0
static let forwardButtonIndex = 1

/// We use -1 so that we can navigate to the first match. User presses ">", we go from -1 to 0 and 0 is the index of the first match.
static let defaultCurrentValue = -1
}


// MARK: - Localization
//
private struct Localization {
static let doneButton = NSLocalizedString("Done", comment: "Done button on Search Matches bar")

static private let matchesSingular = NSLocalizedString("%1$d match", comment: "Number of matches shown on Search Matches bar when the amount is 1. Parameters: %1$d - amount")
static private let matchesPlural = NSLocalizedString("%1$d matches", comment: "Number of matches shown on Search Matches bar when the amount is not 1. Parameters: %1$d - amount")

static func matches(with value: Int) -> String {
let template = value == 1 ? matchesSingular : matchesPlural

return String(format: template, value)
}
}
82 changes: 82 additions & 0 deletions Simplenote/SearchMatchesBarViewController.xib
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SearchMatchesBarViewController" customModule="Simplenote" customModuleProvider="target">
<connections>
<outlet property="doneButton" destination="NK3-iv-sgV" id="yye-qV-ncy"/>
<outlet property="navigationControl" destination="BM3-WQ-hLr" id="dFL-VA-4af"/>
<outlet property="textLabel" destination="4Kj-lG-zos" id="mpk-2G-mhg"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="480" height="102"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="centerY" spacing="10" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Gva-20-TDk">
<rect key="frame" x="16" y="0.0" width="448" height="102"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Kj-lG-zos">
<rect key="frame" x="-2" y="44" width="341" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="lQs-iW-2Af">
<font key="font" metaFont="system" size="12"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<segmentedControl horizontalHuggingPriority="1000" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BM3-WQ-hLr">
<rect key="frame" x="346" y="41" width="43" height="20"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="roundRect" trackingMode="momentary" id="jUP-qD-dDg">
<font key="font" metaFont="cellTitle"/>
<segments>
<segment image="NSGoBackTemplate" width="19"/>
<segment image="NSGoForwardTemplate" width="19" tag="1"/>
</segments>
</segmentedCell>
<connections>
<action selector="handlePressOnNavigationControl:" target="-2" id="TTw-3M-ac4"/>
</connections>
</segmentedControl>
<button horizontalHuggingPriority="1000" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NK3-iv-sgV">
<rect key="frame" x="398" y="41" width="50" height="19"/>
<buttonCell key="cell" type="roundRect" title="Button" bezelStyle="roundedRect" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="DJF-iI-elf">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="systemMedium" size="11"/>
</buttonCell>
<connections>
<action selector="handlePressOnDoneButton:" target="-2" id="qbx-f0-rmg"/>
</connections>
</button>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Gva-20-TDk" secondAttribute="bottom" id="dem-fV-oyz"/>
<constraint firstItem="Gva-20-TDk" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="e9W-BK-URv"/>
<constraint firstItem="Gva-20-TDk" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="16" id="qYr-QL-Wwx"/>
<constraint firstAttribute="trailing" secondItem="Gva-20-TDk" secondAttribute="trailing" constant="16" id="xns-8e-ZEK"/>
</constraints>
<point key="canvasLocation" x="139" y="69"/>
</customView>
</objects>
<resources>
<image name="NSGoBackTemplate" width="9" height="12"/>
<image name="NSGoForwardTemplate" width="9" height="12"/>
</resources>
</document>