├── .gitignore
├── Cartfile
├── Cartfile.resolved
├── LICENSE
├── MobileNoteTaker
├── AppDelegate.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
├── MobileNoteTaker.entitlements
└── NotesTableViewController.swift
├── NoteTaker.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ └── NoteTakerCore.xcscheme
├── NoteTaker
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── compose.imageset
│ │ ├── Contents.json
│ │ └── compose@2x.png
│ └── share.imageset
│ │ ├── Contents.json
│ │ └── share@2x.png
├── Base.lproj
│ └── Main.storyboard
├── Info.plist
├── NSTableView+Diff.h
├── NSTableView+Diff.m
├── NSTableView+Rx.swift
├── NSToolbarFlexibleSpaceItem.h
├── NeedsStorage.swift
├── NoteEditorViewController.swift
├── NoteTaker-Bridging-Header.h
├── NoteTaker.entitlements
├── NotesSplitViewController.swift
└── NotesTableViewController.swift
├── NoteTakerCore
├── Info.plist
├── NoteTakerCore.h
├── Resources
│ └── Editor.html
└── Source
│ ├── Note+RealmNote.swift
│ ├── Note.swift
│ ├── NoteEditorView.swift
│ ├── NoteViewModel.swift
│ ├── NotesStorage.swift
│ ├── NotesSyncEngine.swift
│ ├── RealmNote+CKRecord.swift
│ ├── RealmNote.swift
│ └── String+HTML.swift
├── README.md
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | __MACOSX
3 | *.pbxuser
4 | !default.pbxuser
5 | *.mode1v3
6 | !default.mode1v3
7 | *.mode2v3
8 | !default.mode2v3
9 | *.perspectivev3
10 | !default.perspectivev3
11 | *.xcworkspace
12 | !default.xcworkspace
13 | xcuserdata
14 | profile
15 | *.moved-aside
16 | DerivedData
17 | .idea/
18 | Crashlytics.sh
19 | generatechangelog.sh
20 | Pods/
21 | Carthage
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "ReactiveX/RxSwift" ~> 3.0
2 | github "realm/realm-cocoa" ~> 2.4
3 | github "RxSwiftCommunity/RxRealm" ~> 0.5
4 | github "RxSwiftCommunity/RxOptional" ~> 3.1.3
5 | github "insidegui/IGListKit" "incrementalmove"
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "insidegui/IGListKit" "4afbc24fc03ee57f7b4879960307a8ae22f3cb14"
2 | github "ReactiveX/RxSwift" "3.2.0"
3 | github "realm/realm-cocoa" "v2.4.3"
4 | github "RxSwiftCommunity/RxOptional" "3.1.3"
5 | github "RxSwiftCommunity/RxRealm" "0.5.1"
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 Peixe Urbano
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/MobileNoteTaker/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MobileNoteTaker
4 | //
5 | // Created by Guilherme Rambo on 27/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import NoteTakerCore
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | private var syncEngine: NotesSyncEngine!
16 | private let storage = NotesStorage()
17 |
18 | var window: UIWindow?
19 | private var navigationController: UINavigationController!
20 |
21 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
22 | UIApplication.shared.registerForRemoteNotifications()
23 |
24 | setup()
25 |
26 | syncEngine = NotesSyncEngine(storage: storage)
27 |
28 | return true
29 | }
30 |
31 | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
32 | NotificationCenter.default.post(name: .notesDidChangeRemotely, object: nil, userInfo: userInfo)
33 | }
34 |
35 | func setup() {
36 | window = UIWindow(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
37 | window!.backgroundColor = .white
38 |
39 | let notesController = NotesTableViewController(storage: storage)
40 |
41 | navigationController = UINavigationController(rootViewController: notesController)
42 |
43 | window!.rootViewController = navigationController
44 |
45 | window!.makeKeyAndVisible()
46 | }
47 |
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/MobileNoteTaker/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/MobileNoteTaker/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/MobileNoteTaker/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIBackgroundModes
24 |
25 | fetch
26 | remote-notification
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIRequiredDeviceCapabilities
31 |
32 | armv7
33 |
34 | UISupportedInterfaceOrientations
35 |
36 | UIInterfaceOrientationPortrait
37 | UIInterfaceOrientationLandscapeLeft
38 | UIInterfaceOrientationLandscapeRight
39 |
40 | UISupportedInterfaceOrientations~ipad
41 |
42 | UIInterfaceOrientationPortrait
43 | UIInterfaceOrientationPortraitUpsideDown
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/MobileNoteTaker/MobileNoteTaker.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.developer.icloud-container-identifiers
8 |
9 | iCloud.br.com.guilhermerambo.NoteTakerShared
10 |
11 | com.apple.developer.icloud-services
12 |
13 | CloudKit
14 |
15 | com.apple.developer.ubiquity-kvstore-identifier
16 | $(TeamIdentifierPrefix)$(CFBundleIdentifier)
17 |
18 |
19 |
--------------------------------------------------------------------------------
/MobileNoteTaker/NotesTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotesTableViewController.swift
3 | // MobileNoteTaker
4 | //
5 | // Created by Guilherme Rambo on 27/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 | import RxCocoa
12 | import NoteTakerCore
13 |
14 | class NotesTableViewController: UITableViewController {
15 |
16 | private let storage: NotesStorage
17 |
18 | init(storage: NotesStorage) {
19 | self.storage = storage
20 |
21 | super.init(style: .plain)
22 |
23 | self.title = "NoteTaker"
24 | }
25 |
26 | required init?(coder aDecoder: NSCoder) {
27 | fatalError("init(coder:) has not been implemented")
28 | }
29 |
30 | private let disposeBag = DisposeBag()
31 |
32 | var notes = Variable<[Note]>([])
33 |
34 | var viewModels = [NoteViewModel]() {
35 | didSet {
36 | tableView.reloadData()
37 | // tableView.reloadData(withOldValue: oldValue, newValue: viewModels)
38 | }
39 | }
40 |
41 | private struct Constants {
42 | static let cellIdentifier = "note"
43 | }
44 |
45 | override func viewDidLoad() {
46 | super.viewDidLoad()
47 |
48 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: Constants.cellIdentifier)
49 |
50 | storage.allNotes.asObservable()
51 | .observeOn(MainScheduler.instance)
52 | .bindTo(notes)
53 | .addDisposableTo(disposeBag)
54 |
55 | notes.asObservable()
56 | .map({ $0.map(NoteViewModel.init) })
57 | .subscribe { [weak self] event in
58 | switch event {
59 | case .next(let noteViewModels):
60 | self?.viewModels = noteViewModels
61 | break
62 | default: break
63 | }
64 | }.addDisposableTo(disposeBag)
65 | }
66 |
67 | override func numberOfSections(in tableView: UITableView) -> Int {
68 | return 1
69 | }
70 |
71 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
72 | return viewModels.count
73 | }
74 |
75 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
76 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellIdentifier)
77 |
78 | cell?.accessoryType = .disclosureIndicator
79 | cell?.textLabel?.text = viewModels[indexPath.row].title
80 |
81 | return cell ?? UITableViewCell()
82 | }
83 |
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/NoteTaker.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | DD02010C1E4E583500C4D40D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD02010B1E4E583500C4D40D /* AppDelegate.swift */; };
11 | DD0201101E4E583500C4D40D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DD02010F1E4E583500C4D40D /* Assets.xcassets */; };
12 | DD0201131E4E583500C4D40D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DD0201111E4E583500C4D40D /* Main.storyboard */; };
13 | DD0201221E4E593A00C4D40D /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD02011D1E4E593A00C4D40D /* RxCocoa.framework */; };
14 | DD0201241E4E593A00C4D40D /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD02011F1E4E593A00C4D40D /* RxSwift.framework */; };
15 | DD02013D1E4E8D2F00C4D40D /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD02013C1E4E8D2F00C4D40D /* CloudKit.framework */; };
16 | DD02013F1E4E8EBC00C4D40D /* NotesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD02013E1E4E8EBC00C4D40D /* NotesTableViewController.swift */; };
17 | DD0201421E4E8EC500C4D40D /* NoteEditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0201401E4E8EC500C4D40D /* NoteEditorViewController.swift */; };
18 | DD1A3D081E4E92910068347D /* NotesSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1A3D071E4E92910068347D /* NotesSplitViewController.swift */; };
19 | DD1A3D0A1E4E99020068347D /* IGListKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD1A3D091E4E99020068347D /* IGListKit.framework */; };
20 | DD1A3D141E4EA1110068347D /* NSTableView+Diff.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1A3D131E4EA1110068347D /* NSTableView+Diff.m */; };
21 | DD1A3D191E4EA4520068347D /* NeedsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1A3D181E4EA4520068347D /* NeedsStorage.swift */; };
22 | DD6980471E4F4C9700A22EDC /* NSTableView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6980461E4F4C9700A22EDC /* NSTableView+Rx.swift */; };
23 | DD6980491E4FC5B200A22EDC /* RxOptional.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD6980481E4FC5B200A22EDC /* RxOptional.framework */; };
24 | DDD631951E64974E00937A6A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD631941E64974E00937A6A /* AppDelegate.swift */; };
25 | DDD631971E64974E00937A6A /* NotesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD631961E64974E00937A6A /* NotesTableViewController.swift */; };
26 | DDD6319C1E64974E00937A6A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDD6319B1E64974E00937A6A /* Assets.xcassets */; };
27 | DDD6319F1E64974E00937A6A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DDD6319D1E64974E00937A6A /* LaunchScreen.storyboard */; };
28 | DDD631A61E64976D00937A6A /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDD631A51E64976D00937A6A /* CloudKit.framework */; };
29 | DDD631AF1E64987D00937A6A /* IGListKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDD631A81E64984900937A6A /* IGListKit.framework */; };
30 | DDD631B01E64988300937A6A /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDD631AB1E64984900937A6A /* RxCocoa.framework */; };
31 | DDD631B11E64988600937A6A /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDD631AE1E64984900937A6A /* RxSwift.framework */; };
32 | DDD631B21E64988D00937A6A /* RxOptional.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDD631AC1E64984900937A6A /* RxOptional.framework */; };
33 | DDD631B51E6498F700937A6A /* NotesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E951E647EFD00B30065 /* NotesStorage.swift */; };
34 | DDD631B61E6498F700937A6A /* NoteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E911E647EEE00B30065 /* NoteViewModel.swift */; };
35 | DDD631B71E6498F700937A6A /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E891E647EDE00B30065 /* Note.swift */; };
36 | DDD631B81E6498F700937A6A /* Note+RealmNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E8A1E647EDE00B30065 /* Note+RealmNote.swift */; };
37 | DDD631B91E6498F700937A6A /* NotesSyncEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E961E647EFD00B30065 /* NotesSyncEngine.swift */; };
38 | DDD631BA1E6498F700937A6A /* String+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E931E647EF300B30065 /* String+HTML.swift */; };
39 | DDD631BB1E6498F700937A6A /* RealmNote+CKRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E8C1E647EDE00B30065 /* RealmNote+CKRecord.swift */; };
40 | DDD631BC1E6498F700937A6A /* RealmNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E8B1E647EDE00B30065 /* RealmNote.swift */; };
41 | DDD631C31E6498F700937A6A /* NoteTakerCore.h in Headers */ = {isa = PBXBuildFile; fileRef = DDE82E7A1E647E7800B30065 /* NoteTakerCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
42 | DDD631CA1E64995500937A6A /* NoteTakerCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDD631C81E6498F700937A6A /* NoteTakerCore.framework */; };
43 | DDD631CB1E64995500937A6A /* NoteTakerCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DDD631C81E6498F700937A6A /* NoteTakerCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
44 | DDD631D11E649B4100937A6A /* NoteEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD631D01E649B4100937A6A /* NoteEditorView.swift */; };
45 | DDD631D41E649D7F00937A6A /* Editor.html in Resources */ = {isa = PBXBuildFile; fileRef = DDD631D31E649D7F00937A6A /* Editor.html */; };
46 | DDD631D51E649D7F00937A6A /* Editor.html in Resources */ = {isa = PBXBuildFile; fileRef = DDD631D31E649D7F00937A6A /* Editor.html */; };
47 | DDD631D61E64A38B00937A6A /* NoteEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD631D01E649B4100937A6A /* NoteEditorView.swift */; };
48 | DDE82E7C1E647E7800B30065 /* NoteTakerCore.h in Headers */ = {isa = PBXBuildFile; fileRef = DDE82E7A1E647E7800B30065 /* NoteTakerCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
49 | DDE82E7F1E647E7800B30065 /* NoteTakerCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDE82E781E647E7800B30065 /* NoteTakerCore.framework */; };
50 | DDE82E811E647E7800B30065 /* NoteTakerCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DDE82E781E647E7800B30065 /* NoteTakerCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
51 | DDE82E8D1E647EDE00B30065 /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E891E647EDE00B30065 /* Note.swift */; };
52 | DDE82E8E1E647EDE00B30065 /* Note+RealmNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E8A1E647EDE00B30065 /* Note+RealmNote.swift */; };
53 | DDE82E8F1E647EDE00B30065 /* RealmNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E8B1E647EDE00B30065 /* RealmNote.swift */; };
54 | DDE82E901E647EDE00B30065 /* RealmNote+CKRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E8C1E647EDE00B30065 /* RealmNote+CKRecord.swift */; };
55 | DDE82E921E647EEE00B30065 /* NoteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E911E647EEE00B30065 /* NoteViewModel.swift */; };
56 | DDE82E941E647EF300B30065 /* String+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E931E647EF300B30065 /* String+HTML.swift */; };
57 | DDE82E971E647EFD00B30065 /* NotesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E951E647EFD00B30065 /* NotesStorage.swift */; };
58 | DDE82E981E647EFD00B30065 /* NotesSyncEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE82E961E647EFD00B30065 /* NotesSyncEngine.swift */; };
59 | DDE82E9A1E647F1900B30065 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD02011B1E4E593A00C4D40D /* Realm.framework */; };
60 | DDE82E9B1E647F1B00B30065 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD02011C1E4E593A00C4D40D /* RealmSwift.framework */; };
61 | DDE82E9C1E647F1F00B30065 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD02011F1E4E593A00C4D40D /* RxSwift.framework */; };
62 | DDE82E9D1E647F2200B30065 /* IGListKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD1A3D091E4E99020068347D /* IGListKit.framework */; };
63 | /* End PBXBuildFile section */
64 |
65 | /* Begin PBXContainerItemProxy section */
66 | DDD631CC1E64995500937A6A /* PBXContainerItemProxy */ = {
67 | isa = PBXContainerItemProxy;
68 | containerPortal = DD0201001E4E583500C4D40D /* Project object */;
69 | proxyType = 1;
70 | remoteGlobalIDString = DDD631B31E6498F700937A6A;
71 | remoteInfo = "NoteTakerCore-iOS";
72 | };
73 | DDE82E7D1E647E7800B30065 /* PBXContainerItemProxy */ = {
74 | isa = PBXContainerItemProxy;
75 | containerPortal = DD0201001E4E583500C4D40D /* Project object */;
76 | proxyType = 1;
77 | remoteGlobalIDString = DDE82E771E647E7800B30065;
78 | remoteInfo = NoteTakerCore;
79 | };
80 | /* End PBXContainerItemProxy section */
81 |
82 | /* Begin PBXCopyFilesBuildPhase section */
83 | DD0201261E4E59B900C4D40D /* Carthage: Copy DSYMs */ = {
84 | isa = PBXCopyFilesBuildPhase;
85 | buildActionMask = 2147483647;
86 | dstPath = "";
87 | dstSubfolderSpec = 16;
88 | files = (
89 | );
90 | name = "Carthage: Copy DSYMs";
91 | runOnlyForDeploymentPostprocessing = 0;
92 | };
93 | DDD631CE1E64995500937A6A /* Embed Frameworks */ = {
94 | isa = PBXCopyFilesBuildPhase;
95 | buildActionMask = 2147483647;
96 | dstPath = "";
97 | dstSubfolderSpec = 10;
98 | files = (
99 | DDD631CB1E64995500937A6A /* NoteTakerCore.framework in Embed Frameworks */,
100 | );
101 | name = "Embed Frameworks";
102 | runOnlyForDeploymentPostprocessing = 0;
103 | };
104 | DDE82E801E647E7800B30065 /* Embed Frameworks */ = {
105 | isa = PBXCopyFilesBuildPhase;
106 | buildActionMask = 2147483647;
107 | dstPath = "";
108 | dstSubfolderSpec = 10;
109 | files = (
110 | DDE82E811E647E7800B30065 /* NoteTakerCore.framework in Embed Frameworks */,
111 | );
112 | name = "Embed Frameworks";
113 | runOnlyForDeploymentPostprocessing = 0;
114 | };
115 | /* End PBXCopyFilesBuildPhase section */
116 |
117 | /* Begin PBXFileReference section */
118 | DD0201081E4E583500C4D40D /* NoteTaker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NoteTaker.app; sourceTree = BUILT_PRODUCTS_DIR; };
119 | DD02010B1E4E583500C4D40D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
120 | DD02010F1E4E583500C4D40D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
121 | DD0201121E4E583500C4D40D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
122 | DD0201141E4E583500C4D40D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
123 | DD02011B1E4E593A00C4D40D /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = Carthage/Build/Mac/Realm.framework; sourceTree = ""; };
124 | DD02011C1E4E593A00C4D40D /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RealmSwift.framework; path = Carthage/Build/Mac/RealmSwift.framework; sourceTree = ""; };
125 | DD02011D1E4E593A00C4D40D /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = Carthage/Build/Mac/RxCocoa.framework; sourceTree = ""; };
126 | DD02011E1E4E593A00C4D40D /* RxRealm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxRealm.framework; path = Carthage/Build/Mac/RxRealm.framework; sourceTree = ""; };
127 | DD02011F1E4E593A00C4D40D /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/Mac/RxSwift.framework; sourceTree = ""; };
128 | DD02013B1E4E8D2700C4D40D /* NoteTaker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NoteTaker.entitlements; sourceTree = ""; };
129 | DD02013C1E4E8D2F00C4D40D /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
130 | DD02013E1E4E8EBC00C4D40D /* NotesTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotesTableViewController.swift; sourceTree = ""; };
131 | DD0201401E4E8EC500C4D40D /* NoteEditorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteEditorViewController.swift; sourceTree = ""; };
132 | DD1A3D071E4E92910068347D /* NotesSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotesSplitViewController.swift; sourceTree = ""; };
133 | DD1A3D091E4E99020068347D /* IGListKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IGListKit.framework; path = Carthage/Build/Mac/IGListKit.framework; sourceTree = ""; };
134 | DD1A3D111E4EA1110068347D /* NoteTaker-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NoteTaker-Bridging-Header.h"; sourceTree = ""; };
135 | DD1A3D121E4EA1110068347D /* NSTableView+Diff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTableView+Diff.h"; sourceTree = ""; };
136 | DD1A3D131E4EA1110068347D /* NSTableView+Diff.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTableView+Diff.m"; sourceTree = ""; };
137 | DD1A3D181E4EA4520068347D /* NeedsStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeedsStorage.swift; sourceTree = ""; };
138 | DD6980461E4F4C9700A22EDC /* NSTableView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTableView+Rx.swift"; sourceTree = ""; };
139 | DD6980481E4FC5B200A22EDC /* RxOptional.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxOptional.framework; path = Carthage/Build/Mac/RxOptional.framework; sourceTree = ""; };
140 | DDCDA0AD1E58AE44002B63CB /* NSToolbarFlexibleSpaceItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSToolbarFlexibleSpaceItem.h; sourceTree = ""; };
141 | DDD631921E64974E00937A6A /* MobileNoteTaker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MobileNoteTaker.app; sourceTree = BUILT_PRODUCTS_DIR; };
142 | DDD631941E64974E00937A6A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
143 | DDD631961E64974E00937A6A /* NotesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesTableViewController.swift; sourceTree = ""; };
144 | DDD6319B1E64974E00937A6A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
145 | DDD6319E1E64974E00937A6A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
146 | DDD631A01E64974E00937A6A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
147 | DDD631A41E64976400937A6A /* MobileNoteTaker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MobileNoteTaker.entitlements; sourceTree = ""; };
148 | DDD631A51E64976D00937A6A /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; };
149 | DDD631A81E64984900937A6A /* IGListKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IGListKit.framework; path = Carthage/Build/iOS/IGListKit.framework; sourceTree = ""; };
150 | DDD631A91E64984900937A6A /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = Carthage/Build/iOS/Realm.framework; sourceTree = ""; };
151 | DDD631AA1E64984900937A6A /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RealmSwift.framework; path = Carthage/Build/iOS/RealmSwift.framework; sourceTree = ""; };
152 | DDD631AB1E64984900937A6A /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = Carthage/Build/iOS/RxCocoa.framework; sourceTree = ""; };
153 | DDD631AC1E64984900937A6A /* RxOptional.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxOptional.framework; path = Carthage/Build/iOS/RxOptional.framework; sourceTree = ""; };
154 | DDD631AD1E64984900937A6A /* RxRealm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxRealm.framework; path = Carthage/Build/iOS/RxRealm.framework; sourceTree = ""; };
155 | DDD631AE1E64984900937A6A /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = ""; };
156 | DDD631C81E6498F700937A6A /* NoteTakerCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NoteTakerCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
157 | DDD631D01E649B4100937A6A /* NoteEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NoteEditorView.swift; path = Source/NoteEditorView.swift; sourceTree = ""; };
158 | DDD631D31E649D7F00937A6A /* Editor.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = Editor.html; path = Resources/Editor.html; sourceTree = ""; };
159 | DDE82E781E647E7800B30065 /* NoteTakerCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NoteTakerCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
160 | DDE82E7A1E647E7800B30065 /* NoteTakerCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NoteTakerCore.h; sourceTree = ""; };
161 | DDE82E7B1E647E7800B30065 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
162 | DDE82E891E647EDE00B30065 /* Note.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Note.swift; path = Source/Note.swift; sourceTree = ""; };
163 | DDE82E8A1E647EDE00B30065 /* Note+RealmNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Note+RealmNote.swift"; path = "Source/Note+RealmNote.swift"; sourceTree = ""; };
164 | DDE82E8B1E647EDE00B30065 /* RealmNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RealmNote.swift; path = Source/RealmNote.swift; sourceTree = ""; };
165 | DDE82E8C1E647EDE00B30065 /* RealmNote+CKRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "RealmNote+CKRecord.swift"; path = "Source/RealmNote+CKRecord.swift"; sourceTree = ""; };
166 | DDE82E911E647EEE00B30065 /* NoteViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NoteViewModel.swift; path = Source/NoteViewModel.swift; sourceTree = ""; };
167 | DDE82E931E647EF300B30065 /* String+HTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "String+HTML.swift"; path = "Source/String+HTML.swift"; sourceTree = ""; };
168 | DDE82E951E647EFD00B30065 /* NotesStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotesStorage.swift; path = Source/NotesStorage.swift; sourceTree = ""; };
169 | DDE82E961E647EFD00B30065 /* NotesSyncEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotesSyncEngine.swift; path = Source/NotesSyncEngine.swift; sourceTree = ""; };
170 | /* End PBXFileReference section */
171 |
172 | /* Begin PBXFrameworksBuildPhase section */
173 | DD0201051E4E583500C4D40D /* Frameworks */ = {
174 | isa = PBXFrameworksBuildPhase;
175 | buildActionMask = 2147483647;
176 | files = (
177 | DD0201241E4E593A00C4D40D /* RxSwift.framework in Frameworks */,
178 | DD0201221E4E593A00C4D40D /* RxCocoa.framework in Frameworks */,
179 | DD6980491E4FC5B200A22EDC /* RxOptional.framework in Frameworks */,
180 | DD1A3D0A1E4E99020068347D /* IGListKit.framework in Frameworks */,
181 | DD02013D1E4E8D2F00C4D40D /* CloudKit.framework in Frameworks */,
182 | DDE82E7F1E647E7800B30065 /* NoteTakerCore.framework in Frameworks */,
183 | );
184 | runOnlyForDeploymentPostprocessing = 0;
185 | };
186 | DDD6318F1E64974E00937A6A /* Frameworks */ = {
187 | isa = PBXFrameworksBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | DDD631A61E64976D00937A6A /* CloudKit.framework in Frameworks */,
191 | DDD631B01E64988300937A6A /* RxCocoa.framework in Frameworks */,
192 | DDD631B21E64988D00937A6A /* RxOptional.framework in Frameworks */,
193 | DDD631CA1E64995500937A6A /* NoteTakerCore.framework in Frameworks */,
194 | DDD631B11E64988600937A6A /* RxSwift.framework in Frameworks */,
195 | DDD631AF1E64987D00937A6A /* IGListKit.framework in Frameworks */,
196 | );
197 | runOnlyForDeploymentPostprocessing = 0;
198 | };
199 | DDD631BD1E6498F700937A6A /* Frameworks */ = {
200 | isa = PBXFrameworksBuildPhase;
201 | buildActionMask = 2147483647;
202 | files = (
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | };
206 | DDE82E741E647E7800B30065 /* Frameworks */ = {
207 | isa = PBXFrameworksBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | DDE82E9A1E647F1900B30065 /* Realm.framework in Frameworks */,
211 | DDE82E9B1E647F1B00B30065 /* RealmSwift.framework in Frameworks */,
212 | DDE82E9C1E647F1F00B30065 /* RxSwift.framework in Frameworks */,
213 | DDE82E9D1E647F2200B30065 /* IGListKit.framework in Frameworks */,
214 | );
215 | runOnlyForDeploymentPostprocessing = 0;
216 | };
217 | /* End PBXFrameworksBuildPhase section */
218 |
219 | /* Begin PBXGroup section */
220 | DD0200FF1E4E583500C4D40D = {
221 | isa = PBXGroup;
222 | children = (
223 | DD0201311E4E59DC00C4D40D /* Vendor */,
224 | DD02010A1E4E583500C4D40D /* NoteTaker */,
225 | DDE82E791E647E7800B30065 /* NoteTakerCore */,
226 | DDD631931E64974E00937A6A /* MobileNoteTaker */,
227 | DD0201091E4E583500C4D40D /* Products */,
228 | DD02011A1E4E593A00C4D40D /* Frameworks */,
229 | );
230 | sourceTree = "";
231 | };
232 | DD0201091E4E583500C4D40D /* Products */ = {
233 | isa = PBXGroup;
234 | children = (
235 | DD0201081E4E583500C4D40D /* NoteTaker.app */,
236 | DDE82E781E647E7800B30065 /* NoteTakerCore.framework */,
237 | DDD631921E64974E00937A6A /* MobileNoteTaker.app */,
238 | DDD631C81E6498F700937A6A /* NoteTakerCore.framework */,
239 | );
240 | name = Products;
241 | sourceTree = "";
242 | };
243 | DD02010A1E4E583500C4D40D /* NoteTaker */ = {
244 | isa = PBXGroup;
245 | children = (
246 | DD02013B1E4E8D2700C4D40D /* NoteTaker.entitlements */,
247 | DD0201341E4E59F600C4D40D /* Bootstrap */,
248 | DD0201381E4E5A0C00C4D40D /* Resources */,
249 | DD0201351E4E59FA00C4D40D /* ViewControllers */,
250 | DD1A3D101E4EA1040068347D /* Util */,
251 | );
252 | path = NoteTaker;
253 | sourceTree = "";
254 | };
255 | DD02011A1E4E593A00C4D40D /* Frameworks */ = {
256 | isa = PBXGroup;
257 | children = (
258 | DDD631A51E64976D00937A6A /* CloudKit.framework */,
259 | DD02013C1E4E8D2F00C4D40D /* CloudKit.framework */,
260 | );
261 | name = Frameworks;
262 | sourceTree = "";
263 | };
264 | DD0201311E4E59DC00C4D40D /* Vendor */ = {
265 | isa = PBXGroup;
266 | children = (
267 | DD0201321E4E59E000C4D40D /* Frameworks */,
268 | );
269 | name = Vendor;
270 | sourceTree = "";
271 | };
272 | DD0201321E4E59E000C4D40D /* Frameworks */ = {
273 | isa = PBXGroup;
274 | children = (
275 | DDE82EB51E6481B700B30065 /* macOS */,
276 | DDE82EB71E6481C900B30065 /* iOS */,
277 | );
278 | name = Frameworks;
279 | sourceTree = "";
280 | };
281 | DD0201341E4E59F600C4D40D /* Bootstrap */ = {
282 | isa = PBXGroup;
283 | children = (
284 | DD02010B1E4E583500C4D40D /* AppDelegate.swift */,
285 | );
286 | name = Bootstrap;
287 | sourceTree = "";
288 | };
289 | DD0201351E4E59FA00C4D40D /* ViewControllers */ = {
290 | isa = PBXGroup;
291 | children = (
292 | DD1A3D181E4EA4520068347D /* NeedsStorage.swift */,
293 | DD1A3D071E4E92910068347D /* NotesSplitViewController.swift */,
294 | DD02013E1E4E8EBC00C4D40D /* NotesTableViewController.swift */,
295 | DD0201401E4E8EC500C4D40D /* NoteEditorViewController.swift */,
296 | );
297 | name = ViewControllers;
298 | sourceTree = "";
299 | };
300 | DD0201381E4E5A0C00C4D40D /* Resources */ = {
301 | isa = PBXGroup;
302 | children = (
303 | DD02010F1E4E583500C4D40D /* Assets.xcassets */,
304 | DD0201111E4E583500C4D40D /* Main.storyboard */,
305 | DD0201141E4E583500C4D40D /* Info.plist */,
306 | );
307 | name = Resources;
308 | sourceTree = "";
309 | };
310 | DD1A3D101E4EA1040068347D /* Util */ = {
311 | isa = PBXGroup;
312 | children = (
313 | DD1A3D111E4EA1110068347D /* NoteTaker-Bridging-Header.h */,
314 | DD1A3D121E4EA1110068347D /* NSTableView+Diff.h */,
315 | DD1A3D131E4EA1110068347D /* NSTableView+Diff.m */,
316 | DDCDA0AD1E58AE44002B63CB /* NSToolbarFlexibleSpaceItem.h */,
317 | DD6980461E4F4C9700A22EDC /* NSTableView+Rx.swift */,
318 | );
319 | name = Util;
320 | sourceTree = "";
321 | };
322 | DDD631931E64974E00937A6A /* MobileNoteTaker */ = {
323 | isa = PBXGroup;
324 | children = (
325 | DDD631A41E64976400937A6A /* MobileNoteTaker.entitlements */,
326 | DDD631D71E64A49100937A6A /* Bootstrap */,
327 | DDD631D91E64A49A00937A6A /* Controllers */,
328 | DDD631D81E64A49600937A6A /* Resources */,
329 | );
330 | path = MobileNoteTaker;
331 | sourceTree = "";
332 | };
333 | DDD631CF1E649AD600937A6A /* Views */ = {
334 | isa = PBXGroup;
335 | children = (
336 | DDD631D01E649B4100937A6A /* NoteEditorView.swift */,
337 | );
338 | name = Views;
339 | sourceTree = "";
340 | };
341 | DDD631D21E649D7800937A6A /* Resources */ = {
342 | isa = PBXGroup;
343 | children = (
344 | DDD631D31E649D7F00937A6A /* Editor.html */,
345 | );
346 | name = Resources;
347 | sourceTree = "";
348 | };
349 | DDD631D71E64A49100937A6A /* Bootstrap */ = {
350 | isa = PBXGroup;
351 | children = (
352 | DDD631941E64974E00937A6A /* AppDelegate.swift */,
353 | );
354 | name = Bootstrap;
355 | sourceTree = "";
356 | };
357 | DDD631D81E64A49600937A6A /* Resources */ = {
358 | isa = PBXGroup;
359 | children = (
360 | DDD6319B1E64974E00937A6A /* Assets.xcassets */,
361 | DDD6319D1E64974E00937A6A /* LaunchScreen.storyboard */,
362 | DDD631A01E64974E00937A6A /* Info.plist */,
363 | );
364 | name = Resources;
365 | sourceTree = "";
366 | };
367 | DDD631D91E64A49A00937A6A /* Controllers */ = {
368 | isa = PBXGroup;
369 | children = (
370 | DDD631961E64974E00937A6A /* NotesTableViewController.swift */,
371 | );
372 | name = Controllers;
373 | sourceTree = "";
374 | };
375 | DDE82E791E647E7800B30065 /* NoteTakerCore */ = {
376 | isa = PBXGroup;
377 | children = (
378 | DDD631D21E649D7800937A6A /* Resources */,
379 | DDE82E851E647EB400B30065 /* Source */,
380 | DDE82E7A1E647E7800B30065 /* NoteTakerCore.h */,
381 | DDE82E7B1E647E7800B30065 /* Info.plist */,
382 | );
383 | path = NoteTakerCore;
384 | sourceTree = "";
385 | };
386 | DDE82E851E647EB400B30065 /* Source */ = {
387 | isa = PBXGroup;
388 | children = (
389 | DDD631CF1E649AD600937A6A /* Views */,
390 | DDE82E881E647EBF00B30065 /* Storage and Sync */,
391 | DDE82E861E647EB800B30065 /* Models */,
392 | DDE82E871E647EBC00B30065 /* ViewModels */,
393 | );
394 | name = Source;
395 | sourceTree = "";
396 | };
397 | DDE82E861E647EB800B30065 /* Models */ = {
398 | isa = PBXGroup;
399 | children = (
400 | DDE82E891E647EDE00B30065 /* Note.swift */,
401 | DDE82E8A1E647EDE00B30065 /* Note+RealmNote.swift */,
402 | DDE82E8B1E647EDE00B30065 /* RealmNote.swift */,
403 | DDE82E8C1E647EDE00B30065 /* RealmNote+CKRecord.swift */,
404 | );
405 | name = Models;
406 | sourceTree = "";
407 | };
408 | DDE82E871E647EBC00B30065 /* ViewModels */ = {
409 | isa = PBXGroup;
410 | children = (
411 | DDE82E931E647EF300B30065 /* String+HTML.swift */,
412 | DDE82E911E647EEE00B30065 /* NoteViewModel.swift */,
413 | );
414 | name = ViewModels;
415 | sourceTree = "";
416 | };
417 | DDE82E881E647EBF00B30065 /* Storage and Sync */ = {
418 | isa = PBXGroup;
419 | children = (
420 | DDE82E951E647EFD00B30065 /* NotesStorage.swift */,
421 | DDE82E961E647EFD00B30065 /* NotesSyncEngine.swift */,
422 | );
423 | name = "Storage and Sync";
424 | sourceTree = "";
425 | };
426 | DDE82EB51E6481B700B30065 /* macOS */ = {
427 | isa = PBXGroup;
428 | children = (
429 | DD6980481E4FC5B200A22EDC /* RxOptional.framework */,
430 | DD02011B1E4E593A00C4D40D /* Realm.framework */,
431 | DD02011C1E4E593A00C4D40D /* RealmSwift.framework */,
432 | DD02011D1E4E593A00C4D40D /* RxCocoa.framework */,
433 | DD02011E1E4E593A00C4D40D /* RxRealm.framework */,
434 | DD02011F1E4E593A00C4D40D /* RxSwift.framework */,
435 | DD1A3D091E4E99020068347D /* IGListKit.framework */,
436 | );
437 | name = macOS;
438 | sourceTree = "";
439 | };
440 | DDE82EB71E6481C900B30065 /* iOS */ = {
441 | isa = PBXGroup;
442 | children = (
443 | DDD631A81E64984900937A6A /* IGListKit.framework */,
444 | DDD631A91E64984900937A6A /* Realm.framework */,
445 | DDD631AA1E64984900937A6A /* RealmSwift.framework */,
446 | DDD631AB1E64984900937A6A /* RxCocoa.framework */,
447 | DDD631AC1E64984900937A6A /* RxOptional.framework */,
448 | DDD631AD1E64984900937A6A /* RxRealm.framework */,
449 | DDD631AE1E64984900937A6A /* RxSwift.framework */,
450 | );
451 | name = iOS;
452 | sourceTree = "";
453 | };
454 | /* End PBXGroup section */
455 |
456 | /* Begin PBXHeadersBuildPhase section */
457 | DDD631C21E6498F700937A6A /* Headers */ = {
458 | isa = PBXHeadersBuildPhase;
459 | buildActionMask = 2147483647;
460 | files = (
461 | DDD631C31E6498F700937A6A /* NoteTakerCore.h in Headers */,
462 | );
463 | runOnlyForDeploymentPostprocessing = 0;
464 | };
465 | DDE82E751E647E7800B30065 /* Headers */ = {
466 | isa = PBXHeadersBuildPhase;
467 | buildActionMask = 2147483647;
468 | files = (
469 | DDE82E7C1E647E7800B30065 /* NoteTakerCore.h in Headers */,
470 | );
471 | runOnlyForDeploymentPostprocessing = 0;
472 | };
473 | /* End PBXHeadersBuildPhase section */
474 |
475 | /* Begin PBXNativeTarget section */
476 | DD0201071E4E583500C4D40D /* NoteTaker */ = {
477 | isa = PBXNativeTarget;
478 | buildConfigurationList = DD0201171E4E583500C4D40D /* Build configuration list for PBXNativeTarget "NoteTaker" */;
479 | buildPhases = (
480 | DD0201041E4E583500C4D40D /* Sources */,
481 | DD0201051E4E583500C4D40D /* Frameworks */,
482 | DD0201061E4E583500C4D40D /* Resources */,
483 | DD0201251E4E594000C4D40D /* Carthage: Copy Frameworks */,
484 | DD0201261E4E59B900C4D40D /* Carthage: Copy DSYMs */,
485 | DDE82E801E647E7800B30065 /* Embed Frameworks */,
486 | );
487 | buildRules = (
488 | );
489 | dependencies = (
490 | DDE82E7E1E647E7800B30065 /* PBXTargetDependency */,
491 | );
492 | name = NoteTaker;
493 | productName = NoteTaker;
494 | productReference = DD0201081E4E583500C4D40D /* NoteTaker.app */;
495 | productType = "com.apple.product-type.application";
496 | };
497 | DDD631911E64974E00937A6A /* MobileNoteTaker */ = {
498 | isa = PBXNativeTarget;
499 | buildConfigurationList = DDD631A31E64974E00937A6A /* Build configuration list for PBXNativeTarget "MobileNoteTaker" */;
500 | buildPhases = (
501 | DDD6318E1E64974E00937A6A /* Sources */,
502 | DDD6318F1E64974E00937A6A /* Frameworks */,
503 | DDD631901E64974E00937A6A /* Resources */,
504 | DDD631A71E6497A500937A6A /* Carthage: Copy Frameworks */,
505 | DDD631CE1E64995500937A6A /* Embed Frameworks */,
506 | );
507 | buildRules = (
508 | );
509 | dependencies = (
510 | DDD631CD1E64995500937A6A /* PBXTargetDependency */,
511 | );
512 | name = MobileNoteTaker;
513 | productName = MobileNoteTaker;
514 | productReference = DDD631921E64974E00937A6A /* MobileNoteTaker.app */;
515 | productType = "com.apple.product-type.application";
516 | };
517 | DDD631B31E6498F700937A6A /* NoteTakerCore-iOS */ = {
518 | isa = PBXNativeTarget;
519 | buildConfigurationList = DDD631C51E6498F700937A6A /* Build configuration list for PBXNativeTarget "NoteTakerCore-iOS" */;
520 | buildPhases = (
521 | DDD631B41E6498F700937A6A /* Sources */,
522 | DDD631BD1E6498F700937A6A /* Frameworks */,
523 | DDD631C21E6498F700937A6A /* Headers */,
524 | DDD631C41E6498F700937A6A /* Resources */,
525 | );
526 | buildRules = (
527 | );
528 | dependencies = (
529 | );
530 | name = "NoteTakerCore-iOS";
531 | productName = NoteTakerCore;
532 | productReference = DDD631C81E6498F700937A6A /* NoteTakerCore.framework */;
533 | productType = "com.apple.product-type.framework";
534 | };
535 | DDE82E771E647E7800B30065 /* NoteTakerCore */ = {
536 | isa = PBXNativeTarget;
537 | buildConfigurationList = DDE82E841E647E7800B30065 /* Build configuration list for PBXNativeTarget "NoteTakerCore" */;
538 | buildPhases = (
539 | DDE82E731E647E7800B30065 /* Sources */,
540 | DDE82E741E647E7800B30065 /* Frameworks */,
541 | DDE82E751E647E7800B30065 /* Headers */,
542 | DDE82E761E647E7800B30065 /* Resources */,
543 | );
544 | buildRules = (
545 | );
546 | dependencies = (
547 | );
548 | name = NoteTakerCore;
549 | productName = NoteTakerCore;
550 | productReference = DDE82E781E647E7800B30065 /* NoteTakerCore.framework */;
551 | productType = "com.apple.product-type.framework";
552 | };
553 | /* End PBXNativeTarget section */
554 |
555 | /* Begin PBXProject section */
556 | DD0201001E4E583500C4D40D /* Project object */ = {
557 | isa = PBXProject;
558 | attributes = {
559 | LastSwiftUpdateCheck = 0820;
560 | LastUpgradeCheck = 0830;
561 | ORGANIZATIONNAME = "Guilherme Rambo";
562 | TargetAttributes = {
563 | DD0201071E4E583500C4D40D = {
564 | CreatedOnToolsVersion = 8.2.1;
565 | DevelopmentTeam = 8C7439RJLG;
566 | LastSwiftMigration = 0820;
567 | ProvisioningStyle = Automatic;
568 | SystemCapabilities = {
569 | com.apple.Push = {
570 | enabled = 1;
571 | };
572 | com.apple.iCloud = {
573 | enabled = 1;
574 | };
575 | };
576 | };
577 | DDD631911E64974E00937A6A = {
578 | CreatedOnToolsVersion = 8.2.1;
579 | DevelopmentTeam = 8C7439RJLG;
580 | ProvisioningStyle = Automatic;
581 | SystemCapabilities = {
582 | com.apple.BackgroundModes = {
583 | enabled = 1;
584 | };
585 | com.apple.Push = {
586 | enabled = 1;
587 | };
588 | com.apple.iCloud = {
589 | enabled = 1;
590 | };
591 | };
592 | };
593 | DDD631B31E6498F700937A6A = {
594 | DevelopmentTeam = 8C7439RJLG;
595 | };
596 | DDE82E771E647E7800B30065 = {
597 | CreatedOnToolsVersion = 8.2.1;
598 | DevelopmentTeam = 8C7439RJLG;
599 | LastSwiftMigration = 0820;
600 | ProvisioningStyle = Automatic;
601 | };
602 | };
603 | };
604 | buildConfigurationList = DD0201031E4E583500C4D40D /* Build configuration list for PBXProject "NoteTaker" */;
605 | compatibilityVersion = "Xcode 3.2";
606 | developmentRegion = English;
607 | hasScannedForEncodings = 0;
608 | knownRegions = (
609 | en,
610 | Base,
611 | );
612 | mainGroup = DD0200FF1E4E583500C4D40D;
613 | productRefGroup = DD0201091E4E583500C4D40D /* Products */;
614 | projectDirPath = "";
615 | projectRoot = "";
616 | targets = (
617 | DD0201071E4E583500C4D40D /* NoteTaker */,
618 | DDD631911E64974E00937A6A /* MobileNoteTaker */,
619 | DDE82E771E647E7800B30065 /* NoteTakerCore */,
620 | DDD631B31E6498F700937A6A /* NoteTakerCore-iOS */,
621 | );
622 | };
623 | /* End PBXProject section */
624 |
625 | /* Begin PBXResourcesBuildPhase section */
626 | DD0201061E4E583500C4D40D /* Resources */ = {
627 | isa = PBXResourcesBuildPhase;
628 | buildActionMask = 2147483647;
629 | files = (
630 | DD0201101E4E583500C4D40D /* Assets.xcassets in Resources */,
631 | DD0201131E4E583500C4D40D /* Main.storyboard in Resources */,
632 | );
633 | runOnlyForDeploymentPostprocessing = 0;
634 | };
635 | DDD631901E64974E00937A6A /* Resources */ = {
636 | isa = PBXResourcesBuildPhase;
637 | buildActionMask = 2147483647;
638 | files = (
639 | DDD6319F1E64974E00937A6A /* LaunchScreen.storyboard in Resources */,
640 | DDD6319C1E64974E00937A6A /* Assets.xcassets in Resources */,
641 | );
642 | runOnlyForDeploymentPostprocessing = 0;
643 | };
644 | DDD631C41E6498F700937A6A /* Resources */ = {
645 | isa = PBXResourcesBuildPhase;
646 | buildActionMask = 2147483647;
647 | files = (
648 | DDD631D51E649D7F00937A6A /* Editor.html in Resources */,
649 | );
650 | runOnlyForDeploymentPostprocessing = 0;
651 | };
652 | DDE82E761E647E7800B30065 /* Resources */ = {
653 | isa = PBXResourcesBuildPhase;
654 | buildActionMask = 2147483647;
655 | files = (
656 | DDD631D41E649D7F00937A6A /* Editor.html in Resources */,
657 | );
658 | runOnlyForDeploymentPostprocessing = 0;
659 | };
660 | /* End PBXResourcesBuildPhase section */
661 |
662 | /* Begin PBXShellScriptBuildPhase section */
663 | DD0201251E4E594000C4D40D /* Carthage: Copy Frameworks */ = {
664 | isa = PBXShellScriptBuildPhase;
665 | buildActionMask = 2147483647;
666 | files = (
667 | );
668 | inputPaths = (
669 | "$(SRCROOT)/Carthage/Build/Mac/Realm.framework",
670 | "$(SRCROOT)/Carthage/Build/Mac/RealmSwift.framework",
671 | "$(SRCROOT)/Carthage/Build/Mac/RxRealm.framework",
672 | "$(SRCROOT)/Carthage/Build/Mac/RxSwift.framework",
673 | "$(SRCROOT)/Carthage/Build/Mac/RxCocoa.framework",
674 | "$(SRCROOT)/Carthage/Build/Mac/IGListKit.framework",
675 | "$(SRCROOT)/Carthage/Build/Mac/RxOptional.framework",
676 | );
677 | name = "Carthage: Copy Frameworks";
678 | outputPaths = (
679 | );
680 | runOnlyForDeploymentPostprocessing = 0;
681 | shellPath = /bin/sh;
682 | shellScript = "/usr/local/bin/carthage copy-frameworks";
683 | };
684 | DDD631A71E6497A500937A6A /* Carthage: Copy Frameworks */ = {
685 | isa = PBXShellScriptBuildPhase;
686 | buildActionMask = 2147483647;
687 | files = (
688 | );
689 | inputPaths = (
690 | "$(SRCROOT)/Carthage/Build/iOS/Realm.framework",
691 | "$(SRCROOT)/Carthage/Build/iOS/RealmSwift.framework",
692 | "$(SRCROOT)/Carthage/Build/iOS/RxRealm.framework",
693 | "$(SRCROOT)/Carthage/Build/iOS/RxSwift.framework",
694 | "$(SRCROOT)/Carthage/Build/iOS/RxCocoa.framework",
695 | "$(SRCROOT)/Carthage/Build/iOS/IGListKit.framework",
696 | "$(SRCROOT)/Carthage/Build/iOS/RxOptional.framework",
697 | );
698 | name = "Carthage: Copy Frameworks";
699 | outputPaths = (
700 | );
701 | runOnlyForDeploymentPostprocessing = 0;
702 | shellPath = /bin/sh;
703 | shellScript = "/usr/local/bin/carthage copy-frameworks";
704 | };
705 | /* End PBXShellScriptBuildPhase section */
706 |
707 | /* Begin PBXSourcesBuildPhase section */
708 | DD0201041E4E583500C4D40D /* Sources */ = {
709 | isa = PBXSourcesBuildPhase;
710 | buildActionMask = 2147483647;
711 | files = (
712 | DD02010C1E4E583500C4D40D /* AppDelegate.swift in Sources */,
713 | DD1A3D191E4EA4520068347D /* NeedsStorage.swift in Sources */,
714 | DD0201421E4E8EC500C4D40D /* NoteEditorViewController.swift in Sources */,
715 | DD1A3D141E4EA1110068347D /* NSTableView+Diff.m in Sources */,
716 | DD6980471E4F4C9700A22EDC /* NSTableView+Rx.swift in Sources */,
717 | DD1A3D081E4E92910068347D /* NotesSplitViewController.swift in Sources */,
718 | DD02013F1E4E8EBC00C4D40D /* NotesTableViewController.swift in Sources */,
719 | );
720 | runOnlyForDeploymentPostprocessing = 0;
721 | };
722 | DDD6318E1E64974E00937A6A /* Sources */ = {
723 | isa = PBXSourcesBuildPhase;
724 | buildActionMask = 2147483647;
725 | files = (
726 | DDD631971E64974E00937A6A /* NotesTableViewController.swift in Sources */,
727 | DDD631951E64974E00937A6A /* AppDelegate.swift in Sources */,
728 | );
729 | runOnlyForDeploymentPostprocessing = 0;
730 | };
731 | DDD631B41E6498F700937A6A /* Sources */ = {
732 | isa = PBXSourcesBuildPhase;
733 | buildActionMask = 2147483647;
734 | files = (
735 | DDD631B51E6498F700937A6A /* NotesStorage.swift in Sources */,
736 | DDD631B61E6498F700937A6A /* NoteViewModel.swift in Sources */,
737 | DDD631D61E64A38B00937A6A /* NoteEditorView.swift in Sources */,
738 | DDD631B71E6498F700937A6A /* Note.swift in Sources */,
739 | DDD631B81E6498F700937A6A /* Note+RealmNote.swift in Sources */,
740 | DDD631B91E6498F700937A6A /* NotesSyncEngine.swift in Sources */,
741 | DDD631BA1E6498F700937A6A /* String+HTML.swift in Sources */,
742 | DDD631BB1E6498F700937A6A /* RealmNote+CKRecord.swift in Sources */,
743 | DDD631BC1E6498F700937A6A /* RealmNote.swift in Sources */,
744 | );
745 | runOnlyForDeploymentPostprocessing = 0;
746 | };
747 | DDE82E731E647E7800B30065 /* Sources */ = {
748 | isa = PBXSourcesBuildPhase;
749 | buildActionMask = 2147483647;
750 | files = (
751 | DDE82E971E647EFD00B30065 /* NotesStorage.swift in Sources */,
752 | DDE82E921E647EEE00B30065 /* NoteViewModel.swift in Sources */,
753 | DDD631D11E649B4100937A6A /* NoteEditorView.swift in Sources */,
754 | DDE82E8D1E647EDE00B30065 /* Note.swift in Sources */,
755 | DDE82E8E1E647EDE00B30065 /* Note+RealmNote.swift in Sources */,
756 | DDE82E981E647EFD00B30065 /* NotesSyncEngine.swift in Sources */,
757 | DDE82E941E647EF300B30065 /* String+HTML.swift in Sources */,
758 | DDE82E901E647EDE00B30065 /* RealmNote+CKRecord.swift in Sources */,
759 | DDE82E8F1E647EDE00B30065 /* RealmNote.swift in Sources */,
760 | );
761 | runOnlyForDeploymentPostprocessing = 0;
762 | };
763 | /* End PBXSourcesBuildPhase section */
764 |
765 | /* Begin PBXTargetDependency section */
766 | DDD631CD1E64995500937A6A /* PBXTargetDependency */ = {
767 | isa = PBXTargetDependency;
768 | target = DDD631B31E6498F700937A6A /* NoteTakerCore-iOS */;
769 | targetProxy = DDD631CC1E64995500937A6A /* PBXContainerItemProxy */;
770 | };
771 | DDE82E7E1E647E7800B30065 /* PBXTargetDependency */ = {
772 | isa = PBXTargetDependency;
773 | target = DDE82E771E647E7800B30065 /* NoteTakerCore */;
774 | targetProxy = DDE82E7D1E647E7800B30065 /* PBXContainerItemProxy */;
775 | };
776 | /* End PBXTargetDependency section */
777 |
778 | /* Begin PBXVariantGroup section */
779 | DD0201111E4E583500C4D40D /* Main.storyboard */ = {
780 | isa = PBXVariantGroup;
781 | children = (
782 | DD0201121E4E583500C4D40D /* Base */,
783 | );
784 | name = Main.storyboard;
785 | sourceTree = "";
786 | };
787 | DDD6319D1E64974E00937A6A /* LaunchScreen.storyboard */ = {
788 | isa = PBXVariantGroup;
789 | children = (
790 | DDD6319E1E64974E00937A6A /* Base */,
791 | );
792 | name = LaunchScreen.storyboard;
793 | sourceTree = "";
794 | };
795 | /* End PBXVariantGroup section */
796 |
797 | /* Begin XCBuildConfiguration section */
798 | DD0201151E4E583500C4D40D /* Debug */ = {
799 | isa = XCBuildConfiguration;
800 | buildSettings = {
801 | ALWAYS_SEARCH_USER_PATHS = NO;
802 | CLANG_ANALYZER_NONNULL = YES;
803 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
804 | CLANG_CXX_LIBRARY = "libc++";
805 | CLANG_ENABLE_MODULES = YES;
806 | CLANG_ENABLE_OBJC_ARC = YES;
807 | CLANG_WARN_BOOL_CONVERSION = YES;
808 | CLANG_WARN_CONSTANT_CONVERSION = YES;
809 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
810 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
811 | CLANG_WARN_EMPTY_BODY = YES;
812 | CLANG_WARN_ENUM_CONVERSION = YES;
813 | CLANG_WARN_INFINITE_RECURSION = YES;
814 | CLANG_WARN_INT_CONVERSION = YES;
815 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
816 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
817 | CLANG_WARN_UNREACHABLE_CODE = YES;
818 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
819 | CODE_SIGN_IDENTITY = "-";
820 | COPY_PHASE_STRIP = NO;
821 | DEBUG_INFORMATION_FORMAT = dwarf;
822 | ENABLE_STRICT_OBJC_MSGSEND = YES;
823 | ENABLE_TESTABILITY = YES;
824 | GCC_C_LANGUAGE_STANDARD = gnu99;
825 | GCC_DYNAMIC_NO_PIC = NO;
826 | GCC_NO_COMMON_BLOCKS = YES;
827 | GCC_OPTIMIZATION_LEVEL = 0;
828 | GCC_PREPROCESSOR_DEFINITIONS = (
829 | "DEBUG=1",
830 | "$(inherited)",
831 | );
832 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
833 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
834 | GCC_WARN_UNDECLARED_SELECTOR = YES;
835 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
836 | GCC_WARN_UNUSED_FUNCTION = YES;
837 | GCC_WARN_UNUSED_VARIABLE = YES;
838 | MACOSX_DEPLOYMENT_TARGET = 10.12;
839 | MTL_ENABLE_DEBUG_INFO = YES;
840 | ONLY_ACTIVE_ARCH = YES;
841 | SDKROOT = macosx;
842 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
843 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
844 | };
845 | name = Debug;
846 | };
847 | DD0201161E4E583500C4D40D /* Release */ = {
848 | isa = XCBuildConfiguration;
849 | buildSettings = {
850 | ALWAYS_SEARCH_USER_PATHS = NO;
851 | CLANG_ANALYZER_NONNULL = YES;
852 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
853 | CLANG_CXX_LIBRARY = "libc++";
854 | CLANG_ENABLE_MODULES = YES;
855 | CLANG_ENABLE_OBJC_ARC = YES;
856 | CLANG_WARN_BOOL_CONVERSION = YES;
857 | CLANG_WARN_CONSTANT_CONVERSION = YES;
858 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
859 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
860 | CLANG_WARN_EMPTY_BODY = YES;
861 | CLANG_WARN_ENUM_CONVERSION = YES;
862 | CLANG_WARN_INFINITE_RECURSION = YES;
863 | CLANG_WARN_INT_CONVERSION = YES;
864 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
865 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
866 | CLANG_WARN_UNREACHABLE_CODE = YES;
867 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
868 | CODE_SIGN_IDENTITY = "-";
869 | COPY_PHASE_STRIP = NO;
870 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
871 | ENABLE_NS_ASSERTIONS = NO;
872 | ENABLE_STRICT_OBJC_MSGSEND = YES;
873 | GCC_C_LANGUAGE_STANDARD = gnu99;
874 | GCC_NO_COMMON_BLOCKS = YES;
875 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
876 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
877 | GCC_WARN_UNDECLARED_SELECTOR = YES;
878 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
879 | GCC_WARN_UNUSED_FUNCTION = YES;
880 | GCC_WARN_UNUSED_VARIABLE = YES;
881 | MACOSX_DEPLOYMENT_TARGET = 10.12;
882 | MTL_ENABLE_DEBUG_INFO = NO;
883 | SDKROOT = macosx;
884 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
885 | };
886 | name = Release;
887 | };
888 | DD0201181E4E583500C4D40D /* Debug */ = {
889 | isa = XCBuildConfiguration;
890 | buildSettings = {
891 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
892 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
893 | CLANG_ENABLE_MODULES = YES;
894 | CODE_SIGN_ENTITLEMENTS = NoteTaker/NoteTaker.entitlements;
895 | CODE_SIGN_IDENTITY = "Mac Developer";
896 | COMBINE_HIDPI_IMAGES = YES;
897 | DEVELOPMENT_TEAM = 8C7439RJLG;
898 | FRAMEWORK_SEARCH_PATHS = (
899 | "$(inherited)",
900 | "$(PROJECT_DIR)/Carthage/Build/Mac",
901 | );
902 | INFOPLIST_FILE = NoteTaker/Info.plist;
903 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
904 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.NoteTaker;
905 | PRODUCT_NAME = "$(TARGET_NAME)";
906 | SWIFT_OBJC_BRIDGING_HEADER = "NoteTaker/NoteTaker-Bridging-Header.h";
907 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
908 | SWIFT_VERSION = 3.0;
909 | };
910 | name = Debug;
911 | };
912 | DD0201191E4E583500C4D40D /* Release */ = {
913 | isa = XCBuildConfiguration;
914 | buildSettings = {
915 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
916 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
917 | CLANG_ENABLE_MODULES = YES;
918 | CODE_SIGN_ENTITLEMENTS = NoteTaker/NoteTaker.entitlements;
919 | CODE_SIGN_IDENTITY = "Mac Developer";
920 | COMBINE_HIDPI_IMAGES = YES;
921 | DEVELOPMENT_TEAM = 8C7439RJLG;
922 | FRAMEWORK_SEARCH_PATHS = (
923 | "$(inherited)",
924 | "$(PROJECT_DIR)/Carthage/Build/Mac",
925 | );
926 | INFOPLIST_FILE = NoteTaker/Info.plist;
927 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
928 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.NoteTaker;
929 | PRODUCT_NAME = "$(TARGET_NAME)";
930 | SWIFT_OBJC_BRIDGING_HEADER = "NoteTaker/NoteTaker-Bridging-Header.h";
931 | SWIFT_VERSION = 3.0;
932 | };
933 | name = Release;
934 | };
935 | DDD631A11E64974E00937A6A /* Debug */ = {
936 | isa = XCBuildConfiguration;
937 | buildSettings = {
938 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
939 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
940 | CODE_SIGN_ENTITLEMENTS = MobileNoteTaker/MobileNoteTaker.entitlements;
941 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
942 | DEVELOPMENT_TEAM = 8C7439RJLG;
943 | FRAMEWORK_SEARCH_PATHS = (
944 | "$(inherited)",
945 | "$(PROJECT_DIR)/Carthage/Build/iOS",
946 | );
947 | INFOPLIST_FILE = MobileNoteTaker/Info.plist;
948 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
949 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
950 | OTHER_SWIFT_FLAGS = "-D DEBUG";
951 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.MobileNoteTaker;
952 | PRODUCT_NAME = "$(TARGET_NAME)";
953 | SDKROOT = iphoneos;
954 | SWIFT_VERSION = 3.0;
955 | TARGETED_DEVICE_FAMILY = "1,2";
956 | };
957 | name = Debug;
958 | };
959 | DDD631A21E64974E00937A6A /* Release */ = {
960 | isa = XCBuildConfiguration;
961 | buildSettings = {
962 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
963 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
964 | CODE_SIGN_ENTITLEMENTS = MobileNoteTaker/MobileNoteTaker.entitlements;
965 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
966 | DEVELOPMENT_TEAM = 8C7439RJLG;
967 | FRAMEWORK_SEARCH_PATHS = (
968 | "$(inherited)",
969 | "$(PROJECT_DIR)/Carthage/Build/iOS",
970 | );
971 | INFOPLIST_FILE = MobileNoteTaker/Info.plist;
972 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
973 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
974 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.MobileNoteTaker;
975 | PRODUCT_NAME = "$(TARGET_NAME)";
976 | SDKROOT = iphoneos;
977 | SWIFT_VERSION = 3.0;
978 | TARGETED_DEVICE_FAMILY = "1,2";
979 | VALIDATE_PRODUCT = YES;
980 | };
981 | name = Release;
982 | };
983 | DDD631C61E6498F700937A6A /* Debug */ = {
984 | isa = XCBuildConfiguration;
985 | buildSettings = {
986 | CLANG_ENABLE_MODULES = YES;
987 | CODE_SIGN_IDENTITY = "-";
988 | COMBINE_HIDPI_IMAGES = YES;
989 | CURRENT_PROJECT_VERSION = 1;
990 | DEFINES_MODULE = YES;
991 | DEVELOPMENT_TEAM = 8C7439RJLG;
992 | DYLIB_COMPATIBILITY_VERSION = 1;
993 | DYLIB_CURRENT_VERSION = 1;
994 | DYLIB_INSTALL_NAME_BASE = "@rpath";
995 | FRAMEWORK_SEARCH_PATHS = (
996 | "$(inherited)",
997 | "$(PROJECT_DIR)/Carthage/Build/iOS",
998 | );
999 | FRAMEWORK_VERSION = A;
1000 | INFOPLIST_FILE = NoteTakerCore/Info.plist;
1001 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1002 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
1003 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.NoteTakerCore;
1004 | PRODUCT_NAME = NoteTakerCore;
1005 | SDKROOT = iphoneos;
1006 | SKIP_INSTALL = YES;
1007 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
1008 | SWIFT_VERSION = 3.0;
1009 | VERSIONING_SYSTEM = "apple-generic";
1010 | VERSION_INFO_PREFIX = "";
1011 | };
1012 | name = Debug;
1013 | };
1014 | DDD631C71E6498F700937A6A /* Release */ = {
1015 | isa = XCBuildConfiguration;
1016 | buildSettings = {
1017 | CLANG_ENABLE_MODULES = YES;
1018 | CODE_SIGN_IDENTITY = "-";
1019 | COMBINE_HIDPI_IMAGES = YES;
1020 | CURRENT_PROJECT_VERSION = 1;
1021 | DEFINES_MODULE = YES;
1022 | DEVELOPMENT_TEAM = 8C7439RJLG;
1023 | DYLIB_COMPATIBILITY_VERSION = 1;
1024 | DYLIB_CURRENT_VERSION = 1;
1025 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1026 | FRAMEWORK_SEARCH_PATHS = (
1027 | "$(inherited)",
1028 | "$(PROJECT_DIR)/Carthage/Build/iOS",
1029 | );
1030 | FRAMEWORK_VERSION = A;
1031 | INFOPLIST_FILE = NoteTakerCore/Info.plist;
1032 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1033 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
1034 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.NoteTakerCore;
1035 | PRODUCT_NAME = NoteTakerCore;
1036 | SDKROOT = iphoneos;
1037 | SKIP_INSTALL = YES;
1038 | SWIFT_VERSION = 3.0;
1039 | VERSIONING_SYSTEM = "apple-generic";
1040 | VERSION_INFO_PREFIX = "";
1041 | };
1042 | name = Release;
1043 | };
1044 | DDE82E821E647E7800B30065 /* Debug */ = {
1045 | isa = XCBuildConfiguration;
1046 | buildSettings = {
1047 | CLANG_ENABLE_MODULES = YES;
1048 | CODE_SIGN_IDENTITY = "";
1049 | COMBINE_HIDPI_IMAGES = YES;
1050 | CURRENT_PROJECT_VERSION = 1;
1051 | DEFINES_MODULE = YES;
1052 | DEVELOPMENT_TEAM = 8C7439RJLG;
1053 | DYLIB_COMPATIBILITY_VERSION = 1;
1054 | DYLIB_CURRENT_VERSION = 1;
1055 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1056 | FRAMEWORK_SEARCH_PATHS = (
1057 | "$(inherited)",
1058 | "$(PROJECT_DIR)/Carthage/Build/Mac",
1059 | );
1060 | FRAMEWORK_VERSION = A;
1061 | INFOPLIST_FILE = NoteTakerCore/Info.plist;
1062 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1063 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
1064 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.NoteTakerCore;
1065 | PRODUCT_NAME = "$(TARGET_NAME)";
1066 | SKIP_INSTALL = YES;
1067 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
1068 | SWIFT_VERSION = 3.0;
1069 | VERSIONING_SYSTEM = "apple-generic";
1070 | VERSION_INFO_PREFIX = "";
1071 | };
1072 | name = Debug;
1073 | };
1074 | DDE82E831E647E7800B30065 /* Release */ = {
1075 | isa = XCBuildConfiguration;
1076 | buildSettings = {
1077 | CLANG_ENABLE_MODULES = YES;
1078 | CODE_SIGN_IDENTITY = "";
1079 | COMBINE_HIDPI_IMAGES = YES;
1080 | CURRENT_PROJECT_VERSION = 1;
1081 | DEFINES_MODULE = YES;
1082 | DEVELOPMENT_TEAM = 8C7439RJLG;
1083 | DYLIB_COMPATIBILITY_VERSION = 1;
1084 | DYLIB_CURRENT_VERSION = 1;
1085 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1086 | FRAMEWORK_SEARCH_PATHS = (
1087 | "$(inherited)",
1088 | "$(PROJECT_DIR)/Carthage/Build/Mac",
1089 | );
1090 | FRAMEWORK_VERSION = A;
1091 | INFOPLIST_FILE = NoteTakerCore/Info.plist;
1092 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1093 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
1094 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.NoteTakerCore;
1095 | PRODUCT_NAME = "$(TARGET_NAME)";
1096 | SKIP_INSTALL = YES;
1097 | SWIFT_VERSION = 3.0;
1098 | VERSIONING_SYSTEM = "apple-generic";
1099 | VERSION_INFO_PREFIX = "";
1100 | };
1101 | name = Release;
1102 | };
1103 | /* End XCBuildConfiguration section */
1104 |
1105 | /* Begin XCConfigurationList section */
1106 | DD0201031E4E583500C4D40D /* Build configuration list for PBXProject "NoteTaker" */ = {
1107 | isa = XCConfigurationList;
1108 | buildConfigurations = (
1109 | DD0201151E4E583500C4D40D /* Debug */,
1110 | DD0201161E4E583500C4D40D /* Release */,
1111 | );
1112 | defaultConfigurationIsVisible = 0;
1113 | defaultConfigurationName = Release;
1114 | };
1115 | DD0201171E4E583500C4D40D /* Build configuration list for PBXNativeTarget "NoteTaker" */ = {
1116 | isa = XCConfigurationList;
1117 | buildConfigurations = (
1118 | DD0201181E4E583500C4D40D /* Debug */,
1119 | DD0201191E4E583500C4D40D /* Release */,
1120 | );
1121 | defaultConfigurationIsVisible = 0;
1122 | defaultConfigurationName = Release;
1123 | };
1124 | DDD631A31E64974E00937A6A /* Build configuration list for PBXNativeTarget "MobileNoteTaker" */ = {
1125 | isa = XCConfigurationList;
1126 | buildConfigurations = (
1127 | DDD631A11E64974E00937A6A /* Debug */,
1128 | DDD631A21E64974E00937A6A /* Release */,
1129 | );
1130 | defaultConfigurationIsVisible = 0;
1131 | defaultConfigurationName = Release;
1132 | };
1133 | DDD631C51E6498F700937A6A /* Build configuration list for PBXNativeTarget "NoteTakerCore-iOS" */ = {
1134 | isa = XCConfigurationList;
1135 | buildConfigurations = (
1136 | DDD631C61E6498F700937A6A /* Debug */,
1137 | DDD631C71E6498F700937A6A /* Release */,
1138 | );
1139 | defaultConfigurationIsVisible = 0;
1140 | defaultConfigurationName = Release;
1141 | };
1142 | DDE82E841E647E7800B30065 /* Build configuration list for PBXNativeTarget "NoteTakerCore" */ = {
1143 | isa = XCConfigurationList;
1144 | buildConfigurations = (
1145 | DDE82E821E647E7800B30065 /* Debug */,
1146 | DDE82E831E647E7800B30065 /* Release */,
1147 | );
1148 | defaultConfigurationIsVisible = 0;
1149 | defaultConfigurationName = Release;
1150 | };
1151 | /* End XCConfigurationList section */
1152 | };
1153 | rootObject = DD0201001E4E583500C4D40D /* Project object */;
1154 | }
1155 |
--------------------------------------------------------------------------------
/NoteTaker.xcodeproj/xcshareddata/xcschemes/NoteTakerCore.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/NoteTaker/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import NoteTakerCore
11 |
12 | @NSApplicationMain
13 | class AppDelegate: NSObject, NSApplicationDelegate {
14 |
15 | func applicationDidFinishLaunching(_ aNotification: Notification) {
16 | NSApp.registerForRemoteNotifications(matching: NSRemoteNotificationType())
17 | }
18 |
19 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
20 | return true
21 | }
22 |
23 | func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
24 | NotificationCenter.default.post(name: .notesDidChangeRemotely, object: nil, userInfo: userInfo)
25 | }
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/NoteTaker/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/NoteTaker/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/NoteTaker/Assets.xcassets/compose.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "compose@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
--------------------------------------------------------------------------------
/NoteTaker/Assets.xcassets/compose.imageset/compose@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/NoteTaker/db7e5f1fe60c18b4f27209d7a0889b1f199a383b/NoteTaker/Assets.xcassets/compose.imageset/compose@2x.png
--------------------------------------------------------------------------------
/NoteTaker/Assets.xcassets/share.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "share@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
--------------------------------------------------------------------------------
/NoteTaker/Assets.xcassets/share.imageset/share@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/NoteTaker/db7e5f1fe60c18b4f27209d7a0889b1f199a383b/NoteTaker/Assets.xcassets/share.imageset/share@2x.png
--------------------------------------------------------------------------------
/NoteTaker/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
--------------------------------------------------------------------------------
/NoteTaker/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2017 Guilherme Rambo. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 | CKSharingSupported
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/NoteTaker/NSTableView+Diff.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSTableView+Diff.h
3 | // Dose
4 | //
5 | // Created by Guilherme Rambo on 09/01/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | @import Cocoa;
10 | @import IGListKit;
11 |
12 | @interface NSTableView (Diff)
13 |
14 | - (void)reloadDataWithOldValue:(NSArray > *)oldValue newValue:(NSArray > *)newValue;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/NoteTaker/NSTableView+Diff.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSTableView+Diff.m
3 | // Dose
4 | //
5 | // Created by Guilherme Rambo on 09/01/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import "NSTableView+Diff.h"
10 |
11 | @implementation NSTableView (Diff)
12 |
13 | - (void)reloadDataWithOldValue:(NSArray > *)oldValue newValue:(NSArray > *)newValue
14 | {
15 | IGListIndexSetResult *result = IGListDiffWithBehavior(oldValue, newValue, IGListDiffEquality, IGListDiffBehaviorIncrementalMoves);
16 |
17 | [self beginUpdates];
18 | {
19 | [self reloadDataForRowIndexes:result.updates columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.tableColumns.count)]];
20 | [self insertRowsAtIndexes:result.inserts withAnimation:NSTableViewAnimationSlideDown];
21 | [self removeRowsAtIndexes:result.deletes withAnimation:NSTableViewAnimationSlideUp];
22 | [result.moves enumerateObjectsUsingBlock:^(IGListMoveIndex *index, NSUInteger idx, BOOL *stop) {
23 | [self moveRowAtIndex:index.from toIndex:index.to];
24 | }];
25 | }
26 | [self endUpdates];
27 | }
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/NoteTaker/NSTableView+Rx.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSTableView+Rx.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 11/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import RxSwift
11 | import RxCocoa
12 |
13 | final class RxTableViewDelegateProxy: DelegateProxy, NSTableViewDelegate, DelegateProxyType {
14 |
15 | weak private(set) var tableView: NSTableView?
16 |
17 | fileprivate var selectedRowSubject = PublishSubject()
18 |
19 | required init(parentObject: AnyObject) {
20 | self.tableView = parentObject as? NSTableView
21 |
22 | super.init(parentObject: parentObject)
23 | }
24 |
25 | public override class func createProxyForObject(_ object: AnyObject) -> AnyObject {
26 | let control: NSTableView = object as! NSTableView
27 | return control.createRxDelegateProxy()
28 | }
29 |
30 | public class func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
31 | let tableView: NSTableView = object as! NSTableView
32 | return tableView.delegate
33 | }
34 |
35 | public class func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
36 | let tableView: NSTableView = object as! NSTableView
37 | tableView.delegate = delegate as? NSTableViewDelegate
38 | }
39 |
40 | func tableViewSelectionDidChange(_ notification: Notification) {
41 | guard let numberOfRows = tableView?.numberOfRows, numberOfRows > 0 else { return }
42 | guard let selectedRow = tableView?.selectedRow else { return }
43 |
44 | let row: Int? = (selectedRow >= 0 && selectedRow < numberOfRows) ? selectedRow : nil
45 |
46 | selectedRowSubject.on(.next(row))
47 | }
48 |
49 | }
50 |
51 | extension NSTableView {
52 |
53 | func createRxDelegateProxy() -> RxTableViewDelegateProxy {
54 | return RxTableViewDelegateProxy(parentObject: self)
55 | }
56 |
57 | }
58 |
59 | extension Reactive where Base: NSTableView {
60 |
61 | public var delegate: DelegateProxy {
62 | return RxTableViewDelegateProxy.proxyForObject(base)
63 | }
64 |
65 | public var selectedRow: ControlProperty {
66 | let delegate = RxTableViewDelegateProxy.proxyForObject(base)
67 |
68 | let source = Observable.deferred { [weak tableView = self.base] () -> Observable in
69 | if let startingRow = tableView?.selectedRow, startingRow >= 0 {
70 | return delegate.selectedRowSubject.startWith(startingRow)
71 | } else {
72 | return delegate.selectedRowSubject.startWith(nil)
73 | }
74 | }.takeUntil(deallocated)
75 |
76 | let observer = UIBindingObserver(UIElement: base) { (control, value: Int?) in
77 | if let row = value {
78 | control.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
79 | } else {
80 | control.deselectAll(nil)
81 | }
82 | }
83 |
84 | return ControlProperty(values: source, valueSink: observer.asObserver())
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/NoteTaker/NSToolbarFlexibleSpaceItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSToolbarFlexibleSpaceItem.h
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 18/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | @import Cocoa;
10 |
11 | @interface NSToolbarFlexibleSpaceItem: NSToolbarItem
12 |
13 | @property (weak, nullable) NSSplitView *trackedSplitView;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/NoteTaker/NeedsStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NeedsStorage.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import NoteTakerCore
11 |
12 | protocol NeedsStorage: class {
13 |
14 | var storage: NotesStorage? { get set }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/NoteTaker/NoteEditorViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteEditorViewController.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import RxSwift
11 | import RxCocoa
12 | import NoteTakerCore
13 |
14 | class NoteEditorViewController: NSViewController, NeedsStorage {
15 |
16 | var note = Variable(.empty)
17 | var contents = Variable("")
18 |
19 | var storage: NotesStorage?
20 |
21 | private let disposeBag = DisposeBag()
22 |
23 | private lazy var editor: NoteEditorView = {
24 | let e = NoteEditorView(frame: self.view.bounds)
25 |
26 | e.autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
27 | e.delegate = self
28 |
29 | return e
30 | }()
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 |
35 | view.addSubview(editor)
36 |
37 | // track selected note
38 | note.asObservable()
39 | .observeOn(MainScheduler.instance)
40 | .subscribe(onNext: { [weak self] note in
41 | self?.updateNoteContents(with: note.body)
42 | }).addDisposableTo(disposeBag)
43 |
44 | // track note editing
45 | contents.asObservable()
46 | .observeOn(MainScheduler.instance)
47 | .distinctUntilChanged()
48 | .debounce(1, scheduler: MainScheduler.instance)
49 | .subscribe(onNext: { [weak self] contents in
50 | self?.didEditNote(with: contents)
51 | }).addDisposableTo(disposeBag)
52 | }
53 |
54 | private func updateNoteContents(with body: String) {
55 | editor.setContents(body)
56 |
57 | view.window?.makeFirstResponder(editor)
58 | }
59 |
60 | fileprivate func didEditNote(with body: String) {
61 | var note = self.note.value
62 |
63 | guard note.body != body else { return }
64 |
65 | note.modifiedAt = Date()
66 | note.body = body
67 |
68 | do {
69 | try storage?.store(note: note)
70 | } catch {
71 | NSLog("Error storing note: \(error)")
72 | }
73 | }
74 |
75 | }
76 |
77 | extension NoteEditorViewController: NoteEditorViewDelegate {
78 |
79 | func noteEditorView(_ sender: NoteEditorView, contentsDidChange contents: String) {
80 | self.contents.value = contents
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/NoteTaker/NoteTaker-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "NSTableView+Diff.h"
6 | #import "NSToolbarFlexibleSpaceItem.h"
7 |
--------------------------------------------------------------------------------
/NoteTaker/NoteTaker.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.aps-environment
6 | development
7 | com.apple.developer.icloud-container-identifiers
8 |
9 | iCloud.br.com.guilhermerambo.NoteTakerShared
10 |
11 | com.apple.developer.icloud-services
12 |
13 | CloudKit
14 |
15 | com.apple.developer.ubiquity-kvstore-identifier
16 | $(TeamIdentifierPrefix)$(CFBundleIdentifier)
17 |
18 |
19 |
--------------------------------------------------------------------------------
/NoteTaker/NotesSplitViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotesSplitViewController.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import RxSwift
11 | import RxOptional
12 | import RxCocoa
13 | import CloudKit
14 | import NoteTakerCore
15 |
16 | /**
17 | NOTE: The code in this controller should probably live in a coordinator or something
18 | I usually avoid storyboards but used them here because this is a prototype, sample app
19 | */
20 |
21 | class NotesSplitViewController: NSSplitViewController {
22 |
23 | private let disposeBag = DisposeBag()
24 |
25 | lazy var storage: NotesStorage = {
26 | return NotesStorage()
27 | }()
28 |
29 | var syncEngine: NotesSyncEngine!
30 |
31 | var notesController: NotesTableViewController {
32 | return childViewControllers[0] as! NotesTableViewController
33 | }
34 |
35 | var editorController: NoteEditorViewController {
36 | return childViewControllers[1] as! NoteEditorViewController
37 | }
38 |
39 | override func viewDidLoad() {
40 | super.viewDidLoad()
41 |
42 | childViewControllers.flatMap({ $0 as? NeedsStorage }).forEach({ $0.storage = storage })
43 |
44 | syncEngine = NotesSyncEngine(storage: storage)
45 |
46 | view.wantsLayer = true
47 |
48 | notesController.selectedNote
49 | .asObservable()
50 | .replaceNilWith(.empty)
51 | .bindTo(editorController.note)
52 | .addDisposableTo(disposeBag)
53 | }
54 |
55 | override func viewDidAppear() {
56 | super.viewDidAppear()
57 |
58 | setupWindowStuff()
59 | }
60 |
61 | // Ideally, this window and toolbar setup code would live in a window controller, but this is just a prototype ;)
62 | private func setupWindowStuff() {
63 | guard let window = view.window else { return }
64 |
65 | window.titleVisibility = .hidden
66 |
67 | guard let toolbar = window.toolbar else { return }
68 |
69 | let flexibleItems = toolbar.items.flatMap({ $0 as? NSToolbarFlexibleSpaceItem })
70 |
71 | guard flexibleItems.count > 1 else { return }
72 |
73 | flexibleItems[1].trackedSplitView = self.splitView
74 | }
75 |
76 | @IBAction func newNote(_ sender: Any?) {
77 | let note = Note(body: "New Note")
78 |
79 | do {
80 | try storage.store(note: note)
81 | } catch {
82 | NSAlert(error: error).runModal()
83 | }
84 |
85 | // make sure selectLatestNote is executed after the current runloop cycle so the recently created note is already there
86 | notesController.perform(#selector(NotesTableViewController.selectLatestNote), with: nil, afterDelay: 0)
87 | }
88 |
89 | @IBAction func shareNote(_ sender: Any?) {
90 | guard let window = view.window else { return }
91 |
92 | let alert = NSAlert()
93 | alert.messageText = "Sharing Not Available"
94 | alert.informativeText = "Sharing is not implemented yet"
95 | alert.addButton(withTitle: "OK")
96 | alert.beginSheetModal(for: window, completionHandler: nil)
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/NoteTaker/NotesTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotesTableViewController.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import RxSwift
11 | import RxCocoa
12 | import NoteTakerCore
13 |
14 | class NotesTableViewController: NSViewController, NeedsStorage {
15 |
16 | @IBOutlet weak var tableView: NSTableView!
17 |
18 | var storage: NotesStorage? {
19 | didSet {
20 | guard let storage = storage else { return }
21 |
22 | subscribe(to: storage)
23 | }
24 | }
25 |
26 | var notes = Variable<[Note]>([])
27 |
28 | var viewModels = [NoteViewModel]() {
29 | didSet {
30 | tableView.reloadData(withOldValue: oldValue, newValue: viewModels)
31 |
32 | // trigger table selection to make sure the selected note updates in the editor if needed
33 | perform(#selector(updateSelectedNote), with: nil, afterDelay: 0)
34 | }
35 | }
36 |
37 | lazy var selectedNote: Observable = {
38 | return self.tableView.rx.selectedRow.map { selectedRow -> Note? in
39 | if let row = selectedRow {
40 | return self.viewModels[row].note
41 | } else {
42 | return nil
43 | }
44 | }
45 | }()
46 |
47 | private let disposeBag = DisposeBag()
48 |
49 | override func viewDidLoad() {
50 | super.viewDidLoad()
51 |
52 | tableView.dataSource = self
53 | tableView.delegate = self
54 |
55 | notes.asObservable()
56 | .map({ $0.map(NoteViewModel.init) })
57 | .subscribe { [weak self] event in
58 | switch event {
59 | case .next(let noteViewModels):
60 | self?.viewModels = noteViewModels
61 | break
62 | default: break
63 | }
64 | }.addDisposableTo(disposeBag)
65 | }
66 |
67 | private func subscribe(to storage: NotesStorage) {
68 | storage.allNotes.asObservable()
69 | .observeOn(MainScheduler.instance)
70 | .bindTo(notes)
71 | .addDisposableTo(disposeBag)
72 | }
73 |
74 | @objc func selectLatestNote() {
75 | tableView.rx.selectedRow.onNext(0)
76 | }
77 |
78 | @objc func updateSelectedNote() {
79 | // "flick" selection to force-refresh the note being edited
80 | let selection = self.tableView.selectedRow
81 | tableView.rx.selectedRow.onNext(nil)
82 | tableView.rx.selectedRow.onNext(selection)
83 | }
84 |
85 | @IBAction func delete(_ sender: Any) {
86 | let identifiers = tableView.selectedRowIndexes.map({ viewModels[$0].note.identifier })
87 |
88 | do {
89 | try identifiers.forEach({ try storage?.delete(with: $0) })
90 | } catch {
91 | let alert = NSAlert(error: error)
92 |
93 | if let window = view.window {
94 | alert.beginSheetModal(for: window, completionHandler: nil)
95 | } else {
96 | alert.runModal()
97 | }
98 | }
99 | }
100 |
101 | }
102 |
103 | extension NotesTableViewController: NSTableViewDataSource, NSTableViewDelegate {
104 |
105 | func numberOfRows(in tableView: NSTableView) -> Int {
106 | return viewModels.count
107 | }
108 |
109 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
110 | guard row < viewModels.count else { return nil }
111 |
112 | let cell: NSTableCellView? = tableView.make(withIdentifier: "cell", owner: tableView) as? NSTableCellView
113 |
114 | cell?.textField?.stringValue = viewModels[row].title
115 |
116 | return cell
117 | }
118 |
119 | func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableRowActionEdge) -> [NSTableViewRowAction] {
120 | let deleteAction = NSTableViewRowAction(style: .destructive, title: "Delete") { _, row in
121 | guard row < self.viewModels.count else { return }
122 | let note = self.viewModels[row].note
123 |
124 | do {
125 | try self.storage?.delete(with: note.identifier)
126 | } catch {
127 | NSAlert(error: error).runModal()
128 | }
129 | }
130 |
131 | return [deleteAction]
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/NoteTakerCore/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2017 Guilherme Rambo. All rights reserved.
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/NoteTakerCore/NoteTakerCore.h:
--------------------------------------------------------------------------------
1 | //
2 | // NoteTakerCore.h
3 | // NoteTakerCore
4 | //
5 | // Created by Guilherme Rambo on 27/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #if TARGET_OS_SIMULATOR || TARGET_OS_IOS
12 | #import
13 | #else
14 | #import
15 | #endif
16 |
17 | //! Project version number for NoteTakerCore.
18 | FOUNDATION_EXPORT double NoteTakerCoreVersionNumber;
19 |
20 | //! Project version string for NoteTakerCore.
21 | FOUNDATION_EXPORT const unsigned char NoteTakerCoreVersionString[];
22 |
23 | // In this header, you should import all the public headers of your framework using statements like #import
24 |
25 |
26 |
--------------------------------------------------------------------------------
/NoteTakerCore/Resources/Editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
33 |
34 |
35 | {BODY}
36 |
37 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/Note+RealmNote.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Note+RealmNote.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 12/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 |
12 | extension Note {
13 |
14 | init(realmNote: RealmNote) {
15 | self.identifier = realmNote.identifier
16 | self.body = realmNote.body
17 | self.createdAt = realmNote.createdAt
18 | self.modifiedAt = realmNote.modifiedAt
19 | }
20 |
21 | var realmNote: RealmNote {
22 | let note = RealmNote()
23 |
24 | note.identifier = identifier
25 | note.body = body
26 | note.createdAt = createdAt
27 | note.modifiedAt = modifiedAt
28 |
29 | return note
30 | }
31 |
32 | }
33 |
34 | extension RealmNote {
35 |
36 | var note: Note {
37 | return Note(body: body,
38 | identifier: identifier,
39 | createdAt: createdAt,
40 | modifiedAt: modifiedAt)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/Note.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Note.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Note {
12 |
13 | public let identifier: String
14 | public let createdAt: Date
15 | public var modifiedAt: Date
16 | public var body: String
17 |
18 | public init(body: String,
19 | identifier: String? = nil,
20 | createdAt: Date? = nil,
21 | modifiedAt: Date? = nil) {
22 | self.body = body
23 | self.identifier = identifier ?? UUID().uuidString
24 | self.createdAt = createdAt ?? Date()
25 | self.modifiedAt = modifiedAt ?? Date()
26 | }
27 |
28 | }
29 |
30 | extension Note {
31 |
32 | public static var empty: Note {
33 | return Note(body: "")
34 | }
35 |
36 | }
37 |
38 | extension Note: Equatable {
39 |
40 | public static func ==(lhs: Note, rhs: Note) -> Bool {
41 | return lhs.identifier == rhs.identifier
42 | && lhs.createdAt == rhs.createdAt
43 | && lhs.modifiedAt == rhs.modifiedAt
44 | && lhs.body == rhs.body
45 | }
46 |
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/NoteEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteEditorView.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 27/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #if os(macOS)
10 | import Cocoa
11 |
12 | public typealias UIView = NSView
13 | #else
14 | import UIKit
15 | #endif
16 |
17 | import WebKit
18 |
19 | public protocol NoteEditorViewDelegate: class {
20 | func noteEditorView(_ sender: NoteEditorView, contentsDidChange contents: String)
21 | }
22 |
23 | public final class NoteEditorView: UIView {
24 |
25 | public weak var delegate: NoteEditorViewDelegate?
26 |
27 | public fileprivate(set) var currentContents: String = ""
28 |
29 | public func setContents(_ contents: String) {
30 | loadEditor(with: contents)
31 | }
32 |
33 | fileprivate var webViewIsSettingContents = false
34 |
35 | #if os(macOS)
36 |
37 | public override var isFlipped: Bool {
38 | return true
39 | }
40 |
41 | public override init(frame frameRect: NSRect) {
42 | super.init(frame: frameRect)
43 |
44 | setup()
45 | }
46 |
47 | public required init?(coder: NSCoder) {
48 | super.init(coder: coder)
49 |
50 | setup()
51 | }
52 |
53 | public override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
54 | return webView.acceptsFirstMouse(for: event)
55 | }
56 |
57 | public override var acceptsFirstResponder: Bool {
58 | return true
59 | }
60 |
61 | public override var canBecomeKeyView: Bool {
62 | return true
63 | }
64 |
65 | #else
66 |
67 | public override init(frame: CGRect) {
68 | super.init(frame: frame)
69 |
70 | setup()
71 | }
72 |
73 | public required init?(coder aDecoder: NSCoder) {
74 | super.init(coder: aDecoder)
75 |
76 | setup()
77 | }
78 |
79 | #endif
80 |
81 | public override func becomeFirstResponder() -> Bool {
82 | return webView.becomeFirstResponder()
83 | }
84 |
85 | public override func awakeFromNib() {
86 | super.awakeFromNib()
87 |
88 | setup()
89 | }
90 |
91 | private var setupDone = false
92 |
93 | private func setup() {
94 | guard !setupDone else { return }
95 | setupDone = true
96 |
97 | #if os(macOS)
98 | wantsLayer = true
99 | layer?.backgroundColor = NSColor.white.cgColor
100 | #else
101 | backgroundColor = .white
102 | #endif
103 |
104 | webView.frame = bounds
105 | addSubview(webView)
106 | }
107 |
108 | private lazy var webView: WKWebView = {
109 | let v = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
110 |
111 | #if os(macOS)
112 | v.autoresizingMask = [.viewHeightSizable, .viewWidthSizable]
113 | #else
114 | v.autoresizingMask = [.flexibleWidth, .flexibleHeight]
115 | #endif
116 |
117 | v.configuration.userContentController.add(self, name: "editor")
118 |
119 | return v
120 | }()
121 |
122 | private lazy var editorSource: String? = {
123 | guard let editorSourceFile = Bundle(for: NoteEditorView.self).url(forResource: "Editor", withExtension: "html") else { return nil }
124 | guard let editorSourceData = try? Data(contentsOf: editorSourceFile) else { return nil }
125 |
126 | return String(data: editorSourceData, encoding: .utf8)
127 | }()
128 |
129 | private func loadEditor(with contents: String = "") {
130 | guard let editorSource = editorSource else { return }
131 |
132 | webView.loadHTMLString(editorSource.replacingOccurrences(of: "{BODY}", with: contents), baseURL: nil)
133 | }
134 |
135 | }
136 |
137 | private enum WebViewMessages: String {
138 | case loaded
139 | case input
140 | }
141 |
142 | extension NoteEditorView: WKScriptMessageHandler {
143 |
144 | public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
145 | guard let inputStr = message.body as? String else { return }
146 |
147 | DispatchQueue.main.async {
148 | self.currentContents = inputStr
149 |
150 | self.delegate?.noteEditorView(self, contentsDidChange: inputStr)
151 | }
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/NoteViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteViewModel.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import IGListKit
11 |
12 | public class NoteViewModel: NSObject, IGListDiffable {
13 |
14 | public let note: Note
15 |
16 | public lazy var title: String = {
17 | return self.note.body.firstLine.removingHTML
18 | }()
19 |
20 | public init(note: Note) {
21 | self.note = note
22 |
23 | super.init()
24 | }
25 |
26 | public func diffIdentifier() -> NSObjectProtocol {
27 | return note.identifier as NSObjectProtocol
28 | }
29 |
30 | public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
31 | guard let other = object as? NoteViewModel else { return false }
32 |
33 | return other.note == self.note
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/NotesStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotesStorage.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 10/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import RxRealm
12 | import RxSwift
13 |
14 | enum StorageError: Error {
15 | case recordNotFound(String)
16 |
17 | var localizedDescription: String {
18 | switch self {
19 | case .recordNotFound(let identifier):
20 | return "Record not found with primary key \(identifier)"
21 | }
22 | }
23 | }
24 |
25 | /// This class is responsible for the management of the local database (fetching, saving and deleting notes)
26 | public final class NotesStorage {
27 |
28 | typealias UpdateDecisionHandler = (_ oldObject: T, _ newObject: T) -> Bool
29 |
30 | let realm: Realm
31 |
32 | init(realm: Realm? = nil) {
33 | if let r = realm {
34 | self.realm = r
35 | } else {
36 | self.realm = try! Realm()
37 | }
38 | }
39 |
40 | public convenience init() {
41 | self.init(realm: nil)
42 | }
43 |
44 | public var allNotes: Observable<[Note]> {
45 | // Instead of immediately deleting notes when they are deleted in the UI,
46 | // we simply set a flag on them that is used to delete the record on the Cloud
47 | // This is necessary because of the way Realm collection notifications work,
48 | // it is the best workaround I've found
49 | let objects = self.realm.objects(RealmNote.self)
50 | .filter(NSPredicate(format: "isDeleted == false"))
51 | .sorted(byKeyPath: "modifiedAt", ascending: false)
52 |
53 | return Observable.collection(from: objects).map { realmNotes in
54 | return realmNotes.map({ $0.note })
55 | }
56 | }
57 |
58 | var mostRecentlyModifiedNote: Note? {
59 | let realmNotes = realm.objects(RealmNote.self)
60 | .sorted(byKeyPath: NoteKey.modifiedAt.rawValue, ascending: false)
61 |
62 | return realmNotes.first?.note
63 | }
64 |
65 | public func store(note: Note) throws {
66 | try store(realmNote: note.realmNote)
67 | }
68 |
69 | func store(realmNote: RealmNote, notNotifying token: NotificationToken? = nil) throws {
70 | try insertOrUpdate(object: realmNote, notNotifying: token) { oldNote, newNote in
71 | guard newNote != oldNote else { return false }
72 |
73 | return newNote.modifiedAt > oldNote.modifiedAt
74 | }
75 | }
76 |
77 | public func delete(with identifier: String, hard reallyDelete: Bool = false) throws {
78 | guard let note = realm.object(ofType: RealmNote.self, forPrimaryKey: identifier) else {
79 | throw StorageError.recordNotFound(identifier)
80 | }
81 |
82 | try realm.write {
83 | if reallyDelete {
84 | self.realm.delete(note)
85 | } else {
86 | note.isDeleted = true
87 | self.realm.add(note, update: true)
88 | }
89 | }
90 | }
91 |
92 | func deletePreviouslySoftDeletedNotes(notNotifying token: NotificationToken? = nil) throws {
93 | let objects = realm.objects(RealmNote.self).filter("isDeleted = true")
94 |
95 | let tokens: [NotificationToken]
96 |
97 | if let token = token {
98 | tokens = [token]
99 | } else {
100 | tokens = []
101 | }
102 |
103 | realm.beginWrite()
104 | objects.forEach({ realm.delete($0) })
105 | try realm.commitWrite(withoutNotifying: tokens)
106 | }
107 |
108 | private func insertOrUpdate(objects: [T],
109 | notNotifying token: NotificationToken? = nil,
110 | updateDecisionHandler: @escaping UpdateDecisionHandler) throws {
111 | try objects.forEach({ try self.insertOrUpdate(object: $0, notNotifying: token, updateDecisionHandler: updateDecisionHandler) })
112 | }
113 |
114 | private func insertOrUpdate(object: T,
115 | notNotifying token: NotificationToken? = nil,
116 | updateDecisionHandler: @escaping UpdateDecisionHandler) throws {
117 | guard let primaryKey = T.primaryKey() else {
118 | fatalError("insertOrUpdate can't be used for objects without a primary key")
119 | }
120 |
121 | guard let primaryKeyValue = object.value(forKey: primaryKey) else {
122 | fatalError("insertOrUpdate can't be used for objects without a primary key")
123 | }
124 |
125 | let tokens: [NotificationToken]
126 |
127 | if let token = token {
128 | tokens = [token]
129 | } else {
130 | tokens = []
131 | }
132 |
133 | if let existingObject = realm.object(ofType: T.self, forPrimaryKey: primaryKeyValue) {
134 | // object already exists, call updateDecisionHandler to determine whether we should update it or not
135 | if updateDecisionHandler(existingObject, object) {
136 | realm.beginWrite()
137 | realm.add(object, update: true)
138 | try realm.commitWrite(withoutNotifying: tokens)
139 | }
140 | } else {
141 | // object doesn't exist, just add it
142 | realm.beginWrite()
143 | realm.add(object)
144 | try realm.commitWrite(withoutNotifying: tokens)
145 | }
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/NotesSyncEngine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotesSyncEngine.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 11/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import RxRealm
12 | import RxSwift
13 | import CloudKit
14 |
15 | extension CKContainer {
16 | static let noteTakerShared = CKContainer(identifier: "iCloud.br.com.guilhermerambo.NoteTakerShared")
17 | }
18 |
19 | extension Notification.Name {
20 | public static let notesDidChangeRemotely = Notification.Name(rawValue: "NotesDidChangeRemotely")
21 | }
22 |
23 | private func slog(_ format: String, _ args: CVarArg...) {
24 | guard ProcessInfo.processInfo.arguments.contains("--log-sync") else { return }
25 |
26 | NSLog("[SYNC] " + format, args)
27 | }
28 |
29 | /// This class is responsible for observing changes to the local database and pushing them to CloudKit
30 | /// as well as observing changes in CloudKit and syncing them to the local database
31 | public final class NotesSyncEngine: NSObject {
32 |
33 | private struct Constants {
34 | static let previousChangeToken = "PreviousChangeToken"
35 | static let noteRecordType = "Note"
36 | }
37 |
38 | /// The CloudKit container the sync engine is using
39 | private let container: CKContainer
40 |
41 | /// The user's private CloudKit database
42 | private let privateDatabase: CKDatabase
43 |
44 | /// Local storage controller
45 | private let storage: NotesStorage
46 |
47 | /// Initializes the sync engine with a local storage
48 | public init(storage: NotesStorage, container: CKContainer = .noteTakerShared) {
49 | self.storage = storage
50 | self.container = container
51 | self.privateDatabase = container.privateCloudDatabase
52 |
53 | super.init()
54 |
55 | // Fetch notifications not processed yet
56 | fetchServerNotifications()
57 |
58 | // Do initial cloud fetch
59 | fetchCloudKitNotes()
60 |
61 | // Sync magic
62 | subscribeToLocalDatabaseChanges()
63 | subscribeToCloudKitChanges()
64 |
65 | // Clean database before the app terminates
66 | #if os(macOS)
67 | NotificationCenter.default.addObserver(self, selector: #selector(cleanup(_:)), name: .NSApplicationWillTerminate, object: NSApp)
68 | #else
69 | NotificationCenter.default.addObserver(self, selector: #selector(cleanup(_:)), name: .UIApplicationWillTerminate, object: UIApplication.shared)
70 | #endif
71 | }
72 |
73 | /// The modification date of the last note modified locally to use when querying the server
74 | private var modificationDateForQuery: Date {
75 | return storage.mostRecentlyModifiedNote?.modifiedAt ?? Date.distantPast
76 | }
77 |
78 | /// Download notes from CloudKit
79 | private func fetchCloudKitNotes(_ inputCursor: CKQueryCursor? = nil) {
80 | let operation: CKQueryOperation
81 |
82 | // We may be starting a new query or continuing a previous one if there are many results
83 | if let cursor = inputCursor {
84 | operation = CKQueryOperation(cursor: cursor)
85 | } else {
86 | // This query will fetch all notes modified since the last sync, sorted by modification date (descending)
87 | let predicate = NSPredicate(format: "modifiedAt > %@", modificationDateForQuery as CVarArg)
88 | let query = CKQuery(recordType: Constants.noteRecordType, predicate: predicate)
89 | query.sortDescriptors = [NSSortDescriptor(key: NoteKey.modifiedAt.rawValue, ascending: false)]
90 | operation = CKQueryOperation(query: query)
91 | }
92 |
93 | operation.queryCompletionBlock = { [weak self] cursor, error in
94 | guard error == nil else {
95 | self?.retryCloudKitOperationIfPossible(with: error) {
96 | self?.fetchCloudKitNotes(inputCursor)
97 | }
98 |
99 | return
100 | }
101 |
102 | if let cursor = cursor {
103 | // There are more results to come, continue fetching
104 | self?.fetchCloudKitNotes(cursor)
105 | }
106 | }
107 |
108 | operation.recordFetchedBlock = { [weak self] record in
109 | // When a note is fetched from the cloud, process it into the local database
110 | self?.processFetchedNote(record)
111 | }
112 |
113 | privateDatabase.add(operation)
114 | }
115 |
116 | private let disposeBag = DisposeBag()
117 |
118 | /// Realm collection notification token
119 | private var notificationToken: NotificationToken?
120 |
121 | private func subscribeToLocalDatabaseChanges() {
122 | let notes = storage.realm.objects(RealmNote.self)
123 |
124 | // Here we subscribe to changes in notes to push them to CloudKit
125 | notificationToken = notes.addNotificationBlock { [weak self] changes in
126 | guard let welf = self else { return }
127 |
128 | switch changes {
129 | case .update(let collection, _, let insertions, let modifications):
130 | // Figure out which notes should be saved and which notes should be deleted
131 | let notesToSave = (insertions + modifications).map({ collection[$0] }).filter({ !$0.isDeleted })
132 | let notesToDelete = modifications.map({ collection[$0] }).filter({ $0.isDeleted })
133 |
134 | // Push changes to CloudKitx
135 | welf.pushToCloudKit(notesToUpdate: notesToSave, notesToDelete: notesToDelete)
136 | case .error(let error):
137 | slog("Realm notification error: \(error)")
138 | default: break
139 | }
140 | }
141 | }
142 |
143 | fileprivate func pushToCloudKit(notesToUpdate: [RealmNote], notesToDelete: [RealmNote]) {
144 | guard notesToUpdate.count > 0 || notesToDelete.count > 0 else { return }
145 |
146 | slog("\(notesToUpdate.count) note(s) to save, \(notesToDelete.count) note(s) to delete")
147 |
148 | let recordsToSave = notesToUpdate.map({ $0.record })
149 | let recordsToDelete = notesToDelete.map({ $0.recordID })
150 |
151 | pushRecordsToCloudKit(recordsToUpdate: recordsToSave, recordIDsToDelete: recordsToDelete)
152 | }
153 |
154 | fileprivate func pushRecordsToCloudKit(recordsToUpdate: [CKRecord], recordIDsToDelete: [CKRecordID], completion: ((Error?) -> ())? = nil) {
155 | let operation = CKModifyRecordsOperation(recordsToSave: recordsToUpdate, recordIDsToDelete: recordIDsToDelete)
156 | operation.savePolicy = .changedKeys
157 |
158 | operation.modifyRecordsCompletionBlock = { [weak self] _, _, error in
159 | guard error == nil else {
160 | slog("Error modifying records: \(error!)")
161 |
162 | self?.retryCloudKitOperationIfPossible(with: error) {
163 | self?.pushRecordsToCloudKit(recordsToUpdate: recordsToUpdate,
164 | recordIDsToDelete: recordIDsToDelete,
165 | completion: completion)
166 | }
167 | return
168 | }
169 |
170 | slog("Finished saving records")
171 |
172 | DispatchQueue.main.async {
173 | completion?(nil)
174 | }
175 | }
176 |
177 | privateDatabase.add(operation)
178 | }
179 |
180 | private func subscribeToCloudKitChanges() {
181 | startObservingCloudKitChanges()
182 |
183 | // Create the CloudKit subscription so we receive push notifications when notes change remotely
184 | let subscription = CKQuerySubscription(recordType: Constants.noteRecordType,
185 | predicate: NSPredicate(value: true),
186 | options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion])
187 |
188 | let info = CKNotificationInfo()
189 | info.shouldSendContentAvailable = true
190 | info.soundName = ""
191 | subscription.notificationInfo = info
192 |
193 | privateDatabase.save(subscription) { [weak self] subscription, error in
194 | if subscription != nil {
195 | slog("Successfully subscribed to cloud database changes")
196 | } else {
197 | guard error == nil else {
198 | self?.retryCloudKitOperationIfPossible(with: error) {
199 | self?.subscribeToCloudKitChanges()
200 | }
201 | return
202 | }
203 | }
204 | }
205 | }
206 |
207 | /// Holds the latest change token we got from CloudKit, storing it in UserDefaults
208 | private var previousChangeToken: CKServerChangeToken? {
209 | get {
210 | guard let tokenData = UserDefaults.standard.object(forKey: Constants.previousChangeToken) as? Data else { return nil }
211 |
212 | return NSKeyedUnarchiver.unarchiveObject(with: tokenData) as? CKServerChangeToken
213 | }
214 | set {
215 | guard let newValue = newValue else {
216 | UserDefaults.standard.setNilValueForKey(Constants.previousChangeToken)
217 | return
218 | }
219 |
220 | let data = NSKeyedArchiver.archivedData(withRootObject: newValue)
221 |
222 | UserDefaults.standard.set(data, forKey: Constants.previousChangeToken)
223 | }
224 | }
225 |
226 | // CloudKit notes observer
227 | private var changesObserver: NSObjectProtocol?
228 |
229 | private func startObservingCloudKitChanges() {
230 | // The .notesDidChangeRemotely local notification is posted by the AppDelegate when it receives a push notification from CloudKit
231 | changesObserver = NotificationCenter.default.addObserver(forName: .notesDidChangeRemotely,
232 | object: nil,
233 | queue: OperationQueue.main)
234 | { [weak self] note in
235 | // When a notification is received from the server, we must download the notifications because they might have been coalesced
236 | self?.fetchServerNotifications()
237 | }
238 | }
239 |
240 | private func fetchServerNotifications() {
241 | let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: previousChangeToken)
242 |
243 | // This will hold the identifiers for every changed record
244 | var updatedIdentifiers = [CKRecordID]()
245 |
246 | // This will hold the notification IDs we processed so we can tell CloudKit to never send them to us again
247 | var notificationIDs = [CKNotificationID]()
248 |
249 | operation.notificationChangedBlock = { [weak self] notification in
250 | guard let notification = notification as? CKQueryNotification else { return }
251 | guard let identifier = notification.recordID else { return }
252 |
253 | if let id = notification.notificationID {
254 | notificationIDs.append(id)
255 | }
256 |
257 | DispatchQueue.main.async {
258 | switch notification.queryNotificationReason {
259 | case .recordDeleted:
260 | do {
261 | try self?.storage.delete(with: identifier.recordName, hard: true)
262 | } catch {
263 | slog("Error deleting note from cloud instruction: \(error)")
264 | }
265 | default:
266 | updatedIdentifiers.append(identifier)
267 | }
268 | }
269 | }
270 |
271 | operation.fetchNotificationChangesCompletionBlock = { [weak self] newToken, error in
272 | guard error == nil else {
273 | self?.retryCloudKitOperationIfPossible(with: error) {
274 | self?.fetchServerNotifications()
275 | }
276 |
277 | return
278 | }
279 |
280 | self?.previousChangeToken = newToken
281 |
282 | // All records are in, now save the data locally
283 | self?.consolidateUpdatedCloudNotes(with: updatedIdentifiers)
284 |
285 | // Tell CloudKit we've read the notifications
286 | self?.markNotificationsAsRead(with: notificationIDs)
287 | }
288 |
289 | container.add(operation)
290 | }
291 |
292 | private func markNotificationsAsRead(with identifiers: [CKNotificationID]) {
293 | let operation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: identifiers)
294 |
295 | operation.markNotificationsReadCompletionBlock = { [weak self] _, error in
296 | guard error == nil else {
297 | self?.retryCloudKitOperationIfPossible(with: error) {
298 | self?.markNotificationsAsRead(with: identifiers)
299 | }
300 |
301 | return
302 | }
303 | }
304 |
305 | container.add(operation)
306 | }
307 |
308 | /// Download a list of records from CloudKit and update the local database accordingly
309 | private func consolidateUpdatedCloudNotes(with identifiers: [CKRecordID]) {
310 | let operation = CKFetchRecordsOperation(recordIDs: identifiers)
311 |
312 | operation.fetchRecordsCompletionBlock = { [weak self] records, error in
313 | guard let records = records else {
314 | self?.retryCloudKitOperationIfPossible(with: error) {
315 | self?.consolidateUpdatedCloudNotes(with: identifiers)
316 | }
317 | return
318 | }
319 |
320 | records.values.forEach { record in
321 | self?.processFetchedNote(record)
322 | }
323 | }
324 |
325 | privateDatabase.add(operation)
326 | }
327 |
328 | /// Sync a single note to the local database
329 | private func processFetchedNote(_ cloudKitNote: CKRecord) {
330 | DispatchQueue.main.async {
331 | guard let note = RealmNote.from(record: cloudKitNote) else {
332 | slog("Error creating local note from cloud note \(cloudKitNote.recordID.recordName)")
333 | return
334 | }
335 |
336 | do {
337 | try self.storage.store(realmNote: note, notNotifying: self.notificationToken)
338 | } catch {
339 | slog("Error saving local note from cloud note \(cloudKitNote.recordID.recordName): \(error)")
340 | }
341 | }
342 | }
343 |
344 | @objc func cleanup(_ notification: Notification? = nil) {
345 | do {
346 | try storage.deletePreviouslySoftDeletedNotes(notNotifying: self.notificationToken)
347 | } catch {
348 | NSLog("Failed to delete previously soft deleted notes: \(error)")
349 | }
350 | }
351 |
352 | // MARK: - Util
353 |
354 | /// Helper method to retry a CloudKit operation when its error suggests it
355 | private func retryCloudKitOperationIfPossible(with error: Error?, block: @escaping () -> ()) {
356 | guard let error = error as? CKError else {
357 | slog("CloudKit puked ¯\\_(ツ)_/¯")
358 | return
359 | }
360 |
361 | guard let retryAfter = error.userInfo[CKErrorRetryAfterKey] as? NSNumber else {
362 | slog("CloudKit error: \(error)")
363 | return
364 | }
365 |
366 | slog("CloudKit operation error, retrying after \(retryAfter) seconds...")
367 |
368 | DispatchQueue.main.asyncAfter(deadline: .now() + retryAfter.doubleValue) {
369 | block()
370 | }
371 | }
372 |
373 | }
374 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/RealmNote+CKRecord.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RealmNote+CKRecord.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 12/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CloudKit
11 | import RealmSwift
12 |
13 | enum NoteKey: String {
14 | case identifier, createdAt, modifiedAt, body
15 | }
16 |
17 | extension CKRecord {
18 |
19 | subscript(_ key: NoteKey) -> CKRecordValue {
20 | get {
21 | return self[key.rawValue]!
22 | }
23 | set {
24 | self[key.rawValue] = newValue
25 | }
26 | }
27 |
28 | }
29 |
30 | extension RealmNote {
31 |
32 | var recordID: CKRecordID {
33 | return CKRecordID(recordName: identifier)
34 | }
35 |
36 | var record: CKRecord {
37 | let record = CKRecord(recordType: "Note", recordID: recordID)
38 |
39 | record[.identifier] = identifier as CKRecordValue
40 | record[.createdAt] = createdAt as CKRecordValue
41 | record[.modifiedAt] = modifiedAt as CKRecordValue
42 | record[.body] = body as CKRecordValue
43 |
44 | return record
45 | }
46 |
47 | static func from(record: CKRecord) -> RealmNote? {
48 | guard let identifier = record[.identifier] as? String,
49 | let createdAt = record[.createdAt] as? Date,
50 | let modifiedAt = record[.modifiedAt] as? Date,
51 | let body = record[.body] as? String
52 | else {
53 | return nil
54 | }
55 |
56 | let note = RealmNote()
57 |
58 | note.identifier = identifier
59 | note.createdAt = createdAt
60 | note.modifiedAt = modifiedAt
61 | note.body = body
62 |
63 | return note
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/RealmNote.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RealmNote.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 12/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 |
12 | final class RealmNote: Object {
13 |
14 | dynamic var identifier = ""
15 | dynamic var createdAt = Date()
16 | dynamic var modifiedAt = Date()
17 | dynamic var body = ""
18 | dynamic var isDeleted = false
19 |
20 | override class func primaryKey() -> String? {
21 | return "identifier"
22 | }
23 |
24 | static func ==(lhs: RealmNote, rhs: RealmNote) -> Bool {
25 | return lhs.note == rhs.note && lhs.isDeleted == rhs.isDeleted
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/NoteTakerCore/Source/String+HTML.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+HTML.swift
3 | // NoteTaker
4 | //
5 | // Created by Guilherme Rambo on 12/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | var parsingHTML: NSAttributedString {
14 | let attrs: [String: Any] = [
15 | NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
16 | NSCharacterEncodingDocumentAttribute: String.Encoding.utf8
17 | ]
18 |
19 | return NSAttributedString(string: self, attributes: attrs)
20 | }
21 |
22 | var removingHTML: String {
23 | return replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil)
24 | .replacingOccurrences(of: "&[^\\s]*;", with: "", options: .regularExpression, range: nil)
25 | }
26 |
27 | var firstLine: String {
28 | return components(separatedBy: .newlines).first ?? self
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NoteTaker
2 |
3 | A simple note taking app for macOS and iOS which uses [Realm](https://realm.io) and [CloudKit](https://developer.apple.com/icloud) for syncing.
4 |
5 | This is a sample project to demonstate an architecture for syncing described in my article on CloudKit.
6 |
7 | *Notice: the iOS version of the app only lists the notes and is only available to demonstrate syncing between devices/platforms*
8 |
9 | 
10 |
11 | ## Building
12 |
13 | Building requires:
14 |
15 | - Xcode 8.2.1
16 | - Carthage
17 | - Apple Developer account
18 |
19 | Before opening the project in Xcode, you must build its dependencies with Carthage (this can take several minutes, but has to be done only once):
20 |
21 | ```bash
22 | carthage update
23 | ```
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/NoteTaker/db7e5f1fe60c18b4f27209d7a0889b1f199a383b/screenshot.png
--------------------------------------------------------------------------------