Skip to content

Commit 5007f24

Browse files
committed
[Feature] Add tab, and preferences
- Change Backup Session to Latest Session - Add preferences view for showing latest session - Change add new url -> add current tab
1 parent edb2d3e commit 5007f24

12 files changed

+418
-102
lines changed

SessionBuddy Extension/SafariExtensionHandler.swift

+46-23
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,52 @@ import SafariServices
1010

1111
class SafariExtensionHandler: SFSafariExtensionHandler {
1212

13-
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
14-
// This method will be called when a content script provided by your extension calls safari.extension.dispatchMessage("message").
15-
page.getPropertiesWithCompletionHandler { properties in
16-
NSLog("The extension received a message (\(messageName)) from a script injected into (\(String(describing: properties?.url))) with userInfo (\(userInfo ?? [:]))")
13+
override func messageReceived(
14+
withName messageName: String,
15+
from page: SFSafariPage,
16+
userInfo: [String : Any]?) {
17+
18+
// This method will be called when a content script provided by
19+
// your extension calls safari.extension.dispatchMessage("message").
20+
if messageName == "DOMContentLoaded" || messageName == "BeforeUnload" {
21+
DispatchQueue.global(qos: .userInitiated).async {
22+
self.saveLatestSession()
23+
}
24+
}
25+
}
26+
27+
private func saveLatestSession() {
28+
guard Preferences.showLatestSession else {return}
29+
30+
// Get old backupSessionIdx
31+
LocalStorage.sessions.removeAll(where: \.isBackup)
32+
33+
SFSafariApplication.getActiveWindow { window in
34+
window?.getAllTabs { tabs in
35+
var sessionTabs = [Tab]()
36+
37+
for (index, tab) in tabs.enumerated() {
38+
tab.getActivePage { page in
39+
page?.getPropertiesWithCompletionHandler { properties in
40+
if let url = properties?.url?.absoluteString,
41+
let title = properties?.title {
42+
sessionTabs.append(Tab(title: title, url: url))
43+
}
44+
45+
// Last element
46+
if index == tabs.count - 1 {
47+
var newSession = Session(
48+
title: "Latest Session",
49+
tabs: sessionTabs)
50+
51+
newSession.isBackup = true
52+
LocalStorage.sessions = [newSession] + LocalStorage.sessions
53+
NotificationCenter.default.post(Notification(name: Notification.Name("sessionDidChange")))
54+
}
55+
}
56+
}
57+
}
58+
}
1759
}
1860
}
1961

@@ -30,23 +72,4 @@ class SafariExtensionHandler: SFSafariExtensionHandler {
3072
override func popoverViewController() -> SFSafariExtensionViewController {
3173
return SafariExtensionViewController.shared
3274
}
33-
34-
override func page(_ page: SFSafariPage, willNavigateTo url: URL?) {
35-
guard let url = url else {return}
36-
37-
// Handle auto backup session
38-
if let backupSessionIdx = LocalStorage.sessions.firstIndex(where: { $0.isBackup }) {
39-
// Update existed backup session
40-
var backupSessionTabs = LocalStorage.sessions[backupSessionIdx].tabs
41-
backupSessionTabs.append(Tab(title: url.absoluteString, url: url.absoluteString))
42-
// just add unique URL
43-
LocalStorage.sessions[backupSessionIdx].tabs = backupSessionTabs.unique()
44-
} else {
45-
// Create new backup session
46-
var backupSession = Session(title: "Backups - \(Date().commonStringFormat())", tabs: [Tab(title: url.absoluteString, url: url.absoluteString)])
47-
backupSession.isBackup = true
48-
LocalStorage.sessions.append(backupSession)
49-
}
50-
51-
}
5275
}

SessionBuddy Extension/Views/DetailView/DetailViewController.swift

+46-30
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class DetailViewController: NSViewController {
3434

3535
private var isMultiFuncViewShowing = false
3636

37+
private var newTabURL: String?
38+
3739
init(session: Session) {
3840
self.session = session
3941
super.init(nibName: "DetailViewController", bundle: Bundle.main)
@@ -82,7 +84,7 @@ class DetailViewController: NSViewController {
8284
btnAction.isEnabled = true
8385
containerViewActionInput.isHidden = false
8486
containerViewActions.isHidden = true
85-
87+
txtfieldAction.placeholderString = "Session name"
8688
txtfieldAction.stringValue = session.title
8789
txtfieldAction.becomeFirstResponder()
8890
}
@@ -92,6 +94,7 @@ class DetailViewController: NSViewController {
9294
btnAction.isEnabled = false
9395
containerViewActionInput.isHidden = false
9496
containerViewActions.isHidden = true
97+
txtfieldAction.placeholderString = "[email protected]"
9598
txtfieldAction.becomeFirstResponder()
9699
}
97100

@@ -123,28 +126,30 @@ class DetailViewController: NSViewController {
123126
}
124127

125128
private func share(with email: String) {
126-
do {
127-
let jsonEncoder = JSONEncoder()
128-
jsonEncoder.outputFormatting = .prettyPrinted
129-
let data = try jsonEncoder.encode(ImportExportData(data: [session]))
130-
131-
let tempDir = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
132-
let fileURL = tempDir.appendingPathComponent("\(Date().saveFileStringFormat()).json")
133-
134-
try data.write(to: fileURL)
135-
136-
let sharingService = NSSharingService(named: .composeEmail)
137-
sharingService?.delegate = self
138-
139-
sharingService?.recipients = [email]
140-
sharingService?.subject = "Sharing my Session Buddy session"
141-
let items: [Any] = ["see attachment", fileURL]
142-
sharingService?.perform(withItems: items)
143-
144-
} catch {
145-
NSLog(error.localizedDescription)
146-
Util.showErrorDialog(text: "Data is corrupted, please contact us for more support")
147-
}
129+
guard let session =
130+
LocalStorage.sessions.first(where: { $0.id == self.session.id })
131+
else {return}
132+
133+
let jsonEncoder = JSONEncoder()
134+
jsonEncoder.outputFormatting = .prettyPrinted
135+
136+
guard
137+
let subjectEncoded = "[Session Buddy] Share my session".addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
138+
let bodyEncodedData = try? jsonEncoder.encode(session),
139+
var body = String(data: bodyEncodedData, encoding: .utf8)
140+
else {return}
141+
142+
body = """
143+
Please copy the content below and save it as file.json to import in Session Buddy
144+
145+
\(body)
146+
"""
147+
guard
148+
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .alphanumerics),
149+
let defaultUrl = URL(string: "mailto:\(email)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
150+
else {return}
151+
152+
NSWorkspace.shared.open(defaultUrl)
148153
}
149154

150155
@IBAction func deleteSession(_ sender: Any) {
@@ -167,9 +172,15 @@ class DetailViewController: NSViewController {
167172
tab?.getActivePage(completionHandler: { page in
168173
page?.getPropertiesWithCompletionHandler { properties in
169174
DispatchQueue.main.async {
170-
self.txtfieldURL.stringValue = properties?.url?.absoluteString ?? ""
175+
guard
176+
let title = properties?.title,
177+
let url = properties?.url
178+
else {return}
179+
180+
self.txtfieldURL.stringValue = title
171181
self.txtfieldURL.becomeFirstResponder()
172182
self.btnAdd.isEnabled = !self.txtfieldURL.stringValue.isEmpty
183+
self.newTabURL = url.absoluteString
173184
}
174185
}
175186
})
@@ -187,9 +198,12 @@ class DetailViewController: NSViewController {
187198
self.containerEditview.isHidden = true
188199
self.btnAddUrl.isHidden = false
189200

190-
guard !txtfieldURL.stringValue.isEmpty else {return}
201+
guard
202+
!txtfieldURL.stringValue.isEmpty,
203+
let url = self.newTabURL
204+
else {return}
191205

192-
let newTab = Tab(title: txtfieldURL.stringValue, url: txtfieldURL.stringValue)
206+
let newTab = Tab(title: txtfieldURL.stringValue, url: url)
193207
self.tabs.append(newTab)
194208
self.updateSession(with: self.tabs)
195209

@@ -215,7 +229,7 @@ extension DetailViewController: NSTableViewDelegate {
215229

216230
cell.set(
217231
title: tabs[row].title,
218-
onDelete: self.onDelete(at: row)
232+
onDelete: self.onDelete(id: tabs[row].id)
219233
)
220234

221235
return cell
@@ -231,13 +245,15 @@ extension DetailViewController: NSTableViewDelegate {
231245
return false
232246
}
233247

234-
private func onDelete(at index: Int) -> (() -> Void) {
248+
private func onDelete(id: String) -> (() -> Void) {
235249
return {
236-
self.tabs.remove(at: index)
250+
guard let idx = self.tabs.firstIndex(where: { $0.id == id }) else {return}
251+
252+
self.tabs.remove(at: idx)
237253
self.updateSession(with: self.tabs)
238254

239255
DispatchQueue.main.async {
240-
self.tableView.removeRows(at: .init(integer: index), withAnimation: .effectFade)
256+
self.tableView.removeRows(at: .init(integer: idx), withAnimation: .effectFade)
241257
}
242258

243259
self.shouldReload = true

SessionBuddy Extension/Views/DetailView/DetailViewController.xib

+7-8
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
</textFieldCell>
123123
</textField>
124124
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="g6K-Gs-2IT">
125-
<rect key="frame" x="289.5" y="231" width="47" height="23"/>
125+
<rect key="frame" x="288.5" y="231" width="48" height="23"/>
126126
<buttonCell key="cell" type="roundTextured" title="Open" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyUpOrDown" inset="2" id="YrO-fW-Ucx">
127127
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
128128
<font key="font" metaFont="system"/>
@@ -148,8 +148,8 @@
148148
<rect key="frame" x="0.0" y="0.0" width="344" height="50"/>
149149
<subviews>
150150
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PsF-pz-itA">
151-
<rect key="frame" x="12" y="15" width="80" height="21"/>
152-
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="btn_add_url" imagePosition="only" alignment="center" imageScaling="proportionallyUpOrDown" inset="2" id="aTF-kV-soS">
151+
<rect key="frame" x="11.5" y="13" width="110" height="23"/>
152+
<buttonCell key="cell" type="roundTextured" title="Add current tab" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyUpOrDown" inset="2" id="aTF-kV-soS">
153153
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
154154
<font key="font" metaFont="system"/>
155155
</buttonCell>
@@ -162,7 +162,7 @@
162162
<subviews>
163163
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d91-cE-Siu">
164164
<rect key="frame" x="0.0" y="0.0" width="241" height="26"/>
165-
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="jPK-4c-Udx">
165+
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Tab title" drawsBackground="YES" id="jPK-4c-Udx">
166166
<font key="font" usesAppearanceFont="YES"/>
167167
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
168168
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
@@ -225,7 +225,7 @@ DQ
225225
<rect key="frame" x="8" y="199" width="328" height="20"/>
226226
<subviews>
227227
<stackView distribution="fillEqually" orientation="horizontal" alignment="centerY" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BnX-HY-53G">
228-
<rect key="frame" x="100" y="0.0" width="128" height="20"/>
228+
<rect key="frame" x="71" y="0.0" width="187" height="20"/>
229229
<subviews>
230230
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qch-bE-GK3">
231231
<rect key="frame" x="0.0" y="0.0" width="67" height="20"/>
@@ -253,8 +253,8 @@ DQ
253253
<action selector="deleteSession:" target="-2" id="CPy-O4-kUM"/>
254254
</connections>
255255
</button>
256-
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vxU-kA-507">
257-
<rect key="frame" x="0.0" y="-10" width="51" height="30"/>
256+
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vxU-kA-507">
257+
<rect key="frame" x="136" y="0.0" width="51" height="20"/>
258258
<constraints>
259259
<constraint firstAttribute="width" constant="51" id="YIu-wO-sam"/>
260260
</constraints>
@@ -389,7 +389,6 @@ DQ
389389
<image name="NSTouchBarComposeTemplate" width="21" height="30"/>
390390
<image name="NSTouchBarDeleteTemplate" width="15" height="30"/>
391391
<image name="NSTouchBarShareTemplate" width="15" height="30"/>
392-
<image name="btn_add_url" width="80" height="21"/>
393392
<image name="ico_circle" width="13" height="13"/>
394393
</resources>
395394
</document>

SessionBuddy Extension/Views/Import/ImportViewController.xib

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</textFieldCell>
2929
</textField>
3030
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FKw-gs-67h">
31-
<rect key="frame" x="282.5" y="59" width="54" height="23"/>
31+
<rect key="frame" x="282" y="59" width="54" height="23"/>
3232
<buttonCell key="cell" type="roundTextured" title="Import" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyUpOrDown" inset="2" id="WDg-MZ-apL">
3333
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
3434
<font key="font" metaFont="system"/>
@@ -38,7 +38,7 @@
3838
</connections>
3939
</button>
4040
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wEH-5q-yeN">
41-
<rect key="frame" x="7.5" y="59" width="28" height="23"/>
41+
<rect key="frame" x="8" y="59" width="27" height="23"/>
4242
<constraints>
4343
<constraint firstAttribute="width" constant="27" id="1sc-B4-IZl"/>
4444
</constraints>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// PreferencesViewConttroller.swift
3+
// Session Buddy Extension
4+
//
5+
// Created by phucld on 6/11/20.
6+
// Copyright © 2020 Dwarves Foundation. All rights reserved.
7+
//
8+
9+
import Cocoa
10+
11+
class PreferencesViewConttroller: NSViewController {
12+
13+
@IBOutlet weak var checkboxShowLatestSession: NSButton!
14+
15+
var onNavigationBack: ((Bool) -> Void)?
16+
17+
private var shouldUpdate = false
18+
19+
override func viewDidLoad() {
20+
super.viewDidLoad()
21+
setupViews()
22+
}
23+
24+
func set(onNavigationBack: @escaping ((Bool) -> Void)) {
25+
self.onNavigationBack = onNavigationBack
26+
}
27+
28+
private func setupViews() {
29+
checkboxShowLatestSession.state = Preferences.showLatestSession ? .on : .off
30+
}
31+
32+
@IBAction func toggleShowTheLatestSession(_ sender: Any) {
33+
Preferences.showLatestSession = checkboxShowLatestSession.state == .on
34+
if checkboxShowLatestSession.state == .on {
35+
LocalStorage.sessions.removeAll(where: (\.isBackup))
36+
NotificationCenter.default.post(Notification(name: Notification.Name("sessionDidChange")))
37+
}
38+
39+
shouldUpdate = true
40+
}
41+
42+
@IBAction func backToPrevious(_ sender: Any) {
43+
self.onNavigationBack?(shouldUpdate)
44+
self.view.removeFromSuperview()
45+
self.removeFromParent()
46+
}
47+
48+
}

0 commit comments

Comments
 (0)