Skip to content

Commit 1ff5566

Browse files
Merge pull request #1140 from Automattic/charlie/1000/add-spotlight-support
Add spotlight support for Simplenote
2 parents b2232e9 + fd0a4f6 commit 1ff5566

12 files changed

+227
-14
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
2.21
22
-----
3+
- Added spotlight search support for notes
34
- Added multiple window support for editing notes
45

56
2.20

Simplenote.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,10 +462,14 @@
462462
BA2C65CF26FE996A00FA84E1 /* NSButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */; };
463463
BA4C6D16264CA8C000B723A7 /* SignupRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6D15264CA8C000B723A7 /* SignupRemoteTests.swift */; };
464464
BA4C6D18264CAAF800B723A7 /* URLRequest+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6D17264CAAF800B723A7 /* URLRequest+Simplenote.swift */; };
465+
BA52005B2BC878F1003F1B75 /* CSSearchable+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */; };
466+
BA52005D2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */; };
465467
BA553F0827065E20007737E9 /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA553F0727065E20007737E9 /* FontSettings.swift */; };
466468
BA553F0927065E20007737E9 /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA553F0727065E20007737E9 /* FontSettings.swift */; };
467469
BA5F020526BB57F000581E92 /* NSAlert+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */; };
468470
BA5F020626BB57F000581E92 /* NSAlert+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */; };
471+
BA71EC242BC88FD000F42CB1 /* CSSearchable+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */; };
472+
BA71EC252BC88FFC00F42CB1 /* NSManagedObjectContext+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */; };
469473
BA78AF6F2B5B2BBA00DCF896 /* AutomatticTracks in Frameworks */ = {isa = PBXBuildFile; productRef = BA78AF6E2B5B2BBA00DCF896 /* AutomatticTracks */; };
470474
BA78AF712B5B2BC300DCF896 /* AutomatticTracks in Frameworks */ = {isa = PBXBuildFile; productRef = BA78AF702B5B2BC300DCF896 /* AutomatticTracks */; };
471475
BA938CEC26ACFF4A00BE5A1D /* Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA938CEB26ACFF4A00BE5A1D /* Remote.swift */; };
@@ -872,6 +876,8 @@
872876
BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSButton+Extensions.swift"; sourceTree = "<group>"; };
873877
BA4C6D15264CA8C000B723A7 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.swift; sourceTree = "<group>"; };
874878
BA4C6D17264CAAF800B723A7 /* URLRequest+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+Simplenote.swift"; sourceTree = "<group>"; };
879+
BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSSearchable+Helpers.swift"; sourceTree = "<group>"; };
880+
BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Simplenote.swift"; sourceTree = "<group>"; };
875881
BA553F0727065E20007737E9 /* FontSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSettings.swift; sourceTree = "<group>"; };
876882
BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAlert+Simplenote.swift"; sourceTree = "<group>"; };
877883
BA938CEB26ACFF4A00BE5A1D /* Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Remote.swift; sourceTree = "<group>"; };
@@ -1257,6 +1263,7 @@
12571263
children = (
12581264
B5D21CB624881EF600D57A34 /* Array+Simplenote.swift */,
12591265
B57CB87D244DED2300BA7969 /* Bundle+Simplenote.swift */,
1266+
BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */,
12601267
B53BF19B24ABDE7C00938C34 /* DateFormatter+Simplenote.swift */,
12611268
B5C620AA257ED4CF008359A9 /* NSAnimationContext+Simplenote.swift */,
12621269
B56FA79A2437D2E0002CB9FF /* NSAppearance+Simplenote.swift */,
@@ -1303,6 +1310,7 @@
13031310
BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */,
13041311
BAFB544F26CCA7F1006E037C /* NSProgressIndicator+Simplenote.swift */,
13051312
BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */,
1313+
BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */,
13061314
);
13071315
name = Extensions;
13081316
sourceTree = "<group>";
@@ -2130,6 +2138,7 @@
21302138
BA2C65CB26FE996100FA84E1 /* NSButton+Extensions.swift in Sources */,
21312139
B58117E225B9E5D200927E0C /* AccountVerificationController.swift in Sources */,
21322140
B5009937242130F70037A431 /* UnicodeScalar+Simplenote.swift in Sources */,
2141+
BA52005B2BC878F1003F1B75 /* CSSearchable+Helpers.swift in Sources */,
21332142
B5985AD5242950B40044EDE9 /* NSColor+Simplenote.swift in Sources */,
21342143
B5C7DD3D243E1F1900BEE354 /* VersionsViewController.swift in Sources */,
21352144
B58BBD3D2523FF160025135F /* PopoverWindow.swift in Sources */,
@@ -2166,6 +2175,7 @@
21662175
B59EA98124AA5EFA008ABE4B /* NoteMetrics.swift in Sources */,
21672176
B5EDF338258A8F1B0066D91D /* TagListFilter.swift in Sources */,
21682177
375D293621E033D1007AB25A /* document.c in Sources */,
2178+
BA52005D2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift in Sources */,
21692179
B5609AEC24EEE7200097777A /* SPBucket+Simplenote.swift in Sources */,
21702180
B56FA7902437C672002CB9FF /* ColorStudio.swift in Sources */,
21712181
B5177CD025EEEEFB00A8D834 /* NSWindow+Transitions.swift in Sources */,
@@ -2324,6 +2334,7 @@
23242334
BA2C65CF26FE996A00FA84E1 /* NSButton+Extensions.swift in Sources */,
23252335
375D294121E033D1007AB25A /* html_smartypants.c in Sources */,
23262336
B5E061782450AEDA0076111A /* ToolbarView.swift in Sources */,
2337+
BA71EC242BC88FD000F42CB1 /* CSSearchable+Helpers.swift in Sources */,
23272338
B502C1DE25BA2EB700145D6C /* AccountRemote.swift in Sources */,
23282339
F998F3EC22853C49008C2B59 /* CrashLogging.swift in Sources */,
23292340
B5919365245A7AD300A70C0C /* NSScreen+Simplenote.swift in Sources */,
@@ -2360,6 +2371,7 @@
23602371
B5F807CD2481982B0048CBD7 /* Note+Simplenote.swift in Sources */,
23612372
A6C1E21525E010140076ADF7 /* SPApplication.swift in Sources */,
23622373
466FFEB417CC10A800399652 /* DateTransformer.m in Sources */,
2374+
BA71EC252BC88FFC00F42CB1 /* NSManagedObjectContext+Simplenote.swift in Sources */,
23632375
B5132FA923C4B9760065DD80 /* NSTextStorage+Simplenote.swift in Sources */,
23642376
375D293721E033D1007AB25A /* document.c in Sources */,
23652377
376EE3EC202B748E00E3812E /* SPAboutTextField.swift in Sources */,

