Skip to content

Commit 4fa7528

Browse files
authored
Merge pull request #36 from dteoh/history-navigation
Add back and forward navigation
2 parents c87273d + 0fb671f commit 4fa7528

File tree

3 files changed

+163
-31
lines changed

3 files changed

+163
-31
lines changed

devdocs-macos/DocumentationViewController.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,24 @@ class DocumentationViewController: NSViewController {
103103
webView.evaluateJavaScript("search( (\(args))[\"term\"] );")
104104
}
105105

106+
// MARK:- Navigation
107+
108+
func canGoBack() -> Bool {
109+
return webView.backForwardList.backItem.map { _ in true } ?? false
110+
}
111+
112+
func canGoForward() -> Bool {
113+
return webView.backForwardList.forwardItem.map { _ in true } ?? false
114+
}
115+
116+
@objc func goBack() {
117+
webView.evaluateJavaScript("window.history.back();");
118+
}
119+
120+
@objc func goForward() {
121+
webView.evaluateJavaScript("window.history.forward();");
122+
}
123+
106124
// MARK:- JS integration
107125

108126
func useNativeScrollbars(_ using: Bool) {
@@ -280,3 +298,16 @@ extension DocumentationViewController: NSSearchFieldDelegate {
280298
webView.evaluateJavaScript("resetSearch();")
281299
}
282300
}
301+
302+
// MARK:- NSUserInterfaceValidations
303+
extension DocumentationViewController: NSUserInterfaceValidations {
304+
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
305+
if item.action == #selector(goBack) {
306+
return canGoBack()
307+
}
308+
if item.action == #selector(goForward) {
309+
return canGoForward()
310+
}
311+
return false
312+
}
313+
}

devdocs-macos/DocumentationWindow.xib

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
<dependencies>
44
<deployment identifier="macosx"/>
55
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
6-
<capability name="Search Toolbar Item" minToolsVersion="12.0" minSystemVersion="11.0"/>
76
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
87
</dependencies>
98
<objects>
109
<customObject id="-2" userLabel="File's Owner" customClass="DocumentationWindowController" customModule="DevDocs" customModuleProvider="target">
1110
<connections>
12-
<outlet property="contentSearchField" destination="nuW-bv-dAc" id="VBO-Nb-W9g"/>
1311
<outlet property="documentationViewController" destination="f61-KY-zVD" id="SGK-n1-U1U"/>
1412
<outlet property="window" destination="QvC-M9-y7g" id="LfB-8c-kAm"/>
1513
</connections>
@@ -20,37 +18,11 @@
2018
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
2119
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
2220
<rect key="contentRect" x="196" y="240" width="800" height="600"/>
23-
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
21+
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="2135"/>
2422
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
2523
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
26-
<autoresizingMask key="autoresizingMask"/>
24+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2725
</view>
28-
<toolbar key="toolbar" implicitIdentifier="3EDBA340-82E0-4F6D-A2E5-B64C00F706AB" autosavesConfiguration="NO" displayMode="iconOnly" sizeMode="regular" id="RPh-lZ-I7q">
29-
<allowedToolbarItems>
30-
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="djD-j7-CRO"/>
31-
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="C4V-Bf-4rP"/>
32-
<searchToolbarItem implicitItemIdentifier="199BC3E6-5F40-416D-9966-397495A3A2D8" label="Search" paletteLabel="Search" visibilityPriority="1001" id="6OC-fj-Lhe">
33-
<nil key="toolTip"/>
34-
<searchField key="view" verticalHuggingPriority="750" textCompletion="NO" id="nuW-bv-dAc">
35-
<rect key="frame" x="0.0" y="0.0" width="100" height="21"/>
36-
<autoresizingMask key="autoresizingMask"/>
37-
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" recentsAutosaveName="content-search-term" id="elf-l7-B2W">
38-
<font key="font" usesAppearanceFont="YES"/>
39-
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
40-
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
41-
</searchFieldCell>
42-
<connections>
43-
<action selector="searchPageContents:" target="f61-KY-zVD" id="ZAe-5r-fMu"/>
44-
<outlet property="delegate" destination="f61-KY-zVD" id="6cm-hv-cR5"/>
45-
</connections>
46-
</searchField>
47-
</searchToolbarItem>
48-
</allowedToolbarItems>
49-
<defaultToolbarItems>
50-
<toolbarItem reference="C4V-Bf-4rP"/>
51-
<searchToolbarItem reference="6OC-fj-Lhe"/>
52-
</defaultToolbarItems>
53-
</toolbar>
5426
<point key="canvasLocation" x="-106" y="107"/>
5527
</window>
5628
<customObject id="f61-KY-zVD" customClass="DocumentationViewController" customModule="DevDocs" customModuleProvider="target">

devdocs-macos/DocumentationWindowController.swift

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import WebKit
33

