From e71a4b6589f01df0ce086542cdccbe8b671b994b Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 19 Jun 2018 18:34:22 -0500 Subject: [PATCH 01/52] Wip --- app/assets/javascripts/app/models/api/item.js | 54 +++++++++++-------- app/assets/javascripts/app/models/api/mfa.js | 16 +++--- .../app/models/api/serverExtension.js | 28 +++++----- .../javascripts/app/models/app/component.js | 6 ++- .../javascripts/app/models/app/editor.js | 5 +- .../javascripts/app/models/app/extension.js | 5 +- app/assets/javascripts/app/models/app/note.js | 7 +-- app/assets/javascripts/app/models/app/tag.js | 5 +- .../javascripts/app/models/app/theme.js | 1 - .../javascripts/app/services/modelManager.js | 14 ++--- 10 files changed, 73 insertions(+), 68 deletions(-) diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js index 6fe6fdee7..bddd3f158 100644 --- a/app/assets/javascripts/app/models/api/item.js +++ b/app/assets/javascripts/app/models/api/item.js @@ -30,7 +30,9 @@ class Item { } try { - return JSON.parse(this.content); + // console.log("Parsing json", this.content); + this.content = JSON.parse(this.content); + return this.content; } catch (e) { console.log("Error parsing json", e, this); return {}; @@ -58,6 +60,29 @@ class Item { } } + mapContentToLocalProperties(contentObj) { + if(contentObj.appData) { + this.appData = contentObj.appData; + } + if(!this.appData) { this.appData = {}; } + } + + createContentJSONFromProperties() { + return this.structureParams(); + } + + referenceParams() { + // subclasses can override + return this.contentObject.references || []; + } + + structureParams() { + var params = this.contentObject; + params.appData = this.appData; + params.references = this.referenceParams(); + return params; + } + 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, @@ -119,28 +144,6 @@ class Item { } } - 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 } @@ -172,6 +175,11 @@ class Item { potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID) { // optional override + for(var reference of this.content.references) { + if(reference.uuid == oldUUID) { + reference.uuid = newUUID; + } + } } allReferencedObjects() { diff --git a/app/assets/javascripts/app/models/api/mfa.js b/app/assets/javascripts/app/models/api/mfa.js index d3792654d..8c17fcb78 100644 --- a/app/assets/javascripts/app/models/api/mfa.js +++ b/app/assets/javascripts/app/models/api/mfa.js @@ -4,14 +4,14 @@ class Mfa extends Item { super(json_obj); } - mapContentToLocalProperties(content) { - super.mapContentToLocalProperties(content) - this.serverContent = content; - } - - structureParams() { - return _.merge(this.serverContent, super.structureParams()); - } + // mapContentToLocalProperties(content) { + // super.mapContentToLocalProperties(content) + // this.serverContent = content; + // } + // + // structureParams() { + // return _.merge(this.serverContent, super.structureParams()); + // } toJSON() { return {uuid: this.uuid} diff --git a/app/assets/javascripts/app/models/api/serverExtension.js b/app/assets/javascripts/app/models/api/serverExtension.js index 8149c5a95..03c5e95fc 100644 --- a/app/assets/javascripts/app/models/api/serverExtension.js +++ b/app/assets/javascripts/app/models/api/serverExtension.js @@ -9,20 +9,20 @@ class ServerExtension extends Item { 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; - } + // 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} diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index 57e609ca8..25d05cd1b 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -24,6 +24,7 @@ class Component extends Item { 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; @@ -82,8 +83,9 @@ class Component extends Item { associatedItemIds: this.associatedItemIds, }; - _.merge(params, super.structureParams()); - return params; + var superParams = super.structureParams(); + Object.assign(superParams, params); + return superParams; } toJSON() { diff --git a/app/assets/javascripts/app/models/app/editor.js b/app/assets/javascripts/app/models/app/editor.js index 3a38f0734..a1cb8c5b5 100644 --- a/app/assets/javascripts/app/models/app/editor.js +++ b/app/assets/javascripts/app/models/app/editor.js @@ -28,8 +28,9 @@ class Editor extends Item { systemEditor: this.systemEditor }; - _.merge(params, super.structureParams()); - return params; + var superParams = super.structureParams(); + Object.assign(superParams, params); + return superParams; } referenceParams() { diff --git a/app/assets/javascripts/app/models/app/extension.js b/app/assets/javascripts/app/models/app/extension.js index f990432a5..7df86b99f 100644 --- a/app/assets/javascripts/app/models/app/extension.js +++ b/app/assets/javascripts/app/models/app/extension.js @@ -54,8 +54,9 @@ class Extension extends Component { supported_types: this.supported_types }; - _.merge(params, super.structureParams()); - return params; + var superParams = super.structureParams(); + Object.assign(superParams, params); + return superParams; } } diff --git a/app/assets/javascripts/app/models/app/note.js b/app/assets/javascripts/app/models/app/note.js index e2b981575..9070070aa 100644 --- a/app/assets/javascripts/app/models/app/note.js +++ b/app/assets/javascripts/app/models/app/note.js @@ -35,8 +35,9 @@ class Note extends Item { text: this.text }; - _.merge(params, super.structureParams()); - return params; + var superParams = super.structureParams(); + Object.assign(superParams, params); + return superParams; } addItemAsRelationship(item) { @@ -71,7 +72,7 @@ class Note extends Item { removeReferencesNotPresentIn(references) { this.savedTagsString = null; - + super.removeReferencesNotPresentIn(references); var uuids = references.map(function(ref){return ref.uuid}); diff --git a/app/assets/javascripts/app/models/app/tag.js b/app/assets/javascripts/app/models/app/tag.js index b83585234..04bc11882 100644 --- a/app/assets/javascripts/app/models/app/tag.js +++ b/app/assets/javascripts/app/models/app/tag.js @@ -26,8 +26,9 @@ class Tag extends Item { title: this.title }; - _.merge(params, super.structureParams()); - return params; + var superParams = super.structureParams(); + Object.assign(superParams, params); + return superParams; } addItemAsRelationship(item) { diff --git a/app/assets/javascripts/app/models/app/theme.js b/app/assets/javascripts/app/models/app/theme.js index 7d03cb2c3..0b8fe2c1f 100644 --- a/app/assets/javascripts/app/models/app/theme.js +++ b/app/assets/javascripts/app/models/app/theme.js @@ -2,7 +2,6 @@ class Theme extends Component { constructor(json_obj) { super(json_obj); - this.area = "themes"; } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 8bbb94399..1a1d03610 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -26,10 +26,6 @@ class ModelManager { this.itemsPendingRemoval = []; this.items = []; this._extensions = []; - this.acceptableContentTypes = [ - "Note", "Tag", "Extension", "SN|Editor", "SN|Theme", - "SN|Component", "SF|Extension", "SN|UserPreferences", "SF|MFA" - ]; } resetLocalMemory() { @@ -66,7 +62,6 @@ class ModelManager { this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid); - console.log(item.uuid, "-->", newItem.uuid); var block = () => { @@ -162,16 +157,15 @@ class ModelManager { } 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 == true) { 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) { + if(item) { modelsToNotifyObserversOf.push(item); this.removeItemLocally(item); } @@ -418,9 +412,7 @@ class ModelManager { /* Used when changing encryption key */ setAllItemsDirty(dontUpdateClientDates = true) { - var relevantItems = this.allItems.filter(function(item){ - return _.includes(this.acceptableContentTypes, item.content_type); - }.bind(this)); + var relevantItems = this.allItems; for(var item of relevantItems) { item.setDirty(true, dontUpdateClientDates); From 0e5f1f6478596b438178d4414aab18e360dc53e9 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 27 Jun 2018 10:39:47 -0500 Subject: [PATCH 02/52] Refactor model manager to expect external lib --- .../app/directives/views/accountMenu.js | 2 +- app/assets/javascripts/app/models/api/item.js | 11 - .../javascripts/app/models/app/editor.js | 6 +- app/assets/javascripts/app/models/app/note.js | 5 +- app/assets/javascripts/app/models/app/tag.js | 4 - .../javascripts/app/services/modelManager.js | 404 +----------------- 6 files changed, 11 insertions(+), 421 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 7142dc0fd..31b677134 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -248,7 +248,7 @@ class AccountMenu { items.forEach(function(item){ item.setDirty(true, true); item.deleted = false; - item.markAllReferencesDirty(true); + modelManager.markAllReferencesDirtyForItem(item, true); // We don't want to activate any components during import process in case of exceptions // breaking up the import proccess diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js index 425b354af..52a28c1d4 100644 --- a/app/assets/javascripts/app/models/api/item.js +++ b/app/assets/javascripts/app/models/api/item.js @@ -122,12 +122,6 @@ class Item { } } - 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}); @@ -182,11 +176,6 @@ class Item { } } - allReferencedObjects() { - // must override - return []; - } - doNotEncrypt() { return false; } diff --git a/app/assets/javascripts/app/models/app/editor.js b/app/assets/javascripts/app/models/app/editor.js index a1cb8c5b5..b25c50885 100644 --- a/app/assets/javascripts/app/models/app/editor.js +++ b/app/assets/javascripts/app/models/app/editor.js @@ -79,11 +79,7 @@ class Editor extends Item { this.notes.push(newItem); } } - - allReferencedObjects() { - return this.notes; - } - + toJSON() { return {uuid: this.uuid} } diff --git a/app/assets/javascripts/app/models/app/note.js b/app/assets/javascripts/app/models/app/note.js index 9070070aa..b1140acf8 100644 --- a/app/assets/javascripts/app/models/app/note.js +++ b/app/assets/javascripts/app/models/app/note.js @@ -97,16 +97,13 @@ class Note extends Item { } informReferencesOfUUIDChange(oldUUID, newUUID) { + super.informReferencesOfUUIDChange(); for(var tag of this.tags) { _.pull(tag.notes, {uuid: oldUUID}); tag.notes.push(this); } } - allReferencedObjects() { - return this.tags; - } - safeText() { return this.text || ""; } diff --git a/app/assets/javascripts/app/models/app/tag.js b/app/assets/javascripts/app/models/app/tag.js index 04bc11882..69e3437da 100644 --- a/app/assets/javascripts/app/models/app/tag.js +++ b/app/assets/javascripts/app/models/app/tag.js @@ -84,10 +84,6 @@ class Tag extends Item { 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; diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index df9d461c4..018a806bc 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -1,112 +1,19 @@ class ModelManager { 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"; - - ModelManager.isMappingSourceRetrieved = (source) => { - return [ - ModelManager.MappingSourceRemoteRetrieved, - ModelManager.MappingSourceComponentRetrieved, - ModelManager.MappingSourceRemoteActionRetrieved - ].includes(source); - } - - this.storageManager = storageManager; + super(storageManager); this.notes = []; this.tags = []; - this.itemSyncObservers = []; - this.itemChangeObservers = []; - this.itemsPendingRemoval = []; - this.items = []; this._extensions = []; } resetLocalMemory() { + super.resetLocalMemory(); this.notes.length = 0; this.tags.length = 0; - this.items.length = 0; this._extensions.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 - 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}); - } - findOrCreateTagByTitle(title) { var tag = _.find(this.tags, {title: title}) if(!tag) { @@ -116,181 +23,10 @@ class ModelManager { 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 isDirtyItemPendingDelete = false; - if(json_obj.deleted == true) { - 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) { - 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,11 +47,7 @@ class ModelManager { } } } - - if(!_.find(this.items, {uuid: item.uuid})) { - this.items.push(item); - } - }.bind(this)); + }); } resortTag(tag) { @@ -326,76 +58,12 @@ 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 @@ -406,25 +74,10 @@ class ModelManager { } else if(item.content_type == "Extension") { _.pull(this._extensions, item); } - - item.removeAndDirtyAllRelationships(); - } - - /* Used when changing encryption key */ - setAllItemsDirty(dontUpdateClientDates = true) { - var relevantItems = this.allItems; - - for(var item of relevantItems) { - item.setDirty(true, dontUpdateClientDates); - } } removeItemLocally(item, callback) { - _.pull(this.items, item); - - item.isBeingRemovedLocally(); - - this.itemsPendingRemoval.push(item.uuid); + super.removeItemLocally(item, callback); if(item.content_type == "Tag") { _.pull(this.tags, item); @@ -433,49 +86,8 @@ class ModelManager { } else if(item.content_type == "Extension") { _.pull(this._extensions, item); } - - this.storageManager.deleteModel(item, callback); } - /* - Relationships - */ - - createRelationshipBetweenItems(itemOne, itemTwo) { - itemOne.addItemAsRelationship(itemTwo); - itemTwo.addItemAsRelationship(itemOne); - - itemOne.setDirty(true); - itemTwo.setDirty(true); - } - - - /* - 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 */ From e7ea390fd454d36346bb52bcce659bc43d3207df Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 27 Jun 2018 15:40:57 -0500 Subject: [PATCH 03/52] Integrate SFJS model management --- .../javascripts/app/controllers/editor.js | 6 +- .../javascripts/app/controllers/home.js | 13 +- .../javascripts/app/controllers/notes.js | 5 +- .../app/directives/views/accountMenu.js | 2 +- app/assets/javascripts/app/models/api/item.js | 314 -- app/assets/javascripts/app/models/api/mfa.js | 2 +- .../app/models/api/serverExtension.js | 2 +- .../javascripts/app/models/app/component.js | 2 +- .../javascripts/app/models/app/editor.js | 8 +- app/assets/javascripts/app/models/app/note.js | 42 +- app/assets/javascripts/app/models/app/tag.js | 53 +- .../app/models/local/encryptedStorage.js | 2 +- .../app/models/local/itemParams.js | 72 - .../app/services/actionsManager.js | 4 +- .../javascripts/app/services/authManager.js | 2 +- .../app/services/componentManager.js | 12 +- .../app/services/desktopManager.js | 4 +- .../app/services/migrationManager.js | 8 +- .../javascripts/app/services/modelManager.js | 23 +- .../app/services/storageManager.js | 2 +- .../javascripts/app/services/syncManager.js | 15 +- package-lock.json | 3697 ++++++++++++++++- package.json | 2 +- test/controllers/.keep | 0 test/controllers/apikey_controller_test.rb | 7 - test/controllers/names_controller_test.rb | 7 - test/controllers/proto_controller_test.rb | 7 - test/fixtures/.keep | 0 test/fixtures/api_keys.yml | 7 - test/fixtures/names.yml | 7 - test/helpers/.keep | 0 test/integration/.keep | 0 test/javascripts/mocha.js | 137 + test/mailers/.keep | 0 test/models/.keep | 0 test/models/api_key_test.rb | 7 - test/models/name_test.rb | 7 - .../javascripts/lodash/lodash.custom.js | 38 +- .../javascripts/lodash/lodash.custom.min.js | 93 +- 39 files changed, 3996 insertions(+), 613 deletions(-) delete mode 100644 app/assets/javascripts/app/models/api/item.js delete mode 100644 app/assets/javascripts/app/models/local/itemParams.js delete mode 100644 test/controllers/.keep delete mode 100644 test/controllers/apikey_controller_test.rb delete mode 100644 test/controllers/names_controller_test.rb delete mode 100644 test/controllers/proto_controller_test.rb delete mode 100644 test/fixtures/.keep delete mode 100644 test/fixtures/api_keys.yml delete mode 100644 test/fixtures/names.yml delete mode 100644 test/helpers/.keep delete mode 100644 test/integration/.keep create mode 100644 test/javascripts/mocha.js delete mode 100644 test/mailers/.keep delete mode 100644 test/models/.keep delete mode 100644 test/models/api_key_test.rb delete mode 100644 test/models/name_test.rb diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 39250180e..023889f23 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -64,7 +64,7 @@ angular.module('app') return; } - if(!ModelManager.isMappingSourceRetrieved(source)) { + if(!SFModelManager.isMappingSourceRetrieved(source)) { return; } @@ -554,7 +554,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([this.note], SFModelManager.MappingSourceLocalSaved); } } @@ -564,7 +564,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([this.note], SFModelManager.MappingSourceLocalSaved); } else if(action === "save-items" || action === "save-success" || action == "save-error") { diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 7bd300fd2..cc4ed01ef 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -87,7 +87,8 @@ angular.module('app') } function loadAllTag() { - var allTag = new Tag({all: true, title: "All"}); + var allTag = new Tag({content: {title: "All"}}); + allTag.all = true; allTag.needsLoad = true; $scope.allTag = allTag; $scope.tags = modelManager.tags; @@ -95,7 +96,8 @@ angular.module('app') } function loadArchivedTag() { - var archiveTag = new Tag({archiveTag: true, title: "Archived"}); + var archiveTag = new Tag({content: {title: "Archived"}}); + archiveTag.archiveTag = true; $scope.archiveTag = archiveTag; $scope.archiveTag.notes = modelManager.notes; } @@ -115,8 +117,6 @@ angular.module('app') for(var tagToRemove of toRemove) { note.removeItemAsRelationship(tagToRemove); - tagToRemove.removeItemAsRelationship(note); - tagToRemove.setDirty(true); } var tags = []; @@ -128,7 +128,7 @@ angular.module('app') } for(var tag of tags) { - modelManager.createRelationshipBetweenItems(note, tag); + note.addItemAsRelationship(tag); } note.setDirty(true); @@ -190,7 +190,8 @@ angular.module('app') modelManager.addItem(note); if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag) { - modelManager.createRelationshipBetweenItems($scope.selectedTag, note); + note.addItemAsRelationship($scope.selectedTag); + note.setDirty(true); } } diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index e6f51c142..4759856cb 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -217,8 +217,9 @@ 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; + 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); } diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 31b677134..2a271fd1a 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -244,7 +244,7 @@ class AccountMenu { $scope.importJSONData = function(data, password, callback) { var onDataReady = function(errorCount) { - var items = modelManager.mapResponseItemsToLocalModels(data.items, ModelManager.MappingSourceFileImport); + var items = modelManager.mapResponseItemsToLocalModels(data.items, SFModelManager.MappingSourceFileImport); items.forEach(function(item){ item.setDirty(true, true); item.deleted = false; 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 52a28c1d4..000000000 --- a/app/assets/javascripts/app/models/api/item.js +++ /dev/null @@ -1,314 +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 { - // console.log("Parsing json", this.content); - this.content = JSON.parse(this.content); - return 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(); - } - } - - mapContentToLocalProperties(contentObj) { - if(contentObj.appData) { - this.appData = contentObj.appData; - } - if(!this.appData) { this.appData = {}; } - } - - createContentJSONFromProperties() { - return this.structureParams(); - } - - referenceParams() { - // subclasses can override - return this.contentObject.references || []; - } - - structureParams() { - var params = this.contentObject; - params.appData = this.appData; - params.references = this.referenceParams(); - return params; - } - - 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(); - } - } - - 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); - } - } - - 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 - for(var reference of this.content.references) { - if(reference.uuid == oldUUID) { - reference.uuid = newUUID; - } - } - } - - 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 index 8c17fcb78..e3371f17e 100644 --- a/app/assets/javascripts/app/models/api/mfa.js +++ b/app/assets/javascripts/app/models/api/mfa.js @@ -1,4 +1,4 @@ -class Mfa extends Item { +class Mfa extends SFItem { constructor(json_obj) { super(json_obj); diff --git a/app/assets/javascripts/app/models/api/serverExtension.js b/app/assets/javascripts/app/models/api/serverExtension.js index 03c5e95fc..4d7d3dc0e 100644 --- a/app/assets/javascripts/app/models/api/serverExtension.js +++ b/app/assets/javascripts/app/models/api/serverExtension.js @@ -1,4 +1,4 @@ -class ServerExtension extends Item { +class ServerExtension extends SFItem { constructor(json_obj) { super(json_obj); diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index 25d05cd1b..a077eb733 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -1,4 +1,4 @@ -class Component extends Item { +class Component extends SFItem { constructor(json_obj) { // If making a copy of an existing component (usually during sign in if you have a component active in the session), diff --git a/app/assets/javascripts/app/models/app/editor.js b/app/assets/javascripts/app/models/app/editor.js index b25c50885..cc71e1657 100644 --- a/app/assets/javascripts/app/models/app/editor.js +++ b/app/assets/javascripts/app/models/app/editor.js @@ -1,4 +1,4 @@ -class Editor extends Item { +class Editor extends SFItem { constructor(json_obj) { super(json_obj); @@ -68,18 +68,18 @@ class Editor extends Item { var uuids = references.map(function(ref){return ref.uuid}); this.notes.forEach(function(note){ if(!uuids.includes(note.uuid)) { - _.pull(this.notes, note); + _.remove(this.notes, {uuid: note.uuid}); } }.bind(this)) } potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID) { if(newItem.content_type === "Note" && _.find(this.notes, {uuid: oldUUID})) { - _.pull(this.notes, {uuid: oldUUID}); + _.remove(this.notes, {uuid: oldUUID}); this.notes.push(newItem); } } - + toJSON() { return {uuid: this.uuid} } diff --git a/app/assets/javascripts/app/models/app/note.js b/app/assets/javascripts/app/models/app/note.js index b1140acf8..3669b3420 100644 --- a/app/assets/javascripts/app/models/app/note.js +++ b/app/assets/javascripts/app/models/app/note.js @@ -1,4 +1,4 @@ -class Note extends Item { +export class Note extends SFItem { constructor(json_obj) { super(json_obj); @@ -21,14 +21,6 @@ class Note extends Item { 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, @@ -44,8 +36,9 @@ class Note extends Item { this.savedTagsString = null; if(item.content_type == "Tag") { - if(!_.find(this.tags, item)) { + if(!_.find(this.tags, {uuid: item.uuid})) { this.tags.push(item); + item.notes.push(this); } } super.addItemAsRelationship(item); @@ -55,38 +48,29 @@ class Note extends Item { this.savedTagsString = null; if(item.content_type == "Tag") { - _.pull(this.tags, item); + _.remove(this.tags, {uuid: item.uuid}); + _.remove(item.notes, {uuid: this.uuid}); } super.removeItemAsRelationship(item); } - removeAndDirtyAllRelationships() { + updateLocalRelationships() { 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 references = this.content.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); + _.remove(tag.notes, {uuid: this.uuid}); + _.remove(this.tags, {uuid: tag.uuid}); } }.bind(this)) } isBeingRemovedLocally() { this.tags.forEach(function(tag){ - _.pull(tag.notes, this); + _.remove(tag.notes, {uuid: this.uuid}); }.bind(this)) super.isBeingRemovedLocally(); } @@ -99,7 +83,7 @@ class Note extends Item { informReferencesOfUUIDChange(oldUUID, newUUID) { super.informReferencesOfUUIDChange(); for(var tag of this.tags) { - _.pull(tag.notes, {uuid: oldUUID}); + _.remove(tag.notes, {uuid: oldUUID}); tag.notes.push(this); } } @@ -116,10 +100,6 @@ class Note extends Item { 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 index 69e3437da..f1c89f237 100644 --- a/app/assets/javascripts/app/models/app/tag.js +++ b/app/assets/javascripts/app/models/app/tag.js @@ -1,4 +1,4 @@ -class Tag extends Item { +export class Tag extends SFItem { constructor(json_obj) { super(json_obj); @@ -13,14 +13,6 @@ class Tag extends Item { 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 @@ -31,59 +23,20 @@ class Tag extends Item { return superParams; } - 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); + _.remove(note.tags, {uuid: this.uuid}); }.bind(this)) super.isBeingRemovedLocally(); } informReferencesOfUUIDChange(oldUUID, newUUID) { for(var note of this.notes) { - _.pull(note.tags, {uuid: oldUUID}); + _.remove(note.tags, {uuid: oldUUID}); note.tags.push(this); } } - get content_type() { - return "Tag"; - } - static arrayToDisplayString(tags) { return tags.sort((a, b) => {return a.title > b.title}).map(function(tag, i){ return "#" + tag.title; diff --git a/app/assets/javascripts/app/models/local/encryptedStorage.js b/app/assets/javascripts/app/models/local/encryptedStorage.js index 764d0ae24..b70c678f6 100644 --- a/app/assets/javascripts/app/models/local/encryptedStorage.js +++ b/app/assets/javascripts/app/models/local/encryptedStorage.js @@ -1,4 +1,4 @@ -class EncryptedStorage extends Item { +class EncryptedStorage extends SFItem { constructor(json_obj) { super(json_obj); 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/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index 21c8739a3..fb283a912 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -74,7 +74,7 @@ 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); } @@ -179,7 +179,7 @@ class ActionsManager { if(decrypted) { keys = null; } - var itemParams = new ItemParams(item, keys, this.authManager.protocolVersion()); + var itemParams = new SFItemParams(item, keys, this.authManager.protocolVersion()); return itemParams.paramsForExtension(); } diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index bcc4321b5..49793b140 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -296,7 +296,7 @@ angular.module('app') this.userPreferencesDidChange(); }, (valueCallback) => { // Safe to create. Create and return object. - var prefs = new Item({content_type: prefsContentType}); + var prefs = new SFItem({content_type: prefsContentType}); modelManager.addItem(prefs); prefs.setDirty(true); $rootScope.sync("authManager singletonCreate"); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index a7d3eaaba..2916eef3f 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -53,13 +53,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; // } @@ -70,7 +70,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); } @@ -195,11 +195,11 @@ class ComponentManager { /* 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); @@ -468,7 +468,7 @@ 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}); diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 23edc562d..a643127a7 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,7 +96,7 @@ 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); diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js index 9fec09d65..3e2a50ad4 100644 --- a/app/assets/javascripts/app/services/migrationManager.js +++ b/app/assets/javascripts/app/services/migrationManager.js @@ -36,9 +36,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); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 018a806bc..52012717d 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -1,10 +1,25 @@ -class ModelManager { +SFModelManager.ContentTypeClassMapping = { + "Note" : Note, + "Tag" : Tag, + "Extension" : Extension, + "SN|Editor" : Editor, + "SN|Theme" : Theme, + "SN|Component" : Component, + "SF|Extension" : ServerExtension, + "SF|MFA" : Mfa +}; + +SFItem.AppDomain = "org.standardnotes.sn"; + +class ModelManager extends SFModelManager { constructor(storageManager) { - super(storageManager); + super(); this.notes = []; this.tags = []; this._extensions = []; + + this.storageManager = storageManager; } resetLocalMemory() { @@ -17,7 +32,7 @@ class ModelManager { 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}}); this.addItem(tag); } return tag; @@ -86,6 +101,8 @@ class ModelManager { } else if(item.content_type == "Extension") { _.pull(this._extensions, item); } + + this.storageManager.deleteModel(item, callback); } /* diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index c82500c68..5910e655c 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -153,7 +153,7 @@ class StorageManager { encryptedStorage.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.version); params.paramsForSync().then((syncParams) => { this.setItem("encryptedStorage", JSON.stringify(syncParams), StorageManager.Fixed); }) diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 031d4169d..f2b87ac5c 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -48,7 +48,7 @@ class SyncManager { var keys = this.authManager.offline() ? this.passcodeManager.keys() : this.authManager.keys(); Promise.all(items.map(async (item) => { - var itemParams = new ItemParams(item, keys, version); + var itemParams = new SFItemParams(item, keys, version); itemParams = await itemParams.paramsForLocalStorage(); if(offlineOnly) { delete itemParams.dirty; @@ -79,13 +79,13 @@ class SyncManager { var processed = []; var completion = () => { - Item.sortItemsByDate(processed); + SFItem.sortItemsByDate(processed); callback(processed); } var decryptNext = async () => { var subitems = items.slice(current, current + iteration); - var processedSubitems = await this.handleItemsResponse(subitems, null, ModelManager.MappingSourceLocalRetrieved); + var processedSubitems = await this.handleItemsResponse(subitems, null, SFModelManager.MappingSourceLocalRetrieved); processed.push(processedSubitems); current += subitems.length; @@ -93,6 +93,7 @@ class SyncManager { if(current < total) { this.$timeout(() => { decryptNext(); }); } else { + // this.modelManager.resolveReferencesForAllItems() completion(); } } @@ -341,7 +342,7 @@ class SyncManager { params.limit = 150; await Promise.all(subItems.map((item) => { - var itemParams = new ItemParams(item, keys, version); + var itemParams = new SFItemParams(item, keys, version); itemParams.additionalFields = options.additionalFields; return itemParams.paramsForSync(); })).then((itemsParams) => { @@ -386,7 +387,7 @@ class SyncManager { // 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); + var retrieved = await this.handleItemsResponse(response.retrieved_items, null, SFModelManager.MappingSourceRemoteRetrieved); // Append items to master list of retrieved items for this ongoing sync operation this.allRetreivedItems = this.allRetreivedItems.concat(retrieved); @@ -397,7 +398,7 @@ class SyncManager { var omitFields = ["content", "auth_hash"]; // Map saved items to local data - var saved = await this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved); + var saved = await this.handleItemsResponse(response.saved_items, omitFields, SFModelManager.MappingSourceRemoteSaved); // Append items to master list of saved items for this ongoing sync operation this.allSavedItems = this.allSavedItems.concat(saved); @@ -504,7 +505,7 @@ class SyncManager { refreshErroredItems() { var erroredItems = this.modelManager.allItems.filter((item) => {return item.errorDecrypting == true}); if(erroredItems.length > 0) { - this.handleItemsResponse(erroredItems, null, ModelManager.MappingSourceLocalRetrieved); + this.handleItemsResponse(erroredItems, null, SFModelManager.MappingSourceLocalRetrieved); } } diff --git a/package-lock.json b/package-lock.json index 94c93c089..7699cddf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5721,10 +5721,3699 @@ "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, + "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" + } + } + } + }, + "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", + "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" + } + }, + "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": { + "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" + } + }, + "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": { + "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" + } + }, + "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", + "minimist": "1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "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, + "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 + }, + "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", + "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-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", + "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-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", + "statuses": "1.5.0" + }, + "dependencies": { + "statuses": { + "version": "1.5.0", + "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" + } + }, + "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", + "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", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "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" + } + }, + "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": { + "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" + } + }, + "range-parser": { + "version": "1.2.0", + "bundled": true + }, + "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-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", + "statuses": "1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "bundled": true + } + } + }, + "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, + "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 + }, + "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": { + "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 + }, + "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": { + "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 + } + } + } + } }, "statuses": { "version": "1.5.0", diff --git a/package.json b/package.json index 7aa34d9db..31b877db1 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "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": "file:~/Desktop/sf/sfjs" }, "license": "GPL-3.0" } 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/mocha.js b/test/javascripts/mocha.js new file mode 100644 index 000000000..82f562e7d --- /dev/null +++ b/test/javascripts/mocha.js @@ -0,0 +1,137 @@ +describe("notes and tags", () => { + 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", + } + }; + noteParams.content.references = [ + { + uuid: tagParams.uuid, + content_type: tagParams.content_type + } + ] + + tagParams.content.references = [ + { + uuid: noteParams.uuid, + content_type: noteParams.content_type + } + ] + + return [noteParams, tagParams]; + } + + it('uses proper class for note', () => { + let modelManager = createModelManager(); + let noteParams = getNoteParams(); + modelManager.mapResponseItemsToLocalModels([noteParams]); + let note = modelManager.allItemsMatchingTypes(["Note"])[0]; + expect(note).to.be.an.instanceOf(Note); + }); + + it('creates two-way relationship between note and tag', () => { + let modelManager = createModelManager(); + + let pair = createRelatedNoteTagPair(); + let noteParams = pair[0]; + let tagParams = pair[1]; + + expect(tagParams.content.references.length).to.equal(1); + 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).to.not.be.null; + expect(tag).to.not.be.null; + + expect(note.content.references.length).to.equal(1); + expect(tag.content.references.length).to.equal(1); + + expect(note.hasRelationshipWithItem(tag)).to.equal(true); + 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 = 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(1); + expect(tag.content.references.length).to.equal(1); + + noteParams.content.references = []; + modelManager.mapResponseItemsToLocalModels([noteParams]); + + expect(note.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('properly handles duplication', () => { + let modelManager = 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]; + + var duplicateNote = modelManager.createDuplicateItem(note); + expect(note.uuid).to.equal(duplicateNote.uuid); + + expect(duplicateNote.content.references.length).to.equal(1); + expect(duplicateNote.tags.length).to.equal(1); + + expect(tag.content.references.length).to.equal(1); + expect(tag.notes.length).to.equal(1); + + // expect to be false + expect(note.dirty).to.not.be.ok; + expect(tag.dirty).to.not.be.ok; + }); +}); diff --git a/test/mailers/.keep b/test/mailers/.keep deleted file mode 100644 index e69de29bb..000000000 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/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 Date: Wed, 27 Jun 2018 17:28:42 -0500 Subject: [PATCH 04/52] Use SF Sync, Http, and Storage Manager --- .../javascripts/app/controllers/home.js | 18 +- .../javascripts/app/controllers/lockScreen.js | 2 +- .../app/directives/views/accountMenu.js | 6 +- .../javascripts/app/services/httpManager.js | 79 +-- .../app/services/storageManager.js | 3 +- .../javascripts/app/services/syncManager.js | 583 +----------------- 6 files changed, 30 insertions(+), 661 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index cc4ed01ef..ba42c4789 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -72,6 +72,22 @@ angular.module('app') function initiateSync() { authManager.loadInitialData(); + + syncManager.setKeyRequestHandler(() => { + let offline = authManager.offline(); + let version = offline ? passcodeManager.protocolVersion() : authManager.protocolVersion(); + let keys = offline ? passcodeManager.keys() : authManager.keys(); + return { + keys: keys, + offline: offline, + version: version + } + }); + + syncManager.setEventHandler((syncEvent, data) => { + $rootScope.$broadcast(syncEvent, data || {}); + }); + syncManager.loadLocalItems(function(items) { $scope.allTag.didLoad = true; $scope.$apply(); @@ -282,7 +298,7 @@ angular.module('app') } else { // sign out authManager.signOut(); - syncManager.destroyLocalData(function(){ + storageManager.clearAllData(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js index d3e8ad1e7..6dd3312eb 100644 --- a/app/assets/javascripts/app/controllers/lockScreen.js +++ b/app/assets/javascripts/app/controllers/lockScreen.js @@ -34,7 +34,7 @@ class LockScreen { } authManager.signOut(); - syncManager.destroyLocalData(function(){ + storageManager.clearAllData(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 2a271fd1a..ccbdc3682 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -9,8 +9,8 @@ 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}; @@ -176,7 +176,7 @@ class AccountMenu { } authManager.signOut(); - syncManager.destroyLocalData(function(){ + storageManager.clearAllData(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/services/httpManager.js b/app/assets/javascripts/app/services/httpManager.js index c8f0a722f..49d9433e4 100644 --- a/app/assets/javascripts/app/services/httpManager.js +++ b/app/assets/javascripts/app/services/httpManager.js @@ -1,80 +1,9 @@ -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(storageManager, $timeout); } - - 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/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index 5910e655c..681bf49fb 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -32,9 +32,10 @@ class MemoryStorage { } } -class StorageManager { +class StorageManager extends SFStorageManager { constructor(dbManager) { + super(); this.dbManager = dbManager; } diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index f2b87ac5c..eaac90668 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -1,584 +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 SFItemParams(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 = () => { - SFItem.sortItemsByDate(processed); - callback(processed); - } - - var decryptNext = async () => { - var subitems = items.slice(current, current + iteration); - var processedSubitems = await this.handleItemsResponse(subitems, null, SFModelManager.MappingSourceLocalRetrieved); - processed.push(processedSubitems); - - current += subitems.length; - - if(current < total) { - this.$timeout(() => { decryptNext(); }); - } else { - // this.modelManager.resolveReferencesForAllItems() - 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 SFItemParams(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, SFModelManager.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, SFModelManager.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, SFModelManager.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); } } From 39062f17fd4307713cc25fc42bc1f302f1e9bdc9 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 28 Jun 2018 11:44:23 -0500 Subject: [PATCH 05/52] Refactor auth manager and async storage manager --- .../javascripts/app/controllers/home.js | 8 +- .../app/directives/views/passwordWizard.js | 8 +- .../app/services/actionsManager.js | 8 +- .../app/services/archiveManager.js | 6 +- .../javascripts/app/services/authManager.js | 509 +++++++----------- .../app/services/desktopManager.js | 6 +- .../app/services/old_authManager.js | 326 +++++++++++ .../app/services/passcodeManager.js | 4 +- .../app/services/storageManager.js | 74 +-- 9 files changed, 576 insertions(+), 373 deletions(-) create mode 100644 app/assets/javascripts/app/services/old_authManager.js diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index ba42c4789..ec0f7c1c6 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -34,7 +34,7 @@ angular.module('app') window.location.reload(); } - function load() { + function async load() { // pass keys to storageManager to decrypt storage // Update: Wait, why? passcodeManager already handles this. // storageManager.setKeys(passcodeManager.keys()); @@ -73,10 +73,10 @@ angular.module('app') function initiateSync() { authManager.loadInitialData(); - syncManager.setKeyRequestHandler(() => { + syncManager.setKeyRequestHandler(async () => { let offline = authManager.offline(); - let version = offline ? passcodeManager.protocolVersion() : authManager.protocolVersion(); - let keys = offline ? passcodeManager.keys() : authManager.keys(); + let version = offline ? passcodeManager.protocolVersion() : await authManager.protocolVersion(); + let keys = offline ? passcodeManager.keys() : await authManager.keys(); return { keys: keys, offline: offline, diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js index 3b5024046..430d6ff4c 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; @@ -175,8 +175,8 @@ class PasswordWizard { // Ensure value for current password matches what's saved let authParams = 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 { @@ -209,7 +209,7 @@ class PasswordWizard { // 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) => { + authManager.changePassword(authManager.user.email, 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)); diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index fb283a912..ef626e97f 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -124,7 +124,7 @@ class ActionsManager { case "get": { this.httpManager.getAbsolute(action.url, {}, (response) => { action.error = false; - handleResponseDecryption(response, this.authManager.keys(), true); + handleResponseDecryption(response, await this.authManager.keys(), true); }, (response) => { action.error = true; customCallback(null); @@ -136,7 +136,7 @@ class ActionsManager { this.httpManager.getAbsolute(action.url, {}, (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 SFItemParams(item, keys, this.authManager.protocolVersion()); + var itemParams = new SFItemParams(item, keys, await this.authManager.protocolVersion()); return itemParams.paramsForExtension(); } diff --git a/app/assets/javascripts/app/services/archiveManager.js b/app/assets/javascripts/app/services/archiveManager.js index 638541afe..ccf18e8ad 100644 --- a/app/assets/javascripts/app/services/archiveManager.js +++ b/app/assets/javascripts/app/services/archiveManager.js @@ -10,7 +10,7 @@ class ArchiveManager { Public */ - downloadBackup(encrypted) { + async downloadBackup(encrypted) { // download in Standard File format var keys, authParams, protocolVersion; if(encrypted) { @@ -19,9 +19,9 @@ class ArchiveManager { authParams = this.passcodeManager.passcodeAuthParams(); protocolVersion = authParams.version; } else { - keys = this.authManager.keys(); + keys = await this.authManager.keys(); authParams = this.authManager.getAuthParams(); - protocolVersion = this.authManager.protocolVersion(); + protocolVersion = await this.authManager.protocolVersion(); } } this.__itemsData(keys, authParams, protocolVersion).then((data) => { diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 49793b140..4c6afb0e7 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -1,330 +1,203 @@ -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); + } + } + + getAuthParams() { + if(!this._authParams) { + this._authParams = JSON.parse(this.storageManager.getItemSync("auth_params")); + } + return this._authParams; + } + + keys() { + if(!this._keys) { + var mk = this.storageManager.getItemSync("mk"); + if(!mk) { + return null; + } + this._keys = {mk: mk, ak: this.storageManager.getItemSync("ak")}; + } + return this._keys; + } + + async protocolVersion() { + var authParams = 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}; - } - } + getAuthParamsForEmail(url, email, extraParams, callback) { + super.getAuthParamsForEmail(url, email, extraParams, (response) => { + callback(response); + }) + } + login(url, email, password, ephemeral, strictSignin, extraParams, callback) { + super.login(url, email, password, ephemeral, strictSignin, extraParams, (response) => { + if(!response.error) { + this.setEphemeral(ephemeral); + this.handleAuthResponse(response, email, url, authParams, keys); this.checkForSecurityUpdate(); } + this.$timeout(() => callback(response)); + }) + } - this.offline = function() { - return !this.user; - } - - 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 SFItem({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() { - 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(); - } - } - + handleAuthResponse(response, email, url, authParams, keys) { + try { + super.handleAuthResponse(response, email, url, authParams, keys); + this._authParams = authParams; + this.user = response.user; + this.storageManager.setItem("user", JSON.stringify(response.user)); + } catch (e) { + this.dbManager.displayOfflineAlert(); } + } + + register(url, email, password, ephemeral, callback) { + super.register(url, email, password, ephemeral, (response) => { + if(!response.error) { + this.setEphemeral(ephemeral); + } + callback(response); + }) + } + + changePassword(email, current_server_pw, newKeys, newAuthParams, callback) { + super.changePassword(email, current_server_pw, newKeys, newAuthParams, (response) => { + if(!response.error) { + // Allows security update status to be changed if neccessary + this.checkForSecurityUpdate(); + } + callback(response); + }) + } + + checkForSecurityUpdate() { + if(this.offline()) { + return false; + } + + let latest = SFJS.version(); + let updateAvailable = 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() { + this._keys = null; + this.user = null; + this._authParams = null; + } + + + /* User Preferences */ + + configureUserPrefs() { + let prefsContentType = "SN|UserPreferences"; + + this.singletonManager.registerSingleton({content_type: prefsContentType}, (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("authManager singletonCreate"); + valueCallback(prefs); + }); + } + + userPreferencesDidChange() { + this.$rootScope.$broadcast("user-preferences-changed"); + } + + syncUserPreferences() { + this.userPreferences.setDirty(true); + this.$rootScope.sync("syncUserPreferences"); + } + + 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/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index a643127a7..31f77f5dc 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -129,16 +129,16 @@ class DesktopManager { } } - desktop_requestBackupFile(callback) { + async desktop_requestBackupFile(callback) { var keys, authParams, protocolVersion; if(this.authManager.offline() && this.passcodeManager.hasPasscode()) { keys = this.passcodeManager.keys(); authParams = this.passcodeManager.passcodeAuthParams(); protocolVersion = authParams.version; } else { - keys = this.authManager.keys(); + keys = await this.authManager.keys(); authParams = this.authManager.getAuthParams(); - protocolVersion = this.authManager.protocolVersion(); + protocolVersion = await this.authManager.protocolVersion(); } this.modelManager.getAllItemsJSONData( diff --git a/app/assets/javascripts/app/services/old_authManager.js b/app/assets/javascripts/app/services/old_authManager.js new file mode 100644 index 000000000..4fd9eaf7d --- /dev/null +++ b/app/assets/javascripts/app/services/old_authManager.js @@ -0,0 +1,326 @@ +// angular.module('app') +// .provider('authManager', function () { +// +// function domainName() { +// var domain_comps = location.hostname.split("."); +// var domain = domain_comps[domain_comps.length - 2] + "." + domain_comps[domain_comps.length - 1]; +// return domain; +// } +// +// this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile) { +// return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile); +// } +// +// function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile) { +// +// 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}; +// } +// } +// +// this.checkForSecurityUpdate(); +// } +// +// this.offline = function() { +// return !this.user; +// } +// +// 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.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(!SFJS.supportedVersions().includes(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 SFItem({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() { +// 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(); +// } +// } +// +// } +// }); diff --git a/app/assets/javascripts/app/services/passcodeManager.js b/app/assets/javascripts/app/services/passcodeManager.js index b30711ecc..587a61027 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,7 +23,7 @@ angular.module('app') } this.passcodeAuthParams = function() { - return JSON.parse(storageManager.getItem("offlineParams", StorageManager.Fixed)); + return JSON.parse(storageManager.getItemSync("offlineParams", StorageManager.Fixed)); } this.protocolVersion = function() { diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index 681bf49fb..4f1ba26fb 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -101,7 +101,7 @@ class StorageManager extends SFStorageManager { } } - setItem(key, value, vaultKey) { + async setItem(key, value, vaultKey) { var storage = this.getVault(vaultKey); storage.setItem(key, value); @@ -110,25 +110,21 @@ class StorageManager extends SFStorageManager { } } - 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(); } @@ -197,36 +193,44 @@ class StorageManager extends SFStorageManager { 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); + }); } } From d6c26bd3446763b556ef84ae1de178e091fe5722 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 28 Jun 2018 19:26:28 -0500 Subject: [PATCH 06/52] Auth async --- .../javascripts/app/controllers/home.js | 2 +- .../app/directives/views/accountMenu.js | 5 +- .../app/directives/views/passwordWizard.js | 2 +- .../javascripts/app/services/authManager.js | 73 +++++++++---------- 4 files changed, 38 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index ec0f7c1c6..488012ccc 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -303,7 +303,7 @@ angular.module('app') }) } } 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/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index ccbdc3682..64cc0cf51 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -59,8 +59,7 @@ 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) => { + $scope.formData.ephemeral, $scope.formData.strictSignin, extraParams).then((response) => { if(!response || response.error) { syncManager.unlockSyncing(); @@ -108,7 +107,7 @@ 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){ + authManager.register($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral).then((response) => { if(!response || response.error) { $scope.formData.status = null; var error = response ? response.error : {message: "An unknown error occured."} diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js index 430d6ff4c..27d6c65a0 100644 --- a/app/assets/javascripts/app/directives/views/passwordWizard.js +++ b/app/assets/javascripts/app/directives/views/passwordWizard.js @@ -209,7 +209,7 @@ class PasswordWizard { // 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(authManager.user.email, currentServerPw, newKeys, newAuthParams, (response) => { + authManager.changePassword(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)); diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 4c6afb0e7..44d0a786f 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -56,17 +56,6 @@ class AuthManager extends SFAuthManager { return this._authParams; } - keys() { - if(!this._keys) { - var mk = this.storageManager.getItemSync("mk"); - if(!mk) { - return null; - } - this._keys = {mk: mk, ak: this.storageManager.getItemSync("ak")}; - } - return this._keys; - } - async protocolVersion() { var authParams = this.getAuthParams(); if(authParams && authParams.version) { @@ -82,26 +71,51 @@ class AuthManager extends SFAuthManager { } } - getAuthParamsForEmail(url, email, extraParams, callback) { - super.getAuthParamsForEmail(url, email, extraParams, (response) => { - callback(response); - }) + async resolveResponseInTimeout(response) { + return new Promise((resolve, reject) => { + this.$timeout(() => { + resolve(response); + }); + }); } - login(url, email, password, ephemeral, strictSignin, extraParams, callback) { - super.login(url, email, password, ephemeral, strictSignin, extraParams, (response) => { + 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.handleAuthResponse(response, email, url, authParams, keys); this.checkForSecurityUpdate(); } - this.$timeout(() => callback(response)); + + return this.resolveResponseInTimeout(response); }) } - handleAuthResponse(response, email, url, authParams, keys) { + async register(url, email, password, ephemeral) { + super.register(url, email, password).then((response) => { + if(!response.error) { + this.setEphemeral(ephemeral); + } + return this.resolveResponseInTimeout(response); + }) + } + + async changePassword(email, current_server_pw, newKeys, newAuthParams) { + super.changePassword(email, current_server_pw, newKeys, newAuthParams).then((response) => { + if(!response.error) { + this.checkForSecurityUpdate(); + } + return this.resolveResponseInTimeout(response); + }) + } + + async handleAuthResponse(response, email, url, authParams, keys) { try { - super.handleAuthResponse(response, email, url, authParams, keys); + await super.handleAuthResponse(response, email, url, authParams, keys); this._authParams = authParams; this.user = response.user; this.storageManager.setItem("user", JSON.stringify(response.user)); @@ -110,25 +124,6 @@ class AuthManager extends SFAuthManager { } } - register(url, email, password, ephemeral, callback) { - super.register(url, email, password, ephemeral, (response) => { - if(!response.error) { - this.setEphemeral(ephemeral); - } - callback(response); - }) - } - - changePassword(email, current_server_pw, newKeys, newAuthParams, callback) { - super.changePassword(email, current_server_pw, newKeys, newAuthParams, (response) => { - if(!response.error) { - // Allows security update status to be changed if neccessary - this.checkForSecurityUpdate(); - } - callback(response); - }) - } - checkForSecurityUpdate() { if(this.offline()) { return false; From 7849c00f7ddfff58fb42231939ac14854ba09167 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 29 Jun 2018 10:57:56 -0500 Subject: [PATCH 07/52] Async sync --- .../javascripts/app/controllers/editor.js | 2 +- .../javascripts/app/controllers/footer.js | 6 +- .../javascripts/app/controllers/home.js | 26 +- .../app/directives/views/accountMenu.js | 16 +- .../app/directives/views/editorMenu.js | 4 +- .../app/directives/views/passwordWizard.js | 6 +- .../app/services/actionsManager.js | 6 +- .../javascripts/app/services/authManager.js | 6 +- .../app/services/componentManager.js | 18 +- .../app/services/desktopManager.js | 2 +- .../app/services/migrationManager.js | 2 +- .../app/services/nativeExtManager.js | 8 +- .../app/services/old_authManager.js | 326 ------------------ .../app/services/singletonManager.js | 2 +- 14 files changed, 52 insertions(+), 378 deletions(-) delete mode 100644 app/assets/javascripts/app/services/old_authManager.js diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 023889f23..e51ed23e2 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -207,7 +207,7 @@ angular.module('app') } // Lots of dirtying can happen above, so we'll sync - syncManager.sync("editorMenuOnSelect"); + syncManager.sync(); }.bind(this) this.hasAvailableExtensions = function() { diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 086c44f6d..da68e1b77 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -94,16 +94,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() { diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 488012ccc..29d28c4d1 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() { @@ -34,7 +34,7 @@ angular.module('app') window.location.reload(); } - function async load() { + function load() { // pass keys to storageManager to decrypt storage // Update: Wait, why? passcodeManager already handles this. // storageManager.setKeys(passcodeManager.keys()); @@ -66,7 +66,7 @@ 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(); }) } @@ -94,10 +94,10 @@ angular.module('app') $rootScope.$broadcast("initial-data-loaded"); - syncManager.sync("initiateSync"); + syncManager.sync(); // refresh every 30s setInterval(function () { - syncManager.sync("timer"); + syncManager.sync(); }, 30000); }); } @@ -148,7 +148,7 @@ angular.module('app') } note.setDirty(true); - syncManager.sync("updateTagsForNote"); + syncManager.sync(); } /* @@ -178,7 +178,7 @@ angular.module('app') return; } tag.setDirty(true); - syncManager.sync(callback, null, "tagsSave"); + syncManager.sync().then(callback); $rootScope.$broadcast("tag-changed"); modelManager.resortTag(tag); } @@ -191,10 +191,10 @@ angular.module('app') 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"); + }); } } @@ -218,7 +218,7 @@ angular.module('app') $scope.saveNote = function(note, callback) { note.setDirty(true); - syncManager.sync(function(response){ + syncManager.sync().then((response) => { if(response && response.error) { if(!$scope.didShowErrorAlert) { $scope.didShowErrorAlert = true; @@ -233,7 +233,7 @@ angular.module('app') callback(true); } } - }, null, "saveNote") + }) } $scope.safeApply = function(fn) { @@ -264,7 +264,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 () { @@ -274,7 +274,7 @@ angular.module('app') } else { $rootScope.notifyDelete(); } - }, null, "deleteNote"); + }); } diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 64cc0cf51..53f580059 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -89,7 +89,7 @@ class AccountMenu { else { $scope.onAuthSuccess(() => { syncManager.unlockSyncing(); - syncManager.sync("onLogin"); + syncManager.sync(); }); } }); @@ -114,7 +114,7 @@ class AccountMenu { alert(error.message); } else { $scope.onAuthSuccess(() => { - syncManager.sync("onRegister"); + syncManager.sync(); }); } }); @@ -163,9 +163,9 @@ class AccountMenu { // See: https://github.com/standardnotes/desktop/issues/131 $scope.clearDatabaseAndRewriteAllItems = function(alternateUuids, callback) { storageManager.clearAllModels(() => { - syncManager.markAllItemsDirtyAndSaveOffline(function(){ + syncManager.markAllItemsDirtyAndSaveOffline(alternateUuids).then(() => { callback && callback(); - }, alternateUuids) + }) }); } @@ -242,7 +242,7 @@ class AccountMenu { } $scope.importJSONData = function(data, password, callback) { - var onDataReady = function(errorCount) { + var onDataReady = (errorCount) => { var items = modelManager.mapResponseItemsToLocalModels(data.items, SFModelManager.MappingSourceFileImport); items.forEach(function(item){ item.setDirty(true, true); @@ -256,10 +256,10 @@ class AccountMenu { } }) - syncManager.sync((response) => { + syncManager.sync({additionalFields: ["created_at", "updated_at"]}).then((response) => { 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/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/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js index 27d6c65a0..38967ba39 100644 --- a/app/assets/javascripts/app/directives/views/passwordWizard.js +++ b/app/assets/javascripts/app/directives/views/passwordWizard.js @@ -188,7 +188,7 @@ class PasswordWizard { $scope.resyncData = function(callback) { modelManager.setAllItemsDirty(); - syncManager.sync((response) => { + syncManager.sync().then((response) => { if(response.error) { alert(FailedSyncMessage) $timeout(() => callback(false)); @@ -208,7 +208,7 @@ class PasswordWizard { 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) => { + syncManager.sync().then((response) => { authManager.changePassword(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."); @@ -217,7 +217,7 @@ class PasswordWizard { $timeout(() => callback(true)); } }) - }, null, "submitPasswordChange") + }) }); } } diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index ef626e97f..f672adfed 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -78,7 +78,7 @@ class ActionsManager { 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,7 +122,7 @@ 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, await this.authManager.keys(), true); }, (response) => { @@ -134,7 +134,7 @@ class ActionsManager { case "render": { - this.httpManager.getAbsolute(action.url, {}, (response) => { + this.httpManager.getAbsolute(action.url, {}, async (response) => { action.error = false; handleResponseDecryption(response, await this.authManager.keys(), false); }, (response) => { diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 44d0a786f..b4c647ce1 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -166,7 +166,7 @@ class AuthManager extends SFAuthManager { var prefs = new SFItem({content_type: prefsContentType}); this.modelManager.addItem(prefs); prefs.setDirty(true); - this.$rootScope.sync("authManager singletonCreate"); + this.$rootScope.sync(); valueCallback(prefs); }); } @@ -177,7 +177,7 @@ class AuthManager extends SFAuthManager { syncUserPreferences() { this.userPreferences.setDirty(true); - this.$rootScope.sync("syncUserPreferences"); + this.$rootScope.sync(); } getUserPrefValue(key, defaultValue) { @@ -193,6 +193,6 @@ class AuthManager extends SFAuthManager { 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 2916eef3f..4340a794a 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -479,13 +479,13 @@ class ComponentManager { 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.handleMessage(component, saveMessage); - }, null, "handleSaveItemsMessage"); + }); }); } @@ -513,7 +513,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 = @@ -548,7 +548,7 @@ class ComponentManager { this.modelManager.setItemToBeDeleted(model); } - this.syncManager.sync("handleDeleteItemsMessage"); + this.syncManager.sync(); } }); } @@ -564,7 +564,7 @@ class ComponentManager { this.runWithPermissions(component, [], () => { component.componentData = message.data.componentData; component.setDirty(true); - this.syncManager.sync("handleSetComponentDataMessage"); + this.syncManager.sync(); }); } @@ -657,7 +657,7 @@ class ComponentManager { } } component.setDirty(true); - this.syncManager.sync("promptForPermissions"); + this.syncManager.sync(); } this.permissionDialogs = this.permissionDialogs.filter((pendingDialog) => { @@ -760,7 +760,7 @@ class ComponentManager { if(didChange && !dontSync) { component.setDirty(true); - this.syncManager.sync("activateComponent"); + this.syncManager.sync(); } if(!this.activeComponents.includes(component)) { @@ -785,7 +785,7 @@ class ComponentManager { if(didChange && !dontSync) { component.setDirty(true); - this.syncManager.sync("deactivateComponent"); + this.syncManager.sync(); } _.pull(this.activeComponents, component); @@ -851,7 +851,7 @@ class ComponentManager { deleteComponent(component) { this.modelManager.setItemToBeDeleted(component); - this.syncManager.sync("deleteComponent"); + this.syncManager.sync(); } isComponentActive(component) { diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 31f77f5dc..88967c7ec 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -100,7 +100,7 @@ class DesktopManager { component.setAppDataItem("installError", null); } component.setDirty(true); - this.syncManager.sync("onComponentInstallationComplete"); + this.syncManager.sync(); this.timeout(() => { for(var observer of this.updateObservers) { diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js index 3e2a50ad4..d5a837bf4 100644 --- a/app/assets/javascripts/app/services/migrationManager.js +++ b/app/assets/javascripts/app/services/migrationManager.js @@ -52,7 +52,7 @@ class MigrationManager { this.modelManager.setItemToBeDeleted(editor); } - this.syncManager.sync("addEditorToComponentMigrator"); + this.syncManager.sync(); } }) } diff --git a/app/assets/javascripts/app/services/nativeExtManager.js b/app/assets/javascripts/app/services/nativeExtManager.js index 801ac21d3..f0f6936cb 100644 --- a/app/assets/javascripts/app/services/nativeExtManager.js +++ b/app/assets/javascripts/app/services/nativeExtManager.js @@ -40,7 +40,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 +81,7 @@ class NativeExtManager { this.modelManager.addItem(component); component.setDirty(true); - this.syncManager.sync("resolveExtensionsManager createNew"); + this.syncManager.sync(); this.systemExtensions.push(component.uuid); @@ -110,7 +110,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 +151,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/old_authManager.js b/app/assets/javascripts/app/services/old_authManager.js deleted file mode 100644 index 4fd9eaf7d..000000000 --- a/app/assets/javascripts/app/services/old_authManager.js +++ /dev/null @@ -1,326 +0,0 @@ -// angular.module('app') -// .provider('authManager', function () { -// -// function domainName() { -// var domain_comps = location.hostname.split("."); -// var domain = domain_comps[domain_comps.length - 2] + "." + domain_comps[domain_comps.length - 1]; -// return domain; -// } -// -// this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile) { -// return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile); -// } -// -// function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager, $compile) { -// -// 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}; -// } -// } -// -// this.checkForSecurityUpdate(); -// } -// -// this.offline = function() { -// return !this.user; -// } -// -// 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.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(!SFJS.supportedVersions().includes(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 SFItem({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() { -// 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(); -// } -// } -// -// } -// }); diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 0218846a0..04e06a606 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -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; From b4902f03c50d558d8b59eb02dda4add9efabfe9a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 29 Jun 2018 17:47:02 -0500 Subject: [PATCH 08/52] Functioning integration --- .../javascripts/app/controllers/footer.js | 5 ++++- .../javascripts/app/controllers/home.js | 8 +++---- .../javascripts/app/controllers/lockScreen.js | 2 +- .../app/directives/views/accountMenu.js | 21 +++++++++++++------ .../javascripts/app/services/authManager.js | 5 ++--- .../javascripts/app/services/modelManager.js | 2 +- .../app/services/storageManager.js | 8 +++---- 7 files changed, 31 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index da68e1b77..04687474d 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -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; }) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 29d28c4d1..d8aae28ac 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -88,7 +88,7 @@ angular.module('app') $rootScope.$broadcast(syncEvent, data || {}); }); - syncManager.loadLocalItems(function(items) { + syncManager.loadLocalItems().then(() => { $scope.allTag.didLoad = true; $scope.$apply(); @@ -285,20 +285,20 @@ 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(); - storageManager.clearAllData(() => { + storageManager.clearAllData().then(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js index 6dd3312eb..6c1c8f4c0 100644 --- a/app/assets/javascripts/app/controllers/lockScreen.js +++ b/app/assets/javascripts/app/controllers/lockScreen.js @@ -34,7 +34,7 @@ class LockScreen { } authManager.signOut(); - storageManager.clearAllData(() => { + storageManager.clearAllData().then(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 53f580059..d91b21dfd 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -13,10 +13,19 @@ class AccountMenu { $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(() => { @@ -145,7 +154,7 @@ class AccountMenu { } else { modelManager.resetLocalMemory(); - storageManager.clearAllModels(function(){ + storageManager.clearAllModels().them(() => { block(); }) } @@ -162,7 +171,7 @@ 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(() => { + storageManager.clearAllModels().then(() => { syncManager.markAllItemsDirtyAndSaveOffline(alternateUuids).then(() => { callback && callback(); }) @@ -175,7 +184,7 @@ class AccountMenu { } authManager.signOut(); - storageManager.clearAllData(() => { + storageManager.clearAllData().then(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index b4c647ce1..5e9636ecf 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -87,7 +87,6 @@ class AuthManager extends SFAuthManager { return super.login(url, email, password, strictSignin, extraParams).then((response) => { if(!response.error) { this.setEphemeral(ephemeral); - this.handleAuthResponse(response, email, url, authParams, keys); this.checkForSecurityUpdate(); } @@ -124,13 +123,13 @@ class AuthManager extends SFAuthManager { } } - checkForSecurityUpdate() { + async checkForSecurityUpdate() { if(this.offline()) { return false; } let latest = SFJS.version(); - let updateAvailable = this.protocolVersion() !== latest; + let updateAvailable = await this.protocolVersion() !== latest; if(updateAvailable !== this.securityUpdateAvailable) { this.securityUpdateAvailable = updateAvailable; this.$rootScope.$broadcast("security-update-status-changed"); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 52012717d..668c78977 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -102,7 +102,7 @@ class ModelManager extends SFModelManager { _.pull(this._extensions, item); } - this.storageManager.deleteModel(item, callback); + this.storageManager.deleteModel(item).then(callback); } /* diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index 4f1ba26fb..ade66dd1c 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -70,7 +70,7 @@ class StorageManager extends SFStorageManager { var length = this.storage.length; for(var i = 0; i < length; i++) { var key = this.storage.key(i); - newStorage.setItem(key, this.storage.getItem(key)); + newStorage.setItem(key, this.storage.getItemSync(key)); } this.itemsStorageMode = mode; @@ -134,7 +134,7 @@ class StorageManager extends SFStorageManager { var length = this.storage.length; for(var i = 0; i < length; i++) { var key = this.storage.key(i); - hash[key] = this.storage.getItem(key) + hash[key] = this.storage.getItemSync(key) } return hash; } @@ -157,7 +157,7 @@ class StorageManager extends SFStorageManager { } 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); @@ -167,7 +167,7 @@ class StorageManager extends SFStorageManager { } hasPasscode() { - return this.getItem("encryptedStorage", StorageManager.Fixed) !== null; + return this.getItemSync("encryptedStorage", StorageManager.Fixed) !== null; } From 71a3c68d39d414621a2fccff365f9909f6be7774 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 30 Jun 2018 10:13:55 -0500 Subject: [PATCH 09/52] sn-models package --- Gruntfile.js | 5 +- app/assets/javascripts/app/models/api/mfa.js | 28 - .../app/models/api/serverExtension.js | 38 - .../javascripts/app/models/app/component.js | 147 - .../javascripts/app/models/app/editor.js | 103 - .../javascripts/app/models/app/extension.js | 62 - app/assets/javascripts/app/models/app/note.js | 107 - app/assets/javascripts/app/models/app/tag.js | 45 - .../javascripts/app/models/app/theme.js | 11 - .../app/models/local/encryptedStorage.js | 28 - .../javascripts/app/services/modelManager.js | 1 + package-lock.json | 3630 +++++++++++++++++ package.json | 7 +- test/mocha/lib/factory.js | 82 + test/mocha/lib/localStorageManager.js | 69 + .../mocha.js => mocha/models.test.js} | 84 +- test/mocha/test.html | 23 + test/mocha/vendor/chai-as-promised-built.js | 539 +++ testing-server.js | 7 + 19 files changed, 4422 insertions(+), 594 deletions(-) delete mode 100644 app/assets/javascripts/app/models/api/mfa.js delete mode 100644 app/assets/javascripts/app/models/api/serverExtension.js delete mode 100644 app/assets/javascripts/app/models/app/component.js delete mode 100644 app/assets/javascripts/app/models/app/editor.js delete mode 100644 app/assets/javascripts/app/models/app/extension.js delete mode 100644 app/assets/javascripts/app/models/app/note.js delete mode 100644 app/assets/javascripts/app/models/app/tag.js delete mode 100644 app/assets/javascripts/app/models/app/theme.js delete mode 100644 app/assets/javascripts/app/models/local/encryptedStorage.js create mode 100644 test/mocha/lib/factory.js create mode 100644 test/mocha/lib/localStorageManager.js rename test/{javascripts/mocha.js => mocha/models.test.js} (57%) create mode 100644 test/mocha/test.html create mode 100644 test/mocha/vendor/chai-as-promised-built.js create mode 100644 testing-server.js diff --git a/Gruntfile.js b/Gruntfile.js index 826b1faf8..ae601a6ec 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -70,9 +70,9 @@ 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/services/**/*.js', 'app/assets/javascripts/app/filters/**/*.js', 'app/assets/javascripts/app/directives/**/*.js', @@ -161,9 +161,6 @@ 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']); }; 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 e3371f17e..000000000 --- a/app/assets/javascripts/app/models/api/mfa.js +++ /dev/null @@ -1,28 +0,0 @@ -class Mfa extends SFItem { - - 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 4d7d3dc0e..000000000 --- a/app/assets/javascripts/app/models/api/serverExtension.js +++ /dev/null @@ -1,38 +0,0 @@ -class ServerExtension extends SFItem { - - 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 a077eb733..000000000 --- a/app/assets/javascripts/app/models/app/component.js +++ /dev/null @@ -1,147 +0,0 @@ -class Component extends SFItem { - - 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, - }; - - var superParams = super.structureParams(); - Object.assign(superParams, params); - return superParams; - } - - 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 cc71e1657..000000000 --- a/app/assets/javascripts/app/models/app/editor.js +++ /dev/null @@ -1,103 +0,0 @@ -class Editor extends SFItem { - - 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 - }; - - var superParams = super.structureParams(); - Object.assign(superParams, params); - return superParams; - } - - 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)) { - _.remove(this.notes, {uuid: note.uuid}); - } - }.bind(this)) - } - - potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID) { - if(newItem.content_type === "Note" && _.find(this.notes, {uuid: oldUUID})) { - _.remove(this.notes, {uuid: oldUUID}); - this.notes.push(newItem); - } - } - - 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 7df86b99f..000000000 --- a/app/assets/javascripts/app/models/app/extension.js +++ /dev/null @@ -1,62 +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 - }; - - var superParams = super.structureParams(); - Object.assign(superParams, params); - return superParams; - } - -} 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 3669b3420..000000000 --- a/app/assets/javascripts/app/models/app/note.js +++ /dev/null @@ -1,107 +0,0 @@ -export class Note extends SFItem { - - 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; - } - - structureParams() { - var params = { - title: this.title, - text: this.text - }; - - var superParams = super.structureParams(); - Object.assign(superParams, params); - return superParams; - } - - addItemAsRelationship(item) { - this.savedTagsString = null; - - if(item.content_type == "Tag") { - if(!_.find(this.tags, {uuid: item.uuid})) { - this.tags.push(item); - item.notes.push(this); - } - } - super.addItemAsRelationship(item); - } - - removeItemAsRelationship(item) { - this.savedTagsString = null; - - if(item.content_type == "Tag") { - _.remove(this.tags, {uuid: item.uuid}); - _.remove(item.notes, {uuid: this.uuid}); - } - super.removeItemAsRelationship(item); - } - - updateLocalRelationships() { - this.savedTagsString = null; - - var references = this.content.references; - - var uuids = references.map(function(ref){return ref.uuid}); - this.tags.slice().forEach(function(tag){ - if(!uuids.includes(tag.uuid)) { - _.remove(tag.notes, {uuid: this.uuid}); - _.remove(this.tags, {uuid: tag.uuid}); - } - }.bind(this)) - } - - isBeingRemovedLocally() { - this.tags.forEach(function(tag){ - _.remove(tag.notes, {uuid: this.uuid}); - }.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) { - super.informReferencesOfUUIDChange(); - for(var tag of this.tags) { - _.remove(tag.notes, {uuid: oldUUID}); - tag.notes.push(this); - } - } - - safeText() { - return this.text || ""; - } - - safeTitle() { - return this.title || ""; - } - - toJSON() { - return {uuid: this.uuid} - } - - 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 f1c89f237..000000000 --- a/app/assets/javascripts/app/models/app/tag.js +++ /dev/null @@ -1,45 +0,0 @@ -export class Tag extends SFItem { - - constructor(json_obj) { - super(json_obj); - - if(!this.notes) { - this.notes = []; - } - } - - mapContentToLocalProperties(content) { - super.mapContentToLocalProperties(content) - this.title = content.title; - } - - structureParams() { - var params = { - title: this.title - }; - - var superParams = super.structureParams(); - Object.assign(superParams, params); - return superParams; - } - - isBeingRemovedLocally() { - this.notes.forEach(function(note){ - _.remove(note.tags, {uuid: this.uuid}); - }.bind(this)) - super.isBeingRemovedLocally(); - } - - informReferencesOfUUIDChange(oldUUID, newUUID) { - for(var note of this.notes) { - _.remove(note.tags, {uuid: oldUUID}); - note.tags.push(this); - } - } - - 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 0b8fe2c1f..000000000 --- a/app/assets/javascripts/app/models/app/theme.js +++ /dev/null @@ -1,11 +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 b70c678f6..000000000 --- a/app/assets/javascripts/app/models/local/encryptedStorage.js +++ /dev/null @@ -1,28 +0,0 @@ -class EncryptedStorage extends SFItem { - - 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/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 668c78977..a76f1a567 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -33,6 +33,7 @@ class ModelManager extends SFModelManager { var tag = _.find(this.tags, {title: title}) if(!tag) { tag = this.createItem({content_type: "Tag", content: {title: title}}); + tag.setDirty(true); this.addItem(tag); } return tag; diff --git a/package-lock.json b/package-lock.json index 7699cddf4..8b11a5209 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,3461 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "sn-models": { + "version": "file:../../sn-models", + "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, + "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 + }, + "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", @@ -9702,6 +13326,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 31b877db1..0c7e9d901 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": "file:~/Desktop/sf/sfjs" + "standard-file-js": "file:~/Desktop/sf/sfjs", + "sn-models": "file:~/Desktop/sn-models", + "connect": "^3.6.6", + "mocha": "^5.2.0", + "serve-static": "^1.13.2", + "chai": "^4.1.2" }, "license": "GPL-3.0" } diff --git a/test/mocha/lib/factory.js b/test/mocha/lib/factory.js new file mode 100644 index 000000000..cdaeced09 --- /dev/null +++ b/test/mocha/lib/factory.js @@ -0,0 +1,82 @@ +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(_globalStorageManager); } + 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..b06a4b321 --- /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(`item-${item.uuid}`); + } + } + } + + /* General */ + + clearAllData() { + return Promise.all([ + this.clear(), + this.clearAllModels() + ]) + } +} diff --git a/test/javascripts/mocha.js b/test/mocha/models.test.js similarity index 57% rename from test/javascripts/mocha.js rename to test/mocha/models.test.js index 82f562e7d..020292783 100644 --- a/test/javascripts/mocha.js +++ b/test/mocha/models.test.js @@ -1,3 +1,12 @@ +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; + describe("notes and tags", () => { const getNoteParams = () => { var params = { @@ -27,18 +36,13 @@ describe("notes and tags", () => { } ] - tagParams.content.references = [ - { - uuid: noteParams.uuid, - content_type: noteParams.content_type - } - ] + tagParams.content.references = [] return [noteParams, tagParams]; } it('uses proper class for note', () => { - let modelManager = createModelManager(); + let modelManager = Factory.createModelManager(); let noteParams = getNoteParams(); modelManager.mapResponseItemsToLocalModels([noteParams]); let note = modelManager.allItemsMatchingTypes(["Note"])[0]; @@ -46,14 +50,14 @@ describe("notes and tags", () => { }); it('creates two-way relationship between note and tag', () => { - let modelManager = createModelManager(); + let modelManager = Factory.createModelManager(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; let tagParams = pair[1]; - expect(tagParams.content.references.length).to.equal(1); - expect(tagParams.content.references.length).to.equal(1); + expect(noteParams.content.references.length).to.equal(1); + expect(tagParams.content.references.length).to.equal(0); modelManager.mapResponseItemsToLocalModels([noteParams, tagParams]); let note = modelManager.allItemsMatchingTypes(["Note"])[0]; @@ -67,10 +71,10 @@ describe("notes and tags", () => { expect(tag).to.not.be.null; expect(note.content.references.length).to.equal(1); - expect(tag.content.references.length).to.equal(1); + expect(tag.content.references.length).to.equal(0); expect(note.hasRelationshipWithItem(tag)).to.equal(true); - expect(tag.hasRelationshipWithItem(note)).to.equal(true); + expect(tag.hasRelationshipWithItem(note)).to.equal(false); expect(note.tags.length).to.equal(1); expect(tag.notes.length).to.equal(1); @@ -81,11 +85,13 @@ describe("notes and tags", () => { // expect to be true expect(note.dirty).to.be.ok; - expect(tag.dirty).to.be.ok; + + // expect to be false + expect(tag.dirty).to.not.be.ok; }); it('handles remote deletion of relationship', () => { - let modelManager = createModelManager(); + let modelManager = Factory.createModelManager(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; @@ -96,7 +102,7 @@ describe("notes and tags", () => { let tag = modelManager.allItemsMatchingTypes(["Tag"])[0]; expect(note.content.references.length).to.equal(1); - expect(tag.content.references.length).to.equal(1); + expect(tag.content.references.length).to.equal(0); noteParams.content.references = []; modelManager.mapResponseItemsToLocalModels([noteParams]); @@ -111,7 +117,7 @@ describe("notes and tags", () => { }); it('properly handles duplication', () => { - let modelManager = createModelManager(); + let modelManager = Factory.createModelManager(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; @@ -121,17 +127,55 @@ describe("notes and tags", () => { let note = modelManager.allItemsMatchingTypes(["Note"])[0]; let tag = modelManager.allItemsMatchingTypes(["Tag"])[0]; - var duplicateNote = modelManager.createDuplicateItem(note); - expect(note.uuid).to.equal(duplicateNote.uuid); + // Usually content_type will be provided by a server response + var duplicateParams = _.merge({content_type: "Note"}, note); + duplicateParams.uuid = null; + + expect(duplicateParams.content_type).to.equal("Note"); + var duplicateNote = modelManager.createDuplicateItem(duplicateParams); + modelManager.addItem(duplicateNote); + + expect(note.uuid).to.not.equal(duplicateNote.uuid); + + expect(note.content.references.length).to.equal(1); + expect(note.tags.length).to.equal(1); expect(duplicateNote.content.references.length).to.equal(1); expect(duplicateNote.tags.length).to.equal(1); - expect(tag.content.references.length).to.equal(1); - expect(tag.notes.length).to.equal(1); + expect(tag.content.references.length).to.equal(0); + expect(tag.notes.length).to.equal(2); + + var tagNote1 = tag.notes[0]; + var tagNote2 = tag.notes[1]; + expect(tagNote1.uuid).to.not.equal(tagNote2.uuid); // expect to be false expect(note.dirty).to.not.be.ok; expect(tag.dirty).to.not.be.ok; }); + + it('deleting a tag should update note 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(note.content.references.length).to.equal(1); + expect(note.tags.length).to.equal(1); + + expect(tag.content.references.length).to.equal(0); + expect(tag.notes.length).to.equal(1); + + modelManager.setItemToBeDeleted(tag); + modelManager.mapResponseItemsToLocalModels([tag]); + // expect(tag.notes.length).to.equal(0); + expect(note.content.references.length).to.equal(0); + expect(note.tags.length).to.equal(0); + }); }); 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/testing-server.js b/testing-server.js new file mode 100644 index 000000000..25d8a1a0a --- /dev/null +++ b/testing-server.js @@ -0,0 +1,7 @@ +// Used for running mocha tests + +var connect = require('connect'); +var serveStatic = require('serve-static'); +connect().use(serveStatic(__dirname)).listen(8081, function(){ + console.log('Server running on 8081...'); +}); From 7625e037fed9a45538621f40258d30ac49ea532b Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 1 Jul 2018 10:24:16 -0500 Subject: [PATCH 10/52] Note tag relationships --- .../javascripts/app/controllers/home.js | 11 +- app/assets/stylesheets/app/_notes.scss | 11 +- test/mocha/models.test.js | 304 +++++++++++++----- 3 files changed, 245 insertions(+), 81 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index d8aae28ac..e28adf283 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -132,7 +132,8 @@ angular.module('app') } for(var tagToRemove of toRemove) { - note.removeItemAsRelationship(tagToRemove); + tagToRemove.removeItemAsRelationship(note); + tagToRemove.setDirty(true); } var tags = []; @@ -144,10 +145,10 @@ angular.module('app') } for(var tag of tags) { - note.addItemAsRelationship(tag); + tag.addItemAsRelationship(note); + tag.setDirty(true); } - note.setDirty(true); syncManager.sync(); } @@ -206,8 +207,8 @@ angular.module('app') modelManager.addItem(note); if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag) { - note.addItemAsRelationship($scope.selectedTag); - note.setDirty(true); + $scope.selectedTag.addItemAsRelationship(note); + $scope.selectedTag.setDirty(true); } } 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/test/mocha/models.test.js b/test/mocha/models.test.js index 020292783..fc17b5a1d 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -7,39 +7,39 @@ 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", () => { - 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", - } - }; - noteParams.content.references = [ - { - uuid: tagParams.uuid, - content_type: tagParams.content_type - } - ] - - tagParams.content.references = [] - - return [noteParams, tagParams]; - } it('uses proper class for note', () => { let modelManager = Factory.createModelManager(); @@ -56,8 +56,8 @@ describe("notes and tags", () => { let noteParams = pair[0]; let tagParams = pair[1]; - expect(noteParams.content.references.length).to.equal(1); - expect(tagParams.content.references.length).to.equal(0); + 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]; @@ -67,14 +67,11 @@ describe("notes and tags", () => { expect(note.dirty).to.not.be.ok; expect(tag.dirty).to.not.be.ok; - expect(note).to.not.be.null; - expect(tag).to.not.be.null; + expect(note.content.references.length).to.equal(0); + expect(tag.content.references.length).to.equal(1); - expect(note.content.references.length).to.equal(1); - expect(tag.content.references.length).to.equal(0); - - expect(note.hasRelationshipWithItem(tag)).to.equal(true); - expect(tag.hasRelationshipWithItem(note)).to.equal(false); + 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); @@ -85,9 +82,7 @@ describe("notes and tags", () => { // expect to be true expect(note.dirty).to.be.ok; - - // expect to be false - expect(tag.dirty).to.not.be.ok; + expect(tag.dirty).to.be.ok; }); it('handles remote deletion of relationship', () => { @@ -101,13 +96,13 @@ describe("notes and tags", () => { let note = modelManager.allItemsMatchingTypes(["Note"])[0]; let tag = modelManager.allItemsMatchingTypes(["Tag"])[0]; - expect(note.content.references.length).to.equal(1); - expect(tag.content.references.length).to.equal(0); - - noteParams.content.references = []; - modelManager.mapResponseItemsToLocalModels([noteParams]); - 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); @@ -116,6 +111,73 @@ describe("notes and tags", () => { 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('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 duplication', () => { let modelManager = Factory.createModelManager(); @@ -128,34 +190,33 @@ describe("notes and tags", () => { let tag = modelManager.allItemsMatchingTypes(["Tag"])[0]; // Usually content_type will be provided by a server response - var duplicateParams = _.merge({content_type: "Note"}, note); + var duplicateParams = _.merge({content_type: "Tag"}, tag); duplicateParams.uuid = null; - expect(duplicateParams.content_type).to.equal("Note"); - var duplicateNote = modelManager.createDuplicateItem(duplicateParams); - modelManager.addItem(duplicateNote); + expect(duplicateParams.content_type).to.equal("Tag"); + var duplicateTag = modelManager.createDuplicateItem(duplicateParams); + modelManager.addItem(duplicateTag); - expect(note.uuid).to.not.equal(duplicateNote.uuid); + expect(tag.uuid).to.not.equal(duplicateTag.uuid); - expect(note.content.references.length).to.equal(1); - expect(note.tags.length).to.equal(1); + expect(tag.content.references.length).to.equal(1); + expect(tag.notes.length).to.equal(1); - expect(duplicateNote.content.references.length).to.equal(1); - expect(duplicateNote.tags.length).to.equal(1); + expect(duplicateTag.content.references.length).to.equal(1); + expect(duplicateTag.notes.length).to.equal(1); - expect(tag.content.references.length).to.equal(0); - expect(tag.notes.length).to.equal(2); + expect(note.tags.length).to.equal(2); - var tagNote1 = tag.notes[0]; - var tagNote2 = tag.notes[1]; - expect(tagNote1.uuid).to.not.equal(tagNote2.uuid); + 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('deleting a tag should update note references', () => { + it('deleting a note should update tag references', () => { let modelManager = Factory.createModelManager(); let pair = createRelatedNoteTagPair(); @@ -166,16 +227,115 @@ describe("notes and tags", () => { let note = modelManager.allItemsMatchingTypes(["Note"])[0]; let tag = modelManager.allItemsMatchingTypes(["Tag"])[0]; - expect(note.content.references.length).to.equal(1); - expect(note.tags.length).to.equal(1); - - expect(tag.content.references.length).to.equal(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.notes.length).to.equal(0); - expect(note.content.references.length).to.equal(0); - expect(note.tags.length).to.equal(0); + expect(tag.content.references.length).to.equal(0); + expect(tag.notes.length).to.equal(0); }); }); + +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.setEventHandler(() => { + + }) + + syncManager.setKeyRequestHandler(async () => { + return { + keys: await authManager.keys(), + offline: false + }; + }) + + it('syncing a note many times does not cause duplication', async () => { + modelManager.resetLocalMemory(); + 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 < 25; i++) { + note.setDirty(true); + tag.setDirty(true); + await syncManager.sync(); + 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); + + } + }).timeout(10000); + + it('duplicating a tag should maintian its relationships', async () => { + modelManager.resetLocalMemory(); + 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(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); +}) From 82b1586fd77aab1572c2533cb5f08d0cbd7906c8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 2 Jul 2018 11:11:56 -0500 Subject: [PATCH 11/52] Alternating uuid logic and tests --- .../app/directives/views/accountMenu.js | 2 +- .../javascripts/app/services/modelManager.js | 26 ++++---- test/mocha/lib/localStorageManager.js | 2 +- test/mocha/models.test.js | 59 +++++++++++++++++++ 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index d91b21dfd..aedf8f3e4 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -154,7 +154,7 @@ class AccountMenu { } else { modelManager.resetLocalMemory(); - storageManager.clearAllModels().them(() => { + storageManager.clearAllModels().then(() => { block(); }) } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index a76f1a567..b66884eab 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -83,29 +83,27 @@ class ModelManager extends SFModelManager { // 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); - } + this.removeItemFromRespectiveArray(item); } removeItemLocally(item, callback) { super.removeItemLocally(item, callback); - 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); - } + this.removeItemFromRespectiveArray(item); this.storageManager.deleteModel(item).then(callback); } + removeItemFromRespectiveArray(item) { + if(item.content_type == "Tag") { + _.remove(this.tags, {uuid: item.uuid}); + } else if(item.content_type == "Note") { + _.remove(this.notes, {uuid: item.uuid}); + } else if(item.content_type == "Extension") { + _.remove(this._extensions, {uuid: item.uuid}); + } + } + /* Misc */ diff --git a/test/mocha/lib/localStorageManager.js b/test/mocha/lib/localStorageManager.js index b06a4b321..2089bb753 100644 --- a/test/mocha/lib/localStorageManager.js +++ b/test/mocha/lib/localStorageManager.js @@ -53,7 +53,7 @@ export default class LocalStorageManager extends SFStorageManager { // clear only models for(var key in localStorage) { if(key.startsWith("item-")) { - this.removeItem(`item-${item.uuid}`); + this.removeItem(key); } } } diff --git a/test/mocha/models.test.js b/test/mocha/models.test.js index fc17b5a1d..deb234de3 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -290,6 +290,65 @@ describe("syncing", () => { } }).timeout(10000); + it("handles signing in and merging data", async () => { + + let syncManager = new SFSyncManager(modelManager, Factory.globalStorageManager(), Factory.globalHttpManager()); + + syncManager.setEventHandler(() => {}) + + // be offline + syncManager.setKeyRequestHandler(async () => { + return { + offline: true + }; + }) + + modelManager.resetLocalMemory(); + 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(), + 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.resetLocalMemory(); let pair = createRelatedNoteTagPair(); From c3d94c334aea9cce62f87f5dd9e49e75200446c2 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 2 Jul 2018 17:28:31 -0500 Subject: [PATCH 12/52] Fixes --- .../javascripts/app/controllers/footer.js | 2 +- .../javascripts/app/controllers/home.js | 24 +++--- .../javascripts/app/controllers/lockScreen.js | 2 +- .../app/directives/views/accountMenu.js | 86 +++++++++++-------- .../javascripts/app/services/authManager.js | 18 ++-- .../app/services/storageManager.js | 12 ++- .../directives/account-menu.html.haml | 4 +- 7 files changed, 82 insertions(+), 66 deletions(-) diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 04687474d..0232ad3da 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -26,7 +26,7 @@ angular.module('app') syncManager, storageManager, passcodeManager, componentManager, singletonManager, nativeExtManager) { authManager.checkForSecurityUpdate().then((available) => { - this.securityUpdateAvailable = available; + this.securityUpdateAvailable = available; }) $rootScope.$on("security-update-status-changed", () => { diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index e28adf283..71ccf255b 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -89,10 +89,10 @@ angular.module('app') }); syncManager.loadLocalItems().then(() => { - $scope.allTag.didLoad = true; - $scope.$apply(); - - $rootScope.$broadcast("initial-data-loaded"); + $timeout(() => { + $scope.allTag.didLoad = true; + $rootScope.$broadcast("initial-data-loaded"); + }) syncManager.sync(); // refresh every 30s @@ -225,14 +225,14 @@ angular.module('app') $scope.didShowErrorAlert = true; alert("There was an error saving your note. Please try again."); } - if(callback) { - callback(false); - } + $timeout(() => { + callback && callback(false); + }) } else { note.hasChanges = false; - if(callback) { - callback(true); - } + $timeout(() => { + callback && callback(true); + }); } }) } @@ -273,7 +273,9 @@ angular.module('app') $scope.safeApply(); }, 50); } else { - $rootScope.notifyDelete(); + $timeout(() => { + $rootScope.notifyDelete(); + }); } }); } diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js index 6c1c8f4c0..ffd4e88ae 100644 --- a/app/assets/javascripts/app/controllers/lockScreen.js +++ b/app/assets/javascripts/app/controllers/lockScreen.js @@ -8,7 +8,7 @@ class LockScreen { }; } - controller($scope, passcodeManager, authManager, syncManager) { + controller($scope, passcodeManager, authManager, syncManager, storageManager) { 'ngInject'; $scope.formData = {}; diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index aedf8f3e4..1844f8579 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -14,6 +14,8 @@ class AccountMenu { 'ngInject'; $scope.formData = {mergeLocal: true, ephemeral: false}; + $scope.formData.email = "july2@bitar.io"; + $scope.formData.user_password = "password"; $scope.user = authManager.user; syncManager.getServerURL().then((url) => { @@ -69,38 +71,34 @@ class AccountMenu { $timeout(function(){ authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, $scope.formData.strictSignin, extraParams).then((response) => { - if(!response || response.error) { + $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(); - }); - } + // Success + else { + $scope.onAuthSuccess(() => { + syncManager.unlockSyncing(); + syncManager.sync(); + }); + } + }) }); }) } @@ -117,15 +115,17 @@ class AccountMenu { $timeout(function(){ authManager.register($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral).then((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(); - }); - } + $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(); + }); + } + }) }); }) } @@ -252,11 +252,29 @@ class AccountMenu { $scope.importJSONData = function(data, password, callback) { var onDataReady = (errorCount) => { - var items = modelManager.mapResponseItemsToLocalModels(data.items, SFModelManager.MappingSourceFileImport); + var itemsToBeMapped = []; + for(var itemData of data.items) { + var existing = modelManager.findItem(itemData.uuid); + if(existing) { + // if the item already exists, check to see if it's different from the import data. + // If it's the same, do nothing, otherwise, create a copy. + itemData.uuid = null; + var dup = modelManager.createDuplicateItem(itemData); + if(!itemData.deleted && !existing.isItemContentEqualWith(dup)) { + // Data differs + modelManager.addDuplicatedItem(dup, existing); + itemsToBeMapped.push(dup); + } + } else { + // it doesn't exist, push it into items to be mapped + itemsToBeMapped.push(itemData); + } + } + + var items = modelManager.mapResponseItemsToLocalModels(itemsToBeMapped, SFModelManager.MappingSourceFileImport); items.forEach(function(item){ item.setDirty(true, true); item.deleted = false; - modelManager.markAllReferencesDirtyForItem(item, true); // We don't want to activate any components during import process in case of exceptions // breaking up the import proccess diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 5e9636ecf..f3d36dc0c 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -71,14 +71,6 @@ class AuthManager extends SFAuthManager { } } - async resolveResponseInTimeout(response) { - return new Promise((resolve, reject) => { - this.$timeout(() => { - resolve(response); - }); - }); - } - async getAuthParamsForEmail(url, email, extraParams) { return super.getAuthParamsForEmail(url, email, extraParams); } @@ -90,25 +82,25 @@ class AuthManager extends SFAuthManager { this.checkForSecurityUpdate(); } - return this.resolveResponseInTimeout(response); + return response; }) } async register(url, email, password, ephemeral) { - super.register(url, email, password).then((response) => { + return super.register(url, email, password).then((response) => { if(!response.error) { this.setEphemeral(ephemeral); } - return this.resolveResponseInTimeout(response); + return response; }) } async changePassword(email, current_server_pw, newKeys, newAuthParams) { - super.changePassword(email, current_server_pw, newKeys, newAuthParams).then((response) => { + return super.changePassword(email, current_server_pw, newKeys, newAuthParams).then((response) => { if(!response.error) { this.checkForSecurityUpdate(); } - return this.resolveResponseInTimeout(response); + return response; }) } diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index ade66dd1c..f1fc947b6 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; } @@ -70,7 +74,7 @@ class StorageManager extends SFStorageManager { var length = this.storage.length; for(var i = 0; i < length; i++) { var key = this.storage.key(i); - newStorage.setItem(key, this.storage.getItemSync(key)); + newStorage.setItem(key, this.storage.getItem(key)); } this.itemsStorageMode = mode; @@ -134,7 +138,7 @@ class StorageManager extends SFStorageManager { var length = this.storage.length; for(var i = 0; i < length; i++) { var key = this.storage.key(i); - hash[key] = this.storage.getItemSync(key) + hash[key] = this.storage.getItem(key) } return hash; } @@ -147,7 +151,7 @@ class StorageManager extends SFStorageManager { writeEncryptedStorageToDisk() { var encryptedStorage = new EncryptedStorage(); // 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 SFItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version); @@ -161,7 +165,7 @@ class StorageManager extends SFStorageManager { await SFJS.itemTransformer.decryptItem(stored, this.encryptedStorageKeys); var encryptedStorage = new EncryptedStorage(stored); - for(var key of Object.keys(encryptedStorage.storage)) { + for(var key of Object.keys(encryptedStorage.content.storage)) { this.setItem(key, encryptedStorage.storage[key]); } } 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 From cfcac7008b1a7b3c1ed7ef63ed9a470a354c10ef Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 2 Jul 2018 20:24:05 -0500 Subject: [PATCH 13/52] Updates --- .../app/directives/views/accountMenu.js | 32 +----- test/mocha/models.test.js | 97 ++++++++++++++++++- 2 files changed, 99 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 1844f8579..4378b1200 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -252,36 +252,12 @@ class AccountMenu { $scope.importJSONData = function(data, password, callback) { var onDataReady = (errorCount) => { - var itemsToBeMapped = []; - for(var itemData of data.items) { - var existing = modelManager.findItem(itemData.uuid); - if(existing) { - // if the item already exists, check to see if it's different from the import data. - // If it's the same, do nothing, otherwise, create a copy. - itemData.uuid = null; - var dup = modelManager.createDuplicateItem(itemData); - if(!itemData.deleted && !existing.isItemContentEqualWith(dup)) { - // Data differs - modelManager.addDuplicatedItem(dup, existing); - itemsToBeMapped.push(dup); - } - } else { - // it doesn't exist, push it into items to be mapped - itemsToBeMapped.push(itemData); - } - } - - var items = modelManager.mapResponseItemsToLocalModels(itemsToBeMapped, SFModelManager.MappingSourceFileImport); - items.forEach(function(item){ - item.setDirty(true, true); - item.deleted = false; - + 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({additionalFields: ["created_at", "updated_at"]}).then((response) => { callback(response, errorCount); diff --git a/test/mocha/models.test.js b/test/mocha/models.test.js index deb234de3..df3dd1cce 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -178,7 +178,7 @@ describe("notes and tags", () => { expect(tag.notes.length).to.equal(0); }); - it('properly handles duplication', () => { + it('properly handles tag duplication', () => { let modelManager = Factory.createModelManager(); let pair = createRelatedNoteTagPair(); @@ -195,7 +195,7 @@ describe("notes and tags", () => { expect(duplicateParams.content_type).to.equal("Tag"); var duplicateTag = modelManager.createDuplicateItem(duplicateParams); - modelManager.addItem(duplicateTag); + modelManager.addDuplicatedItem(duplicateTag, tag); expect(tag.uuid).to.not.equal(duplicateTag.uuid); @@ -216,6 +216,29 @@ describe("notes and tags", () => { 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(); @@ -238,6 +261,73 @@ describe("notes and tags", () => { 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", () => { @@ -386,6 +476,9 @@ describe("syncing", () => { 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); From 8c9fd9308ddd9d6cc1ffaa00df3b1ec4f5c4b8ee Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 3 Jul 2018 12:41:12 -0500 Subject: [PATCH 14/52] Updates --- Gruntfile.js | 3 +++ .../javascripts/app/controllers/editor.js | 20 +++++++++----- .../javascripts/app/controllers/home.js | 1 - .../javascripts/app/services/dbManager.js | 20 ++++++++++---- test/mocha/models.test.js | 26 +++++++++++++++++++ 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ae601a6ec..39a79854f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -163,4 +163,7 @@ module.exports = function(grunt) { 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 e51ed23e2..924053b26 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -36,10 +36,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) { @@ -80,6 +76,18 @@ angular.module('app') this.loadTagsString(); }); + modelManager.addItemSyncObserver("component-manager", "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; + } + } + }); + this.noteDidChange = function(note, oldNote) { this.setNote(note, oldNote); this.reloadComponentContext(); @@ -554,7 +562,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], SFModelManager.MappingSourceLocalSaved); + modelManager.notifySyncObserversOfModels([tag], SFModelManager.MappingSourceLocalSaved); } } @@ -564,7 +572,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], SFModelManager.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/home.js b/app/assets/javascripts/app/controllers/home.js index 71ccf255b..e8bd8836c 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -180,7 +180,6 @@ angular.module('app') } tag.setDirty(true); syncManager.sync().then(callback); - $rootScope.$broadcast("tag-changed"); modelManager.resortTag(tag); } 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/test/mocha/models.test.js b/test/mocha/models.test.js index df3dd1cce..d2ab15d06 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -157,6 +157,32 @@ describe("notes and tags", () => { expect(tag.notes.length).to.equal(0); }); + it.only('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(); From 662b3d6a07feadad4ca66ceee323f6159a3feed0 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 3 Jul 2018 18:21:55 -0500 Subject: [PATCH 15/52] Item predicate matching --- .../app/services/singletonManager.js | 24 +------------------ test/mocha/models.test.js | 19 +++++++++++---- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 04e06a606..0add89c00 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -125,32 +125,10 @@ class SingletonManager { filterItemsWithPredicate(items, predicate) { return items.filter((candidate) => { - return this.itemSatisfiesPredicate(candidate, predicate); + return candidate.satisfiesPredicate(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/test/mocha/models.test.js b/test/mocha/models.test.js index d2ab15d06..a4abec17e 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -157,7 +157,7 @@ describe("notes and tags", () => { expect(tag.notes.length).to.equal(0); }); - it.only('resets cached note tags string when tag is renamed', () => { + it('resets cached note tags string when tag is renamed', () => { let modelManager = Factory.createModelManager(); let pair = createRelatedNoteTagPair(); @@ -384,7 +384,15 @@ describe("syncing", () => { }; }) - it('syncing a note many times does not cause duplication', async () => { + const wait = (secs) => { + return new Promise((resolve, reject) => { + setTimeout(function () { + resolve(); + }, secs * 1000); + }) + } + + it.only('syncing a note many times does not cause duplication', async () => { modelManager.resetLocalMemory(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; @@ -394,17 +402,20 @@ describe("syncing", () => { let note = modelManager.allItemsMatchingTypes(["Note"])[0]; let tag = modelManager.allItemsMatchingTypes(["Tag"])[0]; - for(var i = 0; i < 25; i++) { + 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(10000); + }).timeout(20000); it("handles signing in and merging data", async () => { From b270618b53ab4b04d7fe9356275e9ce55d371f31 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 3 Jul 2018 18:34:38 -0500 Subject: [PATCH 16/52] Component manager notify observers on delete --- app/assets/javascripts/app/services/componentManager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 4340a794a..2f4819640 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -546,6 +546,9 @@ 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(); From 428458e8214f0e80de4924cc351ef4c948542337 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 3 Jul 2018 19:29:44 -0500 Subject: [PATCH 17/52] Predicate matching --- .../javascripts/app/services/authManager.js | 3 ++- .../app/services/nativeExtManager.js | 10 ++++++++-- .../app/services/singletonManager.js | 19 ++++++------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index f3d36dc0c..8d240eb41 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -149,7 +149,8 @@ class AuthManager extends SFAuthManager { configureUserPrefs() { let prefsContentType = "SN|UserPreferences"; - this.singletonManager.registerSingleton({content_type: prefsContentType}, (resolvedSingleton) => { + let contentTypePredicate = new SFPredicate("content_type", "=", prefsContentType); + this.singletonManager.registerSingleton([contentTypePredicate], (resolvedSingleton) => { this.userPreferences = resolvedSingleton; this.userPreferencesDidChange(); }, (valueCallback) => { diff --git a/app/assets/javascripts/app/services/nativeExtManager.js b/app/assets/javascripts/app/services/nativeExtManager.js index f0f6936cb..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); @@ -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); diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 0add89c00..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 @@ -122,13 +122,6 @@ class SingletonManager { } } } - - filterItemsWithPredicate(items, predicate) { - return items.filter((candidate) => { - return candidate.satisfiesPredicate(predicate); - }) - } - } angular.module('app').service('singletonManager', SingletonManager); From b33cdea814fa1a4f0783204ed750583c2863d042 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 4 Jul 2018 09:46:21 -0500 Subject: [PATCH 18/52] Tag predicate matching --- .../javascripts/app/controllers/home.js | 7 +----- .../javascripts/app/controllers/tags.js | 22 +++++++++++++------ .../app/services/componentManager.js | 8 +++---- .../javascripts/app/services/modelManager.js | 5 +++++ app/assets/templates/home.html.haml | 2 +- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index e8bd8836c..786bc772f 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -156,11 +156,6 @@ angular.module('app') Tags Ctrl Callbacks */ - - $scope.tagsWillMakeSelection = function(tag) { - - } - $scope.tagsSelectionMade = function(tag) { if($scope.selectedNote && $scope.selectedNote.dummy) { modelManager.removeItemLocally($scope.selectedNote); @@ -205,7 +200,7 @@ angular.module('app') $scope.notesAddNew = function(note) { modelManager.addItem(note); - if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag) { + if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag && !$scope.selectedTag.isSmartTag()) { $scope.selectedTag.addItemAsRelationship(note); $scope.selectedTag.setDirty(true); } diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js index 21a430b45..168cbc365 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,23 @@ 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 params = data.item.content.predicate; + var predicate = new SFPredicate(params.keypath, params.operator, params.value); + var tag = new Tag(data.item); + Object.defineProperty(tag, "notes", { + get: () => { + return modelManager.notesMatchingPredicate(predicate); + } + }); this.selectTag(tag); } - } - - else if(action === "clear-selection") { + } else if(action === "clear-selection") { this.selectTag(this.allTag); } }.bind(this)}); @@ -93,7 +102,6 @@ angular.module('app') } this.selectTag = function(tag) { - this.willSelect()(tag); this.selectedTag = tag; tag.conflict_of = null; // clear conflict this.selectionMade()(tag); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 2f4819640..48531eb32 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -757,7 +757,7 @@ class ComponentManager { component.active = true; for(var handler of this.handlers) { if(handler.areas.includes(component.area) || handler.areas.includes("*")) { - handler.activationHandler(component); + handler.activationHandler && handler.activationHandler(component); } } @@ -782,7 +782,7 @@ class ComponentManager { for(var handler of this.handlers) { if(handler.areas.includes(component.area) || handler.areas.includes("*")) { - handler.activationHandler(component); + handler.activationHandler && handler.activationHandler(component); } } @@ -814,7 +814,7 @@ class ComponentManager { for(var handler of this.handlers) { if(handler.areas.includes(component.area) || handler.areas.includes("*")) { - handler.activationHandler(component); + handler.activationHandler && handler.activationHandler(component); } } @@ -838,7 +838,7 @@ class ComponentManager { component.active = true; for(var handler of this.handlers) { if(handler.areas.includes(component.area) || handler.areas.includes("*")) { - handler.activationHandler(component); + handler.activationHandler && handler.activationHandler(component); } } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index b66884eab..4668198d4 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -104,6 +104,11 @@ class ModelManager extends SFModelManager { } } + notesMatchingPredicate(predicate) { + let contentTypePredicate = new SFPredicate("content_type", "=", "Note"); + return this.itemsMatchingPredicates([contentTypePredicate, predicate]); + } + /* Misc */ diff --git a/app/assets/templates/home.html.haml b/app/assets/templates/home.html.haml index 41c1945cd..54b834bd0 100644 --- a/app/assets/templates/home.html.haml +++ b/app/assets/templates/home.html.haml @@ -1,7 +1,7 @@ .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"} From e7948a8a01d94f09a0a4f3f2e29e0b45126c91b8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 4 Jul 2018 12:59:31 -0500 Subject: [PATCH 19/52] Filtering --- app/assets/javascripts/app/controllers/notes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 4759856cb..245fe87d9 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -227,7 +227,9 @@ angular.module('app') this.noteFilter = {text : ''}; this.filterNotes = function(note) { - if((note.archived && !this.showArchived && !this.tag.archiveTag) || (note.pinned && this.hidePinned)) { + var isSmartTag = this.tag.isSmartTag(); + + if((!isSmartTag && note.archived && !this.showArchived && !this.tag.archiveTag) || (note.pinned && this.hidePinned)) { note.visible = false; return note.visible; } From 5a1d13f28ec6ef41a02efa1db9926b1e366ce136 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 5 Jul 2018 10:43:27 -0500 Subject: [PATCH 20/52] Transition archived tag to smart tag --- .../javascripts/app/controllers/editor.js | 6 ++++ .../javascripts/app/controllers/home.js | 19 +++++++++--- .../javascripts/app/controllers/notes.js | 31 ++++++++++++------- .../javascripts/app/controllers/tags.js | 6 ++-- .../javascripts/app/services/modelManager.js | 1 + app/assets/templates/notes.html.haml | 4 +-- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 924053b26..382440504 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -246,6 +246,12 @@ angular.module('app') this.saveNote = function($event) { 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)) { + alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note."); + return; + } this.save()(note, function(success){ if(success) { if(statusTimeout) $timeout.cancel(statusTimeout); diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 786bc772f..6f626bd0d 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -112,10 +112,13 @@ angular.module('app') } function loadArchivedTag() { - var archiveTag = new Tag({content: {title: "Archived"}}); - archiveTag.archiveTag = true; + var archiveTag = new SmartTag({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; } /* @@ -157,8 +160,15 @@ angular.module('app') */ $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; @@ -200,7 +210,7 @@ angular.module('app') $scope.notesAddNew = function(note) { modelManager.addItem(note); - if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag && !$scope.selectedTag.isSmartTag()) { + if(!$scope.selectedTag.all && !$scope.selectedTag.isSmartTag()) { $scope.selectedTag.addItemAsRelationship(note); $scope.selectedTag.setDirty(true); } @@ -246,7 +256,6 @@ angular.module('app') } $scope.deleteNote = function(note) { - modelManager.setItemToBeDeleted(note); if(note == $scope.selectedNote) { diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 245fe87d9..e90be9064 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; @@ -227,9 +229,16 @@ angular.module('app') this.noteFilter = {text : ''}; this.filterNotes = function(note) { + var canShowArchived = false, canShowPinned = true; var isSmartTag = this.tag.isSmartTag(); + if(isSmartTag) { + canShowArchived = this.tag.isReferencingArchivedNotes(); + } else { + canShowArchived = this.showArchived; + canShowPinned = !this.hidePinned; + } - if((!isSmartTag && note.archived && !this.showArchived && !this.tag.archiveTag) || (note.pinned && this.hidePinned)) { + if((note.archived && !canShowArchived) || (note.pinned && !canShowPinned)) { note.visible = false; return note.visible; } @@ -244,9 +253,9 @@ angular.module('app') note.visible = matchesTitle || matchesBody; } - if(this.tag.archiveTag) { - note.visible = note.visible && note.archived; - } + // 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 168cbc365..93f2275cb 100644 --- a/app/assets/javascripts/app/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -71,12 +71,10 @@ angular.module('app') this.selectTag(tag); } } else if(data.item.content_type == "SN|SmartTag") { - var params = data.item.content.predicate; - var predicate = new SFPredicate(params.keypath, params.operator, params.value); - var tag = new Tag(data.item); + var tag = new SmartTag(data.item); Object.defineProperty(tag, "notes", { get: () => { - return modelManager.notesMatchingPredicate(predicate); + return modelManager.notesMatchingPredicate(tag.content.predicate); } }); this.selectTag(tag); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 4668198d4..65d27a0e0 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -1,6 +1,7 @@ SFModelManager.ContentTypeClassMapping = { "Note" : Note, "Tag" : Tag, + "SN|SmartTag" : SmartTag, "Extension" : Extension, "SN|Editor" : Editor, "SN|Theme" : Theme, 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 From 0a85469b2fc7aa5a90de302a8bd99e1b72543746 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 5 Jul 2018 15:50:40 -0500 Subject: [PATCH 21/52] Editor debouncer --- app/assets/javascripts/app/controllers/editor.js | 3 ++- app/assets/javascripts/app/controllers/notes.js | 9 +++------ app/assets/javascripts/app/filters/sortBy.js | 3 ++- app/assets/templates/editor.html.haml | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 382440504..b54e52449 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -323,7 +323,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(true); } this.nameChanged = function() { diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index e90be9064..74ff16204 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -174,11 +174,12 @@ angular.module('app') } this.setNotes = function(notes) { + notes.forEach(function(note){ note.visible = true; }) - var createNew = notes.length == 0; + var createNew = this.visibleNotes().length == 0; this.selectFirstNote(createNew); } @@ -218,7 +219,7 @@ angular.module('app') } this.createNewNote = function() { - var title = "New Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : ""); + var title = "New Note" + (this.tag.notes ? (" " + (this.visibleNotes().length + 1)) : ""); let newNote = modelManager.createItem({content_type: "Note", content: {text: "", title: title}}); newNote.dummy = true; this.newNote = newNote; @@ -253,10 +254,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/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/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 512134f93..9d8bb27e2 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -58,7 +58,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'"} From 7b1a868676b1556059a423220f97b26817659f90 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 5 Jul 2018 21:10:30 -0500 Subject: [PATCH 22/52] Footer don't keep room state --- app/assets/javascripts/app/controllers/footer.js | 3 +-- .../javascripts/app/services/actionsManager.js | 2 +- .../javascripts/app/services/modelManager.js | 15 ++++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 0232ad3da..ee3669cbe 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -136,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/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index f672adfed..d3383e52f 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) { diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 65d27a0e0..e243a4d69 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -18,7 +18,7 @@ class ModelManager extends SFModelManager { super(); this.notes = []; this.tags = []; - this._extensions = []; + this.components = []; this.storageManager = storageManager; } @@ -27,7 +27,7 @@ class ModelManager extends SFModelManager { super.resetLocalMemory(); this.notes.length = 0; this.tags.length = 0; - this._extensions.length = 0; + this.components.length = 0; } findOrCreateTagByTitle(title) { @@ -58,9 +58,9 @@ class ModelManager extends SFModelManager { 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); } } } @@ -100,8 +100,8 @@ class ModelManager extends SFModelManager { _.remove(this.tags, {uuid: item.uuid}); } else if(item.content_type == "Note") { _.remove(this.notes, {uuid: item.uuid}); - } else if(item.content_type == "Extension") { - _.remove(this._extensions, {uuid: item.uuid}); + } else if(item.content_type == "SN|Component") { + _.remove(this.components, {uuid: item.uuid}); } } @@ -118,6 +118,7 @@ class ModelManager extends SFModelManager { return { "Note" : "note", "Tag" : "tag", + "SN|SmartTag": "smart tag", "Extension" : "action-based extension", "SN|Component" : "component", "SN|Editor" : "editor", From 6fe08106ed1d3021825348e6d5c2f5a50a8ff474 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 6 Jul 2018 17:09:37 -0500 Subject: [PATCH 23/52] Component url to uuid migration --- .../javascripts/app/controllers/home.js | 2 +- .../app/directives/views/componentView.js | 6 +- .../app/services/componentManager.js | 16 ++--- .../app/services/migrationManager.js | 62 ++++++++++++------- 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 6f626bd0d..4f84d647a 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -84,7 +84,7 @@ angular.module('app') } }); - syncManager.setEventHandler((syncEvent, data) => { + syncManager.addEventHandler((syncEvent, data) => { $rootScope.$broadcast(syncEvent, data || {}); }); diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 61b8d98a2..b201c60a2 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -97,9 +97,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(); @@ -129,7 +129,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/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 48531eb32..49adfa327 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; @@ -189,8 +190,7 @@ 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 @@ -268,13 +268,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]; } @@ -474,7 +474,7 @@ class ComponentManager { var responseItem = _.find(responseItems, {uuid: item.uuid}); _.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); } @@ -505,7 +505,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(getClientDataKey(), responseItem.clientData, ComponentManager.ClientDataDomain); } this.modelManager.addItem(item); this.modelManager.resolveReferencesForItem(item, true); diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js index d5a837bf4..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) { @@ -54,10 +46,36 @@ class MigrationManager { 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); From 7339c7c77fe5a59e5d0d70bc10044fd29b560a7c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 6 Jul 2018 23:35:32 -0500 Subject: [PATCH 24/52] Item auth_params --- app/assets/javascripts/app/controllers/home.js | 4 ++-- app/assets/javascripts/app/controllers/notes.js | 1 + .../javascripts/app/directives/views/passwordWizard.js | 2 +- app/assets/javascripts/app/services/actionsManager.js | 2 +- app/assets/javascripts/app/services/archiveManager.js | 10 ++++------ app/assets/javascripts/app/services/authManager.js | 9 +-------- app/assets/javascripts/app/services/desktopManager.js | 5 +---- app/assets/javascripts/app/services/storageManager.js | 2 +- test/mocha/models.test.js | 8 +------- 9 files changed, 13 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 4f84d647a..1cb3fde8e 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -75,12 +75,12 @@ angular.module('app') syncManager.setKeyRequestHandler(async () => { let offline = authManager.offline(); - let version = offline ? passcodeManager.protocolVersion() : await authManager.protocolVersion(); + let auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams(); let keys = offline ? passcodeManager.keys() : await authManager.keys(); return { keys: keys, offline: offline, - version: version + auth_params: auth_params } }); diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 74ff16204..783871d24 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -204,6 +204,7 @@ angular.module('app') this.createNewNote(); return; } + this.selectedNote = note; note.conflict_of = null; // clear conflict this.selectionMade()(note); diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js index 38967ba39..f6bfc294b 100644 --- a/app/assets/javascripts/app/directives/views/passwordWizard.js +++ b/app/assets/javascripts/app/directives/views/passwordWizard.js @@ -173,7 +173,7 @@ 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(async (keys) => { let success = keys.mk === (await authManager.keys()).mk; diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index d3383e52f..017b16b40 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -179,7 +179,7 @@ class ActionsManager { if(decrypted) { keys = null; } - var itemParams = new SFItemParams(item, keys, await this.authManager.protocolVersion()); + var itemParams = new SFItemParams(item, keys, await this.authManager.getAuthParams()); return itemParams.paramsForExtension(); } diff --git a/app/assets/javascripts/app/services/archiveManager.js b/app/assets/javascripts/app/services/archiveManager.js index ccf18e8ad..741e3ee6a 100644 --- a/app/assets/javascripts/app/services/archiveManager.js +++ b/app/assets/javascripts/app/services/archiveManager.js @@ -17,14 +17,12 @@ class ArchiveManager { if(this.authManager.offline() && this.passcodeManager.hasPasscode()) { keys = this.passcodeManager.keys(); authParams = this.passcodeManager.passcodeAuthParams(); - protocolVersion = authParams.version; } else { keys = await this.authManager.keys(); - authParams = this.authManager.getAuthParams(); - protocolVersion = await this.authManager.protocolVersion(); + 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 8d240eb41..361ae4e48 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -49,15 +49,8 @@ class AuthManager extends SFAuthManager { } } - getAuthParams() { - if(!this._authParams) { - this._authParams = JSON.parse(this.storageManager.getItemSync("auth_params")); - } - return this._authParams; - } - async protocolVersion() { - var authParams = this.getAuthParams(); + var authParams = await this.getAuthParams(); if(authParams && authParams.version) { return authParams.version; } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 88967c7ec..e07153208 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -134,17 +134,14 @@ class DesktopManager { if(this.authManager.offline() && this.passcodeManager.hasPasscode()) { keys = this.passcodeManager.keys(); authParams = this.passcodeManager.passcodeAuthParams(); - protocolVersion = authParams.version; } else { keys = await this.authManager.keys(); - authParams = this.authManager.getAuthParams(); - protocolVersion = await this.authManager.protocolVersion(); + 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/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index f1fc947b6..ded89748e 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -154,7 +154,7 @@ class StorageManager extends SFStorageManager { encryptedStorage.content.storage = this.storageAsHash(); // Save new encrypted storage in Fixed storage - var params = new SFItemParams(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); }) diff --git a/test/mocha/models.test.js b/test/mocha/models.test.js index a4abec17e..50645c3a4 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -373,10 +373,6 @@ describe("syncing", () => { let authManager = Factory.globalAuthManager(); let syncManager = new SFSyncManager(modelManager, Factory.globalStorageManager(), Factory.globalHttpManager()); - syncManager.setEventHandler(() => { - - }) - syncManager.setKeyRequestHandler(async () => { return { keys: await authManager.keys(), @@ -392,7 +388,7 @@ describe("syncing", () => { }) } - it.only('syncing a note many times does not cause duplication', async () => { + it('syncing a note many times does not cause duplication', async () => { modelManager.resetLocalMemory(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; @@ -421,8 +417,6 @@ describe("syncing", () => { let syncManager = new SFSyncManager(modelManager, Factory.globalStorageManager(), Factory.globalHttpManager()); - syncManager.setEventHandler(() => {}) - // be offline syncManager.setKeyRequestHandler(async () => { return { From 6fdea5ed37b7ea5dc2247f5c0ed0b0987fe93461 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 7 Jul 2018 09:55:56 -0500 Subject: [PATCH 25/52] Move save function from home into editor controller --- .../javascripts/app/controllers/editor.js | 47 ++++++++++++++----- .../javascripts/app/controllers/footer.js | 2 +- .../javascripts/app/controllers/home.js | 21 --------- app/assets/templates/home.html.haml | 2 +- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index b54e52449..f5e9cadf2 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: "&" @@ -130,7 +129,7 @@ angular.module('app') if(oldNote && oldNote != note) { if(oldNote.hasChanges) { - this.save()(oldNote, null); + this.saveNote(oldNote); } else if(oldNote.dummy) { this.remove()(oldNote); } @@ -243,7 +242,7 @@ angular.module('app') var statusTimeout; - this.saveNote = function($event) { + this.save = function($event) { var note = this.note; note.dummy = false; // Make sure the note exists. A safety measure, as toggling between tags triggers deletes for dummy notes. @@ -252,24 +251,46 @@ angular.module('app') alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note."); 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)); + }); + } + + this.saveNote = function(note, callback) { + note.setDirty(true); + + 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(); } @@ -289,10 +310,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(); + }, delay) } this.showSavingStatus = function() { diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index ee3669cbe..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(); diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 1cb3fde8e..cbcf2f6fc 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -220,27 +220,6 @@ angular.module('app') Shared Callbacks */ - $scope.saveNote = function(note, callback) { - note.setDirty(true); - - syncManager.sync().then((response) => { - if(response && response.error) { - if(!$scope.didShowErrorAlert) { - $scope.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); - }); - } - }) - } - $scope.safeApply = function(fn) { var phase = this.$root.$$phase; if(phase == '$apply' || phase == '$digest') diff --git a/app/assets/templates/home.html.haml b/app/assets/templates/home.html.haml index 54b834bd0..b8d03b4d3 100644 --- a/app/assets/templates/home.html.haml +++ b/app/assets/templates/home.html.haml @@ -6,6 +6,6 @@ %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"} From e53d3723c6bf3f2234ae58be109de0bd4f1e78c2 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 7 Jul 2018 14:32:47 -0500 Subject: [PATCH 26/52] Misc updates --- app/assets/javascripts/app/controllers/lockScreen.js | 3 +++ app/assets/stylesheets/app/_editor.scss | 2 +- app/assets/templates/directives/actions-menu.html.haml | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js index ffd4e88ae..72927f7fd 100644 --- a/app/assets/javascripts/app/controllers/lockScreen.js +++ b/app/assets/javascripts/app/controllers/lockScreen.js @@ -14,6 +14,9 @@ class LockScreen { $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."); 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/templates/directives/actions-menu.html.haml b/app/assets/templates/directives/actions-menu.html.haml index 9fe6397b1..18bd4fae0 100644 --- a/app/assets/templates/directives/actions-menu.html.haml +++ b/app/assets/templates/directives/actions-menu.html.haml @@ -19,6 +19,8 @@ %strong {{action.access_type}} access to this note. + %menu-row{"ng-if" => "extension.actionsWithContextForItem(item).length == 0", "label" => "'No Actions Available'", "faded" => "true"} + .modal.medium-text.medium{"ng-if" => "renderData.showRenderModal", "ng-click" => "$event.stopPropagation();"} .content From b0850ed3e3f366c96b83b81ce0e9e4dca68fcbc7 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 8 Jul 2018 17:49:28 -0500 Subject: [PATCH 27/52] Updates --- .../javascripts/app/controllers/home.js | 13 ++++++---- .../javascripts/app/controllers/lockScreen.js | 3 +-- .../javascripts/app/controllers/tags.js | 2 +- .../app/directives/views/accountMenu.js | 5 ++-- .../javascripts/app/services/authManager.js | 3 +-- .../javascripts/app/services/httpManager.js | 6 ++++- .../javascripts/app/services/modelManager.js | 26 ++++++++----------- .../app/services/storageManager.js | 4 +-- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index cbcf2f6fc..0423f76f5 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -86,6 +86,10 @@ angular.module('app') 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(() => { @@ -103,7 +107,7 @@ angular.module('app') } function loadAllTag() { - var allTag = new Tag({content: {title: "All"}}); + var allTag = new SNTag({content: {title: "All"}}); allTag.all = true; allTag.needsLoad = true; $scope.allTag = allTag; @@ -112,7 +116,7 @@ angular.module('app') } function loadArchivedTag() { - var archiveTag = new SmartTag({content: {title: "Archived", predicate: ["archived", "=", true]}}); + var archiveTag = new SNSmartTag({content: {title: "Archived", predicate: ["archived", "=", true]}}); Object.defineProperty(archiveTag, "notes", { get: () => { return modelManager.notesMatchingPredicate(archiveTag.content.predicate); @@ -282,10 +286,9 @@ angular.module('app') return; } else { // sign out - authManager.signOut(); - storageManager.clearAllData().then(() => { + authManager.signout().then(() => { window.location.reload(); - }) + }); } } else { authManager.login(server, email, pw, false, false, {}).then((response) => { diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js index 72927f7fd..4f3742d2b 100644 --- a/app/assets/javascripts/app/controllers/lockScreen.js +++ b/app/assets/javascripts/app/controllers/lockScreen.js @@ -36,8 +36,7 @@ class LockScreen { return; } - authManager.signOut(); - storageManager.clearAllData().then(() => { + authManager.signout().then(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js index 93f2275cb..95aee80b3 100644 --- a/app/assets/javascripts/app/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -161,7 +161,7 @@ angular.module('app') } 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 4378b1200..4f2efa611 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -153,7 +153,7 @@ class AccountMenu { $scope.clearDatabaseAndRewriteAllItems(true, block); } else { - modelManager.resetLocalMemory(); + modelManager.handleSignout(); storageManager.clearAllModels().then(() => { block(); }) @@ -183,8 +183,7 @@ class AccountMenu { return; } - authManager.signOut(); - storageManager.clearAllData().then(() => { + authManager.signout().then(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 361ae4e48..89b99b163 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -100,7 +100,6 @@ class AuthManager extends SFAuthManager { async handleAuthResponse(response, email, url, authParams, keys) { try { await super.handleAuthResponse(response, email, url, authParams, keys); - this._authParams = authParams; this.user = response.user; this.storageManager.setItem("user", JSON.stringify(response.user)); } catch (e) { @@ -131,7 +130,7 @@ class AuthManager extends SFAuthManager { } signOut() { - this._keys = null; + super.signout(); this.user = null; this._authParams = null; } diff --git a/app/assets/javascripts/app/services/httpManager.js b/app/assets/javascripts/app/services/httpManager.js index 49d9433e4..637db6041 100644 --- a/app/assets/javascripts/app/services/httpManager.js +++ b/app/assets/javascripts/app/services/httpManager.js @@ -2,7 +2,11 @@ class HttpManager extends SFHttpManager { constructor(storageManager, $timeout) { // calling callbacks in a $timeout allows UI to update - super(storageManager, $timeout); + super($timeout); + + this.setJWTRequestHandler(async () => { + return storageManager.getItem("jwt");; + }) } } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index e243a4d69..6c4a6797e 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -1,13 +1,13 @@ SFModelManager.ContentTypeClassMapping = { - "Note" : Note, - "Tag" : Tag, - "SN|SmartTag" : SmartTag, - "Extension" : Extension, - "SN|Editor" : Editor, - "SN|Theme" : Theme, - "SN|Component" : Component, - "SF|Extension" : ServerExtension, - "SF|MFA" : Mfa + "Note" : SNNote, + "Tag" : SNTag, + "SN|SmartTag" : SNSmartTag, + "Extension" : SNExtension, + "SN|Editor" : SNEditor, + "SN|Theme" : SNTheme, + "SN|Component" : SNComponent, + "SF|Extension" : SNServerExtension, + "SF|MFA" : SNMfa }; SFItem.AppDomain = "org.standardnotes.sn"; @@ -23,8 +23,8 @@ class ModelManager extends SFModelManager { this.storageManager = storageManager; } - resetLocalMemory() { - super.resetLocalMemory(); + handleSignout() { + super.handleSignout(); this.notes.length = 0; this.tags.length = 0; this.components.length = 0; @@ -75,10 +75,6 @@ class ModelManager extends SFModelManager { }), 0, tag); } - get filteredNotes() { - return Note.filterDummyNotes(this.notes); - } - setItemToBeDeleted(item) { super.setItemToBeDeleted(item); diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index ded89748e..908490747 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -149,7 +149,7 @@ class StorageManager extends SFStorageManager { } writeEncryptedStorageToDisk() { - var encryptedStorage = new EncryptedStorage(); + var encryptedStorage = new SNEncryptedStorage(); // Copy over totality of current storage encryptedStorage.content.storage = this.storageAsHash(); @@ -163,7 +163,7 @@ class StorageManager extends SFStorageManager { async decryptStorage() { 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.content.storage)) { this.setItem(key, encryptedStorage.storage[key]); From 7fc90ffd669380639736efcd585e043d79d9d1f3 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 11 Jul 2018 09:36:47 -0500 Subject: [PATCH 28/52] Misc updates --- .../javascripts/app/controllers/editor.js | 43 ++++++++++--------- .../javascripts/app/controllers/home.js | 9 +++- .../javascripts/app/controllers/lockScreen.js | 2 +- .../javascripts/app/controllers/notes.js | 5 +-- .../javascripts/app/controllers/tags.js | 2 +- .../app/directives/views/accountMenu.js | 6 +-- .../app/directives/views/passwordWizard.js | 30 ++++++------- .../app/services/archiveManager.js | 2 +- .../javascripts/app/services/authManager.js | 4 +- .../app/services/desktopManager.js | 2 +- .../javascripts/app/services/modelManager.js | 8 ++++ .../app/services/passcodeManager.js | 4 -- 12 files changed, 62 insertions(+), 55 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index f5e9cadf2..7753e524c 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -48,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, @@ -75,7 +75,7 @@ angular.module('app') this.loadTagsString(); }); - modelManager.addItemSyncObserver("component-manager", "Tag", (allItems, validItems, deletedItems, source) => { + modelManager.addItemSyncObserver("editor-tag-observer", "Tag", (allItems, validItems, deletedItems, source) => { if(!this.note) { return; } for(var tag of allItems) { @@ -87,6 +87,25 @@ angular.module('app') } }); + // 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(); @@ -95,6 +114,7 @@ angular.module('app') this.setNote = function(note, oldNote) { this.showExtensions = false; this.showMenu = false; + this.noteStatus = null; this.loadTagsString(); let onReady = () => { @@ -136,25 +156,6 @@ angular.module('app') } } - // 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) { diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 0423f76f5..597f276c7 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -104,6 +104,13 @@ angular.module('app') syncManager.sync(); }, 30000); }); + + authManager.addEventHandler((event) => { + if(event == SFAuthManager.DidSignOutEvent) { + modelManager.handleSignout(); + syncManager.handleSignout(); + } + }) } function loadAllTag() { @@ -286,7 +293,7 @@ angular.module('app') return; } else { // sign out - authManager.signout().then(() => { + authManager.signout(true).then(() => { window.location.reload(); }); } diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js index 4f3742d2b..e38eff9f6 100644 --- a/app/assets/javascripts/app/controllers/lockScreen.js +++ b/app/assets/javascripts/app/controllers/lockScreen.js @@ -36,7 +36,7 @@ class LockScreen { return; } - authManager.signout().then(() => { + 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 783871d24..c78e881c6 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -174,8 +174,7 @@ angular.module('app') } this.setNotes = function(notes) { - - notes.forEach(function(note){ + notes.forEach((note) => { note.visible = true; }) @@ -204,7 +203,7 @@ angular.module('app') this.createNewNote(); return; } - + this.selectedNote = note; note.conflict_of = null; // clear conflict this.selectionMade()(note); diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js index 95aee80b3..bb9149f89 100644 --- a/app/assets/javascripts/app/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -71,7 +71,7 @@ angular.module('app') this.selectTag(tag); } } else if(data.item.content_type == "SN|SmartTag") { - var tag = new SmartTag(data.item); + var tag = new SNSmartTag(data.item); Object.defineProperty(tag, "notes", { get: () => { return modelManager.notesMatchingPredicate(tag.content.predicate); diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 4f2efa611..2eccf5222 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -14,8 +14,6 @@ class AccountMenu { 'ngInject'; $scope.formData = {mergeLocal: true, ephemeral: false}; - $scope.formData.email = "july2@bitar.io"; - $scope.formData.user_password = "password"; $scope.user = authManager.user; syncManager.getServerURL().then((url) => { @@ -153,7 +151,7 @@ class AccountMenu { $scope.clearDatabaseAndRewriteAllItems(true, block); } else { - modelManager.handleSignout(); + modelManager.removeAllItemsFromMemory(); storageManager.clearAllModels().then(() => { block(); }) @@ -183,7 +181,7 @@ class AccountMenu { return; } - authManager.signout().then(() => { + authManager.signout(true).then(() => { window.location.reload(); }) } diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js index f6bfc294b..f953f6839 100644 --- a/app/assets/javascripts/app/directives/views/passwordWizard.js +++ b/app/assets/javascripts/app/directives/views/passwordWizard.js @@ -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().then((response) => { - authManager.changePassword(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)); - } - }) - }) - }); + // 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/services/archiveManager.js b/app/assets/javascripts/app/services/archiveManager.js index 741e3ee6a..8e4616a5b 100644 --- a/app/assets/javascripts/app/services/archiveManager.js +++ b/app/assets/javascripts/app/services/archiveManager.js @@ -12,7 +12,7 @@ class ArchiveManager { 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(); diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 89b99b163..a3a4dd880 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -88,8 +88,8 @@ class AuthManager extends SFAuthManager { }) } - async changePassword(email, current_server_pw, newKeys, newAuthParams) { - return super.changePassword(email, current_server_pw, newKeys, newAuthParams).then((response) => { + 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(); } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index e07153208..23612e64f 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -130,7 +130,7 @@ class DesktopManager { } async desktop_requestBackupFile(callback) { - var keys, authParams, protocolVersion; + var keys, authParams; if(this.authManager.offline() && this.passcodeManager.hasPasscode()) { keys = this.passcodeManager.keys(); authParams = this.passcodeManager.passcodeAuthParams(); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 6c4a6797e..ea5175b09 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -30,6 +30,14 @@ class ModelManager extends SFModelManager { this.components.length = 0; } + removeAllItemsFromMemory() { + for(var item of this.items) { + item.deleted = true; + } + this.notifySyncObserversOfModels(this.items); + this.handleSignout(); + } + findOrCreateTagByTitle(title) { var tag = _.find(this.tags, {title: title}) if(!tag) { diff --git a/app/assets/javascripts/app/services/passcodeManager.js b/app/assets/javascripts/app/services/passcodeManager.js index 587a61027..d70ffd2f1 100644 --- a/app/assets/javascripts/app/services/passcodeManager.js +++ b/app/assets/javascripts/app/services/passcodeManager.js @@ -26,10 +26,6 @@ angular.module('app') return JSON.parse(storageManager.getItemSync("offlineParams", StorageManager.Fixed)); } - this.protocolVersion = function() { - return this._authParams && this._authParams.version; - } - this.unlock = function(passcode, callback) { var params = this.passcodeAuthParams(); SFJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => { From c6bf9d8b48755cf814bace773de09010fed0dfd4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 11 Jul 2018 09:54:16 -0500 Subject: [PATCH 29/52] Test updates --- test/mocha/lib/factory.js | 7 ++++++- test/mocha/models.test.js | 10 ++++++---- testing-server.js | 5 +++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/test/mocha/lib/factory.js b/test/mocha/lib/factory.js index cdaeced09..42efc76e3 100644 --- a/test/mocha/lib/factory.js +++ b/test/mocha/lib/factory.js @@ -28,7 +28,12 @@ export default class Factory { } static globalHttpManager() { - if(_globalHttpManager == null) { _globalHttpManager = new SFHttpManager(_globalStorageManager); } + if(_globalHttpManager == null) { + _globalHttpManager = new SFHttpManager(); + _globalHttpManager.setJWTRequestHandler(async () => { + return this.globalStorageManager().getItem("jwt");; + }) + } return _globalHttpManager; } diff --git a/test/mocha/models.test.js b/test/mocha/models.test.js index 50645c3a4..825925d00 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -46,7 +46,7 @@ describe("notes and tags", () => { let noteParams = getNoteParams(); modelManager.mapResponseItemsToLocalModels([noteParams]); let note = modelManager.allItemsMatchingTypes(["Note"])[0]; - expect(note).to.be.an.instanceOf(Note); + expect(note).to.be.an.instanceOf(SNNote); }); it('creates two-way relationship between note and tag', () => { @@ -376,6 +376,7 @@ describe("syncing", () => { syncManager.setKeyRequestHandler(async () => { return { keys: await authManager.keys(), + auth_params: await authManager.getAuthParams(), offline: false }; }) @@ -389,7 +390,7 @@ describe("syncing", () => { } it('syncing a note many times does not cause duplication', async () => { - modelManager.resetLocalMemory(); + modelManager.handleSignout(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; let tagParams = pair[1]; @@ -424,7 +425,7 @@ describe("syncing", () => { }; }) - modelManager.resetLocalMemory(); + modelManager.handleSignout(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; let tagParams = pair[1]; @@ -445,6 +446,7 @@ describe("syncing", () => { syncManager.setKeyRequestHandler(async () => { return { keys: await authManager.keys(), + auth_params: await authManager.getAuthParams(), offline: false }; }) @@ -471,7 +473,7 @@ describe("syncing", () => { }) it('duplicating a tag should maintian its relationships', async () => { - modelManager.resetLocalMemory(); + modelManager.handleSignout(); let pair = createRelatedNoteTagPair(); let noteParams = pair[0]; let tagParams = pair[1]; diff --git a/testing-server.js b/testing-server.js index 25d8a1a0a..8ceee5329 100644 --- a/testing-server.js +++ b/testing-server.js @@ -2,6 +2,7 @@ var connect = require('connect'); var serveStatic = require('serve-static'); -connect().use(serveStatic(__dirname)).listen(8081, function(){ - console.log('Server running on 8081...'); +var port = 7000; +connect().use(serveStatic(__dirname)).listen(port, function(){ + console.log(`Server running on ${port}...`); }); From 78587e68bd76bbb0fe29625e07f5e2df5f9774b9 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 14 Jul 2018 10:08:06 -0500 Subject: [PATCH 30/52] Relationship test --- .../app/services/componentManager.js | 9 +- package-lock.json | 3699 +++++++++++++++++ test/mocha/models.test.js | 22 + 3 files changed, 3728 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 49adfa327..00c8ef9c8 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -4,7 +4,7 @@ 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; @@ -882,7 +882,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") { @@ -893,6 +895,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/package-lock.json b/package-lock.json index 8b11a5209..1337aa82f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5660,6 +5660,9 @@ "sn-models": { "version": "file:../../sn-models", "dev": true, + "requires": { + "standard-file-js": "file:../../sf/sfjs" + }, "dependencies": { "JSONStream": { "version": "1.3.3", @@ -8807,6 +8810,3702 @@ "version": "1.0.3", "bundled": true }, + "standard-file-js": { + "version": "file:../../sf/sfjs", + "bundled": true, + "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, + "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" + } + } + } + }, + "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", + "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" + } + }, + "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": { + "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" + } + }, + "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": { + "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" + } + }, + "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", + "minimist": "1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "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, + "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 + }, + "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", + "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-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", + "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-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", + "statuses": "1.5.0" + }, + "dependencies": { + "statuses": { + "version": "1.5.0", + "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" + } + }, + "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", + "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", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "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" + } + }, + "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": { + "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" + } + }, + "range-parser": { + "version": "1.2.0", + "bundled": true + }, + "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-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", + "statuses": "1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "bundled": true + } + } + }, + "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, + "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 + }, + "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": { + "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 + }, + "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": { + "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 + } + } + } + } + }, "stream-browserify": { "version": "2.0.1", "bundled": true, diff --git a/test/mocha/models.test.js b/test/mocha/models.test.js index 825925d00..bce424161 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -49,6 +49,28 @@ describe("notes and tags", () => { expect(note).to.be.an.instanceOf(SNNote); }); + it.only('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(); From 5f0ae93a1180da7dd8dc3e0ed69c4385774f9fd7 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 14 Jul 2018 10:23:31 -0500 Subject: [PATCH 31/52] Reload component instead of activating on status change --- app/assets/javascripts/app/directives/views/componentView.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index b201c60a2..c8abd15bf 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -18,6 +18,7 @@ class ComponentView { $scope.el = el; $scope.identifier = "component-view-" + Math.random(); + $scope.componentValid = true; // console.log("Registering handler", $scope.identifier, $scope.component.name); @@ -112,7 +113,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); } } From 5f883467876ee6af4301c467c1b59e740ce24088 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 14 Jul 2018 10:25:58 -0500 Subject: [PATCH 32/52] Submodules --- public/extensions/extensions-manager | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 19d93e23e59c6b7ea82890ac0639053f621721f6 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 15 Jul 2018 09:23:18 -0500 Subject: [PATCH 33/52] Dont update client date when locking --- .../javascripts/app/controllers/editor.js | 20 ++++++++++--------- .../javascripts/app/services/modelManager.js | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 0c50db89e..b6943eb37 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -243,7 +243,7 @@ angular.module('app') var statusTimeout; - this.save = 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. @@ -265,11 +265,12 @@ angular.module('app') this.showErrorStatus(); }, 200) } - }); + }, dontUpdateClientModified); } - this.saveNote = function(note, callback) { - note.setDirty(true); + 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) { @@ -296,7 +297,7 @@ angular.module('app') } 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. @@ -313,7 +314,7 @@ angular.module('app') if(statusTimeout) $timeout.cancel(statusTimeout); saveTimeout = $timeout(() => { this.showSavingStatus(); - this.save(); + this.save(dontUpdateClientModified); }, delay) } @@ -346,7 +347,7 @@ angular.module('app') this.contentChanged = function() { // content changes should bypass manual debouncer as we use the built in ng-model-options debouncer - this.changesMade(true); + this.changesMade({bypassDebouncer: true}); } this.nameChanged = function() { @@ -387,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"); } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index ea5175b09..6ac250eea 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -14,8 +14,8 @@ SFItem.AppDomain = "org.standardnotes.sn"; class ModelManager extends SFModelManager { - constructor(storageManager) { - super(); + constructor(storageManager, $timeout) { + super($timeout); this.notes = []; this.tags = []; this.components = []; From 0f74458fd24a9d11daa8f7b8559eb1483741f4de Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 16 Jul 2018 09:35:29 -0500 Subject: [PATCH 34/52] Published sn packages --- package-lock.json | 718 ++-------------------------------------------- package.json | 4 +- 2 files changed, 30 insertions(+), 692 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1337aa82f..f554e9860 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13157,19 +13157,7 @@ "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" - } - } - } + "dependencies": {} }, "assertion-error": { "version": "1.1.0", @@ -13251,12 +13239,7 @@ "source-map": "0.5.7", "trim-right": "1.0.1" }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "bundled": true - } - } + "dependencies": {} }, "babel-helper-builder-binary-assignment-operator-visitor": { "version": "6.24.1", @@ -13635,12 +13618,7 @@ "core-js": "2.5.6", "regenerator-runtime": "0.10.5" }, - "dependencies": { - "regenerator-runtime": { - "version": "0.10.5", - "bundled": true - } - } + "dependencies": {} }, "babel-preset-env": { "version": "1.7.0", @@ -13735,12 +13713,7 @@ "core-js": "2.5.6", "regenerator-runtime": "0.11.1" }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "bundled": true - } - } + "dependencies": {} }, "babel-template": { "version": "6.26.0", @@ -13958,20 +13931,7 @@ "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 - } - } + "dependencies": {} }, "browserify-rsa": { "version": "4.0.1", @@ -14148,12 +14108,7 @@ "lodash.memoize": "3.0.4", "source-map": "0.5.7" }, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "bundled": true - } - } + "dependencies": {} }, "commander": { "version": "2.15.1", @@ -14348,12 +14303,7 @@ "defined": "1.0.0", "minimist": "1.2.0" }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } + "dependencies": {} }, "diff": { "version": "3.5.0", @@ -14537,19 +14487,7 @@ "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" - } - } - } + "dependencies": {} }, "for-in": { "version": "1.0.2", @@ -14586,458 +14524,7 @@ "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 - } - } + "dependencies": {} }, "function-bind": { "version": "1.1.1", @@ -15135,30 +14622,7 @@ "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" - } - } - } + "dependencies": {} }, "grunt-babel": { "version": "6.0.0", @@ -15179,15 +14643,7 @@ "resolve": "1.1.7", "watchify": "3.11.0" }, - "dependencies": { - "async": { - "version": "2.6.1", - "bundled": true, - "requires": { - "lodash": "4.17.10" - } - } - } + "dependencies": {} }, "grunt-contrib-concat": { "version": "1.0.1", @@ -15217,15 +14673,7 @@ "lodash": "4.17.10", "tiny-lr": "1.1.1" }, - "dependencies": { - "async": { - "version": "2.6.1", - "bundled": true, - "requires": { - "lodash": "4.17.10" - } - } - } + "dependencies": {} }, "grunt-known-options": { "version": "1.1.0", @@ -15248,12 +14696,7 @@ "chalk": "1.1.3", "lodash": "4.3.0" }, - "dependencies": { - "lodash": { - "version": "4.3.0", - "bundled": true - } - } + "dependencies": {} }, "grunt-legacy-util": { "version": "1.0.0", @@ -15267,12 +14710,7 @@ "underscore.string": "3.2.3", "which": "1.2.14" }, - "dependencies": { - "lodash": { - "version": "4.3.0", - "bundled": true - } - } + "dependencies": {} }, "grunt-newer": { "version": "1.3.0", @@ -15281,15 +14719,7 @@ "async": "1.5.2", "rimraf": "2.6.2" }, - "dependencies": { - "rimraf": { - "version": "2.6.2", - "bundled": true, - "requires": { - "glob": "7.1.2" - } - } - } + "dependencies": {} }, "gzip-size": { "version": "1.0.0", @@ -15298,19 +14728,7 @@ "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 - } - } + "dependencies": {} }, "has": { "version": "1.0.1", @@ -15392,12 +14810,7 @@ "setprototypeof": "1.1.0", "statuses": "1.5.0" }, - "dependencies": { - "statuses": { - "version": "1.5.0", - "bundled": true - } - } + "dependencies": {} }, "http-parser-js": { "version": "0.4.13", @@ -15606,12 +15019,7 @@ "isarray": "2.0.4", "stream-splicer": "2.0.0" }, - "dependencies": { - "isarray": { - "version": "2.0.4", - "bundled": true - } - } + "dependencies": {} }, "lazy-cache": { "version": "1.0.4", @@ -15700,12 +15108,7 @@ "redent": "1.0.0", "trim-newlines": "1.0.0" }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } + "dependencies": {} }, "micromatch": { "version": "2.3.11", @@ -15780,22 +15183,7 @@ "mkdirp": "0.5.1", "supports-color": "5.4.0" }, - "dependencies": { - "debug": { - "version": "3.1.0", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "supports-color": { - "version": "5.4.0", - "bundled": true, - "requires": { - "has-flag": "3.0.0" - } - } - } + "dependencies": {} }, "module-deps": { "version": "6.1.0", @@ -15817,15 +15205,7 @@ "through2": "2.0.3", "xtend": "4.0.1" }, - "dependencies": { - "resolve": { - "version": "1.8.1", - "bundled": true, - "requires": { - "path-parse": "1.0.5" - } - } - } + "dependencies": {} }, "ms": { "version": "2.0.0", @@ -16096,16 +15476,7 @@ "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 - } - } + "dependencies": {} }, "randombytes": { "version": "2.0.6", @@ -16133,12 +15504,7 @@ "bytes": "1.0.0", "string_decoder": "0.10.31" }, - "dependencies": { - "string_decoder": { - "version": "0.10.31", - "bundled": true - } - } + "dependencies": {} }, "read-only-stream": { "version": "2.0.0", @@ -16315,12 +15681,7 @@ "range-parser": "1.2.0", "statuses": "1.4.0" }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "bundled": true - } - } + "dependencies": {} }, "serve-static": { "version": "1.13.2", @@ -16494,12 +15855,7 @@ "requires": { "minimist": "1.2.0" }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } + "dependencies": {} }, "supports-color": { "version": "2.0.0", @@ -16542,15 +15898,7 @@ "object-assign": "4.1.1", "qs": "6.5.2" }, - "dependencies": { - "debug": { - "version": "3.1.0", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - } - } + "dependencies": {} }, "to-arraybuffer": { "version": "1.0.1", @@ -16627,12 +15975,7 @@ "punycode": "1.3.2", "querystring": "0.2.0" }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "bundled": true - } - } + "dependencies": {} }, "user-home": { "version": "1.1.1", @@ -16729,12 +16072,7 @@ "decamelize": "1.2.0", "window-size": "0.1.0" }, - "dependencies": { - "camelcase": { - "version": "1.2.1", - "bundled": true - } - } + "dependencies": {} } } }, diff --git a/package.json b/package.json index 0c7e9d901..0b66c8a4b 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", - "standard-file-js": "file:~/Desktop/sf/sfjs", - "sn-models": "file:~/Desktop/sn-models", + "standard-file-js": "0.3.2", + "sn-models": "0.1.0", "connect": "^3.6.6", "mocha": "^5.2.0", "serve-static": "^1.13.2", From a699fc0cb7a58f3ed6989e383c8e331a7cf9b7de Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 16 Jul 2018 13:37:13 -0500 Subject: [PATCH 35/52] Sync response can be null --- app/assets/javascripts/app/directives/views/passwordWizard.js | 2 +- app/assets/javascripts/app/services/componentManager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js index f953f6839..09dd413f1 100644 --- a/app/assets/javascripts/app/directives/views/passwordWizard.js +++ b/app/assets/javascripts/app/directives/views/passwordWizard.js @@ -189,7 +189,7 @@ class PasswordWizard { $scope.resyncData = function(callback) { modelManager.setAllItemsDirty(); syncManager.sync().then((response) => { - if(response.error) { + if(!response || response.error) { alert(FailedSyncMessage) $timeout(() => callback(false)); } else { diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 4bf506f1d..cf68818a5 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -491,7 +491,7 @@ class ComponentManager { // 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); }); }); From f496101ad009163779c4915993077b342a06276d Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 18 Jul 2018 10:36:55 -0500 Subject: [PATCH 36/52] Fix missing object reference --- app/assets/javascripts/app/services/componentManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index cf68818a5..e4a721f37 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -513,7 +513,7 @@ class ComponentManager { for(let responseItem of responseItems) { var item = this.modelManager.createItem(responseItem); if(responseItem.clientData) { - item.setDomainDataItem(getClientDataKey(), responseItem.clientData, ComponentManager.ClientDataDomain); + item.setDomainDataItem(component.getClientDataKey(), responseItem.clientData, ComponentManager.ClientDataDomain); } this.modelManager.addItem(item); this.modelManager.resolveReferencesForItem(item, true); From a1374b88ff52f8954e5efc8f04f08944c2ab0e70 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 18 Jul 2018 13:00:12 -0500 Subject: [PATCH 37/52] Handle offline response for import --- app/assets/javascripts/app/directives/views/accountMenu.js | 4 +++- test/mocha/models.test.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 2eccf5222..776b94658 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -206,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) { @@ -257,6 +258,7 @@ class AccountMenu { } syncManager.sync({additionalFields: ["created_at", "updated_at"]}).then((response) => { + // Response can be null if syncing offline callback(response, errorCount); }); } diff --git a/test/mocha/models.test.js b/test/mocha/models.test.js index bce424161..409079f75 100644 --- a/test/mocha/models.test.js +++ b/test/mocha/models.test.js @@ -49,7 +49,7 @@ describe("notes and tags", () => { expect(note).to.be.an.instanceOf(SNNote); }); - it.only('properly handles legacy relationships', () => { + it('properly handles legacy relationships', () => { // legacy relationships are when a note has a reference to a tag let modelManager = Factory.createModelManager(); let pair = createRelatedNoteTagPair(); From 57a140e272dfb0aa8bf460171edc1aacb8eab7bc Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 18 Jul 2018 20:58:18 -0500 Subject: [PATCH 38/52] Layerable themes --- .../app/services/componentManager.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index e4a721f37..4abcbc739 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -158,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}) @@ -586,11 +589,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.package_info.layerable) { + // Deactive currently active theme if new theme is not layerable + var activeThemes = this.getActiveThemes(); + for(var theme of activeThemes) { + if(theme && !theme.package_info.layerable) { + this.deactivateComponent(theme); + } } } this.activateComponent(targetComponent); From 382c40cfa6997771f603b0b1a2abbccd23ba080e Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 18 Jul 2018 21:10:34 -0500 Subject: [PATCH 39/52] Locale format for last sync date --- app/assets/javascripts/app/filters/appDate.js | 19 ++++++++++++++++++- test/javascripts/filters/DateFilter_spec.js | 5 ----- 2 files changed, 18 insertions(+), 6 deletions(-) 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/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(); From 8a127f97a1ad14ac23662036694c2ce79c0115b7 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 19 Jul 2018 10:16:53 -0500 Subject: [PATCH 40/52] Extensions issue loading indicator --- .../app/directives/views/componentView.js | 26 +++++++++++++------ app/assets/stylesheets/app/_modals.scss | 1 + app/assets/stylesheets/app/_stylekit-sub.scss | 5 ++++ .../directives/component-view.html.haml | 9 +++++++ 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index c8abd15bf..d2a0916f1 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -23,15 +23,25 @@ class ComponentView { // 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) => { diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index e9d6c6fb0..03675a20f 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -99,6 +99,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/_stylekit-sub.scss b/app/assets/stylesheets/app/_stylekit-sub.scss index cd183fcc4..07a0f0255 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; + } + } } 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 From 1250f3fd5e1b89eac4e1172018b8e355bf249b5b Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 19 Jul 2018 22:25:14 -0500 Subject: [PATCH 41/52] Session history --- .../javascripts/app/controllers/editor.js | 4 +- .../app/directives/views/menuRow.js | 2 +- .../directives/views/revisionPreviewModal.js | 49 + .../directives/views/sessionHistoryMenu.js | 72 ++ .../app/services/actionsManager.js | 8 +- .../app/services/sessionHistory.js | 213 +++++ app/assets/stylesheets/app/_modals.scss | 7 + app/assets/stylesheets/app/_stylekit-sub.scss | 6 + .../revision-preview-modal.html.haml | 13 + .../directives/session-history-menu.html.haml | 31 + app/assets/templates/editor.html.haml | 4 + package-lock.json | 888 +----------------- package.json | 2 +- 13 files changed, 456 insertions(+), 843 deletions(-) create mode 100644 app/assets/javascripts/app/directives/views/revisionPreviewModal.js create mode 100644 app/assets/javascripts/app/directives/views/sessionHistoryMenu.js create mode 100644 app/assets/javascripts/app/services/sessionHistory.js create mode 100644 app/assets/templates/directives/revision-preview-modal.html.haml create mode 100644 app/assets/templates/directives/session-history-menu.html.haml diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index b6943eb37..922115c29 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -22,7 +22,9 @@ 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.showSessionHistory = true; this.spellcheck = true; this.componentManager = componentManager; 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/revisionPreviewModal.js b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js new file mode 100644 index 000000000..b83c7067e --- /dev/null +++ b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js @@ -0,0 +1,49 @@ +class RevisionPreviewModal { + + constructor() { + this.restrict = "E"; + this.templateUrl = "directives/revision-preview-modal.html"; + this.scope = { + revision: "=", + show: "=", + callback: "=" + }; + } + + 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.revision.content); + if(contentCopy.title) { contentCopy.title += " (copy)"; } + item = modelManager.createItem({content_type: "Note", content: contentCopy}); + modelManager.addItem(item); + } else { + item = modelManager.findItem($scope.revision.itemUuid); + item.content = Object.assign({}, $scope.revision.content); + item.mapContentToLocalProperties(item.content); + } + 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..859c32680 --- /dev/null +++ b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js @@ -0,0 +1,72 @@ +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.reloadHistory = function() { + $scope.history = sessionHistory.historyForItem($scope.item); + } + $scope.reloadHistory(); + + $scope.openRevision = function(revision) { + actionsManager.presentRevisionPreviewModal(revision); + } + + $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; + }) + }); + } + + } + +} + +angular.module('app').directive('sessionHistoryMenu', () => new SessionHistoryMenu); diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index 017b16b40..7790262cf 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -198,8 +198,14 @@ class ActionsManager { }) } - presentPasswordModal(callback) { + presentRevisionPreviewModal(revision) { + var scope = this.$rootScope.$new(true); + scope.revision = revision; + 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/sessionHistory.js b/app/assets/javascripts/app/services/sessionHistory.js new file mode 100644 index 000000000..dd45f0c04 --- /dev/null +++ b/app/assets/javascripts/app/services/sessionHistory.js @@ -0,0 +1,213 @@ +class SessionHistory { + + constructor(modelManager, storageManager, authManager, passcodeManager, $timeout) { + this.modelManager = modelManager; + this.storageManager = storageManager; + this.authManager = authManager; + this.passcodeManager = passcodeManager; + this.$timeout = $timeout; + + this.loadFromDisk().then(() => { + this.modelManager.addItemSyncObserver("session-history", "Note", (allItems, validItems, deletedItems, source, sourceKey) => { + for(let item of allItems) { + this.addRevision(item); + } + }); + }) + } + + async encryptionParams() { + let offline = this.authManager.offline(); + let auth_params = offline ? this.passcodeManager.passcodeAuthParams() : await this.authManager.getAuthParams(); + let keys = offline ? this.passcodeManager.keys() : await this.authManager.keys(); + return {keys, auth_params}; + } + + addRevision(item) { + var added = this.historyContainer.addRevision(item); + + if(added) { + if(this.diskTimeout) {this.$timeout.cancel(this.diskTimeout)}; + this.diskTimeout = this.$timeout(() => { + this.saveToDisk(); + }, 1000) + } + } + + historyForItem(item) { + return this.historyContainer.historyForItem(item); + } + + async clearItemHistory(item) { + delete this.historyContainer.clearItemHistory(item); + return this.saveToDisk(); + } + + async clearAllHistory() { + this.historyContainer.clearAllHistory(); + return this.storageManager.removeItem("sessionHistory"); + } + + async toggleDiskSaving() { + this.diskEnabled = !this.diskEnabled; + + if(this.diskEnabled) { + this.storageManager.setItem("persistSessionHistory", JSON.stringify(true)); + this.saveToDisk(); + } else { + this.storageManager.removeItem("persistSessionHistory"); + } + } + + async saveToDisk() { + if(!this.diskEnabled) { + return; + } + let encryptionParams = await this.encryptionParams(); + var itemParams = new SFItemParams(this.historyContainer, encryptionParams.keys, encryptionParams.auth_params); + itemParams.paramsForSync().then((syncParams) => { + console.log("Saving to disk", syncParams); + this.storageManager.setItem("sessionHistory", JSON.stringify(syncParams)); + }) + } + + async loadFromDisk() { + var diskValue = await this.storageManager.getItem("persistSessionHistory"); + if(diskValue) { + this.diskEnabled = JSON.parse(diskValue); + } + + var historyValue = await this.storageManager.getItem("sessionHistory"); + if(historyValue) { + historyValue = JSON.parse(historyValue); + let encryptionParams = await this.encryptionParams(); + await SFJS.itemTransformer.decryptItem(historyValue, encryptionParams.keys); + var historyContainer = new HistoryContainer(historyValue); + this.historyContainer = historyContainer; + } else { + this.historyContainer = new HistoryContainer(); + } + } +} + +class HistoryContainer extends SFItem { + constructor(json_obj) { + super(json_obj); + + if(!this.content.itemsDictionary) { + this.content.itemsDictionary = {}; + } + + var objectKeys = Object.keys(this.content.itemsDictionary); + objectKeys.forEach((key) => { + var value = this.content.itemsDictionary[key]; + this.content.itemsDictionary[key] = new ItemHistory(value); + }); + } + + addRevision(item) { + if(!this.content.itemsDictionary[item.uuid]) { + this.content.itemsDictionary[item.uuid] = new ItemHistory(); + } + var itemHistory = this.content.itemsDictionary[item.uuid]; + return itemHistory.addRevision(item); + } + + historyForItem(item) { + return this.content.itemsDictionary[item.uuid]; + } + + clearItemHistory(item) { + delete this.content.itemsDictionary[item.uuid]; + } + + clearAllHistory() { + this.content.itemsDictionary = {}; + } +} + +class ItemHistory { + + constructor(json_obj = {}) { + if(!this.revisions) { + this.revisions = []; + } + if(json_obj.revisions) { + for(var revision of json_obj.revisions) { + this.revisions.push(new NoteRevision(revision, this.revisions[this.revisions.length - 1], revision.date)); + } + } + } + + addRevision(item) { + var previousRevision = this.revisions[this.revisions.length - 1]; + var prospectiveRevision = new NoteRevision(item, previousRevision, item.updated_at); + if(prospectiveRevision.isSameAsRevision(previousRevision)) { + return; + } + this.revisions.push(prospectiveRevision); + return prospectiveRevision; + } + +} + +class ItemRevision { + + constructor(item, previousRevision, date) { + if(typeof(date) == "string") { + this.date = new Date(date); + } else { + this.date = date; + } + this.itemUuid = item.uuid; + this.hasPreviousRevision = previousRevision != null; + this.content = Object.assign({}, item.content); + } + + isSameAsRevision(revision) { + if(!revision) { + return false; + } + return JSON.stringify(this.content) === JSON.stringify(revision.content); + } + +} + +class NoteRevision extends ItemRevision { + constructor(item, previousRevision, date) { + super(item, previousRevision, date); + if(previousRevision) { + this.textCharDiffLength = this.content.text.length - previousRevision.content.text.length; + } else { + this.textCharDiffLength = this.content.text.length; + } + } + + previewTitle() { + return this.date.toLocaleString(); + } + + operationVector() { + if(!this.hasPreviousRevision || this.textCharDiffLength == 0) { + return 0; + } else if(this.textCharDiffLength < 0) { + return -1; + } else { + return 1; + } + } + + previewSubTitle() { + if(!this.hasPreviousRevision) { + 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" + } + } +} + +angular.module('app').service('sessionHistory', SessionHistory); diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index 03675a20f..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; } diff --git a/app/assets/stylesheets/app/_stylekit-sub.scss b/app/assets/stylesheets/app/_stylekit-sub.scss index 07a0f0255..b0a9285e1 100644 --- a/app/assets/stylesheets/app/_stylekit-sub.scss +++ b/app/assets/stylesheets/app/_stylekit-sub.scss @@ -53,3 +53,9 @@ color: $blue-color; } } + +#session-history-menu { + .menu-panel .row .sublabel.opaque { + opacity: 1.0 + } +} 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..1e9b5f107 --- /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 {{revision.content.title}} + %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{revision.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..46814a076 --- /dev/null +++ b/app/assets/templates/directives/session-history-menu.html.haml @@ -0,0 +1,31 @@ +.sn-component#session-history-menu + .menu-panel.dropdown-menu + .header + .column + %h4.title {{history.revisions.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" => "(diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'", "ng-click" => "toggleDiskSaving(); $event.stopPropagation();"} + .sublabel + May increase app loading speed and memory footprint. + + %menu-row{"ng-repeat" => "revision in history.revisions", + "ng-click" => "openRevision(revision); $event.stopPropagation();", + "label" => "revision.previewTitle()"} + .sublabel.opaque{"ng-class" => "classForRevision(revision)"} + {{revision.previewSubTitle()}} + +.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}} diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 9d8bb27e2..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"} diff --git a/package-lock.json b/package-lock.json index f554e9860..699858226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8921,23 +8921,7 @@ }, "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" - } - } - } + "bundled": true }, "assertion-error": { "version": "1.1.0", @@ -9014,16 +8998,9 @@ "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": { @@ -9400,14 +9377,7 @@ "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 - } + "core-js": "2.5.6" } }, "babel-preset-env": { @@ -9502,12 +9472,6 @@ "requires": { "core-js": "2.5.6", "regenerator-runtime": "0.11.1" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "bundled": true - } } }, "babel-template": { @@ -9721,24 +9685,9 @@ "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": { @@ -9911,16 +9860,9 @@ "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": { @@ -10113,14 +10055,7 @@ "bundled": true, "requires": { "acorn-node": "1.5.2", - "defined": "1.0.0", - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } + "defined": "1.0.0" } }, "diff": { @@ -10301,23 +10236,7 @@ }, "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" - } - } - } + "bundled": true }, "for-in": { "version": "1.0.2", @@ -10351,460 +10270,7 @@ "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 - } + "nan": "2.10.0" } }, "function-bind": { @@ -10891,8 +10357,6 @@ "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", @@ -10902,30 +10366,6 @@ "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": { @@ -10939,22 +10379,12 @@ "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": { @@ -10980,19 +10410,9 @@ "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": { @@ -11013,14 +10433,7 @@ "version": "1.0.0", "bundled": true, "requires": { - "chalk": "1.1.3", - "lodash": "4.3.0" - }, - "dependencies": { - "lodash": { - "version": "4.3.0", - "bundled": true - } + "chalk": "1.1.3" } }, "grunt-legacy-util": { @@ -11031,53 +10444,22 @@ "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" - } - } + "async": "1.5.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": { @@ -11157,14 +10539,7 @@ "requires": { "depd": "1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.5.0" - }, - "dependencies": { - "statuses": { - "version": "1.5.0", - "bundled": true - } + "setprototypeof": "1.1.0" } }, "http-parser-js": { @@ -11371,14 +10746,7 @@ "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": { @@ -11461,18 +10829,11 @@ "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": { @@ -11538,31 +10899,13 @@ "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", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "supports-color": { - "version": "5.4.0", - "bundled": true, - "requires": { - "has-flag": "3.0.0" - } - } + "mkdirp": "0.5.1" } }, "module-deps": { @@ -11579,20 +10922,10 @@ "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": { @@ -11860,19 +11193,7 @@ "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": { @@ -11898,14 +11219,7 @@ "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 - } + "bytes": "1.0.0" } }, "read-only-stream": { @@ -12080,14 +11394,7 @@ "mime": "1.4.1", "ms": "2.0.0", "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "bundled": true - } + "range-parser": "1.2.0" } }, "serve-static": { @@ -12258,16 +11565,7 @@ }, "subarg": { "version": "1.0.0", - "bundled": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } + "bundled": true }, "supports-color": { "version": "2.0.0", @@ -12304,20 +11602,10 @@ "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": { @@ -12392,14 +11680,7 @@ "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": { @@ -12492,16 +11773,9 @@ "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 - } } } } @@ -13153,11 +12427,7 @@ }, "assert": { "version": "1.4.1", - "bundled": true, - "requires": { - "util": "0.10.3" - }, - "dependencies": {} + "bundled": true }, "assertion-error": { "version": "1.1.0", @@ -13234,12 +12504,10 @@ "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": {} + } }, "babel-helper-builder-binary-assignment-operator-visitor": { "version": "6.24.1", @@ -13615,10 +12883,8 @@ "bundled": true, "requires": { "babel-runtime": "6.26.0", - "core-js": "2.5.6", - "regenerator-runtime": "0.10.5" - }, - "dependencies": {} + "core-js": "2.5.6" + } }, "babel-preset-env": { "version": "1.7.0", @@ -13712,8 +12978,7 @@ "requires": { "core-js": "2.5.6", "regenerator-runtime": "0.11.1" - }, - "dependencies": {} + } }, "babel-template": { "version": "6.26.0", @@ -13926,12 +13191,10 @@ "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": {} + } }, "browserify-rsa": { "version": "4.0.1", @@ -14103,12 +13366,10 @@ "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": {} + } }, "commander": { "version": "2.15.1", @@ -14300,10 +13561,8 @@ "bundled": true, "requires": { "acorn-node": "1.5.2", - "defined": "1.0.0", - "minimist": "1.2.0" - }, - "dependencies": {} + "defined": "1.0.0" + } }, "diff": { "version": "3.5.0", @@ -14483,11 +13742,7 @@ }, "findup-sync": { "version": "0.3.0", - "bundled": true, - "requires": { - "glob": "5.0.15" - }, - "dependencies": {} + "bundled": true }, "for-in": { "version": "1.0.2", @@ -14521,10 +13776,8 @@ "bundled": true, "optional": true, "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.10.0" - }, - "dependencies": {} + "nan": "2.10.0" + } }, "function-bind": { "version": "1.1.1", @@ -14610,8 +13863,6 @@ "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", @@ -14621,8 +13872,7 @@ "nopt": "3.0.6", "path-is-absolute": "1.0.1", "rimraf": "2.2.8" - }, - "dependencies": {} + } }, "grunt-babel": { "version": "6.0.0", @@ -14635,15 +13885,13 @@ "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": {} + } }, "grunt-contrib-concat": { "version": "1.0.1", @@ -14668,12 +13916,10 @@ "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": {} + } }, "grunt-known-options": { "version": "1.1.0", @@ -14693,10 +13939,8 @@ "version": "1.0.0", "bundled": true, "requires": { - "chalk": "1.1.3", - "lodash": "4.3.0" - }, - "dependencies": {} + "chalk": "1.1.3" + } }, "grunt-legacy-util": { "version": "1.0.0", @@ -14706,29 +13950,23 @@ "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": {} + } }, "grunt-newer": { "version": "1.3.0", "bundled": true, "requires": { - "async": "1.5.2", - "rimraf": "2.6.2" - }, - "dependencies": {} + "async": "1.5.2" + } }, "gzip-size": { "version": "1.0.0", "bundled": true, "requires": { - "browserify-zlib": "0.1.4", "concat-stream": "1.6.2" - }, - "dependencies": {} + } }, "has": { "version": "1.0.1", @@ -14807,10 +14045,8 @@ "requires": { "depd": "1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.5.0" - }, - "dependencies": {} + "setprototypeof": "1.1.0" + } }, "http-parser-js": { "version": "0.4.13", @@ -15016,10 +14252,8 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "isarray": "2.0.4", "stream-splicer": "2.0.0" - }, - "dependencies": {} + } }, "lazy-cache": { "version": "1.0.4", @@ -15101,14 +14335,12 @@ "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": {} + } }, "micromatch": { "version": "2.3.11", @@ -15173,17 +14405,14 @@ "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": {} + "mkdirp": "0.5.1" + } }, "module-deps": { "version": "6.1.0", @@ -15199,13 +14428,11 @@ "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": {} + } }, "ms": { "version": "2.0.0", @@ -15472,11 +14699,8 @@ "version": "3.0.0", "bundled": true, "requires": { - "is-number": "4.0.0", - "kind-of": "6.0.2", "math-random": "1.0.1" - }, - "dependencies": {} + } }, "randombytes": { "version": "2.0.6", @@ -15501,10 +14725,8 @@ "version": "1.1.7", "bundled": true, "requires": { - "bytes": "1.0.0", - "string_decoder": "0.10.31" - }, - "dependencies": {} + "bytes": "1.0.0" + } }, "read-only-stream": { "version": "2.0.0", @@ -15678,10 +14900,8 @@ "mime": "1.4.1", "ms": "2.0.0", "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" - }, - "dependencies": {} + "range-parser": "1.2.0" + } }, "serve-static": { "version": "1.13.2", @@ -15851,11 +15071,7 @@ }, "subarg": { "version": "1.0.0", - "bundled": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": {} + "bundled": true }, "supports-color": { "version": "2.0.0", @@ -15892,13 +15108,11 @@ "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": {} + } }, "to-arraybuffer": { "version": "1.0.1", @@ -15972,10 +15186,8 @@ "version": "0.11.0", "bundled": true, "requires": { - "punycode": "1.3.2", "querystring": "0.2.0" - }, - "dependencies": {} + } }, "user-home": { "version": "1.1.1", @@ -16067,12 +15279,10 @@ "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": {} + } } } }, diff --git a/package.json b/package.json index 0b66c8a4b..7e4395968 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", "standard-file-js": "0.3.2", - "sn-models": "0.1.0", + "sn-models": "file:~/Desktop/sn-models", "connect": "^3.6.6", "mocha": "^5.2.0", "serve-static": "^1.13.2", From 5b6f7246761371610c6767082eec075d606d704f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 19 Jul 2018 22:26:23 -0500 Subject: [PATCH 42/52] Fix text --- app/assets/templates/directives/session-history-menu.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/templates/directives/session-history-menu.html.haml b/app/assets/templates/directives/session-history-menu.html.haml index 46814a076..626a5cc0a 100644 --- a/app/assets/templates/directives/session-history-menu.html.haml +++ b/app/assets/templates/directives/session-history-menu.html.haml @@ -11,7 +11,7 @@ %menu-row{"label" => "'Clear all local history'", "ng-click" => "clearAllHistory(); $event.stopPropagation();"} %menu-row{"label" => "(diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'", "ng-click" => "toggleDiskSaving(); $event.stopPropagation();"} .sublabel - May increase app loading speed and memory footprint. + Saving to disk may decrease app loading speed and increase memory footprint. %menu-row{"ng-repeat" => "revision in history.revisions", "ng-click" => "openRevision(revision); $event.stopPropagation();", From 8eaa22eae39a256b25719c071a77e1639bb232a1 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 20 Jul 2018 09:30:57 -0500 Subject: [PATCH 43/52] Auto cleanup session history --- .../directives/views/sessionHistoryMenu.js | 9 ++ .../app/services/sessionHistory.js | 86 +++++++++++++++++-- .../directives/session-history-menu.html.haml | 5 +- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js index 859c32680..64e94bc5a 100644 --- a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js +++ b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js @@ -12,6 +12,7 @@ class SessionHistoryMenu { 'ngInject'; $scope.diskEnabled = sessionHistory.diskEnabled; + $scope.autoOptimize = sessionHistory.autoOptimize; $scope.reloadHistory = function() { $scope.history = sessionHistory.historyForItem($scope.item); @@ -65,6 +66,14 @@ class SessionHistoryMenu { }); } + $scope.toggleAutoOptimize = function() { + sessionHistory.toggleAutoOptimize().then(() => { + $timeout(() => { + $scope.autoOptimize = sessionHistory.autoOptimize; + }) + }); + } + } } diff --git a/app/assets/javascripts/app/services/sessionHistory.js b/app/assets/javascripts/app/services/sessionHistory.js index dd45f0c04..867c1ebb7 100644 --- a/app/assets/javascripts/app/services/sessionHistory.js +++ b/app/assets/javascripts/app/services/sessionHistory.js @@ -1,3 +1,7 @@ +const SessionHistoryPersistKey = "sessionHistory_persist"; +const SessionHistoryRevisionsKey = "sessionHistory_revisions"; +const SessionHistoryAutoOptimizeKey = "sessionHistory_autoOptimize"; + class SessionHistory { constructor(modelManager, storageManager, authManager, passcodeManager, $timeout) { @@ -45,17 +49,32 @@ class SessionHistory { async clearAllHistory() { this.historyContainer.clearAllHistory(); - return this.storageManager.removeItem("sessionHistory"); + return this.storageManager.removeItem(SessionHistoryRevisionsKey); } async toggleDiskSaving() { this.diskEnabled = !this.diskEnabled; if(this.diskEnabled) { - this.storageManager.setItem("persistSessionHistory", JSON.stringify(true)); + this.storageManager.setItem(SessionHistoryPersistKey, JSON.stringify(true)); this.saveToDisk(); } else { - this.storageManager.removeItem("persistSessionHistory"); + this.storageManager.setItem(SessionHistoryPersistKey, JSON.stringify(false)); + return this.storageManager.removeItem(SessionHistoryRevisionsKey); + } + } + + get autoOptimize() { + return this.historyContainer.autoOptimize; + } + + async toggleAutoOptimize() { + this.historyContainer.autoOptimize = !this.historyContainer.autoOptimize; + + if(this.historyContainer.autoOptimize) { + this.storageManager.setItem(SessionHistoryAutoOptimizeKey, JSON.stringify(true)); + } else { + this.storageManager.setItem(SessionHistoryAutoOptimizeKey, JSON.stringify(false)); } } @@ -66,18 +85,18 @@ class SessionHistory { let encryptionParams = await this.encryptionParams(); var itemParams = new SFItemParams(this.historyContainer, encryptionParams.keys, encryptionParams.auth_params); itemParams.paramsForSync().then((syncParams) => { - console.log("Saving to disk", syncParams); - this.storageManager.setItem("sessionHistory", JSON.stringify(syncParams)); + // console.log("Saving to disk", syncParams); + this.storageManager.setItem(SessionHistoryRevisionsKey, JSON.stringify(syncParams)); }) } async loadFromDisk() { - var diskValue = await this.storageManager.getItem("persistSessionHistory"); + var diskValue = await this.storageManager.getItem(SessionHistoryPersistKey); if(diskValue) { this.diskEnabled = JSON.parse(diskValue); } - var historyValue = await this.storageManager.getItem("sessionHistory"); + var historyValue = await this.storageManager.getItem(SessionHistoryRevisionsKey); if(historyValue) { historyValue = JSON.parse(historyValue); let encryptionParams = await this.encryptionParams(); @@ -87,6 +106,18 @@ class SessionHistory { } else { this.historyContainer = new HistoryContainer(); } + + var autoOptimizeValue = await this.storageManager.getItem(SessionHistoryAutoOptimizeKey); + if(autoOptimizeValue) { + this.historyContainer.autoOptimize = JSON.parse(autoOptimizeValue); + } else { + // default value is true + this.historyContainer.autoOptimize = true; + } + } + + async optimize() { + return this.historyContainer.optimize(); } } @@ -110,7 +141,7 @@ class HistoryContainer extends SFItem { this.content.itemsDictionary[item.uuid] = new ItemHistory(); } var itemHistory = this.content.itemsDictionary[item.uuid]; - return itemHistory.addRevision(item); + return itemHistory.addRevision(item, this.autoOptimize); } historyForItem(item) { @@ -124,6 +155,14 @@ class HistoryContainer extends SFItem { clearAllHistory() { this.content.itemsDictionary = {}; } + + optimize() { + var objectKeys = Object.keys(this.content.itemsDictionary); + objectKeys.forEach((key) => { + var itemHistory = this.content.itemsDictionary[key]; + itemHistory.optimize(); + }); + } } class ItemHistory { @@ -132,6 +171,7 @@ class ItemHistory { if(!this.revisions) { this.revisions = []; } + if(json_obj.revisions) { for(var revision of json_obj.revisions) { this.revisions.push(new NoteRevision(revision, this.revisions[this.revisions.length - 1], revision.date)); @@ -139,16 +179,44 @@ class ItemHistory { } } - addRevision(item) { + addRevision(item, autoOptimize) { var previousRevision = this.revisions[this.revisions.length - 1]; var prospectiveRevision = new NoteRevision(item, previousRevision, item.updated_at); + + // Don't add first revision if text length is 0, as this means it's a new note. + // Actually, we'll skip this. If we do this, the first character added to a new note + // will be displayed as "1 characters loaded" + // if(!previousRevision && prospectiveRevision.textCharDiffLength == 0) { + // return; + // } + + // Don't add if text is the same if(prospectiveRevision.isSameAsRevision(previousRevision)) { return; } + this.revisions.push(prospectiveRevision); + + // Clean up if there are too many revisions + const LargeRevisionAmount = 100; + if(autoOptimize && this.revisions.length > LargeRevisionAmount) { + this.optimize(); + } + return prospectiveRevision; } + optimize() { + const SmallRevisionLength = 15; + this.revisions = this.revisions.filter((revision, index) => { + // Keep only first and last item and items whos diff length is greater than the small revision length. + var isFirst = index == 0; + var isLast = index == this.revisions.length - 1; + var isSmallRevision = Math.abs(revision.textCharDiffLength) < SmallRevisionLength; + return isFirst || isLast || !isSmallRevision; + }) + } + } class ItemRevision { diff --git a/app/assets/templates/directives/session-history-menu.html.haml b/app/assets/templates/directives/session-history-menu.html.haml index 626a5cc0a..09cfa4e90 100644 --- a/app/assets/templates/directives/session-history-menu.html.haml +++ b/app/assets/templates/directives/session-history-menu.html.haml @@ -9,9 +9,12 @@ %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 decrease app loading speed and increase memory footprint. + Saving to disk may increase app loading time and memory footprint. %menu-row{"ng-repeat" => "revision in history.revisions", "ng-click" => "openRevision(revision); $event.stopPropagation();", From 0720e71637063ed1855d78a9d7ddf6c11761ae9d Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 20 Jul 2018 09:34:09 -0500 Subject: [PATCH 44/52] Package --- app/assets/javascripts/app/controllers/editor.js | 2 -- package.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 922115c29..8ebfb10f1 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -24,8 +24,6 @@ angular.module('app') }) .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory) { - this.showSessionHistory = true; - this.spellcheck = true; this.componentManager = componentManager; diff --git a/package.json b/package.json index 7e4395968..0b66c8a4b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", "standard-file-js": "0.3.2", - "sn-models": "file:~/Desktop/sn-models", + "sn-models": "0.1.0", "connect": "^3.6.6", "mocha": "^5.2.0", "serve-static": "^1.13.2", From bc28ad78dea8fa3d216774a52264ece6932045bf Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 20 Jul 2018 09:54:33 -0500 Subject: [PATCH 45/52] Remote revision history use item preview modal --- .../javascripts/app/directives/views/actionsMenu.js | 10 +--------- .../app/directives/views/revisionPreviewModal.js | 6 ++++-- .../javascripts/app/services/sessionHistory.js | 2 +- .../templates/directives/actions-menu.html.haml | 12 ------------ .../directives/session-history-menu.html.haml | 11 ----------- 5 files changed, 6 insertions(+), 35 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js index 846f8d950..7e97d868f 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); } } } @@ -77,8 +71,6 @@ class ActionsMenu { } }) } - - } } diff --git a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js index b83c7067e..40cf3034b 100644 --- a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js +++ b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js @@ -32,9 +32,11 @@ class RevisionPreviewModal { item = modelManager.createItem({content_type: "Note", content: contentCopy}); modelManager.addItem(item); } else { - item = modelManager.findItem($scope.revision.itemUuid); + // revision can be an ItemRevision revision object or just a plain SFItem + var uuid = $scope.revision.uuid; + item = modelManager.findItem(uuid); item.content = Object.assign({}, $scope.revision.content); - item.mapContentToLocalProperties(item.content); + modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved); } item.setDirty(true); syncManager.sync(); diff --git a/app/assets/javascripts/app/services/sessionHistory.js b/app/assets/javascripts/app/services/sessionHistory.js index 867c1ebb7..e20114fe9 100644 --- a/app/assets/javascripts/app/services/sessionHistory.js +++ b/app/assets/javascripts/app/services/sessionHistory.js @@ -227,7 +227,7 @@ class ItemRevision { } else { this.date = date; } - this.itemUuid = item.uuid; + this.uuid = item.uuid; this.hasPreviousRevision = previousRevision != null; this.content = Object.assign({}, item.content); } diff --git a/app/assets/templates/directives/actions-menu.html.haml b/app/assets/templates/directives/actions-menu.html.haml index 18bd4fae0..3596b7e92 100644 --- a/app/assets/templates/directives/actions-menu.html.haml +++ b/app/assets/templates/directives/actions-menu.html.haml @@ -20,15 +20,3 @@ access to this note. %menu-row{"ng-if" => "extension.actionsWithContextForItem(item).length == 0", "label" => "'No Actions Available'", "faded" => "true"} - - -.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}} diff --git a/app/assets/templates/directives/session-history-menu.html.haml b/app/assets/templates/directives/session-history-menu.html.haml index 09cfa4e90..d4556896c 100644 --- a/app/assets/templates/directives/session-history-menu.html.haml +++ b/app/assets/templates/directives/session-history-menu.html.haml @@ -21,14 +21,3 @@ "label" => "revision.previewTitle()"} .sublabel.opaque{"ng-class" => "classForRevision(revision)"} {{revision.previewSubTitle()}} - -.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}} From 0441087ca01cd4eceaa5f27ad03cec054f3b15e7 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 20 Jul 2018 16:08:23 -0500 Subject: [PATCH 46/52] Refactor session history into SFJS --- Gruntfile.js | 1 + .../app/directives/views/actionsMenu.js | 2 +- .../directives/views/revisionPreviewModal.js | 15 +- .../directives/views/sessionHistoryMenu.js | 2 +- .../app/models/noteHistoryEntry.js | 37 +++ .../app/services/actionsManager.js | 7 +- .../app/services/sessionHistory.js | 282 +----------------- .../revision-preview-modal.html.haml | 4 +- .../directives/session-history-menu.html.haml | 4 +- package.json | 2 +- 10 files changed, 68 insertions(+), 288 deletions(-) create mode 100644 app/assets/javascripts/app/models/noteHistoryEntry.js diff --git a/Gruntfile.js b/Gruntfile.js index 39a79854f..f3afa8c4a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -72,6 +72,7 @@ module.exports = function(grunt) { src: [ 'node_modules/sn-models/dist/sn-models.js', 'app/assets/javascripts/app/*.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', diff --git a/app/assets/javascripts/app/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js index 7e97d868f..62cfece78 100644 --- a/app/assets/javascripts/app/directives/views/actionsMenu.js +++ b/app/assets/javascripts/app/directives/views/actionsMenu.js @@ -49,7 +49,7 @@ class ActionsMenu { switch (action.verb) { case "render": { var item = response.item; - actionsManager.presentRevisionPreviewModal(item); + actionsManager.presentRevisionPreviewModal(item.uuid, item.content); } } } diff --git a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js index 40cf3034b..d5d6313d3 100644 --- a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js +++ b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js @@ -4,9 +4,8 @@ class RevisionPreviewModal { this.restrict = "E"; this.templateUrl = "directives/revision-preview-modal.html"; this.scope = { - revision: "=", - show: "=", - callback: "=" + uuid: "=", + content: "=" }; } @@ -27,25 +26,23 @@ class RevisionPreviewModal { var item; if(asCopy) { - var contentCopy = Object.assign({}, $scope.revision.content); + var contentCopy = Object.assign({}, $scope.content); if(contentCopy.title) { contentCopy.title += " (copy)"; } item = modelManager.createItem({content_type: "Note", content: contentCopy}); modelManager.addItem(item); } else { - // revision can be an ItemRevision revision object or just a plain SFItem - var uuid = $scope.revision.uuid; + var uuid = $scope.uuid; item = modelManager.findItem(uuid); - item.content = Object.assign({}, $scope.revision.content); + 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 index 64e94bc5a..4522b9abb 100644 --- a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js +++ b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js @@ -20,7 +20,7 @@ class SessionHistoryMenu { $scope.reloadHistory(); $scope.openRevision = function(revision) { - actionsManager.presentRevisionPreviewModal(revision); + actionsManager.presentRevisionPreviewModal(revision.item.uuid, revision.item.content); } $scope.classForRevision = function(revision) { diff --git a/app/assets/javascripts/app/models/noteHistoryEntry.js b/app/assets/javascripts/app/models/noteHistoryEntry.js new file mode 100644 index 000000000..922388b86 --- /dev/null +++ b/app/assets/javascripts/app/models/noteHistoryEntry.js @@ -0,0 +1,37 @@ +class NoteHistoryEntry extends SFItemHistoryEntry { + + setPreviousEntry(previousEntry) { + super.setPreviousEntry(previousEntry); + if(previousEntry) { + this.textCharDiffLength = this.item.content.text.length - previousEntry.item.content.text.length; + } else { + this.textCharDiffLength = this.item.content.text.length; + } + } + + previewTitle() { + return this.item.updated_at.toLocaleString(); + } + + operationVector() { + if(!this.hasPreviousEntry || this.textCharDiffLength == 0) { + return 0; + } else if(this.textCharDiffLength < 0) { + return -1; + } else { + return 1; + } + } + + 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 7790262cf..1953907c3 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -198,10 +198,11 @@ class ActionsManager { }) } - presentRevisionPreviewModal(revision) { + presentRevisionPreviewModal(uuid, content) { var scope = this.$rootScope.$new(true); - scope.revision = revision; - var el = this.$compile( "" )(scope); + scope.uuid = uuid; + scope.content = content; + var el = this.$compile( "" )(scope); angular.element(document.body).append(el); } diff --git a/app/assets/javascripts/app/services/sessionHistory.js b/app/assets/javascripts/app/services/sessionHistory.js index e20114fe9..d64dec37f 100644 --- a/app/assets/javascripts/app/services/sessionHistory.js +++ b/app/assets/javascripts/app/services/sessionHistory.js @@ -1,280 +1,24 @@ -const SessionHistoryPersistKey = "sessionHistory_persist"; -const SessionHistoryRevisionsKey = "sessionHistory_revisions"; -const SessionHistoryAutoOptimizeKey = "sessionHistory_autoOptimize"; - -class SessionHistory { +class SessionHistory extends SFSessionHistoryManager { constructor(modelManager, storageManager, authManager, passcodeManager, $timeout) { - this.modelManager = modelManager; - this.storageManager = storageManager; - this.authManager = authManager; - this.passcodeManager = passcodeManager; - this.$timeout = $timeout; - this.loadFromDisk().then(() => { - this.modelManager.addItemSyncObserver("session-history", "Note", (allItems, validItems, deletedItems, source, sourceKey) => { - for(let item of allItems) { - this.addRevision(item); - } - }); - }) - } - - async encryptionParams() { - let offline = this.authManager.offline(); - let auth_params = offline ? this.passcodeManager.passcodeAuthParams() : await this.authManager.getAuthParams(); - let keys = offline ? this.passcodeManager.keys() : await this.authManager.keys(); - return {keys, auth_params}; - } - - addRevision(item) { - var added = this.historyContainer.addRevision(item); - - if(added) { - if(this.diskTimeout) {this.$timeout.cancel(this.diskTimeout)}; - this.diskTimeout = this.$timeout(() => { - this.saveToDisk(); - }, 1000) - } - } - - historyForItem(item) { - return this.historyContainer.historyForItem(item); - } - - async clearItemHistory(item) { - delete this.historyContainer.clearItemHistory(item); - return this.saveToDisk(); - } - - async clearAllHistory() { - this.historyContainer.clearAllHistory(); - return this.storageManager.removeItem(SessionHistoryRevisionsKey); - } - - async toggleDiskSaving() { - this.diskEnabled = !this.diskEnabled; - - if(this.diskEnabled) { - this.storageManager.setItem(SessionHistoryPersistKey, JSON.stringify(true)); - this.saveToDisk(); - } else { - this.storageManager.setItem(SessionHistoryPersistKey, JSON.stringify(false)); - return this.storageManager.removeItem(SessionHistoryRevisionsKey); - } - } - - get autoOptimize() { - return this.historyContainer.autoOptimize; - } - - async toggleAutoOptimize() { - this.historyContainer.autoOptimize = !this.historyContainer.autoOptimize; - - if(this.historyContainer.autoOptimize) { - this.storageManager.setItem(SessionHistoryAutoOptimizeKey, JSON.stringify(true)); - } else { - this.storageManager.setItem(SessionHistoryAutoOptimizeKey, JSON.stringify(false)); - } - } - - async saveToDisk() { - if(!this.diskEnabled) { - return; - } - let encryptionParams = await this.encryptionParams(); - var itemParams = new SFItemParams(this.historyContainer, encryptionParams.keys, encryptionParams.auth_params); - itemParams.paramsForSync().then((syncParams) => { - // console.log("Saving to disk", syncParams); - this.storageManager.setItem(SessionHistoryRevisionsKey, JSON.stringify(syncParams)); - }) - } - - async loadFromDisk() { - var diskValue = await this.storageManager.getItem(SessionHistoryPersistKey); - if(diskValue) { - this.diskEnabled = JSON.parse(diskValue); + SFItemHistory.HistoryEntryClassMapping = { + "Note" : NoteHistoryEntry } - var historyValue = await this.storageManager.getItem(SessionHistoryRevisionsKey); - if(historyValue) { - historyValue = JSON.parse(historyValue); - let encryptionParams = await this.encryptionParams(); - await SFJS.itemTransformer.decryptItem(historyValue, encryptionParams.keys); - var historyContainer = new HistoryContainer(historyValue); - this.historyContainer = historyContainer; - } else { - this.historyContainer = new HistoryContainer(); - } - - var autoOptimizeValue = await this.storageManager.getItem(SessionHistoryAutoOptimizeKey); - if(autoOptimizeValue) { - this.historyContainer.autoOptimize = JSON.parse(autoOptimizeValue); - } else { - // default value is true - this.historyContainer.autoOptimize = true; - } - } - - async optimize() { - return this.historyContainer.optimize(); - } -} - -class HistoryContainer extends SFItem { - constructor(json_obj) { - super(json_obj); - - if(!this.content.itemsDictionary) { - this.content.itemsDictionary = {}; - } - - var objectKeys = Object.keys(this.content.itemsDictionary); - objectKeys.forEach((key) => { - var value = this.content.itemsDictionary[key]; - this.content.itemsDictionary[key] = new ItemHistory(value); - }); - } - - addRevision(item) { - if(!this.content.itemsDictionary[item.uuid]) { - this.content.itemsDictionary[item.uuid] = new ItemHistory(); - } - var itemHistory = this.content.itemsDictionary[item.uuid]; - return itemHistory.addRevision(item, this.autoOptimize); - } - - historyForItem(item) { - return this.content.itemsDictionary[item.uuid]; - } - - clearItemHistory(item) { - delete this.content.itemsDictionary[item.uuid]; - } - - clearAllHistory() { - this.content.itemsDictionary = {}; - } - - optimize() { - var objectKeys = Object.keys(this.content.itemsDictionary); - objectKeys.forEach((key) => { - var itemHistory = this.content.itemsDictionary[key]; - itemHistory.optimize(); - }); - } -} - -class ItemHistory { - - constructor(json_obj = {}) { - if(!this.revisions) { - this.revisions = []; - } - - if(json_obj.revisions) { - for(var revision of json_obj.revisions) { - this.revisions.push(new NoteRevision(revision, this.revisions[this.revisions.length - 1], revision.date)); + 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 } } - } - addRevision(item, autoOptimize) { - var previousRevision = this.revisions[this.revisions.length - 1]; - var prospectiveRevision = new NoteRevision(item, previousRevision, item.updated_at); - - // Don't add first revision if text length is 0, as this means it's a new note. - // Actually, we'll skip this. If we do this, the first character added to a new note - // will be displayed as "1 characters loaded" - // if(!previousRevision && prospectiveRevision.textCharDiffLength == 0) { - // return; - // } - - // Don't add if text is the same - if(prospectiveRevision.isSameAsRevision(previousRevision)) { - return; - } - - this.revisions.push(prospectiveRevision); - - // Clean up if there are too many revisions - const LargeRevisionAmount = 100; - if(autoOptimize && this.revisions.length > LargeRevisionAmount) { - this.optimize(); - } - - return prospectiveRevision; - } - - optimize() { - const SmallRevisionLength = 15; - this.revisions = this.revisions.filter((revision, index) => { - // Keep only first and last item and items whos diff length is greater than the small revision length. - var isFirst = index == 0; - var isLast = index == this.revisions.length - 1; - var isSmallRevision = Math.abs(revision.textCharDiffLength) < SmallRevisionLength; - return isFirst || isLast || !isSmallRevision; - }) - } - -} - -class ItemRevision { - - constructor(item, previousRevision, date) { - if(typeof(date) == "string") { - this.date = new Date(date); - } else { - this.date = date; - } - this.uuid = item.uuid; - this.hasPreviousRevision = previousRevision != null; - this.content = Object.assign({}, item.content); - } - - isSameAsRevision(revision) { - if(!revision) { - return false; - } - return JSON.stringify(this.content) === JSON.stringify(revision.content); - } - -} - -class NoteRevision extends ItemRevision { - constructor(item, previousRevision, date) { - super(item, previousRevision, date); - if(previousRevision) { - this.textCharDiffLength = this.content.text.length - previousRevision.content.text.length; - } else { - this.textCharDiffLength = this.content.text.length; - } - } - - previewTitle() { - return this.date.toLocaleString(); - } - - operationVector() { - if(!this.hasPreviousRevision || this.textCharDiffLength == 0) { - return 0; - } else if(this.textCharDiffLength < 0) { - return -1; - } else { - return 1; - } - } - - previewSubTitle() { - if(!this.hasPreviousRevision) { - 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" - } + var contentTypes = ["Note"]; + super(modelManager, storageManager, keyRequestHandler, contentTypes, $timeout); } } diff --git a/app/assets/templates/directives/revision-preview-modal.html.haml b/app/assets/templates/directives/revision-preview-modal.html.haml index 1e9b5f107..df37fb182 100644 --- a/app/assets/templates/directives/revision-preview-modal.html.haml +++ b/app/assets/templates/directives/revision-preview-modal.html.haml @@ -9,5 +9,5 @@ %a.close-button.info{"ng-click" => "restore(true)"} Restore as copy %a.close-button.info{"ng-click" => "dismiss(); $event.stopPropagation()"} Close .content.selectable - %h2 {{revision.content.title}} - %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{revision.content.text}} + %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 index d4556896c..244ee9ba6 100644 --- a/app/assets/templates/directives/session-history-menu.html.haml +++ b/app/assets/templates/directives/session-history-menu.html.haml @@ -2,7 +2,7 @@ .menu-panel.dropdown-menu .header .column - %h4.title {{history.revisions.length || 'No'}} revisions + %h4.title {{history.entries.length || 'No'}} revisions %h4{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"} %a Options @@ -16,7 +16,7 @@ .sublabel Saving to disk may increase app loading time and memory footprint. - %menu-row{"ng-repeat" => "revision in history.revisions", + %menu-row{"ng-repeat" => "revision in history.entries", "ng-click" => "openRevision(revision); $event.stopPropagation();", "label" => "revision.previewTitle()"} .sublabel.opaque{"ng-class" => "classForRevision(revision)"} diff --git a/package.json b/package.json index 0b66c8a4b..e0081141c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", - "standard-file-js": "0.3.2", + "standard-file-js": "file:~/Desktop/sf/sfjs", "sn-models": "0.1.0", "connect": "^3.6.6", "mocha": "^5.2.0", From 8a8d37dd43199008e1e5eb07a91f477b932f9b53 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 20 Jul 2018 19:12:14 -0500 Subject: [PATCH 47/52] Component save-item make sure it exists --- app/assets/javascripts/app/controllers/editor.js | 2 +- app/assets/javascripts/app/services/componentManager.js | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 8ebfb10f1..bbf5c2354 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -23,7 +23,7 @@ angular.module('app') } }) .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory) { - + this.spellcheck = true; this.componentManager = componentManager; diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 4abcbc739..5de8eadaf 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -481,8 +481,13 @@ class ComponentManager { */ 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.getClientDataKey(), responseItem.clientData, ComponentManager.ClientDataDomain); From a8d59700edb92dfeadc641d1e11c09a3679181fb Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 20 Jul 2018 19:47:57 -0500 Subject: [PATCH 48/52] Theme layerable func --- app/assets/javascripts/app/services/componentManager.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 5de8eadaf..8a7ee9383 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -594,11 +594,11 @@ class ComponentManager { if(targetComponent.active) { this.deactivateComponent(targetComponent); } else { - if(targetComponent.content_type == "SN|Theme" && !targetComponent.package_info.layerable) { + 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.package_info.layerable) { + if(theme && !theme.isLayerable()) { this.deactivateComponent(theme); } } diff --git a/package.json b/package.json index e0081141c..7748a0629 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", "standard-file-js": "file:~/Desktop/sf/sfjs", - "sn-models": "0.1.0", + "sn-models": "0.1.1", "connect": "^3.6.6", "mocha": "^5.2.0", "serve-static": "^1.13.2", From 4503c51c326c14c6fbb16e3e37e44a2250aac415 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 20 Jul 2018 19:56:40 -0500 Subject: [PATCH 49/52] Change New Note to Note --- app/assets/javascripts/app/controllers/notes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index c78e881c6..1ab060248 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -219,7 +219,9 @@ angular.module('app') } this.createNewNote = function() { - var title = "New Note" + (this.tag.notes ? (" " + (this.visibleNotes().length + 1)) : ""); + // 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; From cff5fbf72fefe541d2780788d03b524276c7eeb4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 21 Jul 2018 08:43:43 -0500 Subject: [PATCH 50/52] Select all tag after deleting tag --- app/assets/javascripts/app/controllers/home.js | 1 - app/assets/javascripts/app/controllers/tags.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 597f276c7..2ed1ee0df 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -206,7 +206,6 @@ 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().then(() => { // force scope tags to update on sub directives $scope.safeApply(); diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js index bb9149f89..2017b7679 100644 --- a/app/assets/javascripts/app/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -158,6 +158,7 @@ angular.module('app') this.selectedDeleteTag = function(tag) { this.removeTag()(tag); + this.selectTag(this.allTag); } this.noteCount = function(tag) { From 47f1d688741a4492f703b87b9b56182502407697 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 21 Jul 2018 08:52:06 -0500 Subject: [PATCH 51/52] Packages --- package-lock.json | 2971 +-------------------------------------------- package.json | 2 +- 2 files changed, 4 insertions(+), 2969 deletions(-) diff --git a/package-lock.json b/package-lock.json index 699858226..cc02c8ca2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5661,7 +5661,7 @@ "version": "file:../../sn-models", "dev": true, "requires": { - "standard-file-js": "file:../../sf/sfjs" + "standard-file-js": "0.3.2" }, "dependencies": { "JSONStream": { @@ -8811,2974 +8811,9 @@ "bundled": true }, "standard-file-js": { - "version": "file:../../sf/sfjs", + "version": "0.3.2", "bundled": true, - "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" - } - } - } + "dev": true }, "stream-browserify": { "version": "2.0.1", diff --git a/package.json b/package.json index 7748a0629..8b1cbaa48 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", - "standard-file-js": "file:~/Desktop/sf/sfjs", + "standard-file-js": "0.3.3", "sn-models": "0.1.1", "connect": "^3.6.6", "mocha": "^5.2.0", From ebef028096fbf59da4852081dace1446142d0652 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 22 Jul 2018 18:52:18 -0500 Subject: [PATCH 52/52] SFJS 0.3.4 --- .../app/models/noteHistoryEntry.js | 19 ------------------- package.json | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/app/assets/javascripts/app/models/noteHistoryEntry.js b/app/assets/javascripts/app/models/noteHistoryEntry.js index 922388b86..38414d88f 100644 --- a/app/assets/javascripts/app/models/noteHistoryEntry.js +++ b/app/assets/javascripts/app/models/noteHistoryEntry.js @@ -1,28 +1,9 @@ class NoteHistoryEntry extends SFItemHistoryEntry { - setPreviousEntry(previousEntry) { - super.setPreviousEntry(previousEntry); - if(previousEntry) { - this.textCharDiffLength = this.item.content.text.length - previousEntry.item.content.text.length; - } else { - this.textCharDiffLength = this.item.content.text.length; - } - } - previewTitle() { return this.item.updated_at.toLocaleString(); } - operationVector() { - if(!this.hasPreviousEntry || this.textCharDiffLength == 0) { - return 0; - } else if(this.textCharDiffLength < 0) { - return -1; - } else { - return 1; - } - } - previewSubTitle() { if(!this.hasPreviousEntry) { return `${this.textCharDiffLength} characters loaded` diff --git a/package.json b/package.json index 8b1cbaa48..b174e55b0 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", - "standard-file-js": "0.3.3", + "standard-file-js": "0.3.4", "sn-models": "0.1.1", "connect": "^3.6.6", "mocha": "^5.2.0",