├── 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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------