Skip to content

Commit

Permalink
Merge pull request #1140 from Automattic/charlie/1000/add-spotlight-s…
Browse files Browse the repository at this point in the history
…upport

Add spotlight support for Simplenote
  • Loading branch information
charliescheer authored Apr 17, 2024
2 parents b2232e9 + fd0a4f6 commit 1ff5566
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 14 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
2.21
-----
- Added spotlight search support for notes
- Added multiple window support for editing notes

2.20
Expand Down
12 changes: 12 additions & 0 deletions Simplenote.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -462,10 +462,14 @@
BA2C65CF26FE996A00FA84E1 /* NSButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */; };
BA4C6D16264CA8C000B723A7 /* SignupRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6D15264CA8C000B723A7 /* SignupRemoteTests.swift */; };
BA4C6D18264CAAF800B723A7 /* URLRequest+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6D17264CAAF800B723A7 /* URLRequest+Simplenote.swift */; };
BA52005B2BC878F1003F1B75 /* CSSearchable+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */; };
BA52005D2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */; };
BA553F0827065E20007737E9 /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA553F0727065E20007737E9 /* FontSettings.swift */; };
BA553F0927065E20007737E9 /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA553F0727065E20007737E9 /* FontSettings.swift */; };
BA5F020526BB57F000581E92 /* NSAlert+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */; };
BA5F020626BB57F000581E92 /* NSAlert+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */; };
BA71EC242BC88FD000F42CB1 /* CSSearchable+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */; };
BA71EC252BC88FFC00F42CB1 /* NSManagedObjectContext+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */; };
BA78AF6F2B5B2BBA00DCF896 /* AutomatticTracks in Frameworks */ = {isa = PBXBuildFile; productRef = BA78AF6E2B5B2BBA00DCF896 /* AutomatticTracks */; };
BA78AF712B5B2BC300DCF896 /* AutomatticTracks in Frameworks */ = {isa = PBXBuildFile; productRef = BA78AF702B5B2BC300DCF896 /* AutomatticTracks */; };
BA938CEC26ACFF4A00BE5A1D /* Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA938CEB26ACFF4A00BE5A1D /* Remote.swift */; };
Expand Down Expand Up @@ -872,6 +876,8 @@
BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSButton+Extensions.swift"; sourceTree = "<group>"; };
BA4C6D15264CA8C000B723A7 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.swift; sourceTree = "<group>"; };
BA4C6D17264CAAF800B723A7 /* URLRequest+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+Simplenote.swift"; sourceTree = "<group>"; };
BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSSearchable+Helpers.swift"; sourceTree = "<group>"; };
BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Simplenote.swift"; sourceTree = "<group>"; };
BA553F0727065E20007737E9 /* FontSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSettings.swift; sourceTree = "<group>"; };
BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAlert+Simplenote.swift"; sourceTree = "<group>"; };
BA938CEB26ACFF4A00BE5A1D /* Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Remote.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1257,6 +1263,7 @@
children = (
B5D21CB624881EF600D57A34 /* Array+Simplenote.swift */,
B57CB87D244DED2300BA7969 /* Bundle+Simplenote.swift */,
BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */,
B53BF19B24ABDE7C00938C34 /* DateFormatter+Simplenote.swift */,
B5C620AA257ED4CF008359A9 /* NSAnimationContext+Simplenote.swift */,
B56FA79A2437D2E0002CB9FF /* NSAppearance+Simplenote.swift */,
Expand Down Expand Up @@ -1303,6 +1310,7 @@
BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */,
BAFB544F26CCA7F1006E037C /* NSProgressIndicator+Simplenote.swift */,
BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */,
BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */,
);
name = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -2130,6 +2138,7 @@
BA2C65CB26FE996100FA84E1 /* NSButton+Extensions.swift in Sources */,
B58117E225B9E5D200927E0C /* AccountVerificationController.swift in Sources */,
B5009937242130F70037A431 /* UnicodeScalar+Simplenote.swift in Sources */,
BA52005B2BC878F1003F1B75 /* CSSearchable+Helpers.swift in Sources */,
B5985AD5242950B40044EDE9 /* NSColor+Simplenote.swift in Sources */,
B5C7DD3D243E1F1900BEE354 /* VersionsViewController.swift in Sources */,
B58BBD3D2523FF160025135F /* PopoverWindow.swift in Sources */,
Expand Down Expand Up @@ -2166,6 +2175,7 @@
B59EA98124AA5EFA008ABE4B /* NoteMetrics.swift in Sources */,
B5EDF338258A8F1B0066D91D /* TagListFilter.swift in Sources */,
375D293621E033D1007AB25A /* document.c in Sources */,
BA52005D2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift in Sources */,
B5609AEC24EEE7200097777A /* SPBucket+Simplenote.swift in Sources */,
B56FA7902437C672002CB9FF /* ColorStudio.swift in Sources */,
B5177CD025EEEEFB00A8D834 /* NSWindow+Transitions.swift in Sources */,
Expand Down Expand Up @@ -2324,6 +2334,7 @@
BA2C65CF26FE996A00FA84E1 /* NSButton+Extensions.swift in Sources */,
375D294121E033D1007AB25A /* html_smartypants.c in Sources */,
B5E061782450AEDA0076111A /* ToolbarView.swift in Sources */,
BA71EC242BC88FD000F42CB1 /* CSSearchable+Helpers.swift in Sources */,
B502C1DE25BA2EB700145D6C /* AccountRemote.swift in Sources */,
F998F3EC22853C49008C2B59 /* CrashLogging.swift in Sources */,
B5919365245A7AD300A70C0C /* NSScreen+Simplenote.swift in Sources */,
Expand Down Expand Up @@ -2360,6 +2371,7 @@
B5F807CD2481982B0048CBD7 /* Note+Simplenote.swift in Sources */,
A6C1E21525E010140076ADF7 /* SPApplication.swift in Sources */,
466FFEB417CC10A800399652 /* DateTransformer.m in Sources */,
BA71EC252BC88FFC00F42CB1 /* NSManagedObjectContext+Simplenote.swift in Sources */,
B5132FA923C4B9760065DD80 /* NSTextStorage+Simplenote.swift in Sources */,
375D293721E033D1007AB25A /* document.c in Sources */,
376EE3EC202B748E00E3812E /* SPAboutTextField.swift in Sources */,
Expand Down
95 changes: 95 additions & 0 deletions Simplenote/CSSearchable+Helpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// CSSearchableItem+Helpers.swift
// Simplenote
//
// Created by Michal Kosmowski on 25/11/2016.
// Copyright © 2016 Automattic. All rights reserved.
//