44
class DocumentationWindowController: NSWindowController {
55

6-
@IBOutlet weak var contentSearchField: NSSearchField?
76
@IBOutlet weak var documentationViewController: DocumentationViewController?
87

98
var documentation: Documentation!
109
private var observations: Set<NSKeyValueObservation>!
10+
private weak var contentSearchField: NSSearchField?
1111

1212
override var windowNibName: NSNib.Name? {
1313
return NSNib.Name("DocumentationWindow")
@@ -26,6 +26,8 @@ class DocumentationWindowController: NSWindowController {
2626
override func windowDidLoad() {
2727
guard let dvc = documentationViewController else { return }
2828

29+
setupToolbar()
30+
2931
NotificationCenter.default.addObserver(self,
3032
selector: #selector(observeViewerState),
3133
name: .DocumentViewerStateDidChange,
@@ -52,6 +54,20 @@ class DocumentationWindowController: NSWindowController {
5254
object: nil)
5355
}
5456

57+
private func setupToolbar() {
58+
let toolbar = NSToolbar(identifier: "DocumentationWindowToolbar")
59+
toolbar.allowsUserCustomization = true
60+
toolbar.autosavesConfiguration = true
61+
toolbar.displayMode = .iconOnly
62+
toolbar.delegate = self
63+
64+
if let window = window {
65+
window.titlebarAppearsTransparent = true
66+
window.toolbarStyle = .automatic
67+
window.toolbar = toolbar
68+
}
69+
}
70+
5571
// MARK:- NotificationCenter observers
5672

5773
@objc private func observeViewerState() {
@@ -128,3 +144,116 @@ extension DocumentationWindowController: DocumentationViewDelegate {
128144
}
129145
}
130146
}
147+
148+
// MARK:- NSToolbarItem.Identifier
149+
extension NSToolbarItem.Identifier {
150+
static let historyNavigation: NSToolbarItem.Identifier = NSToolbarItem.Identifier("HistoryNavigation")
151+
static let contentSearch: NSToolbarItem.Identifier = NSToolbarItem.Identifier("ContentSearch")
152+
153+
// Sub items
154+
static let navigateBack: NSToolbarItem.Identifier = NSToolbarItem.Identifier("NavigateBack")
155+
static let navigateForward: NSToolbarItem.Identifier = NSToolbarItem.Identifier("NavigateForward")
156+
}
157+
158+
// MARK:- NSToolbarDelegate
159+
extension DocumentationWindowController: NSToolbarDelegate {
160+
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
161+
return [
162+
.historyNavigation,
163+
.space,
164+
.flexibleSpace,
165+
.contentSearch
166+
]
167+
}
168+
169+
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
170+
return [.historyNavigation, .flexibleSpace, .contentSearch]
171+
}
172+
173+
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
174+
switch itemIdentifier {
175+
case .historyNavigation:
176+
let backItem = NSToolbarItem(itemIdentifier: .navigateBack)
177+
backItem.label = NSLocalizedString("Back", comment: "Navigate back")
178+
backItem.toolTip = backItem.label
179+
backItem.isBordered = true
180+
backItem.image = NSImage(systemSymbolName: "chevron.backward",
181+
accessibilityDescription: NSLocalizedString("Navigate back", comment: "Navigate back"))
182+
backItem.autovalidates = true
183+
184+
let forwardItem = NSToolbarItem(itemIdentifier: .navigateForward)
185+
forwardItem.label = NSLocalizedString("Forward", comment: "Navigate forward")
186+
forwardItem.toolTip = forwardItem.label
187+
forwardItem.isBordered = true
188+
forwardItem.image = NSImage(systemSymbolName: "chevron.forward",
189+
accessibilityDescription: NSLocalizedString("Navigate forward", comment: "Navigate forward"))
190+
forwardItem.autovalidates = true
191+
192+
let item = NSToolbarItemGroup(itemIdentifier: itemIdentifier)
193+
item.label = NSLocalizedString("Back / Forward", comment: "History navigation")
194+
item.isNavigational = true
195+
item.subitems = [backItem, forwardItem]
196+
return item
197+
case .contentSearch:
198+
let item = NSSearchToolbarItem(itemIdentifier: itemIdentifier)
199+
item.searchField.recentsAutosaveName = NSSearchField.RecentsAutosaveName("content-search-term")
200+
return item
201+
default:
202+
return nil
203+
}
204+
}
205+
206+
func toolbarWillAddItem(_ notification: Notification) {
207+
guard let item = notification.userInfo?["item"] as? NSToolbarItem else {
208+
return
209+
}
210+
211+
switch item.itemIdentifier {
212+
case .historyNavigation:
213+
let itemGroup = item as! NSToolbarItemGroup
214+
for subitem in itemGroup.subitems {
215+
subitem.target = documentationViewController
216+
switch subitem.itemIdentifier {
217+
case .navigateBack:
218+
subitem.action = #selector(DocumentationViewController.goBack)
219+
case .navigateForward:
220+
subitem.action = #selector(DocumentationViewController.goForward)
221+
default:
222+
break
223+
}
224+
}
225+
case .contentSearch:
226+
let searchItem = item as! NSSearchToolbarItem
227+
searchItem.searchField.delegate = documentationViewController
228+
searchItem.searchField.target = documentationViewController
229+
searchItem.searchField.action = #selector(DocumentationViewController.searchPageContents(_:))
230+
contentSearchField = searchItem.searchField
231+
default:
232+
return
233+
}
234+
}
235+
236+
func toolbarDidRemoveItem(_ notification: Notification) {
237+
guard let item = notification.userInfo?["item"] as? NSToolbarItem else {
238+
return
239+
}
240+
241+
switch item.itemIdentifier {
242+
case .historyNavigation:
243+
let itemGroup = item as! NSToolbarItemGroup
244+
for subitem in itemGroup.subitems {
245+
subitem.target = nil
246+
subitem.action = nil
247+
}
248+
case .contentSearch:
249+
contentSearchField = nil
250+
251+
let searchItem = item as! NSSearchToolbarItem
252+
searchItem.searchField.delegate = nil
253+
searchItem.searchField.target = nil
254+
searchItem.searchField.action = nil
255+
default:
256+
return
257+
}
258+
}
259+
}

0 commit comments

Comments
 (0)