diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 2c6d4c7e9..6c2be7612 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -299,14 +299,14 @@ angular.module('app.frontend') console.log("AuthManager received resolved UserPreferences", resolvedSingleton); this.userPreferences = resolvedSingleton; this.userPreferencesDidChange(); - }, () => { + }, (valueCallback) => { // Safe to create. Create and return object. var prefs = new Item({content_type: prefsContentType}); modelManager.addItem(prefs); prefs.setDirty(true); console.log("Created new prefs", prefs); $rootScope.sync(); - return prefs; + valueCallback(prefs); }); this.userPreferencesDidChange = function() { diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index 63f3d89e4..f237f1aa7 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -7,16 +7,14 @@ class RoomBar { }; } - controller($rootScope, $scope, desktopManager, syncManager, modelManager, componentManager, $timeout) { + controller($rootScope, $scope, desktopManager, syncManager, modelManager, componentManager, $timeout, singletonManager, packageManager) { 'ngInject'; $scope.componentManager = componentManager; $scope.rooms = []; modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { - $scope.rooms = _.uniq($scope.rooms - .concat(allItems - .filter((candidate) => {return candidate.area == "rooms"}))) + $scope.rooms = _.uniq($scope.rooms.concat(allItems.filter((candidate) => {return candidate.area == "rooms"}))) .filter((candidate) => {return !candidate.deleted}); }); @@ -55,6 +53,18 @@ class RoomBar { room.show = false; this.componentManager.deactivateComponent(room); } + + // Handle singleton ProLink instance + singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: "org.standardnotes.prolink"}}, (resolvedSingleton) => { + console.log("Roombar received resolved ProLink", resolvedSingleton); + }, (valueCallback) => { + console.log("Creating prolink"); + // Safe to create. Create and return object. + let url = window._prolink_package_url; + packageManager.installPackage(url, (component) => { + valueCallback(component); + }) + }); } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 70438bc0b..c4f475a7b 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -3,6 +3,7 @@ class ModelManager { constructor(storageManager) { ModelManager.MappingSourceRemoteRetrieved = "MappingSourceRemoteRetrieved"; ModelManager.MappingSourceRemoteSaved = "MappingSourceRemoteSaved"; + ModelManager.MappingSourceLocalSaved = "MappingSourceLocalSaved"; ModelManager.MappingSourceLocalRetrieved = "MappingSourceLocalRetrieved"; ModelManager.MappingSourceComponentRetrieved = "MappingSourceComponentRetrieved"; ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */ @@ -103,6 +104,10 @@ class ModelManager { return tag; } + didSyncModelsOffline(items) { + this.notifySyncObserversOfModels(items, ModelManager.MappingSourceLocalSaved); + } + mapResponseItemsToLocalModels(items, source) { return this.mapResponseItemsToLocalModelsOmittingFields(items, null, source); } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 3f6d260e7..70f92fd96 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -45,7 +45,7 @@ class SingletonManager { } resolveSingletons(retrievedItems, initialLoad) { - for(var singletonHandler of this.singletonHandlers) { + for(let singletonHandler of this.singletonHandlers) { var predicate = singletonHandler.predicate; var singletonItems = this.filterItemsWithPredicate(retrievedItems, predicate); if(singletonItems.length > 0) { @@ -63,7 +63,7 @@ class SingletonManager { */ if(allExtantItemsMatchingPredicate.length >= 2) { var toDelete = []; - for(var extantItem of allExtantItemsMatchingPredicate) { + for(let extantItem of allExtantItemsMatchingPredicate) { if(!singletonItems.includes(extantItem)) { // Delete it toDelete.push(extantItem); @@ -102,10 +102,13 @@ class SingletonManager { // Retrieved items does not include any items of interest. If we don't have a singleton registered to this handler, // we need to create one. Only do this on actual sync completetions and not on initial data load. Because we want // to get the latest from the server before making the decision to create a new item - if(!singletonHandler.singleton && !initialLoad) { - var item = singletonHandler.createBlock(); - singletonHandler.singleton = item; - singletonHandler.resolutionCallback(item); + if(!singletonHandler.singleton && !initialLoad && !singletonHandler.pendingCreateBlockCallback) { + singletonHandler.pendingCreateBlockCallback = true; + singletonHandler.createBlock((created) => { + singletonHandler.singleton = created; + singletonHandler.pendingCreateBlockCallback = false; + singletonHandler.resolutionCallback(created); + }); } } } @@ -113,13 +116,25 @@ class SingletonManager { filterItemsWithPredicate(items, predicate) { return items.filter((candidate) => { - for(var key in predicate) { - if(candidate[key] != predicate[key]) { + return this.itemSatisfiesPredicate(candidate, predicate); + }) + } + + itemSatisfiesPredicate(candidate, predicate) { + for(var key in predicate) { + var predicateValue = predicate[key]; + var candidateValue = candidate[key]; + if(typeof predicateValue == 'object') { + // Check nested properties + if(!this.itemSatisfiesPredicate(candidateValue, predicateValue)) { return false; } } - return true; - }) + else if(candidateValue != predicateValue) { + return false; + } + } + return true; } } diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 17f13fd78..571aac588 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -64,6 +64,9 @@ class SyncManager { this.$rootScope.$broadcast("sync:completed", {}); + // Required in order for modelManager to notify sync observers + this.modelManager.didSyncModelsOffline(items); + if(callback) { callback({success: true}); } diff --git a/app/views/application/frontend.html.erb b/app/views/application/frontend.html.erb index c1dea5432..28d3fb2c9 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/frontend.html.erb @@ -30,6 +30,7 @@ <% if Rails.env.development? %>