├── README.md ├── Shared ├── NSPredicate+helper.swift ├── Persistence.swift ├── SlipboxApp.xcdatamodeld │ ├── .xccurrentversion │ └── SlipboxApp.xcdatamodel │ │ └── contents ├── UnitTestHelpers.swift └── model │ ├── Folder+handleDrop.swift │ ├── Folder+helper.swift │ ├── Keyword+helper.swift │ ├── NSData+text.swift │ ├── Note+handelDrop.swift │ ├── Note+helper.swift │ └── Status.swift ├── SlipboxApp.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── karinprater.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── SlipboxApp ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── background1.colorset │ │ └── Contents.json │ ├── folder.imageset │ │ ├── Contents.json │ │ └── folder.pdf │ ├── folderFull.imageset │ │ ├── Contents.json │ │ └── folderFill.pdf │ ├── hashtag.imageset │ │ ├── Contents.json │ │ └── hashtag.pdf │ ├── hashtagFull.imageset │ │ ├── Contents.json │ │ └── hashtagFull.pdf │ ├── note.imageset │ │ ├── Contents.json │ │ └── note.pdf │ ├── noteFull.imageset │ │ ├── Contents.json │ │ └── noteFull.pdf │ ├── selectedColor.colorset │ │ └── Contents.json │ └── unselectedColor.colorset │ │ └── Contents.json ├── Folder │ ├── DropStatus.swift │ ├── FolderEditorView.swift │ ├── FolderListView.swift │ ├── FolderRow.swift │ └── RecursiveFolderView.swift ├── Info.plist ├── Keyword │ ├── KeywordEditorView.swift │ ├── KeywordListView.swift │ └── KeywordRow.swift ├── Notes │ ├── NoteListView.swift │ ├── NoteRow.swift │ ├── NoteSearchView.swift │ ├── NoteView.swift │ ├── OptionalImage.swift │ ├── TextViewWrapper.swift │ └── helper for nsitem conversion.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SlipboxApp.entitlements ├── app │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Main.storyboard │ └── NSNotification.Name + helper.swift ├── drag and drop │ ├── DataType.swift │ ├── DataTypeDragItem.swift │ └── URL+helper.swift ├── main window │ ├── AppToolbar.swift │ ├── ContentView.swift │ ├── NavigationStateManager.swift │ └── WindowContentView.swift └── prefences │ ├── PreferenceContentView.swift │ └── PreferenceWindow.swift ├── SlipboxAppTests ├── FolderTests.swift ├── Info.plist ├── NotesTests.swift └── SlipboxAppTests.swift ├── SlipboxAppUITests ├── Info.plist └── SlipboxAppUITests.swift └── SlipboxPad ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── selectedColor.colorset │ └── Contents.json └── unselectedColor.colorset │ └── Contents.json ├── ContentView.swift ├── Info.plist ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── SlipboxPad.entitlements ├── SlipboxPad.xcdatamodeld ├── .xccurrentversion └── SlipboxPad.xcdatamodel │ └── contents └── SlipboxPadApp.swift /README.md: -------------------------------------------------------------------------------- 1 | # Slipbox 2 | 3 | To setup this project you need to have a paid developer account, because the project is using Core Data with iCloud sync. Go to project settings under target / signing & capabilities and add team. 4 | You also will need to create a iCloud container under the iCloud capabilities. 5 | 6 | 7 | ## Zettelkasten 8 | The app is a note taking system invented by a German sociologist Lukas Luhmann. You can find more information on the Zettelkasten here: 9 | https://zettelkasten.de 10 | 11 | Screenshot 2020-12-21 at 15 57 45 12 | 13 | When you open the application, you get one window with a toolbar and 4 columns. You can create more windows under the menu Window / new Window. 14 | The app uses a AppDelegate. So we don't get support for the .toolbar modifiers, which work with windowgroup. The toolbar is coded with a NSToolbar (in the project under SlipboxApp / main window). There are 3 toggles in the toolbar to show/hide the keyword column, folder column and notes column. 15 | 16 | You can create new keywords, folder and notes. All elements support drag and drop, so you can drag a keyword to a note to add it. You can drag a note to another note to create a link between these two notes. 17 | 18 | Linked notes appear in the lower part of the note editor area. You can right click on a link and have the option to open the link in a new note. 19 | 20 | Screenshot 2020-12-22 at 19 40 45 21 | 22 | The project organization has a shared folder, which has mainly the Core Data model and model extension files. The shared folder is added to the macOS and iOS target. I did not add much code for iOS yet, but it will run and show a basic NavigationView. 23 | 24 | You can see the use of FetchRequest in the Note Group in the NoteListView. The initializer uses a NSPredicate to find all the notes of this folder. 25 | A more advanced fetch is in the NavigationStateManager. 26 | 27 | Screenshot 2020-12-22 at 19 45 02 28 | 29 | 30 | -------------------------------------------------------------------------------- /Shared/NSPredicate+helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPredicate+helper.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | extension NSPredicate { 12 | static var all = NSPredicate(format: "TRUEPREDICATE") 13 | static var none = NSPredicate(format: "FALSEPREDICATE") 14 | } 15 | -------------------------------------------------------------------------------- /Shared/Persistence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Persistence.swift 3 | // SlipboxPad 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import CoreData 9 | 10 | struct PersistenceController { 11 | 12 | static let shared = PersistenceController() 13 | 14 | 15 | 16 | let container: NSPersistentCloudKitContainer 17 | 18 | init(inMemory: Bool = false) { 19 | 20 | container = NSPersistentCloudKitContainer(name: "SlipboxApp") 21 | 22 | if inMemory { 23 | container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") 24 | } 25 | 26 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 27 | if let error = error as NSError? { 28 | 29 | fatalError("Unresolved error \(error), \(error.userInfo)") 30 | } 31 | }) 32 | 33 | //TODO: - mention 34 | if !inMemory { 35 | container.viewContext.automaticallyMergesChangesFromParent = true 36 | container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 37 | } 38 | } 39 | 40 | 41 | 42 | //MARK: - Preview helper 43 | static var preview: PersistenceController = { 44 | let result = PersistenceController(inMemory: true) 45 | let viewContext = result.container.viewContext 46 | 47 | 48 | //UnitTestHelpers.deletesAllNotes(container: result.container) 49 | 50 | for i in 0..<2 { 51 | let newItem = Note(title: "\(i) note", context: viewContext) 52 | newItem.bodyText = Note.defaultText 53 | let newFolder = Folder(name: "\(i) folder", context: viewContext) 54 | let newkeyword = Keyword(name: "\(i) keyword", context: viewContext) 55 | } 56 | Folder.nestedFolder(context: viewContext) 57 | 58 | do { 59 | try viewContext.save() 60 | } catch { 61 | let nsError = error as NSError 62 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)") 63 | } 64 | 65 | return result 66 | }() 67 | 68 | //MARK: - unit tests 69 | static var empty: PersistenceController = { 70 | return PersistenceController(inMemory: true) 71 | }() 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Shared/SlipboxApp.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | SlipboxApp.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Shared/SlipboxApp.xcdatamodeld/SlipboxApp.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Shared/UnitTestHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnitTestHelpers.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 03.12.20. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | struct UnitTestHelpers { 12 | 13 | static func deletesAllNotes(context: NSManagedObjectContext) { 14 | let request = Note.fetch(.all) 15 | if let result = try? context.fetch(request) { 16 | for r in result { 17 | context.delete(r) 18 | }} 19 | } 20 | 21 | static func deletesAllFolders(context: NSManagedObjectContext) { 22 | let request = Folder.fetch(.all) 23 | if let result = try? context.fetch(request) { 24 | for r in result { 25 | context.delete(r) 26 | }} 27 | } 28 | 29 | static func deleteAllkeywords(context: NSManagedObjectContext) { 30 | let rquest = Keyword.fetch(.all) 31 | if let result = try? context.fetch(rquest) { 32 | for r in result { 33 | context.delete(r) 34 | } 35 | } 36 | 37 | } 38 | 39 | static func deletesAll(container: NSPersistentCloudKitContainer) { 40 | 41 | //UnitTestHelpers.deletesAllNotes(context: container.viewContext) 42 | UnitTestHelpers.deletesAllFolders(context: container.viewContext) 43 | UnitTestHelpers.deleteAllkeywords(context: container.viewContext) 44 | 45 | } 46 | 47 | 48 | static func deleteBatchRequest(entity: String, container: NSPersistentCloudKitContainer) { 49 | 50 | let context = container.viewContext 51 | 52 | let request = NSFetchRequest(entityName: entity) 53 | 54 | let deleteRequest = NSBatchDeleteRequest(fetchRequest: request) 55 | 56 | do { try container.persistentStoreCoordinator.execute(deleteRequest, with: context) 57 | }catch { 58 | print("error \(error.localizedDescription)") 59 | } 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Shared/model/Folder+handleDrop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Folder+handleDrop.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 19.12.20. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | 12 | extension Folder { 13 | 14 | //NEW add the right index 15 | func add(subfolder: Folder, at order: Int32? = nil) { 16 | subfolder.parent = nil 17 | guard let order = order else { 18 | let newOrder = self.children.sorted().last?.order ?? -1 19 | subfolder.order = newOrder + 1 20 | subfolder.parent = self 21 | return 22 | } 23 | 24 | let over = self.children.filter { $0.order >= order } 25 | 26 | for folder in over { 27 | folder.order += 1 28 | } 29 | subfolder.parent = self 30 | subfolder.order = order 31 | 32 | for (i, f) in self.children.sorted().enumerated() { 33 | f.order = Int32(i) 34 | } 35 | } 36 | 37 | 38 | func handleMovedDrop(with providers: [NSItemProvider], dropStatus: DropStatus) -> Bool { 39 | 40 | guard let context = self.managedObjectContext else { 41 | return false 42 | } 43 | let found = providers.loadObjects(ofType: DataTypeDragItem.self) { (item) in 44 | switch dropStatus { 45 | case .note: 46 | if let droppedNote = Note.fetch(id: item.id, in: context) { 47 | self.add(note: droppedNote) 48 | } 49 | 50 | case .folderAfter, .folderBefore, .subfolder: 51 | if let droppedFolder = Folder.fetch(id: item.id, in: context), 52 | !self.allParentFolders().contains(droppedFolder) { 53 | //don't add parent as child -> loops 54 | 55 | let index: Int32 = (dropStatus == .folderBefore) ? 0 : 1 56 | //folderAfter should have order + 1 57 | 58 | if dropStatus == .folderBefore || dropStatus == .folderAfter, let parent = self.parent { 59 | //if we have a parent folder who will do the work 60 | parent.add(subfolder: droppedFolder, at: self.order + index) 61 | 62 | }else if dropStatus == .folderBefore || dropStatus == .folderAfter { 63 | //we don't have a parent folder, so we need to sort the folders at the top level 64 | droppedFolder.parent = nil 65 | 66 | let requeset = Folder.topFolderFetch() 67 | var result = ( try? context.fetch(requeset)) ?? [] 68 | let index = result.firstIndex(of: droppedFolder) ?? 0 69 | result.remove(at: index) 70 | 71 | var runningIndex = Int32(0) 72 | for f in result { 73 | if f == self && dropStatus == .folderAfter { 74 | droppedFolder.order = runningIndex + 1 75 | f.order = runningIndex 76 | runningIndex += 1 77 | }else if f == self && dropStatus == .folderBefore { 78 | droppedFolder.order = runningIndex 79 | f.order = runningIndex + 1 80 | runningIndex += 1 81 | }else { 82 | f.order = runningIndex 83 | } 84 | runningIndex += 1 85 | 86 | } 87 | 88 | }else if dropStatus == .subfolder { 89 | self.add(subfolder: droppedFolder, at: 0) 90 | } 91 | 92 | } 93 | default: 94 | return 95 | } 96 | } 97 | return found 98 | } 99 | 100 | 101 | func allParentFolders() -> [Folder] { 102 | //can get all parent folders 103 | var parents = [Folder]() 104 | var refFolder = self.parent 105 | while refFolder != nil { 106 | parents.append(refFolder!) 107 | refFolder = refFolder?.parent 108 | } 109 | return parents.reversed() 110 | } 111 | 112 | func fullFolderPath() -> [Folder] { 113 | var parents = allParentFolders() 114 | parents.insert(self, at: 0) 115 | //order: first folder is top folder, last folder is folder itself 116 | //can use to show full folder paths like parents[0].name / parents[1].name / parents[2].name / self.name 117 | return parents.reversed() 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /Shared/model/Folder+helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Folder+helper.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 06.12.20. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | extension Folder { 12 | 13 | //TODO: init 14 | 15 | convenience init(name: String, context: NSManagedObjectContext) { 16 | self.init(context: context) 17 | self.name = name 18 | 19 | let requeset = Folder.topFolderFetch() 20 | let result = try? context.fetch(requeset) 21 | let maxFolder = result?.max(by: { $0.order < $1.order }) 22 | self.order = (maxFolder?.order ?? 0) + 1 23 | } 24 | 25 | override public func awakeFromInsert() { 26 | setPrimitiveValue(Date(), forKey: NoteProperties.creationDate) 27 | setPrimitiveValue(UUID(), forKey: NoteProperties.uuid) 28 | } 29 | 30 | var uuid: UUID { 31 | get { uuid_ ?? UUID() } 32 | set { uuid_ = newValue } 33 | } 34 | 35 | var creationDate: Date { 36 | get { return creationDate_ ?? Date() } 37 | set { creationDate_ = newValue } 38 | } 39 | 40 | var name: String { 41 | get { return name_ ?? "" } 42 | set { name_ = newValue } 43 | } 44 | 45 | var notes: Set { 46 | get { notes_ as? Set ?? [] } 47 | // set { notes_ = newValue as NSSet } 48 | } 49 | 50 | var children: Set { 51 | get { children_ as? Set ?? [] } 52 | set { children_ = newValue as NSSet } 53 | } 54 | 55 | func add(note: Note, at index: Int32? = nil) { 56 | let oldNotes = self.notes.sorted() 57 | 58 | if let index = index { 59 | note.order = index + 1 60 | 61 | let changeNotes = oldNotes.filter { $0.order > index} 62 | for note in changeNotes { 63 | note.order += 1 64 | } 65 | 66 | } else { 67 | note.order = (oldNotes.last?.order ?? 0) + 1 68 | } 69 | note.folder = self 70 | // self.notes_?.adding(note) 71 | } 72 | 73 | //MARK: fetch request 74 | static func fetch(_ predicate: NSPredicate) -> NSFetchRequest { 75 | let request = NSFetchRequest(entityName: "Folder") 76 | request.sortDescriptors = [NSSortDescriptor(key: FolderProperties.creationDate, ascending: false)] 77 | 78 | request.predicate = predicate 79 | return request 80 | } 81 | 82 | static func topFolderFetch() -> NSFetchRequest { 83 | let request = NSFetchRequest(entityName: "Folder") 84 | request.sortDescriptors = [NSSortDescriptor(key: FolderProperties.order, ascending: true)] 85 | 86 | let format = FolderProperties.parent + " = nil" 87 | request.predicate = NSPredicate(format: format) 88 | return request 89 | } 90 | 91 | static func fetch(id: String?, in context: NSManagedObjectContext) -> Folder? { 92 | guard let id = id, let uuid = UUID(uuidString: id) else { 93 | return nil 94 | } 95 | let request = Folder.fetch(NSPredicate(format: "%K == %@", FolderProperties.uuid, uuid as CVarArg)) 96 | if let folders = try? context.fetch(request), 97 | let folder = folders.first { 98 | return folder 99 | }else { 100 | return nil 101 | } 102 | } 103 | 104 | //MARK: - delete 105 | static func delete(_ folder: Folder) { 106 | if let context = folder.managedObjectContext { 107 | context.delete(folder) 108 | } 109 | } 110 | 111 | //MARK: - preview helpers 112 | static func nestedFolder(context: NSManagedObjectContext) -> Folder { 113 | let parent = Folder(name: "parent", context: context) 114 | let child1 = Folder(name: "child1", context: context) 115 | let child2 = Folder(name: "child2", context: context) 116 | let child3 = Folder(name: "child3", context: context) 117 | 118 | //child1.parent = parent 119 | //TODO: add children 120 | parent.add(subfolder: child1) 121 | parent.add(subfolder: child2) 122 | child2.add(subfolder: child3) 123 | return parent 124 | } 125 | } 126 | 127 | //MARK: - comparable 128 | extension Folder: Comparable { 129 | public static func < (lhs: Folder, rhs: Folder) -> Bool { 130 | lhs.order < rhs.order 131 | } 132 | 133 | } 134 | 135 | 136 | //MARK:- define my string properties 137 | 138 | struct FolderProperties { 139 | static let uuid = "uuid_" 140 | static let creationDate = "creationDate_" 141 | static let name = "name_" 142 | static let order = "order" 143 | 144 | static let notes = "notes_" 145 | static let parent = "parent" 146 | static let children = "children_" 147 | } 148 | -------------------------------------------------------------------------------- /Shared/model/Keyword+helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keyword+helper.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 11.12.20. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | extension Keyword { 12 | 13 | convenience init(name: String, context: NSManagedObjectContext) { 14 | self.init(context: context) 15 | self.name = name 16 | } 17 | 18 | override public func awakeFromInsert() { 19 | setPrimitiveValue(UUID(), forKey: NoteProperties.uuid) 20 | } 21 | 22 | var name: String { 23 | get { name_ ?? "" } 24 | set { name_ = newValue } 25 | } 26 | 27 | var uuid: UUID { 28 | get { uuid_ ?? UUID() } 29 | set { uuid_ = newValue } 30 | } 31 | 32 | var notes: Set { 33 | get { notes_ as? Set ?? [] } 34 | set { notes_ = newValue as NSSet } 35 | } 36 | 37 | //MARK: - fetch request 38 | 39 | static func fetch(_ predicate: NSPredicate) -> NSFetchRequest { 40 | let request = NSFetchRequest(entityName: "Keyword") 41 | request.sortDescriptors = [NSSortDescriptor(key: KeywordProperties.name, ascending: true), NSSortDescriptor(key: KeywordProperties.uuid, ascending: true)] 42 | request.predicate = predicate 43 | return request 44 | } 45 | 46 | static func fetch(id: UUID, context: NSManagedObjectContext) -> Keyword? { 47 | let request = Keyword.fetch(NSPredicate(format: "%K == %@", KeywordProperties.uuid, id as CVarArg)) 48 | if let keys = try? context.fetch(request), 49 | let keyword = keys.first { 50 | return keyword 51 | }else { 52 | return nil 53 | } 54 | } 55 | 56 | //TODO: delete 57 | 58 | static func delete(keyword: Keyword) { 59 | if let context = keyword.managedObjectContext { 60 | try? context.delete(keyword) 61 | } 62 | } 63 | } 64 | 65 | //MARK: - sort notes for showing in list 66 | extension Keyword: Comparable { 67 | public static func < (lhs: Keyword, rhs: Keyword) -> Bool { 68 | lhs.name < rhs.name 69 | } 70 | } 71 | 72 | //MARK: - string properties 73 | 74 | struct KeywordProperties { 75 | static let name = "name_" 76 | static let uuid = "uuid_" 77 | 78 | static let notes = "notes_" 79 | } 80 | -------------------------------------------------------------------------------- /Shared/model/NSData+text.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+text.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 05.12.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Data { 11 | 12 | func toAttributedString() -> NSAttributedString { 13 | 14 | let options: [NSAttributedString.DocumentReadingOptionKey : Any] = [.documentType: NSAttributedString.DocumentType.rtfd, .characterEncoding: String.Encoding.utf8] 15 | 16 | let result = try? NSAttributedString(data: self, options: options, documentAttributes: nil) 17 | 18 | return result ?? NSAttributedString(string: "") 19 | } 20 | 21 | } 22 | 23 | extension NSAttributedString { 24 | 25 | func toData() -> Data? { 26 | 27 | let options: [NSAttributedString.DocumentAttributeKey : Any] = [.documentType: NSAttributedString.DocumentType.rtfd, .characterEncoding: String.Encoding.utf8] 28 | 29 | let range = NSRange(location: 0, length: length) 30 | let result = try? data(from: range, documentAttributes: options) 31 | 32 | return result 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Shared/model/Note+handelDrop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Note+handelDrop.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 16.12.20. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | extension Note { 12 | 13 | func handleMove(providers: [NSItemProvider]) -> Bool { 14 | let found = providers.loadObjects(ofType: DataTypeDragItem.self) { (item) in 15 | if let context = self.managedObjectContext , 16 | let dropedNote = Note.fetch(id: item.id, in: context), 17 | self != dropedNote { 18 | dropedNote.folder = nil 19 | self.folder?.add(note: dropedNote, at: self.order - 1) 20 | } 21 | } 22 | return found 23 | } 24 | 25 | 26 | func handleDrop(providers: [NSItemProvider]) -> Bool { 27 | 28 | var found = providers.loadObjects(ofType: URL.self) { item in 29 | 30 | DispatchQueue.global(qos: .userInitiated).async { 31 | if let data = try? Data(contentsOf: item.absoluteURL) { 32 | DispatchQueue.main.async { 33 | self.img = data 34 | } 35 | } 36 | if let data = try? Data(contentsOf: item.imageURL) { 37 | DispatchQueue.main.async { 38 | self.img = data 39 | } 40 | } 41 | } 42 | 43 | } 44 | if !found { 45 | found = providers.loadObjects(ofType: DataTypeDragItem.self, using: { (item) in 46 | if let id = item.id, 47 | let uuid = UUID(uuidString: id), 48 | let type = item.type, 49 | let dataType = DataType.type(string: type), 50 | let context = self.managedObjectContext { 51 | 52 | switch dataType { 53 | case .note: 54 | 55 | if let dropedNote = Note.fetch(id: item.id, in: context), 56 | dropedNote != self { 57 | self.linkedNotes.insert(dropedNote) 58 | } 59 | 60 | case .keyword: 61 | if let dropedKeyword = Keyword.fetch(id: uuid, context: context) { 62 | self.keywords.insert(dropedKeyword) 63 | } 64 | default: 65 | return 66 | } 67 | 68 | } 69 | }) 70 | 71 | } 72 | 73 | return found 74 | } 75 | 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Shared/model/Note+helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Note+helper.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | extension Note { 12 | 13 | convenience init(title: String, context: NSManagedObjectContext) { 14 | self.init(context: context) 15 | self.title = title 16 | try? context.save() 17 | } 18 | 19 | override public func awakeFromInsert() { 20 | setPrimitiveValue(Date(), forKey: NoteProperties.creationDate) 21 | setPrimitiveValue(UUID(), forKey: NoteProperties.uuid) 22 | setPrimitiveValue(Status.draft.rawValue, forKey: NoteProperties.status) 23 | } 24 | 25 | var title: String { 26 | get { return title_ ?? "" } 27 | set { title_ = newValue } 28 | } 29 | 30 | var creationDate: Date { 31 | get { return creationDate_ ?? Date() } 32 | set { creationDate_ = newValue } 33 | } 34 | 35 | var bodyText: String { 36 | get { return bodyText_ ?? "" } 37 | set { bodyText_ = newValue } 38 | } 39 | 40 | var formattedText: NSAttributedString { 41 | get { 42 | if let data = formattedText_ { 43 | return data.toAttributedString() 44 | }else { 45 | return NSAttributedString(string: "") 46 | } 47 | } 48 | set { 49 | bodyText_ = newValue.string 50 | formattedText_ = newValue.toData() 51 | } 52 | } 53 | 54 | var uuid: UUID { 55 | get { uuid_ ?? UUID() } 56 | set { uuid_ = newValue } 57 | } 58 | 59 | var status: Status { 60 | get { return Status(rawValue: status_ ?? "") ?? Status.draft } 61 | set { status_ = newValue.rawValue } 62 | } 63 | 64 | var linkedNotes: Set { 65 | get { linkedNotes_ as? Set ?? [] } 66 | set { linkedNotes_ = newValue as NSSet } 67 | } 68 | 69 | var backlinks: Set { 70 | get { backlinks_ as? Set ?? [] } 71 | } 72 | 73 | var keywords: Set { 74 | get { keywords_ as? Set ?? [] } 75 | set { keywords_ = newValue as NSSet } 76 | } 77 | 78 | 79 | 80 | //MARK: - fetch helpers 81 | static func fetch(_ predicate: NSPredicate) -> NSFetchRequest { 82 | let request = NSFetchRequest(entityName: "Note") 83 | request.sortDescriptors = [NSSortDescriptor(key: NoteProperties.order, ascending: true)] 84 | 85 | request.predicate = predicate 86 | return request 87 | } 88 | 89 | static func fetch(id: String?, in context: NSManagedObjectContext) -> Note? { 90 | 91 | guard let id = id, let uuid = UUID(uuidString: id) else { 92 | return nil 93 | } 94 | 95 | let request = Note.fetch(NSPredicate(format: "%K == %@", NoteProperties.uuid, uuid as CVarArg)) 96 | if let notes = try? context.fetch(request), 97 | let note = notes.first { 98 | return note 99 | }else { 100 | return nil 101 | } 102 | } 103 | 104 | 105 | static func delete(note: Note) { 106 | //TODO 107 | if let context = note.managedObjectContext { 108 | context.delete(note) 109 | } 110 | } 111 | 112 | //MARK: - preview helper properties 113 | static let defaultText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 114 | 115 | static func defaultNote(context: NSManagedObjectContext)-> Note { 116 | let note = Note(title: "lorum ipsum", context: context) 117 | note.formattedText = NSAttributedString(string: Note.defaultText) 118 | 119 | let link1 = Note(title: "linked note 1", context: context) 120 | let link2 = Note(title: "linked note 2", context: context) 121 | note.linkedNotes.insert(link1) 122 | note.linkedNotes.insert(link2) 123 | 124 | let keyword1 = Keyword(name: "awesome", context: context) 125 | let keyword2 = Keyword(name: "stuff", context: context) 126 | 127 | note.keywords.insert(keyword1) 128 | note.keywords.insert(keyword2) 129 | 130 | return note 131 | } 132 | } 133 | 134 | //MARK: - sort notes for showing in list 135 | extension Note: Comparable { 136 | public static func < (lhs: Note, rhs: Note) -> Bool { 137 | lhs.order < rhs.order 138 | } 139 | } 140 | 141 | //MARK: - property names as strings 142 | 143 | struct NoteProperties { 144 | static let creationDate = "creationDate_" 145 | static let title = "title_" 146 | static let bodyText = "bodyText_" 147 | static let formattedText = "formattedText_" 148 | static let status = "status_" 149 | static let uuid = "uuid_" 150 | static let img = "img" 151 | 152 | static let folder = "folder" 153 | static let order = "order" 154 | 155 | static let linkedNotes = "linkedNotes_" 156 | static let backlinks = "backlinks_" 157 | 158 | static let keywords = "keywords_" 159 | } 160 | -------------------------------------------------------------------------------- /Shared/model/Status.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Status.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 05.12.20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Status: String, CaseIterable { 11 | case draft = "draft" 12 | case review = "review" 13 | case archived = "archived" 14 | } 15 | -------------------------------------------------------------------------------- /SlipboxApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 170F9013257BB46F00BCC9D6 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170F9012257BB46F00BCC9D6 /* Status.swift */; }; 11 | 170F9014257BB46F00BCC9D6 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170F9012257BB46F00BCC9D6 /* Status.swift */; }; 12 | 170F901A257BB83400BCC9D6 /* NSData+text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170F9019257BB83400BCC9D6 /* NSData+text.swift */; }; 13 | 170F901B257BB83400BCC9D6 /* NSData+text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170F9019257BB83400BCC9D6 /* NSData+text.swift */; }; 14 | 170F9021257BBE1800BCC9D6 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170F9020257BBE1800BCC9D6 /* TextViewWrapper.swift */; }; 15 | 172C1935258367F500A0B3FA /* DataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C1934258367F500A0B3FA /* DataType.swift */; }; 16 | 172C1936258367F500A0B3FA /* DataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C1934258367F500A0B3FA /* DataType.swift */; }; 17 | 172C193C258367FD00A0B3FA /* DataTypeDragItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C193B258367FD00A0B3FA /* DataTypeDragItem.swift */; }; 18 | 172C193D258367FD00A0B3FA /* DataTypeDragItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C193B258367FD00A0B3FA /* DataTypeDragItem.swift */; }; 19 | 172C19522583754700A0B3FA /* Keyword+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C19512583754700A0B3FA /* Keyword+helper.swift */; }; 20 | 172C19532583754700A0B3FA /* Keyword+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C19512583754700A0B3FA /* Keyword+helper.swift */; }; 21 | 172C195A25837A5700A0B3FA /* KeywordListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C195925837A5700A0B3FA /* KeywordListView.swift */; }; 22 | 172C196525837A7B00A0B3FA /* KeywordRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C196425837A7B00A0B3FA /* KeywordRow.swift */; }; 23 | 172C196C25837A8F00A0B3FA /* KeywordEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C196B25837A8F00A0B3FA /* KeywordEditorView.swift */; }; 24 | 173B92EC25861E12008BB5F0 /* NoteSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173B92EB25861E12008BB5F0 /* NoteSearchView.swift */; }; 25 | 173B92FB2586254F008BB5F0 /* PreferenceWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173B92FA2586254F008BB5F0 /* PreferenceWindow.swift */; }; 26 | 173B930525862567008BB5F0 /* PreferenceContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173B930425862567008BB5F0 /* PreferenceContentView.swift */; }; 27 | 175E4C312577880E001A155E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175E4C302577880E001A155E /* AppDelegate.swift */; }; 28 | 175E4C332577880E001A155E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175E4C322577880E001A155E /* ContentView.swift */; }; 29 | 175E4C362577880E001A155E /* SlipboxApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 175E4C342577880E001A155E /* SlipboxApp.xcdatamodeld */; }; 30 | 175E4C3825778811001A155E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 175E4C3725778811001A155E /* Assets.xcassets */; }; 31 | 175E4C3B25778811001A155E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 175E4C3A25778811001A155E /* Preview Assets.xcassets */; }; 32 | 175E4C3E25778811001A155E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 175E4C3C25778811001A155E /* Main.storyboard */; }; 33 | 175E4C4A25778811001A155E /* SlipboxAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175E4C4925778811001A155E /* SlipboxAppTests.swift */; }; 34 | 175E4C5525778811001A155E /* SlipboxAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175E4C5425778811001A155E /* SlipboxAppUITests.swift */; }; 35 | 1762780725921024008A2546 /* Folder+handleDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762780625921024008A2546 /* Folder+handleDrop.swift */; }; 36 | 1762780825921024008A2546 /* Folder+handleDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762780625921024008A2546 /* Folder+handleDrop.swift */; }; 37 | 1762780E2592105C008A2546 /* DropStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762780D2592105C008A2546 /* DropStatus.swift */; }; 38 | 1762780F2592105C008A2546 /* DropStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762780D2592105C008A2546 /* DropStatus.swift */; }; 39 | 176384E32578CCFE00911F6D /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E1B25778A3C002DD489 /* Persistence.swift */; }; 40 | 176384F42578DB1500911F6D /* NotesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176384F32578DB1500911F6D /* NotesTests.swift */; }; 41 | 176384FE2578DF8800911F6D /* UnitTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176384FD2578DF8800911F6D /* UnitTestHelpers.swift */; }; 42 | 176384FF2578DF8800911F6D /* UnitTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176384FD2578DF8800911F6D /* UnitTestHelpers.swift */; }; 43 | 1763850A2579247400911F6D /* NoteListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176385092579247400911F6D /* NoteListView.swift */; }; 44 | 1763850B2579247400911F6D /* NoteListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176385092579247400911F6D /* NoteListView.swift */; }; 45 | 176385152579264F00911F6D /* NoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176385142579264F00911F6D /* NoteView.swift */; }; 46 | 176385162579264F00911F6D /* NoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176385142579264F00911F6D /* NoteView.swift */; }; 47 | 176EDAE4257D015600F1C300 /* Folder+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDAE3257D015600F1C300 /* Folder+helper.swift */; }; 48 | 176EDAE5257D015600F1C300 /* Folder+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDAE3257D015600F1C300 /* Folder+helper.swift */; }; 49 | 176EDAEB257D020900F1C300 /* FolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDAEA257D020900F1C300 /* FolderTests.swift */; }; 50 | 176EDAF2257D132D00F1C300 /* FolderListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDAF1257D132D00F1C300 /* FolderListView.swift */; }; 51 | 176EDAF3257D132D00F1C300 /* FolderListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDAF1257D132D00F1C300 /* FolderListView.swift */; }; 52 | 176EDAFD257D1FAE00F1C300 /* FolderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDAFC257D1FAE00F1C300 /* FolderRow.swift */; }; 53 | 176EDAFE257D1FAE00F1C300 /* FolderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDAFC257D1FAE00F1C300 /* FolderRow.swift */; }; 54 | 176EDB08257D20AD00F1C300 /* FolderEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDB07257D20AD00F1C300 /* FolderEditorView.swift */; }; 55 | 176EDB09257D20AD00F1C300 /* FolderEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176EDB07257D20AD00F1C300 /* FolderEditorView.swift */; }; 56 | 17988E0B25778926002DD489 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17988E0A25778926002DD489 /* CloudKit.framework */; }; 57 | 17988E1325778A3C002DD489 /* SlipboxPadApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E1225778A3C002DD489 /* SlipboxPadApp.swift */; }; 58 | 17988E1525778A3C002DD489 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E1425778A3C002DD489 /* ContentView.swift */; }; 59 | 17988E1725778A3C002DD489 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17988E1625778A3C002DD489 /* Assets.xcassets */; }; 60 | 17988E1A25778A3C002DD489 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17988E1925778A3C002DD489 /* Preview Assets.xcassets */; }; 61 | 17988E1C25778A3C002DD489 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E1B25778A3C002DD489 /* Persistence.swift */; }; 62 | 17988E2A25778A64002DD489 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17988E2925778A64002DD489 /* CloudKit.framework */; }; 63 | 17988E3025778AF6002DD489 /* SlipboxApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 175E4C342577880E001A155E /* SlipboxApp.xcdatamodeld */; }; 64 | 17988E3B25778F76002DD489 /* Note+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E3A25778F76002DD489 /* Note+helper.swift */; }; 65 | 17988E4525779145002DD489 /* NSPredicate+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E4425779145002DD489 /* NSPredicate+helper.swift */; }; 66 | 17988E4A257791C0002DD489 /* NSPredicate+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E4425779145002DD489 /* NSPredicate+helper.swift */; }; 67 | 17988E4F25779576002DD489 /* Note+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17988E3A25778F76002DD489 /* Note+helper.swift */; }; 68 | 179B12E52584B77E0026CF48 /* RecursiveFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17CD4C36257E332B00C9FC85 /* RecursiveFolderView.swift */; }; 69 | 179B12EA2584B7940026CF48 /* NoteRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17EFEC41257B9383005BC6E0 /* NoteRow.swift */; }; 70 | 179B12F82584B7C00026CF48 /* helper for nsitem conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A8F3B4257CE8A5004F34D2 /* helper for nsitem conversion.swift */; }; 71 | 179B12FE2584CCD10026CF48 /* WindowContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179B12FD2584CCD10026CF48 /* WindowContentView.swift */; }; 72 | 17A8F3B5257CE8A5004F34D2 /* helper for nsitem conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A8F3B4257CE8A5004F34D2 /* helper for nsitem conversion.swift */; }; 73 | 17C951252584D68300C9396F /* NSNotification.Name + helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C951242584D68300C9396F /* NSNotification.Name + helper.swift */; }; 74 | 17CD4C37257E332B00C9FC85 /* RecursiveFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17CD4C36257E332B00C9FC85 /* RecursiveFolderView.swift */; }; 75 | 17CD4C3D257E371F00C9FC85 /* NavigationStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17CD4C3C257E371F00C9FC85 /* NavigationStateManager.swift */; }; 76 | 17DC673A258A83C900F5FDA2 /* Note+handelDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC6739258A83C900F5FDA2 /* Note+handelDrop.swift */; }; 77 | 17DC673B258A83C900F5FDA2 /* Note+handelDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC6739258A83C900F5FDA2 /* Note+handelDrop.swift */; }; 78 | 17DC6741258A842D00F5FDA2 /* URL+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC6740258A842C00F5FDA2 /* URL+helper.swift */; }; 79 | 17DC6742258A842D00F5FDA2 /* URL+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC6740258A842C00F5FDA2 /* URL+helper.swift */; }; 80 | 17DC6748258A845600F5FDA2 /* OptionalImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC6747258A845600F5FDA2 /* OptionalImage.swift */; }; 81 | 17DC6749258A845600F5FDA2 /* OptionalImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DC6747258A845600F5FDA2 /* OptionalImage.swift */; }; 82 | 17E1F99A2587A93900482A7E /* NavigationStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17CD4C3C257E371F00C9FC85 /* NavigationStateManager.swift */; }; 83 | 17E1F99F2587A96400482A7E /* NSNotification.Name + helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C951242584D68300C9396F /* NSNotification.Name + helper.swift */; }; 84 | 17EDECF92586076700536926 /* AppToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17EDECF82586076700536926 /* AppToolbar.swift */; }; 85 | 17EFEC42257B9383005BC6E0 /* NoteRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17EFEC41257B9383005BC6E0 /* NoteRow.swift */; }; 86 | /* End PBXBuildFile section */ 87 | 88 | /* Begin PBXContainerItemProxy section */ 89 | 175E4C4625778811001A155E /* PBXContainerItemProxy */ = { 90 | isa = PBXContainerItemProxy; 91 | containerPortal = 175E4C252577880E001A155E /* Project object */; 92 | proxyType = 1; 93 | remoteGlobalIDString = 175E4C2C2577880E001A155E; 94 | remoteInfo = SlipboxApp; 95 | }; 96 | 175E4C5125778811001A155E /* PBXContainerItemProxy */ = { 97 | isa = PBXContainerItemProxy; 98 | containerPortal = 175E4C252577880E001A155E /* Project object */; 99 | proxyType = 1; 100 | remoteGlobalIDString = 175E4C2C2577880E001A155E; 101 | remoteInfo = SlipboxApp; 102 | }; 103 | /* End PBXContainerItemProxy section */ 104 | 105 | /* Begin PBXFileReference section */ 106 | 170F9012257BB46F00BCC9D6 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; 107 | 170F9019257BB83400BCC9D6 /* NSData+text.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSData+text.swift"; sourceTree = ""; }; 108 | 170F9020257BBE1800BCC9D6 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = ""; }; 109 | 172C1934258367F500A0B3FA /* DataType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataType.swift; sourceTree = ""; }; 110 | 172C193B258367FD00A0B3FA /* DataTypeDragItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataTypeDragItem.swift; sourceTree = ""; }; 111 | 172C19512583754700A0B3FA /* Keyword+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Keyword+helper.swift"; sourceTree = ""; }; 112 | 172C195925837A5700A0B3FA /* KeywordListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordListView.swift; sourceTree = ""; }; 113 | 172C196425837A7B00A0B3FA /* KeywordRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordRow.swift; sourceTree = ""; }; 114 | 172C196B25837A8F00A0B3FA /* KeywordEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordEditorView.swift; sourceTree = ""; }; 115 | 173B92EB25861E12008BB5F0 /* NoteSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteSearchView.swift; sourceTree = ""; }; 116 | 173B92FA2586254F008BB5F0 /* PreferenceWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceWindow.swift; sourceTree = ""; }; 117 | 173B930425862567008BB5F0 /* PreferenceContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceContentView.swift; sourceTree = ""; }; 118 | 175E4C2D2577880E001A155E /* SlipboxApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SlipboxApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 119 | 175E4C302577880E001A155E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 120 | 175E4C322577880E001A155E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 121 | 175E4C352577880E001A155E /* SlipboxApp.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = SlipboxApp.xcdatamodel; sourceTree = ""; }; 122 | 175E4C3725778811001A155E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 123 | 175E4C3A25778811001A155E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 124 | 175E4C3D25778811001A155E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 125 | 175E4C3F25778811001A155E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 126 | 175E4C4025778811001A155E /* SlipboxApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SlipboxApp.entitlements; sourceTree = ""; }; 127 | 175E4C4525778811001A155E /* SlipboxAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SlipboxAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 128 | 175E4C4925778811001A155E /* SlipboxAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlipboxAppTests.swift; sourceTree = ""; }; 129 | 175E4C4B25778811001A155E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 130 | 175E4C5025778811001A155E /* SlipboxAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SlipboxAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 131 | 175E4C5425778811001A155E /* SlipboxAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlipboxAppUITests.swift; sourceTree = ""; }; 132 | 175E4C5625778812001A155E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 133 | 1762780625921024008A2546 /* Folder+handleDrop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Folder+handleDrop.swift"; sourceTree = ""; }; 134 | 1762780D2592105C008A2546 /* DropStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropStatus.swift; sourceTree = ""; }; 135 | 176384F32578DB1500911F6D /* NotesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesTests.swift; sourceTree = ""; }; 136 | 176384FD2578DF8800911F6D /* UnitTestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTestHelpers.swift; sourceTree = ""; }; 137 | 176385092579247400911F6D /* NoteListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteListView.swift; sourceTree = ""; }; 138 | 176385142579264F00911F6D /* NoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteView.swift; sourceTree = ""; }; 139 | 176EDAE3257D015600F1C300 /* Folder+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+helper.swift"; sourceTree = ""; }; 140 | 176EDAEA257D020900F1C300 /* FolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderTests.swift; sourceTree = ""; }; 141 | 176EDAF1257D132D00F1C300 /* FolderListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderListView.swift; sourceTree = ""; }; 142 | 176EDAFC257D1FAE00F1C300 /* FolderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderRow.swift; sourceTree = ""; }; 143 | 176EDB07257D20AD00F1C300 /* FolderEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderEditorView.swift; sourceTree = ""; }; 144 | 17988E0A25778926002DD489 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 145 | 17988E1025778A3C002DD489 /* SlipboxPad.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SlipboxPad.app; sourceTree = BUILT_PRODUCTS_DIR; }; 146 | 17988E1225778A3C002DD489 /* SlipboxPadApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlipboxPadApp.swift; sourceTree = ""; }; 147 | 17988E1425778A3C002DD489 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 148 | 17988E1625778A3C002DD489 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 149 | 17988E1925778A3C002DD489 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 150 | 17988E1B25778A3C002DD489 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; 151 | 17988E2025778A3C002DD489 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 152 | 17988E2825778A5D002DD489 /* SlipboxPad.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SlipboxPad.entitlements; sourceTree = ""; }; 153 | 17988E2925778A64002DD489 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.2.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; 154 | 17988E3A25778F76002DD489 /* Note+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Note+helper.swift"; sourceTree = ""; }; 155 | 17988E4425779145002DD489 /* NSPredicate+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPredicate+helper.swift"; sourceTree = ""; }; 156 | 179B12FD2584CCD10026CF48 /* WindowContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowContentView.swift; sourceTree = ""; }; 157 | 17A8F3B4257CE8A5004F34D2 /* helper for nsitem conversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "helper for nsitem conversion.swift"; sourceTree = ""; }; 158 | 17C951242584D68300C9396F /* NSNotification.Name + helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNotification.Name + helper.swift"; sourceTree = ""; }; 159 | 17CD4C36257E332B00C9FC85 /* RecursiveFolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecursiveFolderView.swift; sourceTree = ""; }; 160 | 17CD4C3C257E371F00C9FC85 /* NavigationStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStateManager.swift; sourceTree = ""; }; 161 | 17DC6739258A83C900F5FDA2 /* Note+handelDrop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Note+handelDrop.swift"; sourceTree = ""; }; 162 | 17DC6740258A842C00F5FDA2 /* URL+helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+helper.swift"; sourceTree = ""; }; 163 | 17DC6747258A845600F5FDA2 /* OptionalImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalImage.swift; sourceTree = ""; }; 164 | 17EDECF82586076700536926 /* AppToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppToolbar.swift; sourceTree = ""; }; 165 | 17EFEC41257B9383005BC6E0 /* NoteRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteRow.swift; sourceTree = ""; }; 166 | /* End PBXFileReference section */ 167 | 168 | /* Begin PBXFrameworksBuildPhase section */ 169 | 175E4C2A2577880E001A155E /* Frameworks */ = { 170 | isa = PBXFrameworksBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | 17988E0B25778926002DD489 /* CloudKit.framework in Frameworks */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | 175E4C4225778811001A155E /* Frameworks */ = { 178 | isa = PBXFrameworksBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | 175E4C4D25778811001A155E /* Frameworks */ = { 185 | isa = PBXFrameworksBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | 17988E0D25778A3C002DD489 /* Frameworks */ = { 192 | isa = PBXFrameworksBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | 17988E2A25778A64002DD489 /* CloudKit.framework in Frameworks */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXFrameworksBuildPhase section */ 200 | 201 | /* Begin PBXGroup section */ 202 | 172C1933258367DA00A0B3FA /* drag and drop */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | 172C1934258367F500A0B3FA /* DataType.swift */, 206 | 172C193B258367FD00A0B3FA /* DataTypeDragItem.swift */, 207 | 17DC6740258A842C00F5FDA2 /* URL+helper.swift */, 208 | ); 209 | path = "drag and drop"; 210 | sourceTree = ""; 211 | }; 212 | 172C195825837A3B00A0B3FA /* Keyword */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 172C195925837A5700A0B3FA /* KeywordListView.swift */, 216 | 172C196425837A7B00A0B3FA /* KeywordRow.swift */, 217 | 172C196B25837A8F00A0B3FA /* KeywordEditorView.swift */, 218 | ); 219 | path = Keyword; 220 | sourceTree = ""; 221 | }; 222 | 173B92F925862533008BB5F0 /* prefences */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | 173B92FA2586254F008BB5F0 /* PreferenceWindow.swift */, 226 | 173B930425862567008BB5F0 /* PreferenceContentView.swift */, 227 | ); 228 | path = prefences; 229 | sourceTree = ""; 230 | }; 231 | 175E4C242577880E001A155E = { 232 | isa = PBXGroup; 233 | children = ( 234 | 17988E2B25778AAE002DD489 /* Shared */, 235 | 175E4C2F2577880E001A155E /* SlipboxApp */, 236 | 17988E1125778A3C002DD489 /* SlipboxPad */, 237 | 175E4C4825778811001A155E /* SlipboxAppTests */, 238 | 175E4C5325778811001A155E /* SlipboxAppUITests */, 239 | 175E4C2E2577880E001A155E /* Products */, 240 | 17988E0925778926002DD489 /* Frameworks */, 241 | ); 242 | sourceTree = ""; 243 | }; 244 | 175E4C2E2577880E001A155E /* Products */ = { 245 | isa = PBXGroup; 246 | children = ( 247 | 175E4C2D2577880E001A155E /* SlipboxApp.app */, 248 | 175E4C4525778811001A155E /* SlipboxAppTests.xctest */, 249 | 175E4C5025778811001A155E /* SlipboxAppUITests.xctest */, 250 | 17988E1025778A3C002DD489 /* SlipboxPad.app */, 251 | ); 252 | name = Products; 253 | sourceTree = ""; 254 | }; 255 | 175E4C2F2577880E001A155E /* SlipboxApp */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 179B13032584CCD80026CF48 /* app */, 259 | 173B92F925862533008BB5F0 /* prefences */, 260 | 179B13042584CCE30026CF48 /* main window */, 261 | 172C195825837A3B00A0B3FA /* Keyword */, 262 | 176EDAF0257D131B00F1C300 /* Folder */, 263 | 176385082579245C00911F6D /* Notes */, 264 | 172C1933258367DA00A0B3FA /* drag and drop */, 265 | 175E4C3725778811001A155E /* Assets.xcassets */, 266 | 175E4C3F25778811001A155E /* Info.plist */, 267 | 175E4C4025778811001A155E /* SlipboxApp.entitlements */, 268 | 175E4C3925778811001A155E /* Preview Content */, 269 | ); 270 | path = SlipboxApp; 271 | sourceTree = ""; 272 | }; 273 | 175E4C3925778811001A155E /* Preview Content */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | 175E4C3A25778811001A155E /* Preview Assets.xcassets */, 277 | ); 278 | path = "Preview Content"; 279 | sourceTree = ""; 280 | }; 281 | 175E4C4825778811001A155E /* SlipboxAppTests */ = { 282 | isa = PBXGroup; 283 | children = ( 284 | 176EDAEA257D020900F1C300 /* FolderTests.swift */, 285 | 175E4C4925778811001A155E /* SlipboxAppTests.swift */, 286 | 176384F32578DB1500911F6D /* NotesTests.swift */, 287 | 175E4C4B25778811001A155E /* Info.plist */, 288 | ); 289 | path = SlipboxAppTests; 290 | sourceTree = ""; 291 | }; 292 | 175E4C5325778811001A155E /* SlipboxAppUITests */ = { 293 | isa = PBXGroup; 294 | children = ( 295 | 175E4C5425778811001A155E /* SlipboxAppUITests.swift */, 296 | 175E4C5625778812001A155E /* Info.plist */, 297 | ); 298 | path = SlipboxAppUITests; 299 | sourceTree = ""; 300 | }; 301 | 176385082579245C00911F6D /* Notes */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | 176385092579247400911F6D /* NoteListView.swift */, 305 | 17EFEC41257B9383005BC6E0 /* NoteRow.swift */, 306 | 176385142579264F00911F6D /* NoteView.swift */, 307 | 17DC6747258A845600F5FDA2 /* OptionalImage.swift */, 308 | 170F9020257BBE1800BCC9D6 /* TextViewWrapper.swift */, 309 | 17A8F3B4257CE8A5004F34D2 /* helper for nsitem conversion.swift */, 310 | 173B92EB25861E12008BB5F0 /* NoteSearchView.swift */, 311 | ); 312 | path = Notes; 313 | sourceTree = ""; 314 | }; 315 | 176EDAF0257D131B00F1C300 /* Folder */ = { 316 | isa = PBXGroup; 317 | children = ( 318 | 176EDAF1257D132D00F1C300 /* FolderListView.swift */, 319 | 17CD4C36257E332B00C9FC85 /* RecursiveFolderView.swift */, 320 | 176EDAFC257D1FAE00F1C300 /* FolderRow.swift */, 321 | 1762780D2592105C008A2546 /* DropStatus.swift */, 322 | 176EDB07257D20AD00F1C300 /* FolderEditorView.swift */, 323 | ); 324 | path = Folder; 325 | sourceTree = ""; 326 | }; 327 | 17988E0925778926002DD489 /* Frameworks */ = { 328 | isa = PBXGroup; 329 | children = ( 330 | 17988E2925778A64002DD489 /* CloudKit.framework */, 331 | 17988E0A25778926002DD489 /* CloudKit.framework */, 332 | ); 333 | name = Frameworks; 334 | sourceTree = ""; 335 | }; 336 | 17988E1125778A3C002DD489 /* SlipboxPad */ = { 337 | isa = PBXGroup; 338 | children = ( 339 | 17988E1225778A3C002DD489 /* SlipboxPadApp.swift */, 340 | 17988E2825778A5D002DD489 /* SlipboxPad.entitlements */, 341 | 17988E1425778A3C002DD489 /* ContentView.swift */, 342 | 17988E1625778A3C002DD489 /* Assets.xcassets */, 343 | 17988E2025778A3C002DD489 /* Info.plist */, 344 | 17988E1825778A3C002DD489 /* Preview Content */, 345 | ); 346 | path = SlipboxPad; 347 | sourceTree = ""; 348 | }; 349 | 17988E1825778A3C002DD489 /* Preview Content */ = { 350 | isa = PBXGroup; 351 | children = ( 352 | 17988E1925778A3C002DD489 /* Preview Assets.xcassets */, 353 | ); 354 | path = "Preview Content"; 355 | sourceTree = ""; 356 | }; 357 | 17988E2B25778AAE002DD489 /* Shared */ = { 358 | isa = PBXGroup; 359 | children = ( 360 | 17988E1B25778A3C002DD489 /* Persistence.swift */, 361 | 176384FD2578DF8800911F6D /* UnitTestHelpers.swift */, 362 | 17988E3925778F63002DD489 /* model */, 363 | 175E4C342577880E001A155E /* SlipboxApp.xcdatamodeld */, 364 | 17988E4425779145002DD489 /* NSPredicate+helper.swift */, 365 | ); 366 | path = Shared; 367 | sourceTree = ""; 368 | }; 369 | 17988E3925778F63002DD489 /* model */ = { 370 | isa = PBXGroup; 371 | children = ( 372 | 176EDAE3257D015600F1C300 /* Folder+helper.swift */, 373 | 1762780625921024008A2546 /* Folder+handleDrop.swift */, 374 | 17988E3A25778F76002DD489 /* Note+helper.swift */, 375 | 17DC6739258A83C900F5FDA2 /* Note+handelDrop.swift */, 376 | 172C19512583754700A0B3FA /* Keyword+helper.swift */, 377 | 170F9012257BB46F00BCC9D6 /* Status.swift */, 378 | 170F9019257BB83400BCC9D6 /* NSData+text.swift */, 379 | ); 380 | path = model; 381 | sourceTree = ""; 382 | }; 383 | 179B13032584CCD80026CF48 /* app */ = { 384 | isa = PBXGroup; 385 | children = ( 386 | 175E4C302577880E001A155E /* AppDelegate.swift */, 387 | 175E4C3C25778811001A155E /* Main.storyboard */, 388 | 17C951242584D68300C9396F /* NSNotification.Name + helper.swift */, 389 | ); 390 | path = app; 391 | sourceTree = ""; 392 | }; 393 | 179B13042584CCE30026CF48 /* main window */ = { 394 | isa = PBXGroup; 395 | children = ( 396 | 179B12FD2584CCD10026CF48 /* WindowContentView.swift */, 397 | 17EDECF82586076700536926 /* AppToolbar.swift */, 398 | 175E4C322577880E001A155E /* ContentView.swift */, 399 | 17CD4C3C257E371F00C9FC85 /* NavigationStateManager.swift */, 400 | ); 401 | path = "main window"; 402 | sourceTree = ""; 403 | }; 404 | /* End PBXGroup section */ 405 | 406 | /* Begin PBXNativeTarget section */ 407 | 175E4C2C2577880E001A155E /* SlipboxApp */ = { 408 | isa = PBXNativeTarget; 409 | buildConfigurationList = 175E4C5925778812001A155E /* Build configuration list for PBXNativeTarget "SlipboxApp" */; 410 | buildPhases = ( 411 | 175E4C292577880E001A155E /* Sources */, 412 | 175E4C2A2577880E001A155E /* Frameworks */, 413 | 175E4C2B2577880E001A155E /* Resources */, 414 | ); 415 | buildRules = ( 416 | ); 417 | dependencies = ( 418 | ); 419 | name = SlipboxApp; 420 | productName = SlipboxApp; 421 | productReference = 175E4C2D2577880E001A155E /* SlipboxApp.app */; 422 | productType = "com.apple.product-type.application"; 423 | }; 424 | 175E4C4425778811001A155E /* SlipboxAppTests */ = { 425 | isa = PBXNativeTarget; 426 | buildConfigurationList = 175E4C5C25778812001A155E /* Build configuration list for PBXNativeTarget "SlipboxAppTests" */; 427 | buildPhases = ( 428 | 175E4C4125778811001A155E /* Sources */, 429 | 175E4C4225778811001A155E /* Frameworks */, 430 | 175E4C4325778811001A155E /* Resources */, 431 | ); 432 | buildRules = ( 433 | ); 434 | dependencies = ( 435 | 175E4C4725778811001A155E /* PBXTargetDependency */, 436 | ); 437 | name = SlipboxAppTests; 438 | productName = SlipboxAppTests; 439 | productReference = 175E4C4525778811001A155E /* SlipboxAppTests.xctest */; 440 | productType = "com.apple.product-type.bundle.unit-test"; 441 | }; 442 | 175E4C4F25778811001A155E /* SlipboxAppUITests */ = { 443 | isa = PBXNativeTarget; 444 | buildConfigurationList = 175E4C5F25778812001A155E /* Build configuration list for PBXNativeTarget "SlipboxAppUITests" */; 445 | buildPhases = ( 446 | 175E4C4C25778811001A155E /* Sources */, 447 | 175E4C4D25778811001A155E /* Frameworks */, 448 | 175E4C4E25778811001A155E /* Resources */, 449 | ); 450 | buildRules = ( 451 | ); 452 | dependencies = ( 453 | 175E4C5225778811001A155E /* PBXTargetDependency */, 454 | ); 455 | name = SlipboxAppUITests; 456 | productName = SlipboxAppUITests; 457 | productReference = 175E4C5025778811001A155E /* SlipboxAppUITests.xctest */; 458 | productType = "com.apple.product-type.bundle.ui-testing"; 459 | }; 460 | 17988E0F25778A3C002DD489 /* SlipboxPad */ = { 461 | isa = PBXNativeTarget; 462 | buildConfigurationList = 17988E2125778A3C002DD489 /* Build configuration list for PBXNativeTarget "SlipboxPad" */; 463 | buildPhases = ( 464 | 17988E0C25778A3C002DD489 /* Sources */, 465 | 17988E0D25778A3C002DD489 /* Frameworks */, 466 | 17988E0E25778A3C002DD489 /* Resources */, 467 | ); 468 | buildRules = ( 469 | ); 470 | dependencies = ( 471 | ); 472 | name = SlipboxPad; 473 | productName = SlipboxPad; 474 | productReference = 17988E1025778A3C002DD489 /* SlipboxPad.app */; 475 | productType = "com.apple.product-type.application"; 476 | }; 477 | /* End PBXNativeTarget section */ 478 | 479 | /* Begin PBXProject section */ 480 | 175E4C252577880E001A155E /* Project object */ = { 481 | isa = PBXProject; 482 | attributes = { 483 | LastSwiftUpdateCheck = 1220; 484 | LastUpgradeCheck = 1220; 485 | TargetAttributes = { 486 | 175E4C2C2577880E001A155E = { 487 | CreatedOnToolsVersion = 12.2; 488 | }; 489 | 175E4C4425778811001A155E = { 490 | CreatedOnToolsVersion = 12.2; 491 | TestTargetID = 175E4C2C2577880E001A155E; 492 | }; 493 | 175E4C4F25778811001A155E = { 494 | CreatedOnToolsVersion = 12.2; 495 | TestTargetID = 175E4C2C2577880E001A155E; 496 | }; 497 | 17988E0F25778A3C002DD489 = { 498 | CreatedOnToolsVersion = 12.2; 499 | }; 500 | }; 501 | }; 502 | buildConfigurationList = 175E4C282577880E001A155E /* Build configuration list for PBXProject "SlipboxApp" */; 503 | compatibilityVersion = "Xcode 9.3"; 504 | developmentRegion = en; 505 | hasScannedForEncodings = 0; 506 | knownRegions = ( 507 | en, 508 | Base, 509 | ); 510 | mainGroup = 175E4C242577880E001A155E; 511 | productRefGroup = 175E4C2E2577880E001A155E /* Products */; 512 | projectDirPath = ""; 513 | projectRoot = ""; 514 | targets = ( 515 | 175E4C2C2577880E001A155E /* SlipboxApp */, 516 | 175E4C4425778811001A155E /* SlipboxAppTests */, 517 | 175E4C4F25778811001A155E /* SlipboxAppUITests */, 518 | 17988E0F25778A3C002DD489 /* SlipboxPad */, 519 | ); 520 | }; 521 | /* End PBXProject section */ 522 | 523 | /* Begin PBXResourcesBuildPhase section */ 524 | 175E4C2B2577880E001A155E /* Resources */ = { 525 | isa = PBXResourcesBuildPhase; 526 | buildActionMask = 2147483647; 527 | files = ( 528 | 175E4C3E25778811001A155E /* Main.storyboard in Resources */, 529 | 175E4C3B25778811001A155E /* Preview Assets.xcassets in Resources */, 530 | 175E4C3825778811001A155E /* Assets.xcassets in Resources */, 531 | ); 532 | runOnlyForDeploymentPostprocessing = 0; 533 | }; 534 | 175E4C4325778811001A155E /* Resources */ = { 535 | isa = PBXResourcesBuildPhase; 536 | buildActionMask = 2147483647; 537 | files = ( 538 | ); 539 | runOnlyForDeploymentPostprocessing = 0; 540 | }; 541 | 175E4C4E25778811001A155E /* Resources */ = { 542 | isa = PBXResourcesBuildPhase; 543 | buildActionMask = 2147483647; 544 | files = ( 545 | ); 546 | runOnlyForDeploymentPostprocessing = 0; 547 | }; 548 | 17988E0E25778A3C002DD489 /* Resources */ = { 549 | isa = PBXResourcesBuildPhase; 550 | buildActionMask = 2147483647; 551 | files = ( 552 | 17988E1A25778A3C002DD489 /* Preview Assets.xcassets in Resources */, 553 | 17988E1725778A3C002DD489 /* Assets.xcassets in Resources */, 554 | ); 555 | runOnlyForDeploymentPostprocessing = 0; 556 | }; 557 | /* End PBXResourcesBuildPhase section */ 558 | 559 | /* Begin PBXSourcesBuildPhase section */ 560 | 175E4C292577880E001A155E /* Sources */ = { 561 | isa = PBXSourcesBuildPhase; 562 | buildActionMask = 2147483647; 563 | files = ( 564 | 17EFEC42257B9383005BC6E0 /* NoteRow.swift in Sources */, 565 | 176384FE2578DF8800911F6D /* UnitTestHelpers.swift in Sources */, 566 | 17CD4C37257E332B00C9FC85 /* RecursiveFolderView.swift in Sources */, 567 | 17C951252584D68300C9396F /* NSNotification.Name + helper.swift in Sources */, 568 | 17CD4C3D257E371F00C9FC85 /* NavigationStateManager.swift in Sources */, 569 | 172C195A25837A5700A0B3FA /* KeywordListView.swift in Sources */, 570 | 17EDECF92586076700536926 /* AppToolbar.swift in Sources */, 571 | 176EDAF2257D132D00F1C300 /* FolderListView.swift in Sources */, 572 | 17988E4525779145002DD489 /* NSPredicate+helper.swift in Sources */, 573 | 172C196C25837A8F00A0B3FA /* KeywordEditorView.swift in Sources */, 574 | 173B92EC25861E12008BB5F0 /* NoteSearchView.swift in Sources */, 575 | 173B930525862567008BB5F0 /* PreferenceContentView.swift in Sources */, 576 | 17DC673A258A83C900F5FDA2 /* Note+handelDrop.swift in Sources */, 577 | 1763850A2579247400911F6D /* NoteListView.swift in Sources */, 578 | 176EDAFD257D1FAE00F1C300 /* FolderRow.swift in Sources */, 579 | 17DC6741258A842D00F5FDA2 /* URL+helper.swift in Sources */, 580 | 170F9013257BB46F00BCC9D6 /* Status.swift in Sources */, 581 | 175E4C362577880E001A155E /* SlipboxApp.xcdatamodeld in Sources */, 582 | 176EDAE4257D015600F1C300 /* Folder+helper.swift in Sources */, 583 | 176EDB08257D20AD00F1C300 /* FolderEditorView.swift in Sources */, 584 | 1762780E2592105C008A2546 /* DropStatus.swift in Sources */, 585 | 17988E3B25778F76002DD489 /* Note+helper.swift in Sources */, 586 | 172C1935258367F500A0B3FA /* DataType.swift in Sources */, 587 | 176384E32578CCFE00911F6D /* Persistence.swift in Sources */, 588 | 175E4C332577880E001A155E /* ContentView.swift in Sources */, 589 | 172C19522583754700A0B3FA /* Keyword+helper.swift in Sources */, 590 | 175E4C312577880E001A155E /* AppDelegate.swift in Sources */, 591 | 170F9021257BBE1800BCC9D6 /* TextViewWrapper.swift in Sources */, 592 | 176385152579264F00911F6D /* NoteView.swift in Sources */, 593 | 172C196525837A7B00A0B3FA /* KeywordRow.swift in Sources */, 594 | 179B12FE2584CCD10026CF48 /* WindowContentView.swift in Sources */, 595 | 1762780725921024008A2546 /* Folder+handleDrop.swift in Sources */, 596 | 173B92FB2586254F008BB5F0 /* PreferenceWindow.swift in Sources */, 597 | 17A8F3B5257CE8A5004F34D2 /* helper for nsitem conversion.swift in Sources */, 598 | 172C193C258367FD00A0B3FA /* DataTypeDragItem.swift in Sources */, 599 | 17DC6748258A845600F5FDA2 /* OptionalImage.swift in Sources */, 600 | 170F901A257BB83400BCC9D6 /* NSData+text.swift in Sources */, 601 | ); 602 | runOnlyForDeploymentPostprocessing = 0; 603 | }; 604 | 175E4C4125778811001A155E /* Sources */ = { 605 | isa = PBXSourcesBuildPhase; 606 | buildActionMask = 2147483647; 607 | files = ( 608 | 175E4C4A25778811001A155E /* SlipboxAppTests.swift in Sources */, 609 | 176EDAEB257D020900F1C300 /* FolderTests.swift in Sources */, 610 | 176384F42578DB1500911F6D /* NotesTests.swift in Sources */, 611 | ); 612 | runOnlyForDeploymentPostprocessing = 0; 613 | }; 614 | 175E4C4C25778811001A155E /* Sources */ = { 615 | isa = PBXSourcesBuildPhase; 616 | buildActionMask = 2147483647; 617 | files = ( 618 | 175E4C5525778811001A155E /* SlipboxAppUITests.swift in Sources */, 619 | ); 620 | runOnlyForDeploymentPostprocessing = 0; 621 | }; 622 | 17988E0C25778A3C002DD489 /* Sources */ = { 623 | isa = PBXSourcesBuildPhase; 624 | buildActionMask = 2147483647; 625 | files = ( 626 | 172C193D258367FD00A0B3FA /* DataTypeDragItem.swift in Sources */, 627 | 176EDAF3257D132D00F1C300 /* FolderListView.swift in Sources */, 628 | 176384FF2578DF8800911F6D /* UnitTestHelpers.swift in Sources */, 629 | 17E1F99F2587A96400482A7E /* NSNotification.Name + helper.swift in Sources */, 630 | 170F9014257BB46F00BCC9D6 /* Status.swift in Sources */, 631 | 179B12E52584B77E0026CF48 /* RecursiveFolderView.swift in Sources */, 632 | 179B12F82584B7C00026CF48 /* helper for nsitem conversion.swift in Sources */, 633 | 17988E4A257791C0002DD489 /* NSPredicate+helper.swift in Sources */, 634 | 17E1F99A2587A93900482A7E /* NavigationStateManager.swift in Sources */, 635 | 179B12EA2584B7940026CF48 /* NoteRow.swift in Sources */, 636 | 1763850B2579247400911F6D /* NoteListView.swift in Sources */, 637 | 17DC6742258A842D00F5FDA2 /* URL+helper.swift in Sources */, 638 | 1762780825921024008A2546 /* Folder+handleDrop.swift in Sources */, 639 | 172C1936258367F500A0B3FA /* DataType.swift in Sources */, 640 | 176EDAFE257D1FAE00F1C300 /* FolderRow.swift in Sources */, 641 | 17988E4F25779576002DD489 /* Note+helper.swift in Sources */, 642 | 17988E1C25778A3C002DD489 /* Persistence.swift in Sources */, 643 | 17988E3025778AF6002DD489 /* SlipboxApp.xcdatamodeld in Sources */, 644 | 170F901B257BB83400BCC9D6 /* NSData+text.swift in Sources */, 645 | 17988E1525778A3C002DD489 /* ContentView.swift in Sources */, 646 | 17DC6749258A845600F5FDA2 /* OptionalImage.swift in Sources */, 647 | 17DC673B258A83C900F5FDA2 /* Note+handelDrop.swift in Sources */, 648 | 176EDAE5257D015600F1C300 /* Folder+helper.swift in Sources */, 649 | 176EDB09257D20AD00F1C300 /* FolderEditorView.swift in Sources */, 650 | 1762780F2592105C008A2546 /* DropStatus.swift in Sources */, 651 | 17988E1325778A3C002DD489 /* SlipboxPadApp.swift in Sources */, 652 | 176385162579264F00911F6D /* NoteView.swift in Sources */, 653 | 172C19532583754700A0B3FA /* Keyword+helper.swift in Sources */, 654 | ); 655 | runOnlyForDeploymentPostprocessing = 0; 656 | }; 657 | /* End PBXSourcesBuildPhase section */ 658 | 659 | /* Begin PBXTargetDependency section */ 660 | 175E4C4725778811001A155E /* PBXTargetDependency */ = { 661 | isa = PBXTargetDependency; 662 | target = 175E4C2C2577880E001A155E /* SlipboxApp */; 663 | targetProxy = 175E4C4625778811001A155E /* PBXContainerItemProxy */; 664 | }; 665 | 175E4C5225778811001A155E /* PBXTargetDependency */ = { 666 | isa = PBXTargetDependency; 667 | target = 175E4C2C2577880E001A155E /* SlipboxApp */; 668 | targetProxy = 175E4C5125778811001A155E /* PBXContainerItemProxy */; 669 | }; 670 | /* End PBXTargetDependency section */ 671 | 672 | /* Begin PBXVariantGroup section */ 673 | 175E4C3C25778811001A155E /* Main.storyboard */ = { 674 | isa = PBXVariantGroup; 675 | children = ( 676 | 175E4C3D25778811001A155E /* Base */, 677 | ); 678 | name = Main.storyboard; 679 | sourceTree = ""; 680 | }; 681 | /* End PBXVariantGroup section */ 682 | 683 | /* Begin XCBuildConfiguration section */ 684 | 175E4C5725778812001A155E /* Debug */ = { 685 | isa = XCBuildConfiguration; 686 | buildSettings = { 687 | ALWAYS_SEARCH_USER_PATHS = NO; 688 | CLANG_ANALYZER_NONNULL = YES; 689 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 690 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 691 | CLANG_CXX_LIBRARY = "libc++"; 692 | CLANG_ENABLE_MODULES = YES; 693 | CLANG_ENABLE_OBJC_ARC = YES; 694 | CLANG_ENABLE_OBJC_WEAK = YES; 695 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 696 | CLANG_WARN_BOOL_CONVERSION = YES; 697 | CLANG_WARN_COMMA = YES; 698 | CLANG_WARN_CONSTANT_CONVERSION = YES; 699 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 700 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 701 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 702 | CLANG_WARN_EMPTY_BODY = YES; 703 | CLANG_WARN_ENUM_CONVERSION = YES; 704 | CLANG_WARN_INFINITE_RECURSION = YES; 705 | CLANG_WARN_INT_CONVERSION = YES; 706 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 707 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 708 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 709 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 710 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 711 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 712 | CLANG_WARN_STRICT_PROTOTYPES = YES; 713 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 714 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 715 | CLANG_WARN_UNREACHABLE_CODE = YES; 716 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 717 | COPY_PHASE_STRIP = NO; 718 | DEBUG_INFORMATION_FORMAT = dwarf; 719 | ENABLE_STRICT_OBJC_MSGSEND = YES; 720 | ENABLE_TESTABILITY = YES; 721 | GCC_C_LANGUAGE_STANDARD = gnu11; 722 | GCC_DYNAMIC_NO_PIC = NO; 723 | GCC_NO_COMMON_BLOCKS = YES; 724 | GCC_OPTIMIZATION_LEVEL = 0; 725 | GCC_PREPROCESSOR_DEFINITIONS = ( 726 | "DEBUG=1", 727 | "$(inherited)", 728 | ); 729 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 730 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 731 | GCC_WARN_UNDECLARED_SELECTOR = YES; 732 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 733 | GCC_WARN_UNUSED_FUNCTION = YES; 734 | GCC_WARN_UNUSED_VARIABLE = YES; 735 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 736 | MACOSX_DEPLOYMENT_TARGET = 11.0; 737 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 738 | MTL_FAST_MATH = YES; 739 | ONLY_ACTIVE_ARCH = YES; 740 | SDKROOT = macosx; 741 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 742 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 743 | }; 744 | name = Debug; 745 | }; 746 | 175E4C5825778812001A155E /* Release */ = { 747 | isa = XCBuildConfiguration; 748 | buildSettings = { 749 | ALWAYS_SEARCH_USER_PATHS = NO; 750 | CLANG_ANALYZER_NONNULL = YES; 751 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 752 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 753 | CLANG_CXX_LIBRARY = "libc++"; 754 | CLANG_ENABLE_MODULES = YES; 755 | CLANG_ENABLE_OBJC_ARC = YES; 756 | CLANG_ENABLE_OBJC_WEAK = YES; 757 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 758 | CLANG_WARN_BOOL_CONVERSION = YES; 759 | CLANG_WARN_COMMA = YES; 760 | CLANG_WARN_CONSTANT_CONVERSION = YES; 761 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 762 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 763 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 764 | CLANG_WARN_EMPTY_BODY = YES; 765 | CLANG_WARN_ENUM_CONVERSION = YES; 766 | CLANG_WARN_INFINITE_RECURSION = YES; 767 | CLANG_WARN_INT_CONVERSION = YES; 768 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 769 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 770 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 771 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 772 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 773 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 774 | CLANG_WARN_STRICT_PROTOTYPES = YES; 775 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 776 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 777 | CLANG_WARN_UNREACHABLE_CODE = YES; 778 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 779 | COPY_PHASE_STRIP = NO; 780 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 781 | ENABLE_NS_ASSERTIONS = NO; 782 | ENABLE_STRICT_OBJC_MSGSEND = YES; 783 | GCC_C_LANGUAGE_STANDARD = gnu11; 784 | GCC_NO_COMMON_BLOCKS = YES; 785 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 786 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 787 | GCC_WARN_UNDECLARED_SELECTOR = YES; 788 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 789 | GCC_WARN_UNUSED_FUNCTION = YES; 790 | GCC_WARN_UNUSED_VARIABLE = YES; 791 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 792 | MACOSX_DEPLOYMENT_TARGET = 11.0; 793 | MTL_ENABLE_DEBUG_INFO = NO; 794 | MTL_FAST_MATH = YES; 795 | SDKROOT = macosx; 796 | SWIFT_COMPILATION_MODE = wholemodule; 797 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 798 | }; 799 | name = Release; 800 | }; 801 | 175E4C5A25778812001A155E /* Debug */ = { 802 | isa = XCBuildConfiguration; 803 | buildSettings = { 804 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 805 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 806 | CODE_SIGN_ENTITLEMENTS = SlipboxApp/SlipboxApp.entitlements; 807 | CODE_SIGN_STYLE = Automatic; 808 | COMBINE_HIDPI_IMAGES = YES; 809 | DEVELOPMENT_ASSET_PATHS = "\"SlipboxApp/Preview Content\""; 810 | DEVELOPMENT_TEAM = 2ZB33FC454; 811 | ENABLE_HARDENED_RUNTIME = YES; 812 | ENABLE_PREVIEWS = YES; 813 | INFOPLIST_FILE = SlipboxApp/Info.plist; 814 | LD_RUNPATH_SEARCH_PATHS = ( 815 | "$(inherited)", 816 | "@executable_path/../Frameworks", 817 | ); 818 | MACOSX_DEPLOYMENT_TARGET = 11.0; 819 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxApp; 820 | PRODUCT_NAME = "$(TARGET_NAME)"; 821 | SWIFT_VERSION = 5.0; 822 | }; 823 | name = Debug; 824 | }; 825 | 175E4C5B25778812001A155E /* Release */ = { 826 | isa = XCBuildConfiguration; 827 | buildSettings = { 828 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 829 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 830 | CODE_SIGN_ENTITLEMENTS = SlipboxApp/SlipboxApp.entitlements; 831 | CODE_SIGN_STYLE = Automatic; 832 | COMBINE_HIDPI_IMAGES = YES; 833 | DEVELOPMENT_ASSET_PATHS = "\"SlipboxApp/Preview Content\""; 834 | DEVELOPMENT_TEAM = 2ZB33FC454; 835 | ENABLE_HARDENED_RUNTIME = YES; 836 | ENABLE_PREVIEWS = YES; 837 | INFOPLIST_FILE = SlipboxApp/Info.plist; 838 | LD_RUNPATH_SEARCH_PATHS = ( 839 | "$(inherited)", 840 | "@executable_path/../Frameworks", 841 | ); 842 | MACOSX_DEPLOYMENT_TARGET = 11.0; 843 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxApp; 844 | PRODUCT_NAME = "$(TARGET_NAME)"; 845 | SWIFT_VERSION = 5.0; 846 | }; 847 | name = Release; 848 | }; 849 | 175E4C5D25778812001A155E /* Debug */ = { 850 | isa = XCBuildConfiguration; 851 | buildSettings = { 852 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 853 | BUNDLE_LOADER = "$(TEST_HOST)"; 854 | CODE_SIGN_IDENTITY = "Apple Development"; 855 | CODE_SIGN_STYLE = Automatic; 856 | COMBINE_HIDPI_IMAGES = YES; 857 | DEVELOPMENT_TEAM = 2ZB33FC454; 858 | INFOPLIST_FILE = SlipboxAppTests/Info.plist; 859 | LD_RUNPATH_SEARCH_PATHS = ( 860 | "$(inherited)", 861 | "@executable_path/../Frameworks", 862 | "@loader_path/../Frameworks", 863 | ); 864 | MACOSX_DEPLOYMENT_TARGET = 11.0; 865 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxAppTests; 866 | PRODUCT_NAME = "$(TARGET_NAME)"; 867 | SWIFT_VERSION = 5.0; 868 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SlipboxApp.app/Contents/MacOS/SlipboxApp"; 869 | }; 870 | name = Debug; 871 | }; 872 | 175E4C5E25778812001A155E /* Release */ = { 873 | isa = XCBuildConfiguration; 874 | buildSettings = { 875 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 876 | BUNDLE_LOADER = "$(TEST_HOST)"; 877 | CODE_SIGN_IDENTITY = "Apple Development"; 878 | CODE_SIGN_STYLE = Automatic; 879 | COMBINE_HIDPI_IMAGES = YES; 880 | DEVELOPMENT_TEAM = 2ZB33FC454; 881 | INFOPLIST_FILE = SlipboxAppTests/Info.plist; 882 | LD_RUNPATH_SEARCH_PATHS = ( 883 | "$(inherited)", 884 | "@executable_path/../Frameworks", 885 | "@loader_path/../Frameworks", 886 | ); 887 | MACOSX_DEPLOYMENT_TARGET = 11.0; 888 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxAppTests; 889 | PRODUCT_NAME = "$(TARGET_NAME)"; 890 | SWIFT_VERSION = 5.0; 891 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SlipboxApp.app/Contents/MacOS/SlipboxApp"; 892 | }; 893 | name = Release; 894 | }; 895 | 175E4C6025778812001A155E /* Debug */ = { 896 | isa = XCBuildConfiguration; 897 | buildSettings = { 898 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 899 | CODE_SIGN_STYLE = Automatic; 900 | COMBINE_HIDPI_IMAGES = YES; 901 | DEVELOPMENT_TEAM = 2ZB33FC454; 902 | INFOPLIST_FILE = SlipboxAppUITests/Info.plist; 903 | LD_RUNPATH_SEARCH_PATHS = ( 904 | "$(inherited)", 905 | "@executable_path/../Frameworks", 906 | "@loader_path/../Frameworks", 907 | ); 908 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxAppUITests; 909 | PRODUCT_NAME = "$(TARGET_NAME)"; 910 | SWIFT_VERSION = 5.0; 911 | TEST_TARGET_NAME = SlipboxApp; 912 | }; 913 | name = Debug; 914 | }; 915 | 175E4C6125778812001A155E /* Release */ = { 916 | isa = XCBuildConfiguration; 917 | buildSettings = { 918 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 919 | CODE_SIGN_STYLE = Automatic; 920 | COMBINE_HIDPI_IMAGES = YES; 921 | DEVELOPMENT_TEAM = 2ZB33FC454; 922 | INFOPLIST_FILE = SlipboxAppUITests/Info.plist; 923 | LD_RUNPATH_SEARCH_PATHS = ( 924 | "$(inherited)", 925 | "@executable_path/../Frameworks", 926 | "@loader_path/../Frameworks", 927 | ); 928 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxAppUITests; 929 | PRODUCT_NAME = "$(TARGET_NAME)"; 930 | SWIFT_VERSION = 5.0; 931 | TEST_TARGET_NAME = SlipboxApp; 932 | }; 933 | name = Release; 934 | }; 935 | 17988E2225778A3C002DD489 /* Debug */ = { 936 | isa = XCBuildConfiguration; 937 | buildSettings = { 938 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 939 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 940 | CODE_SIGN_ENTITLEMENTS = SlipboxPad/SlipboxPad.entitlements; 941 | CODE_SIGN_STYLE = Automatic; 942 | DEVELOPMENT_ASSET_PATHS = "\"SlipboxPad/Preview Content\""; 943 | DEVELOPMENT_TEAM = 2ZB33FC454; 944 | ENABLE_PREVIEWS = YES; 945 | INFOPLIST_FILE = SlipboxPad/Info.plist; 946 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 947 | LD_RUNPATH_SEARCH_PATHS = ( 948 | "$(inherited)", 949 | "@executable_path/Frameworks", 950 | ); 951 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxPad; 952 | PRODUCT_NAME = "$(TARGET_NAME)"; 953 | SDKROOT = iphoneos; 954 | SWIFT_VERSION = 5.0; 955 | TARGETED_DEVICE_FAMILY = "1,2"; 956 | }; 957 | name = Debug; 958 | }; 959 | 17988E2325778A3C002DD489 /* Release */ = { 960 | isa = XCBuildConfiguration; 961 | buildSettings = { 962 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 963 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 964 | CODE_SIGN_ENTITLEMENTS = SlipboxPad/SlipboxPad.entitlements; 965 | CODE_SIGN_STYLE = Automatic; 966 | DEVELOPMENT_ASSET_PATHS = "\"SlipboxPad/Preview Content\""; 967 | DEVELOPMENT_TEAM = 2ZB33FC454; 968 | ENABLE_PREVIEWS = YES; 969 | INFOPLIST_FILE = SlipboxPad/Info.plist; 970 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 971 | LD_RUNPATH_SEARCH_PATHS = ( 972 | "$(inherited)", 973 | "@executable_path/Frameworks", 974 | ); 975 | PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.SlipboxPad; 976 | PRODUCT_NAME = "$(TARGET_NAME)"; 977 | SDKROOT = iphoneos; 978 | SWIFT_VERSION = 5.0; 979 | TARGETED_DEVICE_FAMILY = "1,2"; 980 | VALIDATE_PRODUCT = YES; 981 | }; 982 | name = Release; 983 | }; 984 | /* End XCBuildConfiguration section */ 985 | 986 | /* Begin XCConfigurationList section */ 987 | 175E4C282577880E001A155E /* Build configuration list for PBXProject "SlipboxApp" */ = { 988 | isa = XCConfigurationList; 989 | buildConfigurations = ( 990 | 175E4C5725778812001A155E /* Debug */, 991 | 175E4C5825778812001A155E /* Release */, 992 | ); 993 | defaultConfigurationIsVisible = 0; 994 | defaultConfigurationName = Release; 995 | }; 996 | 175E4C5925778812001A155E /* Build configuration list for PBXNativeTarget "SlipboxApp" */ = { 997 | isa = XCConfigurationList; 998 | buildConfigurations = ( 999 | 175E4C5A25778812001A155E /* Debug */, 1000 | 175E4C5B25778812001A155E /* Release */, 1001 | ); 1002 | defaultConfigurationIsVisible = 0; 1003 | defaultConfigurationName = Release; 1004 | }; 1005 | 175E4C5C25778812001A155E /* Build configuration list for PBXNativeTarget "SlipboxAppTests" */ = { 1006 | isa = XCConfigurationList; 1007 | buildConfigurations = ( 1008 | 175E4C5D25778812001A155E /* Debug */, 1009 | 175E4C5E25778812001A155E /* Release */, 1010 | ); 1011 | defaultConfigurationIsVisible = 0; 1012 | defaultConfigurationName = Release; 1013 | }; 1014 | 175E4C5F25778812001A155E /* Build configuration list for PBXNativeTarget "SlipboxAppUITests" */ = { 1015 | isa = XCConfigurationList; 1016 | buildConfigurations = ( 1017 | 175E4C6025778812001A155E /* Debug */, 1018 | 175E4C6125778812001A155E /* Release */, 1019 | ); 1020 | defaultConfigurationIsVisible = 0; 1021 | defaultConfigurationName = Release; 1022 | }; 1023 | 17988E2125778A3C002DD489 /* Build configuration list for PBXNativeTarget "SlipboxPad" */ = { 1024 | isa = XCConfigurationList; 1025 | buildConfigurations = ( 1026 | 17988E2225778A3C002DD489 /* Debug */, 1027 | 17988E2325778A3C002DD489 /* Release */, 1028 | ); 1029 | defaultConfigurationIsVisible = 0; 1030 | defaultConfigurationName = Release; 1031 | }; 1032 | /* End XCConfigurationList section */ 1033 | 1034 | /* Begin XCVersionGroup section */ 1035 | 175E4C342577880E001A155E /* SlipboxApp.xcdatamodeld */ = { 1036 | isa = XCVersionGroup; 1037 | children = ( 1038 | 175E4C352577880E001A155E /* SlipboxApp.xcdatamodel */, 1039 | ); 1040 | currentVersion = 175E4C352577880E001A155E /* SlipboxApp.xcdatamodel */; 1041 | path = SlipboxApp.xcdatamodeld; 1042 | sourceTree = ""; 1043 | versionGroupType = wrapper.xcdatamodel; 1044 | }; 1045 | /* End XCVersionGroup section */ 1046 | }; 1047 | rootObject = 175E4C252577880E001A155E /* Project object */; 1048 | } 1049 | -------------------------------------------------------------------------------- /SlipboxApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SlipboxApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SlipboxApp.xcodeproj/xcuserdata/karinprater.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SlipboxApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | SlipboxPad.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 175E4C2C2577880E001A155E 21 | 22 | primary 23 | 24 | 25 | 175E4C4425778811001A155E 26 | 27 | primary 28 | 29 | 30 | 175E4C4F25778811001A155E 31 | 32 | primary 33 | 34 | 35 | 17988E0F25778A3C002DD489 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.848", 9 | "green" : "0.063", 10 | "red" : "0.325" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/background1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/folder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "folder.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/folder.imageset/folder.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gahntpo/Slipbox/feac750595badc4b237bd5357443983698eb8ec1/SlipboxApp/Assets.xcassets/folder.imageset/folder.pdf -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/folderFull.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "folderFill.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/folderFull.imageset/folderFill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gahntpo/Slipbox/feac750595badc4b237bd5357443983698eb8ec1/SlipboxApp/Assets.xcassets/folderFull.imageset/folderFill.pdf -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/hashtag.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "hashtag.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/hashtag.imageset/hashtag.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gahntpo/Slipbox/feac750595badc4b237bd5357443983698eb8ec1/SlipboxApp/Assets.xcassets/hashtag.imageset/hashtag.pdf -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/hashtagFull.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "hashtagFull.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/hashtagFull.imageset/hashtagFull.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gahntpo/Slipbox/feac750595badc4b237bd5357443983698eb8ec1/SlipboxApp/Assets.xcassets/hashtagFull.imageset/hashtagFull.pdf -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/note.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "note.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/note.imageset/note.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gahntpo/Slipbox/feac750595badc4b237bd5357443983698eb8ec1/SlipboxApp/Assets.xcassets/note.imageset/note.pdf -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/noteFull.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "noteFull.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/noteFull.imageset/noteFull.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gahntpo/Slipbox/feac750595badc4b237bd5357443983698eb8ec1/SlipboxApp/Assets.xcassets/noteFull.imageset/noteFull.pdf -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/selectedColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "0.762", 10 | "red" : "0.753" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SlipboxApp/Assets.xcassets/unselectedColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.858", 9 | "green" : "0.858", 10 | "red" : "0.858" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SlipboxApp/Folder/DropStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropStatus.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 22.12.20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum DropStatus { 11 | case inActive 12 | case note 13 | case folderBefore 14 | case folderAfter 15 | case subfolder 16 | 17 | var folderRelated: Bool { 18 | switch self { 19 | case .folderAfter, .folderBefore, .subfolder: 20 | return true 21 | default: 22 | return false 23 | } 24 | } 25 | 26 | var dropAfter: Bool { 27 | switch self { 28 | case .folderAfter, .subfolder: 29 | return true 30 | default: 31 | return false 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /SlipboxApp/Folder/FolderEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderEditorView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 06.12.20. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct FolderEditorView: View { 12 | 13 | @State private var folderName: String = "" 14 | 15 | @Environment(\.presentationMode) var presentation 16 | 17 | @Environment(\.managedObjectContext) var context: NSManagedObjectContext 18 | 19 | @EnvironmentObject var nav: NavigationStateManager 20 | 21 | //NEW parent folder 22 | let editorStatus: FolderEditorStatus 23 | let contextFolder: Folder? 24 | 25 | var body: some View { 26 | VStack { 27 | 28 | if editorStatus == FolderEditorStatus.addAsSubFolder { 29 | Text("Add subfolder for \(contextFolder?.name ?? "")") 30 | .font(.title) 31 | }else { 32 | Text("Create new Folder").font(.title) 33 | } 34 | 35 | //else editorStatus == FolderEditorStatus.add 36 | 37 | TextField("name", text: $folderName) { _ in 38 | } onCommit: { 39 | addFolder() 40 | } 41 | 42 | 43 | HStack { 44 | Button(action: { 45 | presentation.wrappedValue.dismiss() 46 | }, label: { 47 | Text("Cancel") 48 | }) 49 | 50 | Button(action: { 51 | addFolder() 52 | }, label: { 53 | Text("Create") 54 | }) 55 | } 56 | 57 | }.padding() 58 | // .onAppear { 59 | // if editorStatus == .editContextFolder { 60 | // folderName = contextFolder?.name ?? "" 61 | // } 62 | // } 63 | } 64 | 65 | func addFolder() { 66 | let folder = Folder(name: folderName, context: context) 67 | if editorStatus == .addAsSubFolder { 68 | contextFolder?.add(subfolder: folder) 69 | } else if let beforeFolder = self.contextFolder, 70 | let parent = beforeFolder.parent { 71 | //NEW add the right index 72 | parent.add(subfolder: folder, at: beforeFolder.order + 1) 73 | } 74 | 75 | nav.selectedFolder = folder 76 | presentation.wrappedValue.dismiss() 77 | } 78 | } 79 | 80 | struct FolderEditorView_Previews: PreviewProvider { 81 | static var previews: some View { 82 | 83 | let context = PersistenceController.preview.container.viewContext 84 | 85 | return Group { 86 | FolderEditorView(editorStatus: .addFolder, contextFolder: Folder(name: "parent", context: context)) 87 | 88 | FolderEditorView(editorStatus: .addAsSubFolder, contextFolder: Folder(name: "parent", context: context)) 89 | 90 | } 91 | .environment(\.managedObjectContext, context) 92 | } 93 | } 94 | 95 | //MARK: - status helper 96 | enum FolderEditorStatus: String, Identifiable { 97 | 98 | case editContextFolder = "editContextFolder" 99 | case addFolder = "addFolder" 100 | case addAsSubFolder = "addAsSubFolder" 101 | 102 | var id: String { 103 | self.rawValue 104 | } 105 | 106 | func newFolder() -> Bool { 107 | switch self { 108 | case .addAsSubFolder, .addFolder: 109 | return true 110 | case .editContextFolder: 111 | return false 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /SlipboxApp/Folder/FolderListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderListView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 06.12.20. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct FolderListView: View { 12 | 13 | @EnvironmentObject var nav: NavigationStateManager 14 | 15 | @State private var makeNewFolderStatus: FolderEditorStatus? = nil 16 | 17 | @Environment(\.managedObjectContext) var context: NSManagedObjectContext 18 | 19 | @FetchRequest(fetchRequest: Folder.topFolderFetch()) var folders: FetchedResults 20 | 21 | var body: some View { 22 | VStack { 23 | HStack { 24 | Text("Folder").font(.title) 25 | 26 | Spacer() 27 | 28 | Button(action: { 29 | makeNewFolderStatus = FolderEditorStatus.addFolder 30 | }, label: { 31 | Image(systemName: "plus") 32 | 33 | }) 34 | }.padding([.horizontal, .top]) 35 | 36 | 37 | 38 | List { 39 | ForEach(folders) { folder in 40 | 41 | RecursiveFolderView(folder: folder) 42 | 43 | }.listRowInsets(.init(top: 0, leading: 0, bottom: 1, trailing: 0)) 44 | } 45 | 46 | } 47 | .frame(maxWidth: .infinity, maxHeight: .infinity) 48 | .sheet(item: $makeNewFolderStatus) { status in 49 | FolderEditorView(editorStatus: status, contextFolder: nav.selectedFolder) 50 | .environment(\.managedObjectContext, context) 51 | .environmentObject(nav) 52 | 53 | } 54 | // .sheet(isPresented: $makeNewFolder, content: { 55 | //FolderEditorView( 56 | // FolderEditorView(parentFolder: nil, referenceFolder: nil) 57 | // .environment(\.managedObjectContext, context) 58 | // }) 59 | } 60 | } 61 | 62 | struct FolderListView_Previews: PreviewProvider { 63 | static var previews: some View { 64 | 65 | FolderListView() 66 | .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 67 | .environmentObject(NavigationStateManager()) 68 | .frame(width: 200) 69 | } 70 | 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /SlipboxApp/Folder/FolderRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderRow$.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 06.12.20. 6 | // 7 | 8 | 9 | 10 | import SwiftUI 11 | 12 | struct FolderRow: View { 13 | 14 | @EnvironmentObject var nav: NavigationStateManager 15 | @ObservedObject var folder: Folder 16 | 17 | let selectedColor: Color = Color("selectedColor") 18 | let unSelectedColor: Color = Color("unselectedColor") 19 | let dropColor: Color = Color(.lightGray) 20 | 21 | @State private var makeNewFolderStatus: FolderEditorStatus? = nil 22 | @State private var showDelete: Bool = false 23 | @State private var edit: Bool = false 24 | @State private var dropStatus: DropStatus = .inActive 25 | 26 | var body: some View { 27 | 28 | VStack(spacing: 0) { 29 | 30 | RoundedRectangle(cornerRadius: 5) 31 | .fill(dropColor) 32 | .frame(height: dropStatus == .folderBefore ? 30 : 0) 33 | .padding(.bottom, 1) 34 | .animation(.easeInOut) 35 | 36 | ZStack { 37 | if edit { 38 | TextField("title", text: $folder.name) { _ in 39 | } onCommit: { 40 | edit.toggle() 41 | } 42 | .textFieldStyle(RoundedBorderTextFieldStyle()) 43 | 44 | }else { 45 | Text(folder.name) 46 | } 47 | } 48 | .frame(maxWidth: .infinity, alignment: .leading) 49 | .padding(5) 50 | .background(RoundedRectangle(cornerRadius: 5).fill(nav.selectedFolder == folder ? selectedColor : unSelectedColor)) 51 | 52 | .overlay(RoundedRectangle(cornerRadius: 5).stroke(showDelete ? Color.pink : Color.clear, lineWidth: 3)) 53 | 54 | RoundedRectangle(cornerRadius: 5) 55 | .fill(dropColor) 56 | .frame(height: dropStatus.dropAfter ? 30 : 0) 57 | .animation(.easeInOut) 58 | .padding(.bottom, 1) 59 | .padding(.leading, dropStatus == .subfolder ? 20 : 0) 60 | .animation(nil) 61 | 62 | Rectangle() 63 | .frame(height: dropStatus == .note ? 1 : 0) 64 | .animation(.easeInOut) 65 | .padding(dropStatus == .note ? 5 : 0) 66 | } 67 | 68 | 69 | .gesture(TapGesture(count: 2).onEnded({ _ in 70 | self.edit.toggle() 71 | })) 72 | 73 | .onTapGesture { 74 | nav.selectedFolder = folder 75 | } 76 | 77 | 78 | .onDrag({ NSItemProvider(object: DataTypeDragItem(id: folder.uuid.uuidString, type: DataType.folder.rawValue)) }) 79 | .onDrop(of: [kUTTypeData as String], delegate: FolderDropDelegate(destinationFolder: folder, dropStatus: $dropStatus)) 80 | 81 | 82 | .contextMenu(ContextMenu(menuItems: { 83 | Button(action: { 84 | self.edit.toggle() 85 | }, label: { 86 | Text("Rename folder") 87 | }) 88 | 89 | Divider() 90 | Button(action: { 91 | self.makeNewFolderStatus = .addFolder 92 | }, label: { 93 | Text("Add folder") 94 | }) 95 | 96 | Button(action: { 97 | self.makeNewFolderStatus = .addAsSubFolder 98 | }, label: { 99 | Text("Add subfolder") 100 | }) 101 | 102 | Divider() 103 | 104 | //TODO: delete alert 105 | Button(action: { 106 | showDelete.toggle() 107 | }, label: { 108 | Text("Delete") 109 | }) 110 | 111 | })) 112 | 113 | //MARK: - presentations 114 | .alert(isPresented: $showDelete, content: { 115 | Alert(title: Text("Do you really want to delete this folder?"), 116 | message: nil, 117 | primaryButton: Alert.Button.cancel(), 118 | secondaryButton: Alert.Button.destructive(Text("Delete"), action: { 119 | if folder == nav.selectedFolder { 120 | nav.selectedFolder = nil 121 | } 122 | Folder.delete(folder) 123 | 124 | })) 125 | }) 126 | 127 | .sheet(item: $makeNewFolderStatus) { status in 128 | FolderEditorView(editorStatus: status, contextFolder: folder) 129 | } 130 | 131 | .onReceive(nav.$selectedFolder, perform: { _ in 132 | self.edit = false 133 | }) 134 | .onReceive(nav.$selectedNote, perform: { _ in 135 | self.edit = false 136 | }) 137 | 138 | } 139 | 140 | //MARK: - DropDelegate 141 | struct FolderDropDelegate: DropDelegate { 142 | 143 | let destinationFolder: Folder 144 | @Binding var dropStatus: DropStatus 145 | 146 | func dropEntered(info: DropInfo) { 147 | //check folder or note 148 | let providers = info.itemProviders(for: [kUTTypeData as String]) 149 | _ = providers.loadObjects(ofType: DataTypeDragItem.self) { (item) in 150 | 151 | if destinationFolder.uuid.uuidString == item.id { 152 | return 153 | }else if item.type == DataType.note.rawValue { 154 | dropStatus = .note 155 | }else if item.type == DataType.folder.rawValue { 156 | changeFolder(location: info.location) 157 | }else { 158 | dropStatus = .inActive 159 | } 160 | } 161 | } 162 | 163 | func changeFolder(location: CGPoint) { 164 | if location.y < 10 { 165 | dropStatus = .folderBefore 166 | }else { 167 | if location.x < 60 { 168 | dropStatus = .folderAfter 169 | }else { 170 | dropStatus = .subfolder 171 | } 172 | } 173 | 174 | } 175 | 176 | func dropUpdated(info: DropInfo) -> DropProposal? { 177 | if dropStatus.folderRelated { 178 | changeFolder(location: info.location) 179 | } 180 | return nil 181 | } 182 | 183 | func dropExited(info: DropInfo) { 184 | finishDrop() 185 | } 186 | 187 | func finishDrop() { 188 | dropStatus = .inActive 189 | NotificationCenter.default.post(name: .finishDrop, object: nil) 190 | } 191 | 192 | func performDrop(info: DropInfo) -> Bool { 193 | let providers = info.itemProviders(for: [kUTTypeData as String]) 194 | let found = destinationFolder.handleMovedDrop(with: providers, dropStatus: dropStatus) 195 | 196 | finishDrop() 197 | return found 198 | } 199 | } 200 | 201 | } 202 | 203 | struct FolderRow_Previews: PreviewProvider { 204 | static var previews: some View { 205 | 206 | let context = PersistenceController.preview.container.viewContext 207 | let nav = NavigationStateManager() 208 | let folder = Folder(name: "selected Folder", context: context) 209 | nav.selectedFolder = folder 210 | 211 | return List { 212 | FolderRow(folder: Folder.nestedFolder(context: context)) 213 | FolderRow(folder: folder) 214 | 215 | }.frame(width: 200) 216 | 217 | .environmentObject(nav) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /SlipboxApp/Folder/RecursiveFolderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecursiveFolderView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 07.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RecursiveFolderView: View { 11 | 12 | @ObservedObject var folder: Folder 13 | 14 | @EnvironmentObject var nav: NavigationStateManager 15 | 16 | @State private var showSubfolders: Bool = true 17 | 18 | var body: some View { 19 | Group { 20 | 21 | HStack { 22 | Text("\(folder.order)").bold() 23 | FolderRow(folder: folder) 24 | Spacer() 25 | 26 | if folder.children.count > 0 { 27 | Button(action: { 28 | withAnimation { 29 | showSubfolders.toggle() 30 | } 31 | 32 | }, label: { 33 | Image(systemName: "chevron.right") 34 | .rotationEffect(.init(degrees: showSubfolders ? 90 : 0)) 35 | 36 | }).buttonStyle(PlainButtonStyle()) 37 | } 38 | } 39 | 40 | if showSubfolders { 41 | ForEach(folder.children.sorted(), content: { child in 42 | 43 | RecursiveFolderView(folder: child).padding(.leading) 44 | }) 45 | 46 | } 47 | } 48 | } 49 | } 50 | 51 | struct RecursiveFolderView_Previews: PreviewProvider { 52 | static var previews: some View { 53 | 54 | List { 55 | RecursiveFolderView(folder: Folder.nestedFolder(context: PersistenceController.preview.container.viewContext)) 56 | } 57 | .frame(width: 200) 58 | .environmentObject(NavigationStateManager()) 59 | 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SlipboxApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.productivity 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /SlipboxApp/Keyword/KeywordEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeywordEditorView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 11.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct KeywordEditorView: View { 11 | @Environment(\.managedObjectContext) var context 12 | 13 | @Environment(\.presentationMode) var presentation 14 | 15 | @State private var name: String = "" 16 | 17 | var body: some View { 18 | 19 | VStack(spacing: 30) { 20 | Text("Create new keyword").font(.title) 21 | 22 | TextField("keyword", text: $name) 23 | .textFieldStyle(RoundedBorderTextFieldStyle()) 24 | .frame(maxWidth: 200) 25 | 26 | HStack { 27 | Button(action: { 28 | presentation.wrappedValue.dismiss() 29 | }, label: { 30 | Text("Cancel") 31 | }) 32 | 33 | Button(action: { 34 | _ = Keyword(name: name, context: context) 35 | presentation.wrappedValue.dismiss() 36 | }, label: { 37 | Text("Create") 38 | }).disabled(name.count == 0) 39 | } 40 | 41 | }.padding().padding() 42 | 43 | } 44 | } 45 | 46 | struct KeywordEditorView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | KeywordEditorView() 49 | .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SlipboxApp/Keyword/KeywordListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeywordListView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 11.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct KeywordListView: View { 11 | 12 | @Environment(\.managedObjectContext) var context 13 | 14 | @FetchRequest(fetchRequest: Keyword.fetch(.all)) var keywords 15 | 16 | @State private var newKeyword: Bool = false 17 | 18 | var body: some View { 19 | 20 | VStack { 21 | 22 | HStack { 23 | Text("Keywords").font(.title) 24 | Spacer() 25 | Button(action: { 26 | newKeyword.toggle() 27 | }, label: { 28 | Image(systemName: "plus") 29 | }) 30 | }.padding([.top, .horizontal]) 31 | 32 | List { 33 | ForEach(keywords) { keyword in 34 | KeywordRow(keyword: keyword) 35 | } 36 | } 37 | } 38 | .sheet(isPresented: $newKeyword, content: { 39 | KeywordEditorView() 40 | .environment(\.managedObjectContext, context) 41 | }) 42 | 43 | } 44 | } 45 | 46 | struct KeywordListView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | KeywordListView() 49 | .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 50 | 51 | .environmentObject(NavigationStateManager()) 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SlipboxApp/Keyword/KeywordRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeywordRow.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 11.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct KeywordRow: View { 11 | 12 | @EnvironmentObject var nav: NavigationStateManager 13 | 14 | @ObservedObject var keyword: Keyword 15 | 16 | let selectedColor: Color = Color("selectedColor") 17 | let unSelectedColor: Color = Color("unselectedColor") 18 | 19 | @State private var deleteKey: Bool = false 20 | 21 | var body: some View { 22 | HStack(spacing: 20) { 23 | Text(keyword.name) 24 | 25 | Text("\(keyword.notes.count)").padding(5) 26 | .background(Circle().fill(Color.white)) 27 | } 28 | .padding(5) 29 | .background(RoundedRectangle(cornerRadius: 5).fill(nav.selectedKewords.contains(keyword) ? selectedColor : unSelectedColor)) 30 | .onTapGesture { 31 | if nav.selectedKewords.contains(keyword) { 32 | nav.selectedKewords.remove(keyword) 33 | }else { 34 | nav.selectedKewords.insert(keyword) 35 | } 36 | } 37 | 38 | .onDrag({ NSItemProvider(object: DataTypeDragItem(id: keyword.uuid.uuidString, type: DataType.keyword.rawValue)) }) 39 | 40 | .contextMenu(ContextMenu(menuItems: { 41 | Button { 42 | deleteKey.toggle() 43 | } label: { 44 | Text("Delete") 45 | } 46 | })) 47 | 48 | .alert(isPresented: $deleteKey, content: { 49 | Alert(title: Text("Are you sure to delete keyword '\(keyword.name)'? "), message: nil, primaryButton: Alert.Button.cancel(), secondaryButton: .destructive(Text("Delete"), action: { 50 | if nav.selectedKewords.contains(keyword) { 51 | nav.selectedKewords.remove(keyword) 52 | } 53 | Keyword.delete(keyword: keyword) 54 | })) 55 | }) 56 | 57 | } 58 | } 59 | 60 | struct KeywordRow_Previews: PreviewProvider { 61 | static var previews: some View { 62 | 63 | let nav = NavigationStateManager() 64 | let selecedKeyword = Keyword(name: "selected", context: PersistenceController.preview.container.viewContext) 65 | nav.selectedKewords.insert(selecedKeyword) 66 | 67 | return VStack { 68 | KeywordRow(keyword: Keyword(name: "new", context: PersistenceController.preview.container.viewContext)) 69 | 70 | KeywordRow(keyword: selecedKeyword) 71 | 72 | } 73 | .frame(width: 300, height: 300, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) 74 | .previewLayout(.fixed(width: 300, height: 300)) 75 | 76 | .environmentObject(nav) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SlipboxApp/Notes/NoteListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteListView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 03.12.20. 6 | // 7 | 8 | import SwiftUI 9 | #if os(iOS) 10 | import MobileCoreServices 11 | #endif 12 | 13 | 14 | struct NoteListView: View { 15 | 16 | init(folder: Folder?, selectedNote: Binding) { 17 | self._selectedNote = selectedNote 18 | 19 | var predicate = NSPredicate.none 20 | if let folder = folder { 21 | //predicate = NSPredicate(format: "folder == %@ ", folder) 22 | predicate = NSPredicate(format: "%K == %@ ",NoteProperties.folder, folder) 23 | } 24 | self._notes = FetchRequest(fetchRequest: Note.fetch(predicate)) 25 | self.folder = folder 26 | } 27 | 28 | let folder: Folder? 29 | 30 | @Binding var selectedNote: Note? 31 | 32 | @State private var shouldDeleteNote: Note? = nil 33 | 34 | @Environment(\.managedObjectContext) private var context 35 | 36 | @EnvironmentObject var nav: NavigationStateManager 37 | 38 | @FetchRequest(fetchRequest: Note.fetch(NSPredicate.all)) private var notes: FetchedResults 39 | 40 | 41 | var body: some View { 42 | VStack { 43 | HStack { 44 | Text("Notes") 45 | .font(.title) 46 | 47 | Spacer() 48 | 49 | Button(action: { 50 | createNewNote() 51 | 52 | }, label: { 53 | Image(systemName: "plus") 54 | // Text("Add") 55 | }).disabled(folder == nil) 56 | 57 | .onReceive(NotificationCenter.default.publisher(for: .newNote), perform: { _ in 58 | if nav.isKey { 59 | createNewNote() 60 | } 61 | }) 62 | 63 | 64 | 65 | }.padding([.top, .horizontal]) 66 | 67 | List { 68 | 69 | ForEach(notes) { note in 70 | 71 | NoteRow(note: note) 72 | 73 | .onTapGesture { 74 | selectedNote = note 75 | } 76 | 77 | .contextMenu(ContextMenu(menuItems: { 78 | 79 | Button(action: { 80 | self.shouldDeleteNote = note 81 | 82 | }, label: { 83 | Text("Delete") 84 | }) 85 | })) 86 | } 87 | .listRowInsets(.init(top: 0, leading: 0, bottom: 1, trailing: 0)) 88 | 89 | } 90 | 91 | .alert(item: $shouldDeleteNote) { noteToDelete in 92 | deleteAlert(note: noteToDelete) 93 | } 94 | } 95 | } 96 | 97 | func deleteAlert(note: Note) -> Alert { 98 | Alert(title: Text("Are you sure to delete this note?"), 99 | message: nil, 100 | primaryButton: Alert.Button.cancel(), 101 | secondaryButton: Alert.Button.destructive(Text("Delete"), action: { 102 | if selectedNote == note { 103 | selectedNote = nil 104 | } 105 | Note.delete(note: note) 106 | })) 107 | } 108 | 109 | func createNewNote() { 110 | let note = Note(title: "new note", context: context) 111 | 112 | folder?.add(note: note, at: selectedNote?.order) 113 | selectedNote = note 114 | } 115 | 116 | } 117 | 118 | 119 | 120 | 121 | struct NoteListView_Previews: PreviewProvider { 122 | static var previews: some View { 123 | 124 | let context = PersistenceController.preview.container.viewContext 125 | let request = Note.fetch(NSPredicate.all) 126 | let fechtedNotes = try? context.fetch(request) 127 | let folder = Folder(context: context) 128 | for note in fechtedNotes! { 129 | folder.add(note: note) 130 | } 131 | 132 | return NoteListView(folder: folder, selectedNote: .constant(fechtedNotes?.last)) 133 | .environment(\.managedObjectContext, context) 134 | } 135 | } 136 | 137 | 138 | -------------------------------------------------------------------------------- /SlipboxApp/Notes/NoteRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteRow.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 05.12.20. 6 | // 7 | #if os(iOS) 8 | import MobileCoreServices 9 | #endif 10 | 11 | import SwiftUI 12 | 13 | struct NoteRow: View { 14 | 15 | @Environment(\.managedObjectContext) var context 16 | @State private var isDropTargeted: Bool = false 17 | 18 | @ObservedObject var note: Note 19 | @EnvironmentObject var nav: NavigationStateManager 20 | 21 | let selectedColor: Color = Color("selectedColor") 22 | let unSelectedColor: Color = Color("unselectedColor") 23 | 24 | var body: some View { 25 | 26 | VStack(alignment: .leading, spacing: 3) { 27 | HStack { 28 | 29 | Text(note.title)//.bold() 30 | Spacer() 31 | Text(note.creationDate , formatter: itemFormatter) 32 | .font(.footnote) 33 | 34 | } 35 | 36 | Text(note.bodyText) 37 | .lineLimit(3) 38 | .font(.caption) 39 | 40 | } 41 | .padding(5) 42 | 43 | .background(RoundedRectangle(cornerRadius: 5).fill(nav.selectedNote == note ? selectedColor : unSelectedColor)) 44 | .animation(nil) 45 | .padding(.top, isDropTargeted ? 50 : 0) 46 | .animation(.easeInOut) 47 | 48 | .onDrag({ NSItemProvider(object: DataTypeDragItem(id: note.uuid.uuidString, type: DataType.note.rawValue)) }) 49 | 50 | .onDrop(of: [dragTypeID, kUTTypeData as String], isTargeted: $isDropTargeted, perform: { providers in 51 | note.handleMove(providers: providers) 52 | }) 53 | 54 | } 55 | } 56 | 57 | private let itemFormatter: DateFormatter = { 58 | let formatter = DateFormatter() 59 | formatter.dateStyle = .short 60 | formatter.timeStyle = .medium 61 | return formatter 62 | }() 63 | 64 | struct NoteRow_Previews: PreviewProvider { 65 | static var previews: some View { 66 | 67 | let note = Note.defaultNote(context: PersistenceController.preview.container.viewContext) 68 | let nav = NavigationStateManager() 69 | nav.selectedNote = note 70 | 71 | return VStack(spacing: 5) { 72 | NoteRow(note: note) 73 | NoteRow(note: Note.defaultNote(context: PersistenceController.preview.container.viewContext)) 74 | 75 | }.padding() 76 | .frame(width: 250) 77 | 78 | .environmentObject(NavigationStateManager()) 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /SlipboxApp/Notes/NoteSearchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteSearchView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 13.12.20. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct NoteSearchView: View { 12 | 13 | @EnvironmentObject var nav: NavigationStateManager 14 | 15 | init(pred: NSPredicate) { 16 | let request = Note.fetch(pred) 17 | self._notes = FetchRequest(fetchRequest: request) 18 | } 19 | 20 | @FetchRequest(fetchRequest: Note.fetch(NSPredicate.all)) private var notes: FetchedResults 21 | 22 | var body: some View { 23 | VStack(spacing: 0) { 24 | Text("Your search results") 25 | .font(.title) 26 | .foregroundColor(.white) 27 | .padding([.top, .horizontal]) 28 | .frame(maxWidth: .infinity) 29 | .background(Color.black) 30 | 31 | List { 32 | 33 | ForEach(notes) { note in 34 | NoteRow(note: note) 35 | .onTapGesture { 36 | nav.selectedNote = note 37 | } 38 | } 39 | .listRowInsets(.init(top: 0, leading: 0, bottom: 1, trailing: 0)) 40 | 41 | } 42 | }.onAppear { 43 | nav.selectedNote = notes.first 44 | } 45 | } 46 | } 47 | 48 | struct NoteSearchView_Previews: PreviewProvider { 49 | static var previews: some View { 50 | NoteSearchView(pred: .all) 51 | .environmentObject(NavigationStateManager()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SlipboxApp/Notes/NoteView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 03.12.20. 6 | // 7 | 8 | #if os(iOS) 9 | import MobileCoreServices 10 | #endif 11 | 12 | import SwiftUI 13 | import CoreData 14 | 15 | struct NoteView: View { 16 | 17 | @EnvironmentObject var nav: NavigationStateManager 18 | 19 | @Environment(\.managedObjectContext) var context: NSManagedObjectContext 20 | 21 | @ObservedObject var note: Note 22 | 23 | @State private var isDropTargeted: Bool = false 24 | 25 | var body: some View { 26 | 27 | VStack(alignment: .leading, spacing: 10) { 28 | 29 | Picker(selection: $note.status, label: Text("Status"), content: { 30 | ForEach(Status.allCases, id: \.self) { status in 31 | Text(status.rawValue) 32 | } 33 | }).pickerStyle(SegmentedPickerStyle()) 34 | .frame(maxWidth: 250) 35 | .frame(maxWidth: .infinity, alignment: .trailing) 36 | 37 | TextField("notes title", text: $note.title) 38 | //.font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/) 39 | .textFieldStyle(RoundedBorderTextFieldStyle()) 40 | 41 | #if os(macOS) 42 | //TextEditor(text: $note.bodyText) 43 | TextViewWrapper(note: note) 44 | #endif 45 | 46 | OptionalImage(data: note.img) 47 | .contextMenu(ContextMenu(menuItems: { 48 | Button(action: { 49 | note.img = nil 50 | }, label: { 51 | Text("Remove Image") 52 | }) 53 | })) 54 | 55 | 56 | 57 | HStack { 58 | Text("Keywords:") 59 | 60 | ForEach(note.keywords.sorted()) { key in 61 | Text(key.name) 62 | } 63 | } 64 | 65 | HStack(alignment: .top) { 66 | Text("linked Notes:") 67 | VStack(alignment: .leading) { 68 | ForEach(note.linkedNotes.sorted()) { link in 69 | Button(action: { 70 | nav.selectedNote = link 71 | nav.selectedFolder = link.folder 72 | }, label: { 73 | Text(link.title).bold() 74 | }).buttonStyle(PlainButtonStyle()) 75 | .foregroundColor(.accentColor) 76 | .contextMenu { 77 | #if os(macOS) 78 | Button { 79 | if let app = NSApplication.shared.delegate as? AppDelegate { 80 | 81 | app.makeWindow(for: link) 82 | 83 | } 84 | 85 | } label: { 86 | Text("open in new window") 87 | } 88 | #endif 89 | 90 | //TODO 91 | Button { 92 | 93 | } label: { 94 | Text("Delete link") 95 | } 96 | 97 | 98 | } 99 | 100 | } 101 | } 102 | } 103 | 104 | }.padding() 105 | .background(isDropTargeted ? Color.gray : Color.clear) 106 | .onDrop(of: ["public.url", "public.file-url", kUTTypeData as String, dragTypeID], isTargeted: $isDropTargeted, perform: { providers in 107 | note.handleDrop(providers: providers) 108 | }) 109 | } 110 | 111 | } 112 | 113 | struct NoteView_Previews: PreviewProvider { 114 | static var previews: some View { 115 | NoteView(note: Note.defaultNote(context: PersistenceController.preview.container.viewContext)) 116 | .frame(width: 400, height: 400) 117 | .previewLayout(.fixed(width: /*@START_MENU_TOKEN@*/400.0/*@END_MENU_TOKEN@*/, height: /*@START_MENU_TOKEN@*/400.0/*@END_MENU_TOKEN@*/)) 118 | 119 | .environmentObject(NavigationStateManager()) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /SlipboxApp/Notes/OptionalImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalImage.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 16.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct OptionalImage: View { 11 | 12 | let data: Data? 13 | 14 | var body: some View { 15 | Group { 16 | if data != nil { 17 | #if os(macOS) 18 | 19 | Image(nsImage: NSImage(data:data!) ?? NSImage()) 20 | .resizable() 21 | .scaledToFit() 22 | .onDrag({ 23 | let provider = NSItemProvider(item: data as NSSecureCoding?, typeIdentifier: kUTTypeTIFF as String) 24 | return provider 25 | 26 | }) 27 | 28 | #else 29 | Image(uiImage: UIImage(data: data!) ?? UIImage()) 30 | .resizable() 31 | .scaledToFit() 32 | .onDrag { 33 | let provider = NSItemProvider(object: UIImage(data: data!) ?? UIImage()) 34 | provider.suggestedName = "cat" 35 | return provider 36 | } 37 | 38 | #endif 39 | } 40 | } 41 | } 42 | } 43 | 44 | //struct OptionalImage_Previews: PreviewProvider { 45 | // static var previews: some View { 46 | // OptionalImage() 47 | // } 48 | //} 49 | -------------------------------------------------------------------------------- /SlipboxApp/Notes/TextViewWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextViewWrapper.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 05.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TextViewWrapper: NSViewRepresentable { 11 | 12 | let note: Note 13 | 14 | func makeCoordinator() -> Coordinator { 15 | Coordinator(self, note: note) 16 | } 17 | 18 | func makeNSView(context: Context) -> NSTextView { 19 | let nsview = NSTextView() 20 | 21 | nsview.isRichText = true 22 | nsview.isEditable = true 23 | nsview.isSelectable = true 24 | nsview.allowsUndo = true 25 | 26 | nsview.usesInspectorBar = true 27 | 28 | nsview.usesFindPanel = true 29 | nsview.usesFindBar = true 30 | 31 | nsview.isGrammarCheckingEnabled = true 32 | nsview.isContinuousSpellCheckingEnabled = true 33 | 34 | nsview.usesRuler = true 35 | 36 | nsview.textStorage?.setAttributedString(note.formattedText) 37 | nsview.delegate = context.coordinator 38 | 39 | return nsview 40 | } 41 | 42 | func updateNSView(_ nsView: NSTextView, context: Context) { 43 | nsView.textStorage?.setAttributedString(note.formattedText) 44 | context.coordinator.note = note 45 | } 46 | 47 | 48 | class Coordinator: NSObject, NSTextViewDelegate { 49 | 50 | var parent: TextViewWrapper 51 | var note: Note 52 | 53 | init(_ parent: TextViewWrapper, note: Note) { 54 | self.parent = parent 55 | self.note = note 56 | } 57 | 58 | func textDidChange(_ notification: Notification) { 59 | if let textview = notification.object as? NSTextView { 60 | 61 | note.formattedText = textview.attributedString() 62 | } 63 | 64 | } 65 | } 66 | 67 | 68 | } 69 | 70 | struct TextViewWrapper_Previews: PreviewProvider { 71 | static var previews: some View { 72 | TextViewWrapper(note: Note(context: PersistenceController.preview.container.viewContext)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SlipboxApp/Notes/helper for nsitem conversion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // helper for nsitem conversion.swift 3 | // ZettelkastenApp 4 | // 5 | // Created by Karin Prater on 05.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | // simplifies the drag/drop portion of the demo 11 | // you might be able to grok this 12 | // but it does use a generic function 13 | // and also is doing multithreaded stuff here 14 | // and also is bridging to Objective-C-based API 15 | // so kind of too much to talk about during lecture at this point in the game! 16 | 17 | extension Array where Element == NSItemProvider { 18 | 19 | func loadObjects(ofType theType: T.Type, firstOnly: Bool = false, using load: @escaping (T) -> Void) -> Bool where T: NSItemProviderReading { 20 | if let provider = self.first(where: { $0.canLoadObject(ofClass: theType) }) { 21 | provider.loadObject(ofClass: theType) { object, error in 22 | if let value = object as? T { 23 | DispatchQueue.main.async { 24 | load(value) 25 | } 26 | } 27 | } 28 | return true 29 | } 30 | return false 31 | } 32 | func loadObjects(ofType theType: T.Type, firstOnly: Bool = false, using load: @escaping (T) -> Void) -> Bool where T: _ObjectiveCBridgeable, T._ObjectiveCType: NSItemProviderReading { 33 | if let provider = self.first(where: { $0.canLoadObject(ofClass: theType) }) { 34 | let _ = provider.loadObject(ofClass: theType) { object, error in 35 | if let value = object { 36 | DispatchQueue.main.async { 37 | load(value) 38 | } 39 | } 40 | } 41 | return true 42 | } 43 | return false 44 | } 45 | func loadFirstObject(ofType theType: T.Type, using load: @escaping (T) -> Void) -> Bool where T: NSItemProviderReading { 46 | self.loadObjects(ofType: theType, firstOnly: true, using: load) 47 | } 48 | func loadFirstObject(ofType theType: T.Type, using load: @escaping (T) -> Void) -> Bool where T: _ObjectiveCBridgeable, T._ObjectiveCType: NSItemProviderReading { 49 | self.loadObjects(ofType: theType, firstOnly: true, using: load) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SlipboxApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SlipboxApp/SlipboxApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.com.karinprater.SlipboxApp 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | com.apple.security.app-sandbox 16 | 17 | com.apple.security.files.user-selected.read-only 18 | 19 | com.apple.security.network.client 20 | 21 | com.apple.security.network.server 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SlipboxApp/app/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import Cocoa 9 | import SwiftUI 10 | 11 | @main 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | var windowContents = [WindowContentView]() 15 | var prefWindow: PreferenceWindow? 16 | 17 | func applicationDidFinishLaunching(_ aNotification: Notification) { 18 | // UnitTestHelpers.deletesAllNotes(context: PersistenceController.shared.container.viewContext) 19 | makeEmptWindow() 20 | 21 | } 22 | 23 | func makeEmptWindow() { 24 | let newWindow = WindowContentView(nav: NavigationStateManager()) 25 | windowContents.append(newWindow) 26 | } 27 | 28 | func makeWindow(for note: Note) { 29 | 30 | if let openWindow = windowContents.first(where: { $0.nav.selectedNote == note }), openWindow.windowDelegate.windowIsOpen { 31 | openWindow.window.makeKeyAndOrderFront(nil) 32 | }else { 33 | 34 | let nav = NavigationStateManager() 35 | nav.selectedNote = note 36 | nav.selectedFolder = note.folder 37 | nav.showNotesColumn = false 38 | nav.showFolderColumn = false 39 | nav.showKeyColumn = false 40 | 41 | let window = WindowContentView(nav: nav) 42 | windowContents.append(window) 43 | } 44 | 45 | } 46 | 47 | func applicationWillTerminate(_ aNotification: Notification) { 48 | // Insert code here to tear down your application 49 | } 50 | 51 | //MARK: - menu 52 | @IBAction func newWindw(_ sender: NSMenuItem) { 53 | makeEmptWindow() 54 | } 55 | 56 | @IBAction func newNote(_ sender: NSMenuItem) { 57 | NotificationCenter.default.post(name: NSNotification.Name.newNote, object: nil) 58 | } 59 | 60 | @IBAction func newFolder(_ sender: NSMenuItem) { 61 | NotificationCenter.default.post(name: NSNotification.Name.newFolder, object: nil) 62 | } 63 | 64 | @IBAction func preference(_ sender: NSMenuItem) { 65 | if let old = prefWindow, old.windowDelegate.windowIsOpen { 66 | old.window.makeKeyAndOrderFront(nil) 67 | }else { 68 | prefWindow = PreferenceWindow() 69 | } 70 | 71 | } 72 | 73 | 74 | // MARK: - Core Data stack 75 | let persistenceController = PersistenceController.shared 76 | 77 | // MARK: - Core Data Saving and Undo support 78 | 79 | @IBAction func saveAction(_ sender: AnyObject?) { 80 | // Performs the save action for the application, which is to send the save: message to the application's managed object context. Any encountered errors are presented to the user. 81 | let context = persistenceController.container.viewContext 82 | 83 | if !context.commitEditing() { 84 | NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing before saving") 85 | } 86 | if context.hasChanges { 87 | do { 88 | try context.save() 89 | } catch { 90 | // Customize this code block to include application-specific recovery steps. 91 | let nserror = error as NSError 92 | NSApplication.shared.presentError(nserror) 93 | } 94 | } 95 | } 96 | 97 | func windowWillReturnUndoManager(window: NSWindow) -> UndoManager? { 98 | // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application. 99 | return persistenceController.container.viewContext.undoManager 100 | } 101 | 102 | func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { 103 | // Save changes in the application's managed object context before the application terminates. 104 | let context = persistenceController.container.viewContext 105 | 106 | if !context.commitEditing() { 107 | NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing to terminate") 108 | return .terminateCancel 109 | } 110 | 111 | if !context.hasChanges { 112 | return .terminateNow 113 | } 114 | 115 | do { 116 | try context.save() 117 | } catch { 118 | let nserror = error as NSError 119 | 120 | // Customize this code block to include application-specific recovery steps. 121 | let result = sender.presentError(nserror) 122 | if (result) { 123 | return .terminateCancel 124 | } 125 | 126 | let question = NSLocalizedString("Could not save changes while quitting. Quit anyway?", comment: "Quit without saves error question message") 127 | let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info"); 128 | let quitButton = NSLocalizedString("Quit anyway", comment: "Quit anyway button title") 129 | let cancelButton = NSLocalizedString("Cancel", comment: "Cancel button title") 130 | let alert = NSAlert() 131 | alert.messageText = question 132 | alert.informativeText = info 133 | alert.addButton(withTitle: quitButton) 134 | alert.addButton(withTitle: cancelButton) 135 | 136 | let answer = alert.runModal() 137 | if answer == .alertSecondButtonReturn { 138 | return .terminateCancel 139 | } 140 | } 141 | // If we got here, it is time to quit. 142 | return .terminateNow 143 | } 144 | 145 | } 146 | 147 | -------------------------------------------------------------------------------- /SlipboxApp/app/NSNotification.Name + helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSNotification.Name + helper.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 12.12.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Notification.Name { 11 | 12 | static let newNote = Notification.Name("newNote") 13 | 14 | static let newFolder = Notification.Name("newFolder") 15 | 16 | static let finishDrop = Notification.Name("finishDrop") 17 | 18 | } 19 | -------------------------------------------------------------------------------- /SlipboxApp/drag and drop/DataType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataType.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 08.12.20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum DataType: String, CaseIterable, Codable { 11 | 12 | case keyword = "keyword" 13 | case note = "note" 14 | case folder = "folder" 15 | case reference = "reference" 16 | 17 | static func type(string: String) -> DataType? { 18 | 19 | if string == keyword.rawValue { 20 | return DataType.keyword 21 | }else if string == note.rawValue { 22 | return DataType.note 23 | }else if string == folder.rawValue { 24 | return DataType.folder 25 | }else if string == reference.rawValue { 26 | return DataType.reference 27 | }else { 28 | return nil 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SlipboxApp/drag and drop/DataTypeDragItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataTypeDragItem.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 08.12.20. 6 | // 7 | 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | #if os(iOS) 13 | import MobileCoreServices 14 | #endif 15 | 16 | public let dragTypeID = "com.slipboxapp.datatype" 17 | 18 | enum EncodingError: Error { 19 | case invalidData 20 | } 21 | 22 | public class DataTypeDragItem: NSObject, Codable { 23 | 24 | // MARK: - Properties 25 | public var id: String? 26 | public var type: String? 27 | 28 | // MARK: - Initialization 29 | public required init( 30 | id: String? = nil, 31 | type: String? = nil 32 | ) { 33 | self.id = id 34 | self.type = type 35 | } 36 | 37 | public required init(_ info: DataTypeDragItem) { 38 | self.id = info.id 39 | self.type = info.type 40 | super.init() 41 | } 42 | } 43 | 44 | // MARK: - NSItemProviderWriting 45 | extension DataTypeDragItem: NSItemProviderWriting { 46 | 47 | public static var writableTypeIdentifiersForItemProvider: [String] { 48 | //Different than in tutorial 49 | //there is a problem for macos 50 | //if you are on IOS the dragTypeId is enough 51 | return [dragTypeID, kUTTypeData as String] 52 | } 53 | 54 | public func loadData( 55 | withTypeIdentifier typeIdentifier: String, 56 | forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? { 57 | 58 | let progress = Progress(totalUnitCount: 100) 59 | do { 60 | let coder = JSONEncoder() 61 | let myJSON = try coder.encode(self) 62 | progress.completedUnitCount = 100 63 | completionHandler(myJSON, nil) 64 | 65 | } catch { 66 | print("error \(error.localizedDescription)") 67 | completionHandler(nil, error) 68 | } 69 | 70 | return progress 71 | } 72 | } 73 | 74 | 75 | 76 | 77 | 78 | // MARK: - NSItemProviderReading 79 | extension DataTypeDragItem: NSItemProviderReading { 80 | public static var readableTypeIdentifiersForItemProvider: [String] { 81 | //Different than in tutorial 82 | //there is a problem for macos 83 | //if you are on IOS the dragTypeId is enough 84 | return [dragTypeID, kUTTypeData as String] 85 | } 86 | 87 | public static func object(withItemProviderData data: Data, 88 | typeIdentifier: String) throws -> Self { 89 | 90 | let decoder = JSONDecoder() 91 | 92 | do { 93 | let item = try decoder.decode(DataTypeDragItem.self, from: data) 94 | return self.init(item) 95 | 96 | } catch { 97 | throw EncodingError.invalidData 98 | } 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /SlipboxApp/drag and drop/URL+helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+helper.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 15.12.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URL { 11 | var imageURL: URL { 12 | // check to see if there is an embedded imgurl reference 13 | for query in query?.components(separatedBy: "&") ?? [] { 14 | let queryComponents = query.components(separatedBy: "=") 15 | if queryComponents.count == 2 { 16 | if queryComponents[0] == "imgurl", let url = URL(string: queryComponents[1].removingPercentEncoding ?? "") { 17 | return url 18 | } 19 | } 20 | } 21 | // this snippet supports the demo in Lecture 14 22 | // see storeInFilesystem below 23 | if isFileURL { 24 | var url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first 25 | url = url?.appendingPathComponent(self.lastPathComponent) 26 | if url != nil { 27 | return url! 28 | } 29 | } 30 | return self.baseURL ?? self 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SlipboxApp/main window/AppToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppToolbar.swift 3 | // SlipboxApp 4 | 5 | 6 | import Cocoa 7 | 8 | class AppToolbar: NSToolbar, NSToolbarDelegate, NSSearchFieldDelegate { 9 | 10 | var searchTextField: NSView! 11 | 12 | var keywordColumnToggle: NSView! 13 | var folderColumnToggle: NSView! 14 | var noteColumnToggle: NSView! 15 | 16 | var statusPicker: NSView! 17 | 18 | var keyImage: NSImage { 19 | let image = nav.showKeyColumn ? #imageLiteral(resourceName: "hashtagFull") : #imageLiteral(resourceName: "hashtag") 20 | image.size = CGSize(width: 20, height: 20) 21 | return image 22 | } 23 | var folderImage: NSImage { 24 | let image = nav.showFolderColumn ? #imageLiteral(resourceName: "folderFull") : #imageLiteral(resourceName: "folder") 25 | image.size = CGSize(width: 20, height: 18) 26 | return image 27 | } 28 | var noteImage: NSImage { 29 | let image = nav.showNotesColumn ? #imageLiteral(resourceName: "noteFull") : #imageLiteral(resourceName: "note") 30 | image.size = CGSize(width: 16, height: 20) 31 | return image 32 | } 33 | 34 | 35 | var nav: NavigationStateManager = NavigationStateManager() { 36 | didSet { 37 | (keywordColumnToggle as? NSButton)?.image = keyImage 38 | (folderColumnToggle as? NSButton)?.image = folderImage 39 | ( noteColumnToggle as? NSButton)?.image = noteImage 40 | } 41 | } 42 | 43 | 44 | 45 | 46 | override init(identifier: NSToolbar.Identifier) { 47 | super.init(identifier: identifier) 48 | //make AppToolbar the NSToolbarDelegate 49 | delegate = self 50 | 51 | //setup all the views you use in the toolbaritems 52 | let field = NSSearchField(string: "") 53 | field.delegate = self 54 | searchTextField = field 55 | 56 | keywordColumnToggle = NSButton(image: keyImage, target: self, action: #selector(keyToggleChange(_:))) 57 | folderColumnToggle = NSButton(image: folderImage, target: self, action: #selector(folderToggleChange(_:))) 58 | noteColumnToggle = NSButton(image: noteImage, target: self, action: #selector(noteToggleChange(_:))) 59 | 60 | let picker = NSPopUpButton(frame: NSRect.init(x: 0, y: 0, width: 100, height: 100), pullsDown: false) 61 | picker.removeAllItems() 62 | 63 | let allItem = NSMenuItem(title: "all", action: #selector(selectAll), keyEquivalent: "l") 64 | allItem.target = self 65 | picker.menu?.insertItem(allItem, at: 0) 66 | let draftItem = NSMenuItem(title: Status.draft.rawValue, action: #selector(selectDraft), keyEquivalent: "d") 67 | draftItem.target = self 68 | picker.menu?.insertItem(draftItem, at: 1) 69 | let reviewItem = NSMenuItem(title: Status.review.rawValue, action: #selector(selectRev), keyEquivalent: "r") 70 | reviewItem.target = self 71 | picker.menu?.insertItem(reviewItem, at: 2) 72 | let archItem = NSMenuItem(title: Status.archived.rawValue, action: #selector(selectArch), keyEquivalent: "a") 73 | archItem.target = self 74 | picker.menu?.insertItem(archItem, at: 3) 75 | 76 | self.statusPicker = picker 77 | 78 | 79 | self.allowsUserCustomization = true 80 | self.autosavesConfiguration = true 81 | self.displayMode = .iconOnly 82 | } 83 | 84 | //MARK: - construct toolbar 85 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 86 | // allowed default elements 87 | return [.search, .keys, .folder, .notes, .status, .print, .showColors, .space, .flexibleSpace] 88 | } 89 | 90 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 91 | // set toolbar elements 92 | return [.keys, .folder, .notes, .flexibleSpace, .search, .space, .status ,.space] 93 | } 94 | 95 | //MARK: - get toolbaritem for each identifier 96 | func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { 97 | 98 | var toolbarItem: NSToolbarItem = NSToolbarItem() 99 | 100 | if itemIdentifier == NSToolbarItem.Identifier.search { 101 | toolbarItem = NSToolbarItem(itemIdentifier: .search) 102 | toolbarItem.view = searchTextField 103 | toolbarItem.label = "search notes" 104 | toolbarItem.paletteLabel = "Search" 105 | toolbarItem.toolTip = "search term for your notes" 106 | toolbarItem.target = self 107 | } else if itemIdentifier == NSToolbarItem.Identifier.keys { 108 | toolbarItem = NSToolbarItem(itemIdentifier: .keys) 109 | toolbarItem.view = keywordColumnToggle 110 | toolbarItem.label = "keywords" 111 | toolbarItem.toolTip = "show/hide keyword column" 112 | toolbarItem.target = self 113 | 114 | } else if itemIdentifier == NSToolbarItem.Identifier.folder { 115 | toolbarItem = NSToolbarItem(itemIdentifier: .folder) 116 | toolbarItem.view = folderColumnToggle 117 | toolbarItem.toolTip = "show/hide folder column" 118 | toolbarItem.label = "folders" 119 | toolbarItem.target = self 120 | 121 | } else if itemIdentifier == NSToolbarItem.Identifier.notes { 122 | toolbarItem = NSToolbarItem(itemIdentifier: .notes) 123 | toolbarItem.view = noteColumnToggle 124 | toolbarItem.label = "notes" 125 | toolbarItem.toolTip = "show/hide notes column" 126 | toolbarItem.target = self 127 | }else if itemIdentifier == NSToolbarItem.Identifier.status { 128 | toolbarItem = NSToolbarItem(itemIdentifier: .status) 129 | toolbarItem.view = statusPicker 130 | toolbarItem.label = "notes status" 131 | toolbarItem.toolTip = "search notes for status" 132 | toolbarItem.target = self 133 | } 134 | 135 | 136 | return toolbarItem 137 | } 138 | 139 | //MARK: - control action 140 | func controlTextDidChange(_ obj: Notification) { 141 | let text = (obj.object as? NSTextField)?.stringValue 142 | nav.searchText = text ?? "" 143 | } 144 | 145 | @objc func keyToggleChange(_ sender: NSButton) { 146 | nav.showKeyColumn.toggle() 147 | sender.image = keyImage 148 | } 149 | 150 | @objc func folderToggleChange(_ sender: NSButton) { 151 | nav.showFolderColumn.toggle() 152 | sender.image = folderImage 153 | } 154 | 155 | @objc func noteToggleChange(_ sender: NSButton) { 156 | nav.showNotesColumn.toggle() 157 | sender.image = noteImage 158 | } 159 | 160 | //picker 161 | @objc func selectAll() { 162 | nav.searchStatus = nil 163 | } 164 | 165 | @objc func selectDraft() { 166 | nav.searchStatus = .draft 167 | } 168 | @objc func selectArch() { 169 | nav.searchStatus = .archived 170 | } 171 | @objc func selectRev() { 172 | nav.searchStatus = .review 173 | } 174 | } 175 | 176 | //MARK: - identifiers 177 | private extension NSToolbarItem.Identifier { 178 | static let search: NSToolbarItem.Identifier = NSToolbarItem.Identifier(rawValue: "Search") 179 | 180 | static let notes: NSToolbarItem.Identifier = NSToolbarItem.Identifier(rawValue: "Note") 181 | static let keys: NSToolbarItem.Identifier = NSToolbarItem.Identifier(rawValue: "Keyword") 182 | static let folder: NSToolbarItem.Identifier = NSToolbarItem.Identifier(rawValue: "Folder") 183 | 184 | static let status: NSToolbarItem.Identifier = NSToolbarItem.Identifier(rawValue: "status") 185 | } 186 | -------------------------------------------------------------------------------- /SlipboxApp/main window/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | @EnvironmentObject var nav: NavigationStateManager 13 | 14 | var body: some View { 15 | 16 | HSplitView { 17 | 18 | if nav.showKeyColumn { 19 | KeywordListView() 20 | .frame(minWidth: 150, idealWidth: 150, maxWidth: 200) 21 | } 22 | if nav.showFolderColumn { 23 | FolderListView() 24 | .frame(minWidth: 150, idealWidth: 150, maxWidth: 300) 25 | } 26 | 27 | 28 | if nav.predicate() == nil { 29 | if nav.showNotesColumn { 30 | NoteListView(folder: nav.selectedFolder, selectedNote: $nav.selectedNote) 31 | .frame(minWidth: 150, idealWidth: 150, maxWidth: 300) 32 | } 33 | } else { 34 | NoteSearchView(pred: nav.predicate()!) 35 | .frame(minWidth: 150, idealWidth: 150, maxWidth: 300) 36 | } 37 | 38 | 39 | if nav.selectedNote != nil { 40 | NoteView(note: nav.selectedNote!) 41 | }else { 42 | Text("please select a note") 43 | .foregroundColor(.gray) 44 | .frame(maxWidth: .infinity, maxHeight: .infinity) 45 | } 46 | 47 | 48 | }.frame(maxWidth: .infinity, maxHeight: .infinity) 49 | 50 | 51 | } 52 | } 53 | 54 | 55 | 56 | struct ContentView_Previews: PreviewProvider { 57 | static var previews: some View { 58 | ContentView() 59 | .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 60 | .environmentObject(NavigationStateManager()) 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /SlipboxApp/main window/NavigationStateManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationStateManager.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 07.12.20. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | class NavigationStateManager: ObservableObject { 12 | 13 | @Published var selectedNote: Note? = nil 14 | @Published var selectedFolder: Folder? = nil 15 | 16 | @Published var isKey: Bool = false 17 | 18 | @Published var showKeyColumn: Bool = true 19 | @Published var showFolderColumn: Bool = true 20 | @Published var showNotesColumn: Bool = true 21 | 22 | //MARK: - advanced search 23 | @Published var selectedKewords: Set = [] 24 | @Published var searchText: String = "" 25 | @Published var searchStatus: Status? = nil 26 | 27 | func predicate() -> NSPredicate? { 28 | 29 | var predicates = [NSPredicate]() 30 | 31 | if selectedKewords.count > 0 { 32 | let p = NSPredicate(format: "ANY %K in %@ ", NoteProperties.keywords, selectedKewords) 33 | predicates.append(p) 34 | } 35 | if searchText.count > 0 { 36 | let p1 = NSPredicate(format: "%K CONTAINS[c] %@", NoteProperties.bodyText, searchText as CVarArg) 37 | let p2 = NSPredicate(format: "%K CONTAINS[c] %@", NoteProperties.title, searchText as CVarArg) 38 | let p = NSCompoundPredicate(orPredicateWithSubpredicates: [p1, p2]) 39 | predicates.append(p) 40 | } 41 | if let status = searchStatus { 42 | let p = NSPredicate(format: "%K = %@", NoteProperties.status, status.rawValue as! CVarArg) 43 | predicates.append(p) 44 | } 45 | 46 | if predicates.count == 0 { 47 | return nil 48 | }else { 49 | return NSCompoundPredicate(andPredicateWithSubpredicates: predicates) 50 | } 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /SlipboxApp/main window/WindowContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowContentView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 12.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WindowContentView: View { 11 | 12 | var window: NSWindow! 13 | @ObservedObject var nav: NavigationStateManager 14 | 15 | @State var windowDelegate: CustomWindowDelegate 16 | 17 | init(nav: NavigationStateManager) { 18 | self.nav = nav 19 | 20 | self._windowDelegate = State(initialValue: CustomWindowDelegate(nav: nav)) 21 | windowDelegate.windowIsOpen = true 22 | 23 | window = NSWindow( 24 | contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), 25 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 26 | backing: .buffered, defer: false) 27 | 28 | window.delegate = windowDelegate 29 | 30 | window.center() 31 | window.setFrameAutosaveName("Main Window") 32 | window.contentView = NSHostingView(rootView: self) 33 | window.makeKeyAndOrderFront(nil) 34 | 35 | let toolbar = AppToolbar(identifier: .init("Default")) 36 | toolbar.nav = nav 37 | window.toolbar = toolbar 38 | window.title = "Slipbox app" 39 | } 40 | 41 | var body: some View { 42 | 43 | ContentView() 44 | .environment(\.managedObjectContext, PersistenceController.shared.container.viewContext) 45 | .environmentObject(nav) 46 | // .brightness(nav.isKey ? 0 : -0.5) 47 | 48 | } 49 | 50 | class CustomWindowDelegate: NSObject, NSWindowDelegate { 51 | 52 | let nav: NavigationStateManager 53 | 54 | var windowIsOpen = false 55 | 56 | init(nav: NavigationStateManager) { 57 | self.nav = nav 58 | } 59 | 60 | func windowDidBecomeKey(_ notification: Notification) { 61 | nav.isKey = true 62 | } 63 | 64 | func windowDidResignKey(_ notification: Notification) { 65 | nav.isKey = false 66 | } 67 | 68 | func windowWillClose(_ notification: Notification) { 69 | windowIsOpen = false 70 | } 71 | } 72 | 73 | 74 | } 75 | 76 | struct WindowContentView_Previews: PreviewProvider { 77 | static var previews: some View { 78 | WindowContentView(nav: NavigationStateManager()) 79 | .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /SlipboxApp/prefences/PreferenceContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferenceContentView.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 13.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PreferenceContentView: View { 11 | var body: some View { 12 | Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) 13 | } 14 | } 15 | 16 | struct PreferenceContentView_Previews: PreviewProvider { 17 | static var previews: some View { 18 | PreferenceContentView() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SlipboxApp/prefences/PreferenceWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferenceWindow.swift 3 | // SlipboxApp 4 | // 5 | // Created by Karin Prater on 13.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PreferenceWindow: View { 11 | 12 | var window: NSWindow! 13 | @State var windowDelegate: CustomWindowDelegate 14 | 15 | init() { 16 | self._windowDelegate = State(initialValue: CustomWindowDelegate()) 17 | windowDelegate.windowIsOpen = true 18 | window = NSWindow( 19 | contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), 20 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 21 | backing: .buffered, defer: false) 22 | 23 | window.delegate = windowDelegate 24 | window.delegate = windowDelegate 25 | 26 | window.center() 27 | window.setFrameAutosaveName("Preference Window") 28 | window.contentView = NSHostingView(rootView: self) 29 | window.makeKeyAndOrderFront(nil) 30 | window.title = "Preferences" 31 | 32 | } 33 | 34 | 35 | 36 | var body: some View { 37 | PreferenceContentView() 38 | .frame(maxWidth: .infinity, maxHeight: .infinity) 39 | } 40 | 41 | class CustomWindowDelegate: NSObject, NSWindowDelegate { 42 | 43 | var windowIsOpen = false 44 | 45 | func windowWillClose(_ notification: Notification) { 46 | windowIsOpen = false 47 | } 48 | } 49 | } 50 | 51 | struct PreferenceWindow_Previews: PreviewProvider { 52 | static var previews: some View { 53 | PreferenceWindow() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /SlipboxAppTests/FolderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderTests.swift 3 | // SlipboxAppTests 4 | // 5 | // Created by Karin Prater on 06.12.20. 6 | // 7 | 8 | import XCTest 9 | @testable import SlipboxApp 10 | 11 | class FolderTests: XCTestCase { 12 | 13 | var controller: PersistenceController! 14 | 15 | var context: NSManagedObjectContext { 16 | return controller.container.viewContext 17 | } 18 | 19 | override func setUp() { 20 | super.setUp() 21 | controller = PersistenceController.empty 22 | } 23 | 24 | override func tearDown() { 25 | super.tearDown() 26 | 27 | UnitTestHelpers.deletesAll(container: controller.container) 28 | } 29 | 30 | func testAddFolder() { 31 | let folder = Folder(name: "new", context: context) 32 | 33 | XCTAssertNotNil(folder.uuid) 34 | XCTAssertNotNil(folder.creationDate, "folder needs to have a creation date") 35 | XCTAssertTrue(folder.notes.count == 0, "created a folder with no notes") 36 | XCTAssertTrue(folder.order == 1, "folder needs order 1") 37 | 38 | let folder2 = Folder(name: "second", context: context) 39 | XCTAssertTrue(folder2.order == 2, "folder order higher than first folder") 40 | } 41 | 42 | func testAddSubfolder() { 43 | let parent = Folder(name: "parent", context: context) 44 | let child1 = Folder(name: "child1", context: context) 45 | let child2 = Folder(name: "child2", context: context) 46 | parent.add(subfolder: child1) 47 | parent.add(subfolder: child2) 48 | XCTAssertTrue(child1.order == 1) 49 | XCTAssertTrue(child2.order == 2) 50 | 51 | } 52 | 53 | //NEW: test add folder after other folder 54 | func testAddFolderAfterOther() { 55 | let parent = Folder(name: "parent", context: context) 56 | let child1 = Folder(name: "child1", context: context) 57 | let child2 = Folder(name: "child2", context: context) 58 | let child3 = Folder(name: "child1", context: context) 59 | 60 | parent.add(subfolder: child1) 61 | parent.add(subfolder: child2) 62 | 63 | parent.add(subfolder: child3, at: child1.order) 64 | let order1 = child1.order 65 | let order2 = child2.order 66 | let order3 = child3.order 67 | 68 | XCTAssertTrue(child1.order == 1) 69 | XCTAssertTrue(child3.order == 2) 70 | XCTAssertTrue(child2.order == 3) 71 | 72 | } 73 | 74 | 75 | func testAddNoteToFolder() { 76 | let notesTitle = "new" 77 | let folder = Folder(name: notesTitle, context: context) 78 | let note = Note(title: "add me", context: context) 79 | 80 | note.folder = folder 81 | 82 | 83 | XCTAssertTrue(note.folder?.name == notesTitle) 84 | XCTAssertNotNil(note.folder, "note should have been added to a folder") 85 | XCTAssertTrue(folder.notes.count == 1) 86 | 87 | } 88 | 89 | 90 | func testAddMultipleNotes() { 91 | let folder = Folder(name: "folder", context: context) 92 | let note1 = Note(title: "first", context: context) 93 | let note2 = Note(title: "second", context: context) 94 | 95 | folder.add(note: note1) 96 | folder.add(note: note2) 97 | 98 | XCTAssertTrue(folder.notes.count == 2) 99 | 100 | XCTAssertTrue(folder.notes.sorted().first == note1) 101 | 102 | XCTAssertTrue(folder.notes.sorted().last == note2) 103 | 104 | } 105 | 106 | 107 | func testAddNoteAtIndex() { 108 | let folder = Folder(name: "folder", context: context) 109 | let note1 = Note(title: "first", context: context) 110 | let note2 = Note(title: "second", context: context) 111 | let note3 = Note(title: "third", context: context) 112 | 113 | folder.add(note: note1) 114 | folder.add(note: note2) 115 | folder.add(note: note3, at: 0) 116 | 117 | XCTAssertTrue(folder.notes.sorted().first == note3) 118 | XCTAssertTrue(folder.notes.sorted().last == note2) 119 | 120 | } 121 | 122 | func testFetchFolder() { 123 | 124 | let folder = Folder(name: "folder", context: context) 125 | 126 | let request = Folder.fetch(.all) 127 | 128 | let result = try? context.fetch(request) 129 | 130 | XCTAssertTrue(result?.count == 1) 131 | 132 | } 133 | 134 | func testTopFolders() { 135 | let folder = Folder(name: "folder", context: context) 136 | let parent = Folder(name: "parent", context: context) 137 | folder.parent = parent 138 | 139 | let request = Folder.fetch(.all) 140 | let result = try? context.fetch(request) 141 | XCTAssertTrue(result?.count == 2) 142 | 143 | let parentFetch = Folder.topFolderFetch() 144 | let parents = try? context.fetch(parentFetch) 145 | XCTAssertTrue(parents?.count == 1) 146 | XCTAssertTrue(parents?.first == parent) 147 | 148 | } 149 | 150 | 151 | 152 | 153 | 154 | //TODO test fetch all folders without parent folder 155 | 156 | } 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /SlipboxAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SlipboxAppTests/NotesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotesTests.swift 3 | // SlipboxAppTests 4 | // 5 | // Created by Karin Prater on 03.12.20. 6 | // 7 | 8 | import XCTest 9 | @testable import SlipboxApp 10 | 11 | 12 | class NotesTests: XCTestCase { 13 | 14 | var controller: PersistenceController! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | controller = PersistenceController.empty 19 | } 20 | 21 | override func tearDown() { 22 | super.tearDown() 23 | UnitTestHelpers.deletesAllNotes(context: controller.container.viewContext) 24 | // UnitTestHelpers.deletesAll(container: controller.container) 25 | controller = nil 26 | } 27 | 28 | func testNoteBasicInit() { 29 | //testing the code that I added in Note awakeFromInsert() 30 | let context = controller.container.viewContext 31 | let note = Note(context: context) 32 | XCTAssertNotNil(note.creationDate, "note should have a date") 33 | XCTAssertNotNil(note.uuid, "note needs to have unique identifier") 34 | } 35 | 36 | func testAddNote() { 37 | let context = controller.container.viewContext 38 | let title = "new" 39 | let note = Note(title: title, context: context) 40 | 41 | XCTAssertTrue(note.title == title) 42 | 43 | XCTAssertNotNil(note.creationDate, "note should have a date") 44 | XCTAssertNotNil(note.uuid, "note needs to have unique identifier") 45 | } 46 | 47 | 48 | func testUpdateNote() { 49 | let context = controller.container.viewContext 50 | let note = Note(title: "old", context: context) 51 | note.title = "new" 52 | 53 | XCTAssertTrue(note.title == "new") 54 | XCTAssertFalse(note.title == "old", "note's title not correctly updated") 55 | 56 | } 57 | 58 | func testFetchNotes() { 59 | let context = controller.container.viewContext 60 | 61 | let note = Note(title: "fetch me", context: context) 62 | 63 | let request = Note.fetch(NSPredicate.all) 64 | 65 | let fechtedNotes = try? context.fetch(request) 66 | 67 | XCTAssertTrue(fechtedNotes!.count == 1, "need to have at least one note") 68 | 69 | XCTAssertTrue(fechtedNotes?.first == note, "new note should be fetched") 70 | } 71 | 72 | func testSave() { 73 | //asynchronous testing 74 | 75 | expectation(forNotification: .NSManagedObjectContextDidSave, object: controller.container.viewContext) { _ in 76 | return true 77 | } 78 | 79 | controller.container.viewContext.perform { 80 | let note = Note(title: "title", context: self.controller.container.viewContext) 81 | XCTAssertNotNil(note, "note should be there") 82 | } 83 | 84 | waitForExpectations(timeout: 2.0) { (error) in 85 | XCTAssertNil(error, "saving not complete") 86 | } 87 | } 88 | 89 | func testDeleteNote() { 90 | let context = controller.container.viewContext 91 | let note = Note(title: "note to delete", context: context) 92 | 93 | Note.delete(note: note) 94 | 95 | let request = Note.fetch(NSPredicate.all) 96 | let fechtedNotes = try? context.fetch(request) 97 | 98 | XCTAssertTrue(fechtedNotes!.count == 0, "core data fetch should be empty") 99 | 100 | XCTAssertFalse(fechtedNotes!.contains(note), "fetched notes should not contain my deleted note") 101 | 102 | } 103 | 104 | 105 | func testFetchId() { 106 | let context = controller.container.viewContext 107 | let note = Note(title: "title", context: context) 108 | 109 | let fetchedNote = Note.fetch(id: note.uuid.uuidString, in: context) 110 | 111 | XCTAssertNotNil(fetchedNote, "should be able to fetch this note") 112 | 113 | XCTAssertTrue(fetchedNote == note) 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /SlipboxAppTests/SlipboxAppTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SlipboxAppTests.swift 3 | // SlipboxAppTests 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import XCTest 9 | //@testable import SlipboxApp 10 | 11 | class SlipboxAppTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SlipboxAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SlipboxAppUITests/SlipboxAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SlipboxAppUITests.swift 3 | // SlipboxAppUITests 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import XCTest 9 | 10 | class SlipboxAppUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SlipboxPad/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SlipboxPad/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SlipboxPad/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SlipboxPad/Assets.xcassets/selectedColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.838", 9 | "green" : "0.988", 10 | "red" : "0.451" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SlipboxPad/Assets.xcassets/unselectedColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.921", 9 | "green" : "0.921", 10 | "red" : "0.921" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SlipboxPad/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SlipboxPad 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct ContentView: View { 12 | @Environment(\.managedObjectContext) private var viewContext 13 | 14 | @EnvironmentObject var nav: NavigationStateManager 15 | 16 | var body: some View { 17 | 18 | NavigationView { 19 | FolderListView() 20 | 21 | NoteListView(folder: nav.selectedFolder, selectedNote: $nav.selectedNote) 22 | 23 | if nav.selectedNote != nil { 24 | NoteView(note: nav.selectedNote!) 25 | } 26 | 27 | } 28 | 29 | 30 | 31 | } 32 | 33 | 34 | } 35 | 36 | 37 | 38 | struct ContentView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | ContentView() 41 | .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 42 | .environmentObject(NavigationStateManager()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SlipboxPad/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UIBackgroundModes 31 | 32 | fetch 33 | remote-notification 34 | 35 | UILaunchScreen 36 | 37 | UIRequiredDeviceCapabilities 38 | 39 | armv7 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /SlipboxPad/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SlipboxPad/SlipboxPad.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.com.karinprater.SlipboxApp 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SlipboxPad/SlipboxPad.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SlipboxPad/SlipboxPad.xcdatamodeld/SlipboxPad.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SlipboxPad/SlipboxPadApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SlipboxPadApp.swift 3 | // SlipboxPad 4 | // 5 | // Created by Karin Prater on 02.12.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct SlipboxPadApp: App { 12 | 13 | let persistenceController = PersistenceController.shared 14 | 15 | var body: some Scene { 16 | WindowGroup { 17 | ContentView() 18 | .environment(\.managedObjectContext, persistenceController.container.viewContext) 19 | .environmentObject(NavigationStateManager()) 20 | 21 | } 22 | } 23 | } 24 | --------------------------------------------------------------------------------