Simplenote/CSSearchable+Helpers.swift

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// CSSearchableItem+Helpers.swift
3+
// Simplenote
4+
//
5+
// Created by Michal Kosmowski on 25/11/2016.
6+
// Copyright © 2016 Automattic. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import CoreSpotlight
11+
import UniformTypeIdentifiers
12+
13+
extension CSSearchableItemAttributeSet {
14+
15+
convenience init(note: Note) {
16+
self.init(contentType: UTType.data)
17+
note.ensurePreviewStringsAreAvailable()
18+
title = note.titlePreview
19+
contentDescription = note.bodyPreview
20+
}
21+
22+
}
23+
24+
extension CSSearchableItem {
25+
26+
convenience init(note: Note) {
27+
let attributeSet = CSSearchableItemAttributeSet(note: note)
28+
self.init(uniqueIdentifier: note.simperiumKey, domainIdentifier: "notes", attributeSet: attributeSet)
29+
}
30+
31+
}
32+
33+
extension CSSearchableIndex {
34+
// MARK: - Index Notes
35+
@objc
36+
func indexSpotlightItems(in context: NSManagedObjectContext) {
37+
guard Options.shared.indexNotesForSpotlight else {
38+
return
39+
}
40+
41+
context.perform {
42+
if let deleted = try? context.fetchObjects(for: "Note", withPredicate: NSPredicate(format: "deleted == YES")) as? [Note] {
43+
self.deleteSearchableNotes(deleted)
44+
}
45+
46+
if let notes = try? context.fetchObjects(for: "Note", withPredicate: NSPredicate(format: "deleted == NO")) as? [Note] {
47+
self.indexSearchableNotes(notes)
48+
}
49+
}
50+
}
51+
52+
@objc func indexSearchableNote(_ note: Note) {
53+
guard Options.shared.indexNotesForSpotlight else {
54+
return
55+
}
56+
57+
let item = CSSearchableItem(note: note)
58+
indexSearchableItems([item]) { error in
59+
if let error = error {
60+
NSLog("Couldn't index note in spotlight: \(error.localizedDescription)")
61+
}
62+
}
63+
}
64+
65+
@objc func indexSearchableNotes(_ notes: [Note]) {
66+
guard Options.shared.indexNotesForSpotlight else {
67+
return
68+
}
69+
70+
let items = notes.map {
71+
return CSSearchableItem(note: $0)
72+
}
73+
74+
indexSearchableItems(items) { error in
75+
if let error = error {
76+
NSLog("Couldn't index notes in spotlight: \(error.localizedDescription)")
77+
}
78+
}
79+
}
80+
81+
@objc func deleteSearchableNote(_ note: Note) {
82+
deleteSearchableNotes([note])
83+
}
84+
85+
@objc func deleteSearchableNotes(_ notes: [Note]) {
86+
let ids = notes.compactMap({ $0.simperiumKey })
87+
88+
deleteSearchableItems(withIdentifiers: ids) { error in
89+
if let error = error {
90+
NSLog("Couldn't delete notes from spotlight index: \(error.localizedDescription)")
91+
}
92+
}
93+
}
94+
95+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// NSManagedObjectContext+Simplenote.swift
3+
// Simplenote
4+
//
5+
// Created by Charlie Scheer on 4/11/24.
6+
// Copyright © 2024 Simperium. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import CoreData
11+
12+
extension NSManagedObjectContext {
13+
@objc(fetchObjectsForEntityName: withPredicate: error:)
14+
func fetchObjects(for entityName: String, withPredicate predicate: NSPredicate) throws -> Array<NSFetchRequestResult> {
15+
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
16+
let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: self)
17+
18+
fetchRequest.entity = entityDescription
19+
20+
return try fetch(fetchRequest)
21+
}
22+
}

