diff --git a/Gruntfile.js b/Gruntfile.js
index 826b1faf8..f3afa8c4a 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -70,9 +70,10 @@ module.exports = function(grunt) {
},
app: {
src: [
+ 'node_modules/sn-models/dist/sn-models.js',
'app/assets/javascripts/app/*.js',
- 'app/assets/javascripts/app/controllers/**/*.js',
'app/assets/javascripts/app/models/**/*.js',
+ 'app/assets/javascripts/app/controllers/**/*.js',
'app/assets/javascripts/app/services/**/*.js',
'app/assets/javascripts/app/filters/**/*.js',
'app/assets/javascripts/app/directives/**/*.js',
@@ -161,9 +162,9 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-browserify');
- // grunt.registerTask('default', ['haml', 'ngtemplates', 'sass', 'concat:app',
- // 'concat:lib', 'concat:dist', 'concat:css', 'babel', 'browserify', 'uglify']);
-
grunt.registerTask('default', ['haml', 'ngtemplates', 'sass', 'concat:app', 'babel', 'browserify',
'concat:lib', 'concat:dist', 'ngAnnotate', 'concat:css', 'uglify']);
+
+ grunt.registerTask('vendor', ['concat:app', 'babel', 'browserify',
+ 'concat:lib', 'concat:dist', 'ngAnnotate', 'concat:css', 'uglify']);
};
diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js
index 4022e18ae..bbf5c2354 100644
--- a/app/assets/javascripts/app/controllers/editor.js
+++ b/app/assets/javascripts/app/controllers/editor.js
@@ -3,7 +3,6 @@ angular.module('app')
return {
restrict: 'E',
scope: {
- save: "&",
remove: "&",
note: "=",
updateTags: "&"
@@ -23,8 +22,8 @@ angular.module('app')
}
}
})
- .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager) {
-
+ .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory) {
+
this.spellcheck = true;
this.componentManager = componentManager;
@@ -36,10 +35,6 @@ angular.module('app')
this.syncTakingTooLong = false;
}.bind(this));
- $rootScope.$on("tag-changed", function(){
- this.loadTagsString();
- }.bind(this));
-
// Right now this only handles offline saving status changes.
this.syncStatusObserver = syncManager.registerSyncStatusObserver((status) => {
if(status.localError) {
@@ -53,7 +48,7 @@ angular.module('app')
}
})
- modelManager.addItemSyncObserver("component-manager", "Note", (allItems, validItems, deletedItems, source) => {
+ modelManager.addItemSyncObserver("editor-note-observer", "Note", (allItems, validItems, deletedItems, source) => {
if(!this.note) { return; }
// Before checking if isMappingSourceRetrieved, we check if this item was deleted via a local source,
@@ -64,7 +59,7 @@ angular.module('app')
return;
}
- if(!ModelManager.isMappingSourceRetrieved(source)) {
+ if(!SFModelManager.isMappingSourceRetrieved(source)) {
return;
}
@@ -80,6 +75,37 @@ angular.module('app')
this.loadTagsString();
});
+ modelManager.addItemSyncObserver("editor-tag-observer", "Tag", (allItems, validItems, deletedItems, source) => {
+ if(!this.note) { return; }
+
+ for(var tag of allItems) {
+ // If a tag is deleted then we'll have lost references to notes. Reload anyway.
+ if(this.note.savedTagsString == null || tag.deleted || tag.hasRelationshipWithItem(this.note)) {
+ this.loadTagsString();
+ return;
+ }
+ }
+ });
+
+ // Observe editor changes to see if the current note should update its editor
+
+ modelManager.addItemSyncObserver("editor-component-observer", "SN|Component", (allItems, validItems, deletedItems, source) => {
+ if(!this.note) { return; }
+
+ var editors = allItems.filter(function(item) {
+ return item.isEditor();
+ });
+
+ // If no editors have changed
+ if(editors.length == 0) {
+ return;
+ }
+
+ // Look through editors again and find the most proper one
+ var editor = this.editorForNote(this.note);
+ this.selectedEditor = editor;
+ });
+
this.noteDidChange = function(note, oldNote) {
this.setNote(note, oldNote);
this.reloadComponentContext();
@@ -88,6 +114,7 @@ angular.module('app')
this.setNote = function(note, oldNote) {
this.showExtensions = false;
this.showMenu = false;
+ this.noteStatus = null;
this.loadTagsString();
let onReady = () => {
@@ -122,32 +149,13 @@ angular.module('app')
if(oldNote && oldNote != note) {
if(oldNote.hasChanges) {
- this.save()(oldNote, null);
+ this.saveNote(oldNote);
} else if(oldNote.dummy) {
this.remove()(oldNote);
}
}
}
- // Observe editor changes to see if the current note should update its editor
-
- modelManager.addItemSyncObserver("component-manager", "SN|Component", (allItems, validItems, deletedItems, source) => {
- if(!this.note) { return; }
-
- var editors = allItems.filter(function(item) {
- return item.isEditor();
- });
-
- // If no editors have changed
- if(editors.length == 0) {
- return;
- }
-
- // Look through editors again and find the most proper one
- var editor = this.editorForNote(this.note);
- this.selectedEditor = editor;
- });
-
this.editorForNote = function(note) {
let editors = componentManager.componentsForArea("editor-editor");
for(var editor of editors) {
@@ -207,7 +215,7 @@ angular.module('app')
}
// Lots of dirtying can happen above, so we'll sync
- syncManager.sync("editorMenuOnSelect");
+ syncManager.sync();
}.bind(this)
this.hasAvailableExtensions = function() {
@@ -235,10 +243,9 @@ angular.module('app')
var statusTimeout;
- this.saveNote = function($event) {
+ this.save = function(dontUpdateClientModified) {
var note = this.note;
note.dummy = false;
-
// Make sure the note exists. A safety measure, as toggling between tags triggers deletes for dummy notes.
// Race conditions have been fixed, but we'll keep this here just in case.
if(!modelManager.findItem(note.uuid)) {
@@ -246,29 +253,51 @@ angular.module('app')
return;
}
- this.save()(note, function(success){
+ this.saveNote(note, (success) => {
if(success) {
if(statusTimeout) $timeout.cancel(statusTimeout);
- statusTimeout = $timeout(function(){
+ statusTimeout = $timeout(() => {
this.showAllChangesSavedStatus();
- }.bind(this), 200)
+ }, 200)
} else {
if(statusTimeout) $timeout.cancel(statusTimeout);
- statusTimeout = $timeout(function(){
+ statusTimeout = $timeout(() => {
this.showErrorStatus();
- }.bind(this), 200)
+ }, 200)
}
- }.bind(this));
+ }, dontUpdateClientModified);
+ }
+
+ this.saveNote = function(note, callback, dontUpdateClientModified) {
+ // We don't want to update the client modified date if toggling lock for note.
+ note.setDirty(true, dontUpdateClientModified);
+
+ syncManager.sync().then((response) => {
+ if(response && response.error) {
+ if(!this.didShowErrorAlert) {
+ this.didShowErrorAlert = true;
+ alert("There was an error saving your note. Please try again.");
+ }
+ $timeout(() => {
+ callback && callback(false);
+ })
+ } else {
+ note.hasChanges = false;
+ $timeout(() => {
+ callback && callback(true);
+ });
+ }
+ })
}
this.saveTitle = function($event) {
$event.target.blur();
- this.saveNote($event);
+ this.save($event);
this.focusEditor();
}
var saveTimeout;
- this.changesMade = function(bypassDebouncer = false) {
+ this.changesMade = function({bypassDebouncer, dontUpdateClientModified} = {}) {
this.note.dummy = false;
/* In the case of keystrokes, saving should go through a debouncer to avoid frequent calls.
@@ -283,10 +312,10 @@ angular.module('app')
if(saveTimeout) $timeout.cancel(saveTimeout);
if(statusTimeout) $timeout.cancel(statusTimeout);
- saveTimeout = $timeout(function(){
+ saveTimeout = $timeout(() => {
this.showSavingStatus();
- this.saveNote();
- }.bind(this), delay)
+ this.save(dontUpdateClientModified);
+ }, delay)
}
this.showSavingStatus = function() {
@@ -317,7 +346,8 @@ angular.module('app')
}
this.contentChanged = function() {
- this.changesMade();
+ // content changes should bypass manual debouncer as we use the built in ng-model-options debouncer
+ this.changesMade({bypassDebouncer: true});
}
this.nameChanged = function() {
@@ -358,12 +388,13 @@ angular.module('app')
this.toggleLockNote = function() {
this.note.setAppDataItem("locked", !this.note.locked);
- this.changesMade();
+ var dontUpdateClientModified = true;
+ this.changesMade({dontUpdateClientModified});
}
this.toggleArchiveNote = function() {
this.note.setAppDataItem("archived", !this.note.archived);
- this.changesMade(true);
+ this.changesMade({bypassDebouncer: true});
$rootScope.$broadcast("noteArchived");
}
@@ -518,7 +549,7 @@ angular.module('app')
// We also check if the selectedEditor is active. If it's inactive, we want to treat it as an external reference wishing to deactivate this editor (i.e componentView)
if(this.selectedEditor && this.selectedEditor == component && component.active == false) {
this.selectedEditor = null;
- }
+ }
else if(this.selectedEditor) {
if(this.selectedEditor.active) {
if(component.isExplicitlyEnabledForItem(this.note)) {
@@ -568,7 +599,7 @@ angular.module('app')
// Currently extensions are not notified of association until a full server sync completes.
// We need a better system for this, but for now, we'll manually notify observers
- modelManager.notifySyncObserversOfModels([this.note], ModelManager.MappingSourceLocalSaved);
+ modelManager.notifySyncObserversOfModels([tag], SFModelManager.MappingSourceLocalSaved);
}
}
@@ -578,7 +609,7 @@ angular.module('app')
// Currently extensions are not notified of association until a full server sync completes.
// We need a better system for this, but for now, we'll manually notify observers
- modelManager.notifySyncObserversOfModels([this.note], ModelManager.MappingSourceLocalSaved);
+ modelManager.notifySyncObserversOfModels([tag], SFModelManager.MappingSourceLocalSaved);
}
else if(action === "save-items" || action === "save-success" || action == "save-error") {
diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js
index 086c44f6d..6491e40dd 100644
--- a/app/assets/javascripts/app/controllers/footer.js
+++ b/app/assets/javascripts/app/controllers/footer.js
@@ -10,7 +10,7 @@ angular.module('app')
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
- scope.$on("sync:updated_token", function(){
+ scope.$on("sync:completed", function(){
ctrl.syncUpdated();
ctrl.findErrors();
ctrl.updateOfflineStatus();
@@ -25,7 +25,10 @@ angular.module('app')
.controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager,
syncManager, storageManager, passcodeManager, componentManager, singletonManager, nativeExtManager) {
- this.securityUpdateAvailable = authManager.checkForSecurityUpdate();
+ authManager.checkForSecurityUpdate().then((available) => {
+ this.securityUpdateAvailable = available;
+ })
+
$rootScope.$on("security-update-status-changed", () => {
this.securityUpdateAvailable = authManager.securityUpdateAvailable;
})
@@ -94,16 +97,16 @@ angular.module('app')
this.refreshData = function() {
this.isRefreshing = true;
- syncManager.sync((response) => {
+ syncManager.sync({force: true}).then((response) => {
$timeout(function(){
this.isRefreshing = false;
}.bind(this), 200)
if(response && response.error) {
- alert("There was an error syncing. Please try again. If all else fails, log out and log back in.");
+ alert("There was an error syncing. Please try again. If all else fails, try signing out and signing back in.");
} else {
this.syncUpdated();
}
- }, {force: true}, "refreshData");
+ });
}
this.syncUpdated = function() {
@@ -133,8 +136,7 @@ angular.module('app')
this.rooms = [];
modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => {
- var incomingRooms = allItems.filter((candidate) => {return candidate.area == "rooms"});
- this.rooms = _.uniq(this.rooms.concat(incomingRooms)).filter((candidate) => {return !candidate.deleted});
+ this.rooms = modelManager.components.filter((candidate) => {return candidate.area == "rooms" && !candidate.deleted});
});
componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => {
diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js
index 7bd300fd2..2ed1ee0df 100644
--- a/app/assets/javascripts/app/controllers/home.js
+++ b/app/assets/javascripts/app/controllers/home.js
@@ -26,7 +26,7 @@ angular.module('app')
/* Used to avoid circular dependencies where syncManager cannot be imported but rootScope can */
$rootScope.sync = function(source) {
- syncManager.sync("$rootScope.sync - " + source);
+ syncManager.sync();
}
$rootScope.lockApplication = function() {
@@ -66,28 +66,56 @@ angular.module('app')
dbManager.openDatabase(null, function() {
// new database, delete syncToken so that items can be refetched entirely from server
syncManager.clearSyncToken();
- syncManager.sync("openDatabase");
+ syncManager.sync();
})
}
function initiateSync() {
authManager.loadInitialData();
- syncManager.loadLocalItems(function(items) {
- $scope.allTag.didLoad = true;
- $scope.$apply();
- $rootScope.$broadcast("initial-data-loaded");
+ syncManager.setKeyRequestHandler(async () => {
+ let offline = authManager.offline();
+ let auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams();
+ let keys = offline ? passcodeManager.keys() : await authManager.keys();
+ return {
+ keys: keys,
+ offline: offline,
+ auth_params: auth_params
+ }
+ });
- syncManager.sync("initiateSync");
+ syncManager.addEventHandler((syncEvent, data) => {
+ $rootScope.$broadcast(syncEvent, data || {});
+
+ if(syncEvent == "sync-session-invalid") {
+ alert("Your session has expired. New changes will not be pulled in. Please sign out and sign back in to refresh your session.");
+ }
+ });
+
+ syncManager.loadLocalItems().then(() => {
+ $timeout(() => {
+ $scope.allTag.didLoad = true;
+ $rootScope.$broadcast("initial-data-loaded");
+ })
+
+ syncManager.sync();
// refresh every 30s
setInterval(function () {
- syncManager.sync("timer");
+ syncManager.sync();
}, 30000);
});
+
+ authManager.addEventHandler((event) => {
+ if(event == SFAuthManager.DidSignOutEvent) {
+ modelManager.handleSignout();
+ syncManager.handleSignout();
+ }
+ })
}
function loadAllTag() {
- var allTag = new Tag({all: true, title: "All"});
+ var allTag = new SNTag({content: {title: "All"}});
+ allTag.all = true;
allTag.needsLoad = true;
$scope.allTag = allTag;
$scope.tags = modelManager.tags;
@@ -95,9 +123,13 @@ angular.module('app')
}
function loadArchivedTag() {
- var archiveTag = new Tag({archiveTag: true, title: "Archived"});
+ var archiveTag = new SNSmartTag({content: {title: "Archived", predicate: ["archived", "=", true]}});
+ Object.defineProperty(archiveTag, "notes", {
+ get: () => {
+ return modelManager.notesMatchingPredicate(archiveTag.content.predicate);
+ }
+ });
$scope.archiveTag = archiveTag;
- $scope.archiveTag.notes = modelManager.notes;
}
/*
@@ -114,7 +146,6 @@ angular.module('app')
}
for(var tagToRemove of toRemove) {
- note.removeItemAsRelationship(tagToRemove);
tagToRemove.removeItemAsRelationship(note);
tagToRemove.setDirty(true);
}
@@ -128,25 +159,27 @@ angular.module('app')
}
for(var tag of tags) {
- modelManager.createRelationshipBetweenItems(note, tag);
+ tag.addItemAsRelationship(note);
+ tag.setDirty(true);
}
- note.setDirty(true);
- syncManager.sync("updateTagsForNote");
+ syncManager.sync();
}
/*
Tags Ctrl Callbacks
*/
-
- $scope.tagsWillMakeSelection = function(tag) {
-
- }
-
$scope.tagsSelectionMade = function(tag) {
+ // If a tag is selected twice, then the needed dummy note is removed.
+ // So we perform this check.
+ if($scope.selectedTag && tag && $scope.selectedTag.uuid == tag.uuid) {
+ return;
+ }
+
if($scope.selectedNote && $scope.selectedNote.dummy) {
modelManager.removeItemLocally($scope.selectedNote);
+ $scope.selectedNote = null;
}
$scope.selectedTag = tag;
@@ -162,8 +195,7 @@ angular.module('app')
return;
}
tag.setDirty(true);
- syncManager.sync(callback, null, "tagsSave");
- $rootScope.$broadcast("tag-changed");
+ syncManager.sync().then(callback);
modelManager.resortTag(tag);
}
@@ -174,11 +206,10 @@ angular.module('app')
$scope.removeTag = function(tag) {
if(confirm("Are you sure you want to delete this tag? Note: deleting a tag will not delete its notes.")) {
modelManager.setItemToBeDeleted(tag);
- // if no more notes, delete tag
- syncManager.sync(function(){
+ syncManager.sync().then(() => {
// force scope tags to update on sub directives
$scope.safeApply();
- }, null, "removeTag");
+ });
}
}
@@ -189,8 +220,9 @@ angular.module('app')
$scope.notesAddNew = function(note) {
modelManager.addItem(note);
- if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag) {
- modelManager.createRelationshipBetweenItems($scope.selectedTag, note);
+ if(!$scope.selectedTag.all && !$scope.selectedTag.isSmartTag()) {
+ $scope.selectedTag.addItemAsRelationship(note);
+ $scope.selectedTag.setDirty(true);
}
}
@@ -198,27 +230,6 @@ angular.module('app')
Shared Callbacks
*/
- $scope.saveNote = function(note, callback) {
- note.setDirty(true);
-
- syncManager.sync(function(response){
- if(response && response.error) {
- if(!$scope.didShowErrorAlert) {
- $scope.didShowErrorAlert = true;
- alert("There was an error saving your note. Please try again.");
- }
- if(callback) {
- callback(false);
- }
- } else {
- note.hasChanges = false;
- if(callback) {
- callback(true);
- }
- }
- }, null, "saveNote")
- }
-
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest')
@@ -234,7 +245,6 @@ angular.module('app')
}
$scope.deleteNote = function(note) {
-
modelManager.setItemToBeDeleted(note);
if(note == $scope.selectedNote) {
@@ -247,7 +257,7 @@ angular.module('app')
return;
}
- syncManager.sync(function(){
+ syncManager.sync().then(() => {
if(authManager.offline()) {
// when deleting items while ofline, we need to explictly tell angular to refresh UI
setTimeout(function () {
@@ -255,9 +265,11 @@ angular.module('app')
$scope.safeApply();
}, 50);
} else {
- $rootScope.notifyDelete();
+ $timeout(() => {
+ $rootScope.notifyDelete();
+ });
}
- }, null, "deleteNote");
+ });
}
@@ -268,25 +280,24 @@ angular.module('app')
return $location.search()[key];
}
- function autoSignInFromParams() {
+ async function autoSignInFromParams() {
var server = urlParam("server");
var email = urlParam("email");
var pw = urlParam("pw");
if(!authManager.offline()) {
// check if current account
- if(syncManager.serverURL === server && authManager.user.email === email) {
+ if(await syncManager.getServerURL() === server && authManager.user.email === email) {
// already signed in, return
return;
} else {
// sign out
- authManager.signOut();
- syncManager.destroyLocalData(function(){
+ authManager.signout(true).then(() => {
window.location.reload();
- })
+ });
}
} else {
- authManager.login(server, email, pw, false, false, {}, function(response){
+ authManager.login(server, email, pw, false, false, {}).then((response) => {
window.location.reload();
})
}
diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js
index d3e8ad1e7..e38eff9f6 100644
--- a/app/assets/javascripts/app/controllers/lockScreen.js
+++ b/app/assets/javascripts/app/controllers/lockScreen.js
@@ -8,12 +8,15 @@ class LockScreen {
};
}
- controller($scope, passcodeManager, authManager, syncManager) {
+ controller($scope, passcodeManager, authManager, syncManager, storageManager) {
'ngInject';
$scope.formData = {};
$scope.submitPasscodeForm = function() {
+ if(!$scope.formData.passcode || $scope.formData.passcode.length == 0) {
+ return;
+ }
passcodeManager.unlock($scope.formData.passcode, (success) => {
if(!success) {
alert("Invalid passcode. Please try again.");
@@ -33,8 +36,7 @@ class LockScreen {
return;
}
- authManager.signOut();
- syncManager.destroyLocalData(function(){
+ authManager.signout(true).then(() => {
window.location.reload();
})
}
diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js
index e6f51c142..1ab060248 100644
--- a/app/assets/javascripts/app/controllers/notes.js
+++ b/app/assets/javascripts/app/controllers/notes.js
@@ -14,7 +14,7 @@ angular.module('app')
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
- scope.$watch('ctrl.tag', function(tag, oldTag){
+ scope.$watch('ctrl.tag', (tag, oldTag) => {
if(tag) {
if(tag.needsLoad) {
scope.$watch('ctrl.tag.didLoad', function(didLoad){
@@ -133,12 +133,14 @@ angular.module('app')
base += " Title";
}
- if(this.showArchived && (!this.tag || !this.tag.archiveTag)) {
- base += " | + Archived"
- }
-
- if(this.hidePinned) {
- base += " | – Pinned"
+ if(!this.tag || !this.tag.isSmartTag()) {
+ // These rules don't apply for smart tags
+ if(this.showArchived) {
+ base += " | + Archived"
+ }
+ if(this.hidePinned) {
+ base += " | – Pinned"
+ }
}
return base;
@@ -172,11 +174,11 @@ angular.module('app')
}
this.setNotes = function(notes) {
- notes.forEach(function(note){
+ notes.forEach((note) => {
note.visible = true;
})
- var createNew = notes.length == 0;
+ var createNew = this.visibleNotes().length == 0;
this.selectFirstNote(createNew);
}
@@ -201,6 +203,7 @@ angular.module('app')
this.createNewNote();
return;
}
+
this.selectedNote = note;
note.conflict_of = null; // clear conflict
this.selectionMade()(note);
@@ -216,9 +219,12 @@ angular.module('app')
}
this.createNewNote = function() {
- var title = "New Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : "");
- this.newNote = modelManager.createItem({content_type: "Note", dummy: true, text: ""});
- this.newNote.title = title;
+ // The "Note X" counter is based off this.tag.notes.length, but sometimes, what you see in the list is only a subset.
+ // We can use this.visibleNotes().length, but that only accounts for non-paginated results, so first 15 or so.
+ var title = "Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : "");
+ let newNote = modelManager.createItem({content_type: "Note", content: {text: "", title: title}});
+ newNote.dummy = true;
+ this.newNote = newNote;
this.selectNote(this.newNote);
this.addNew()(this.newNote);
}
@@ -226,7 +232,16 @@ angular.module('app')
this.noteFilter = {text : ''};
this.filterNotes = function(note) {
- if((note.archived && !this.showArchived && !this.tag.archiveTag) || (note.pinned && this.hidePinned)) {
+ var canShowArchived = false, canShowPinned = true;
+ var isSmartTag = this.tag.isSmartTag();
+ if(isSmartTag) {
+ canShowArchived = this.tag.isReferencingArchivedNotes();
+ } else {
+ canShowArchived = this.showArchived;
+ canShowPinned = !this.hidePinned;
+ }
+
+ if((note.archived && !canShowArchived) || (note.pinned && !canShowPinned)) {
note.visible = false;
return note.visible;
}
@@ -241,10 +256,6 @@ angular.module('app')
note.visible = matchesTitle || matchesBody;
}
- if(this.tag.archiveTag) {
- note.visible = note.visible && note.archived;
- }
-
return note.visible;
}.bind(this)
diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js
index 21a430b45..2017b7679 100644
--- a/app/assets/javascripts/app/controllers/tags.js
+++ b/app/assets/javascripts/app/controllers/tags.js
@@ -5,7 +5,6 @@ angular.module('app')
scope: {
addNew: "&",
selectionMade: "&",
- willSelect: "&",
save: "&",
tags: "=",
allTag: "=",
@@ -66,13 +65,21 @@ angular.module('app')
return null;
}.bind(this), actionHandler: function(component, action, data){
if(action === "select-item") {
- var tag = modelManager.findItem(data.item.uuid);
- if(tag) {
+ if(data.item.content_type == "Tag") {
+ var tag = modelManager.findItem(data.item.uuid);
+ if(tag) {
+ this.selectTag(tag);
+ }
+ } else if(data.item.content_type == "SN|SmartTag") {
+ var tag = new SNSmartTag(data.item);
+ Object.defineProperty(tag, "notes", {
+ get: () => {
+ return modelManager.notesMatchingPredicate(tag.content.predicate);
+ }
+ });
this.selectTag(tag);
}
- }
-
- else if(action === "clear-selection") {
+ } else if(action === "clear-selection") {
this.selectTag(this.allTag);
}
}.bind(this)});
@@ -93,7 +100,6 @@ angular.module('app')
}
this.selectTag = function(tag) {
- this.willSelect()(tag);
this.selectedTag = tag;
tag.conflict_of = null; // clear conflict
this.selectionMade()(tag);
@@ -152,10 +158,11 @@ angular.module('app')
this.selectedDeleteTag = function(tag) {
this.removeTag()(tag);
+ this.selectTag(this.allTag);
}
this.noteCount = function(tag) {
- var validNotes = Note.filterDummyNotes(tag.notes).filter(function(note){
+ var validNotes = SNNote.filterDummyNotes(tag.notes).filter(function(note){
return !note.archived;
});
return validNotes.length;
diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js
index 7142dc0fd..776b94658 100644
--- a/app/assets/javascripts/app/directives/views/accountMenu.js
+++ b/app/assets/javascripts/app/directives/views/accountMenu.js
@@ -9,14 +9,23 @@ class AccountMenu {
};
}
- controller($scope, $rootScope, authManager, modelManager, syncManager, dbManager, passcodeManager,
- $timeout, storageManager, $compile, archiveManager) {
+ controller($scope, $rootScope, authManager, modelManager, syncManager, storageManager, dbManager, passcodeManager,
+ $timeout, $compile, archiveManager) {
'ngInject';
- $scope.formData = {mergeLocal: true, url: syncManager.serverURL, ephemeral: false};
+ $scope.formData = {mergeLocal: true, ephemeral: false};
$scope.user = authManager.user;
- $scope.server = syncManager.serverURL;
- $scope.securityUpdateAvailable = authManager.checkForSecurityUpdate();
+
+ syncManager.getServerURL().then((url) => {
+ $timeout(() => {
+ $scope.server = url;
+ $scope.formData.url = url;
+ })
+ })
+
+ authManager.checkForSecurityUpdate().then((available) => {
+ $scope.securityUpdateAvailable = available;
+ })
$scope.close = function() {
$timeout(() => {
@@ -59,40 +68,35 @@ class AccountMenu {
$scope.formData.status = "Generating Login Keys...";
$timeout(function(){
authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password,
- $scope.formData.ephemeral, $scope.formData.strictSignin, extraParams,
- (response) => {
- if(!response || response.error) {
+ $scope.formData.ephemeral, $scope.formData.strictSignin, extraParams).then((response) => {
+ $timeout(() => {
+ if(!response || response.error) {
- syncManager.unlockSyncing();
+ syncManager.unlockSyncing();
- $scope.formData.status = null;
- var error = response ? response.error : {message: "An unknown error occured."}
+ $scope.formData.status = null;
+ var error = response ? response.error : {message: "An unknown error occured."}
- // MFA Error
- if(error.tag == "mfa-required" || error.tag == "mfa-invalid") {
- $timeout(() => {
+ // MFA Error
+ if(error.tag == "mfa-required" || error.tag == "mfa-invalid") {
$scope.formData.showLogin = false;
$scope.formData.mfa = error;
- })
- }
-
- // General Error
- else {
- $timeout(() => {
+ }
+ // General Error
+ else {
$scope.formData.showLogin = true;
$scope.formData.mfa = null;
if(error.message) { alert(error.message); }
- })
+ }
}
- }
-
- // Success
- else {
- $scope.onAuthSuccess(() => {
- syncManager.unlockSyncing();
- syncManager.sync("onLogin");
- });
- }
+ // Success
+ else {
+ $scope.onAuthSuccess(() => {
+ syncManager.unlockSyncing();
+ syncManager.sync();
+ });
+ }
+ })
});
})
}
@@ -108,16 +112,18 @@ class AccountMenu {
$scope.formData.status = "Generating Account Keys...";
$timeout(function(){
- authManager.register($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral ,function(response){
- if(!response || response.error) {
- $scope.formData.status = null;
- var error = response ? response.error : {message: "An unknown error occured."}
- alert(error.message);
- } else {
- $scope.onAuthSuccess(() => {
- syncManager.sync("onRegister");
- });
- }
+ authManager.register($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral).then((response) => {
+ $timeout(() => {
+ if(!response || response.error) {
+ $scope.formData.status = null;
+ var error = response ? response.error : {message: "An unknown error occured."}
+ alert(error.message);
+ } else {
+ $scope.onAuthSuccess(() => {
+ syncManager.sync();
+ });
+ }
+ })
});
})
}
@@ -145,8 +151,8 @@ class AccountMenu {
$scope.clearDatabaseAndRewriteAllItems(true, block);
}
else {
- modelManager.resetLocalMemory();
- storageManager.clearAllModels(function(){
+ modelManager.removeAllItemsFromMemory();
+ storageManager.clearAllModels().then(() => {
block();
})
}
@@ -163,10 +169,10 @@ class AccountMenu {
// clearAllModels will remove data from backing store, but not from working memory
// See: https://github.com/standardnotes/desktop/issues/131
$scope.clearDatabaseAndRewriteAllItems = function(alternateUuids, callback) {
- storageManager.clearAllModels(() => {
- syncManager.markAllItemsDirtyAndSaveOffline(function(){
+ storageManager.clearAllModels().then(() => {
+ syncManager.markAllItemsDirtyAndSaveOffline(alternateUuids).then(() => {
callback && callback();
- }, alternateUuids)
+ })
});
}
@@ -175,8 +181,7 @@ class AccountMenu {
return;
}
- authManager.signOut();
- syncManager.destroyLocalData(function(){
+ authManager.signout(true).then(() => {
window.location.reload();
})
}
@@ -201,7 +206,8 @@ class AccountMenu {
// Update UI before showing alert
setTimeout(function () {
- if(!response) {
+ // Response can be null if syncing offline
+ if(response && response.error) {
alert("There was an error importing your data. Please try again.");
} else {
if(errorCount > 0) {
@@ -243,24 +249,19 @@ class AccountMenu {
}
$scope.importJSONData = function(data, password, callback) {
- var onDataReady = function(errorCount) {
- var items = modelManager.mapResponseItemsToLocalModels(data.items, ModelManager.MappingSourceFileImport);
- items.forEach(function(item){
- item.setDirty(true, true);
- item.deleted = false;
- item.markAllReferencesDirty(true);
-
+ var onDataReady = (errorCount) => {
+ var items = modelManager.importItems(data.items);
+ for(var item of items) {
// We don't want to activate any components during import process in case of exceptions
// breaking up the import proccess
- if(item.content_type == "SN|Component") {
- item.active = false;
- }
- })
+ if(item.content_type == "SN|Component") { item.active = false; }
+ }
- syncManager.sync((response) => {
+ syncManager.sync({additionalFields: ["created_at", "updated_at"]}).then((response) => {
+ // Response can be null if syncing offline
callback(response, errorCount);
- }, {additionalFields: ["created_at", "updated_at"]}, "importJSONData");
- }.bind(this)
+ });
+ }
if(data.auth_params) {
SFJS.crypto.computeEncryptionKeysForUser(password, data.auth_params).then((keys) => {
diff --git a/app/assets/javascripts/app/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js
index 846f8d950..62cfece78 100644
--- a/app/assets/javascripts/app/directives/views/actionsMenu.js
+++ b/app/assets/javascripts/app/directives/views/actionsMenu.js
@@ -11,8 +11,6 @@ class ActionsMenu {
controller($scope, modelManager, actionsManager) {
'ngInject';
- $scope.renderData = {};
-
$scope.extensions = actionsManager.extensions.sort((a, b) => {return a.name.toLowerCase() > b.name.toLowerCase()});
for(let ext of $scope.extensions) {
@@ -51,11 +49,7 @@ class ActionsMenu {
switch (action.verb) {
case "render": {
var item = response.item;
- if(item.content_type == "Note") {
- $scope.renderData.title = item.title;
- $scope.renderData.text = item.text;
- $scope.renderData.showRenderModal = true;
- }
+ actionsManager.presentRevisionPreviewModal(item.uuid, item.content);
}
}
}
@@ -77,8 +71,6 @@ class ActionsMenu {
}
})
}
-
-
}
}
diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js
index 61b8d98a2..d2a0916f1 100644
--- a/app/assets/javascripts/app/directives/views/componentView.js
+++ b/app/assets/javascripts/app/directives/views/componentView.js
@@ -18,19 +18,30 @@ class ComponentView {
$scope.el = el;
$scope.identifier = "component-view-" + Math.random();
+ $scope.componentValid = true;
// console.log("Registering handler", $scope.identifier, $scope.component.name);
this.componentManager.registerHandler({identifier: $scope.identifier, areas: [$scope.component.area], activationHandler: (component) => {
+ // activationHandlers may be called multiple times, design below to be idempotent
if(component.active) {
- this.timeout(() => {
- var iframe = this.componentManager.iframeForComponent(component);
- if(iframe) {
- iframe.onload = function() {
- this.componentManager.registerComponentWindow(component, iframe.contentWindow);
- }.bind(this);
- }
- });
+ $scope.loading = true;
+ let iframe = this.componentManager.iframeForComponent(component);
+ if(iframe) {
+ // begin loading error handler. If onload isn't called in x seconds, display an error
+ if($scope.loadTimeout) { this.timeout.cancel($scope.loadTimeout);}
+ $scope.loadTimeout = this.timeout(() => {
+ if($scope.loading) {
+ $scope.issueLoading = true;
+ }
+ }, 3500)
+ iframe.onload = function(event) {
+ this.timeout.cancel($scope.loadTimeout);
+ $scope.loading = false;
+ $scope.issueLoading = false;
+ this.componentManager.registerComponentWindow(component, iframe.contentWindow);
+ }.bind(this);
+ }
}
},
actionHandler: (component, action, data) => {
@@ -97,9 +108,9 @@ class ComponentView {
offlineRestricted = component.offlineOnly && !isDesktopApplication();
urlError =
- (!isDesktopApplication() && (!component.url && !component.hosted_url))
+ (!isDesktopApplication() && (!component.hasValidHostedUrl()))
||
- (isDesktopApplication() && (!component.local_url && !component.url && !component.hosted_url))
+ (isDesktopApplication() && (!component.local_url && !component.hasValidHostedUrl()))
expired = component.valid_until && component.valid_until <= new Date();
@@ -112,7 +123,8 @@ class ComponentView {
if($scope.componentValid !== previouslyValid) {
if($scope.componentValid) {
- componentManager.activateComponent(component, true);
+ // We want to reload here, rather than `activateComponent`, because the component will already have attempted to been activated.
+ componentManager.reloadComponent(component, true);
}
}
@@ -129,7 +141,7 @@ class ComponentView {
$scope.getUrl = function() {
var url = componentManager.urlForComponent($scope.component);
- $scope.component.runningLocally = (url !== $scope.component.url) && url !== ($scope.component.hosted_url);
+ $scope.component.runningLocally = (url == $scope.component.local_url);
return url;
}
diff --git a/app/assets/javascripts/app/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js
index 734a829cc..02c5b505f 100644
--- a/app/assets/javascripts/app/directives/views/editorMenu.js
+++ b/app/assets/javascripts/app/directives/views/editorMenu.js
@@ -53,7 +53,7 @@ class EditorMenu {
component.setAppDataItem("defaultEditor", true);
component.setDirty(true);
- syncManager.sync("makeEditorDefault");
+ syncManager.sync();
$scope.defaultEditor = component;
}
@@ -61,7 +61,7 @@ class EditorMenu {
$scope.removeEditorDefault = function(component) {
component.setAppDataItem("defaultEditor", false);
component.setDirty(true);
- syncManager.sync("removeEditorDefault");
+ syncManager.sync();
$scope.defaultEditor = null;
}
diff --git a/app/assets/javascripts/app/directives/views/menuRow.js b/app/assets/javascripts/app/directives/views/menuRow.js
index 468212fd2..cb6c791e7 100644
--- a/app/assets/javascripts/app/directives/views/menuRow.js
+++ b/app/assets/javascripts/app/directives/views/menuRow.js
@@ -7,7 +7,7 @@ class MenuRow {
this.scope = {
circle: "=",
label: "=",
- subtite: "=",
+ subtitle: "=",
hasButton: "=",
buttonText: "=",
buttonClass: "=",
diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js
index 3b5024046..09dd413f1 100644
--- a/app/assets/javascripts/app/directives/views/passwordWizard.js
+++ b/app/assets/javascripts/app/directives/views/passwordWizard.js
@@ -147,7 +147,7 @@ class PasswordWizard {
}
}
- $scope.validateCurrentPassword = function(callback) {
+ $scope.validateCurrentPassword = async function(callback) {
let currentPassword = $scope.formData.currentPassword;
let newPass = $scope.securityUpdate ? currentPassword : $scope.formData.newPassword;
@@ -173,10 +173,10 @@ class PasswordWizard {
}
// Ensure value for current password matches what's saved
- let authParams = authManager.getAuthParams();
+ let authParams = await authManager.getAuthParams();
let password = $scope.formData.currentPassword;
- SFJS.crypto.computeEncryptionKeysForUser(password, authParams).then((keys) => {
- let success = keys.mk === authManager.keys().mk;
+ SFJS.crypto.computeEncryptionKeysForUser(password, authParams).then(async (keys) => {
+ let success = keys.mk === (await authManager.keys()).mk;
if(success) {
this.currentServerPw = keys.pw;
} else {
@@ -188,8 +188,8 @@ class PasswordWizard {
$scope.resyncData = function(callback) {
modelManager.setAllItemsDirty();
- syncManager.sync((response) => {
- if(response.error) {
+ syncManager.sync().then((response) => {
+ if(!response || response.error) {
alert(FailedSyncMessage)
$timeout(() => callback(false));
} else {
@@ -198,27 +198,25 @@ class PasswordWizard {
});
}
- $scope.processPasswordChange = function(callback) {
+ $scope.processPasswordChange = async function(callback) {
let newUserPassword = $scope.securityUpdate ? $scope.formData.currentPassword : $scope.formData.newPassword;
let currentServerPw = this.currentServerPw;
- SFJS.crypto.generateInitialKeysAndAuthParamsForUser(authManager.user.email, newUserPassword).then((results) => {
- let newKeys = results.keys;
- let newAuthParams = results.authParams;
+ let results = await SFJS.crypto.generateInitialKeysAndAuthParamsForUser(authManager.user.email, newUserPassword);
+ let newKeys = results.keys;
+ let newAuthParams = results.authParams;
- // perform a sync beforehand to pull in any last minutes changes before we change the encryption key (and thus cant decrypt new changes)
- syncManager.sync((response) => {
- authManager.changePassword(currentServerPw, newKeys, newAuthParams, (response) => {
- if(response.error) {
- alert(response.error.message ? response.error.message : "There was an error changing your password. Please try again.");
- $timeout(() => callback(false));
- } else {
- $timeout(() => callback(true));
- }
- })
- }, null, "submitPasswordChange")
- });
+ // perform a sync beforehand to pull in any last minutes changes before we change the encryption key (and thus cant decrypt new changes)
+ let syncResponse = await syncManager.sync();
+ authManager.changePassword(await syncManager.getServerURL(), authManager.user.email, currentServerPw, newKeys, newAuthParams).then((response) => {
+ if(response.error) {
+ alert(response.error.message ? response.error.message : "There was an error changing your password. Please try again.");
+ $timeout(() => callback(false));
+ } else {
+ $timeout(() => callback(true));
+ }
+ })
}
}
diff --git a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js
new file mode 100644
index 000000000..d5d6313d3
--- /dev/null
+++ b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js
@@ -0,0 +1,48 @@
+class RevisionPreviewModal {
+
+ constructor() {
+ this.restrict = "E";
+ this.templateUrl = "directives/revision-preview-modal.html";
+ this.scope = {
+ uuid: "=",
+ content: "="
+ };
+ }
+
+ link($scope, el, attrs) {
+
+ $scope.dismiss = function() {
+ el.remove();
+ }
+ }
+
+ controller($scope, modelManager, syncManager) {
+ 'ngInject';
+
+ $scope.restore = function(asCopy) {
+ if(!asCopy && !confirm("Are you sure you want to replace the current note's contents with what you see in this preview?")) {
+ return;
+ }
+
+ var item;
+ if(asCopy) {
+ var contentCopy = Object.assign({}, $scope.content);
+ if(contentCopy.title) { contentCopy.title += " (copy)"; }
+ item = modelManager.createItem({content_type: "Note", content: contentCopy});
+ modelManager.addItem(item);
+ } else {
+ var uuid = $scope.uuid;
+ item = modelManager.findItem(uuid);
+ item.content = Object.assign({}, $scope.content);
+ modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved);
+ }
+
+ item.setDirty(true);
+ syncManager.sync();
+
+ $scope.dismiss();
+ }
+ }
+}
+
+angular.module('app').directive('revisionPreviewModal', () => new RevisionPreviewModal);
diff --git a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js
new file mode 100644
index 000000000..4522b9abb
--- /dev/null
+++ b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js
@@ -0,0 +1,81 @@
+class SessionHistoryMenu {
+
+ constructor() {
+ this.restrict = "E";
+ this.templateUrl = "directives/session-history-menu.html";
+ this.scope = {
+ item: "="
+ };
+ }
+
+ controller($scope, modelManager, sessionHistory, actionsManager, $timeout) {
+ 'ngInject';
+
+ $scope.diskEnabled = sessionHistory.diskEnabled;
+ $scope.autoOptimize = sessionHistory.autoOptimize;
+
+ $scope.reloadHistory = function() {
+ $scope.history = sessionHistory.historyForItem($scope.item);
+ }
+ $scope.reloadHistory();
+
+ $scope.openRevision = function(revision) {
+ actionsManager.presentRevisionPreviewModal(revision.item.uuid, revision.item.content);
+ }
+
+ $scope.classForRevision = function(revision) {
+ var vector = revision.operationVector();
+ if(vector == 0) {
+ return "default";
+ } else if(vector == 1) {
+ return "success";
+ } else if(vector == -1) {
+ return "danger";
+ }
+ }
+
+ $scope.clearItemHistory = function() {
+ if(!confirm("Are you sure you want to delete the local session history for this note?")) {
+ return;
+ }
+
+ sessionHistory.clearItemHistory($scope.item).then(() => {
+ $timeout(() => {
+ $scope.reloadHistory();
+ })
+ });
+ }
+
+ $scope.clearAllHistory = function() {
+ if(!confirm("Are you sure you want to delete the local session history for all notes?")) {
+ return;
+ }
+
+ sessionHistory.clearAllHistory().then(() => {
+ $timeout(() => {
+ $scope.reloadHistory();
+ })
+ });
+ }
+
+ $scope.toggleDiskSaving = function() {
+ sessionHistory.toggleDiskSaving().then(() => {
+ $timeout(() => {
+ $scope.diskEnabled = sessionHistory.diskEnabled;
+ })
+ });
+ }
+
+ $scope.toggleAutoOptimize = function() {
+ sessionHistory.toggleAutoOptimize().then(() => {
+ $timeout(() => {
+ $scope.autoOptimize = sessionHistory.autoOptimize;
+ })
+ });
+ }
+
+ }
+
+}
+
+angular.module('app').directive('sessionHistoryMenu', () => new SessionHistoryMenu);
diff --git a/app/assets/javascripts/app/filters/appDate.js b/app/assets/javascripts/app/filters/appDate.js
index 9a84a796e..f41db3381 100644
--- a/app/assets/javascripts/app/filters/appDate.js
+++ b/app/assets/javascripts/app/filters/appDate.js
@@ -1,3 +1,6 @@
+// reuse
+var locale, formatter;
+
angular.module('app')
.filter('appDate', function ($filter) {
return function (input) {
@@ -6,6 +9,20 @@ angular.module('app')
})
.filter('appDateTime', function ($filter) {
return function (input) {
+ if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
+ if (!formatter) {
+ locale = (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language;
+ formatter = new Intl.DateTimeFormat(locale, {
+ year: 'numeric',
+ month: 'numeric',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ }
+ return formatter.format(input);
+ } else {
return input ? $filter('date')(new Date(input), 'MM/dd/yyyy h:mm a') : '';
- };
+ }
+ }
});
diff --git a/app/assets/javascripts/app/filters/sortBy.js b/app/assets/javascripts/app/filters/sortBy.js
index 2f19d3769..e182b19a5 100644
--- a/app/assets/javascripts/app/filters/sortBy.js
+++ b/app/assets/javascripts/app/filters/sortBy.js
@@ -35,8 +35,9 @@ angular.module('app')
}
items = items || [];
- return items.sort(function(a, b){
+ var result = items.sort(function(a, b){
return sortValueFn(a, b);
})
+ return result;
};
});
diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js
deleted file mode 100644
index 888c477de..000000000
--- a/app/assets/javascripts/app/models/api/item.js
+++ /dev/null
@@ -1,317 +0,0 @@
-let AppDomain = "org.standardnotes.sn";
-var dateFormatter;
-
-class Item {
-
- constructor(json_obj = {}) {
- this.appData = {};
- this.updateFromJSON(json_obj);
- this.observers = [];
-
- if(!this.uuid) {
- this.uuid = SFJS.crypto.generateUUIDSync();
- }
- }
-
- static sortItemsByDate(items) {
- items.sort(function(a,b){
- return new Date(b.created_at) - new Date(a.created_at);
- });
- }
-
- get contentObject() {
- if(!this.content) {
- return {};
- }
-
- if(this.content !== null && typeof this.content === 'object') {
- // this is the case when mapping localStorage content, in which case the content is already parsed
- return this.content;
- }
-
- try {
- return JSON.parse(this.content);
- } catch (e) {
- console.log("Error parsing json", e, this);
- return {};
- }
- }
-
- updateFromJSON(json) {
- _.merge(this, json);
-
- if(this.created_at) {
- this.created_at = new Date(this.created_at);
- this.updated_at = new Date(this.updated_at);
- } else {
- this.created_at = new Date();
- this.updated_at = new Date();
- }
-
- // Allows the getter to be re-invoked
- this._client_updated_at = null;
-
- if(json.content) {
- this.mapContentToLocalProperties(this.contentObject);
- } else if(json.deleted == true) {
- this.handleDeletedContent();
- }
- }
-
- refreshContentObject() {
- // Before an item can be duplicated or cloned, we must update this.content (if it is an object) with the object's
- // current physical properties, because updateFromJSON, which is what all new items must go through,
- // will call this.mapContentToLocalProperties(this.contentObject), which may have stale values if not explicitly updated.
-
- this.content = this.structureParams();
- }
-
- /* Allows the item to handle the case where the item is deleted and the content is null */
- handleDeletedContent() {
- // Subclasses can override
- }
-
- setDirty(dirty, dontUpdateClientDate) {
- this.dirty = dirty;
-
- // Allows the syncManager to check if an item has been marked dirty after a sync has been started
- // This prevents it from clearing it as a dirty item after sync completion, if someone else has marked it dirty
- // again after an ongoing sync.
- if(!this.dirtyCount) { this.dirtyCount = 0; }
- if(dirty) {
- this.dirtyCount++;
- } else {
- this.dirtyCount = 0;
- }
-
- if(dirty && !dontUpdateClientDate) {
- // Set the client modified date to now if marking the item as dirty
- this.client_updated_at = new Date();
- } else if(!this.hasRawClientUpdatedAtValue()) {
- // copy updated_at
- this.client_updated_at = new Date(this.updated_at);
- }
-
- if(dirty) {
- this.notifyObserversOfChange();
- }
- }
-
- markAllReferencesDirty(dontUpdateClientDate) {
- this.allReferencedObjects().forEach(function(reference){
- reference.setDirty(true, dontUpdateClientDate);
- })
- }
-
- addObserver(observer, callback) {
- if(!_.find(this.observers, observer)) {
- this.observers.push({observer: observer, callback: callback});
- }
- }
-
- removeObserver(observer) {
- _.remove(this.observers, {observer: observer})
- }
-
- notifyObserversOfChange() {
- for(var observer of this.observers) {
- observer.callback(this);
- }
- }
-
- mapContentToLocalProperties(contentObj) {
- if(contentObj.appData) {
- this.appData = contentObj.appData;
- }
- if(!this.appData) { this.appData = {}; }
- }
-
- createContentJSONFromProperties() {
- return this.structureParams();
- }
-
- referenceParams() {
- // must override
- }
-
- structureParams() {
- return {
- references: this.referenceParams(),
- appData: this.appData
- }
- }
-
- addItemAsRelationship(item) {
- // must override
- }
-
- removeItemAsRelationship(item) {
- // must override
- }
-
- isBeingRemovedLocally() {
-
- }
-
- removeAndDirtyAllRelationships() {
- // must override
- this.setDirty(true);
- }
-
- removeReferencesNotPresentIn(references) {
-
- }
-
- mergeMetadataFromItem(item) {
- _.merge(this, _.omit(item, ["content"]));
- }
-
- informReferencesOfUUIDChange(oldUUID, newUUID) {
- // optional override
- }
-
- potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID) {
- // optional override
- }
-
- allReferencedObjects() {
- // must override
- return [];
- }
-
- doNotEncrypt() {
- return false;
- }
-
- /*
- App Data
- */
-
- setDomainDataItem(key, value, domain) {
- var data = this.appData[domain];
- if(!data) {
- data = {}
- }
- data[key] = value;
- this.appData[domain] = data;
- }
-
- getDomainDataItem(key, domain) {
- var data = this.appData[domain];
- if(data) {
- return data[key];
- } else {
- return null;
- }
- }
-
- setAppDataItem(key, value) {
- this.setDomainDataItem(key, value, AppDomain);
- }
-
- getAppDataItem(key) {
- return this.getDomainDataItem(key, AppDomain);
- }
-
- get pinned() {
- return this.getAppDataItem("pinned");
- }
-
- get archived() {
- return this.getAppDataItem("archived");
- }
-
- get locked() {
- return this.getAppDataItem("locked");
- }
-
- hasRawClientUpdatedAtValue() {
- return this.getAppDataItem("client_updated_at") != null;
- }
-
- get client_updated_at() {
- if(!this._client_updated_at) {
- var saved = this.getAppDataItem("client_updated_at");
- if(saved) {
- this._client_updated_at = new Date(saved);
- } else {
- this._client_updated_at = new Date(this.updated_at);
- }
- }
- return this._client_updated_at;
- }
-
- set client_updated_at(date) {
- this._client_updated_at = date;
-
- this.setAppDataItem("client_updated_at", date);
- }
-
- /*
- During sync conflicts, when determing whether to create a duplicate for an item, we can omit keys that have no
- meaningful weight and can be ignored. For example, if one component has active = true and another component has active = false,
- it would be silly to duplicate them, so instead we ignore this.
- */
- keysToIgnoreWhenCheckingContentEquality() {
- return [];
- }
-
- // Same as above, but keys inside appData[AppDomain]
- appDataKeysToIgnoreWhenCheckingContentEquality() {
- return ["client_updated_at"];
- }
-
- isItemContentEqualWith(otherItem) {
- let omit = (obj, keys) => {
- for(var key of keys) {
- delete obj[key];
- }
- return obj;
- }
-
- var left = this.structureParams();
- left.appData[AppDomain] = omit(left.appData[AppDomain], this.appDataKeysToIgnoreWhenCheckingContentEquality());
- left = omit(left, this.keysToIgnoreWhenCheckingContentEquality());
-
- var right = otherItem.structureParams();
- right.appData[AppDomain] = omit(right.appData[AppDomain], otherItem.appDataKeysToIgnoreWhenCheckingContentEquality());
- right = omit(right, otherItem.keysToIgnoreWhenCheckingContentEquality());
-
- return JSON.stringify(left) === JSON.stringify(right)
- }
-
- /*
- Dates
- */
-
- createdAtString() {
- return this.dateToLocalizedString(this.created_at);
- }
-
- updatedAtString() {
- return this.dateToLocalizedString(this.client_updated_at);
- }
-
- dateToLocalizedString(date) {
- if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
- if (!dateFormatter) {
- var locale = (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language;
- dateFormatter = new Intl.DateTimeFormat(locale, {
- year: 'numeric',
- month: 'short',
- day: '2-digit',
- weekday: 'long',
- hour: '2-digit',
- minute: '2-digit',
- });
- }
- return dateFormatter.format(date);
- } else {
- // IE < 11, Safari <= 9.0.
- // In English, this generates the string most similar to
- // the toLocaleDateString() result above.
- return date.toDateString() + ' ' + date.toLocaleTimeString();
- }
- }
-
-}
diff --git a/app/assets/javascripts/app/models/api/mfa.js b/app/assets/javascripts/app/models/api/mfa.js
deleted file mode 100644
index d3792654d..000000000
--- a/app/assets/javascripts/app/models/api/mfa.js
+++ /dev/null
@@ -1,28 +0,0 @@
-class Mfa extends Item {
-
- constructor(json_obj) {
- super(json_obj);
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- this.serverContent = content;
- }
-
- structureParams() {
- return _.merge(this.serverContent, super.structureParams());
- }
-
- toJSON() {
- return {uuid: this.uuid}
- }
-
- get content_type() {
- return "SF|MFA";
- }
-
- doNotEncrypt() {
- return true;
- }
-
-}
diff --git a/app/assets/javascripts/app/models/api/serverExtension.js b/app/assets/javascripts/app/models/api/serverExtension.js
deleted file mode 100644
index 8149c5a95..000000000
--- a/app/assets/javascripts/app/models/api/serverExtension.js
+++ /dev/null
@@ -1,38 +0,0 @@
-class ServerExtension extends Item {
-
- constructor(json_obj) {
- super(json_obj);
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- this.url = content.url;
- }
-
- structureParams() {
- // There was a bug with the way Base64 content was parsed in previous releases related to this item.
- // The bug would not parse the JSON behind the base64 string and thus saved data in an invalid format.
- // This is the line: https://github.com/standardnotes/web/commit/1ad0bf73d8e995b7588854f1b1e4e4a02303a42f#diff-15753bac364782a3a5876032bcdbf99aR76
- // We'll remedy this for affected users by trying to parse the content string
- if(typeof this.content !== 'object') {
- try {
- this.content = JSON.parse(this.content);
- } catch (e) {}
- }
- var params = this.content || {};
- _.merge(params, super.structureParams());
- return params;
- }
-
- toJSON() {
- return {uuid: this.uuid}
- }
-
- get content_type() {
- return "SF|Extension";
- }
-
- doNotEncrypt() {
- return true;
- }
-}
diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js
deleted file mode 100644
index 57e609ca8..000000000
--- a/app/assets/javascripts/app/models/app/component.js
+++ /dev/null
@@ -1,145 +0,0 @@
-class Component extends Item {
-
- constructor(json_obj) {
- // If making a copy of an existing component (usually during sign in if you have a component active in the session),
- // which may have window set, you may get a cross-origin exception since you'll be trying to copy the window. So we clear it here.
- json_obj.window = null;
-
- super(json_obj);
-
- if(!this.componentData) {
- this.componentData = {};
- }
-
- if(!this.disassociatedItemIds) {
- this.disassociatedItemIds = [];
- }
-
- if(!this.associatedItemIds) {
- this.associatedItemIds = [];
- }
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- /* Legacy */
- this.url = content.url || content.hosted_url;
- /* New */
- this.local_url = content.local_url;
- this.hosted_url = content.hosted_url || content.url;
- this.offlineOnly = content.offlineOnly;
-
- if(content.valid_until) {
- this.valid_until = new Date(content.valid_until);
- }
-
- this.name = content.name;
- this.autoupdateDisabled = content.autoupdateDisabled;
-
- this.package_info = content.package_info;
-
- // the location in the view this component is located in. Valid values are currently tags-list, note-tags, and editor-stack`
- this.area = content.area;
-
- this.permissions = content.permissions;
- if(!this.permissions) {
- this.permissions = [];
- }
-
- this.active = content.active;
-
- // custom data that a component can store in itself
- this.componentData = content.componentData || {};
-
- // items that have requested a component to be disabled in its context
- this.disassociatedItemIds = content.disassociatedItemIds || [];
-
- // items that have requested a component to be enabled in its context
- this.associatedItemIds = content.associatedItemIds || [];
- }
-
- handleDeletedContent() {
- super.handleDeletedContent();
-
- this.active = false;
- }
-
- structureParams() {
- var params = {
- url: this.url,
- hosted_url: this.hosted_url,
- local_url: this.local_url,
- valid_until: this.valid_until,
- offlineOnly: this.offlineOnly,
- name: this.name,
- area: this.area,
- package_info: this.package_info,
- permissions: this.permissions,
- active: this.active,
- autoupdateDisabled: this.autoupdateDisabled,
- componentData: this.componentData,
- disassociatedItemIds: this.disassociatedItemIds,
- associatedItemIds: this.associatedItemIds,
- };
-
- _.merge(params, super.structureParams());
- return params;
- }
-
- toJSON() {
- return {uuid: this.uuid}
- }
-
- get content_type() {
- return "SN|Component";
- }
-
- isEditor() {
- return this.area == "editor-editor";
- }
-
- isTheme() {
- return this.content_type == "SN|Theme" || this.area == "themes";
- }
-
- isDefaultEditor() {
- return this.getAppDataItem("defaultEditor") == true;
- }
-
- setLastSize(size) {
- this.setAppDataItem("lastSize", size);
- }
-
- getLastSize() {
- return this.getAppDataItem("lastSize");
- }
-
- keysToIgnoreWhenCheckingContentEquality() {
- return ["active"].concat(super.keysToIgnoreWhenCheckingContentEquality());
- }
-
-
- /*
- An associative component depends on being explicitly activated for a given item, compared to a dissaciative component,
- which is enabled by default in areas unrelated to a certain item.
- */
- static associativeAreas() {
- return ["editor-editor"];
- }
-
- isAssociative() {
- return Component.associativeAreas().includes(this.area);
- }
-
- associateWithItem(item) {
- this.associatedItemIds.push(item.uuid);
- }
-
- isExplicitlyEnabledForItem(item) {
- return this.associatedItemIds.indexOf(item.uuid) !== -1;
- }
-
- isExplicitlyDisabledForItem(item) {
- return this.disassociatedItemIds.indexOf(item.uuid) !== -1;
- }
-}
diff --git a/app/assets/javascripts/app/models/app/editor.js b/app/assets/javascripts/app/models/app/editor.js
deleted file mode 100644
index 3a38f0734..000000000
--- a/app/assets/javascripts/app/models/app/editor.js
+++ /dev/null
@@ -1,106 +0,0 @@
-class Editor extends Item {
-
- constructor(json_obj) {
- super(json_obj);
- if(!this.notes) {
- this.notes = [];
- }
- if(!this.data) {
- this.data = {};
- }
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- this.url = content.url;
- this.name = content.name;
- this.data = content.data || {};
- this.default = content.default;
- this.systemEditor = content.systemEditor;
- }
-
- structureParams() {
- var params = {
- url: this.url,
- name: this.name,
- data: this.data,
- default: this.default,
- systemEditor: this.systemEditor
- };
-
- _.merge(params, super.structureParams());
- return params;
- }
-
- referenceParams() {
- var references = _.map(this.notes, function(note){
- return {uuid: note.uuid, content_type: note.content_type};
- })
-
- return references;
- }
-
- addItemAsRelationship(item) {
- if(item.content_type == "Note") {
- if(!_.find(this.notes, item)) {
- this.notes.push(item);
- }
- }
- super.addItemAsRelationship(item);
- }
-
- removeItemAsRelationship(item) {
- if(item.content_type == "Note") {
- _.pull(this.notes, item);
- }
- super.removeItemAsRelationship(item);
- }
-
- removeAndDirtyAllRelationships() {
- super.removeAndDirtyAllRelationships();
- this.notes = [];
- }
-
- removeReferencesNotPresentIn(references) {
- super.removeReferencesNotPresentIn(references);
-
- var uuids = references.map(function(ref){return ref.uuid});
- this.notes.forEach(function(note){
- if(!uuids.includes(note.uuid)) {
- _.pull(this.notes, note);
- }
- }.bind(this))
- }
-
- potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID) {
- if(newItem.content_type === "Note" && _.find(this.notes, {uuid: oldUUID})) {
- _.pull(this.notes, {uuid: oldUUID});
- this.notes.push(newItem);
- }
- }
-
- allReferencedObjects() {
- return this.notes;
- }
-
- toJSON() {
- return {uuid: this.uuid}
- }
-
- get content_type() {
- return "SN|Editor";
- }
-
- setData(key, value) {
- var dataHasChanged = JSON.stringify(this.data[key]) !== JSON.stringify(value);
- if(dataHasChanged) {
- this.data[key] = value;
- return true;
- }
- return false;
- }
-
- dataForKey(key) {
- return this.data[key] || {};
- }
-}
diff --git a/app/assets/javascripts/app/models/app/extension.js b/app/assets/javascripts/app/models/app/extension.js
deleted file mode 100644
index f990432a5..000000000
--- a/app/assets/javascripts/app/models/app/extension.js
+++ /dev/null
@@ -1,61 +0,0 @@
-class Action {
- constructor(json) {
- _.merge(this, json);
- this.running = false; // in case running=true was synced with server since model is uploaded nondiscriminatory
- this.error = false;
- if(this.lastExecuted) {
- // is string
- this.lastExecuted = new Date(this.lastExecuted);
- }
- }
-}
-
-class Extension extends Component {
- constructor(json) {
- super(json);
-
- if(json.actions) {
- this.actions = json.actions.map(function(action){
- return new Action(action);
- })
- }
-
- if(!this.actions) {
- this.actions = [];
- }
- }
-
- actionsWithContextForItem(item) {
- return this.actions.filter(function(action){
- return action.context == item.content_type || action.context == "Item";
- })
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- this.description = content.description;
-
- this.supported_types = content.supported_types;
- if(content.actions) {
- this.actions = content.actions.map(function(action){
- return new Action(action);
- })
- }
- }
-
- get content_type() {
- return "Extension";
- }
-
- structureParams() {
- var params = {
- description: this.description,
- actions: this.actions.map((a) => {return _.omit(a, ["subrows", "subactions"])}),
- supported_types: this.supported_types
- };
-
- _.merge(params, super.structureParams());
- return params;
- }
-
-}
diff --git a/app/assets/javascripts/app/models/app/note.js b/app/assets/javascripts/app/models/app/note.js
deleted file mode 100644
index e2b981575..000000000
--- a/app/assets/javascripts/app/models/app/note.js
+++ /dev/null
@@ -1,129 +0,0 @@
-class Note extends Item {
-
- constructor(json_obj) {
- super(json_obj);
-
- if(!this.text) {
- // Some external editors can't handle a null value for text.
- // Notes created on mobile with no text have a null value for it,
- // so we'll just set a default here.
- this.text = "";
- }
-
- if(!this.tags) {
- this.tags = [];
- }
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- this.title = content.title;
- this.text = content.text;
- }
-
- referenceParams() {
- var references = _.map(this.tags, function(tag){
- return {uuid: tag.uuid, content_type: tag.content_type};
- })
-
- return references;
- }
-
- structureParams() {
- var params = {
- title: this.title,
- text: this.text
- };
-
- _.merge(params, super.structureParams());
- return params;
- }
-
- addItemAsRelationship(item) {
- this.savedTagsString = null;
-
- if(item.content_type == "Tag") {
- if(!_.find(this.tags, item)) {
- this.tags.push(item);
- }
- }
- super.addItemAsRelationship(item);
- }
-
- removeItemAsRelationship(item) {
- this.savedTagsString = null;
-
- if(item.content_type == "Tag") {
- _.pull(this.tags, item);
- }
- super.removeItemAsRelationship(item);
- }
-
- removeAndDirtyAllRelationships() {
- this.savedTagsString = null;
-
- this.tags.forEach(function(tag){
- _.pull(tag.notes, this);
- tag.setDirty(true);
- }.bind(this))
- this.tags = [];
- }
-
- removeReferencesNotPresentIn(references) {
- this.savedTagsString = null;
-
- super.removeReferencesNotPresentIn(references);
-
- var uuids = references.map(function(ref){return ref.uuid});
- this.tags.slice().forEach(function(tag){
- if(!uuids.includes(tag.uuid)) {
- _.pull(tag.notes, this);
- _.pull(this.tags, tag);
- }
- }.bind(this))
- }
-
- isBeingRemovedLocally() {
- this.tags.forEach(function(tag){
- _.pull(tag.notes, this);
- }.bind(this))
- super.isBeingRemovedLocally();
- }
-
- static filterDummyNotes(notes) {
- var filtered = notes.filter(function(note){return note.dummy == false || note.dummy == null});
- return filtered;
- }
-
- informReferencesOfUUIDChange(oldUUID, newUUID) {
- for(var tag of this.tags) {
- _.pull(tag.notes, {uuid: oldUUID});
- tag.notes.push(this);
- }
- }
-
- allReferencedObjects() {
- return this.tags;
- }
-
- safeText() {
- return this.text || "";
- }
-
- safeTitle() {
- return this.title || "";
- }
-
- toJSON() {
- return {uuid: this.uuid}
- }
-
- get content_type() {
- return "Note";
- }
-
- tagsString() {
- this.savedTagsString = Tag.arrayToDisplayString(this.tags);
- return this.savedTagsString;
- }
-}
diff --git a/app/assets/javascripts/app/models/app/tag.js b/app/assets/javascripts/app/models/app/tag.js
deleted file mode 100644
index b83585234..000000000
--- a/app/assets/javascripts/app/models/app/tag.js
+++ /dev/null
@@ -1,95 +0,0 @@
-class Tag extends Item {
-
- constructor(json_obj) {
- super(json_obj);
-
- if(!this.notes) {
- this.notes = [];
- }
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- this.title = content.title;
- }
-
- referenceParams() {
- var references = _.map(this.notes, function(note){
- return {uuid: note.uuid, content_type: note.content_type};
- })
-
- return references;
- }
-
- structureParams() {
- var params = {
- title: this.title
- };
-
- _.merge(params, super.structureParams());
- return params;
- }
-
- addItemAsRelationship(item) {
- if(item.content_type == "Note") {
- if(!_.find(this.notes, item)) {
- this.notes.unshift(item);
- }
- }
- super.addItemAsRelationship(item);
- }
-
- removeItemAsRelationship(item) {
- if(item.content_type == "Note") {
- _.pull(this.notes, item);
- }
- super.removeItemAsRelationship(item);
- }
-
- removeAndDirtyAllRelationships() {
- this.notes.forEach(function(note){
- _.pull(note.tags, this);
- note.setDirty(true);
- }.bind(this))
-
- this.notes = [];
- }
-
- removeReferencesNotPresentIn(references) {
- var uuids = references.map(function(ref){return ref.uuid});
- this.notes.slice().forEach(function(note){
- if(!uuids.includes(note.uuid)) {
- _.pull(note.tags, this);
- _.pull(this.notes, note);
- }
- }.bind(this))
- }
-
- isBeingRemovedLocally() {
- this.notes.forEach(function(note){
- _.pull(note.tags, this);
- }.bind(this))
- super.isBeingRemovedLocally();
- }
-
- informReferencesOfUUIDChange(oldUUID, newUUID) {
- for(var note of this.notes) {
- _.pull(note.tags, {uuid: oldUUID});
- note.tags.push(this);
- }
- }
-
- get content_type() {
- return "Tag";
- }
-
- allReferencedObjects() {
- return this.notes;
- }
-
- static arrayToDisplayString(tags) {
- return tags.sort((a, b) => {return a.title > b.title}).map(function(tag, i){
- return "#" + tag.title;
- }).join(" ");
- }
-}
diff --git a/app/assets/javascripts/app/models/app/theme.js b/app/assets/javascripts/app/models/app/theme.js
deleted file mode 100644
index 7d03cb2c3..000000000
--- a/app/assets/javascripts/app/models/app/theme.js
+++ /dev/null
@@ -1,12 +0,0 @@
-class Theme extends Component {
-
- constructor(json_obj) {
- super(json_obj);
-
- this.area = "themes";
- }
-
- get content_type() {
- return "SN|Theme";
- }
-}
diff --git a/app/assets/javascripts/app/models/local/encryptedStorage.js b/app/assets/javascripts/app/models/local/encryptedStorage.js
deleted file mode 100644
index 764d0ae24..000000000
--- a/app/assets/javascripts/app/models/local/encryptedStorage.js
+++ /dev/null
@@ -1,28 +0,0 @@
-class EncryptedStorage extends Item {
-
- constructor(json_obj) {
- super(json_obj);
- }
-
- mapContentToLocalProperties(content) {
- super.mapContentToLocalProperties(content)
- this.storage = content.storage;
- }
-
- structureParams() {
- var params = {
- storage: this.storage,
- };
-
- _.merge(params, super.structureParams());
- return params;
- }
-
- toJSON() {
- return {uuid: this.uuid}
- }
-
- get content_type() {
- return "SN|EncryptedStorage";
- }
-}
diff --git a/app/assets/javascripts/app/models/local/itemParams.js b/app/assets/javascripts/app/models/local/itemParams.js
deleted file mode 100644
index c59de929a..000000000
--- a/app/assets/javascripts/app/models/local/itemParams.js
+++ /dev/null
@@ -1,72 +0,0 @@
-class ItemParams {
-
- constructor(item, keys, version) {
- this.item = item;
- this.keys = keys;
- this.version = version || SFJS.version();
- }
-
- async paramsForExportFile(includeDeleted) {
- this.additionalFields = ["updated_at"];
- this.forExportFile = true;
- if(includeDeleted) {
- return this.__params();
- } else {
- var result = await this.__params();
- return _.omit(result, ["deleted"]);
- }
- }
-
- async paramsForExtension() {
- return this.paramsForExportFile();
- }
-
- async paramsForLocalStorage() {
- this.additionalFields = ["updated_at", "dirty", "errorDecrypting"];
- this.forExportFile = true;
- return this.__params();
- }
-
- async paramsForSync() {
- return this.__params();
- }
-
- async __params() {
-
- console.assert(!this.item.dummy, "Item is dummy, should not have gotten here.", this.item.dummy)
-
- var params = {uuid: this.item.uuid, content_type: this.item.content_type, deleted: this.item.deleted, created_at: this.item.created_at};
- if(!this.item.errorDecrypting) {
- // Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params.
- var doNotEncrypt = this.item.doNotEncrypt() && !this.forExportFile;
- if(this.keys && !doNotEncrypt) {
- var encryptedParams = await SFJS.itemTransformer.encryptItem(this.item, this.keys, this.version);
- _.merge(params, encryptedParams);
-
- if(this.version !== "001") {
- params.auth_hash = null;
- }
- }
- else {
- params.content = this.forExportFile ? this.item.createContentJSONFromProperties() : "000" + await SFJS.crypto.base64(JSON.stringify(this.item.createContentJSONFromProperties()));
- if(!this.forExportFile) {
- params.enc_item_key = null;
- params.auth_hash = null;
- }
- }
- } else {
- // Error decrypting, keep "content" and related fields as is (and do not try to encrypt, otherwise that would be undefined behavior)
- params.content = this.item.content;
- params.enc_item_key = this.item.enc_item_key;
- params.auth_hash = this.item.auth_hash;
- }
-
- if(this.additionalFields) {
- _.merge(params, _.pick(this.item, this.additionalFields));
- }
-
- return params;
- }
-
-
-}
diff --git a/app/assets/javascripts/app/models/noteHistoryEntry.js b/app/assets/javascripts/app/models/noteHistoryEntry.js
new file mode 100644
index 000000000..38414d88f
--- /dev/null
+++ b/app/assets/javascripts/app/models/noteHistoryEntry.js
@@ -0,0 +1,18 @@
+class NoteHistoryEntry extends SFItemHistoryEntry {
+
+ previewTitle() {
+ return this.item.updated_at.toLocaleString();
+ }
+
+ previewSubTitle() {
+ if(!this.hasPreviousEntry) {
+ return `${this.textCharDiffLength} characters loaded`
+ } else if(this.textCharDiffLength < 0) {
+ return `${this.textCharDiffLength * -1} characters removed`
+ } else if(this.textCharDiffLength > 0) {
+ return `${this.textCharDiffLength} characters added`
+ } else {
+ return "Title changed"
+ }
+ }
+}
diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js
index 21c8739a3..1953907c3 100644
--- a/app/assets/javascripts/app/services/actionsManager.js
+++ b/app/assets/javascripts/app/services/actionsManager.js
@@ -14,7 +14,7 @@ class ActionsManager {
}
get extensions() {
- return this.modelManager.extensions;
+ return this.modelManager.validItemsForContentType("Extension");
}
extensionsInContextOfItem(item) {
@@ -74,11 +74,11 @@ class ActionsManager {
if(!item.errorDecrypting) {
if(merge) {
- var items = this.modelManager.mapResponseItemsToLocalModels([item], ModelManager.MappingSourceRemoteActionRetrieved);
+ var items = this.modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved);
for(var mappedItem of items) {
mappedItem.setDirty(true);
}
- this.syncManager.sync(null);
+ this.syncManager.sync();
customCallback({item: item});
} else {
item = this.modelManager.createItem(item, true /* Dont notify observers */);
@@ -122,9 +122,9 @@ class ActionsManager {
switch (action.verb) {
case "get": {
- this.httpManager.getAbsolute(action.url, {}, (response) => {
+ this.httpManager.getAbsolute(action.url, {}, async (response) => {
action.error = false;
- handleResponseDecryption(response, this.authManager.keys(), true);
+ handleResponseDecryption(response, await this.authManager.keys(), true);
}, (response) => {
action.error = true;
customCallback(null);
@@ -134,9 +134,9 @@ class ActionsManager {
case "render": {
- this.httpManager.getAbsolute(action.url, {}, (response) => {
+ this.httpManager.getAbsolute(action.url, {}, async (response) => {
action.error = false;
- handleResponseDecryption(response, this.authManager.keys(), false);
+ handleResponseDecryption(response, await this.authManager.keys(), false);
}, (response) => {
action.error = true;
customCallback(null);
@@ -175,11 +175,11 @@ class ActionsManager {
}
async outgoingParamsForItem(item, extension, decrypted = false) {
- var keys = this.authManager.keys();
+ var keys = await this.authManager.keys();
if(decrypted) {
keys = null;
}
- var itemParams = new ItemParams(item, keys, this.authManager.protocolVersion());
+ var itemParams = new SFItemParams(item, keys, await this.authManager.getAuthParams());
return itemParams.paramsForExtension();
}
@@ -198,8 +198,15 @@ class ActionsManager {
})
}
- presentPasswordModal(callback) {
+ presentRevisionPreviewModal(uuid, content) {
+ var scope = this.$rootScope.$new(true);
+ scope.uuid = uuid;
+ scope.content = content;
+ var el = this.$compile( "" )(scope);
+ angular.element(document.body).append(el);
+ }
+ presentPasswordModal(callback) {
var scope = this.$rootScope.$new(true);
scope.type = "password";
scope.title = "Decryption Assistance";
diff --git a/app/assets/javascripts/app/services/archiveManager.js b/app/assets/javascripts/app/services/archiveManager.js
index 638541afe..8e4616a5b 100644
--- a/app/assets/javascripts/app/services/archiveManager.js
+++ b/app/assets/javascripts/app/services/archiveManager.js
@@ -10,21 +10,19 @@ class ArchiveManager {
Public
*/
- downloadBackup(encrypted) {
+ async downloadBackup(encrypted) {
// download in Standard File format
- var keys, authParams, protocolVersion;
+ var keys, authParams;
if(encrypted) {
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
keys = this.passcodeManager.keys();
authParams = this.passcodeManager.passcodeAuthParams();
- protocolVersion = authParams.version;
} else {
- keys = this.authManager.keys();
- authParams = this.authManager.getAuthParams();
- protocolVersion = this.authManager.protocolVersion();
+ keys = await this.authManager.keys();
+ authParams = await this.authManager.getAuthParams();
}
}
- this.__itemsData(keys, authParams, protocolVersion).then((data) => {
+ this.__itemsData(keys, authParams).then((data) => {
this.__downloadData(data, `SN Archive - ${new Date()}.txt`);
// download as zipped plain text files
@@ -39,8 +37,8 @@ class ArchiveManager {
Private
*/
- async __itemsData(keys, authParams, protocolVersion) {
- let data = await this.modelManager.getAllItemsJSONData(keys, authParams, protocolVersion);
+ async __itemsData(keys, authParams) {
+ let data = await this.modelManager.getAllItemsJSONData(keys, authParams);
let blobData = new Blob([data], {type: 'text/json'});
return blobData;
}
diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js
index f5717e5e9..655ba8c26 100644
--- a/app/assets/javascripts/app/services/authManager.js
+++ b/app/assets/javascripts/app/services/authManager.js
@@ -1,332 +1,184 @@
-angular.module('app')
- .provider('authManager', function () {
+class AuthManager extends SFAuthManager {
- function domainName() {
- var domain_comps = location.hostname.split(".");
- var domain = domain_comps[domain_comps.length - 2] + "." + domain_comps[domain_comps.length - 1];
- return domain;
+ constructor(modelManager, singletonManager, storageManager, dbManager, httpManager, $rootScope, $timeout, $compile) {
+ super(storageManager, httpManager, $timeout);
+ this.$rootScope = $rootScope;
+ this.$compile = $compile;
+ this.modelManager = modelManager;
+ this.singletonManager = singletonManager;
+ this.storageManager = storageManager;
+ this.dbManager = dbManager;
+ }
+
+ loadInitialData() {
+ var userData = this.storageManager.getItemSync("user");
+ if(userData) {
+ this.user = JSON.parse(userData);
+ } else {
+ // legacy, check for uuid
+ var idData = this.storageManager.getItemSync("uuid");
+ if(idData) {
+ this.user = {uuid: idData};
+ }
}
- this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile) {
- return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile);
+ this.configureUserPrefs();
+ this.checkForSecurityUpdate();
+ }
+
+ offline() {
+ return !this.user;
+ }
+
+ isEphemeralSession() {
+ if(this.ephemeral == null || this.ephemeral == undefined) {
+ this.ephemeral = JSON.parse(this.storageManager.getItemSync("ephemeral", StorageManager.Fixed));
+ }
+ return this.ephemeral;
+ }
+
+ setEphemeral(ephemeral) {
+ this.ephemeral = ephemeral;
+ if(ephemeral) {
+ this.storageManager.setModelStorageMode(StorageManager.Ephemeral);
+ this.storageManager.setItemsMode(StorageManager.Ephemeral);
+ } else {
+ this.storageManager.setModelStorageMode(StorageManager.Fixed);
+ this.storageManager.setItemsMode(this.storageManager.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Fixed);
+ this.storageManager.setItem("ephemeral", JSON.stringify(false), StorageManager.Fixed);
+ }
+ }
+
+ async protocolVersion() {
+ var authParams = await this.getAuthParams();
+ if(authParams && authParams.version) {
+ return authParams.version;
}
- function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile) {
+ var keys = await this.keys();
+ if(keys && keys.ak) {
+ // If there's no version stored, and there's an ak, it has to be 002. Newer versions would have thier version stored in authParams.
+ return "002";
+ } else {
+ return "001";
+ }
+ }
- this.loadInitialData = function() {
- var userData = storageManager.getItem("user");
- if(userData) {
- this.user = JSON.parse(userData);
- } else {
- // legacy, check for uuid
- var idData = storageManager.getItem("uuid");
- if(idData) {
- this.user = {uuid: idData};
- }
- }
+ async getAuthParamsForEmail(url, email, extraParams) {
+ return super.getAuthParamsForEmail(url, email, extraParams);
+ }
+ async login(url, email, password, ephemeral, strictSignin, extraParams) {
+ return super.login(url, email, password, strictSignin, extraParams).then((response) => {
+ if(!response.error) {
+ this.setEphemeral(ephemeral);
this.checkForSecurityUpdate();
}
- this.offline = function() {
- return !this.user;
+ return response;
+ })
+ }
+
+ async register(url, email, password, ephemeral) {
+ return super.register(url, email, password).then((response) => {
+ if(!response.error) {
+ this.setEphemeral(ephemeral);
}
+ return response;
+ })
+ }
- this.isEphemeralSession = function() {
- if(this.ephemeral == null || this.ephemeral == undefined) {
- this.ephemeral = JSON.parse(storageManager.getItem("ephemeral", StorageManager.Fixed));
- }
- return this.ephemeral;
- }
-
- this.setEphemeral = function(ephemeral) {
- this.ephemeral = ephemeral;
- if(ephemeral) {
- storageManager.setModelStorageMode(StorageManager.Ephemeral);
- storageManager.setItemsMode(StorageManager.Ephemeral);
- } else {
- storageManager.setModelStorageMode(StorageManager.Fixed);
- storageManager.setItemsMode(storageManager.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Fixed);
- storageManager.setItem("ephemeral", JSON.stringify(false), StorageManager.Fixed);
- }
- }
-
- this.getAuthParams = function() {
- if(!this._authParams) {
- this._authParams = JSON.parse(storageManager.getItem("auth_params"));
- }
- return this._authParams;
- }
-
- this.keys = function() {
- if(!this._keys) {
- var mk = storageManager.getItem("mk");
- if(!mk) {
- return null;
- }
- this._keys = {mk: mk, ak: storageManager.getItem("ak")};
- }
- return this._keys;
- }
-
- this.protocolVersion = function() {
- var authParams = this.getAuthParams();
- if(authParams && authParams.version) {
- return authParams.version;
- }
-
- var keys = this.keys();
- if(keys && keys.ak) {
- // If there's no version stored, and there's an ak, it has to be 002. Newer versions would have thier version stored in authParams.
- return "002";
- } else {
- return "001";
- }
- }
-
- this.isProtocolVersionSupported = function(version) {
- return SFJS.supportedVersions().includes(version);
- }
-
- this.getAuthParamsForEmail = function(url, email, extraParams, callback) {
- var requestUrl = url + "/auth/params";
- httpManager.getAbsolute(requestUrl, _.merge({email: email}, extraParams), function(response){
- callback(response);
- }, function(response){
- console.error("Error getting auth params", response);
- if(typeof response !== 'object') {
- response = {error: {message: "A server error occurred while trying to sign in. Please try again."}};
- }
- callback(response);
- })
- }
-
- this.login = function(url, email, password, ephemeral, strictSignin, extraParams, callback) {
- this.getAuthParamsForEmail(url, email, extraParams, (authParams) => {
-
- // SF3 requires a unique identifier in the auth params
- authParams.identifier = email;
-
- if(authParams.error) {
- callback(authParams);
- return;
- }
-
- if(!authParams || !authParams.pw_cost) {
- callback({error : {message: "Invalid email or password."}});
- return;
- }
-
- if(!this.isProtocolVersionSupported(authParams.version)) {
- var message;
- if(SFJS.isVersionNewerThanLibraryVersion(authParams.version)) {
- // The user has a new account type, but is signing in to an older client.
- message = "This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.";
- } else {
- // The user has a very old account type, which is no longer supported by this client
- message = "The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.org/help/security for more information.";
- }
- callback({error: {message: message}});
- return;
- }
-
- if(SFJS.isProtocolVersionOutdated(authParams.version)) {
- let message = `The encryption version for your account, ${authParams.version}, is outdated and requires upgrade. You may proceed with login, but are advised to follow prompts for Security Updates once inside. Please visit standardnotes.org/help/security for more information.\n\nClick 'OK' to proceed with login.`
- if(!confirm(message)) {
- callback({error: {}});
- return;
- }
- }
-
- if(!SFJS.supportsPasswordDerivationCost(authParams.pw_cost)) {
- let message = "Your account was created on a platform with higher security capabilities than this browser supports. " +
- "If we attempted to generate your login keys here, it would take hours. " +
- "Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in."
- callback({error: {message: message}});
- return;
- }
-
- var minimum = SFJS.costMinimumForVersion(authParams.version);
- if(authParams.pw_cost < minimum) {
- let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/security for more information.";
- callback({error: {message: message}});
- return;
- }
-
- if(strictSignin) {
- // Refuse sign in if authParams.version is anything but the latest version
- var latestVersion = SFJS.version();
- if(authParams.version !== latestVersion) {
- let message = `Strict sign in refused server sign in parameters. The latest security version is ${latestVersion}, but your account is reported to have version ${authParams.version}. If you'd like to proceed with sign in anyway, please disable strict sign in and try again.`;
- callback({error: {message: message}});
- return;
- }
- }
-
- SFJS.crypto.computeEncryptionKeysForUser(password, authParams).then((keys) => {
- var requestUrl = url + "/auth/sign_in";
- var params = _.merge({password: keys.pw, email: email}, extraParams);
-
- httpManager.postAbsolute(requestUrl, params, (response) => {
- this.setEphemeral(ephemeral);
- this.handleAuthResponse(response, email, url, authParams, keys);
- this.checkForSecurityUpdate();
- $timeout(() => callback(response));
- }, (response) => {
- console.error("Error logging in", response);
- if(typeof response !== 'object') {
- response = {error: {message: "A server error occurred while trying to sign in. Please try again."}};
- }
- $timeout(() => callback(response));
- });
-
- });
- })
- }
-
- this.handleAuthResponse = function(response, email, url, authParams, keys) {
- try {
- if(url) {
- storageManager.setItem("server", url);
- }
-
- this.user = response.user;
- storageManager.setItem("user", JSON.stringify(response.user));
-
- this._authParams = authParams;
- storageManager.setItem("auth_params", JSON.stringify(authParams));
-
- storageManager.setItem("jwt", response.token);
- this.saveKeys(keys);
- } catch(e) {
- dbManager.displayOfflineAlert();
- }
- }
-
- this.saveKeys = function(keys) {
- this._keys = keys;
- // pw doesn't need to be saved.
- // storageManager.setItem("pw", keys.pw);
- storageManager.setItem("mk", keys.mk);
- storageManager.setItem("ak", keys.ak);
- }
-
- this.register = function(url, email, password, ephemeral, callback) {
- SFJS.crypto.generateInitialKeysAndAuthParamsForUser(email, password).then((results) => {
- let keys = results.keys;
- let authParams = results.authParams;
-
- var requestUrl = url + "/auth";
- var params = _.merge({password: keys.pw, email: email}, authParams);
-
- httpManager.postAbsolute(requestUrl, params, (response) => {
- this.setEphemeral(ephemeral);
- this.handleAuthResponse(response, email, url, authParams, keys);
- callback(response);
- }, (response) => {
- console.error("Registration error", response);
- if(typeof response !== 'object') {
- response = {error: {message: "A server error occurred while trying to register. Please try again."}};
- }
- callback(response);
- })
- });
- }
-
- this.changePassword = function(current_server_pw, newKeys, newAuthParams, callback) {
- let email = this.user.email;
- let newServerPw = newKeys.pw;
-
- var requestUrl = storageManager.getItem("server") + "/auth/change_pw";
- var params = _.merge({new_password: newServerPw, current_password: current_server_pw}, newAuthParams);
-
- httpManager.postAbsolute(requestUrl, params, (response) => {
- this.handleAuthResponse(response, email, null, newAuthParams, newKeys);
- callback(response);
-
- // Allows security update status to be changed if neccessary
- this.checkForSecurityUpdate();
- }, (response) => {
- if(typeof response !== 'object') {
- response = {error: {message: "Something went wrong while changing your password. Your password was not changed. Please try again."}}
- }
- callback(response);
- })
- }
-
- this.checkForSecurityUpdate = function() {
- if(this.offline()) {
- return false;
- }
-
- let latest = SFJS.version();
- let updateAvailable = this.protocolVersion() !== latest;
- if(updateAvailable !== this.securityUpdateAvailable) {
- this.securityUpdateAvailable = updateAvailable;
- $rootScope.$broadcast("security-update-status-changed");
- }
-
- return this.securityUpdateAvailable;
- }
-
- this.presentPasswordWizard = function(type) {
- var scope = $rootScope.$new(true);
- scope.type = type;
- var el = $compile( "" )(scope);
- angular.element(document.body).append(el);
- }
-
- this.staticifyObject = function(object) {
- return JSON.parse(JSON.stringify(object));
- }
-
- this.signOut = function() {
- this._keys = null;
- this.user = null;
- this._authParams = null;
- }
-
-
- /* User Preferences */
-
- let prefsContentType = "SN|UserPreferences";
-
- singletonManager.registerSingleton({content_type: prefsContentType}, (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);
- $rootScope.sync("authManager singletonCreate");
- valueCallback(prefs);
- });
-
- this.userPreferencesDidChange = function() {
- $rootScope.$broadcast("user-preferences-changed");
- }
-
- this.syncUserPreferences = function() {
- if(this.userPreferences) {
- this.userPreferences.setDirty(true);
- $rootScope.sync("syncUserPreferences");
- }
- }
-
- this.getUserPrefValue = function(key, defaultValue) {
- if(!this.userPreferences) { return defaultValue; }
- var value = this.userPreferences.getAppDataItem(key);
- return (value !== undefined && value != null) ? value : defaultValue;
- }
-
- this.setUserPrefValue = function(key, value, sync) {
- if(!this.userPreferences) { console.log("Prefs are null, not setting value", key); return; }
- this.userPreferences.setAppDataItem(key, value);
- if(sync) {
- this.syncUserPreferences();
- }
+ async changePassword(url, email, current_server_pw, newKeys, newAuthParams) {
+ return super.changePassword(url, email, current_server_pw, newKeys, newAuthParams).then((response) => {
+ if(!response.error) {
+ this.checkForSecurityUpdate();
}
+ return response;
+ })
+ }
+ async handleAuthResponse(response, email, url, authParams, keys) {
+ try {
+ await super.handleAuthResponse(response, email, url, authParams, keys);
+ this.user = response.user;
+ this.storageManager.setItem("user", JSON.stringify(response.user));
+ } catch (e) {
+ this.dbManager.displayOfflineAlert();
}
-});
+ }
+
+ async checkForSecurityUpdate() {
+ if(this.offline()) {
+ return false;
+ }
+
+ let latest = SFJS.version();
+ let updateAvailable = await this.protocolVersion() !== latest;
+ if(updateAvailable !== this.securityUpdateAvailable) {
+ this.securityUpdateAvailable = updateAvailable;
+ this.$rootScope.$broadcast("security-update-status-changed");
+ }
+
+ return this.securityUpdateAvailable;
+ }
+
+ presentPasswordWizard(type) {
+ var scope = this.$rootScope.$new(true);
+ scope.type = type;
+ var el = this.$compile( "" )(scope);
+ angular.element(document.body).append(el);
+ }
+
+ signOut() {
+ super.signout();
+ this.user = null;
+ this._authParams = null;
+ }
+
+
+ /* User Preferences */
+
+ configureUserPrefs() {
+ let prefsContentType = "SN|UserPreferences";
+
+ let contentTypePredicate = new SFPredicate("content_type", "=", prefsContentType);
+ this.singletonManager.registerSingleton([contentTypePredicate], (resolvedSingleton) => {
+ this.userPreferences = resolvedSingleton;
+ this.userPreferencesDidChange();
+ }, (valueCallback) => {
+ // Safe to create. Create and return object.
+ var prefs = new SFItem({content_type: prefsContentType});
+ this.modelManager.addItem(prefs);
+ prefs.setDirty(true);
+ this.$rootScope.sync();
+ valueCallback(prefs);
+ });
+ }
+
+ userPreferencesDidChange() {
+ this.$rootScope.$broadcast("user-preferences-changed");
+ }
+
+ syncUserPreferences() {
+ if(this.userPreferences) {
+ this.userPreferences.setDirty(true);
+ this.$rootScope.sync();
+ }
+ }
+
+ getUserPrefValue(key, defaultValue) {
+ if(!this.userPreferences) { return defaultValue; }
+ var value = this.userPreferences.getAppDataItem(key);
+ return (value !== undefined && value != null) ? value : defaultValue;
+ }
+
+ setUserPrefValue(key, value, sync) {
+ if(!this.userPreferences) { console.log("Prefs are null, not setting value", key); return; }
+ this.userPreferences.setAppDataItem(key, value);
+ if(sync) {
+ this.syncUserPreferences();
+ }
+ }
+}
+
+angular.module('app').service('authManager', AuthManager);
diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js
index 88acc5bb2..8a7ee9383 100644
--- a/app/assets/javascripts/app/services/componentManager.js
+++ b/app/assets/javascripts/app/services/componentManager.js
@@ -1,9 +1,10 @@
-/* This domain will be used to save context item client data */
-let ClientDataDomain = "org.standardnotes.sn.components";
class ComponentManager {
constructor($rootScope, modelManager, syncManager, desktopManager, nativeExtManager, $timeout, $compile) {
+ /* This domain will be used to save context item client data */
+ ComponentManager.ClientDataDomain = "org.standardnotes.sn.components";
+
this.$compile = $compile;
this.$rootScope = $rootScope;
this.modelManager = modelManager;
@@ -57,13 +58,13 @@ class ComponentManager {
/* If the source of these new or updated items is from a Component itself saving items, we don't need to notify
components again of the same item. Regarding notifying other components than the issuing component, other mapping sources
- will take care of that, like ModelManager.MappingSourceRemoteSaved
+ will take care of that, like SFModelManager.MappingSourceRemoteSaved
Update: We will now check sourceKey to determine whether the incoming change should be sent to
a component. If sourceKey == component.uuid, it will be skipped. This way, if one component triggers a change,
it's sent to other components.
*/
- // if(source == ModelManager.MappingSourceComponentRetrieved) {
+ // if(source == SFModelManager.MappingSourceComponentRetrieved) {
// return;
// }
@@ -74,7 +75,7 @@ class ComponentManager {
/* We only want to sync if the item source is Retrieved, not MappingSourceRemoteSaved to avoid
recursion caused by the component being modified and saved after it is updated.
*/
- if(syncedComponents.length > 0 && source != ModelManager.MappingSourceRemoteSaved) {
+ if(syncedComponents.length > 0 && source != SFModelManager.MappingSourceRemoteSaved) {
// Ensure any component in our data is installed by the system
this.desktopManager.syncComponentsInstallation(syncedComponents);
}
@@ -157,14 +158,17 @@ class ComponentManager {
}
}
- getActiveTheme() {
- return this.componentsForArea("themes").find((theme) => {return theme.active});
+ getActiveThemes() {
+ return this.componentsForArea("themes").filter((theme) => {return theme.active});
}
postActiveThemeToComponent(component) {
- var activeTheme = this.getActiveTheme();
+ var themes = this.getActiveThemes();
+ var urls = themes.map((theme) => {
+ return this.urlForComponent(theme);
+ })
var data = {
- themes: [activeTheme ? this.urlForComponent(activeTheme) : null]
+ themes: urls
}
this.sendMessageToComponent(component, {action: "themes", data: data})
@@ -193,17 +197,16 @@ class ComponentManager {
jsonForItem(item, component, source) {
var params = {uuid: item.uuid, content_type: item.content_type, created_at: item.created_at, updated_at: item.updated_at, deleted: item.deleted};
params.content = item.createContentJSONFromProperties();
- /* Legacy is using component.url key, so if it's present, use it, otherwise use uuid */
- params.clientData = item.getDomainDataItem(component.url || component.uuid, ClientDataDomain) || {};
+ params.clientData = item.getDomainDataItem(component.getClientDataKey(), ComponentManager.ClientDataDomain) || {};
/* This means the this function is being triggered through a remote Saving response, which should not update
actual local content values. The reason is, Save responses may be delayed, and a user may have changed some values
in between the Save was initiated, and the time it completes. So we only want to update actual content values (and not just metadata)
- when its another source, like ModelManager.MappingSourceRemoteRetrieved.
+ when its another source, like SFModelManager.MappingSourceRemoteRetrieved.
3/7/18: Add MappingSourceLocalSaved as well to handle fully offline saving. github.com/standardnotes/forum/issues/169
*/
- if(source && (source == ModelManager.MappingSourceRemoteSaved || source == ModelManager.MappingSourceLocalSaved)) {
+ if(source && (source == SFModelManager.MappingSourceRemoteSaved || source == SFModelManager.MappingSourceLocalSaved)) {
params.isMetadataUpdate = true;
}
this.removePrivatePropertiesFromResponseItems([params], component);
@@ -277,13 +280,13 @@ class ComponentManager {
if(component.offlineOnly || (isDesktopApplication() && component.local_url)) {
return component.local_url && component.local_url.replace("sn://", offlinePrefix + this.desktopManager.getApplicationDataPath() + "/");
} else {
- return component.hosted_url || component.url;
+ return component.hosted_url || component.legacy_url;
}
}
componentForUrl(url) {
return this.components.filter(function(component){
- return component.url === url || component.hosted_url === url;
+ return component.hosted_url === url || component.legacy_url === url;
})[0];
}
@@ -476,24 +479,29 @@ class ComponentManager {
We map the items here because modelManager is what updates the UI. If you were to instead get the items directly,
this would update them server side via sync, but would never make its way back to the UI.
*/
- var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, ModelManager.MappingSourceComponentRetrieved, component.uuid);
+ var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, SFModelManager.MappingSourceComponentRetrieved, component.uuid);
- for(var item of localItems) {
- var responseItem = _.find(responseItems, {uuid: item.uuid});
+ for(var responseItem of responseItems) {
+ var item = _.find(localItems, {uuid: responseItem.uuid});
+ if(!item) {
+ // An item this extension is trying to save was possibly removed locally, notify user
+ alert(`The extension ${component.name} is trying to save an item with type ${responseItem.content_type}, but that item does not exist. Please restart this extension and try again.`);
+ continue;
+ }
_.merge(item.content, responseItem.content);
if(responseItem.clientData) {
- item.setDomainDataItem(component.url || component.uuid, responseItem.clientData, ClientDataDomain);
+ item.setDomainDataItem(component.getClientDataKey(), responseItem.clientData, ComponentManager.ClientDataDomain);
}
item.setDirty(true);
}
- this.syncManager.sync((response) => {
+ this.syncManager.sync().then((response) => {
// Allow handlers to be notified when a save begins and ends, to update the UI
var saveMessage = Object.assign({}, message);
saveMessage.action = response && response.error ? "save-error" : "save-success";
- this.replyToMessage(component, message, {error: response.error})
+ this.replyToMessage(component, message, {error: response && response.error})
this.handleMessage(component, saveMessage);
- }, null, "handleSaveItemsMessage");
+ });
});
}
@@ -513,7 +521,7 @@ class ComponentManager {
for(let responseItem of responseItems) {
var item = this.modelManager.createItem(responseItem);
if(responseItem.clientData) {
- item.setDomainDataItem(component.url || component.uuid, responseItem.clientData, ClientDataDomain);
+ item.setDomainDataItem(component.getClientDataKey(), responseItem.clientData, ComponentManager.ClientDataDomain);
}
this.modelManager.addItem(item);
this.modelManager.resolveReferencesForItem(item, true);
@@ -521,7 +529,7 @@ class ComponentManager {
processedItems.push(item);
}
- this.syncManager.sync("handleCreateItemMessage");
+ this.syncManager.sync();
// "create-item" or "create-items" are possible messages handled here
let reply =
@@ -554,9 +562,12 @@ class ComponentManager {
this.deactivateComponent(model, true);
}
this.modelManager.setItemToBeDeleted(model);
+ // Currently extensions are not notified of association until a full server sync completes.
+ // We manually notify observers.
+ this.modelManager.notifySyncObserversOfModels([model], SFModelManager.MappingSourceRemoteSaved);
}
- this.syncManager.sync("handleDeleteItemsMessage");
+ this.syncManager.sync();
}
});
}
@@ -572,7 +583,7 @@ class ComponentManager {
this.runWithPermissions(component, [], () => {
component.componentData = message.data.componentData;
component.setDirty(true);
- this.syncManager.sync("handleSetComponentDataMessage");
+ this.syncManager.sync();
});
}
@@ -583,11 +594,13 @@ class ComponentManager {
if(targetComponent.active) {
this.deactivateComponent(targetComponent);
} else {
- if(targetComponent.content_type == "SN|Theme") {
- // Deactive currently active theme
- var activeTheme = this.getActiveTheme();
- if(activeTheme) {
- this.deactivateComponent(activeTheme);
+ if(targetComponent.content_type == "SN|Theme" && !targetComponent.isLayerable()) {
+ // Deactive currently active theme if new theme is not layerable
+ var activeThemes = this.getActiveThemes();
+ for(var theme of activeThemes) {
+ if(theme && !theme.isLayerable()) {
+ this.deactivateComponent(theme);
+ }
}
}
this.activateComponent(targetComponent);
@@ -665,7 +678,7 @@ class ComponentManager {
}
}
component.setDirty(true);
- this.syncManager.sync("promptForPermissions");
+ this.syncManager.sync();
}
this.permissionDialogs = this.permissionDialogs.filter((pendingDialog) => {
@@ -776,14 +789,14 @@ class ComponentManager {
// We want to run the handler in a $timeout so the UI updates, but we also don't want it to run asyncronously
// so that the steps below this one are run before the handler. So we run in a waitTimeout.
await this.waitTimeout(() => {
- handler.activationHandler(component);
+ handler.activationHandler && handler.activationHandler(component);
})
}
}
if(didChange && !dontSync) {
component.setDirty(true);
- this.syncManager.sync("activateComponent");
+ this.syncManager.sync();
}
if(!this.activeComponents.includes(component)) {
@@ -803,14 +816,14 @@ class ComponentManager {
for(let handler of this.handlers) {
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
await this.waitTimeout(() => {
- handler.activationHandler(component);
+ handler.activationHandler && handler.activationHandler(component);
})
}
}
if(didChange && !dontSync) {
component.setDirty(true);
- this.syncManager.sync("deactivateComponent");
+ this.syncManager.sync();
}
_.pull(this.activeComponents, component);
@@ -837,7 +850,7 @@ class ComponentManager {
for(let handler of this.handlers) {
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
await this.waitTimeout(() => {
- handler.activationHandler(component);
+ handler.activationHandler && handler.activationHandler(component);
})
}
}
@@ -863,7 +876,7 @@ class ComponentManager {
for(var handler of this.handlers) {
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
await this.waitTimeout(() => {
- handler.activationHandler(component);
+ handler.activationHandler && handler.activationHandler(component);
})
}
}
@@ -880,7 +893,7 @@ class ComponentManager {
deleteComponent(component) {
this.modelManager.setItemToBeDeleted(component);
- this.syncManager.sync("deleteComponent");
+ this.syncManager.sync();
}
isComponentActive(component) {
@@ -908,7 +921,9 @@ class ComponentManager {
var setSize = function(element, size) {
var widthString = typeof size.width === 'string' ? size.width : `${data.width}px`;
var heightString = typeof size.height === 'string' ? size.height : `${data.height}px`;
- element.setAttribute("style", `width:${widthString}; height:${heightString};`);
+ if(element) {
+ element.setAttribute("style", `width:${widthString}; height:${heightString};`);
+ }
}
if(component.area == "rooms" || component.area == "modal") {
@@ -919,6 +934,9 @@ class ComponentManager {
}
} else {
var iframe = this.iframeForComponent(component);
+ if(!iframe) {
+ return;
+ }
var width = data.width;
var height = data.height;
iframe.width = width;
diff --git a/app/assets/javascripts/app/services/dbManager.js b/app/assets/javascripts/app/services/dbManager.js
index fb9de21d3..d0074a446 100644
--- a/app/assets/javascripts/app/services/dbManager.js
+++ b/app/assets/javascripts/app/services/dbManager.js
@@ -47,6 +47,10 @@ class DBManager {
}
};
+ request.onblocked = (event) => {
+ console.error("Request blocked error:", event.target.errorCode);
+ }
+
request.onupgradeneeded = (event) => {
var db = event.target.result;
@@ -106,7 +110,11 @@ class DBManager {
};
transaction.onerror = function(event) {
- console.log("Transaction error:", event.target.errorCode);
+ console.error("Transaction error:", event.target.errorCode);
+ };
+
+ transaction.onblocked = function(event) {
+ console.error("Transaction blocked error:", event.target.errorCode);
};
transaction.onabort = function(event) {
@@ -127,12 +135,14 @@ class DBManager {
function putNext() {
if (i < items.length) {
var item = items[i];
- itemObjectStore.put(item).onsuccess = putNext;
+ var request = itemObjectStore.put(item);
+ request.onerror = (event) => {
+ console.error("DB put error:", event.target.error);
+ }
+ request.onsuccess = putNext;
++i;
} else {
- if(onsuccess){
- onsuccess();
- }
+ onsuccess && onsuccess();
}
}
}, null)
diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js
index 23edc562d..23612e64f 100644
--- a/app/assets/javascripts/app/services/desktopManager.js
+++ b/app/assets/javascripts/app/services/desktopManager.js
@@ -37,7 +37,7 @@ class DesktopManager {
Keys are not passed into ItemParams, so the result is not encrypted
*/
async convertComponentForTransmission(component) {
- return new ItemParams(component).paramsForExportFile(true);
+ return new SFItemParams(component).paramsForExportFile(true);
}
// All `components` should be installed
@@ -96,11 +96,11 @@ class DesktopManager {
for(var key of permissableKeys) {
component[key] = componentData.content[key];
}
- this.modelManager.notifySyncObserversOfModels([component], ModelManager.MappingSourceDesktopInstalled);
+ this.modelManager.notifySyncObserversOfModels([component], SFModelManager.MappingSourceDesktopInstalled);
component.setAppDataItem("installError", null);
}
component.setDirty(true);
- this.syncManager.sync("onComponentInstallationComplete");
+ this.syncManager.sync();
this.timeout(() => {
for(var observer of this.updateObservers) {
@@ -129,22 +129,19 @@ class DesktopManager {
}
}
- desktop_requestBackupFile(callback) {
- var keys, authParams, protocolVersion;
+ async desktop_requestBackupFile(callback) {
+ var keys, authParams;
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
keys = this.passcodeManager.keys();
authParams = this.passcodeManager.passcodeAuthParams();
- protocolVersion = authParams.version;
} else {
- keys = this.authManager.keys();
- authParams = this.authManager.getAuthParams();
- protocolVersion = this.authManager.protocolVersion();
+ keys = await this.authManager.keys();
+ authParams = await this.authManager.getAuthParams();
}
this.modelManager.getAllItemsJSONData(
keys,
authParams,
- protocolVersion,
true /* return null on empty */
).then((data) => {
callback(data);
diff --git a/app/assets/javascripts/app/services/httpManager.js b/app/assets/javascripts/app/services/httpManager.js
index c8f0a722f..637db6041 100644
--- a/app/assets/javascripts/app/services/httpManager.js
+++ b/app/assets/javascripts/app/services/httpManager.js
@@ -1,80 +1,13 @@
-class HttpManager {
+class HttpManager extends SFHttpManager {
- constructor($timeout, storageManager) {
- // calling callbacks in a $timeout allows angular UI to update
- this.$timeout = $timeout;
- this.storageManager = storageManager;
+ constructor(storageManager, $timeout) {
+ // calling callbacks in a $timeout allows UI to update
+ super($timeout);
+
+ this.setJWTRequestHandler(async () => {
+ return storageManager.getItem("jwt");;
+ })
}
-
- setAuthHeadersForRequest(request) {
- var token = this.storageManager.getItem("jwt");
- if(token) {
- request.setRequestHeader('Authorization', 'Bearer ' + token);
- }
- }
-
- postAbsolute(url, params, onsuccess, onerror) {
- this.httpRequest("post", url, params, onsuccess, onerror);
- }
-
- patchAbsolute(url, params, onsuccess, onerror) {
- this.httpRequest("patch", url, params, onsuccess, onerror);
- }
-
- getAbsolute(url, params, onsuccess, onerror) {
- this.httpRequest("get", url, params, onsuccess, onerror);
- }
-
- httpRequest(verb, url, params, onsuccess, onerror) {
-
- var xmlhttp = new XMLHttpRequest();
-
- xmlhttp.onreadystatechange = function() {
- if (xmlhttp.readyState == 4) {
- var response = xmlhttp.responseText;
- if(response) {
- try {
- response = JSON.parse(response);
- } catch(e) {}
- }
-
- if(xmlhttp.status >= 200 && xmlhttp.status <= 299){
- this.$timeout(function(){
- onsuccess(response);
- })
- } else {
- console.error("Request error:", response);
- this.$timeout(function(){
- onerror(response, xmlhttp.status)
- })
- }
- }
- }.bind(this)
-
- if(verb == "get" && Object.keys(params).length > 0) {
- url = url + this.formatParams(params);
- }
-
- xmlhttp.open(verb, url, true);
- this.setAuthHeadersForRequest(xmlhttp);
- xmlhttp.setRequestHeader('Content-type', 'application/json');
-
- if(verb == "post" || verb == "patch") {
- xmlhttp.send(JSON.stringify(params));
- } else {
- xmlhttp.send();
- }
- }
-
- formatParams(params) {
- return "?" + Object
- .keys(params)
- .map(function(key){
- return key+"="+encodeURIComponent(params[key])
- })
- .join("&")
- }
-
}
angular.module('app').service('httpManager', HttpManager);
diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js
index 9fec09d65..6e67da829 100644
--- a/app/assets/javascripts/app/services/migrationManager.js
+++ b/app/assets/javascripts/app/services/migrationManager.js
@@ -1,23 +1,15 @@
-class MigrationManager {
+class MigrationManager extends SFMigrationManager {
- constructor($rootScope, modelManager, syncManager, componentManager) {
- this.$rootScope = $rootScope;
- this.modelManager = modelManager;
- this.syncManager = syncManager;
+ constructor($rootScope, modelManager, syncManager, componentManager, storageManager) {
+ super(modelManager, syncManager, storageManager);
this.componentManager = componentManager;
+ }
- this.migrators = [];
-
- this.addEditorToComponentMigrator();
-
- this.modelManager.addItemSyncObserver("migration-manager", "*", (allItems, validItems, deletedItems) => {
- for(var migrator of this.migrators) {
- var items = allItems.filter((item) => {return item.content_type == migrator.content_type});
- if(items.length > 0) {
- migrator.handler(items);
- }
- }
- });
+ registeredMigrations() {
+ return [
+ this.editorToComponentMigration(),
+ this.componentUrlToHostedUrl()
+ ];
}
/*
@@ -25,10 +17,10 @@ class MigrationManager {
convert to using the new component API.
*/
- addEditorToComponentMigrator() {
- this.migrators.push({
+ editorToComponentMigration() {
+ return {
+ name: "editor-to-component",
content_type: "SN|Editor",
-
handler: (editors) => {
// Convert editors to components
for(var editor of editors) {
@@ -36,9 +28,11 @@ class MigrationManager {
if(editor.url && !this.componentManager.componentForUrl(editor.url)) {
var component = this.modelManager.createItem({
content_type: "SN|Component",
- url: editor.url,
- name: editor.name,
- area: "editor-editor"
+ content: {
+ url: editor.url,
+ name: editor.name,
+ area: "editor-editor"
+ }
})
component.setAppDataItem("data", editor.data);
component.setDirty(true);
@@ -50,12 +44,38 @@ class MigrationManager {
this.modelManager.setItemToBeDeleted(editor);
}
- this.syncManager.sync("addEditorToComponentMigrator");
+ this.syncManager.sync();
}
- })
+ }
}
-
+ /*
+ Migrate component.url fields to component.hosted_url. This involves rewriting any note data that relied on the
+ component.url value to store clientData, such as the CodeEditor, which stores the programming language for the note
+ in the note's clientData[component.url]. We want to rewrite any matching items to transfer that clientData into
+ clientData[component.uuid].
+ Created: July 6, 2018
+ */
+ componentUrlToHostedUrl() {
+ return {
+ name: "component-url-to-hosted-url",
+ content_type: "SN|Component",
+ handler: (components) => {
+ var notes = this.modelManager.validItemsForContentType("Note");
+ for(var note of notes) {
+ for(var component of components) {
+ var clientData = note.getDomainDataItem(component.hosted_url, ComponentManager.ClientDataDomain);
+ if(clientData) {
+ note.setDomainDataItem(component.uuid, clientData, ComponentManager.ClientDataDomain);
+ note.setDomainDataItem(component.hosted_url, null, ComponentManager.ClientDataDomain);
+ note.setDirty(true);
+ }
+ }
+ }
+ this.syncManager.sync();
+ }
+ }
+ }
}
angular.module('app').service('migrationManager', MigrationManager);
diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js
index 3361e441f..6ac250eea 100644
--- a/app/assets/javascripts/app/services/modelManager.js
+++ b/app/assets/javascripts/app/services/modelManager.js
@@ -1,302 +1,57 @@
-class ModelManager {
+SFModelManager.ContentTypeClassMapping = {
+ "Note" : SNNote,
+ "Tag" : SNTag,
+ "SN|SmartTag" : SNSmartTag,
+ "Extension" : SNExtension,
+ "SN|Editor" : SNEditor,
+ "SN|Theme" : SNTheme,
+ "SN|Component" : SNComponent,
+ "SF|Extension" : SNServerExtension,
+ "SF|MFA" : SNMfa
+};
- constructor(storageManager) {
- ModelManager.MappingSourceRemoteRetrieved = "MappingSourceRemoteRetrieved";
- ModelManager.MappingSourceRemoteSaved = "MappingSourceRemoteSaved";
- ModelManager.MappingSourceLocalSaved = "MappingSourceLocalSaved";
- ModelManager.MappingSourceLocalRetrieved = "MappingSourceLocalRetrieved";
- ModelManager.MappingSourceComponentRetrieved = "MappingSourceComponentRetrieved";
- ModelManager.MappingSourceDesktopInstalled = "MappingSourceDesktopInstalled"; // When a component is installed by the desktop and some of its values change
- ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */
- ModelManager.MappingSourceFileImport = "MappingSourceFileImport";
+SFItem.AppDomain = "org.standardnotes.sn";
- ModelManager.isMappingSourceRetrieved = (source) => {
- return [
- ModelManager.MappingSourceRemoteRetrieved,
- ModelManager.MappingSourceComponentRetrieved,
- ModelManager.MappingSourceRemoteActionRetrieved
- ].includes(source);
- }
+class ModelManager extends SFModelManager {
- this.storageManager = storageManager;
+ constructor(storageManager, $timeout) {
+ super($timeout);
this.notes = [];
this.tags = [];
- this.itemSyncObservers = [];
- this.itemChangeObservers = [];
- this.itemsPendingRemoval = [];
- this.items = [];
- this._extensions = [];
- this.acceptableContentTypes = [
- "Note", "Tag", "Extension", "SN|Editor", "SN|Theme",
- "SN|Component", "SF|Extension", "SN|UserPreferences", "SF|MFA"
- ];
+ this.components = [];
+
+ this.storageManager = storageManager;
}
- resetLocalMemory() {
+ handleSignout() {
+ super.handleSignout();
this.notes.length = 0;
this.tags.length = 0;
- this.items.length = 0;
- this._extensions.length = 0;
+ this.components.length = 0;
}
- get allItems() {
- return this.items.filter(function(item){
- return !item.dummy;
- })
- }
-
- get extensions() {
- return this._extensions.filter(function(ext){
- return !ext.deleted;
- })
- }
-
- alternateUUIDForItem(item, callback, removeOriginal) {
- // We need to clone this item and give it a new uuid, then delete item with old uuid from db (you can't modify uuid's in our indexeddb setup)
-
- // Collapse in memory properties to item's content object, as the new item will be created based on the content object, and not the physical properties. (like note.text or note.title)
- item.refreshContentObject();
-
- var newItem = this.createItem(item);
-
- newItem.uuid = SFJS.crypto.generateUUIDSync();
-
- // Update uuids of relationships
- newItem.informReferencesOfUUIDChange(item.uuid, newItem.uuid);
-
- this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid);
-
-
- console.log(item.uuid, "-->", newItem.uuid);
-
- var block = () => {
- this.addItem(newItem);
- newItem.setDirty(true);
- newItem.markAllReferencesDirty();
- callback();
- }
-
- if(removeOriginal) {
- // Set to deleted, then run through mapping function so that observers can be notified
+ removeAllItemsFromMemory() {
+ for(var item of this.items) {
item.deleted = true;
- this.mapResponseItemsToLocalModels([item], ModelManager.MappingSourceLocalSaved);
- block();
- } else {
- block();
}
- }
-
- informModelsOfUUIDChangeForItem(newItem, oldUUID, newUUID) {
- // some models that only have one-way relationships might be interested to hear that an item has changed its uuid
- // for example, editors have a one way relationship with notes. When a note changes its UUID, it has no way to inform the editor
- // to update its relationships
-
- for(var model of this.items) {
- model.potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID);
- }
- }
-
- allItemsMatchingTypes(contentTypes) {
- return this.allItems.filter(function(item){
- return (_.includes(contentTypes, item.content_type) || _.includes(contentTypes, "*")) && !item.dummy;
- })
- }
-
- validItemsForContentType(contentType) {
- return this.allItems.filter((item) => {
- return item.content_type == contentType && !item.errorDecrypting;
- });
- }
-
- findItem(itemId) {
- return _.find(this.items, {uuid: itemId});
+ this.notifySyncObserversOfModels(this.items);
+ this.handleSignout();
}
findOrCreateTagByTitle(title) {
var tag = _.find(this.tags, {title: title})
if(!tag) {
- tag = this.createItem({content_type: "Tag", title: title});
+ tag = this.createItem({content_type: "Tag", content: {title: title}});
+ tag.setDirty(true);
this.addItem(tag);
}
return tag;
}
- didSyncModelsOffline(items) {
- this.notifySyncObserversOfModels(items, ModelManager.MappingSourceLocalSaved);
- }
-
- mapResponseItemsToLocalModels(items, source, sourceKey) {
- return this.mapResponseItemsToLocalModelsOmittingFields(items, null, source, sourceKey);
- }
-
- mapResponseItemsToLocalModelsOmittingFields(items, omitFields, source, sourceKey) {
- var models = [], processedObjects = [], modelsToNotifyObserversOf = [];
-
- // first loop should add and process items
- for (var json_obj of items) {
- if((!json_obj.content_type || !json_obj.content) && !json_obj.deleted && !json_obj.errorDecrypting) {
- // An item that is not deleted should never have empty content
- console.error("Server response item is corrupt:", json_obj);
- continue;
- }
-
- // Lodash's _.omit, which was previously used, seems to cause unexpected behavior
- // when json_obj is an ES6 item class. So we instead manually omit each key.
- if(Array.isArray(omitFields)) {
- for(var key of omitFields) {
- delete json_obj[key];
- }
- }
-
- var item = this.findItem(json_obj.uuid);
-
- if(item) {
- item.updateFromJSON(json_obj);
- // If an item goes through mapping, it can no longer be a dummy.
- item.dummy = false;
- }
-
- if(this.itemsPendingRemoval.includes(json_obj.uuid)) {
- _.pull(this.itemsPendingRemoval, json_obj.uuid);
- continue;
- }
-
- let contentType = json_obj["content_type"] || (item && item.content_type);
- var unknownContentType = !_.includes(this.acceptableContentTypes, contentType);
- var isDirtyItemPendingDelete = false;
- if(json_obj.deleted == true || unknownContentType) {
- if(json_obj.deleted && json_obj.dirty) {
- // Item was marked as deleted but not yet synced
- // We need to create this item as usual, but just not add it to individual arrays
- // i.e add to this.items but not this.notes (so that it can be retrieved with getDirtyItems)
- isDirtyItemPendingDelete = true;
- } else {
- if(item && !unknownContentType) {
- modelsToNotifyObserversOf.push(item);
- this.removeItemLocally(item);
- }
- continue;
- }
- }
-
- if(!item) {
- item = this.createItem(json_obj, true);
- }
-
- this.addItem(item, isDirtyItemPendingDelete);
-
- // Observers do not need to handle items that errored while decrypting.
- if(!item.errorDecrypting) {
- modelsToNotifyObserversOf.push(item);
- }
-
- models.push(item);
- processedObjects.push(json_obj);
- }
-
- // second loop should process references
- for (var index in processedObjects) {
- var json_obj = processedObjects[index];
- if(json_obj.content) {
- this.resolveReferencesForItem(models[index]);
- }
- }
-
- this.notifySyncObserversOfModels(modelsToNotifyObserversOf, source, sourceKey);
-
- return models;
- }
-
- /* Note that this function is public, and can also be called manually (desktopManager uses it) */
- notifySyncObserversOfModels(models, source, sourceKey) {
- for(var observer of this.itemSyncObservers) {
- var allRelevantItems = observer.type == "*" ? models : models.filter(function(item){return item.content_type == observer.type});
- var validItems = [], deletedItems = [];
- for(var item of allRelevantItems) {
- if(item.deleted) {
- deletedItems.push(item);
- } else {
- validItems.push(item);
- }
- }
-
- if(allRelevantItems.length > 0) {
- observer.callback(allRelevantItems, validItems, deletedItems, source, sourceKey);
- }
- }
- }
-
- notifyItemChangeObserversOfModels(models) {
- for(var observer of this.itemChangeObservers) {
- var relevantItems = models.filter(function(item){
- return _.includes(observer.content_types, item.content_type) || _.includes(observer.content_types, "*");
- });
-
- if(relevantItems.length > 0) {
- observer.callback(relevantItems);
- }
- }
- }
-
- createItem(json_obj, dontNotifyObservers) {
- var item;
- if(json_obj.content_type == "Note") {
- item = new Note(json_obj);
- } else if(json_obj.content_type == "Tag") {
- item = new Tag(json_obj);
- } else if(json_obj.content_type == "Extension") {
- item = new Extension(json_obj);
- } else if(json_obj.content_type == "SN|Editor") {
- item = new Editor(json_obj);
- } else if(json_obj.content_type == "SN|Theme") {
- item = new Theme(json_obj);
- } else if(json_obj.content_type == "SN|Component") {
- item = new Component(json_obj);
- } else if(json_obj.content_type == "SF|Extension") {
- item = new ServerExtension(json_obj);
- } else if(json_obj.content_type == "SF|MFA") {
- item = new Mfa(json_obj);
- }
-
- else {
- item = new Item(json_obj);
- }
-
- // Some observers would be interested to know when an an item is locally created
- // If we don't send this out, these observers would have to wait until MappingSourceRemoteSaved
- // to hear about it, but sometimes, RemoveSaved is explicitly ignored by the observer to avoid
- // recursive callbacks. See componentManager's syncObserver callback.
- // dontNotifyObservers is currently only set true by modelManagers mapResponseItemsToLocalModels
- if(!dontNotifyObservers) {
- this.notifySyncObserversOfModels([item], ModelManager.MappingSourceLocalSaved);
- }
-
- item.addObserver(this, function(changedItem){
- this.notifyItemChangeObserversOfModels([changedItem]);
- }.bind(this));
-
- return item;
- }
-
- /*
- Be sure itemResponse is a generic Javascript object, and not an Item.
- An Item needs to collapse its properties into its content object before it can be duplicated.
- Note: the reason we need this function is specificallty for the call to resolveReferencesForItem.
- This method creates but does not add the item to the global inventory. It's used by syncManager
- to check if this prospective duplicate item is identical to another item, including the references.
- */
- createDuplicateItem(itemResponse) {
- var dup = this.createItem(itemResponse, true);
- this.resolveReferencesForItem(dup);
- return dup;
- }
-
- addItem(item, globalOnly = false) {
- this.addItems([item], globalOnly);
- }
-
addItems(items, globalOnly = false) {
- items.forEach(function(item){
+ super.addItems(items, globalOnly);
+
+ items.forEach((item) => {
// In some cases, you just want to add the item to this.items, and not to the individual arrays
// This applies when you want to keep an item syncable, but not display it via the individual arrays
if(!globalOnly) {
@@ -311,17 +66,13 @@ class ModelManager {
if(!_.find(this.notes, {uuid: item.uuid})) {
this.notes.unshift(item);
}
- } else if(item.content_type == "Extension") {
- if(!_.find(this._extensions, {uuid: item.uuid})) {
- this._extensions.unshift(item);
+ } else if(item.content_type == "SN|Component") {
+ if(!_.find(this.components, {uuid: item.uuid})) {
+ this.components.unshift(item);
}
}
}
-
- if(!_.find(this.items, {uuid: item.uuid})) {
- this.items.push(item);
- }
- }.bind(this));
+ });
}
resortTag(tag) {
@@ -332,158 +83,37 @@ class ModelManager {
}), 0, tag);
}
- resolveReferencesForItem(item, markReferencesDirty = false) {
-
- var contentObject = item.contentObject;
-
- // If another client removes an item's references, this client won't pick up the removal unless
- // we remove everything not present in the current list of references
- item.removeReferencesNotPresentIn(contentObject.references || []);
-
- if(!contentObject.references) {
- return;
- }
-
- for(var reference of contentObject.references) {
- var referencedItem = this.findItem(reference.uuid);
- if(referencedItem) {
- item.addItemAsRelationship(referencedItem);
- referencedItem.addItemAsRelationship(item);
-
- if(markReferencesDirty) {
- referencedItem.setDirty(true);
- }
- } else {
- // console.log("Unable to find reference:", reference.uuid, "for item:", item);
- }
- }
- }
-
- addItemSyncObserver(id, type, callback) {
- this.itemSyncObservers.push({id: id, type: type, callback: callback});
- }
-
- removeItemSyncObserver(id) {
- _.remove(this.itemSyncObservers, _.find(this.itemSyncObservers, {id: id}));
- }
-
- addItemChangeObserver(id, content_types, callback) {
- this.itemChangeObservers.push({id: id, content_types: content_types, callback: callback});
- }
-
- removeItemChangeObserver(id) {
- _.remove(this.itemChangeObservers, _.find(this.itemChangeObservers, {id: id}));
- }
-
- get filteredNotes() {
- return Note.filterDummyNotes(this.notes);
- }
-
- getDirtyItems() {
- return this.items.filter((item) => {
- // An item that has an error decrypting can be synced only if it is being deleted.
- // Otherwise, we don't want to send corrupt content up to the server.
- return item.dirty == true && !item.dummy && (!item.errorDecrypting || item.deleted);
- })
- }
-
- clearDirtyItems(items) {
- for(var item of items) {
- item.setDirty(false);
- }
- }
-
- clearAllDirtyItems() {
- this.clearDirtyItems(this.getDirtyItems());
- }
-
setItemToBeDeleted(item) {
- item.deleted = true;
- if(!item.dummy) {
- item.setDirty(true);
- }
+ super.setItemToBeDeleted(item);
// remove from relevant array, but don't remove from all items.
// This way, it's removed from the display, but still synced via get dirty items
- if(item.content_type == "Tag") {
- _.pull(this.tags, item);
- } else if(item.content_type == "Note") {
- _.pull(this.notes, item);
- } else if(item.content_type == "Extension") {
- _.pull(this._extensions, item);
- }
-
- item.removeAndDirtyAllRelationships();
- }
-
- /* Used when changing encryption key */
- setAllItemsDirty(dontUpdateClientDates = true) {
- var relevantItems = this.allItems.filter(function(item){
- return _.includes(this.acceptableContentTypes, item.content_type);
- }.bind(this));
-
- for(var item of relevantItems) {
- item.setDirty(true, dontUpdateClientDates);
- }
+ this.removeItemFromRespectiveArray(item);
}
removeItemLocally(item, callback) {
- _.pull(this.items, item);
+ super.removeItemLocally(item, callback);
- item.isBeingRemovedLocally();
+ this.removeItemFromRespectiveArray(item);
- this.itemsPendingRemoval.push(item.uuid);
+ this.storageManager.deleteModel(item).then(callback);
+ }
+ removeItemFromRespectiveArray(item) {
if(item.content_type == "Tag") {
- _.pull(this.tags, item);
+ _.remove(this.tags, {uuid: item.uuid});
} else if(item.content_type == "Note") {
- _.pull(this.notes, item);
- } else if(item.content_type == "Extension") {
- _.pull(this._extensions, item);
+ _.remove(this.notes, {uuid: item.uuid});
+ } else if(item.content_type == "SN|Component") {
+ _.remove(this.components, {uuid: item.uuid});
}
-
- this.storageManager.deleteModel(item, callback);
}
- /*
- Relationships
- */
-
- createRelationshipBetweenItems(itemOne, itemTwo) {
- itemOne.addItemAsRelationship(itemTwo);
- itemTwo.addItemAsRelationship(itemOne);
-
- itemOne.setDirty(true);
- itemTwo.setDirty(true);
+ notesMatchingPredicate(predicate) {
+ let contentTypePredicate = new SFPredicate("content_type", "=", "Note");
+ return this.itemsMatchingPredicates([contentTypePredicate, predicate]);
}
-
- /*
- Archives
- */
-
- async getAllItemsJSONData(keys, authParams, protocolVersion, returnNullIfEmpty) {
- return Promise.all(this.allItems.map((item) => {
- var itemParams = new ItemParams(item, keys, protocolVersion);
- return itemParams.paramsForExportFile();
- })).then((items) => {
- if(returnNullIfEmpty && items.length == 0) {
- return null;
- }
-
- var data = {items: items}
-
- if(keys) {
- // auth params are only needed when encrypted with a standard file key
- data["auth_params"] = authParams;
- }
-
- return JSON.stringify(data, null, 2 /* pretty print */);
- })
-
- }
-
-
/*
Misc
*/
@@ -492,6 +122,7 @@ class ModelManager {
return {
"Note" : "note",
"Tag" : "tag",
+ "SN|SmartTag": "smart tag",
"Extension" : "action-based extension",
"SN|Component" : "component",
"SN|Editor" : "editor",
diff --git a/app/assets/javascripts/app/services/nativeExtManager.js b/app/assets/javascripts/app/services/nativeExtManager.js
index 801ac21d3..c44561488 100644
--- a/app/assets/javascripts/app/services/nativeExtManager.js
+++ b/app/assets/javascripts/app/services/nativeExtManager.js
@@ -21,7 +21,10 @@ class NativeExtManager {
resolveExtensionsManager() {
- this.singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: this.extensionsManagerIdentifier}}, (resolvedSingleton) => {
+ let contentTypePredicate = new SFPredicate("content_type", "=", "SN|Component");
+ let packagePredicate = new SFPredicate("package_info.identifier", "=", this.extensionsManagerIdentifier);
+
+ this.singletonManager.registerSingleton([contentTypePredicate, packagePredicate], (resolvedSingleton) => {
// Resolved Singleton
this.systemExtensions.push(resolvedSingleton.uuid);
@@ -40,7 +43,7 @@ class NativeExtManager {
if(needsSync) {
resolvedSingleton.setDirty(true);
- this.syncManager.sync("resolveExtensionsManager");
+ this.syncManager.sync();
}
}, (valueCallback) => {
// Safe to create. Create and return object.
@@ -81,7 +84,7 @@ class NativeExtManager {
this.modelManager.addItem(component);
component.setDirty(true);
- this.syncManager.sync("resolveExtensionsManager createNew");
+ this.syncManager.sync();
this.systemExtensions.push(component.uuid);
@@ -91,7 +94,10 @@ class NativeExtManager {
resolveBatchManager() {
- this.singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: this.batchManagerIdentifier}}, (resolvedSingleton) => {
+ let contentTypePredicate = new SFPredicate("content_type", "=", "SN|Component");
+ let packagePredicate = new SFPredicate("package_info.identifier", "=", this.batchManagerIdentifier);
+
+ this.singletonManager.registerSingleton([contentTypePredicate, packagePredicate], (resolvedSingleton) => {
// Resolved Singleton
this.systemExtensions.push(resolvedSingleton.uuid);
@@ -110,7 +116,7 @@ class NativeExtManager {
if(needsSync) {
resolvedSingleton.setDirty(true);
- this.syncManager.sync("resolveExtensionsManager");
+ this.syncManager.sync();
}
}, (valueCallback) => {
// Safe to create. Create and return object.
@@ -151,7 +157,7 @@ class NativeExtManager {
this.modelManager.addItem(component);
component.setDirty(true);
- this.syncManager.sync("resolveBatchManager createNew");
+ this.syncManager.sync();
this.systemExtensions.push(component.uuid);
diff --git a/app/assets/javascripts/app/services/passcodeManager.js b/app/assets/javascripts/app/services/passcodeManager.js
index b30711ecc..d70ffd2f1 100644
--- a/app/assets/javascripts/app/services/passcodeManager.js
+++ b/app/assets/javascripts/app/services/passcodeManager.js
@@ -7,7 +7,7 @@ angular.module('app')
function PasscodeManager($rootScope, $timeout, modelManager, dbManager, authManager, storageManager) {
- this._hasPasscode = storageManager.getItem("offlineParams", StorageManager.Fixed) != null;
+ this._hasPasscode = storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
this._locked = this._hasPasscode;
this.isLocked = function() {
@@ -23,11 +23,7 @@ angular.module('app')
}
this.passcodeAuthParams = function() {
- return JSON.parse(storageManager.getItem("offlineParams", StorageManager.Fixed));
- }
-
- this.protocolVersion = function() {
- return this._authParams && this._authParams.version;
+ return JSON.parse(storageManager.getItemSync("offlineParams", StorageManager.Fixed));
}
this.unlock = function(passcode, callback) {
diff --git a/app/assets/javascripts/app/services/sessionHistory.js b/app/assets/javascripts/app/services/sessionHistory.js
new file mode 100644
index 000000000..d64dec37f
--- /dev/null
+++ b/app/assets/javascripts/app/services/sessionHistory.js
@@ -0,0 +1,25 @@
+class SessionHistory extends SFSessionHistoryManager {
+
+ constructor(modelManager, storageManager, authManager, passcodeManager, $timeout) {
+
+ SFItemHistory.HistoryEntryClassMapping = {
+ "Note" : NoteHistoryEntry
+ }
+
+ var keyRequestHandler = async () => {
+ let offline = authManager.offline();
+ let auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams();
+ let keys = offline ? passcodeManager.keys() : await authManager.keys();
+ return {
+ keys: keys,
+ offline: offline,
+ auth_params: auth_params
+ }
+ }
+
+ var contentTypes = ["Note"];
+ super(modelManager, storageManager, keyRequestHandler, contentTypes, $timeout);
+ }
+}
+
+angular.module('app').service('sessionHistory', SessionHistory);
diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js
index 0218846a0..dd1624072 100644
--- a/app/assets/javascripts/app/services/singletonManager.js
+++ b/app/assets/javascripts/app/services/singletonManager.js
@@ -40,14 +40,14 @@ class SingletonManager {
})
}
- registerSingleton(predicate, resolveCallback, createBlock) {
+ registerSingleton(predicates, resolveCallback, createBlock) {
/*
predicate: a key/value pair that specifies properties that should match in order for an item to be considered a predicate
resolveCallback: called when one or more items are deleted and a new item becomes the reigning singleton
createBlock: called when a sync is complete and no items are found. The createBlock should create the item and return it.
*/
this.singletonHandlers.push({
- predicate: predicate,
+ predicates: predicates,
resolutionCallback: resolveCallback,
createBlock: createBlock
});
@@ -58,20 +58,20 @@ class SingletonManager {
savedItems = savedItems || [];
for(let singletonHandler of this.singletonHandlers) {
- var predicate = singletonHandler.predicate;
- let retrievedSingletonItems = this.filterItemsWithPredicate(retrievedItems, predicate);
+ var predicates = singletonHandler.predicates;
+ let retrievedSingletonItems = this.modelManager.filterItemsWithPredicates(retrievedItems, predicates);
// We only want to consider saved items count to see if it's more than 0, and do nothing else with it.
// This way we know there was some action and things need to be resolved. The saved items will come up
// in filterItemsWithPredicate(this.modelManager.allItems) and be deleted anyway
- let savedSingletonItemsCount = this.filterItemsWithPredicate(savedItems, predicate).length;
+ let savedSingletonItemsCount = this.modelManager.filterItemsWithPredicates(savedItems, predicates).length;
if(retrievedSingletonItems.length > 0 || savedSingletonItemsCount > 0) {
/*
Check local inventory and make sure only 1 similar item exists. If more than 1, delete newest
Note that this local inventory will also contain whatever is in retrievedItems.
*/
- var allExtantItemsMatchingPredicate = this.filterItemsWithPredicate(this.modelManager.allItems, predicate);
+ var allExtantItemsMatchingPredicate = this.modelManager.itemsMatchingPredicates(predicates);
/*
Delete all but the earliest created
@@ -92,7 +92,7 @@ class SingletonManager {
this.modelManager.setItemToBeDeleted(d);
}
- this.$rootScope.sync("resolveSingletons");
+ this.$rootScope.sync();
// Send remaining item to callback
singletonHandler.singleton = winningItem;
@@ -122,35 +122,6 @@ class SingletonManager {
}
}
}
-
- filterItemsWithPredicate(items, predicate) {
- return items.filter((candidate) => {
- 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(!candidateValue) {
- // predicateValue is 'object' but candidateValue is null
- return false;
- }
-
- if(!this.itemSatisfiesPredicate(candidateValue, predicateValue)) {
- return false;
- }
- }
- else if(candidateValue != predicateValue) {
- return false;
- }
- }
- return true;
- }
-
}
angular.module('app').service('singletonManager', SingletonManager);
diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js
index c82500c68..908490747 100644
--- a/app/assets/javascripts/app/services/storageManager.js
+++ b/app/assets/javascripts/app/services/storageManager.js
@@ -7,6 +7,10 @@ class MemoryStorage {
return this.memory[key] || null;
}
+ getItemSync(key) {
+ return this.getItem(key);
+ }
+
get length() {
return Object.keys(this.memory).length;
}
@@ -32,9 +36,10 @@ class MemoryStorage {
}
}
-class StorageManager {
+class StorageManager extends SFStorageManager {
constructor(dbManager) {
+ super();
this.dbManager = dbManager;
}
@@ -100,7 +105,7 @@ class StorageManager {
}
}
- setItem(key, value, vaultKey) {
+ async setItem(key, value, vaultKey) {
var storage = this.getVault(vaultKey);
storage.setItem(key, value);
@@ -109,25 +114,21 @@ class StorageManager {
}
}
- getItem(key, vault) {
+ async getItem(key, vault) {
+ return this.getItemSync(key, vault);
+ }
+
+ getItemSync(key, vault) {
var storage = this.getVault(vault);
return storage.getItem(key);
}
- setBooleanValue(key, value, vault) {
- this.setItem(key, JSON.stringify(value), vault);
- }
-
- getBooleanValue(key, vault) {
- return JSON.parse(this.getItem(key, vault));
- }
-
- removeItem(key, vault) {
+ async removeItem(key, vault) {
var storage = this.getVault(vault);
- storage.removeItem(key);
+ return storage.removeItem(key);
}
- clear() {
+ async clear() {
this.memoryStorage.clear();
localStorage.clear();
}
@@ -148,29 +149,29 @@ class StorageManager {
}
writeEncryptedStorageToDisk() {
- var encryptedStorage = new EncryptedStorage();
+ var encryptedStorage = new SNEncryptedStorage();
// Copy over totality of current storage
- encryptedStorage.storage = this.storageAsHash();
+ encryptedStorage.content.storage = this.storageAsHash();
// Save new encrypted storage in Fixed storage
- var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version);
+ var params = new SFItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams);
params.paramsForSync().then((syncParams) => {
this.setItem("encryptedStorage", JSON.stringify(syncParams), StorageManager.Fixed);
})
}
async decryptStorage() {
- var stored = JSON.parse(this.getItem("encryptedStorage", StorageManager.Fixed));
+ var stored = JSON.parse(this.getItemSync("encryptedStorage", StorageManager.Fixed));
await SFJS.itemTransformer.decryptItem(stored, this.encryptedStorageKeys);
- var encryptedStorage = new EncryptedStorage(stored);
+ var encryptedStorage = new SNEncryptedStorage(stored);
- for(var key of Object.keys(encryptedStorage.storage)) {
+ for(var key of Object.keys(encryptedStorage.content.storage)) {
this.setItem(key, encryptedStorage.storage[key]);
}
}
hasPasscode() {
- return this.getItem("encryptedStorage", StorageManager.Fixed) !== null;
+ return this.getItemSync("encryptedStorage", StorageManager.Fixed) !== null;
}
@@ -196,36 +197,44 @@ class StorageManager {
this.modelStorageMode = mode;
}
- getAllModels(callback) {
- if(this.modelStorageMode == StorageManager.Fixed) {
- this.dbManager.getAllModels(callback);
- } else {
- callback && callback();
- }
+ async getAllModels() {
+ return new Promise((resolve, reject) => {
+ if(this.modelStorageMode == StorageManager.Fixed) {
+ this.dbManager.getAllModels(resolve);
+ } else {
+ resolve();
+ }
+ })
}
- saveModel(item) {
- this.saveModels([item]);
+ async saveModel(item) {
+ return this.saveModels([item]);
}
- saveModels(items, onsuccess, onerror) {
- if(this.modelStorageMode == StorageManager.Fixed) {
- this.dbManager.saveModels(items, onsuccess, onerror);
- } else {
- onsuccess && onsuccess();
- }
+ async saveModels(items, onsuccess, onerror) {
+ return new Promise((resolve, reject) => {
+ if(this.modelStorageMode == StorageManager.Fixed) {
+ this.dbManager.saveModels(items, resolve, reject);
+ } else {
+ resolve();
+ }
+ });
}
- deleteModel(item, callback) {
- if(this.modelStorageMode == StorageManager.Fixed) {
- this.dbManager.deleteModel(item, callback);
- } else {
- callback && callback();
- }
+ async deleteModel(item) {
+ return new Promise((resolve, reject) => {
+ if(this.modelStorageMode == StorageManager.Fixed) {
+ this.dbManager.deleteModel(item, resolve);
+ } else {
+ resolve();
+ }
+ });
}
- clearAllModels(callback) {
- this.dbManager.clearAllModels(callback);
+ async clearAllModels() {
+ return new Promise((resolve, reject) => {
+ this.dbManager.clearAllModels(resolve);
+ });
}
}
diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js
index 031d4169d..eaac90668 100644
--- a/app/assets/javascripts/app/services/syncManager.js
+++ b/app/assets/javascripts/app/services/syncManager.js
@@ -1,583 +1,7 @@
-class SyncManager {
+class SyncManager extends SFSyncManager {
- constructor($rootScope, modelManager, authManager, dbManager, httpManager, $interval, $timeout, storageManager, passcodeManager) {
- this.$rootScope = $rootScope;
- this.httpManager = httpManager;
- this.modelManager = modelManager;
- this.authManager = authManager;
- this.dbManager = dbManager;
- this.$interval = $interval;
- this.$timeout = $timeout;
- this.storageManager = storageManager;
- this.passcodeManager = passcodeManager;
- this.syncStatus = {};
- this.syncStatusObservers = [];
- }
-
- get serverURL() {
- return this.storageManager.getItem("server") || window._default_sf_server;
- }
-
- get masterKey() {
- return this.storageManager.getItem("mk");
- }
-
- registerSyncStatusObserver(callback) {
- var observer = {key: new Date(), callback: callback};
- this.syncStatusObservers.push(observer);
- return observer;
- }
-
- removeSyncStatusObserver(observer) {
- _.pull(this.syncStatusObservers, observer);
- }
-
- syncStatusDidChange() {
- this.syncStatusObservers.forEach((observer) => {
- observer.callback(this.syncStatus);
- })
- }
-
- writeItemsToLocalStorage(items, offlineOnly, callback) {
- if(items.length == 0) {
- callback && callback();
- return;
- }
-
- var version = this.authManager.offline() ? this.passcodeManager.protocolVersion() : this.authManager.protocolVersion();
- var keys = this.authManager.offline() ? this.passcodeManager.keys() : this.authManager.keys();
-
- Promise.all(items.map(async (item) => {
- var itemParams = new ItemParams(item, keys, version);
- itemParams = await itemParams.paramsForLocalStorage();
- if(offlineOnly) {
- delete itemParams.dirty;
- }
- return itemParams;
- })).then((params) => {
- this.storageManager.saveModels(params, () => {
- // on success
- if(this.syncStatus.localError) {
- this.syncStatus.localError = null;
- this.syncStatusDidChange();
- }
- callback && callback();
- }, (error) => {
- // on error
- this.syncStatus.localError = error;
- this.syncStatusDidChange();
- });
- })
- }
-
- async loadLocalItems(callback) {
- this.storageManager.getAllModels((items) => {
- // break it up into chunks to make interface more responsive for large item counts
- let total = items.length;
- let iteration = 50;
- var current = 0;
- var processed = [];
-
- var completion = () => {
- Item.sortItemsByDate(processed);
- callback(processed);
- }
-
- var decryptNext = async () => {
- var subitems = items.slice(current, current + iteration);
- var processedSubitems = await this.handleItemsResponse(subitems, null, ModelManager.MappingSourceLocalRetrieved);
- processed.push(processedSubitems);
-
- current += subitems.length;
-
- if(current < total) {
- this.$timeout(() => { decryptNext(); });
- } else {
- completion();
- }
- }
-
- decryptNext();
- })
- }
-
- syncOffline(items, callback) {
- // Update all items updated_at to now
- for(var item of items) {
- item.updated_at = new Date();
- }
- this.writeItemsToLocalStorage(items, true, (responseItems) => {
- // delete anything needing to be deleted
- for(var item of items) {
- if(item.deleted) {
- this.modelManager.removeItemLocally(item);
- }
- }
-
- this.$rootScope.$broadcast("sync:completed", {});
-
- // Required in order for modelManager to notify sync observers
- this.modelManager.didSyncModelsOffline(items);
-
- if(callback) {
- callback({success: true});
- }
- })
-
- }
-
- /*
- In the case of signing in and merging local data, we alternative UUIDs
- to avoid overwriting data a user may retrieve that has the same UUID.
- Alternating here forces us to to create duplicates of the items instead.
- */
- markAllItemsDirtyAndSaveOffline(callback, alternateUUIDs) {
-
- // use a copy, as alternating uuid will affect array
- var originalItems = this.modelManager.allItems.filter((item) => {return !item.errorDecrypting}).slice();
-
- var block = () => {
- var allItems = this.modelManager.allItems;
- for(var item of allItems) {
- item.setDirty(true);
- }
- this.writeItemsToLocalStorage(allItems, false, callback);
- }
-
- if(alternateUUIDs) {
- var index = 0;
-
- let alternateNextItem = () => {
- if(index >= originalItems.length) {
- // We don't use originalItems as alternating UUID will have deleted them.
- block();
- return;
- }
-
- var item = originalItems[index];
- index++;
-
- // alternateUUIDForItem last param is a boolean that controls whether the original item
- // should be removed locally after new item is created. We set this to true, since during sign in,
- // all item ids are alternated, and we only want one final copy of the entire data set.
- // Passing false can be desired sometimes, when for example the app has signed out the user,
- // but for some reason retained their data (This happens in Firefox when using private mode).
- // In this case, we should pass false so that both copies are kept. However, it's difficult to
- // detect when the app has entered this state. We will just use true to remove original items for now.
- this.modelManager.alternateUUIDForItem(item, alternateNextItem, true);
- }
-
- alternateNextItem();
- } else {
- block();
- }
- }
-
- get syncURL() {
- return this.serverURL + "/items/sync";
- }
-
- set syncToken(token) {
- this._syncToken = token;
- this.storageManager.setItem("syncToken", token);
- }
-
- get syncToken() {
- if(!this._syncToken) {
- this._syncToken = this.storageManager.getItem("syncToken");
- }
- return this._syncToken;
- }
-
- set cursorToken(token) {
- this._cursorToken = token;
- if(token) {
- this.storageManager.setItem("cursorToken", token);
- } else {
- this.storageManager.removeItem("cursorToken");
- }
- }
-
- get cursorToken() {
- if(!this._cursorToken) {
- this._cursorToken = this.storageManager.getItem("cursorToken");
- }
- return this._cursorToken;
- }
-
- get queuedCallbacks() {
- if(!this._queuedCallbacks) {
- this._queuedCallbacks = [];
- }
- return this._queuedCallbacks;
- }
-
- clearQueuedCallbacks() {
- this._queuedCallbacks = [];
- }
-
- callQueuedCallbacksAndCurrent(currentCallback, response) {
- var allCallbacks = this.queuedCallbacks;
- if(currentCallback) {
- allCallbacks.push(currentCallback);
- }
- if(allCallbacks.length) {
- for(var eachCallback of allCallbacks) {
- eachCallback(response);
- }
- this.clearQueuedCallbacks();
- }
- }
-
- beginCheckingIfSyncIsTakingTooLong() {
- this.syncStatus.checker = this.$interval(function(){
- // check to see if the ongoing sync is taking too long, alert the user
- var secondsPassed = (new Date() - this.syncStatus.syncStart) / 1000;
- var warningThreshold = 5.0; // seconds
- if(secondsPassed > warningThreshold) {
- this.$rootScope.$broadcast("sync:taking-too-long");
- this.stopCheckingIfSyncIsTakingTooLong();
- }
- }.bind(this), 500)
- }
-
- stopCheckingIfSyncIsTakingTooLong() {
- this.$interval.cancel(this.syncStatus.checker);
- }
-
- lockSyncing() {
- this.syncLocked = true;
- }
-
- unlockSyncing() {
- this.syncLocked = false;
- }
-
- async sync(callback, options = {}, source) {
-
- if(this.syncLocked) {
- console.log("Sync Locked, Returning;");
- return;
- }
-
- if(!options) options = {};
-
- if(typeof callback == 'string') {
- // is source string, used to avoid filling parameters on call
- source = callback;
- callback = null;
- }
-
- // console.log("Syncing from", source);
-
- var allDirtyItems = this.modelManager.getDirtyItems();
-
- // When a user hits the physical refresh button, we want to force refresh, in case
- // the sync engine is stuck in some inProgress loop.
- if(this.syncStatus.syncOpInProgress && !options.force) {
- this.repeatOnCompletion = true;
- if(callback) {
- this.queuedCallbacks.push(callback);
- }
-
- // write to local storage nonetheless, since some users may see several second delay in server response.
- // if they close the browser before the ongoing sync request completes, local changes will be lost if we dont save here
- this.writeItemsToLocalStorage(allDirtyItems, false, null);
-
- console.log("Sync op in progress; returning.");
- return;
- }
-
- // we want to write all dirty items to disk only if the user is offline, or if the sync op fails
- // if the sync op succeeds, these items will be written to disk by handling the "saved_items" response from the server
- if(this.authManager.offline()) {
- this.syncOffline(allDirtyItems, callback);
- this.modelManager.clearDirtyItems(allDirtyItems);
- return;
- }
-
- var isContinuationSync = this.syncStatus.needsMoreSync;
-
- this.syncStatus.syncOpInProgress = true;
- this.syncStatus.syncStart = new Date();
- this.beginCheckingIfSyncIsTakingTooLong();
-
- let submitLimit = 100;
- var subItems = allDirtyItems.slice(0, submitLimit);
- if(subItems.length < allDirtyItems.length) {
- // more items left to be synced, repeat
- this.syncStatus.needsMoreSync = true;
- } else {
- this.syncStatus.needsMoreSync = false;
- }
-
- if(!isContinuationSync) {
- this.syncStatus.total = allDirtyItems.length;
- this.syncStatus.current = 0;
- }
-
- // If items are marked as dirty during a long running sync request, total isn't updated
- // This happens mostly in the case of large imports and sync conflicts where duplicated items are created
- if(this.syncStatus.current > this.syncStatus.total) {
- this.syncStatus.total = this.syncStatus.current;
- }
-
- // when doing a sync request that returns items greater than the limit, and thus subsequent syncs are required,
- // we want to keep track of all retreived items, then save to local storage only once all items have been retrieved,
- // so that relationships remain intact
- if(!this.allRetreivedItems) {
- this.allRetreivedItems = [];
- }
-
- // We also want to do this for savedItems
- if(!this.allSavedItems) {
- this.allSavedItems = [];
- }
-
- var version = this.authManager.protocolVersion();
- var keys = this.authManager.keys();
-
- var params = {};
- params.limit = 150;
-
- await Promise.all(subItems.map((item) => {
- var itemParams = new ItemParams(item, keys, version);
- itemParams.additionalFields = options.additionalFields;
- return itemParams.paramsForSync();
- })).then((itemsParams) => {
- params.items = itemsParams;
- })
-
- for(var item of subItems) {
- // Reset dirty counter to 0, since we're about to sync it.
- // This means anyone marking the item as dirty after this will cause it so sync again and not be cleared on sync completion.
- item.dirtyCount = 0;
- }
-
- params.sync_token = this.syncToken;
- params.cursor_token = this.cursorToken;
-
- var onSyncCompletion = function(response) {
- this.stopCheckingIfSyncIsTakingTooLong();
- }.bind(this);
-
- var onSyncSuccess = async function(response) {
- // Check to make sure any subItem hasn't been marked as dirty again while a sync was ongoing
- var itemsToClearAsDirty = [];
- for(var item of subItems) {
- if(item.dirtyCount == 0) {
- // Safe to clear as dirty
- itemsToClearAsDirty.push(item);
- }
- }
- this.modelManager.clearDirtyItems(itemsToClearAsDirty);
- this.syncStatus.error = null;
-
- this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
-
- // Filter retrieved_items to remove any items that may be in saved_items for this complete sync operation
- // When signing in, and a user requires many round trips to complete entire retrieval of data, an item may be saved
- // on the first trip, then on subsequent trips using cursor_token, this same item may be returned, since it's date is
- // greater than cursor_token. We keep track of all saved items in whole sync operation with this.allSavedItems
- // We need this because singletonManager looks at retrievedItems as higher precendence than savedItems, but if it comes in both
- // then that's problematic.
- let allSavedUUIDs = this.allSavedItems.map((item) => {return item.uuid});
- response.retrieved_items = response.retrieved_items.filter((candidate) => {return !allSavedUUIDs.includes(candidate.uuid)});
-
- // Map retrieved items to local data
- // Note that deleted items will not be returned
- var retrieved = await this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved);
-
- // Append items to master list of retrieved items for this ongoing sync operation
- this.allRetreivedItems = this.allRetreivedItems.concat(retrieved);
-
- // Merge only metadata for saved items
- // we write saved items to disk now because it clears their dirty status then saves
- // if we saved items before completion, we had have to save them as dirty and save them again on success as clean
- var omitFields = ["content", "auth_hash"];
-
- // Map saved items to local data
- var saved = await this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved);
-
- // Append items to master list of saved items for this ongoing sync operation
- this.allSavedItems = this.allSavedItems.concat(saved);
-
- // Create copies of items or alternate their uuids if neccessary
- var unsaved = response.unsaved;
- this.handleUnsavedItemsResponse(unsaved)
-
- this.writeItemsToLocalStorage(saved, false, null);
-
- this.syncStatus.syncOpInProgress = false;
- this.syncStatus.current += subItems.length;
-
- // set the sync token at the end, so that if any errors happen above, you can resync
- this.syncToken = response.sync_token;
- this.cursorToken = response.cursor_token;
-
- onSyncCompletion(response);
-
- if(this.cursorToken || this.syncStatus.needsMoreSync) {
- setTimeout(function () {
- this.sync(callback, options, "onSyncSuccess cursorToken || needsMoreSync");
- }.bind(this), 10); // wait 10ms to allow UI to update
- } else if(this.repeatOnCompletion) {
- this.repeatOnCompletion = false;
- setTimeout(function () {
- this.sync(callback, options, "onSyncSuccess repeatOnCompletion");
- }.bind(this), 10); // wait 10ms to allow UI to update
- } else {
- this.writeItemsToLocalStorage(this.allRetreivedItems, false, null);
-
- // The number of changed items that constitute a major change
- // This is used by the desktop app to create backups
- let majorDataChangeThreshold = 10;
- if(
- this.allRetreivedItems.length >= majorDataChangeThreshold ||
- saved.length >= majorDataChangeThreshold ||
- unsaved.length >= majorDataChangeThreshold
- ) {
- this.$rootScope.$broadcast("major-data-change");
- }
-
- this.callQueuedCallbacksAndCurrent(callback, response);
- this.$rootScope.$broadcast("sync:completed", {retrievedItems: this.allRetreivedItems, savedItems: this.allSavedItems});
-
- this.allRetreivedItems = [];
- this.allSavedItems = [];
- }
- }.bind(this);
-
- try {
- this.httpManager.postAbsolute(this.syncURL, params, function(response){
-
- try {
- onSyncSuccess(response);
- } catch(e) {
- console.log("Caught sync success exception:", e);
- }
-
- }.bind(this), function(response, statusCode){
- if(statusCode == 401) {
- alert("Your session has expired. New changes will not be pulled in. Please sign out and sign back in to refresh your session.");
- }
- console.log("Sync error: ", response);
- var error = response ? response.error : {message: "Could not connect to server."};
-
- this.syncStatus.syncOpInProgress = false;
- this.syncStatus.error = error;
- this.writeItemsToLocalStorage(allDirtyItems, false, null);
-
- onSyncCompletion(response);
-
- this.$rootScope.$broadcast("sync:error", error);
-
- this.callQueuedCallbacksAndCurrent(callback, {error: "Sync error"});
- }.bind(this));
- }
- catch(e) {
- console.log("Sync exception caught:", e);
- }
- }
-
- async handleItemsResponse(responseItems, omitFields, source) {
- var keys = this.authManager.keys() || this.passcodeManager.keys();
- await SFJS.itemTransformer.decryptMultipleItems(responseItems, keys);
- var items = this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields, source);
-
- // During the decryption process, items may be marked as "errorDecrypting". If so, we want to be sure
- // to persist this new state by writing these items back to local storage. When an item's "errorDecrypting"
- // flag is changed, its "errorDecryptingValueChanged" flag will be set, so we can find these items by filtering (then unsetting) below:
- var itemsWithErrorStatusChange = items.filter((item) => {
- var valueChanged = item.errorDecryptingValueChanged;
- // unset after consuming value
- item.errorDecryptingValueChanged = false;
- return valueChanged;
- });
- if(itemsWithErrorStatusChange.length > 0) {
- this.writeItemsToLocalStorage(itemsWithErrorStatusChange, false, null);
- }
-
- return items;
- }
-
- refreshErroredItems() {
- var erroredItems = this.modelManager.allItems.filter((item) => {return item.errorDecrypting == true});
- if(erroredItems.length > 0) {
- this.handleItemsResponse(erroredItems, null, ModelManager.MappingSourceLocalRetrieved);
- }
- }
-
- async handleUnsavedItemsResponse(unsaved) {
- if(unsaved.length == 0) {
- return;
- }
-
- console.log("Handle unsaved", unsaved);
-
- var i = 0;
- var handleNext = async () => {
- if(i >= unsaved.length) {
- // Handled all items
- this.sync(null, {additionalFields: ["created_at", "updated_at"]});
- return;
- }
-
- var mapping = unsaved[i];
- var itemResponse = mapping.item;
- await SFJS.itemTransformer.decryptMultipleItems([itemResponse], this.authManager.keys());
- var item = this.modelManager.findItem(itemResponse.uuid);
-
- if(!item) {
- // Could be deleted
- return;
- }
-
- var error = mapping.error;
-
- if(error.tag === "uuid_conflict") {
- // UUID conflicts can occur if a user attempts to
- // import an old data archive with uuids from the old account into a new account
- this.modelManager.alternateUUIDForItem(item, () => {
- i++;
- handleNext();
- }, true);
- }
-
- else if(error.tag === "sync_conflict") {
- // Create a new item with the same contents of this item if the contents differ
-
- // We want a new uuid for the new item. Note that this won't neccessarily adjust references.
- itemResponse.uuid = null;
-
- var dup = this.modelManager.createDuplicateItem(itemResponse);
- if(!itemResponse.deleted && !item.isItemContentEqualWith(dup)) {
- this.modelManager.addItem(dup);
- dup.conflict_of = item.uuid;
- dup.setDirty(true);
- }
-
- i++;
- handleNext();
- }
- }
-
- handleNext();
- }
-
- clearSyncToken() {
- this.storageManager.removeItem("syncToken");
- }
-
- destroyLocalData(callback) {
- this.storageManager.clear();
- this.storageManager.clearAllModels(function(){
- if(callback) {
- this.$timeout(function(){
- callback();
- })
- }
- }.bind(this));
+ constructor(modelManager, storageManager, httpManager, $timeout, $interval) {
+ super(modelManager, storageManager, httpManager, $timeout, $interval);
}
}
diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss
index 533372a11..e07ea1e77 100644
--- a/app/assets/stylesheets/app/_editor.scss
+++ b/app/assets/stylesheets/app/_editor.scss
@@ -62,7 +62,7 @@ $heading-height: 75px;
text-align: right;
color: rgba(black, 0.23);
- .error {
+ &.error, .error {
color: #f6a200;
}
}
diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss
index e9d6c6fb0..ffe6415f4 100644
--- a/app/assets/stylesheets/app/_modals.scss
+++ b/app/assets/stylesheets/app/_modals.scss
@@ -16,6 +16,13 @@
font-size: 16px;
}
+#item-preview-modal {
+ > .content {
+ width: 800px;
+ height: 500px;
+ }
+}
+
.panel {
background-color: white;
}
@@ -99,6 +106,7 @@
.component-view {
flex-grow: 1;
display: flex;
+ flex-direction: column;
// not sure why we need this. Removed because it creates unncessary scroll bars. Tested on folders extension, creates horizontal scrollbar at bottom on windows
// overflow: auto;
diff --git a/app/assets/stylesheets/app/_notes.scss b/app/assets/stylesheets/app/_notes.scss
index c4e05442c..7e25a1f9c 100644
--- a/app/assets/stylesheets/app/_notes.scss
+++ b/app/assets/stylesheets/app/_notes.scss
@@ -87,10 +87,13 @@
height: inherit;
// Autohide scrollbar on Windows.
- // Unfortunately must affect every platform since no way to hide just for Windows.
- overflow-y: hidden;
- &:hover {
- overflow-y: scroll;
+ @at-root {
+ .windows-web &, .windows-desktop & {
+ overflow-y: hidden;
+ &:hover {
+ overflow-y: scroll;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/app/_stylekit-sub.scss b/app/assets/stylesheets/app/_stylekit-sub.scss
index cd183fcc4..b0a9285e1 100644
--- a/app/assets/stylesheets/app/_stylekit-sub.scss
+++ b/app/assets/stylesheets/app/_stylekit-sub.scss
@@ -18,6 +18,11 @@
}
+ .app-bar {
+ &.no-top-edge {
+ border-top: 0;
+ }
+ }
}
@@ -48,3 +53,9 @@
color: $blue-color;
}
}
+
+#session-history-menu {
+ .menu-panel .row .sublabel.opaque {
+ opacity: 1.0
+ }
+}
diff --git a/app/assets/templates/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml
index 9573cc38c..ed18f5ab4 100644
--- a/app/assets/templates/directives/account-menu.html.haml
+++ b/app/assets/templates/directives/account-menu.html.haml
@@ -159,8 +159,8 @@
%button.button.info{"type" => "submit"}
.label Decrypt & Import
%p
- Importing from backup will overwrite existing notes with matching note from backup. Existing notes not found in the backup will remain as-is and won't be overwritten.
- %p If you'd like to import only a selection of notes instead of the whole file, please use the Batch Manager extension instead.
+ Importing from backup will not overwrite existing data, but instead create a duplicate of any differing data.
+ %p If you'd like to import only a selection of items instead of the whole file, please use the Batch Manager extension.
.panel-row
.spinner.small.info{"ng-if" => "importData.loading"}
.footer
diff --git a/app/assets/templates/directives/actions-menu.html.haml b/app/assets/templates/directives/actions-menu.html.haml
index 9fe6397b1..3596b7e92 100644
--- a/app/assets/templates/directives/actions-menu.html.haml
+++ b/app/assets/templates/directives/actions-menu.html.haml
@@ -19,14 +19,4 @@
%strong {{action.access_type}}
access to this note.
-
-.modal.medium-text.medium{"ng-if" => "renderData.showRenderModal", "ng-click" => "$event.stopPropagation();"}
- .content
- .sn-component
- .panel
- .header
- %h1.title Preview
- %a.close-button.info{"ng-click" => "renderData.showRenderModal = false; $event.stopPropagation();"} Close
- .content.selectable
- %h2 {{renderData.title}}
- %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{renderData.text}}
+ %menu-row{"ng-if" => "extension.actionsWithContextForItem(item).length == 0", "label" => "'No Actions Available'", "faded" => "true"}
diff --git a/app/assets/templates/directives/component-view.html.haml b/app/assets/templates/directives/component-view.html.haml
index 69fc9f54d..55470a8a8 100644
--- a/app/assets/templates/directives/component-view.html.haml
+++ b/app/assets/templates/directives/component-view.html.haml
@@ -1,3 +1,12 @@
+.sn-component{"ng-if" => "issueLoading"}
+ .app-bar.no-edges.no-top-edge
+ .left
+ .item
+ .label.warning There was an issue loading {{component.name}}.
+ .right
+ .item{"ng-click" => "reloadComponent()"}
+ .label Reload
+
.sn-component{"ng-if" => "error == 'expired'"}
.panel.static
.content
diff --git a/app/assets/templates/directives/revision-preview-modal.html.haml b/app/assets/templates/directives/revision-preview-modal.html.haml
new file mode 100644
index 000000000..df37fb182
--- /dev/null
+++ b/app/assets/templates/directives/revision-preview-modal.html.haml
@@ -0,0 +1,13 @@
+.modal.medium#item-preview-modal
+ .content
+ .sn-component
+ .panel
+ .header
+ %h1.title Preview
+ .horizontal-group
+ %a.close-button.info{"ng-click" => "restore(false)"} Restore
+ %a.close-button.info{"ng-click" => "restore(true)"} Restore as copy
+ %a.close-button.info{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
+ .content.selectable
+ %h2 {{content.title}}
+ %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{content.text}}
diff --git a/app/assets/templates/directives/session-history-menu.html.haml b/app/assets/templates/directives/session-history-menu.html.haml
new file mode 100644
index 000000000..244ee9ba6
--- /dev/null
+++ b/app/assets/templates/directives/session-history-menu.html.haml
@@ -0,0 +1,23 @@
+.sn-component#session-history-menu
+ .menu-panel.dropdown-menu
+ .header
+ .column
+ %h4.title {{history.entries.length || 'No'}} revisions
+ %h4{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"}
+ %a Options
+
+ %div{"ng-if" => "showOptions"}
+ %menu-row{"label" => "'Clear note local history'", "ng-click" => "clearItemHistory(); $event.stopPropagation();"}
+ %menu-row{"label" => "'Clear all local history'", "ng-click" => "clearAllHistory(); $event.stopPropagation();"}
+ %menu-row{"label" => "(autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'", "ng-click" => "toggleAutoOptimize(); $event.stopPropagation();"}
+ .sublabel
+ Automatically cleans up small revisions to conserve space.
+ %menu-row{"label" => "(diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'", "ng-click" => "toggleDiskSaving(); $event.stopPropagation();"}
+ .sublabel
+ Saving to disk may increase app loading time and memory footprint.
+
+ %menu-row{"ng-repeat" => "revision in history.entries",
+ "ng-click" => "openRevision(revision); $event.stopPropagation();",
+ "label" => "revision.previewTitle()"}
+ .sublabel.opaque{"ng-class" => "classForRevision(revision)"}
+ {{revision.previewSubTitle()}}
diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml
index 512134f93..5a822c08a 100644
--- a/app/assets/templates/editor.html.haml
+++ b/app/assets/templates/editor.html.haml
@@ -50,6 +50,10 @@
.label Actions
%actions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
+ .item{"ng-click" => "ctrl.showSessionHistory = !ctrl.showSessionHistory; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "click-outside" => "ctrl.showSessionHistory = false;", "is-open" => "ctrl.showSessionHistory"}
+ .label Session History
+ %session-history-menu{"ng-if" => "ctrl.showSessionHistory", "item" => "ctrl.note"}
+
.editor-content#editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting"}
%panel-resizer.left{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "property" => "'left'", "hoverable" => "true"}
@@ -58,7 +62,7 @@
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", "ng-readonly" => "ctrl.note.locked",
"ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()",
- "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}"}
+ "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}", "ng-model-options"=>"{ debounce: 200 }"}
{{ctrl.onSystemEditorLoad()}}
%panel-resizer{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "hoverable" => "true", "property" => "'right'"}
diff --git a/app/assets/templates/home.html.haml b/app/assets/templates/home.html.haml
index 41c1945cd..b8d03b4d3 100644
--- a/app/assets/templates/home.html.haml
+++ b/app/assets/templates/home.html.haml
@@ -1,11 +1,11 @@
.main-ui-view{"ng-class" => "platform"}
%lock-screen{"ng-if" => "needsUnlock", "on-success" => "onSuccessfulUnlock"}
.app#app{"ng-if" => "!needsUnlock"}
- %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "will-select" => "tagsWillMakeSelection", "selection-made" => "tagsSelectionMade",
+ %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "selection-made" => "tagsSelectionMade",
"all-tag" => "allTag", "archive-tag" => "archiveTag", "tags" => "tags", "remove-tag" => "removeTag"}
%notes-section{"add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", "tag" => "selectedTag"}
- %editor-section{"note" => "selectedNote", "remove" => "deleteNote", "save" => "saveNote", "update-tags" => "updateTagsForNote"}
+ %editor-section{"note" => "selectedNote", "remove" => "deleteNote", "update-tags" => "updateTagsForNote"}
%footer{"ng-if" => "!needsUnlock"}
diff --git a/app/assets/templates/notes.html.haml b/app/assets/templates/notes.html.haml
index 7d22da5bd..87e837568 100644
--- a/app/assets/templates/notes.html.haml
+++ b/app/assets/templates/notes.html.haml
@@ -30,7 +30,7 @@
%menu-row{"label" => "'Date Modified'", "circle" => "ctrl.sortBy == 'client_updated_at' && 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByUpdated()", "desc" => "'Sort notes with the most recently updated first'"}
%menu-row{"label" => "'Title'", "circle" => "ctrl.sortBy == 'title' && 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByTitle()", "desc" => "'Sort notes alphabetically by their title'"}
- .section{"ng-if" => "!ctrl.tag.archiveTag"}
+ .section{"ng-if" => "!ctrl.tag.isSmartTag()"}
.header
%h4.title Display
@@ -52,7 +52,7 @@
%i.icon.ion-bookmark
%strong.medium-text Pinned
- .archived.tinted{"ng-if" => "note.archived && !ctrl.tag.archiveTag", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"}
+ .archived.tinted{"ng-if" => "note.archived && !ctrl.tag.isSmartTag()", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"}
%i.icon.ion-ios-box
%strong.medium-text Archived
diff --git a/package-lock.json b/package-lock.json
index 94c93c089..cc02c8ca2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -255,6 +255,12 @@
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
+ "assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
"astw": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz",
@@ -1189,6 +1195,12 @@
"resolve": "1.1.7"
}
},
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
"browserify": {
"version": "16.2.2",
"resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.2.tgz",
@@ -1466,6 +1478,20 @@
"lazy-cache": "1.0.4"
}
},
+ "chai": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
+ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
+ "dev": true,
+ "requires": {
+ "assertion-error": "1.1.0",
+ "check-error": "1.0.2",
+ "deep-eql": "3.0.1",
+ "get-func-name": "2.0.0",
+ "pathval": "1.1.0",
+ "type-detect": "4.0.8"
+ }
+ },
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
@@ -1505,6 +1531,12 @@
"upper-case-first": "1.1.2"
}
},
+ "check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+ "dev": true
+ },
"chokidar": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
@@ -1888,6 +1920,15 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
+ "deep-eql": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+ "dev": true,
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
"define-properties": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
@@ -1938,6 +1979,12 @@
"minimalistic-assert": "1.0.1"
}
},
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+ "dev": true
+ },
"detect-indent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
@@ -1972,6 +2019,12 @@
"integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=",
"dev": true
},
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true
+ },
"diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -2205,6 +2258,12 @@
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
},
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+ "dev": true
+ },
"eventemitter2": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
@@ -2514,6 +2573,12 @@
"mime-types": "2.1.18"
}
},
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+ "dev": true
+ },
"fs-extra": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
@@ -3081,6 +3146,12 @@
"globule": "1.2.1"
}
},
+ "get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+ "dev": true
+ },
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
@@ -3164,6 +3235,12 @@
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
"dev": true
},
+ "growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+ "dev": true
+ },
"grunt": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.3.tgz",
@@ -4495,6 +4572,45 @@
"minimist": "0.0.8"
}
},
+ "mocha": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
+ "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
+ "dev": true,
+ "requires": {
+ "browser-stdout": "1.3.1",
+ "commander": "2.15.1",
+ "debug": "3.1.0",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "glob": "7.1.2",
+ "growl": "1.10.5",
+ "he": "1.1.1",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.1",
+ "supports-color": "5.4.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
+ "dev": true,
+ "requires": {
+ "has-flag": "3.0.0"
+ }
+ }
+ }
+ },
"module-deps": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.1.0.tgz",
@@ -4923,6 +5039,12 @@
"pinkie-promise": "2.0.1"
}
},
+ "pathval": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+ "dev": true
+ },
"pbkdf2": {
"version": "3.0.16",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz",
@@ -5410,6 +5532,41 @@
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true
},
+ "send": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+ "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "1.1.2",
+ "destroy": "1.0.4",
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "etag": "1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "1.6.3",
+ "mime": "1.4.1",
+ "ms": "2.0.0",
+ "on-finished": "2.3.0",
+ "range-parser": "1.2.0",
+ "statuses": "1.4.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
+ "dev": true
+ },
+ "statuses": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+ "dev": true
+ }
+ }
+ },
"sentence-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz",
@@ -5420,6 +5577,18 @@
"upper-case-first": "1.1.2"
}
},
+ "serve-static": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+ "dev": true,
+ "requires": {
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "parseurl": "1.3.2",
+ "send": "0.16.2"
+ }
+ },
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
@@ -5488,6 +5657,3469 @@
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true
},
+ "sn-models": {
+ "version": "file:../../sn-models",
+ "dev": true,
+ "requires": {
+ "standard-file-js": "0.3.2"
+ },
+ "dependencies": {
+ "JSONStream": {
+ "version": "1.3.3",
+ "bundled": true,
+ "requires": {
+ "jsonparse": "1.3.1",
+ "through": "2.3.8"
+ }
+ },
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "acorn": {
+ "version": "5.7.1",
+ "bundled": true
+ },
+ "acorn-dynamic-import": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "acorn": "5.7.1"
+ }
+ },
+ "acorn-node": {
+ "version": "1.5.2",
+ "bundled": true,
+ "requires": {
+ "acorn": "5.7.1",
+ "acorn-dynamic-import": "3.0.0",
+ "xtend": "4.0.1"
+ }
+ },
+ "align-text": {
+ "version": "0.1.4",
+ "bundled": true,
+ "requires": {
+ "kind-of": "3.2.2",
+ "longest": "1.0.1",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "bundled": true
+ },
+ "anymatch": {
+ "version": "1.3.2",
+ "bundled": true,
+ "requires": {
+ "micromatch": "2.3.11",
+ "normalize-path": "2.1.1"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "bundled": true,
+ "requires": {
+ "sprintf-js": "1.0.3"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "arr-flatten": "1.1.0"
+ }
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "array-filter": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "array-find-index": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "array-map": {
+ "version": "0.0.0",
+ "bundled": true
+ },
+ "array-reduce": {
+ "version": "0.0.0",
+ "bundled": true
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "asn1.js": {
+ "version": "4.10.1",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "assert": {
+ "version": "1.4.1",
+ "bundled": true,
+ "requires": {
+ "util": "0.10.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "bundled": true
+ },
+ "util": {
+ "version": "0.10.3",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.1"
+ }
+ }
+ }
+ },
+ "async": {
+ "version": "1.5.2",
+ "bundled": true
+ },
+ "async-each": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "babel-cli": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-core": "6.26.3",
+ "babel-polyfill": "6.26.0",
+ "babel-register": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "chokidar": "1.7.0",
+ "commander": "2.15.1",
+ "convert-source-map": "1.5.1",
+ "fs-readdir-recursive": "1.1.0",
+ "glob": "7.1.2",
+ "lodash": "4.17.10",
+ "output-file-sync": "1.1.2",
+ "path-is-absolute": "1.0.1",
+ "slash": "1.0.0",
+ "source-map": "0.5.7",
+ "v8flags": "2.1.1"
+ }
+ },
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "esutils": "2.0.2",
+ "js-tokens": "3.0.2"
+ }
+ },
+ "babel-core": {
+ "version": "6.26.3",
+ "bundled": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "babel-generator": "6.26.1",
+ "babel-helpers": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-register": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "convert-source-map": "1.5.1",
+ "debug": "2.6.9",
+ "json5": "0.5.1",
+ "lodash": "4.17.10",
+ "minimatch": "3.0.4",
+ "path-is-absolute": "1.0.1",
+ "private": "0.1.8",
+ "slash": "1.0.0",
+ "source-map": "0.5.7"
+ }
+ },
+ "babel-generator": {
+ "version": "6.26.1",
+ "bundled": true,
+ "requires": {
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "detect-indent": "4.0.0",
+ "jsesc": "1.3.0",
+ "lodash": "4.17.10",
+ "source-map": "0.5.7",
+ "trim-right": "1.0.1"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "1.3.0",
+ "bundled": true
+ }
+ }
+ },
+ "babel-helper-builder-binary-assignment-operator-visitor": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-explode-assignable-expression": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-call-delegate": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-hoist-variables": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-define-map": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-helper-explode-assignable-expression": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-function-name": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-get-function-arity": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-get-function-arity": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-hoist-variables": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-optimise-call-expression": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-regex": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-helper-remap-async-to-generator": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-replace-supers": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-optimise-call-expression": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helpers": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-messages": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-check-es2015-constants": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-syntax-async-functions": {
+ "version": "6.13.0",
+ "bundled": true
+ },
+ "babel-plugin-syntax-exponentiation-operator": {
+ "version": "6.13.0",
+ "bundled": true
+ },
+ "babel-plugin-syntax-trailing-function-commas": {
+ "version": "6.22.0",
+ "bundled": true
+ },
+ "babel-plugin-transform-async-to-generator": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-remap-async-to-generator": "6.24.1",
+ "babel-plugin-syntax-async-functions": "6.13.0",
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-arrow-functions": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoped-functions": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoping": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-plugin-transform-es2015-classes": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-define-map": "6.26.0",
+ "babel-helper-function-name": "6.24.1",
+ "babel-helper-optimise-call-expression": "6.24.1",
+ "babel-helper-replace-supers": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-computed-properties": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-destructuring": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-duplicate-keys": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-for-of": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-function-name": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-literals": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-amd": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-commonjs": {
+ "version": "6.26.2",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-strict-mode": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-systemjs": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-hoist-variables": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-umd": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-object-super": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-replace-supers": "6.24.1",
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-parameters": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-call-delegate": "6.24.1",
+ "babel-helper-get-function-arity": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-shorthand-properties": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-spread": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-sticky-regex": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-regex": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-template-literals": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-typeof-symbol": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-unicode-regex": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-regex": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "regexpu-core": "2.0.0"
+ }
+ },
+ "babel-plugin-transform-exponentiation-operator": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1",
+ "babel-plugin-syntax-exponentiation-operator": "6.13.0",
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-regenerator": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "regenerator-transform": "0.10.1"
+ }
+ },
+ "babel-plugin-transform-runtime": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-strict-mode": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-polyfill": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "core-js": "2.5.6",
+ "regenerator-runtime": "0.10.5"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.10.5",
+ "bundled": true
+ }
+ }
+ },
+ "babel-preset-env": {
+ "version": "1.7.0",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-check-es2015-constants": "6.22.0",
+ "babel-plugin-syntax-trailing-function-commas": "6.22.0",
+ "babel-plugin-transform-async-to-generator": "6.24.1",
+ "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoping": "6.26.0",
+ "babel-plugin-transform-es2015-classes": "6.24.1",
+ "babel-plugin-transform-es2015-computed-properties": "6.24.1",
+ "babel-plugin-transform-es2015-destructuring": "6.23.0",
+ "babel-plugin-transform-es2015-duplicate-keys": "6.24.1",
+ "babel-plugin-transform-es2015-for-of": "6.23.0",
+ "babel-plugin-transform-es2015-function-name": "6.24.1",
+ "babel-plugin-transform-es2015-literals": "6.22.0",
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
+ "babel-plugin-transform-es2015-modules-umd": "6.24.1",
+ "babel-plugin-transform-es2015-object-super": "6.24.1",
+ "babel-plugin-transform-es2015-parameters": "6.24.1",
+ "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
+ "babel-plugin-transform-es2015-spread": "6.22.0",
+ "babel-plugin-transform-es2015-sticky-regex": "6.24.1",
+ "babel-plugin-transform-es2015-template-literals": "6.22.0",
+ "babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
+ "babel-plugin-transform-es2015-unicode-regex": "6.24.1",
+ "babel-plugin-transform-exponentiation-operator": "6.24.1",
+ "babel-plugin-transform-regenerator": "6.26.0",
+ "browserslist": "3.2.8",
+ "invariant": "2.2.4",
+ "semver": "5.5.0"
+ }
+ },
+ "babel-preset-es2015": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-check-es2015-constants": "6.22.0",
+ "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoping": "6.26.0",
+ "babel-plugin-transform-es2015-classes": "6.24.1",
+ "babel-plugin-transform-es2015-computed-properties": "6.24.1",
+ "babel-plugin-transform-es2015-destructuring": "6.23.0",
+ "babel-plugin-transform-es2015-duplicate-keys": "6.24.1",
+ "babel-plugin-transform-es2015-for-of": "6.23.0",
+ "babel-plugin-transform-es2015-function-name": "6.24.1",
+ "babel-plugin-transform-es2015-literals": "6.22.0",
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
+ "babel-plugin-transform-es2015-modules-umd": "6.24.1",
+ "babel-plugin-transform-es2015-object-super": "6.24.1",
+ "babel-plugin-transform-es2015-parameters": "6.24.1",
+ "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
+ "babel-plugin-transform-es2015-spread": "6.22.0",
+ "babel-plugin-transform-es2015-sticky-regex": "6.24.1",
+ "babel-plugin-transform-es2015-template-literals": "6.22.0",
+ "babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
+ "babel-plugin-transform-es2015-unicode-regex": "6.24.1",
+ "babel-plugin-transform-regenerator": "6.26.0"
+ }
+ },
+ "babel-preset-es2016": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-exponentiation-operator": "6.24.1"
+ }
+ },
+ "babel-register": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-core": "6.26.3",
+ "babel-runtime": "6.26.0",
+ "core-js": "2.5.6",
+ "home-or-tmp": "2.0.0",
+ "lodash": "4.17.10",
+ "mkdirp": "0.5.1",
+ "source-map-support": "0.4.18"
+ }
+ },
+ "babel-runtime": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "core-js": "2.5.6",
+ "regenerator-runtime": "0.11.1"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.11.1",
+ "bundled": true
+ }
+ }
+ },
+ "babel-template": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-traverse": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "debug": "2.6.9",
+ "globals": "9.18.0",
+ "invariant": "2.2.4",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-types": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "esutils": "2.0.2",
+ "lodash": "4.17.10",
+ "to-fast-properties": "1.0.3"
+ }
+ },
+ "babelify": {
+ "version": "8.0.0",
+ "bundled": true
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "bundled": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "base64-js": {
+ "version": "1.3.0",
+ "bundled": true
+ },
+ "binary-extensions": {
+ "version": "1.11.0",
+ "bundled": true
+ },
+ "bn.js": {
+ "version": "4.11.8",
+ "bundled": true
+ },
+ "body": {
+ "version": "5.1.0",
+ "bundled": true,
+ "requires": {
+ "continuable-cache": "0.3.1",
+ "error": "7.0.2",
+ "raw-body": "1.1.7",
+ "safe-json-parse": "1.0.1"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "requires": {
+ "balanced-match": "1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "1.8.5",
+ "bundled": true,
+ "requires": {
+ "expand-range": "1.8.2",
+ "preserve": "0.2.0",
+ "repeat-element": "1.1.2"
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "browser-pack": {
+ "version": "6.1.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "combine-source-map": "0.8.0",
+ "defined": "1.0.0",
+ "safe-buffer": "5.1.2",
+ "through2": "2.0.3",
+ "umd": "3.0.3"
+ }
+ },
+ "browser-resolve": {
+ "version": "1.11.3",
+ "bundled": true,
+ "requires": {
+ "resolve": "1.1.7"
+ }
+ },
+ "browserify": {
+ "version": "16.2.2",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "assert": "1.4.1",
+ "browser-pack": "6.1.0",
+ "browser-resolve": "1.11.3",
+ "browserify-zlib": "0.2.0",
+ "buffer": "5.1.0",
+ "cached-path-relative": "1.0.1",
+ "concat-stream": "1.6.2",
+ "console-browserify": "1.1.0",
+ "constants-browserify": "1.0.0",
+ "crypto-browserify": "3.12.0",
+ "defined": "1.0.0",
+ "deps-sort": "2.0.0",
+ "domain-browser": "1.2.0",
+ "duplexer2": "0.1.4",
+ "events": "2.1.0",
+ "glob": "7.1.2",
+ "has": "1.0.1",
+ "htmlescape": "1.1.1",
+ "https-browserify": "1.0.0",
+ "inherits": "2.0.3",
+ "insert-module-globals": "7.2.0",
+ "labeled-stream-splicer": "2.0.1",
+ "mkdirp": "0.5.1",
+ "module-deps": "6.1.0",
+ "os-browserify": "0.3.0",
+ "parents": "1.0.1",
+ "path-browserify": "0.0.1",
+ "process": "0.11.10",
+ "punycode": "1.4.1",
+ "querystring-es3": "0.2.1",
+ "read-only-stream": "2.0.0",
+ "readable-stream": "2.3.6",
+ "resolve": "1.1.7",
+ "shasum": "1.0.2",
+ "shell-quote": "1.6.1",
+ "stream-browserify": "2.0.1",
+ "stream-http": "2.8.3",
+ "string_decoder": "1.1.1",
+ "subarg": "1.0.0",
+ "syntax-error": "1.4.0",
+ "through2": "2.0.3",
+ "timers-browserify": "1.4.2",
+ "tty-browserify": "0.0.1",
+ "url": "0.11.0",
+ "util": "0.10.4",
+ "vm-browserify": "1.1.0",
+ "xtend": "4.0.1"
+ }
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "bundled": true,
+ "requires": {
+ "buffer-xor": "1.0.3",
+ "cipher-base": "1.0.4",
+ "create-hash": "1.2.0",
+ "evp_bytestokey": "1.0.3",
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "browserify-cache-api": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "async": "1.5.2",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "browserify-aes": "1.2.0",
+ "browserify-des": "1.0.1",
+ "evp_bytestokey": "1.0.3"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "des.js": "1.0.0",
+ "inherits": "2.0.3"
+ }
+ },
+ "browserify-incremental": {
+ "version": "3.1.1",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "0.10.0",
+ "browserify-cache-api": "3.0.1",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ },
+ "dependencies": {
+ "JSONStream": {
+ "version": "0.10.0",
+ "bundled": true,
+ "requires": {
+ "jsonparse": "0.0.5",
+ "through": "2.3.8"
+ }
+ },
+ "jsonparse": {
+ "version": "0.0.5",
+ "bundled": true
+ }
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "randombytes": "2.0.6"
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "browserify-rsa": "4.0.1",
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "elliptic": "6.4.0",
+ "inherits": "2.0.3",
+ "parse-asn1": "5.1.1"
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "bundled": true,
+ "requires": {
+ "pako": "1.0.6"
+ }
+ },
+ "browserslist": {
+ "version": "3.2.8",
+ "bundled": true,
+ "requires": {
+ "caniuse-lite": "1.0.30000844",
+ "electron-to-chromium": "1.3.47"
+ }
+ },
+ "buffer": {
+ "version": "5.1.0",
+ "bundled": true,
+ "requires": {
+ "base64-js": "1.3.0",
+ "ieee754": "1.1.12"
+ }
+ },
+ "buffer-from": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "bytes": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "cached-path-relative": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "camelcase": {
+ "version": "2.1.1",
+ "bundled": true
+ },
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "camelcase": "2.1.1",
+ "map-obj": "1.0.1"
+ }
+ },
+ "caniuse-lite": {
+ "version": "1.0.30000844",
+ "bundled": true
+ },
+ "center-align": {
+ "version": "0.1.3",
+ "bundled": true,
+ "requires": {
+ "align-text": "0.1.4",
+ "lazy-cache": "1.0.4"
+ }
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "bundled": true,
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ }
+ },
+ "chokidar": {
+ "version": "1.7.0",
+ "bundled": true,
+ "requires": {
+ "anymatch": "1.3.2",
+ "async-each": "1.0.1",
+ "fsevents": "1.2.4",
+ "glob-parent": "2.0.0",
+ "inherits": "2.0.3",
+ "is-binary-path": "1.0.1",
+ "is-glob": "2.0.1",
+ "path-is-absolute": "1.0.1",
+ "readdirp": "2.1.0"
+ }
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "center-align": "0.1.3",
+ "right-align": "0.1.3",
+ "wordwrap": "0.0.2"
+ }
+ },
+ "coffeescript": {
+ "version": "1.10.0",
+ "bundled": true
+ },
+ "colors": {
+ "version": "1.1.2",
+ "bundled": true
+ },
+ "combine-source-map": {
+ "version": "0.8.0",
+ "bundled": true,
+ "requires": {
+ "convert-source-map": "1.1.3",
+ "inline-source-map": "0.6.2",
+ "lodash.memoize": "3.0.4",
+ "source-map": "0.5.7"
+ },
+ "dependencies": {
+ "convert-source-map": {
+ "version": "1.1.3",
+ "bundled": true
+ }
+ }
+ },
+ "commander": {
+ "version": "2.15.1",
+ "bundled": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "bundled": true,
+ "requires": {
+ "buffer-from": "1.0.0",
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6",
+ "typedarray": "0.0.6"
+ }
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "date-now": "0.1.4"
+ }
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "continuable-cache": {
+ "version": "0.3.1",
+ "bundled": true
+ },
+ "convert-source-map": {
+ "version": "1.5.1",
+ "bundled": true
+ },
+ "core-js": {
+ "version": "2.5.6",
+ "bundled": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "create-ecdh": {
+ "version": "4.0.3",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "elliptic": "6.4.0"
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "bundled": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "inherits": "2.0.3",
+ "md5.js": "1.3.4",
+ "ripemd160": "2.0.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "bundled": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "create-hash": "1.2.0",
+ "inherits": "2.0.3",
+ "ripemd160": "2.0.2",
+ "safe-buffer": "5.1.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "bundled": true,
+ "requires": {
+ "browserify-cipher": "1.0.1",
+ "browserify-sign": "4.0.4",
+ "create-ecdh": "4.0.3",
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "diffie-hellman": "5.0.3",
+ "inherits": "2.0.3",
+ "pbkdf2": "3.0.16",
+ "public-encrypt": "4.0.2",
+ "randombytes": "2.0.6",
+ "randomfill": "1.0.4"
+ }
+ },
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "bundled": true,
+ "requires": {
+ "array-find-index": "1.0.2"
+ }
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "bundled": true
+ },
+ "dateformat": {
+ "version": "1.0.12",
+ "bundled": true,
+ "requires": {
+ "get-stdin": "4.0.1",
+ "meow": "3.7.0"
+ }
+ },
+ "debug": {
+ "version": "2.6.9",
+ "bundled": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "bundled": true
+ },
+ "define-properties": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "foreach": "2.0.5",
+ "object-keys": "1.0.11"
+ }
+ },
+ "defined": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "deps-sort": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "shasum": "1.0.2",
+ "subarg": "1.0.0",
+ "through2": "2.0.3"
+ }
+ },
+ "des.js": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "detect-indent": {
+ "version": "4.0.0",
+ "bundled": true,
+ "requires": {
+ "repeating": "2.0.1"
+ }
+ },
+ "detective": {
+ "version": "5.1.0",
+ "bundled": true,
+ "requires": {
+ "acorn-node": "1.5.2",
+ "defined": "1.0.0",
+ "minimist": "1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true
+ }
+ }
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "miller-rabin": "4.0.1",
+ "randombytes": "2.0.6"
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "bundled": true
+ },
+ "duplexer2": {
+ "version": "0.1.4",
+ "bundled": true,
+ "requires": {
+ "readable-stream": "2.3.6"
+ }
+ },
+ "electron-to-chromium": {
+ "version": "1.3.47",
+ "bundled": true
+ },
+ "elliptic": {
+ "version": "6.4.0",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "brorand": "1.1.0",
+ "hash.js": "1.1.4",
+ "hmac-drbg": "1.0.1",
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1",
+ "minimalistic-crypto-utils": "1.0.1"
+ }
+ },
+ "error": {
+ "version": "7.0.2",
+ "bundled": true,
+ "requires": {
+ "string-template": "0.2.1",
+ "xtend": "4.0.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.1",
+ "bundled": true,
+ "requires": {
+ "is-arrayish": "0.2.1"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "bundled": true
+ },
+ "esprima": {
+ "version": "2.7.3",
+ "bundled": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "bundled": true
+ },
+ "eventemitter2": {
+ "version": "0.4.14",
+ "bundled": true
+ },
+ "events": {
+ "version": "2.1.0",
+ "bundled": true
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "bundled": true,
+ "requires": {
+ "md5.js": "1.3.4",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "exit": {
+ "version": "0.1.2",
+ "bundled": true
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "bundled": true,
+ "requires": {
+ "is-posix-bracket": "0.1.1"
+ }
+ },
+ "expand-range": {
+ "version": "1.8.2",
+ "bundled": true,
+ "requires": {
+ "fill-range": "2.2.4"
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "bundled": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ },
+ "faye-websocket": {
+ "version": "0.10.0",
+ "bundled": true,
+ "requires": {
+ "websocket-driver": "0.7.0"
+ }
+ },
+ "figures": {
+ "version": "1.7.0",
+ "bundled": true,
+ "requires": {
+ "escape-string-regexp": "1.0.5",
+ "object-assign": "4.1.1"
+ }
+ },
+ "filename-regex": {
+ "version": "2.0.1",
+ "bundled": true
+ },
+ "fill-range": {
+ "version": "2.2.4",
+ "bundled": true,
+ "requires": {
+ "is-number": "2.1.0",
+ "isobject": "2.1.0",
+ "randomatic": "3.0.0",
+ "repeat-element": "1.1.2",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "find-up": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "path-exists": "2.1.0",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "findup-sync": {
+ "version": "0.3.0",
+ "bundled": true,
+ "requires": {
+ "glob": "5.0.15"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "5.0.15",
+ "bundled": true,
+ "requires": {
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ }
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "for-own": {
+ "version": "0.1.5",
+ "bundled": true,
+ "requires": {
+ "for-in": "1.0.2"
+ }
+ },
+ "foreach": {
+ "version": "2.0.5",
+ "bundled": true
+ },
+ "fs-readdir-recursive": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "fsevents": {
+ "version": "1.2.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "nan": "2.10.0",
+ "node-pre-gyp": "0.10.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "delegates": "1.0.0",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "requires": {
+ "balanced-match": "1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.5.1",
+ "bundled": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minipass": "2.2.4"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "aproba": "1.2.0",
+ "console-control-strings": "1.1.0",
+ "has-unicode": "2.0.1",
+ "object-assign": "4.1.1",
+ "signal-exit": "3.0.2",
+ "string-width": "1.0.2",
+ "strip-ansi": "3.0.1",
+ "wide-align": "1.1.2"
+ }
+ },
+ "glob": {
+ "version": "7.1.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.21",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": "2.1.2"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "number-is-nan": "1.0.1"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "1.1.11"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true
+ },
+ "minipass": {
+ "version": "2.2.4",
+ "bundled": true,
+ "requires": {
+ "safe-buffer": "5.1.1",
+ "yallist": "3.0.2"
+ }
+ },
+ "minizlib": {
+ "version": "1.1.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minipass": "2.2.4"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.2.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "debug": "2.6.9",
+ "iconv-lite": "0.4.21",
+ "sax": "1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.10.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "1.0.3",
+ "mkdirp": "0.5.1",
+ "needle": "2.2.0",
+ "nopt": "4.0.1",
+ "npm-packlist": "1.1.10",
+ "npmlog": "4.1.2",
+ "rc": "1.2.7",
+ "rimraf": "2.6.2",
+ "semver": "5.5.0",
+ "tar": "4.4.1"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1.1.1",
+ "osenv": "0.1.5"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.3",
+ "bundled": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.1.10",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "3.0.1",
+ "npm-bundled": "1.0.3"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "1.1.4",
+ "console-control-strings": "1.1.0",
+ "gauge": "2.7.4",
+ "set-blocking": "2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "1.0.2",
+ "os-tmpdir": "1.0.2"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.7",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "0.5.1",
+ "ini": "1.3.5",
+ "minimist": "1.2.0",
+ "strip-json-comments": "2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "2.0.0",
+ "safe-buffer": "5.1.1",
+ "string_decoder": "1.1.1",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "glob": "7.1.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "bundled": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.5.0",
+ "bundled": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "code-point-at": "1.1.0",
+ "is-fullwidth-code-point": "1.0.0",
+ "strip-ansi": "3.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "chownr": "1.0.1",
+ "fs-minipass": "1.2.5",
+ "minipass": "2.2.4",
+ "minizlib": "1.1.0",
+ "mkdirp": "0.5.1",
+ "safe-buffer": "5.1.1",
+ "yallist": "3.0.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "string-width": "1.0.2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "yallist": {
+ "version": "3.0.2",
+ "bundled": true
+ }
+ }
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "gaze": {
+ "version": "1.1.3",
+ "bundled": true,
+ "requires": {
+ "globule": "1.2.0"
+ }
+ },
+ "get-assigned-identifiers": {
+ "version": "1.2.0",
+ "bundled": true
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "bundled": true
+ },
+ "getobject": {
+ "version": "0.1.0",
+ "bundled": true
+ },
+ "glob": {
+ "version": "7.1.2",
+ "bundled": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "glob-base": {
+ "version": "0.3.0",
+ "bundled": true,
+ "requires": {
+ "glob-parent": "2.0.0",
+ "is-glob": "2.0.1"
+ }
+ },
+ "glob-parent": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "is-glob": "2.0.1"
+ }
+ },
+ "globals": {
+ "version": "9.18.0",
+ "bundled": true
+ },
+ "globule": {
+ "version": "1.2.0",
+ "bundled": true,
+ "requires": {
+ "glob": "7.1.2",
+ "lodash": "4.17.10",
+ "minimatch": "3.0.4"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "bundled": true
+ },
+ "grunt": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "coffeescript": "1.10.0",
+ "dateformat": "1.0.12",
+ "eventemitter2": "0.4.14",
+ "exit": "0.1.2",
+ "findup-sync": "0.3.0",
+ "glob": "7.0.6",
+ "grunt-cli": "1.2.0",
+ "grunt-known-options": "1.1.0",
+ "grunt-legacy-log": "1.0.2",
+ "grunt-legacy-util": "1.0.0",
+ "iconv-lite": "0.4.23",
+ "js-yaml": "3.5.5",
+ "minimatch": "3.0.4",
+ "nopt": "3.0.6",
+ "path-is-absolute": "1.0.1",
+ "rimraf": "2.2.8"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.0.6",
+ "bundled": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "grunt-cli": {
+ "version": "1.2.0",
+ "bundled": true,
+ "requires": {
+ "findup-sync": "0.3.0",
+ "grunt-known-options": "1.1.0",
+ "nopt": "3.0.6",
+ "resolve": "1.1.7"
+ }
+ }
+ }
+ },
+ "grunt-babel": {
+ "version": "6.0.0",
+ "bundled": true,
+ "requires": {
+ "babel-core": "6.26.3"
+ }
+ },
+ "grunt-browserify": {
+ "version": "5.3.0",
+ "bundled": true,
+ "requires": {
+ "async": "2.6.1",
+ "browserify": "16.2.2",
+ "browserify-incremental": "3.1.1",
+ "glob": "7.1.2",
+ "lodash": "4.17.10",
+ "resolve": "1.1.7",
+ "watchify": "3.11.0"
+ },
+ "dependencies": {
+ "async": {
+ "version": "2.6.1",
+ "bundled": true,
+ "requires": {
+ "lodash": "4.17.10"
+ }
+ }
+ }
+ },
+ "grunt-contrib-concat": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "source-map": "0.5.7"
+ }
+ },
+ "grunt-contrib-uglify": {
+ "version": "2.3.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "maxmin": "1.1.0",
+ "object.assign": "4.1.0",
+ "uglify-js": "2.8.29",
+ "uri-path": "1.0.0"
+ }
+ },
+ "grunt-contrib-watch": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "async": "2.6.1",
+ "gaze": "1.1.3",
+ "lodash": "4.17.10",
+ "tiny-lr": "1.1.1"
+ },
+ "dependencies": {
+ "async": {
+ "version": "2.6.1",
+ "bundled": true,
+ "requires": {
+ "lodash": "4.17.10"
+ }
+ }
+ }
+ },
+ "grunt-known-options": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "grunt-legacy-log": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "colors": "1.1.2",
+ "grunt-legacy-log-utils": "1.0.0",
+ "hooker": "0.2.3",
+ "lodash": "4.17.10"
+ }
+ },
+ "grunt-legacy-log-utils": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "lodash": "4.3.0"
+ },
+ "dependencies": {
+ "lodash": {
+ "version": "4.3.0",
+ "bundled": true
+ }
+ }
+ },
+ "grunt-legacy-util": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "async": "1.5.2",
+ "exit": "0.1.2",
+ "getobject": "0.1.0",
+ "hooker": "0.2.3",
+ "lodash": "4.3.0",
+ "underscore.string": "3.2.3",
+ "which": "1.2.14"
+ },
+ "dependencies": {
+ "lodash": {
+ "version": "4.3.0",
+ "bundled": true
+ }
+ }
+ },
+ "grunt-newer": {
+ "version": "1.3.0",
+ "bundled": true,
+ "requires": {
+ "async": "1.5.2",
+ "rimraf": "2.6.2"
+ },
+ "dependencies": {
+ "rimraf": {
+ "version": "2.6.2",
+ "bundled": true,
+ "requires": {
+ "glob": "7.1.2"
+ }
+ }
+ }
+ },
+ "gzip-size": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "browserify-zlib": "0.1.4",
+ "concat-stream": "1.6.2"
+ },
+ "dependencies": {
+ "browserify-zlib": {
+ "version": "0.1.4",
+ "bundled": true,
+ "requires": {
+ "pako": "0.2.9"
+ }
+ },
+ "pako": {
+ "version": "0.2.9",
+ "bundled": true
+ }
+ }
+ },
+ "has": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "function-bind": "1.1.1"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "hash.js": "1.1.4",
+ "minimalistic-assert": "1.0.1",
+ "minimalistic-crypto-utils": "1.0.1"
+ }
+ },
+ "home-or-tmp": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "os-homedir": "1.0.2",
+ "os-tmpdir": "1.0.2"
+ }
+ },
+ "hooker": {
+ "version": "0.2.3",
+ "bundled": true
+ },
+ "hosted-git-info": {
+ "version": "2.6.0",
+ "bundled": true
+ },
+ "htmlescape": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "http-parser-js": {
+ "version": "0.4.13",
+ "bundled": true
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "iconv-lite": {
+ "version": "0.4.23",
+ "bundled": true,
+ "requires": {
+ "safer-buffer": "2.1.2"
+ }
+ },
+ "ieee754": {
+ "version": "1.1.12",
+ "bundled": true
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "repeating": "2.0.1"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true
+ },
+ "inline-source-map": {
+ "version": "0.6.2",
+ "bundled": true,
+ "requires": {
+ "source-map": "0.5.7"
+ }
+ },
+ "insert-module-globals": {
+ "version": "7.2.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "acorn-node": "1.5.2",
+ "combine-source-map": "0.8.0",
+ "concat-stream": "1.6.2",
+ "is-buffer": "1.1.6",
+ "path-is-absolute": "1.0.1",
+ "process": "0.11.10",
+ "through2": "2.0.3",
+ "undeclared-identifiers": "1.1.2",
+ "xtend": "4.0.1"
+ }
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "bundled": true,
+ "requires": {
+ "loose-envify": "1.3.1"
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "binary-extensions": "1.11.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "bundled": true
+ },
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "builtin-modules": "1.1.1"
+ }
+ },
+ "is-dotfile": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "is-equal-shallow": {
+ "version": "0.1.3",
+ "bundled": true,
+ "requires": {
+ "is-primitive": "2.0.0"
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "bundled": true
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "is-finite": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "number-is-nan": "1.0.1"
+ }
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ },
+ "is-number": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ }
+ },
+ "is-posix-bracket": {
+ "version": "0.1.1",
+ "bundled": true
+ },
+ "is-primitive": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "isobject": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "bundled": true
+ },
+ "js-yaml": {
+ "version": "3.5.5",
+ "bundled": true,
+ "requires": {
+ "argparse": "1.0.10",
+ "esprima": "2.7.3"
+ }
+ },
+ "jsesc": {
+ "version": "0.5.0",
+ "bundled": true
+ },
+ "json-stable-stringify": {
+ "version": "0.0.1",
+ "bundled": true,
+ "requires": {
+ "jsonify": "0.0.0"
+ }
+ },
+ "json5": {
+ "version": "0.5.1",
+ "bundled": true
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "bundled": true
+ },
+ "jsonparse": {
+ "version": "1.3.1",
+ "bundled": true
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "bundled": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ },
+ "labeled-stream-splicer": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "isarray": "2.0.4",
+ "stream-splicer": "2.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "2.0.4",
+ "bundled": true
+ }
+ }
+ },
+ "lazy-cache": {
+ "version": "1.0.4",
+ "bundled": true
+ },
+ "livereload-js": {
+ "version": "2.3.0",
+ "bundled": true
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "parse-json": "2.2.0",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1",
+ "strip-bom": "2.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.10",
+ "bundled": true
+ },
+ "lodash.memoize": {
+ "version": "3.0.4",
+ "bundled": true
+ },
+ "longest": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "loose-envify": {
+ "version": "1.3.1",
+ "bundled": true,
+ "requires": {
+ "js-tokens": "3.0.2"
+ }
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "bundled": true,
+ "requires": {
+ "currently-unhandled": "0.4.1",
+ "signal-exit": "3.0.2"
+ }
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "math-random": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "maxmin": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "figures": "1.7.0",
+ "gzip-size": "1.0.0",
+ "pretty-bytes": "1.0.4"
+ }
+ },
+ "md5.js": {
+ "version": "1.3.4",
+ "bundled": true,
+ "requires": {
+ "hash-base": "3.0.4",
+ "inherits": "2.0.3"
+ }
+ },
+ "meow": {
+ "version": "3.7.0",
+ "bundled": true,
+ "requires": {
+ "camelcase-keys": "2.1.0",
+ "decamelize": "1.2.0",
+ "loud-rejection": "1.6.0",
+ "map-obj": "1.0.1",
+ "minimist": "1.2.0",
+ "normalize-package-data": "2.4.0",
+ "object-assign": "4.1.1",
+ "read-pkg-up": "1.0.1",
+ "redent": "1.0.0",
+ "trim-newlines": "1.0.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true
+ }
+ }
+ },
+ "micromatch": {
+ "version": "2.3.11",
+ "bundled": true,
+ "requires": {
+ "arr-diff": "2.0.0",
+ "array-unique": "0.2.1",
+ "braces": "1.8.5",
+ "expand-brackets": "0.1.5",
+ "extglob": "0.3.2",
+ "filename-regex": "2.0.1",
+ "is-extglob": "1.0.0",
+ "is-glob": "2.0.1",
+ "kind-of": "3.2.2",
+ "normalize-path": "2.1.1",
+ "object.omit": "2.0.1",
+ "parse-glob": "3.0.4",
+ "regex-cache": "0.4.4"
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "brorand": "1.1.0"
+ }
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "1.1.11"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "module-deps": {
+ "version": "6.1.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "browser-resolve": "1.11.3",
+ "cached-path-relative": "1.0.1",
+ "concat-stream": "1.6.2",
+ "defined": "1.0.0",
+ "detective": "5.1.0",
+ "duplexer2": "0.1.4",
+ "inherits": "2.0.3",
+ "parents": "1.0.1",
+ "readable-stream": "2.3.6",
+ "resolve": "1.8.1",
+ "stream-combiner2": "1.1.1",
+ "subarg": "1.0.0",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.8.1",
+ "bundled": true,
+ "requires": {
+ "path-parse": "1.0.5"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "nan": {
+ "version": "2.10.0",
+ "bundled": true,
+ "optional": true
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "bundled": true,
+ "requires": {
+ "abbrev": "1.1.1"
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.4.0",
+ "bundled": true,
+ "requires": {
+ "hosted-git-info": "2.6.0",
+ "is-builtin-module": "1.0.0",
+ "semver": "5.5.0",
+ "validate-npm-package-license": "3.0.3"
+ }
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "bundled": true,
+ "requires": {
+ "remove-trailing-separator": "1.1.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true
+ },
+ "object-keys": {
+ "version": "1.0.11",
+ "bundled": true
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "bundled": true,
+ "requires": {
+ "define-properties": "1.1.2",
+ "function-bind": "1.1.1",
+ "has-symbols": "1.0.0",
+ "object-keys": "1.0.11"
+ }
+ },
+ "object.omit": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "for-own": "0.1.5",
+ "is-extendable": "0.1.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "bundled": true
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "outpipe": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "shell-quote": "1.6.1"
+ }
+ },
+ "output-file-sync": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "mkdirp": "0.5.1",
+ "object-assign": "4.1.1"
+ }
+ },
+ "pako": {
+ "version": "1.0.6",
+ "bundled": true
+ },
+ "parents": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "path-platform": "0.11.15"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.1",
+ "bundled": true,
+ "requires": {
+ "asn1.js": "4.10.1",
+ "browserify-aes": "1.2.0",
+ "create-hash": "1.2.0",
+ "evp_bytestokey": "1.0.3",
+ "pbkdf2": "3.0.16"
+ }
+ },
+ "parse-glob": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "glob-base": "0.3.0",
+ "is-dotfile": "1.0.3",
+ "is-extglob": "1.0.0",
+ "is-glob": "2.0.1"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "bundled": true,
+ "requires": {
+ "error-ex": "1.3.1"
+ }
+ },
+ "path-browserify": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "path-parse": {
+ "version": "1.0.5",
+ "bundled": true
+ },
+ "path-platform": {
+ "version": "0.11.15",
+ "bundled": true
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "pbkdf2": {
+ "version": "3.0.16",
+ "bundled": true,
+ "requires": {
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "ripemd160": "2.0.2",
+ "safe-buffer": "5.1.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "bundled": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "bundled": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "pinkie": "2.0.4"
+ }
+ },
+ "preserve": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "pretty-bytes": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "get-stdin": "4.0.1",
+ "meow": "3.7.0"
+ }
+ },
+ "private": {
+ "version": "0.1.8",
+ "bundled": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "bundled": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "public-encrypt": {
+ "version": "4.0.2",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "browserify-rsa": "4.0.1",
+ "create-hash": "1.2.0",
+ "parse-asn1": "5.1.1",
+ "randombytes": "2.0.6"
+ }
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "bundled": true
+ },
+ "qs": {
+ "version": "6.5.2",
+ "bundled": true
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "randomatic": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "is-number": "4.0.0",
+ "kind-of": "6.0.2",
+ "math-random": "1.0.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "bundled": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "bundled": true
+ }
+ }
+ },
+ "randombytes": {
+ "version": "2.0.6",
+ "bundled": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "randombytes": "2.0.6",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "raw-body": {
+ "version": "1.1.7",
+ "bundled": true,
+ "requires": {
+ "bytes": "1.0.0",
+ "string_decoder": "0.10.31"
+ },
+ "dependencies": {
+ "string_decoder": {
+ "version": "0.10.31",
+ "bundled": true
+ }
+ }
+ },
+ "read-only-stream": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "readable-stream": "2.3.6"
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "load-json-file": "1.1.0",
+ "normalize-package-data": "2.4.0",
+ "path-type": "1.1.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "find-up": "1.1.2",
+ "read-pkg": "1.1.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "2.0.0",
+ "safe-buffer": "5.1.2",
+ "string_decoder": "1.1.1",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "readdirp": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "minimatch": "3.0.4",
+ "readable-stream": "2.3.6",
+ "set-immediate-shim": "1.0.1"
+ }
+ },
+ "redent": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "indent-string": "2.1.0",
+ "strip-indent": "1.0.1"
+ }
+ },
+ "regenerate": {
+ "version": "1.4.0",
+ "bundled": true
+ },
+ "regenerator-transform": {
+ "version": "0.10.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "private": "0.1.8"
+ }
+ },
+ "regex-cache": {
+ "version": "0.4.4",
+ "bundled": true,
+ "requires": {
+ "is-equal-shallow": "0.1.3"
+ }
+ },
+ "regexpu-core": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "regenerate": "1.4.0",
+ "regjsgen": "0.2.0",
+ "regjsparser": "0.1.5"
+ }
+ },
+ "regjsgen": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "regjsparser": {
+ "version": "0.1.5",
+ "bundled": true,
+ "requires": {
+ "jsesc": "0.5.0"
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "repeat-element": {
+ "version": "1.1.2",
+ "bundled": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "bundled": true
+ },
+ "repeating": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "is-finite": "1.0.2"
+ }
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "bundled": true
+ },
+ "right-align": {
+ "version": "0.1.3",
+ "bundled": true,
+ "requires": {
+ "align-text": "0.1.4"
+ }
+ },
+ "rimraf": {
+ "version": "2.2.8",
+ "bundled": true
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "bundled": true,
+ "requires": {
+ "hash-base": "3.0.4",
+ "inherits": "2.0.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ },
+ "safe-json-parse": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true
+ },
+ "semver": {
+ "version": "5.5.0",
+ "bundled": true
+ },
+ "set-immediate-shim": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "shasum": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "json-stable-stringify": "0.0.1",
+ "sha.js": "2.4.11"
+ }
+ },
+ "shell-quote": {
+ "version": "1.6.1",
+ "bundled": true,
+ "requires": {
+ "array-filter": "0.0.1",
+ "array-map": "0.0.0",
+ "array-reduce": "0.0.0",
+ "jsonify": "0.0.0"
+ }
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true
+ },
+ "simple-concat": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "slash": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "bundled": true
+ },
+ "source-map-support": {
+ "version": "0.4.18",
+ "bundled": true,
+ "requires": {
+ "source-map": "0.5.7"
+ }
+ },
+ "spdx-correct": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "spdx-expression-parse": "3.0.0",
+ "spdx-license-ids": "3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.1.0",
+ "bundled": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "spdx-exceptions": "2.1.0",
+ "spdx-license-ids": "3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "standard-file-js": {
+ "version": "0.3.2",
+ "bundled": true,
+ "dev": true
+ },
+ "stream-browserify": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "stream-combiner2": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "duplexer2": "0.1.4",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "bundled": true,
+ "requires": {
+ "builtin-status-codes": "3.0.0",
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6",
+ "to-arraybuffer": "1.0.1",
+ "xtend": "4.0.1"
+ }
+ },
+ "stream-splicer": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "string-template": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "is-utf8": "0.2.1"
+ }
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "get-stdin": "4.0.1"
+ }
+ },
+ "subarg": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "minimist": "1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true
+ }
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "syntax-error": {
+ "version": "1.4.0",
+ "bundled": true,
+ "requires": {
+ "acorn-node": "1.5.2"
+ }
+ },
+ "through": {
+ "version": "2.3.8",
+ "bundled": true
+ },
+ "through2": {
+ "version": "2.0.3",
+ "bundled": true,
+ "requires": {
+ "readable-stream": "2.3.6",
+ "xtend": "4.0.1"
+ }
+ },
+ "timers-browserify": {
+ "version": "1.4.2",
+ "bundled": true,
+ "requires": {
+ "process": "0.11.10"
+ }
+ },
+ "tiny-lr": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "body": "5.1.0",
+ "debug": "3.1.0",
+ "faye-websocket": "0.10.0",
+ "livereload-js": "2.3.0",
+ "object-assign": "4.1.1",
+ "qs": "6.5.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "bundled": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "tty-browserify": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "bundled": true
+ },
+ "uglify-js": {
+ "version": "2.8.29",
+ "bundled": true,
+ "requires": {
+ "source-map": "0.5.7",
+ "uglify-to-browserify": "1.0.2",
+ "yargs": "3.10.0"
+ }
+ },
+ "uglify-to-browserify": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "umd": {
+ "version": "3.0.3",
+ "bundled": true
+ },
+ "undeclared-identifiers": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "acorn-node": "1.5.2",
+ "get-assigned-identifiers": "1.2.0",
+ "simple-concat": "1.0.0",
+ "xtend": "4.0.1"
+ }
+ },
+ "underscore.string": {
+ "version": "3.2.3",
+ "bundled": true
+ },
+ "uri-path": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "bundled": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "bundled": true
+ }
+ }
+ },
+ "user-home": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "util": {
+ "version": "0.10.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "v8flags": {
+ "version": "2.1.1",
+ "bundled": true,
+ "requires": {
+ "user-home": "1.1.1"
+ }
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.3",
+ "bundled": true,
+ "requires": {
+ "spdx-correct": "3.0.0",
+ "spdx-expression-parse": "3.0.0"
+ }
+ },
+ "vm-browserify": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "watchify": {
+ "version": "3.11.0",
+ "bundled": true,
+ "requires": {
+ "anymatch": "1.3.2",
+ "browserify": "16.2.2",
+ "chokidar": "1.7.0",
+ "defined": "1.0.0",
+ "outpipe": "1.1.1",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ }
+ },
+ "websocket-driver": {
+ "version": "0.7.0",
+ "bundled": true,
+ "requires": {
+ "http-parser-js": "0.4.13",
+ "websocket-extensions": "0.1.3"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.3",
+ "bundled": true
+ },
+ "which": {
+ "version": "1.2.14",
+ "bundled": true,
+ "requires": {
+ "isexe": "2.0.0"
+ }
+ },
+ "window-size": {
+ "version": "0.1.0",
+ "bundled": true
+ },
+ "wordwrap": {
+ "version": "0.0.2",
+ "bundled": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "bundled": true
+ },
+ "yargs": {
+ "version": "3.10.0",
+ "bundled": true,
+ "requires": {
+ "camelcase": "1.2.1",
+ "cliui": "2.1.0",
+ "decamelize": "1.2.0",
+ "window-size": "0.1.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "1.2.1",
+ "bundled": true
+ }
+ }
+ }
+ }
+ },
"sn-stylekit": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.15.tgz",
@@ -5721,10 +9353,2973 @@
"dev": true
},
"standard-file-js": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.1.tgz",
- "integrity": "sha512-UEYlIiIMJJeqZSjmeJnLPlWH6SYa8x4HW9tuklF4mqSdGuZvUc1eJcUPiJ2n1Tv+rHEmKeX5IHAhSFOFoiSKag==",
- "dev": true
+ "version": "file:../../sf/sfjs",
+ "dev": true,
+ "dependencies": {
+ "JSONStream": {
+ "version": "1.3.3",
+ "bundled": true,
+ "requires": {
+ "jsonparse": "1.3.1",
+ "through": "2.3.8"
+ }
+ },
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "acorn": {
+ "version": "5.7.1",
+ "bundled": true
+ },
+ "acorn-dynamic-import": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "acorn": "5.7.1"
+ }
+ },
+ "acorn-node": {
+ "version": "1.5.2",
+ "bundled": true,
+ "requires": {
+ "acorn": "5.7.1",
+ "acorn-dynamic-import": "3.0.0",
+ "xtend": "4.0.1"
+ }
+ },
+ "align-text": {
+ "version": "0.1.4",
+ "bundled": true,
+ "requires": {
+ "kind-of": "3.2.2",
+ "longest": "1.0.1",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "bundled": true
+ },
+ "anymatch": {
+ "version": "1.3.2",
+ "bundled": true,
+ "requires": {
+ "micromatch": "2.3.11",
+ "normalize-path": "2.1.1"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "bundled": true,
+ "requires": {
+ "sprintf-js": "1.0.3"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "arr-flatten": "1.1.0"
+ }
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "array-filter": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "array-find-index": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "array-map": {
+ "version": "0.0.0",
+ "bundled": true
+ },
+ "array-reduce": {
+ "version": "0.0.0",
+ "bundled": true
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "asn1.js": {
+ "version": "4.10.1",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "assert": {
+ "version": "1.4.1",
+ "bundled": true
+ },
+ "assertion-error": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "async": {
+ "version": "1.5.2",
+ "bundled": true
+ },
+ "async-each": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "babel-cli": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-core": "6.26.3",
+ "babel-polyfill": "6.26.0",
+ "babel-register": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "chokidar": "1.7.0",
+ "commander": "2.15.1",
+ "convert-source-map": "1.5.1",
+ "fs-readdir-recursive": "1.1.0",
+ "glob": "7.1.2",
+ "lodash": "4.17.10",
+ "output-file-sync": "1.1.2",
+ "path-is-absolute": "1.0.1",
+ "slash": "1.0.0",
+ "source-map": "0.5.7",
+ "v8flags": "2.1.1"
+ }
+ },
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "esutils": "2.0.2",
+ "js-tokens": "3.0.2"
+ }
+ },
+ "babel-core": {
+ "version": "6.26.3",
+ "bundled": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "babel-generator": "6.26.1",
+ "babel-helpers": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-register": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "convert-source-map": "1.5.1",
+ "debug": "2.6.9",
+ "json5": "0.5.1",
+ "lodash": "4.17.10",
+ "minimatch": "3.0.4",
+ "path-is-absolute": "1.0.1",
+ "private": "0.1.8",
+ "slash": "1.0.0",
+ "source-map": "0.5.7"
+ }
+ },
+ "babel-generator": {
+ "version": "6.26.1",
+ "bundled": true,
+ "requires": {
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "detect-indent": "4.0.0",
+ "lodash": "4.17.10",
+ "source-map": "0.5.7",
+ "trim-right": "1.0.1"
+ }
+ },
+ "babel-helper-builder-binary-assignment-operator-visitor": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-explode-assignable-expression": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-call-delegate": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-hoist-variables": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-define-map": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-helper-explode-assignable-expression": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-function-name": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-get-function-arity": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-get-function-arity": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-hoist-variables": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-optimise-call-expression": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-regex": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-helper-remap-async-to-generator": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-replace-supers": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-optimise-call-expression": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helpers": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-messages": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-check-es2015-constants": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-syntax-async-functions": {
+ "version": "6.13.0",
+ "bundled": true
+ },
+ "babel-plugin-syntax-exponentiation-operator": {
+ "version": "6.13.0",
+ "bundled": true
+ },
+ "babel-plugin-syntax-trailing-function-commas": {
+ "version": "6.22.0",
+ "bundled": true
+ },
+ "babel-plugin-transform-async-to-generator": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-remap-async-to-generator": "6.24.1",
+ "babel-plugin-syntax-async-functions": "6.13.0",
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-arrow-functions": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoped-functions": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoping": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-plugin-transform-es2015-classes": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-define-map": "6.26.0",
+ "babel-helper-function-name": "6.24.1",
+ "babel-helper-optimise-call-expression": "6.24.1",
+ "babel-helper-replace-supers": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-computed-properties": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-destructuring": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-duplicate-keys": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-for-of": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-function-name": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-literals": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-amd": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-commonjs": {
+ "version": "6.26.2",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-strict-mode": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-systemjs": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-hoist-variables": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-umd": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-object-super": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-replace-supers": "6.24.1",
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-parameters": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-call-delegate": "6.24.1",
+ "babel-helper-get-function-arity": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-shorthand-properties": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-spread": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-sticky-regex": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-regex": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-template-literals": {
+ "version": "6.22.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-typeof-symbol": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-unicode-regex": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-regex": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "regexpu-core": "2.0.0"
+ }
+ },
+ "babel-plugin-transform-exponentiation-operator": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1",
+ "babel-plugin-syntax-exponentiation-operator": "6.13.0",
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-regenerator": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "regenerator-transform": "0.10.1"
+ }
+ },
+ "babel-plugin-transform-runtime": {
+ "version": "6.23.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-strict-mode": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-polyfill": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "core-js": "2.5.6"
+ }
+ },
+ "babel-preset-env": {
+ "version": "1.7.0",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-check-es2015-constants": "6.22.0",
+ "babel-plugin-syntax-trailing-function-commas": "6.22.0",
+ "babel-plugin-transform-async-to-generator": "6.24.1",
+ "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoping": "6.26.0",
+ "babel-plugin-transform-es2015-classes": "6.24.1",
+ "babel-plugin-transform-es2015-computed-properties": "6.24.1",
+ "babel-plugin-transform-es2015-destructuring": "6.23.0",
+ "babel-plugin-transform-es2015-duplicate-keys": "6.24.1",
+ "babel-plugin-transform-es2015-for-of": "6.23.0",
+ "babel-plugin-transform-es2015-function-name": "6.24.1",
+ "babel-plugin-transform-es2015-literals": "6.22.0",
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
+ "babel-plugin-transform-es2015-modules-umd": "6.24.1",
+ "babel-plugin-transform-es2015-object-super": "6.24.1",
+ "babel-plugin-transform-es2015-parameters": "6.24.1",
+ "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
+ "babel-plugin-transform-es2015-spread": "6.22.0",
+ "babel-plugin-transform-es2015-sticky-regex": "6.24.1",
+ "babel-plugin-transform-es2015-template-literals": "6.22.0",
+ "babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
+ "babel-plugin-transform-es2015-unicode-regex": "6.24.1",
+ "babel-plugin-transform-exponentiation-operator": "6.24.1",
+ "babel-plugin-transform-regenerator": "6.26.0",
+ "browserslist": "3.2.8",
+ "invariant": "2.2.4",
+ "semver": "5.5.0"
+ }
+ },
+ "babel-preset-es2015": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-check-es2015-constants": "6.22.0",
+ "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoping": "6.26.0",
+ "babel-plugin-transform-es2015-classes": "6.24.1",
+ "babel-plugin-transform-es2015-computed-properties": "6.24.1",
+ "babel-plugin-transform-es2015-destructuring": "6.23.0",
+ "babel-plugin-transform-es2015-duplicate-keys": "6.24.1",
+ "babel-plugin-transform-es2015-for-of": "6.23.0",
+ "babel-plugin-transform-es2015-function-name": "6.24.1",
+ "babel-plugin-transform-es2015-literals": "6.22.0",
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
+ "babel-plugin-transform-es2015-modules-umd": "6.24.1",
+ "babel-plugin-transform-es2015-object-super": "6.24.1",
+ "babel-plugin-transform-es2015-parameters": "6.24.1",
+ "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
+ "babel-plugin-transform-es2015-spread": "6.22.0",
+ "babel-plugin-transform-es2015-sticky-regex": "6.24.1",
+ "babel-plugin-transform-es2015-template-literals": "6.22.0",
+ "babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
+ "babel-plugin-transform-es2015-unicode-regex": "6.24.1",
+ "babel-plugin-transform-regenerator": "6.26.0"
+ }
+ },
+ "babel-preset-es2016": {
+ "version": "6.24.1",
+ "bundled": true,
+ "requires": {
+ "babel-plugin-transform-exponentiation-operator": "6.24.1"
+ }
+ },
+ "babel-register": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-core": "6.26.3",
+ "babel-runtime": "6.26.0",
+ "core-js": "2.5.6",
+ "home-or-tmp": "2.0.0",
+ "lodash": "4.17.10",
+ "mkdirp": "0.5.1",
+ "source-map-support": "0.4.18"
+ }
+ },
+ "babel-runtime": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "core-js": "2.5.6",
+ "regenerator-runtime": "0.11.1"
+ }
+ },
+ "babel-template": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-traverse": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "debug": "2.6.9",
+ "globals": "9.18.0",
+ "invariant": "2.2.4",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-types": {
+ "version": "6.26.0",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "esutils": "2.0.2",
+ "lodash": "4.17.10",
+ "to-fast-properties": "1.0.3"
+ }
+ },
+ "babelify": {
+ "version": "8.0.0",
+ "bundled": true
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "bundled": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "base64-js": {
+ "version": "1.3.0",
+ "bundled": true
+ },
+ "binary-extensions": {
+ "version": "1.11.0",
+ "bundled": true
+ },
+ "bn.js": {
+ "version": "4.11.8",
+ "bundled": true
+ },
+ "body": {
+ "version": "5.1.0",
+ "bundled": true,
+ "requires": {
+ "continuable-cache": "0.3.1",
+ "error": "7.0.2",
+ "raw-body": "1.1.7",
+ "safe-json-parse": "1.0.1"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "requires": {
+ "balanced-match": "1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "1.8.5",
+ "bundled": true,
+ "requires": {
+ "expand-range": "1.8.2",
+ "preserve": "0.2.0",
+ "repeat-element": "1.1.2"
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "browser-pack": {
+ "version": "6.1.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "combine-source-map": "0.8.0",
+ "defined": "1.0.0",
+ "safe-buffer": "5.1.2",
+ "through2": "2.0.3",
+ "umd": "3.0.3"
+ }
+ },
+ "browser-resolve": {
+ "version": "1.11.3",
+ "bundled": true,
+ "requires": {
+ "resolve": "1.1.7"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "bundled": true
+ },
+ "browserify": {
+ "version": "16.2.2",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "assert": "1.4.1",
+ "browser-pack": "6.1.0",
+ "browser-resolve": "1.11.3",
+ "browserify-zlib": "0.2.0",
+ "buffer": "5.1.0",
+ "cached-path-relative": "1.0.1",
+ "concat-stream": "1.6.2",
+ "console-browserify": "1.1.0",
+ "constants-browserify": "1.0.0",
+ "crypto-browserify": "3.12.0",
+ "defined": "1.0.0",
+ "deps-sort": "2.0.0",
+ "domain-browser": "1.2.0",
+ "duplexer2": "0.1.4",
+ "events": "2.1.0",
+ "glob": "7.1.2",
+ "has": "1.0.1",
+ "htmlescape": "1.1.1",
+ "https-browserify": "1.0.0",
+ "inherits": "2.0.3",
+ "insert-module-globals": "7.2.0",
+ "labeled-stream-splicer": "2.0.1",
+ "mkdirp": "0.5.1",
+ "module-deps": "6.1.0",
+ "os-browserify": "0.3.0",
+ "parents": "1.0.1",
+ "path-browserify": "0.0.1",
+ "process": "0.11.10",
+ "punycode": "1.4.1",
+ "querystring-es3": "0.2.1",
+ "read-only-stream": "2.0.0",
+ "readable-stream": "2.3.6",
+ "resolve": "1.1.7",
+ "shasum": "1.0.2",
+ "shell-quote": "1.6.1",
+ "stream-browserify": "2.0.1",
+ "stream-http": "2.8.3",
+ "string_decoder": "1.1.1",
+ "subarg": "1.0.0",
+ "syntax-error": "1.4.0",
+ "through2": "2.0.3",
+ "timers-browserify": "1.4.2",
+ "tty-browserify": "0.0.1",
+ "url": "0.11.0",
+ "util": "0.10.4",
+ "vm-browserify": "1.1.0",
+ "xtend": "4.0.1"
+ }
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "bundled": true,
+ "requires": {
+ "buffer-xor": "1.0.3",
+ "cipher-base": "1.0.4",
+ "create-hash": "1.2.0",
+ "evp_bytestokey": "1.0.3",
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "browserify-cache-api": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "async": "1.5.2",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "browserify-aes": "1.2.0",
+ "browserify-des": "1.0.1",
+ "evp_bytestokey": "1.0.3"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "des.js": "1.0.0",
+ "inherits": "2.0.3"
+ }
+ },
+ "browserify-incremental": {
+ "version": "3.1.1",
+ "bundled": true,
+ "requires": {
+ "browserify-cache-api": "3.0.1",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "randombytes": "2.0.6"
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "browserify-rsa": "4.0.1",
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "elliptic": "6.4.0",
+ "inherits": "2.0.3",
+ "parse-asn1": "5.1.1"
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "bundled": true,
+ "requires": {
+ "pako": "1.0.6"
+ }
+ },
+ "browserslist": {
+ "version": "3.2.8",
+ "bundled": true,
+ "requires": {
+ "caniuse-lite": "1.0.30000844",
+ "electron-to-chromium": "1.3.47"
+ }
+ },
+ "buffer": {
+ "version": "5.1.0",
+ "bundled": true,
+ "requires": {
+ "base64-js": "1.3.0",
+ "ieee754": "1.1.12"
+ }
+ },
+ "buffer-from": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "bytes": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "cached-path-relative": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "camelcase": {
+ "version": "2.1.1",
+ "bundled": true
+ },
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "camelcase": "2.1.1",
+ "map-obj": "1.0.1"
+ }
+ },
+ "caniuse-lite": {
+ "version": "1.0.30000844",
+ "bundled": true
+ },
+ "center-align": {
+ "version": "0.1.3",
+ "bundled": true,
+ "requires": {
+ "align-text": "0.1.4",
+ "lazy-cache": "1.0.4"
+ }
+ },
+ "chai": {
+ "version": "4.1.2",
+ "bundled": true,
+ "requires": {
+ "assertion-error": "1.1.0",
+ "check-error": "1.0.2",
+ "deep-eql": "3.0.1",
+ "get-func-name": "2.0.0",
+ "pathval": "1.1.0",
+ "type-detect": "4.0.8"
+ }
+ },
+ "chai-as-promised": {
+ "version": "7.1.1",
+ "bundled": true,
+ "requires": {
+ "check-error": "1.0.2"
+ }
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "bundled": true,
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ }
+ },
+ "check-error": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "chokidar": {
+ "version": "1.7.0",
+ "bundled": true,
+ "requires": {
+ "anymatch": "1.3.2",
+ "async-each": "1.0.1",
+ "fsevents": "1.2.4",
+ "glob-parent": "2.0.0",
+ "inherits": "2.0.3",
+ "is-binary-path": "1.0.1",
+ "is-glob": "2.0.1",
+ "path-is-absolute": "1.0.1",
+ "readdirp": "2.1.0"
+ }
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "center-align": "0.1.3",
+ "right-align": "0.1.3",
+ "wordwrap": "0.0.2"
+ }
+ },
+ "coffeescript": {
+ "version": "1.10.0",
+ "bundled": true
+ },
+ "colors": {
+ "version": "1.1.2",
+ "bundled": true
+ },
+ "combine-source-map": {
+ "version": "0.8.0",
+ "bundled": true,
+ "requires": {
+ "inline-source-map": "0.6.2",
+ "lodash.memoize": "3.0.4",
+ "source-map": "0.5.7"
+ }
+ },
+ "commander": {
+ "version": "2.15.1",
+ "bundled": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "bundled": true,
+ "requires": {
+ "buffer-from": "1.0.0",
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6",
+ "typedarray": "0.0.6"
+ }
+ },
+ "connect": {
+ "version": "3.6.6",
+ "bundled": true,
+ "requires": {
+ "debug": "2.6.9",
+ "finalhandler": "1.1.0",
+ "parseurl": "1.3.2",
+ "utils-merge": "1.0.1"
+ }
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "date-now": "0.1.4"
+ }
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "continuable-cache": {
+ "version": "0.3.1",
+ "bundled": true
+ },
+ "convert-source-map": {
+ "version": "1.5.1",
+ "bundled": true
+ },
+ "core-js": {
+ "version": "2.5.6",
+ "bundled": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "create-ecdh": {
+ "version": "4.0.3",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "elliptic": "6.4.0"
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "bundled": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "inherits": "2.0.3",
+ "md5.js": "1.3.4",
+ "ripemd160": "2.0.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "bundled": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "create-hash": "1.2.0",
+ "inherits": "2.0.3",
+ "ripemd160": "2.0.2",
+ "safe-buffer": "5.1.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "bundled": true,
+ "requires": {
+ "browserify-cipher": "1.0.1",
+ "browserify-sign": "4.0.4",
+ "create-ecdh": "4.0.3",
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "diffie-hellman": "5.0.3",
+ "inherits": "2.0.3",
+ "pbkdf2": "3.0.16",
+ "public-encrypt": "4.0.2",
+ "randombytes": "2.0.6",
+ "randomfill": "1.0.4"
+ }
+ },
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "bundled": true,
+ "requires": {
+ "array-find-index": "1.0.2"
+ }
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "bundled": true
+ },
+ "dateformat": {
+ "version": "1.0.12",
+ "bundled": true,
+ "requires": {
+ "get-stdin": "4.0.1",
+ "meow": "3.7.0"
+ }
+ },
+ "debug": {
+ "version": "2.6.9",
+ "bundled": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "bundled": true
+ },
+ "deep-eql": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "define-properties": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "foreach": "2.0.5",
+ "object-keys": "1.0.11"
+ }
+ },
+ "defined": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "depd": {
+ "version": "1.1.2",
+ "bundled": true
+ },
+ "deps-sort": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "shasum": "1.0.2",
+ "subarg": "1.0.0",
+ "through2": "2.0.3"
+ }
+ },
+ "des.js": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "bundled": true
+ },
+ "detect-indent": {
+ "version": "4.0.0",
+ "bundled": true,
+ "requires": {
+ "repeating": "2.0.1"
+ }
+ },
+ "detective": {
+ "version": "5.1.0",
+ "bundled": true,
+ "requires": {
+ "acorn-node": "1.5.2",
+ "defined": "1.0.0"
+ }
+ },
+ "diff": {
+ "version": "3.5.0",
+ "bundled": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "miller-rabin": "4.0.1",
+ "randombytes": "2.0.6"
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "bundled": true
+ },
+ "duplexer2": {
+ "version": "0.1.4",
+ "bundled": true,
+ "requires": {
+ "readable-stream": "2.3.6"
+ }
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "electron-to-chromium": {
+ "version": "1.3.47",
+ "bundled": true
+ },
+ "elliptic": {
+ "version": "6.4.0",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "brorand": "1.1.0",
+ "hash.js": "1.1.4",
+ "hmac-drbg": "1.0.1",
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1",
+ "minimalistic-crypto-utils": "1.0.1"
+ }
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "error": {
+ "version": "7.0.2",
+ "bundled": true,
+ "requires": {
+ "string-template": "0.2.1",
+ "xtend": "4.0.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.1",
+ "bundled": true,
+ "requires": {
+ "is-arrayish": "0.2.1"
+ }
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "bundled": true
+ },
+ "esprima": {
+ "version": "2.7.3",
+ "bundled": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "bundled": true
+ },
+ "etag": {
+ "version": "1.8.1",
+ "bundled": true
+ },
+ "eventemitter2": {
+ "version": "0.4.14",
+ "bundled": true
+ },
+ "events": {
+ "version": "2.1.0",
+ "bundled": true
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "bundled": true,
+ "requires": {
+ "md5.js": "1.3.4",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "exit": {
+ "version": "0.1.2",
+ "bundled": true
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "bundled": true,
+ "requires": {
+ "is-posix-bracket": "0.1.1"
+ }
+ },
+ "expand-range": {
+ "version": "1.8.2",
+ "bundled": true,
+ "requires": {
+ "fill-range": "2.2.4"
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "bundled": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ },
+ "faye-websocket": {
+ "version": "0.10.0",
+ "bundled": true,
+ "requires": {
+ "websocket-driver": "0.7.0"
+ }
+ },
+ "figures": {
+ "version": "1.7.0",
+ "bundled": true,
+ "requires": {
+ "escape-string-regexp": "1.0.5",
+ "object-assign": "4.1.1"
+ }
+ },
+ "filename-regex": {
+ "version": "2.0.1",
+ "bundled": true
+ },
+ "fill-range": {
+ "version": "2.2.4",
+ "bundled": true,
+ "requires": {
+ "is-number": "2.1.0",
+ "isobject": "2.1.0",
+ "randomatic": "3.0.0",
+ "repeat-element": "1.1.2",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "on-finished": "2.3.0",
+ "parseurl": "1.3.2",
+ "statuses": "1.3.1",
+ "unpipe": "1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "path-exists": "2.1.0",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "findup-sync": {
+ "version": "0.3.0",
+ "bundled": true
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "for-own": {
+ "version": "0.1.5",
+ "bundled": true,
+ "requires": {
+ "for-in": "1.0.2"
+ }
+ },
+ "foreach": {
+ "version": "2.0.5",
+ "bundled": true
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "bundled": true
+ },
+ "fs-readdir-recursive": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "fsevents": {
+ "version": "1.2.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "nan": "2.10.0"
+ }
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "gaze": {
+ "version": "1.1.3",
+ "bundled": true,
+ "requires": {
+ "globule": "1.2.0"
+ }
+ },
+ "get-assigned-identifiers": {
+ "version": "1.2.0",
+ "bundled": true
+ },
+ "get-func-name": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "bundled": true
+ },
+ "getobject": {
+ "version": "0.1.0",
+ "bundled": true
+ },
+ "glob": {
+ "version": "7.1.2",
+ "bundled": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "glob-base": {
+ "version": "0.3.0",
+ "bundled": true,
+ "requires": {
+ "glob-parent": "2.0.0",
+ "is-glob": "2.0.1"
+ }
+ },
+ "glob-parent": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "is-glob": "2.0.1"
+ }
+ },
+ "globals": {
+ "version": "9.18.0",
+ "bundled": true
+ },
+ "globule": {
+ "version": "1.2.0",
+ "bundled": true,
+ "requires": {
+ "glob": "7.1.2",
+ "lodash": "4.17.10",
+ "minimatch": "3.0.4"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "bundled": true
+ },
+ "growl": {
+ "version": "1.10.5",
+ "bundled": true
+ },
+ "grunt": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "coffeescript": "1.10.0",
+ "dateformat": "1.0.12",
+ "eventemitter2": "0.4.14",
+ "exit": "0.1.2",
+ "findup-sync": "0.3.0",
+ "grunt-known-options": "1.1.0",
+ "grunt-legacy-log": "1.0.2",
+ "grunt-legacy-util": "1.0.0",
+ "iconv-lite": "0.4.23",
+ "js-yaml": "3.5.5",
+ "minimatch": "3.0.4",
+ "nopt": "3.0.6",
+ "path-is-absolute": "1.0.1",
+ "rimraf": "2.2.8"
+ }
+ },
+ "grunt-babel": {
+ "version": "6.0.0",
+ "bundled": true,
+ "requires": {
+ "babel-core": "6.26.3"
+ }
+ },
+ "grunt-browserify": {
+ "version": "5.3.0",
+ "bundled": true,
+ "requires": {
+ "browserify": "16.2.2",
+ "browserify-incremental": "3.1.1",
+ "glob": "7.1.2",
+ "lodash": "4.17.10",
+ "resolve": "1.1.7",
+ "watchify": "3.11.0"
+ }
+ },
+ "grunt-contrib-concat": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "source-map": "0.5.7"
+ }
+ },
+ "grunt-contrib-uglify": {
+ "version": "2.3.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "maxmin": "1.1.0",
+ "object.assign": "4.1.0",
+ "uglify-js": "2.8.29",
+ "uri-path": "1.0.0"
+ }
+ },
+ "grunt-contrib-watch": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "gaze": "1.1.3",
+ "lodash": "4.17.10",
+ "tiny-lr": "1.1.1"
+ }
+ },
+ "grunt-known-options": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "grunt-legacy-log": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "colors": "1.1.2",
+ "grunt-legacy-log-utils": "1.0.0",
+ "hooker": "0.2.3",
+ "lodash": "4.17.10"
+ }
+ },
+ "grunt-legacy-log-utils": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3"
+ }
+ },
+ "grunt-legacy-util": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "async": "1.5.2",
+ "exit": "0.1.2",
+ "getobject": "0.1.0",
+ "hooker": "0.2.3",
+ "underscore.string": "3.2.3",
+ "which": "1.2.14"
+ }
+ },
+ "grunt-newer": {
+ "version": "1.3.0",
+ "bundled": true,
+ "requires": {
+ "async": "1.5.2"
+ }
+ },
+ "gzip-size": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "concat-stream": "1.6.2"
+ }
+ },
+ "has": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "function-bind": "1.1.1"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "he": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "hash.js": "1.1.4",
+ "minimalistic-assert": "1.0.1",
+ "minimalistic-crypto-utils": "1.0.1"
+ }
+ },
+ "home-or-tmp": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "os-homedir": "1.0.2",
+ "os-tmpdir": "1.0.2"
+ }
+ },
+ "hooker": {
+ "version": "0.2.3",
+ "bundled": true
+ },
+ "hosted-git-info": {
+ "version": "2.6.0",
+ "bundled": true
+ },
+ "htmlescape": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "http-errors": {
+ "version": "1.6.3",
+ "bundled": true,
+ "requires": {
+ "depd": "1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0"
+ }
+ },
+ "http-parser-js": {
+ "version": "0.4.13",
+ "bundled": true
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "iconv-lite": {
+ "version": "0.4.23",
+ "bundled": true,
+ "requires": {
+ "safer-buffer": "2.1.2"
+ }
+ },
+ "ieee754": {
+ "version": "1.1.12",
+ "bundled": true
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "repeating": "2.0.1"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true
+ },
+ "inline-source-map": {
+ "version": "0.6.2",
+ "bundled": true,
+ "requires": {
+ "source-map": "0.5.7"
+ }
+ },
+ "insert-module-globals": {
+ "version": "7.2.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "acorn-node": "1.5.2",
+ "combine-source-map": "0.8.0",
+ "concat-stream": "1.6.2",
+ "is-buffer": "1.1.6",
+ "path-is-absolute": "1.0.1",
+ "process": "0.11.10",
+ "through2": "2.0.3",
+ "undeclared-identifiers": "1.1.2",
+ "xtend": "4.0.1"
+ }
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "bundled": true,
+ "requires": {
+ "loose-envify": "1.3.1"
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "binary-extensions": "1.11.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "bundled": true
+ },
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "builtin-modules": "1.1.1"
+ }
+ },
+ "is-dotfile": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "is-equal-shallow": {
+ "version": "0.1.3",
+ "bundled": true,
+ "requires": {
+ "is-primitive": "2.0.0"
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "bundled": true
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "is-finite": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "number-is-nan": "1.0.1"
+ }
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ },
+ "is-number": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ }
+ },
+ "is-posix-bracket": {
+ "version": "0.1.1",
+ "bundled": true
+ },
+ "is-primitive": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "isobject": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "bundled": true
+ },
+ "js-yaml": {
+ "version": "3.5.5",
+ "bundled": true,
+ "requires": {
+ "argparse": "1.0.10",
+ "esprima": "2.7.3"
+ }
+ },
+ "jsesc": {
+ "version": "0.5.0",
+ "bundled": true
+ },
+ "json-stable-stringify": {
+ "version": "0.0.1",
+ "bundled": true,
+ "requires": {
+ "jsonify": "0.0.0"
+ }
+ },
+ "json5": {
+ "version": "0.5.1",
+ "bundled": true
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "bundled": true
+ },
+ "jsonparse": {
+ "version": "1.3.1",
+ "bundled": true
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "bundled": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ },
+ "labeled-stream-splicer": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "stream-splicer": "2.0.0"
+ }
+ },
+ "lazy-cache": {
+ "version": "1.0.4",
+ "bundled": true
+ },
+ "livereload-js": {
+ "version": "2.3.0",
+ "bundled": true
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "parse-json": "2.2.0",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1",
+ "strip-bom": "2.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.10",
+ "bundled": true
+ },
+ "lodash.memoize": {
+ "version": "3.0.4",
+ "bundled": true
+ },
+ "longest": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "loose-envify": {
+ "version": "1.3.1",
+ "bundled": true,
+ "requires": {
+ "js-tokens": "3.0.2"
+ }
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "bundled": true,
+ "requires": {
+ "currently-unhandled": "0.4.1",
+ "signal-exit": "3.0.2"
+ }
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "math-random": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "maxmin": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "figures": "1.7.0",
+ "gzip-size": "1.0.0",
+ "pretty-bytes": "1.0.4"
+ }
+ },
+ "md5.js": {
+ "version": "1.3.4",
+ "bundled": true,
+ "requires": {
+ "hash-base": "3.0.4",
+ "inherits": "2.0.3"
+ }
+ },
+ "meow": {
+ "version": "3.7.0",
+ "bundled": true,
+ "requires": {
+ "camelcase-keys": "2.1.0",
+ "decamelize": "1.2.0",
+ "loud-rejection": "1.6.0",
+ "map-obj": "1.0.1",
+ "normalize-package-data": "2.4.0",
+ "object-assign": "4.1.1",
+ "read-pkg-up": "1.0.1",
+ "redent": "1.0.0",
+ "trim-newlines": "1.0.0"
+ }
+ },
+ "micromatch": {
+ "version": "2.3.11",
+ "bundled": true,
+ "requires": {
+ "arr-diff": "2.0.0",
+ "array-unique": "0.2.1",
+ "braces": "1.8.5",
+ "expand-brackets": "0.1.5",
+ "extglob": "0.3.2",
+ "filename-regex": "2.0.1",
+ "is-extglob": "1.0.0",
+ "is-glob": "2.0.1",
+ "kind-of": "3.2.2",
+ "normalize-path": "2.1.1",
+ "object.omit": "2.0.1",
+ "parse-glob": "3.0.4",
+ "regex-cache": "0.4.4"
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "brorand": "1.1.0"
+ }
+ },
+ "mime": {
+ "version": "1.4.1",
+ "bundled": true
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "1.1.11"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "mocha": {
+ "version": "5.2.0",
+ "bundled": true,
+ "requires": {
+ "browser-stdout": "1.3.1",
+ "commander": "2.15.1",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "glob": "7.1.2",
+ "growl": "1.10.5",
+ "he": "1.1.1",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.1"
+ }
+ },
+ "module-deps": {
+ "version": "6.1.0",
+ "bundled": true,
+ "requires": {
+ "JSONStream": "1.3.3",
+ "browser-resolve": "1.11.3",
+ "cached-path-relative": "1.0.1",
+ "concat-stream": "1.6.2",
+ "defined": "1.0.0",
+ "detective": "5.1.0",
+ "duplexer2": "0.1.4",
+ "inherits": "2.0.3",
+ "parents": "1.0.1",
+ "readable-stream": "2.3.6",
+ "stream-combiner2": "1.1.1",
+ "subarg": "1.0.0",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "nan": {
+ "version": "2.10.0",
+ "bundled": true,
+ "optional": true
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "bundled": true,
+ "requires": {
+ "abbrev": "1.1.1"
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.4.0",
+ "bundled": true,
+ "requires": {
+ "hosted-git-info": "2.6.0",
+ "is-builtin-module": "1.0.0",
+ "semver": "5.5.0",
+ "validate-npm-package-license": "3.0.3"
+ }
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "bundled": true,
+ "requires": {
+ "remove-trailing-separator": "1.1.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true
+ },
+ "object-keys": {
+ "version": "1.0.11",
+ "bundled": true
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "bundled": true,
+ "requires": {
+ "define-properties": "1.1.2",
+ "function-bind": "1.1.1",
+ "has-symbols": "1.0.0",
+ "object-keys": "1.0.11"
+ }
+ },
+ "object.omit": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "for-own": "0.1.5",
+ "is-extendable": "0.1.1"
+ }
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "bundled": true,
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "bundled": true
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "outpipe": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "shell-quote": "1.6.1"
+ }
+ },
+ "output-file-sync": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "mkdirp": "0.5.1",
+ "object-assign": "4.1.1"
+ }
+ },
+ "pako": {
+ "version": "1.0.6",
+ "bundled": true
+ },
+ "parents": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "path-platform": "0.11.15"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.1",
+ "bundled": true,
+ "requires": {
+ "asn1.js": "4.10.1",
+ "browserify-aes": "1.2.0",
+ "create-hash": "1.2.0",
+ "evp_bytestokey": "1.0.3",
+ "pbkdf2": "3.0.16"
+ }
+ },
+ "parse-glob": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "glob-base": "0.3.0",
+ "is-dotfile": "1.0.3",
+ "is-extglob": "1.0.0",
+ "is-glob": "2.0.1"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "bundled": true,
+ "requires": {
+ "error-ex": "1.3.1"
+ }
+ },
+ "parseurl": {
+ "version": "1.3.2",
+ "bundled": true
+ },
+ "path-browserify": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "path-parse": {
+ "version": "1.0.5",
+ "bundled": true
+ },
+ "path-platform": {
+ "version": "0.11.15",
+ "bundled": true
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "pathval": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "pbkdf2": {
+ "version": "3.0.16",
+ "bundled": true,
+ "requires": {
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "ripemd160": "2.0.2",
+ "safe-buffer": "5.1.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "bundled": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "bundled": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "pinkie": "2.0.4"
+ }
+ },
+ "preserve": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "pretty-bytes": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "get-stdin": "4.0.1",
+ "meow": "3.7.0"
+ }
+ },
+ "private": {
+ "version": "0.1.8",
+ "bundled": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "bundled": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "public-encrypt": {
+ "version": "4.0.2",
+ "bundled": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "browserify-rsa": "4.0.1",
+ "create-hash": "1.2.0",
+ "parse-asn1": "5.1.1",
+ "randombytes": "2.0.6"
+ }
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "bundled": true
+ },
+ "qs": {
+ "version": "6.5.2",
+ "bundled": true
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "randomatic": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "math-random": "1.0.1"
+ }
+ },
+ "randombytes": {
+ "version": "2.0.6",
+ "bundled": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "randombytes": "2.0.6",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "range-parser": {
+ "version": "1.2.0",
+ "bundled": true
+ },
+ "raw-body": {
+ "version": "1.1.7",
+ "bundled": true,
+ "requires": {
+ "bytes": "1.0.0"
+ }
+ },
+ "read-only-stream": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "readable-stream": "2.3.6"
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "bundled": true,
+ "requires": {
+ "load-json-file": "1.1.0",
+ "normalize-package-data": "2.4.0",
+ "path-type": "1.1.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "find-up": "1.1.2",
+ "read-pkg": "1.1.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "2.0.0",
+ "safe-buffer": "5.1.2",
+ "string_decoder": "1.1.1",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "readdirp": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "minimatch": "3.0.4",
+ "readable-stream": "2.3.6",
+ "set-immediate-shim": "1.0.1"
+ }
+ },
+ "redent": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "indent-string": "2.1.0",
+ "strip-indent": "1.0.1"
+ }
+ },
+ "regenerate": {
+ "version": "1.4.0",
+ "bundled": true
+ },
+ "regenerator-runtime": {
+ "version": "0.11.1",
+ "bundled": true
+ },
+ "regenerator-transform": {
+ "version": "0.10.1",
+ "bundled": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "private": "0.1.8"
+ }
+ },
+ "regex-cache": {
+ "version": "0.4.4",
+ "bundled": true,
+ "requires": {
+ "is-equal-shallow": "0.1.3"
+ }
+ },
+ "regexpu-core": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "regenerate": "1.4.0",
+ "regjsgen": "0.2.0",
+ "regjsparser": "0.1.5"
+ }
+ },
+ "regjsgen": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "regjsparser": {
+ "version": "0.1.5",
+ "bundled": true,
+ "requires": {
+ "jsesc": "0.5.0"
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "repeat-element": {
+ "version": "1.1.2",
+ "bundled": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "bundled": true
+ },
+ "repeating": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "is-finite": "1.0.2"
+ }
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "bundled": true
+ },
+ "right-align": {
+ "version": "0.1.3",
+ "bundled": true,
+ "requires": {
+ "align-text": "0.1.4"
+ }
+ },
+ "rimraf": {
+ "version": "2.2.8",
+ "bundled": true
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "bundled": true,
+ "requires": {
+ "hash-base": "3.0.4",
+ "inherits": "2.0.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ },
+ "safe-json-parse": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true
+ },
+ "semver": {
+ "version": "5.5.0",
+ "bundled": true
+ },
+ "send": {
+ "version": "0.16.2",
+ "bundled": true,
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "1.1.2",
+ "destroy": "1.0.4",
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "etag": "1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "1.6.3",
+ "mime": "1.4.1",
+ "ms": "2.0.0",
+ "on-finished": "2.3.0",
+ "range-parser": "1.2.0"
+ }
+ },
+ "serve-static": {
+ "version": "1.13.2",
+ "bundled": true,
+ "requires": {
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "parseurl": "1.3.2",
+ "send": "0.16.2"
+ }
+ },
+ "set-immediate-shim": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "shasum": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "json-stable-stringify": "0.0.1",
+ "sha.js": "2.4.11"
+ }
+ },
+ "shell-quote": {
+ "version": "1.6.1",
+ "bundled": true,
+ "requires": {
+ "array-filter": "0.0.1",
+ "array-map": "0.0.0",
+ "array-reduce": "0.0.0",
+ "jsonify": "0.0.0"
+ }
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true
+ },
+ "simple-concat": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "slash": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "bundled": true
+ },
+ "source-map-support": {
+ "version": "0.4.18",
+ "bundled": true,
+ "requires": {
+ "source-map": "0.5.7"
+ }
+ },
+ "spdx-correct": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "spdx-expression-parse": "3.0.0",
+ "spdx-license-ids": "3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.1.0",
+ "bundled": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "spdx-exceptions": "2.1.0",
+ "spdx-license-ids": "3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "statuses": {
+ "version": "1.3.1",
+ "bundled": true
+ },
+ "stream-browserify": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "stream-combiner2": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "duplexer2": "0.1.4",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "bundled": true,
+ "requires": {
+ "builtin-status-codes": "3.0.0",
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6",
+ "to-arraybuffer": "1.0.1",
+ "xtend": "4.0.1"
+ }
+ },
+ "stream-splicer": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "string-template": {
+ "version": "0.2.1",
+ "bundled": true
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "is-utf8": "0.2.1"
+ }
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "get-stdin": "4.0.1"
+ }
+ },
+ "subarg": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "syntax-error": {
+ "version": "1.4.0",
+ "bundled": true,
+ "requires": {
+ "acorn-node": "1.5.2"
+ }
+ },
+ "through": {
+ "version": "2.3.8",
+ "bundled": true
+ },
+ "through2": {
+ "version": "2.0.3",
+ "bundled": true,
+ "requires": {
+ "readable-stream": "2.3.6",
+ "xtend": "4.0.1"
+ }
+ },
+ "timers-browserify": {
+ "version": "1.4.2",
+ "bundled": true,
+ "requires": {
+ "process": "0.11.10"
+ }
+ },
+ "tiny-lr": {
+ "version": "1.1.1",
+ "bundled": true,
+ "requires": {
+ "body": "5.1.0",
+ "faye-websocket": "0.10.0",
+ "livereload-js": "2.3.0",
+ "object-assign": "4.1.1",
+ "qs": "6.5.2"
+ }
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "bundled": true
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "tty-browserify": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "type-detect": {
+ "version": "4.0.8",
+ "bundled": true
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "bundled": true
+ },
+ "uglify-js": {
+ "version": "2.8.29",
+ "bundled": true,
+ "requires": {
+ "source-map": "0.5.7",
+ "uglify-to-browserify": "1.0.2",
+ "yargs": "3.10.0"
+ }
+ },
+ "uglify-to-browserify": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "umd": {
+ "version": "3.0.3",
+ "bundled": true
+ },
+ "undeclared-identifiers": {
+ "version": "1.1.2",
+ "bundled": true,
+ "requires": {
+ "acorn-node": "1.5.2",
+ "get-assigned-identifiers": "1.2.0",
+ "simple-concat": "1.0.0",
+ "xtend": "4.0.1"
+ }
+ },
+ "underscore.string": {
+ "version": "3.2.3",
+ "bundled": true
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "uri-path": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "bundled": true,
+ "requires": {
+ "querystring": "0.2.0"
+ }
+ },
+ "user-home": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "util": {
+ "version": "0.10.4",
+ "bundled": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "v8flags": {
+ "version": "2.1.1",
+ "bundled": true,
+ "requires": {
+ "user-home": "1.1.1"
+ }
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.3",
+ "bundled": true,
+ "requires": {
+ "spdx-correct": "3.0.0",
+ "spdx-expression-parse": "3.0.0"
+ }
+ },
+ "vm-browserify": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "watchify": {
+ "version": "3.11.0",
+ "bundled": true,
+ "requires": {
+ "anymatch": "1.3.2",
+ "browserify": "16.2.2",
+ "chokidar": "1.7.0",
+ "defined": "1.0.0",
+ "outpipe": "1.1.1",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ }
+ },
+ "websocket-driver": {
+ "version": "0.7.0",
+ "bundled": true,
+ "requires": {
+ "http-parser-js": "0.4.13",
+ "websocket-extensions": "0.1.3"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.3",
+ "bundled": true
+ },
+ "which": {
+ "version": "1.2.14",
+ "bundled": true,
+ "requires": {
+ "isexe": "2.0.0"
+ }
+ },
+ "window-size": {
+ "version": "0.1.0",
+ "bundled": true
+ },
+ "wordwrap": {
+ "version": "0.0.2",
+ "bundled": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "bundled": true
+ },
+ "yargs": {
+ "version": "3.10.0",
+ "bundled": true,
+ "requires": {
+ "cliui": "2.1.0",
+ "decamelize": "1.2.0",
+ "window-size": "0.1.0"
+ }
+ }
+ }
},
"statuses": {
"version": "1.5.0",
@@ -6013,6 +12608,12 @@
"dev": true,
"optional": true
},
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
"type-is": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
diff --git a/package.json b/package.json
index 7aa34d9db..b174e55b0 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,12 @@
"karma-jasmine": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.2",
"sn-stylekit": "1.0.15",
- "standard-file-js": "0.3.1"
+ "standard-file-js": "0.3.4",
+ "sn-models": "0.1.1",
+ "connect": "^3.6.6",
+ "mocha": "^5.2.0",
+ "serve-static": "^1.13.2",
+ "chai": "^4.1.2"
},
"license": "GPL-3.0"
}
diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager
index 8784a82e1..68b78744c 160000
--- a/public/extensions/extensions-manager
+++ b/public/extensions/extensions-manager
@@ -1 +1 @@
-Subproject commit 8784a82e186d3e254c17dbf5a31bd61cd9dadf45
+Subproject commit 68b78744cbcac3812f07dea59a407b670ab4c5e7
diff --git a/test/controllers/.keep b/test/controllers/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/controllers/apikey_controller_test.rb b/test/controllers/apikey_controller_test.rb
deleted file mode 100644
index 820a42899..000000000
--- a/test/controllers/apikey_controller_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class ApikeyControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/test/controllers/names_controller_test.rb b/test/controllers/names_controller_test.rb
deleted file mode 100644
index fcb52fbec..000000000
--- a/test/controllers/names_controller_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class NamesControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/test/controllers/proto_controller_test.rb b/test/controllers/proto_controller_test.rb
deleted file mode 100644
index f8ce50c03..000000000
--- a/test/controllers/proto_controller_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class ProtoControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/test/fixtures/.keep b/test/fixtures/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/fixtures/api_keys.yml b/test/fixtures/api_keys.yml
deleted file mode 100644
index bbd2b2b65..000000000
--- a/test/fixtures/api_keys.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-one:
- access_token: MyString
-
-two:
- access_token: MyString
diff --git a/test/fixtures/names.yml b/test/fixtures/names.yml
deleted file mode 100644
index 861ee7df1..000000000
--- a/test/fixtures/names.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-one:
- text: MyString
-
-two:
- text: MyString
diff --git a/test/helpers/.keep b/test/helpers/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/integration/.keep b/test/integration/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/javascripts/filters/DateFilter_spec.js b/test/javascripts/filters/DateFilter_spec.js
index cdfa55a55..b561b03a7 100644
--- a/test/javascripts/filters/DateFilter_spec.js
+++ b/test/javascripts/filters/DateFilter_spec.js
@@ -6,11 +6,6 @@ describe("date filter", function() {
$filter = _$filter_;
}));
- it('returns a defined time', function() {
- var date = $filter('appDate');
- expect(date(Date())).toBeDefined();
- });
-
it('returns time', function() {
var dateTime = $filter('appDateTime');
expect(dateTime(Date())).toBeDefined();
diff --git a/test/mailers/.keep b/test/mailers/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/mocha/lib/factory.js b/test/mocha/lib/factory.js
new file mode 100644
index 000000000..42efc76e3
--- /dev/null
+++ b/test/mocha/lib/factory.js
@@ -0,0 +1,87 @@
+import '../../../vendor/assets/javascripts/compiled.js';
+import '../../../node_modules/chai/chai.js';
+import '../vendor/chai-as-promised-built.js';
+import '../../../vendor/assets/javascripts/lodash/lodash.custom.js';
+
+import LocalStorageManager from './localStorageManager.js';
+const sf_default = new StandardFile();
+SFItem.AppDomain = "org.standardnotes.sn";
+
+var _globalStorageManager = null;
+var _globalHttpManager = null;
+var _globalAuthManager = null;
+var _globalModelManager = null;
+var _globalStandardFile = null;
+
+export default class Factory {
+
+ static initialize() {
+ this.globalStorageManager();
+ this.globalHttpManager();
+ this.globalAuthManager();
+ this.globalModelManager();
+ }
+
+ static globalStorageManager() {
+ if(_globalStorageManager == null) { _globalStorageManager = new LocalStorageManager(); }
+ return _globalStorageManager;
+ }
+
+ static globalHttpManager() {
+ if(_globalHttpManager == null) {
+ _globalHttpManager = new SFHttpManager();
+ _globalHttpManager.setJWTRequestHandler(async () => {
+ return this.globalStorageManager().getItem("jwt");;
+ })
+ }
+ return _globalHttpManager;
+ }
+
+ static globalAuthManager() {
+ if(_globalAuthManager == null) { _globalAuthManager = new SFAuthManager(_globalStorageManager, _globalHttpManager); }
+ return _globalAuthManager;
+ }
+
+ static globalModelManager() {
+ if(_globalModelManager == null) { _globalModelManager = new SFModelManager(); }
+ return _globalModelManager;
+ }
+
+ static globalStandardFile() {
+ if(_globalStandardFile == null) { _globalStandardFile = new StandardFile(); }
+ return _globalStandardFile;
+ }
+
+ static createModelManager() {
+ return new SFModelManager();
+ }
+
+ static createItemParams() {
+ var params = {
+ uuid: SFJS.crypto.generateUUIDSync(),
+ content_type: "Note",
+ content: {
+ title: "hello",
+ text: "world"
+ }
+ };
+ return params;
+ }
+
+ static createItem() {
+ return new SFItem(this.createItemParams());
+ }
+
+ static serverURL() {
+ return "http://localhost:3000";
+ }
+
+ static async newRegisteredUser(email, password) {
+ let url = this.serverURL();
+ if(!email) email = sf_default.crypto.generateUUIDSync();
+ if(!password) password = sf_default.crypto.generateUUIDSync();
+ return this.globalAuthManager().register(url, email, password, false);
+ }
+}
+
+Factory.initialize();
diff --git a/test/mocha/lib/localStorageManager.js b/test/mocha/lib/localStorageManager.js
new file mode 100644
index 000000000..2089bb753
--- /dev/null
+++ b/test/mocha/lib/localStorageManager.js
@@ -0,0 +1,69 @@
+// A test StorageManager class using LocalStorage
+
+export default class LocalStorageManager extends SFStorageManager {
+
+ /* Simple Key/Value Storage */
+
+ async setItem(key, value, vaultKey) {
+ localStorage.setItem(key, value);
+ }
+
+ async getItem(key, vault) {
+ return localStorage.getItem(key)
+ }
+
+ async removeItem(key, vault) {
+ localStorage.removeItem(key);
+ }
+
+ async clear() {
+ // clear only simple key/values
+ localStorage.clear();
+ }
+
+ /*
+ Model Storage
+ */
+
+ async getAllModels() {
+ var models = [];
+ for(var key in localStorage) {
+ if(key.startsWith("item-")) {
+ models.push(JSON.parse(localStorage[key]))
+ }
+ }
+ return models;
+ }
+
+ async saveModel(item) {
+ return this.saveModels([item]);
+ }
+
+ async saveModels(items) {
+ return Promise.all(items.map((item) => {
+ return this.setItem(`item-${item.uuid}`, JSON.stringify(item));
+ }))
+ }
+
+ async deleteModel(item,) {
+ return this.removeItem(`item-${item.uuid}`);
+ }
+
+ async clearAllModels() {
+ // clear only models
+ for(var key in localStorage) {
+ if(key.startsWith("item-")) {
+ this.removeItem(key);
+ }
+ }
+ }
+
+ /* General */
+
+ clearAllData() {
+ return Promise.all([
+ this.clear(),
+ this.clearAllModels()
+ ])
+ }
+}
diff --git a/test/mocha/models.test.js b/test/mocha/models.test.js
new file mode 100644
index 000000000..409079f75
--- /dev/null
+++ b/test/mocha/models.test.js
@@ -0,0 +1,548 @@
+import '../../vendor/assets/javascripts/compiled.js';
+import '../../node_modules/chai/chai.js';
+import './vendor/chai-as-promised-built.js';
+import '../../vendor/assets/javascripts/lodash/lodash.custom.js';
+import Factory from './lib/factory.js';
+
+chai.use(chaiAsPromised);
+var expect = chai.expect;
+
+const getNoteParams = () => {
+ var params = {
+ uuid: SFJS.crypto.generateUUIDSync(),
+ content_type: "Note",
+ content: {
+ title: "hello",
+ text: "world"
+ }
+ };
+ return params;
+}
+
+const createRelatedNoteTagPair = () => {
+ let noteParams = getNoteParams();
+ let tagParams = {
+ uuid: SFJS.crypto.generateUUIDSync(),
+ content_type: "Tag",
+ content: {
+ title: "thoughts",
+ }
+ };
+ tagParams.content.references = [
+ {
+ uuid: noteParams.uuid,
+ content_type: noteParams.content_type
+ }
+ ]
+
+ noteParams.content.references = []
+
+ return [noteParams, tagParams];
+}
+describe("notes and tags", () => {
+
+ it('uses proper class for note', () => {
+ let modelManager = Factory.createModelManager();
+ let noteParams = getNoteParams();
+ modelManager.mapResponseItemsToLocalModels([noteParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ expect(note).to.be.an.instanceOf(SNNote);
+ });
+
+ it('properly handles legacy relationships', () => {
+ // legacy relationships are when a note has a reference to a tag
+ let modelManager = Factory.createModelManager();
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+ tagParams.content.references = null;
+ noteParams.content.references = [
+ {
+ uuid: tagParams.uuid,
+ content_type: tagParams.content_type
+ }
+ ];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(note.tags.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+ })
+
+ it('creates two-way relationship between note and tag', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ expect(noteParams.content.references.length).to.equal(0);
+ expect(tagParams.content.references.length).to.equal(1);
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ // expect to be false
+ expect(note.dirty).to.not.be.ok;
+ expect(tag.dirty).to.not.be.ok;
+
+ expect(note.content.references.length).to.equal(0);
+ expect(tag.content.references.length).to.equal(1);
+
+ expect(note.hasRelationshipWithItem(tag)).to.equal(false);
+ expect(tag.hasRelationshipWithItem(note)).to.equal(true);
+
+ expect(note.tags.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+
+ modelManager.setItemToBeDeleted(note);
+ expect(note.tags.length).to.equal(0);
+ expect(tag.notes.length).to.equal(0);
+
+ // expect to be true
+ expect(note.dirty).to.be.ok;
+ expect(tag.dirty).to.be.ok;
+ });
+
+ it('handles remote deletion of relationship', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(note.content.references.length).to.equal(0);
+ expect(tag.content.references.length).to.equal(1);
+
+ tagParams.content.references = [];
+ modelManager.mapResponseItemsToLocalModels([tagParams]);
+
+ expect(tag.content.references.length).to.equal(0);
+ expect(note.tags.length).to.equal(0);
+ expect(tag.notes.length).to.equal(0);
+
+ // expect to be false
+ expect(note.dirty).to.not.be.ok;
+ expect(tag.dirty).to.not.be.ok;
+ });
+
+ it('resets cached note tags string when tag is deleted from remote source', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(note.tagsString().length).to.not.equal(0);
+
+ tagParams.deleted = true;
+ modelManager.mapResponseItemsToLocalModels([tagParams]);
+
+ // should be null
+ expect(note.savedTagsString).to.not.be.ok;
+
+ expect(note.tags.length).to.equal(0);
+ expect(tag.notes.length).to.equal(0);
+ });
+
+ it('resets cached note tags string when tag reference is removed from remote source', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(note.tagsString().length).to.not.equal(0);
+
+ tagParams.content.references = [];
+ modelManager.mapResponseItemsToLocalModels([tagParams]);
+
+ // should be null
+ expect(note.savedTagsString).to.not.be.ok;
+
+ expect(note.tags.length).to.equal(0);
+ expect(tag.notes.length).to.equal(0);
+ });
+
+ it('resets cached note tags string when tag is renamed', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(note.tagsString()).to.equal(`#${tagParams.content.title}`);
+
+ var title = Math.random();
+
+ // Saving involves modifying local state first, then syncing with omitting content.
+ tag.title = title;
+ tagParams.content.title = title;
+ // simulate a save, which omits `content`
+ modelManager.mapResponseItemsToLocalModelsOmittingFields([tagParams], ['content']);
+
+ // should be null
+ expect(note.savedTagsString).to.not.be.ok;
+ expect(note.tagsString()).to.equal(`#${title}`);
+ });
+
+ it('handles removing relationship between note and tag', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(note.content.references.length).to.equal(0);
+ expect(tag.content.references.length).to.equal(1);
+
+ tag.removeItemAsRelationship(note);
+ modelManager.mapResponseItemsToLocalModels([tag]);
+
+ expect(note.tags.length).to.equal(0);
+ expect(tag.notes.length).to.equal(0);
+ });
+
+ it('properly handles tag duplication', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ // Usually content_type will be provided by a server response
+ var duplicateParams = _.merge({content_type: "Tag"}, tag);
+ duplicateParams.uuid = null;
+
+ expect(duplicateParams.content_type).to.equal("Tag");
+ var duplicateTag = modelManager.createDuplicateItem(duplicateParams);
+ modelManager.addDuplicatedItem(duplicateTag, tag);
+
+ expect(tag.uuid).to.not.equal(duplicateTag.uuid);
+
+ expect(tag.content.references.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+
+ expect(duplicateTag.content.references.length).to.equal(1);
+ expect(duplicateTag.notes.length).to.equal(1);
+
+ expect(note.tags.length).to.equal(2);
+
+ var noteTag1 = note.tags[0];
+ var noteTag2 = note.tags[1];
+ expect(noteTag1.uuid).to.not.equal(noteTag2.uuid);
+
+ // expect to be false
+ expect(note.dirty).to.not.be.ok;
+ expect(tag.dirty).to.not.be.ok;
+ });
+
+ it('duplicating a note should maintain its tag references', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ // Usually content_type will be provided by a server response
+ var duplicateParams = _.merge({content_type: "Note"}, note);
+ duplicateParams.uuid = null;
+
+ var duplicateNote = modelManager.createDuplicateItem(duplicateParams);
+ modelManager.addDuplicatedItem(duplicateNote, note);
+
+ expect(note.uuid).to.not.equal(duplicateNote.uuid);
+
+ expect(duplicateNote.tags.length).to.equal(note.tags.length);
+ });
+
+ it('deleting a note should update tag references', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(tag.content.references.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+
+ expect(note.content.references.length).to.equal(0);
+ expect(note.tags.length).to.equal(1);
+
+ modelManager.setItemToBeDeleted(tag);
+ modelManager.mapResponseItemsToLocalModels([tag]);
+ expect(tag.content.references.length).to.equal(0);
+ expect(tag.notes.length).to.equal(0);
+ });
+
+ it('importing existing data should keep relationships valid', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(tag.content.references.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+
+ expect(note.content.references.length).to.equal(0);
+ expect(note.tags.length).to.equal(1);
+
+ modelManager.importItems([noteParams, tagParams]);
+
+ expect(modelManager.allItems.length).to.equal(2);
+
+ expect(tag.content.references.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+
+ expect(note.content.references.length).to.equal(0);
+ expect(note.referencingObjects.length).to.equal(1);
+ expect(note.tags.length).to.equal(1);
+ });
+
+ it('importing data with differing content should create duplicates', () => {
+ let modelManager = Factory.createModelManager();
+
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ noteParams.content.title = Math.random();
+ tagParams.content.title = Math.random();
+ modelManager.importItems([noteParams, tagParams]);
+
+ expect(modelManager.allItems.length).to.equal(4);
+
+ var newNote = modelManager.allItemsMatchingTypes(["Note"])[1];
+ var newTag = modelManager.allItemsMatchingTypes(["Tag"])[1];
+
+ expect(newNote.uuid).to.not.equal(note.uuid);
+ expect(newTag.uuid).to.not.equal(tag.uuid);
+
+ expect(tag.content.references.length).to.equal(2);
+ expect(tag.notes.length).to.equal(2);
+
+ expect(note.content.references.length).to.equal(0);
+ expect(note.referencingObjects.length).to.equal(2);
+ expect(note.tags.length).to.equal(2);
+
+ expect(newTag.content.references.length).to.equal(1);
+ expect(newTag.notes.length).to.equal(1);
+
+ expect(newNote.content.references.length).to.equal(0);
+ expect(newNote.referencingObjects.length).to.equal(1);
+ expect(newNote.tags.length).to.equal(1);
+ });
+});
+
+describe("syncing", () => {
+ var totalItemCount = 0;
+
+ beforeEach((done) => {
+ var email = Factory.globalStandardFile().crypto.generateUUIDSync();
+ var password = Factory.globalStandardFile().crypto.generateUUIDSync();
+ Factory.globalStorageManager().clearAllData().then(() => {
+ Factory.newRegisteredUser(email, password).then((user) => {
+ done();
+ })
+ })
+ })
+
+ let modelManager = Factory.createModelManager();
+ let authManager = Factory.globalAuthManager();
+ let syncManager = new SFSyncManager(modelManager, Factory.globalStorageManager(), Factory.globalHttpManager());
+
+ syncManager.setKeyRequestHandler(async () => {
+ return {
+ keys: await authManager.keys(),
+ auth_params: await authManager.getAuthParams(),
+ offline: false
+ };
+ })
+
+ const wait = (secs) => {
+ return new Promise((resolve, reject) => {
+ setTimeout(function () {
+ resolve();
+ }, secs * 1000);
+ })
+ }
+
+ it('syncing a note many times does not cause duplication', async () => {
+ modelManager.handleSignout();
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ for(var i = 0; i < 9; i++) {
+ note.setDirty(true);
+ tag.setDirty(true);
+ await syncManager.sync();
+ syncManager.clearSyncToken();
+ expect(tag.content.references.length).to.equal(1);
+ expect(note.tags.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+ expect(modelManager.allItems.length).to.equal(2);
+ console.log("Waiting 1.1s...");
+ await wait(1.1);
+
+ }
+ }).timeout(20000);
+
+ it("handles signing in and merging data", async () => {
+
+ let syncManager = new SFSyncManager(modelManager, Factory.globalStorageManager(), Factory.globalHttpManager());
+
+ // be offline
+ syncManager.setKeyRequestHandler(async () => {
+ return {
+ offline: true
+ };
+ })
+
+ modelManager.handleSignout();
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let originalNote = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let originalTag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+ originalNote.setDirty(true);
+ originalTag.setDirty(true);
+
+ await syncManager.sync();
+
+ expect(originalTag.content.references.length).to.equal(1);
+ expect(originalTag.notes.length).to.equal(1);
+ expect(originalNote.tags.length).to.equal(1);
+
+ // go online
+ syncManager.setKeyRequestHandler(async () => {
+ return {
+ keys: await authManager.keys(),
+ auth_params: await authManager.getAuthParams(),
+ offline: false
+ };
+ })
+
+ // when signing in, all local items are cleared from storage (but kept in memory; to clear desktop logs),
+ // then resaved with alternated uuids.
+ await Factory.globalStorageManager().clearAllModels();
+ return expect(syncManager.markAllItemsDirtyAndSaveOffline(true)).to.be.fulfilled.then(() => {
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ expect(modelManager.allItems.length).to.equal(2);
+
+ expect(note.uuid).to.not.equal(originalNote.uuid);
+ expect(tag.uuid).to.not.equal(originalTag.uuid);
+
+ expect(tag.content.references.length).to.equal(1);
+ expect(note.content.references.length).to.equal(0);
+
+ expect(note.referencingObjects.length).to.equal(1);
+ expect(tag.notes.length).to.equal(1);
+ expect(note.tags.length).to.equal(1);
+ });
+ })
+
+ it('duplicating a tag should maintian its relationships', async () => {
+ modelManager.handleSignout();
+ let pair = createRelatedNoteTagPair();
+ let noteParams = pair[0];
+ let tagParams = pair[1];
+
+ modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]);
+ let note = modelManager.allItemsMatchingTypes(["Note"])[0];
+ let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
+
+ note.setDirty(true);
+ tag.setDirty(true);
+
+ await syncManager.sync();
+ await syncManager.clearSyncToken();
+
+ expect(modelManager.allItems.length).to.equal(2);
+
+ tag.title = `${Math.random()}`
+ tag.setDirty(true);
+
+ expect(note.referencingObjects.length).to.equal(1);
+
+ // wait about 1s, which is the value the dev server will ignore conflicting changes
+ return expect(new Promise((resolve, reject) => {
+ setTimeout(function () {
+ resolve();
+ }, 1100);
+ })).to.be.fulfilled.then(async () => {
+ return expect(syncManager.sync()).to.be.fulfilled.then(async (response) => {
+ // tag should now be conflicted and a copy created
+ let models = modelManager.allItems;
+ expect(modelManager.allItems.length).to.equal(3);
+ var tags = modelManager.allItemsMatchingTypes(["Tag"]);
+ var tag1 = tags[0];
+ var tag2 = tags[1];
+
+ expect(tag1.uuid).to.not.equal(tag2.uuid);
+
+ expect(tag1.uuid).to.equal(tag.uuid);
+ expect(tag2.conflict_of).to.equal(tag1.uuid);
+ expect(tag1.notes.length).to.equal(tag2.notes.length);
+ expect(tag1.referencingObjects.length).to.equal(0);
+ expect(tag2.referencingObjects.length).to.equal(0);
+
+ // Two tags now link to this note
+ expect(note.referencingObjects.length).to.equal(2);
+ expect(note.referencingObjects[0]).to.not.equal(note.referencingObjects[1]);
+ })
+ })
+ }).timeout(10000);
+})
diff --git a/test/mocha/test.html b/test/mocha/test.html
new file mode 100644
index 000000000..8eb6058e3
--- /dev/null
+++ b/test/mocha/test.html
@@ -0,0 +1,23 @@
+
+
+
+ Mocha Tests
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/mocha/vendor/chai-as-promised-built.js b/test/mocha/vendor/chai-as-promised-built.js
new file mode 100644
index 000000000..5f1d2f6e1
--- /dev/null
+++ b/test/mocha/vendor/chai-as-promised-built.js
@@ -0,0 +1,539 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.chaiAsPromised = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i {
+ const Assertion = chai.Assertion;
+ const assert = chai.assert;
+ const proxify = utils.proxify;
+
+ // If we are using a version of Chai that has checkError on it,
+ // we want to use that version to be consistent. Otherwise, we use
+ // what was passed to the factory.
+ if (utils.checkError) {
+ checkError = utils.checkError;
+ }
+
+ function isLegacyJQueryPromise(thenable) {
+ // jQuery promises are Promises/A+-compatible since 3.0.0. jQuery 3.0.0 is also the first version
+ // to define the catch method.
+ return typeof thenable.catch !== "function" &&
+ typeof thenable.always === "function" &&
+ typeof thenable.done === "function" &&
+ typeof thenable.fail === "function" &&
+ typeof thenable.pipe === "function" &&
+ typeof thenable.progress === "function" &&
+ typeof thenable.state === "function";
+ }
+
+ function assertIsAboutPromise(assertion) {
+ if (typeof assertion._obj.then !== "function") {
+ throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
+ }
+ if (isLegacyJQueryPromise(assertion._obj)) {
+ throw new TypeError("Chai as Promised is incompatible with thenables of jQuery<3.0.0, sorry! Please " +
+ "upgrade jQuery or use another Promises/A+ compatible library (see " +
+ "http://promisesaplus.com/).");
+ }
+ }
+
+ function proxifyIfSupported(assertion) {
+ return proxify === undefined ? assertion : proxify(assertion);
+ }
+
+ function method(name, asserter) {
+ utils.addMethod(Assertion.prototype, name, function () {
+ assertIsAboutPromise(this);
+ return asserter.apply(this, arguments);
+ });
+ }
+
+ function property(name, asserter) {
+ utils.addProperty(Assertion.prototype, name, function () {
+ assertIsAboutPromise(this);
+ return proxifyIfSupported(asserter.apply(this, arguments));
+ });
+ }
+
+ function doNotify(promise, done) {
+ promise.then(() => done(), done);
+ }
+
+ // These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
+ function assertIfNegated(assertion, message, extra) {
+ assertion.assert(true, null, message, extra.expected, extra.actual);
+ }
+
+ function assertIfNotNegated(assertion, message, extra) {
+ assertion.assert(false, message, null, extra.expected, extra.actual);
+ }
+
+ function getBasePromise(assertion) {
+ // We need to chain subsequent asserters on top of ones in the chain already (consider
+ // `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
+ // So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
+ // previously derived promises, to chain off of.
+ return typeof assertion.then === "function" ? assertion : assertion._obj;
+ }
+
+ function getReasonName(reason) {
+ return reason instanceof Error ? reason.toString() : checkError.getConstructorName(reason);
+ }
+
+ // Grab these first, before we modify `Assertion.prototype`.
+
+ const propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
+
+ const propertyDescs = {};
+ for (const name of propertyNames) {
+ propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
+ }
+
+ property("fulfilled", function () {
+ const derivedPromise = getBasePromise(this).then(
+ value => {
+ assertIfNegated(this,
+ "expected promise not to be fulfilled but it was fulfilled with #{act}",
+ { actual: value });
+ return value;
+ },
+ reason => {
+ assertIfNotNegated(this,
+ "expected promise to be fulfilled but it was rejected with #{act}",
+ { actual: getReasonName(reason) });
+ return reason;
+ }
+ );
+
+ module.exports.transferPromiseness(this, derivedPromise);
+ return this;
+ });
+
+ property("rejected", function () {
+ const derivedPromise = getBasePromise(this).then(
+ value => {
+ assertIfNotNegated(this,
+ "expected promise to be rejected but it was fulfilled with #{act}",
+ { actual: value });
+ return value;
+ },
+ reason => {
+ assertIfNegated(this,
+ "expected promise not to be rejected but it was rejected with #{act}",
+ { actual: getReasonName(reason) });
+
+ // Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
+ // `promise.should.be.rejected.and.eventually.equal("reason")`.
+ return reason;
+ }
+ );
+
+ module.exports.transferPromiseness(this, derivedPromise);
+ return this;
+ });
+
+ method("rejectedWith", function (errorLike, errMsgMatcher, message) {
+ let errorLikeName = null;
+ const negate = utils.flag(this, "negate") || false;
+
+ // rejectedWith with that is called without arguments is
+ // the same as a plain ".rejected" use.
+ if (errorLike === undefined && errMsgMatcher === undefined &&
+ message === undefined) {
+ /* eslint-disable no-unused-expressions */
+ return this.rejected;
+ /* eslint-enable no-unused-expressions */
+ }
+
+ if (message !== undefined) {
+ utils.flag(this, "message", message);
+ }
+
+ if (errorLike instanceof RegExp || typeof errorLike === "string") {
+ errMsgMatcher = errorLike;
+ errorLike = null;
+ } else if (errorLike && errorLike instanceof Error) {
+ errorLikeName = errorLike.toString();
+ } else if (typeof errorLike === "function") {
+ errorLikeName = checkError.getConstructorName(errorLike);
+ } else {
+ errorLike = null;
+ }
+ const everyArgIsDefined = Boolean(errorLike && errMsgMatcher);
+
+ let matcherRelation = "including";
+ if (errMsgMatcher instanceof RegExp) {
+ matcherRelation = "matching";
+ }
+
+ const derivedPromise = getBasePromise(this).then(
+ value => {
+ let assertionMessage = null;
+ let expected = null;
+
+ if (errorLike) {
+ assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with #{act}";
+ expected = errorLikeName;
+ } else if (errMsgMatcher) {
+ assertionMessage = `expected promise to be rejected with an error ${matcherRelation} #{exp} but ` +
+ `it was fulfilled with #{act}`;
+ expected = errMsgMatcher;
+ }
+
+ assertIfNotNegated(this, assertionMessage, { expected, actual: value });
+ return value;
+ },
+ reason => {
+ const errorLikeCompatible = errorLike && (errorLike instanceof Error ?
+ checkError.compatibleInstance(reason, errorLike) :
+ checkError.compatibleConstructor(reason, errorLike));
+
+ const errMsgMatcherCompatible = errMsgMatcher && checkError.compatibleMessage(reason, errMsgMatcher);
+
+ const reasonName = getReasonName(reason);
+
+ if (negate && everyArgIsDefined) {
+ if (errorLikeCompatible && errMsgMatcherCompatible) {
+ this.assert(true,
+ null,
+ "expected promise not to be rejected with #{exp} but it was rejected " +
+ "with #{act}",
+ errorLikeName,
+ reasonName);
+ }
+ } else {
+ if (errorLike) {
+ this.assert(errorLikeCompatible,
+ "expected promise to be rejected with #{exp} but it was rejected with #{act}",
+ "expected promise not to be rejected with #{exp} but it was rejected " +
+ "with #{act}",
+ errorLikeName,
+ reasonName);
+ }
+
+ if (errMsgMatcher) {
+ this.assert(errMsgMatcherCompatible,
+ `expected promise to be rejected with an error ${matcherRelation} #{exp} but got ` +
+ `#{act}`,
+ `expected promise not to be rejected with an error ${matcherRelation} #{exp}`,
+ errMsgMatcher,
+ checkError.getMessage(reason));
+ }
+ }
+
+ return reason;
+ }
+ );
+
+ module.exports.transferPromiseness(this, derivedPromise);
+ return this;
+ });
+
+ property("eventually", function () {
+ utils.flag(this, "eventually", true);
+ return this;
+ });
+
+ method("notify", function (done) {
+ doNotify(getBasePromise(this), done);
+ return this;
+ });
+
+ method("become", function (value, message) {
+ return this.eventually.deep.equal(value, message);
+ });
+
+ // ### `eventually`
+
+ // We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
+ const methodNames = propertyNames.filter(name => {
+ return name !== "assert" && typeof propertyDescs[name].value === "function";
+ });
+
+ methodNames.forEach(methodName => {
+ Assertion.overwriteMethod(methodName, originalMethod => function () {
+ return doAsserterAsyncAndAddThen(originalMethod, this, arguments);
+ });
+ });
+
+ const getterNames = propertyNames.filter(name => {
+ return name !== "_obj" && typeof propertyDescs[name].get === "function";
+ });
+
+ getterNames.forEach(getterName => {
+ // Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
+ // `should.be.an("object")`. We need to handle those specially.
+ const isChainableMethod = Assertion.prototype.__methods.hasOwnProperty(getterName);
+
+ if (isChainableMethod) {
+ Assertion.overwriteChainableMethod(
+ getterName,
+ originalMethod => function () {
+ return doAsserterAsyncAndAddThen(originalMethod, this, arguments);
+ },
+ originalGetter => function () {
+ return doAsserterAsyncAndAddThen(originalGetter, this);
+ }
+ );
+ } else {
+ Assertion.overwriteProperty(getterName, originalGetter => function () {
+ return proxifyIfSupported(doAsserterAsyncAndAddThen(originalGetter, this));
+ });
+ }
+ });
+
+ function doAsserterAsyncAndAddThen(asserter, assertion, args) {
+ // Since we're intercepting all methods/properties, we need to just pass through if they don't want
+ // `eventually`, or if we've already fulfilled the promise (see below).
+ if (!utils.flag(assertion, "eventually")) {
+ asserter.apply(assertion, args);
+ return assertion;
+ }
+
+ const derivedPromise = getBasePromise(assertion).then(value => {
+ // Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
+ // now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
+ // just the base Chai code that we get to via the short-circuit above.
+ assertion._obj = value;
+ utils.flag(assertion, "eventually", false);
+
+ return args ? module.exports.transformAsserterArgs(args) : args;
+ }).then(newArgs => {
+ asserter.apply(assertion, newArgs);
+
+ // Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
+ // flag), we need to communicate this value change to subsequent chained asserters. Since we build a
+ // promise chain paralleling the asserter chain, we can use it to communicate such changes.
+ return assertion._obj;
+ });
+
+ module.exports.transferPromiseness(assertion, derivedPromise);
+ return assertion;
+ }
+
+ // ### Now use the `Assertion` framework to build an `assert` interface.
+ const originalAssertMethods = Object.getOwnPropertyNames(assert).filter(propName => {
+ return typeof assert[propName] === "function";
+ });
+
+ assert.isFulfilled = (promise, message) => (new Assertion(promise, message)).to.be.fulfilled;
+
+ assert.isRejected = (promise, errorLike, errMsgMatcher, message) => {
+ const assertion = new Assertion(promise, message);
+ return assertion.to.be.rejectedWith(errorLike, errMsgMatcher, message);
+ };
+
+ assert.becomes = (promise, value, message) => assert.eventually.deepEqual(promise, value, message);
+
+ assert.doesNotBecome = (promise, value, message) => assert.eventually.notDeepEqual(promise, value, message);
+
+ assert.eventually = {};
+ originalAssertMethods.forEach(assertMethodName => {
+ assert.eventually[assertMethodName] = function (promise) {
+ const otherArgs = Array.prototype.slice.call(arguments, 1);
+
+ let customRejectionHandler;
+ const message = arguments[assert[assertMethodName].length - 1];
+ if (typeof message === "string") {
+ customRejectionHandler = reason => {
+ throw new chai.AssertionError(`${message}\n\nOriginal reason: ${utils.inspect(reason)}`);
+ };
+ }
+
+ const returnedPromise = promise.then(
+ fulfillmentValue => assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs)),
+ customRejectionHandler
+ );
+
+ returnedPromise.notify = done => {
+ doNotify(returnedPromise, done);
+ };
+
+ return returnedPromise;
+ };
+ });
+};
+
+module.exports.transferPromiseness = (assertion, promise) => {
+ assertion.then = promise.then.bind(promise);
+};
+
+module.exports.transformAsserterArgs = values => values;
+
+},{"check-error":2}],2:[function(require,module,exports){
+'use strict';
+
+/* !
+ * Chai - checkError utility
+ * Copyright(c) 2012-2016 Jake Luer
+ * MIT Licensed
+ */
+
+/**
+ * ### .checkError
+ *
+ * Checks that an error conforms to a given set of criteria and/or retrieves information about it.
+ *
+ * @api public
+ */
+
+/**
+ * ### .compatibleInstance(thrown, errorLike)
+ *
+ * Checks if two instances are compatible (strict equal).
+ * Returns false if errorLike is not an instance of Error, because instances
+ * can only be compatible if they're both error instances.
+ *
+ * @name compatibleInstance
+ * @param {Error} thrown error
+ * @param {Error|ErrorConstructor} errorLike object to compare against
+ * @namespace Utils
+ * @api public
+ */
+
+function compatibleInstance(thrown, errorLike) {
+ return errorLike instanceof Error && thrown === errorLike;
+}
+
+/**
+ * ### .compatibleConstructor(thrown, errorLike)
+ *
+ * Checks if two constructors are compatible.
+ * This function can receive either an error constructor or
+ * an error instance as the `errorLike` argument.
+ * Constructors are compatible if they're the same or if one is
+ * an instance of another.
+ *
+ * @name compatibleConstructor
+ * @param {Error} thrown error
+ * @param {Error|ErrorConstructor} errorLike object to compare against
+ * @namespace Utils
+ * @api public
+ */
+
+function compatibleConstructor(thrown, errorLike) {
+ if (errorLike instanceof Error) {
+ // If `errorLike` is an instance of any error we compare their constructors
+ return thrown.constructor === errorLike.constructor || thrown instanceof errorLike.constructor;
+ } else if (errorLike.prototype instanceof Error || errorLike === Error) {
+ // If `errorLike` is a constructor that inherits from Error, we compare `thrown` to `errorLike` directly
+ return thrown.constructor === errorLike || thrown instanceof errorLike;
+ }
+
+ return false;
+}
+
+/**
+ * ### .compatibleMessage(thrown, errMatcher)
+ *
+ * Checks if an error's message is compatible with a matcher (String or RegExp).
+ * If the message contains the String or passes the RegExp test,
+ * it is considered compatible.
+ *
+ * @name compatibleMessage
+ * @param {Error} thrown error
+ * @param {String|RegExp} errMatcher to look for into the message
+ * @namespace Utils
+ * @api public
+ */
+
+function compatibleMessage(thrown, errMatcher) {
+ var comparisonString = typeof thrown === 'string' ? thrown : thrown.message;
+ if (errMatcher instanceof RegExp) {
+ return errMatcher.test(comparisonString);
+ } else if (typeof errMatcher === 'string') {
+ return comparisonString.indexOf(errMatcher) !== -1; // eslint-disable-line no-magic-numbers
+ }
+
+ return false;
+}
+
+/**
+ * ### .getFunctionName(constructorFn)
+ *
+ * Returns the name of a function.
+ * This also includes a polyfill function if `constructorFn.name` is not defined.
+ *
+ * @name getFunctionName
+ * @param {Function} constructorFn
+ * @namespace Utils
+ * @api private
+ */
+
+var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\(\/]+)/;
+function getFunctionName(constructorFn) {
+ var name = '';
+ if (typeof constructorFn.name === 'undefined') {
+ // Here we run a polyfill if constructorFn.name is not defined
+ var match = String(constructorFn).match(functionNameMatch);
+ if (match) {
+ name = match[1];
+ }
+ } else {
+ name = constructorFn.name;
+ }
+
+ return name;
+}
+
+/**
+ * ### .getConstructorName(errorLike)
+ *
+ * Gets the constructor name for an Error instance or constructor itself.
+ *
+ * @name getConstructorName
+ * @param {Error|ErrorConstructor} errorLike
+ * @namespace Utils
+ * @api public
+ */
+
+function getConstructorName(errorLike) {
+ var constructorName = errorLike;
+ if (errorLike instanceof Error) {
+ constructorName = getFunctionName(errorLike.constructor);
+ } else if (typeof errorLike === 'function') {
+ // If `err` is not an instance of Error it is an error constructor itself or another function.
+ // If we've got a common function we get its name, otherwise we may need to create a new instance
+ // of the error just in case it's a poorly-constructed error. Please see chaijs/chai/issues/45 to know more.
+ constructorName = getFunctionName(errorLike).trim() ||
+ getFunctionName(new errorLike()); // eslint-disable-line new-cap
+ }
+
+ return constructorName;
+}
+
+/**
+ * ### .getMessage(errorLike)
+ *
+ * Gets the error message from an error.
+ * If `err` is a String itself, we return it.
+ * If the error has no message, we return an empty string.
+ *
+ * @name getMessage
+ * @param {Error|String} errorLike
+ * @namespace Utils
+ * @api public
+ */
+
+function getMessage(errorLike) {
+ var msg = '';
+ if (errorLike && errorLike.message) {
+ msg = errorLike.message;
+ } else if (typeof errorLike === 'string') {
+ msg = errorLike;
+ }
+
+ return msg;
+}
+
+module.exports = {
+ compatibleInstance: compatibleInstance,
+ compatibleConstructor: compatibleConstructor,
+ compatibleMessage: compatibleMessage,
+ getMessage: getMessage,
+ getConstructorName: getConstructorName,
+};
+
+},{}]},{},[1])(1)
+});
diff --git a/test/models/.keep b/test/models/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/models/api_key_test.rb b/test/models/api_key_test.rb
deleted file mode 100644
index 2b1012784..000000000
--- a/test/models/api_key_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class ApiKeyTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/test/models/name_test.rb b/test/models/name_test.rb
deleted file mode 100644
index 5f9eba9e1..000000000
--- a/test/models/name_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class NameTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/testing-server.js b/testing-server.js
new file mode 100644
index 000000000..8ceee5329
--- /dev/null
+++ b/testing-server.js
@@ -0,0 +1,8 @@
+// Used for running mocha tests
+
+var connect = require('connect');
+var serveStatic = require('serve-static');
+var port = 7000;
+connect().use(serveStatic(__dirname)).listen(port, function(){
+ console.log(`Server running on ${port}...`);
+});
diff --git a/vendor/assets/javascripts/lodash/lodash.custom.js b/vendor/assets/javascripts/lodash/lodash.custom.js
index 558796f7b..cc8de1a6b 100644
--- a/vendor/assets/javascripts/lodash/lodash.custom.js
+++ b/vendor/assets/javascripts/lodash/lodash.custom.js
@@ -1,7 +1,7 @@
/**
* @license
* Lodash (Custom Build)
- * Build: `lodash include="includes,merge,filter,map,remove,find,omit,pull,cloneDeep,pick,uniq,sortedIndexBy"`
+ * Build: `lodash include="includes,merge,filter,map,remove,find,omit,pull,cloneDeep,pick,uniq,sortedIndexBy,mergeWith"`
* Copyright JS Foundation and other contributors
* Released under MIT license
* Based on Underscore.js 1.8.3
@@ -4890,6 +4890,41 @@
baseMerge(object, source, srcIndex);
});
+ /**
+ * This method is like `_.merge` except that it accepts `customizer` which
+ * is invoked to produce the merged values of the destination and source
+ * properties. If `customizer` returns `undefined`, merging is handled by the
+ * method instead. The `customizer` is invoked with six arguments:
+ * (objValue, srcValue, key, object, source, stack).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} customizer The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * if (_.isArray(objValue)) {
+ * return objValue.concat(srcValue);
+ * }
+ * }
+ *
+ * var object = { 'a': [1], 'b': [2] };
+ * var other = { 'a': [3], 'b': [4] };
+ *
+ * _.mergeWith(object, other, customizer);
+ * // => { 'a': [1, 3], 'b': [2, 4] }
+ */
+ var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
+ baseMerge(object, source, srcIndex, customizer);
+ });
+
/**
* The opposite of `_.pick`; this method creates an object composed of the
* own and inherited enumerable property paths of `object` that are not omitted.
@@ -5169,6 +5204,7 @@
lodash.map = map;
lodash.memoize = memoize;
lodash.merge = merge;
+ lodash.mergeWith = mergeWith;
lodash.omit = omit;
lodash.pick = pick;
lodash.property = property;
diff --git a/vendor/assets/javascripts/lodash/lodash.custom.min.js b/vendor/assets/javascripts/lodash/lodash.custom.min.js
index ae5bffea2..51b6ea591 100644
--- a/vendor/assets/javascripts/lodash/lodash.custom.min.js
+++ b/vendor/assets/javascripts/lodash/lodash.custom.min.js
@@ -1,50 +1,51 @@
/**
* @license
* Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
- * Build: `lodash include="includes,merge,filter,map,remove,find,omit,pull,cloneDeep,pick,uniq,sortedIndexBy"`
+ * Build: `lodash include="includes,merge,filter,map,remove,find,omit,pull,cloneDeep,pick,uniq,sortedIndexBy,mergeWith"`
*/
-;(function(){function t(t,n){return t.set(n[0],n[1]),t}function n(t,n){return t.add(n),t}function r(t,n,r){switch(r.length){case 0:return t.call(n);case 1:return t.call(n,r[0]);case 2:return t.call(n,r[0],r[1]);case 3:return t.call(n,r[0],r[1],r[2])}return t.apply(n,r)}function e(t,n){for(var r=-1,e=null==t?0:t.length;++r-1;
-}function i(t,n,r){for(var e=-1,u=null==t?0:t.length;++e-1}function T(t,n){var r=this.__data__,e=rt(r,t);return e<0?(++this.size,r.push([t,n])):r[e][1]=n,this}function U(t){var n=-1,r=null==t?0:t.length;for(this.clear();++n0&&r(c)?n>1?ft(c,n-1,r,e,u):f(u,c):e||(u[u.length]=c)}return u}function at(t,n){return t&&Eu(t,n,hr)}function lt(t,n){n=Ct(n,t);for(var r=0,e=n.length;null!=t&&r-1;)f!==t&&iu.call(f,a,1),iu.call(t,a,1);return t}function Pt(t,n){for(var r=t?n.length:0,e=r-1;r--;){var u=n[r];if(r==e||u!==o){
-var o=u;wn(u)?iu.call(t,u,1):Nt(t,u)}}return t}function Et(t,n){return Nu(Pn(t,n,gr),t+"")}function Ft(t,n,r,e){if(!tr(t))return t;n=Ct(n,t);for(var u=-1,o=n.length,i=o-1,c=t;null!=c&&++uu?0:u+n),r=r>u?u:r,r<0&&(r+=u),u=n>r?0:r-n>>>0,n>>>=0;for(var o=Array(u);++e=Ar){var s=n?null:Bu(t);if(s)return m(s);f=false,u=d,l=new q}else l=n?[]:a;t:for(;++e1?r[u-1]:Or,i=u>2?r[2]:Or;for(o=t.length>3&&typeof o=="function"?(u--,o):Or,i&&On(r[0],r[1],i)&&(o=u<3?Or:o,u=1),n=Object(n);++e-1?u[o?n[i]:i]:Or}}function en(t){return rr(t)?Or:t}function un(t,n,r,e,u,o){var i=r&Lr,c=t.length,f=n.length;if(c!=f&&!(i&&f>c))return false;var a=o.get(t);if(a&&o.get(n))return a==n;var s=-1,h=true,v=r&Pr?new q:Or;
-for(o.set(t,n),o.set(n,t);++s-1&&t%1==0&&t0){if(++n>=Er)return arguments[0]}else n=0;return t.apply(Or,arguments)}}function Bn(t){if(typeof t=="string"||ur(t))return t;var n=t+"";return"0"==n&&1/t==-Br?"-0":n}function Mn(t){if(null!=t){try{return He.call(t)}catch(t){}try{return t+""}catch(t){}}return""}function Tn(t,n,r){var e=null==t?0:t.length;if(!e)return-1;var u=null==r?0:ir(r);return u<0&&(u=pu(e+u,0)),s(t,sn(n,3),u)}function Un(t){return(null==t?0:t.length)?ft(t,1):[]}function Nn(t){var n=null==t?0:t.length;
-return n?t[n-1]:Or}function Cn(t,n){return t&&t.length&&n&&n.length?Lt(t,n):t}function Dn(t,n){var r=[];if(!t||!t.length)return r;var e=-1,u=[],o=t.length;for(n=sn(n,3);++e-1:!!u&&h(t,n,r)>-1;
-}function Gn(t,n){return(qu(t)?c:mt)(t,sn(n,3))}function Hn(t,n){if(typeof t!="function"||null!=n&&typeof n!="function")throw new TypeError(zr);var r=function(){var e=arguments,u=n?n.apply(this,e):e[0],o=r.cache;if(o.has(u))return o.get(u);var i=t.apply(this,e);return r.cache=o.set(u,i)||o,i};return r.cache=new(Hn.Cache||U),r}function Jn(t){return it(t,kr|Ir)}function Kn(t,n){return t===n||t!==t&&n!==n}function Qn(t){return null!=t&&Zn(t.length)&&!Yn(t)}function Xn(t){return nr(t)&&Qn(t)}function Yn(t){
-if(!tr(t))return false;var n=ht(t);return n==Hr||n==Jr||n==Vr||n==te}function Zn(t){return typeof t=="number"&&t>-1&&t%1==0&&t<=Mr}function tr(t){var n=typeof t;return null!=t&&("object"==n||"function"==n)}function nr(t){return null!=t&&typeof t=="object"}function rr(t){if(!nr(t)||ht(t)!=Yr)return false;var n=eu(t);if(null===n)return true;var r=Je.call(n,"constructor")&&n.constructor;return typeof r=="function"&&r instanceof r&&He.call(r)==Xe}function er(t){return typeof t=="string"||!qu(t)&&nr(t)&&ht(t)==ee}function ur(t){
-return typeof t=="symbol"||nr(t)&&ht(t)==ue}function or(t){if(!t)return 0===t?t:0;if(t=cr(t),t===Br||t===-Br){return(t<0?-1:1)*Tr}return t===t?t:0}function ir(t){var n=or(t),r=n%1;return n===n?r?n-r:n:0}function cr(t){if(typeof t=="number")return t;if(ur(t))return Ur;if(tr(t)){var n=typeof t.valueOf=="function"?t.valueOf():t;t=tr(n)?n+"":n}if(typeof t!="string")return 0===t?t:+t;t=t.replace(me,"");var r=Se.test(t);return r||$e.test(t)?Ee(t.slice(2),r?2:8):xe.test(t)?Ur:+t}function fr(t){return Qt(t,vr(t));
-}function ar(t){return null==t?"":Tt(t)}function lr(t,n,r){var e=null==t?Or:lt(t,n);return e===Or?r:e}function sr(t,n){return null!=t&&gn(t,n,vt)}function hr(t){return Qn(t)?Z(t):wt(t)}function vr(t){return Qn(t)?Z(t,true):Ot(t)}function pr(t){return null==t?[]:_(t,hr(t))}function yr(t){return function(){return t}}function gr(t){return t}function br(t){return jt(typeof t=="function"?t:it(t,kr))}function _r(){}function dr(t){return mn(t)?y(Bn(t)):It(t)}function jr(){return[]}function wr(){return false}var Or,mr="4.17.4",Ar=200,zr="Expected a function",xr="__lodash_hash_undefined__",Sr=500,kr=1,$r=2,Ir=4,Lr=1,Pr=2,Er=800,Fr=16,Br=1/0,Mr=9007199254740991,Tr=1.7976931348623157e308,Ur=NaN,Nr=4294967295,Cr=Nr-1,Dr="[object Arguments]",Rr="[object Array]",Vr="[object AsyncFunction]",qr="[object Boolean]",Wr="[object Date]",Gr="[object Error]",Hr="[object Function]",Jr="[object GeneratorFunction]",Kr="[object Map]",Qr="[object Number]",Xr="[object Null]",Yr="[object Object]",Zr="[object Promise]",te="[object Proxy]",ne="[object RegExp]",re="[object Set]",ee="[object String]",ue="[object Symbol]",oe="[object Undefined]",ie="[object WeakMap]",ce="[object ArrayBuffer]",fe="[object DataView]",ae="[object Float32Array]",le="[object Float64Array]",se="[object Int8Array]",he="[object Int16Array]",ve="[object Int32Array]",pe="[object Uint8Array]",ye="[object Uint8ClampedArray]",ge="[object Uint16Array]",be="[object Uint32Array]",_e=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,de=/^\w*$/,je=/^\./,we=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Oe=/[\\^$.*+?()[\]{}|]/g,me=/^\s+|\s+$/g,Ae=/\\(\\)?/g,ze=/\w*$/,xe=/^[-+]0x[0-9a-f]+$/i,Se=/^0b[01]+$/i,ke=/^\[object .+?Constructor\]$/,$e=/^0o[0-7]+$/i,Ie=/^(?:0|[1-9]\d*)$/,Le={};
-Le[ae]=Le[le]=Le[se]=Le[he]=Le[ve]=Le[pe]=Le[ye]=Le[ge]=Le[be]=true,Le[Dr]=Le[Rr]=Le[ce]=Le[qr]=Le[fe]=Le[Wr]=Le[Gr]=Le[Hr]=Le[Kr]=Le[Qr]=Le[Yr]=Le[ne]=Le[re]=Le[ee]=Le[ie]=false;var Pe={};Pe[Dr]=Pe[Rr]=Pe[ce]=Pe[fe]=Pe[qr]=Pe[Wr]=Pe[ae]=Pe[le]=Pe[se]=Pe[he]=Pe[ve]=Pe[Kr]=Pe[Qr]=Pe[Yr]=Pe[ne]=Pe[re]=Pe[ee]=Pe[ue]=Pe[pe]=Pe[ye]=Pe[ge]=Pe[be]=true,Pe[Gr]=Pe[Hr]=Pe[ie]=false;var Ee=parseInt,Fe=typeof global=="object"&&global&&global.Object===Object&&global,Be=typeof self=="object"&&self&&self.Object===Object&&self,Me=Fe||Be||Function("return this")(),Te=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Ue=Te&&typeof module=="object"&&module&&!module.nodeType&&module,Ne=Ue&&Ue.exports===Te,Ce=Ne&&Fe.process,De=function(){
-try{return Ce&&Ce.binding&&Ce.binding("util")}catch(t){}}(),Re=De&&De.isTypedArray,Ve=Array.prototype,qe=Function.prototype,We=Object.prototype,Ge=Me["__core-js_shared__"],He=qe.toString,Je=We.hasOwnProperty,Ke=function(){var t=/[^.]+$/.exec(Ge&&Ge.keys&&Ge.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}(),Qe=We.toString,Xe=He.call(Object),Ye=RegExp("^"+He.call(Je).replace(Oe,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Ze=Ne?Me.Buffer:Or,tu=Me.Symbol,nu=Me.Uint8Array,ru=Ze?Ze.allocUnsafe:Or,eu=O(Object.getPrototypeOf,Object),uu=Object.create,ou=We.propertyIsEnumerable,iu=Ve.splice,cu=tu?tu.isConcatSpreadable:Or,fu=tu?tu.toStringTag:Or,au=function(){
-try{var t=pn(Object,"defineProperty");return t({},"",{}),t}catch(t){}}(),lu=Math.floor,su=Object.getOwnPropertySymbols,hu=Ze?Ze.isBuffer:Or,vu=O(Object.keys,Object),pu=Math.max,yu=Math.min,gu=Date.now,bu=pn(Me,"DataView"),_u=pn(Me,"Map"),du=pn(Me,"Promise"),ju=pn(Me,"Set"),wu=pn(Me,"WeakMap"),Ou=pn(Object,"create"),mu=Mn(bu),Au=Mn(_u),zu=Mn(du),xu=Mn(ju),Su=Mn(wu),ku=tu?tu.prototype:Or,$u=ku?ku.valueOf:Or,Iu=ku?ku.toString:Or,Lu=function(){function t(){}return function(n){if(!tr(n))return{};if(uu)return uu(n);
-t.prototype=n;var r=new t;return t.prototype=Or,r}}();x.prototype.clear=S,x.prototype.delete=k,x.prototype.get=$,x.prototype.has=I,x.prototype.set=L,P.prototype.clear=E,P.prototype.delete=F,P.prototype.get=B,P.prototype.has=M,P.prototype.set=T,U.prototype.clear=N,U.prototype.delete=C,U.prototype.get=D,U.prototype.has=R,U.prototype.set=V,q.prototype.add=q.prototype.push=W,q.prototype.has=G,H.prototype.clear=J,H.prototype.delete=K,H.prototype.get=Q,H.prototype.has=X,H.prototype.set=Y;var Pu=tn(at),Eu=nn(),Fu=au?function(t,n){
-return au(t,"toString",{configurable:true,enumerable:false,value:yr(n),writable:true})}:gr,Bu=ju&&1/m(new ju([,-0]))[1]==Br?function(t){return new ju(t)}:_r,Mu=su?function(t){return null==t?[]:(t=Object(t),u(su(t),function(n){return ou.call(t,n)}))}:jr,Tu=su?function(t){for(var n=[];t;)f(n,Mu(t)),t=eu(t);return n}:jr,Uu=ht;(bu&&Uu(new bu(new ArrayBuffer(1)))!=fe||_u&&Uu(new _u)!=Kr||du&&Uu(du.resolve())!=Zr||ju&&Uu(new ju)!=re||wu&&Uu(new wu)!=ie)&&(Uu=function(t){var n=ht(t),r=n==Yr?t.constructor:Or,e=r?Mn(r):"";
-if(e)switch(e){case mu:return fe;case Au:return Kr;case zu:return Zr;case xu:return re;case Su:return ie}return n});var Nu=Fn(Fu),Cu=$n(function(t){var n=[];return je.test(t)&&n.push(""),t.replace(we,function(t,r,e,u){n.push(e?u.replace(Ae,"$1"):r||t)}),n}),Du=Et(Cn),Ru=rn(Tn);Hn.Cache=U;var Vu=pt(function(){return arguments}())?pt:function(t){return nr(t)&&Je.call(t,"callee")&&!ou.call(t,"callee")},qu=Array.isArray,Wu=hu||wr,Gu=Re?b(Re):dt,Hu=Zt(function(t,n,r){xt(t,n,r)}),Ju=fn(function(t,n){var r={};
-if(null==t)return r;var e=false;n=c(n,function(n){return n=Ct(n,t),e||(e=n.length>1),n}),Qt(t,ln(t),r),e&&(r=it(r,kr|$r|Ir,en));for(var u=n.length;u--;)Nt(r,n[u]);return r}),Ku=fn(function(t,n){return null==t?{}:kt(t,n)});z.constant=yr,z.filter=qn,z.flatten=Un,z.iteratee=br,z.keys=hr,z.keysIn=vr,z.map=Gn,z.memoize=Hn,z.merge=Hu,z.omit=Ju,z.pick=Ku,z.property=dr,z.pull=Du,z.pullAll=Cn,z.remove=Dn,z.toPlainObject=fr,z.uniq=Vn,z.values=pr,z.cloneDeep=Jn,z.eq=Kn,z.find=Ru,z.findIndex=Tn,z.get=lr,z.hasIn=sr,
-z.identity=gr,z.includes=Wn,z.isArguments=Vu,z.isArray=qu,z.isArrayLike=Qn,z.isArrayLikeObject=Xn,z.isBuffer=Wu,z.isFunction=Yn,z.isLength=Zn,z.isObject=tr,z.isObjectLike=nr,z.isPlainObject=rr,z.isString=er,z.isSymbol=ur,z.isTypedArray=Gu,z.last=Nn,z.stubArray=jr,z.stubFalse=wr,z.noop=_r,z.sortedIndexBy=Rn,z.toFinite=or,z.toInteger=ir,z.toNumber=cr,z.toString=ar,z.VERSION=mr,typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Me._=z, define(function(){return z})):Ue?((Ue.exports=z)._=z,
-Te._=z):Me._=z}).call(this);
\ No newline at end of file
+;(function(){function t(t,e){return t.set(e[0],e[1]),t}function e(t,e){return t.add(e),t}function n(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}function r(t,e){for(var n=-1,r=null==t?0:t.length;++ne.length)n=t;else{n=e;var r=0,o=-1,u=-1,c=n.length;for(0>r&&(r=-r>c?0:c+r),o=o>c?c:o,0>o&&(o+=c),c=r>o?0:o-r>>>0,r>>>=0,o=Array(c);++uu?ae:c,u=1),e=Object(e);++oi))return false;if((a=u.get(t))&&u.get(e))return a==e;var a=-1,l=true,s=2&n?new O:ae;for(u.set(t,e),u.set(e,t);++an&&(n=un(r+n,0)),l(t,yt(e,3),n)):-1}function Et(t){return(null==t?0:t.length)?B(t,1):[]}function Mt(t){
+var e=null==t?0:t.length;return e?t[e-1]:ae}function $t(t,e){var n;if(t&&t.length&&e&&e.length){n=e;var r=s,o=-1,u=n.length;for(t===n&&(n=ot(n));++o=t}function Vt(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function Ct(t){return null!=t&&typeof t=="object";
+}function Rt(t){return!(!Ct(t)||"[object Object]"!=L(t))&&(t=Je(t),null===t||(t=Ne.call(t,"constructor")&&t.constructor,typeof t=="function"&&t instanceof t&&Le.call(t)==Re))}function Tt(t){return typeof t=="string"||!Dn(t)&&Ct(t)&&"[object String]"==L(t)}function Wt(t){return typeof t=="symbol"||Ct(t)&&"[object Symbol]"==L(t)}function qt(t){return t?(t=Ht(t),t===fe||t===-fe?1.7976931348623157e308*(0>t?-1:1):t===t?t:0):0===t?t:0}function Gt(t){t=qt(t);var e=t%1;return t===t?e?t-e:t:0}function Ht(t){
+if(typeof t=="number")return t;if(Wt(t))return le;if(Vt(t)&&(t=typeof t.valueOf=="function"?t.valueOf():t,t=Vt(t)?t+"":t),typeof t!="string")return 0===t?t:+t;t=t.replace(ye,"");var e=_e.test(t);return e||Ae.test(t)?ke(t.slice(2),e?2:8):ge.test(t)?le:+t}function Jt(t){return ut(t,Zt(t))}function Kt(t){return null==t?"":Y(t)}function Qt(t,e,n){return t=null==t?ae:D(t,e),t===ae?n:t}function Xt(t,e){var n;if(n=null!=t){n=t;var r;r=tt(e,n);for(var o=-1,u=r.length,c=false;++ot)&&(t==e.length-1?e.pop():Xe.call(e,t,1),--this.size,true)},w.prototype.get=function(t){var e=this.__data__;return t=I(e,t),0>t?ae:e[t][1]},w.prototype.has=function(t){return-1r?(++this.size,n.push([t,e])):n[r][1]=e,this},m.prototype.clear=function(){this.size=0,this.__data__={
+hash:new A,map:new(ln||w),string:new A}},m.prototype.delete=function(t){return t=jt(this,t).delete(t),this.size-=t?1:0,t},m.prototype.get=function(t){return jt(this,t).get(t)},m.prototype.has=function(t){return jt(this,t).has(t)},m.prototype.set=function(t,e){var n=jt(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this},O.prototype.add=O.prototype.push=function(t){return this.__data__.set(t,"__lodash_hash_undefined__"),this},O.prototype.has=function(t){return this.__data__.has(t)},S.prototype.clear=function(){
+this.__data__=new w,this.size=0},S.prototype.delete=function(t){var e=this.__data__;return t=e.delete(t),this.size=e.size,t},S.prototype.get=function(t){return this.__data__.get(t)},S.prototype.has=function(t){return this.__data__.has(t)},S.prototype.set=function(t,e){var n=this.__data__;if(n instanceof w){var r=n.__data__;if(!ln||199>r.length)return r.push([t,e]),this.size=++n.size,this;n=this.__data__=new m(r)}return n.set(t,e),this.size=n.size,this};var On=function(t,e){return function(n,r){if(null==n)return n;
+if(!Dt(n))return t(n,r);for(var o=n.length,u=e?o:-1,c=Object(n);(e?u--:++un&&(n=un(r+n,0)),Tt(t)?n<=r&&-1