import Foundation
import CoreSpotlight
import UniformTypeIdentifiers

extension CSSearchableItemAttributeSet {

convenience init(note: Note) {
self.init(contentType: UTType.data)
note.ensurePreviewStringsAreAvailable()
title = note.titlePreview
contentDescription = note.bodyPreview
}

}

extension CSSearchableItem {

convenience init(note: Note) {
let attributeSet = CSSearchableItemAttributeSet(note: note)
self.init(uniqueIdentifier: note.simperiumKey, domainIdentifier: "notes", attributeSet: attributeSet)
}

}

extension CSSearchableIndex {
// MARK: - Index Notes
@objc
func indexSpotlightItems(in context: NSManagedObjectContext) {
guard Options.shared.indexNotesForSpotlight else {
return
}

context.perform {
if let deleted = try? context.fetchObjects(for: "Note", withPredicate: NSPredicate(format: "deleted == YES")) as? [Note] {
self.deleteSearchableNotes(deleted)
}

if let notes = try? context.fetchObjects(for: "Note", withPredicate: NSPredicate(format: "deleted == NO")) as? [Note] {
self.indexSearchableNotes(notes)
}
}
}

@objc func indexSearchableNote(_ note: Note) {
guard Options.shared.indexNotesForSpotlight else {
return
}

let item = CSSearchableItem(note: note)
indexSearchableItems([item]) { error in
if let error = error {
NSLog("Couldn't index note in spotlight: \(error.localizedDescription)")
}
}
}

@objc func indexSearchableNotes(_ notes: [Note]) {
guard Options.shared.indexNotesForSpotlight else {
return
}

let items = notes.map {
return CSSearchableItem(note: $0)
}

indexSearchableItems(items) { error in
if let error = error {
NSLog("Couldn't index notes in spotlight: \(error.localizedDescription)")
}
}
}

@objc func deleteSearchableNote(_ note: Note) {
deleteSearchableNotes([note])
}

@objc func deleteSearchableNotes(_ notes: [Note]) {
let ids = notes.compactMap({ $0.simperiumKey })

deleteSearchableItems(withIdentifiers: ids) { error in
if let error = error {
NSLog("Couldn't delete notes from spotlight index: \(error.localizedDescription)")
}
}
}

}
22 changes: 22 additions & 0 deletions Simplenote/NSManagedObjectContext+Simplenote.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// NSManagedObjectContext+Simplenote.swift
// Simplenote
//
// Created by Charlie Scheer on 4/11/24.
// Copyright © 2024 Simperium. All rights reserved.
//