Simplenote/NoteEditorViewController.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ - (void)save
148148
[self.saveTimer invalidate];
149149
self.saveTimer = nil;
150150

151+
[[CSSearchableIndex defaultSearchableIndex] indexSearchableNote:self.note];
152+
151153
if (editorHasFocus) {
152154
[[NSApp keyWindow] makeFirstResponder:self.noteEditor];
153155

@@ -404,6 +406,7 @@ - (IBAction)deleteAction:(id)sender
404406
[SPTracker trackEditorNoteDeleted];
405407
noteToDelete.deleted = YES;
406408
[self.noteActionsDelegate editorController:self deletedNoteWithSimperiumKey:noteToDelete.simperiumKey];
409+
[[CSSearchableIndex defaultSearchableIndex] deleteSearchableNote:noteToDelete];
407410
}
408411

409412
[self save];

Simplenote/NoteListViewController.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import SimplenoteSearch
3+
import CoreSpotlight
34

45
// MARK: - NotesControllerDelegate
56
//
@@ -813,6 +814,7 @@ extension NoteListViewController {
813814
for note in selectedNotes {
814815
SPTracker.trackListNoteDeleted()
815816
note.deleted = true
817+
CSSearchableIndex.default().deleteSearchableNote(note)
816818
}
817819

818820
simperium.save()
@@ -867,6 +869,7 @@ extension NoteListViewController {
867869
note.deleted = false
868870
simperium.save()
869871

872+
CSSearchableIndex.default().indexSearchableNote(note)
870873
SPTracker.trackListNoteRestored()
871874
}
872875

Simplenote/Options.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,19 @@ extension Options {
140140
NotificationCenter.default.post(name: .FontSizeDidChange, object: nil)
141141
}
142142
}
143+
144+
/// Index notes for spotlight
145+
///
146+
@objc
147+
var indexNotesForSpotlight: Bool {
148+
get {
149+
defaults.bool(forKey: .indexNotesForSpotlight)
150+
}
151+
152+
set {
153+
defaults.set(newValue, forKey: .indexNotesForSpotlight)
154+
}
155+
}
143156
}
144157

145158
// MARK: - Migrations

0 commit comments

Comments
 (0)