import Foundation
import CoreData

extension NSManagedObjectContext {
@objc(fetchObjectsForEntityName: withPredicate: error:)
func fetchObjects(for entityName: String, withPredicate predicate: NSPredicate) throws -> Array<NSFetchRequestResult> {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: self)

fetchRequest.entity = entityDescription

return try fetch(fetchRequest)
}
}
3 changes: 3 additions & 0 deletions Simplenote/NoteEditorViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ - (void)save
[self.saveTimer invalidate];
self.saveTimer = nil;

[[CSSearchableIndex defaultSearchableIndex] indexSearchableNote:self.note];

if (editorHasFocus) {
[[NSApp keyWindow] makeFirstResponder:self.noteEditor];

Expand Down Expand Up @@ -404,6 +406,7 @@ - (IBAction)deleteAction:(id)sender
[SPTracker trackEditorNoteDeleted];
noteToDelete.deleted = YES;
[self.noteActionsDelegate editorController:self deletedNoteWithSimperiumKey:noteToDelete.simperiumKey];
[[CSSearchableIndex defaultSearchableIndex] deleteSearchableNote:noteToDelete];
}

[self save];
Expand Down
3 changes: 3 additions & 0 deletions Simplenote/NoteListViewController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import SimplenoteSearch
import CoreSpotlight

// MARK: - NotesControllerDelegate
//
Expand Down Expand Up @@ -813,6 +814,7 @@ extension NoteListViewController {
for note in selectedNotes {
SPTracker.trackListNoteDeleted()
note.deleted = true
CSSearchableIndex.default().deleteSearchableNote(note)
}

simperium.save()
Expand Down Expand Up @@ -867,6 +869,7 @@ extension NoteListViewController {
note.deleted = false
simperium.save()

CSSearchableIndex.default().indexSearchableNote(note)
SPTracker.trackListNoteRestored()
}

Expand Down
13 changes: 13 additions & 0 deletions Simplenote/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ extension Options {
NotificationCenter.default.post(name: .FontSizeDidChange, object: nil)
}
}

/// Index notes for spotlight
///
@objc
var indexNotesForSpotlight: Bool {
get {
defaults.bool(forKey: .indexNotesForSpotlight)
}

set {
defaults.set(newValue, forKey: .indexNotesForSpotlight)
}
}
}

// MARK: - Migrations
Expand Down
Loading

0 comments on commit 1ff5566

Please sign in to comment.