From 81b392bdaa5e1ad637d71cdb273873d7a0ba19b8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 21 Dec 2017 12:31:56 -0600 Subject: [PATCH 01/99] Wip --- .../directives/views/globalExtensionsMenu.js | 8 ++++- .../app/services/directives/views/roomBar.js | 28 +++++++++++++++ .../app/services/packageManager.js | 35 +++++++++++++++++++ app/assets/stylesheets/app/_footer.scss | 14 +++++++- .../frontend/directives/room-bar.html.haml | 2 ++ .../templates/frontend/footer.html.haml | 2 ++ 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/app/services/directives/views/roomBar.js create mode 100644 app/assets/javascripts/app/services/packageManager.js create mode 100644 app/assets/templates/frontend/directives/room-bar.html.haml diff --git a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js index f046d48d5..c5780abfe 100644 --- a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js @@ -7,7 +7,7 @@ class GlobalExtensionsMenu { }; } - controller($scope, extensionManager, syncManager, modelManager, themeManager, componentManager) { + controller($scope, extensionManager, syncManager, modelManager, themeManager, componentManager, packageManager) { 'ngInject'; $scope.formData = {}; @@ -176,6 +176,8 @@ class GlobalExtensionsMenu { $scope.handleThemeLink(link, completion); } else if(type == "component") { $scope.handleComponentLink(link, completion); + } else if(type == "package") { + $scope.handlePackageLink(link, completion); } else { @@ -184,6 +186,10 @@ class GlobalExtensionsMenu { } } + $scope.handlePackageLink = function(link, completion) { + packageManager.installPackage(link, completion); + } + $scope.handleSyncAdapterLink = function(link, completion) { var params = parametersFromURL(link); params["url"] = link; diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js new file mode 100644 index 000000000..27ca308cc --- /dev/null +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -0,0 +1,28 @@ +class RoomBar { + + constructor() { + this.restrict = "E"; + this.templateUrl = "frontend/directives/room-bar.html"; + this.scope = { + }; + } + + controller($rootScope, $scope, extensionManager, syncManager, modelManager, componentManager, $timeout) { + 'ngInject'; + + $scope.extensionManager = extensionManager; + $scope.componentManager = componentManager; + + $rootScope.$on("initial-data-loaded", () => { + $timeout(() => { + $scope.rooms = componentManager.componentsForArea("rooms"); + console.log("Rooms:", $scope.rooms); + }) + }); + + } + + +} + +angular.module('app.frontend').directive('roomBar', () => new RoomBar); diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js new file mode 100644 index 000000000..d3a16666f --- /dev/null +++ b/app/assets/javascripts/app/services/packageManager.js @@ -0,0 +1,35 @@ +class PackageManager { + + constructor(httpManager, modelManager, syncManager) { + this.httpManager = httpManager; + this.modelManager = modelManager; + this.syncManager = syncManager; + } + + + installPackage(url, callback) { + this.httpManager.getAbsolute(url, {}, function(aPackage){ + console.log("Got package data", aPackage); + if(typeof aPackage !== 'object') { + callback(null); + return; + } + + var assembled = this.modelManager.createItem(aPackage); + this.modelManager.addItem(assembled); + assembled.setDirty(true); + this.syncManager.sync(); + + console.log("Created assembled", assembled); + + callback && callback(assembled); + }.bind(this), function(response){ + console.error("Error retrieving package", response); + callback(null); + }) + } + + +} + +angular.module('app.frontend').service('packageManager', PackageManager); diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index ac85969c9..e3e856325 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -50,7 +50,7 @@ h2 { font-weight: bold; margin-bottom: 4px; } - + h1 { font-size: 16px; } @@ -83,6 +83,18 @@ h2 { // margin-bottom: 10px; border-radius: 0px; } + + #room-bar { + display: inline-block; + border-left: 1px solid gray; + padding-left: 10px; + padding-right: 10px; + margin-left: 5px; + + .room-item { + display: inline-block; + } + } } #footer-bar .footer-bar-link { diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml new file mode 100644 index 000000000..2aa40b9c9 --- /dev/null +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -0,0 +1,2 @@ +.room-item{"ng-repeat" => "room in rooms"} + {{room.name}} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index 1f7506f45..b0943a8f9 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -12,6 +12,8 @@ %a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help + %room-bar#room-bar + .pull-right .footer-bar-link{"ng-if" => "ctrl.newUpdateAvailable", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"} From 2bc52578e0eb029e4e466e7ba4d75a7c220a36cb Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 21 Dec 2017 12:52:10 -0600 Subject: [PATCH 02/99] Wip --- .../javascripts/app/services/directives/views/roomBar.js | 4 ++++ app/assets/templates/frontend/directives/room-bar.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index 27ca308cc..53169c393 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -20,6 +20,10 @@ class RoomBar { }) }); + $scope.selectRoom = function(room) { + + } + } diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index 2aa40b9c9..fbb5ecad3 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,2 +1,2 @@ -.room-item{"ng-repeat" => "room in rooms"} +.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"} {{room.name}} From 0c31668625991008cce48bcb1df3937ed845fc6f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 23 Dec 2017 11:25:08 -0600 Subject: [PATCH 03/99] Wip --- .../app/frontend/controllers/tags.js | 2 + .../app/frontend/models/app/component.js | 5 ++ .../app/frontend/models/app/theme.js | 2 + .../app/services/componentManager.js | 53 ++++++++++++++----- .../app/services/desktopManager.js | 43 +++++++++++++++ .../services/directives/views/editorMenu.js | 6 +++ .../app/services/directives/views/roomBar.js | 45 ++++++++++++++-- app/assets/stylesheets/app/_footer.scss | 23 ++++++++ app/assets/stylesheets/app/_menus.scss | 7 ++- app/assets/stylesheets/app/_ui.scss | 16 +++--- .../frontend/directives/editor-menu.html.haml | 4 +- .../frontend/directives/room-bar.html.haml | 4 +- .../templates/frontend/editor.html.haml | 6 +-- app/assets/templates/frontend/tags.html.haml | 2 +- 14 files changed, 186 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/app/frontend/controllers/tags.js b/app/assets/javascripts/app/frontend/controllers/tags.js index abad7e59f..e7f686b8e 100644 --- a/app/assets/javascripts/app/frontend/controllers/tags.js +++ b/app/assets/javascripts/app/frontend/controllers/tags.js @@ -38,6 +38,8 @@ angular.module('app.frontend') var initialLoad = true; + this.componentManager = componentManager; + componentManager.registerHandler({identifier: "tags", areas: ["tags-list"], activationHandler: function(component){ this.component = component; diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index ac0c30d0e..202073279 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -21,11 +21,14 @@ class Component extends Item { this.url = content.url; this.name = content.name; + 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; this.active = content.active; + this.local = content.local; // custom data that a component can store in itself this.componentData = content.componentData || {}; @@ -42,8 +45,10 @@ class Component extends Item { url: this.url, name: this.name, area: this.area, + package_info: this.package_info, permissions: this.permissions, active: this.active, + local: this.local, componentData: this.componentData, disassociatedItemIds: this.disassociatedItemIds, associatedItemIds: this.associatedItemIds, diff --git a/app/assets/javascripts/app/frontend/models/app/theme.js b/app/assets/javascripts/app/frontend/models/app/theme.js index ec7486012..d69eaad76 100644 --- a/app/assets/javascripts/app/frontend/models/app/theme.js +++ b/app/assets/javascripts/app/frontend/models/app/theme.js @@ -8,12 +8,14 @@ class Theme extends Item { super.mapContentToLocalProperties(content) this.url = content.url; this.name = content.name; + this.local = content.local; } structureParams() { var params = { url: this.url, name: this.name, + local: this.local }; _.merge(params, super.structureParams()); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index c22cdbd85..049e05c75 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -3,12 +3,13 @@ let ClientDataDomain = "org.standardnotes.sn.components"; class ComponentManager { - constructor($rootScope, modelManager, syncManager, themeManager, $timeout, $compile) { + constructor($rootScope, modelManager, syncManager, desktopManager, themeManager, $timeout, $compile) { this.$compile = $compile; this.$rootScope = $rootScope; this.modelManager = modelManager; this.syncManager = syncManager; this.themeManager = themeManager; + this.desktopManager = desktopManager; this.timeout = $timeout; this.streamObservers = []; this.contextStreamObservers = []; @@ -42,6 +43,10 @@ class ComponentManager { } var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" }); + + // Ensure any component in our data is installed by the system + this.desktopManager.syncComponentsInstallation(syncedComponents); + for(var component of syncedComponents) { var activeComponent = _.find(this.activeComponents, {uuid: component.uuid}); if(component.active && !component.deleted && !activeComponent) { @@ -191,19 +196,20 @@ class ComponentManager { /** Possible Messages: - set-size - stream-items - stream-context-item - save-items - select-item - associate-item - deassociate-item - clear-selection - create-item - delete-items - set-component-data - save-context-client-data - get-context-client-data + set-size + stream-items + stream-context-item + save-items + select-item + associate-item + deassociate-item + clear-selection + create-item + delete-items + set-component-data + save-context-client-data + get-context-client-data + install-local-component */ if(message.action === "stream-items") { @@ -274,6 +280,17 @@ class ComponentManager { }); } + else if(message.action === "install-local-component") { + console.log("Received install-local-component event"); + this.desktopManager.installOfflineComponentFromData(message.data, (response) => { + console.log("componentManager: installed component:", response); + var component = this.modelManager.mapResponseItemsToLocalModels([response], ModelManager.MappingSourceComponentRetrieved)[0]; + // Save updated URL + component.setDirty(true); + this.syncManager.sync(); + }) + } + for(let handler of this.handlers) { if(handler.areas.includes(component.area)) { this.timeout(function(){ @@ -585,6 +602,14 @@ class ComponentManager { component.ignoreEvents = !on; } + urlForComponent(component) { + if(isDesktopApplication() && component.local && component.url.startsWith("sn://")) { + return component.url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); + } else { + return component.url; + } + } + iframeForComponent(component) { for(var frame of document.getElementsByTagName("iframe")) { var componentId = frame.dataset.componentId; diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 1cb28506c..ac3b3b380 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -8,6 +8,8 @@ class DesktopManager { this.authManager = authManager; this.$rootScope = $rootScope; + this.isDesktop = isDesktopApplication(); + $rootScope.$on("initial-data-loaded", () => { this.dataLoaded = true; if(this.dataLoadHandler) { @@ -22,6 +24,47 @@ class DesktopManager { }) } + /* Can handle components and themes */ + installOfflineComponentFromData(componentData, callback) { + this.componentInstallationHandler(componentData, (installedComponent) => { + componentData.content.url = installedComponent.content.url; + componentData.content.local = true; + callback(componentData); + }); + } + + getApplicationDataPath() { + console.assert(this.applicationDataPath, "applicationDataPath is null"); + return this.applicationDataPath; + } + + /* Sending a component in its raw state is really slow for the desktop app */ + convertComponentForTransmission(component) { + return new ItemParams(component).paramsForExportFile(); + } + + // All `components` should be installed + syncComponentsInstallation(components) { + if(!this.isDesktop) return; + var data = components.map((component) => { + return this.convertComponentForTransmission(component); + }) + this.installationSyncHandler(data); + } + + /* Used to resolve "sn://" */ + desktop_setApplicationDataPath(path) { + this.applicationDataPath = path; + } + + desktop_setComponentInstallationSyncHandler(handler) { + this.installationSyncHandler = handler; + } + + desktop_setOfflineComponentInstallationHandler(handler) { + this.componentInstallationHandler = handler; + } + desktop_setInitialDataLoadHandler(handler) { this.dataLoadHandler = handler; if(this.dataLoaded) { diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 008c5fadc..26a8de3f7 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -16,10 +16,16 @@ class EditorMenu { $scope.editors = componentManager.componentsForArea("editor-editor"); + $scope.isDesktop = isDesktopApplication(); + $scope.selectEditor = function($event, editor) { if(editor) { editor.conflict_of = null; // clear conflict if applicable } + if(editor.local && !isDesktopApplication()) { + alert("This editor is installed ") + return; + } $scope.callback()(editor); } diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index 53169c393..ff492e739 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -7,10 +7,9 @@ class RoomBar { }; } - controller($rootScope, $scope, extensionManager, syncManager, modelManager, componentManager, $timeout) { + controller($rootScope, $scope, desktopManager, syncManager, modelManager, componentManager, $timeout) { 'ngInject'; - $scope.extensionManager = extensionManager; $scope.componentManager = componentManager; $rootScope.$on("initial-data-loaded", () => { @@ -20,8 +19,48 @@ class RoomBar { }) }); + componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: function(component){ + if(component.active) { + $timeout(function(){ + var iframe = componentManager.iframeForComponent(component); + if(iframe) { + iframe.onload = function() { + componentManager.registerComponentWindow(component, iframe.contentWindow); + }.bind(this); + } + }.bind(this)); + } + }.bind(this), actionHandler: function(component, action, data){ + if(action === "set-size") { + console.log("Set size event", data); + 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(data.type === "content") { + var iframe = componentManager.iframeForComponent(component); + var width = data.width; + var height = data.height; + iframe.width = width; + iframe.height = height; + + setSize(iframe, data); + } else { + var container = document.getElementById("room-" + component.uuid); + setSize(container, data); + } + } + }.bind(this)}); + $scope.selectRoom = function(room) { - + room.show = !room.show; + if(room.show) { + this.componentManager.activateComponent(room); + } else { + this.componentManager.deactivateComponent(room); + } } } diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index e3e856325..7dc364919 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -90,9 +90,32 @@ h2 { padding-left: 10px; padding-right: 10px; margin-left: 5px; + position: relative; .room-item { display: inline-block; + position: relative; + + .room-container { + max-height: 85vh; + position: absolute; + right: 0px; + bottom: 20px; + min-width: 300px; + z-index: 1000; + margin-top: 15px; + + box-shadow: 0px 0px 15px rgba(black, 0.2); + border: none; + cursor: default; + overflow: auto; + background-color: white; + } + + .room-iframe { + width: 100%; + height: 100%; + } } } } diff --git a/app/assets/stylesheets/app/_menus.scss b/app/assets/stylesheets/app/_menus.scss index e1bfbd4af..8e851aad6 100644 --- a/app/assets/stylesheets/app/_menus.scss +++ b/app/assets/stylesheets/app/_menus.scss @@ -112,7 +112,6 @@ ul.section-menu-bar { cursor: pointer; } - .shortcut { float: right; font-size: 12px; @@ -122,6 +121,12 @@ ul.section-menu-bar { padding-right: 10px; } } + + .sublabel { + font-size: 12px; + font-weight: normal; + margin-top: 5px; + } } diff --git a/app/assets/stylesheets/app/_ui.scss b/app/assets/stylesheets/app/_ui.scss index fcbaa03eb..bbad720ee 100644 --- a/app/assets/stylesheets/app/_ui.scss +++ b/app/assets/stylesheets/app/_ui.scss @@ -101,14 +101,14 @@ button:focus {outline:0;} } .panel { - position: absolute; - right: 0px; - min-width: 300px; - z-index: 1000; - margin-top: 10px; - box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2); - border: none; - background-color: white; + position: absolute; + right: 0px; + min-width: 300px; + z-index: 1000; + margin-top: 10px; + box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2); + border: none; + background-color: white; } .panel-right { diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index cad2f2d0b..97e11de66 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -9,10 +9,12 @@ %div{"ng-if" => "editors.length > 0"} .header .title External Editors - .subtitle Can access your current note decrypted. %ul %li.menu-item{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)"} %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy %label.menu-item-title %span.inline.tinted.mr-10{"ng-if" => "selectedEditor === editor"} ✓ {{editor.name}} + .sublabel{"ng-if" => "editor.local"} + Locally Installed + .sublabel.faded{"ng-if" => "editor.local && !isDesktop"} Unavailable in Web Browser diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index fbb5ecad3..4d671b109 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,2 +1,4 @@ .room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"} - {{room.name}} + %span {{room.name}} + .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "room-{{room.uuid}}"} + %iframe.room-iframe{"ng-src" => "{{componentManager.urlForComponent(room) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{room.uuid}}"} diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index a33cb2a5a..7bb9d071b 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -9,7 +9,7 @@ .editor-tags #note-tags-component-container{"ng-if" => "ctrl.tagsComponent && ctrl.tagsComponent.active"} - %iframe#note-tags-iframe{"ng-src" => "{{ctrl.tagsComponent.url | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts", "data-component-id" => "{{ctrl.tagsComponent.uuid}}"} + %iframe#note-tags-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.tagsComponent) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts", "data-component-id" => "{{ctrl.tagsComponent.uuid}}"} %input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} %ul.section-menu-bar{"ng-if" => "ctrl.note"} @@ -46,7 +46,7 @@ %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} .editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"} - %iframe#editor-iframe{"ng-if" => "ctrl.editorComponent && ctrl.editorComponent.active", "ng-src" => "{{ctrl.editorComponent.url | trusted}}", "data-component-id" => "{{ctrl.editorComponent.uuid}}", "frameBorder" => "0", "style" => "width: 100%;"} + %iframe#editor-iframe{"ng-if" => "ctrl.editorComponent && ctrl.editorComponent.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.editorComponent) | trusted}}", "data-component-id" => "{{ctrl.editorComponent.uuid}}", "frameBorder" => "0", "style" => "width: 100%;"} Loading %textarea.editable#note-text-editor{"ng-if" => "!ctrl.editorComponent", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text", "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} @@ -59,4 +59,4 @@ #editor-pane-component-stack .component.component-stack-border{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.ignoreEvents", "id" => "{{'component-' + component.uuid}}", "ng-mouseover" => "component.showExit = true", "ng-mouseleave" => "component.showExit = false"} .exit-button.body-text-color{"ng-if" => "component.showExit", "ng-click" => "ctrl.disableComponentForCurrentItem(component, true)"} × - %iframe#note-tags-iframe{"ng-src" => "{{component.url | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} + %iframe#note-tags-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(component) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} diff --git a/app/assets/templates/frontend/tags.html.haml b/app/assets/templates/frontend/tags.html.haml index 4b689860c..5a5a4a807 100644 --- a/app/assets/templates/frontend/tags.html.haml +++ b/app/assets/templates/frontend/tags.html.haml @@ -1,5 +1,5 @@ .section.tags#tags-column - %iframe#tags-list-iframe{"ng-if" => "ctrl.component && ctrl.component.active", "ng-src" => "{{ctrl.component.url | trusted}}", "frameBorder" => "0", "style" => "width: 100%; height: 100%;", "sandbox" => "allow-scripts"} + %iframe#tags-list-iframe{"ng-if" => "ctrl.component && ctrl.component.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.component) | trusted}}", "frameBorder" => "0", "style" => "width: 100%; height: 100%;", "sandbox" => "allow-scripts"} #tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"} #tags-title-bar.section-title-bar .title Tags From 93ea71672b8a3a8aa8dd2aa51a66b3c7c163ae00 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 23 Dec 2017 14:21:37 -0600 Subject: [PATCH 04/99] Mfa wip --- .../app/frontend/models/api/mfa.js | 33 +++++++++++++++++++ .../javascripts/app/services/authManager.js | 4 +-- .../services/directives/views/accountMenu.js | 32 ++++++++++++------ .../javascripts/app/services/modelManager.js | 4 ++- .../directives/account-menu.html.haml | 7 +++- 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 app/assets/javascripts/app/frontend/models/api/mfa.js diff --git a/app/assets/javascripts/app/frontend/models/api/mfa.js b/app/assets/javascripts/app/frontend/models/api/mfa.js new file mode 100644 index 000000000..41e8cce5d --- /dev/null +++ b/app/assets/javascripts/app/frontend/models/api/mfa.js @@ -0,0 +1,33 @@ +class Mfa extends Item { + + constructor(json_obj) { + super(json_obj); + } + + mapContentToLocalProperties(content) { + super.mapContentToLocalProperties(content) + this.name = content.name; + } + + structureParams() { + var params = { + name: this.name, + }; + + _.merge(params, super.structureParams()); + return params; + } + + toJSON() { + return {uuid: this.uuid} + } + + get content_type() { + return "SN|MFA"; + } + + doNotEncrypt() { + return true; + } + +} diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 9763e716e..e4588e5ba 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -117,7 +117,7 @@ angular.module('app.frontend') } } - this.login = function(url, email, password, ephemeral, callback) { + this.login = function(url, email, password, ephemeral, extraParams, callback) { this.getAuthParamsForEmail(url, email, function(authParams){ if(!authParams || !authParams.pw_cost) { @@ -150,7 +150,7 @@ angular.module('app.frontend') Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){ var requestUrl = url + "/auth/sign_in"; - var params = {password: keys.pw, email: email}; + var params = _.merge({password: keys.pw, email: email}, extraParams); httpManager.postAbsolute(requestUrl, params, function(response){ this.setEphemeral(ephemeral); this.handleAuthResponse(response, email, url, authParams, keys); diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index 21ddfe81d..83f033aa0 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -89,6 +89,12 @@ class AccountMenu { }) } + $scope.submitMfaForm = function() { + var params = {}; + params[$scope.formData.mfa.payload.mfa_key] = $scope.formData.userMfaCode; + $scope.login(params); + } + $scope.submitAuthForm = function() { if($scope.formData.showLogin) { $scope.login(); @@ -97,19 +103,25 @@ class AccountMenu { } } - $scope.login = function() { + $scope.login = function(extraParams) { $scope.formData.status = "Generating Login Keys..."; $timeout(function(){ - authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, function(response){ - if(!response || response.error) { - $scope.formData.status = null; - var error = response ? response.error : {message: "An unknown error occured."} - if(!response || (response && !response.didDisplayAlert)) { - alert(error.message); + authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, extraParams, + (response) => { + if(!response || response.error) { + $scope.formData.status = null; + var error = response ? response.error : {message: "An unknown error occured."} + if(error.tag == "mfa-required") { + $timeout(() => { + $scope.formData.showLogin = false; + $scope.formData.mfa = error; + }) + } else if(!response || (response && !response.didDisplayAlert)) { + alert(error.message); + } + } else { + $scope.onAuthSuccess(); } - } else { - $scope.onAuthSuccess(); - } }); }) } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 831b184b8..73a49d80b 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -18,7 +18,7 @@ class ModelManager { this._extensions = []; this.acceptableContentTypes = [ "Note", "Tag", "Extension", "SN|Editor", "SN|Theme", - "SN|Component", "SF|Extension", "SN|UserPreferences" + "SN|Component", "SF|Extension", "SN|UserPreferences", "SF|MFA" ]; } @@ -211,6 +211,8 @@ class ModelManager { item = new Component(json_obj); } else if(json_obj.content_type == "SF|Extension") { item = new SyncAdapter(json_obj); + } else if(json_obj.content_type == "SF|MFA") { + item = new Mfa(json_obj); } else { diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 87ca42432..099460fd9 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -5,7 +5,7 @@ -# Account Section .mb-10 - .step-one{"ng-if" => "!formData.showLogin && !formData.showRegister"} + .step-one{"ng-if" => "!formData.showLogin && !formData.showRegister && !formData.mfa"} %h3 Sign in or register to enable sync and end-to-end encryption. .small-v-space @@ -41,6 +41,11 @@ Merge local data ({{notesAndTagsCount()}} notes and tags) %button.ui-button.block.mt-10{"ng-click" => "submitAuthForm()"} {{formData.showLogin ? "Sign In" : "Register"}} + %form.mt-5{"ng-if" => "formData.mfa"} + %p {{formData.mfa.message}} + %input.form-control.mt-10{:autofocus => 'autofocus', :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'} + %button.ui-button.block.mt-10{"ng-click" => "submitMfaForm()"} {{"Sign In"}} + .mt-15{"ng-if" => "formData.showRegister"} %h3 No Password Reset. %p.mt-5 Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password. From 5df39b1c2fb73a0b1e8bae93a9fbec0415542f23 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 23 Dec 2017 17:43:52 -0600 Subject: [PATCH 05/99] Component modals wip --- .../javascripts/app/services/authManager.js | 6 +-- .../app/services/componentManager.js | 40 ++++++++++++++++++ .../services/directives/views/accountMenu.js | 4 ++ .../directives/views/componentModal.js | 41 +++++++++++++++++++ .../services/directives/views/editorMenu.js | 8 ++-- .../app/services/directives/views/roomBar.js | 23 +---------- .../{_permissions-modal.scss => _modals.scss} | 10 ++++- app/assets/stylesheets/frontend.css.scss | 2 +- .../directives/account-menu.html.haml | 2 +- .../directives/component-modal.html.haml | 5 +++ 10 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 app/assets/javascripts/app/services/directives/views/componentModal.js rename app/assets/stylesheets/app/{_permissions-modal.scss => _modals.scss} (91%) create mode 100644 app/assets/templates/frontend/directives/component-modal.html.haml diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 5b7d9b7bd..e5126ab76 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -95,9 +95,9 @@ angular.module('app.frontend') return supportedVersions.includes(version); } - this.getAuthParamsForEmail = function(url, email, callback) { + this.getAuthParamsForEmail = function(url, email, extraParams, callback) { var requestUrl = url + "/auth/params"; - httpManager.getAbsolute(requestUrl, {email: email}, function(response){ + httpManager.getAbsolute(requestUrl, _.merge({email: email}, extraParams), function(response){ callback(response); }, function(response){ console.error("Error getting auth params", response); @@ -121,7 +121,7 @@ angular.module('app.frontend') } this.login = function(url, email, password, ephemeral, extraParams, callback) { - this.getAuthParamsForEmail(url, email, function(authParams){ + this.getAuthParamsForEmail(url, email, extraParams, function(authParams){ if(authParams.error) { callback(authParams); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 049e05c75..89e2304fd 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -210,6 +210,7 @@ class ComponentManager { save-context-client-data get-context-client-data install-local-component + open-component */ if(message.action === "stream-items") { @@ -291,6 +292,12 @@ class ComponentManager { }) } + else if(message.action === "open-component") { + let openComponent = this.modelManager.findItem(message.data.uuid); + console.log("Received open-component event", openComponent); + this.openModalComponent(openComponent); + } + for(let handler of this.handlers) { if(handler.areas.includes(component.area)) { this.timeout(function(){ @@ -449,6 +456,13 @@ class ComponentManager { } } + openModalComponent(component) { + var scope = this.$rootScope.$new(true); + scope.component = component; + var el = this.$compile( "" )(scope); + angular.element(document.body).append(el); + } + replyToMessage(component, originalMessage, replyData) { var reply = { action: "reply", @@ -511,6 +525,11 @@ class ComponentManager { this.handlers.push(handler); } + deregisterHandler(identifier) { + var handler = _.find(this.handlers, {identifier: identifier}); + this.handlers.splice(this.handlers.indexOf(handler), 1); + } + // Called by other views when the iframe is ready registerComponentWindow(component, componentWindow) { if(component.window === componentWindow) { @@ -619,6 +638,27 @@ class ComponentManager { } } + handleSetSizeEvent(component, data) { + 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(data.type === "content") { + var iframe = this.iframeForComponent(component); + var width = data.width; + var height = data.height; + iframe.width = width; + iframe.height = height; + + setSize(iframe, data); + } else { + var container = document.getElementById("room-" + component.uuid); + setSize(container, data); + } + } + } diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index 83f033aa0..4782899bd 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -117,6 +117,10 @@ class AccountMenu { $scope.formData.mfa = error; }) } else if(!response || (response && !response.didDisplayAlert)) { + $timeout(() => { + $scope.formData.showLogin = true; + $scope.formData.mfa = null; + }) alert(error.message); } } else { diff --git a/app/assets/javascripts/app/services/directives/views/componentModal.js b/app/assets/javascripts/app/services/directives/views/componentModal.js new file mode 100644 index 000000000..02748887f --- /dev/null +++ b/app/assets/javascripts/app/services/directives/views/componentModal.js @@ -0,0 +1,41 @@ +class ComponentModal { + + constructor() { + this.restrict = "E"; + this.templateUrl = "frontend/directives/component-modal.html"; + this.scope = { + show: "=", + component: "=", + callback: "=" + }; + } + + link($scope, el, attrs, componentManager) { + $scope.el = el; + } + + controller($scope, componentManager) { + 'ngInject'; + + let identifier = "modal-" + $scope.component.uuid; + + $scope.dismiss = function() { + componentManager.deregisterHandler(identifier); + $scope.el.remove(); + } + + $scope.url = function() { + return componentManager.urlForComponent($scope.component); + } + + componentManager.registerHandler({identifier: identifier, areas: ["modal"], + actionHandler: function(component, action, data){ + if(action == "set-size") { + componentManager.handleSetSizeEvent(component, data); + } + }.bind(this)}); + } + +} + +angular.module('app.frontend').directive('componentModal', () => new ComponentModal); diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 26a8de3f7..2092c08f1 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -21,10 +21,10 @@ class EditorMenu { $scope.selectEditor = function($event, editor) { if(editor) { editor.conflict_of = null; // clear conflict if applicable - } - if(editor.local && !isDesktopApplication()) { - alert("This editor is installed ") - return; + if(editor.local && !isDesktopApplication()) { + alert("This editor is installed ") + return; + } } $scope.callback()(editor); } diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index ff492e739..4d9c552cb 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -15,7 +15,6 @@ class RoomBar { $rootScope.$on("initial-data-loaded", () => { $timeout(() => { $scope.rooms = componentManager.componentsForArea("rooms"); - console.log("Rooms:", $scope.rooms); }) }); @@ -31,26 +30,8 @@ class RoomBar { }.bind(this)); } }.bind(this), actionHandler: function(component, action, data){ - if(action === "set-size") { - console.log("Set size event", data); - 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(data.type === "content") { - var iframe = componentManager.iframeForComponent(component); - var width = data.width; - var height = data.height; - iframe.width = width; - iframe.height = height; - - setSize(iframe, data); - } else { - var container = document.getElementById("room-" + component.uuid); - setSize(container, data); - } + if(action == "set-size") { + componentManager.handleSetSizeEvent(component, data); } }.bind(this)}); diff --git a/app/assets/stylesheets/app/_permissions-modal.scss b/app/assets/stylesheets/app/_modals.scss similarity index 91% rename from app/assets/stylesheets/app/_permissions-modal.scss rename to app/assets/stylesheets/app/_modals.scss index c51800dd1..a1bb020c5 100644 --- a/app/assets/stylesheets/app/_permissions-modal.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -1,4 +1,4 @@ -.permissions-modal { +.permissions-modal, .component-modal { position: fixed; margin-left: auto; margin-right: auto; @@ -82,5 +82,13 @@ border: 1px solid gray; } } +} + +.modal-iframe-container { + + .modal-iframe { + width: 100%; + height: 100%; + } } diff --git a/app/assets/stylesheets/frontend.css.scss b/app/assets/stylesheets/frontend.css.scss index 249d4f9ed..529b6ed35 100644 --- a/app/assets/stylesheets/frontend.css.scss +++ b/app/assets/stylesheets/frontend.css.scss @@ -9,7 +9,7 @@ $dark-gray: #2e2e2e; @import "app/editor"; @import "app/extensions"; @import "app/menus"; -@import "app/permissions-modal"; +@import "app/modals"; @import "app/lock-screen"; @import "ionicons"; diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 099460fd9..6b5311743 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -43,7 +43,7 @@ %form.mt-5{"ng-if" => "formData.mfa"} %p {{formData.mfa.message}} - %input.form-control.mt-10{:autofocus => 'autofocus', :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'} + %input.form-control.mt-10{:autofocus => "true", :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'} %button.ui-button.block.mt-10{"ng-click" => "submitMfaForm()"} {{"Sign In"}} .mt-15{"ng-if" => "formData.showRegister"} diff --git a/app/assets/templates/frontend/directives/component-modal.html.haml b/app/assets/templates/frontend/directives/component-modal.html.haml new file mode 100644 index 000000000..4ce5aaf62 --- /dev/null +++ b/app/assets/templates/frontend/directives/component-modal.html.haml @@ -0,0 +1,5 @@ +.background{"ng-click" => "dismiss()"} + +.content + .modal-iframe-container{"ng-attr-id" => "component-{{component.uuid}}"} + %iframe.modal-iframe{"ng-src" => "{{url() | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} From f4352253c3af40e1fe1b79f3afc671e18d1f20bf Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 24 Dec 2017 14:46:00 -0600 Subject: [PATCH 06/99] Component modals --- .../app/frontend/models/api/mfa.js | 11 +++------ .../app/services/componentManager.js | 2 +- .../directives/views/componentModal.js | 24 +++++++++++++++---- .../frontend/directives/room-bar.html.haml | 2 +- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/app/frontend/models/api/mfa.js b/app/assets/javascripts/app/frontend/models/api/mfa.js index 41e8cce5d..d3792654d 100644 --- a/app/assets/javascripts/app/frontend/models/api/mfa.js +++ b/app/assets/javascripts/app/frontend/models/api/mfa.js @@ -6,16 +6,11 @@ class Mfa extends Item { mapContentToLocalProperties(content) { super.mapContentToLocalProperties(content) - this.name = content.name; + this.serverContent = content; } structureParams() { - var params = { - name: this.name, - }; - - _.merge(params, super.structureParams()); - return params; + return _.merge(this.serverContent, super.structureParams()); } toJSON() { @@ -23,7 +18,7 @@ class Mfa extends Item { } get content_type() { - return "SN|MFA"; + return "SF|MFA"; } doNotEncrypt() { diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 89e2304fd..5ade3a822 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -654,7 +654,7 @@ class ComponentManager { setSize(iframe, data); } else { - var container = document.getElementById("room-" + component.uuid); + var container = document.getElementById("component-" + component.uuid); setSize(container, data); } } diff --git a/app/assets/javascripts/app/services/directives/views/componentModal.js b/app/assets/javascripts/app/services/directives/views/componentModal.js index 02748887f..d1235fea9 100644 --- a/app/assets/javascripts/app/services/directives/views/componentModal.js +++ b/app/assets/javascripts/app/services/directives/views/componentModal.js @@ -10,17 +10,18 @@ class ComponentModal { }; } - link($scope, el, attrs, componentManager) { + link($scope, el, attrs) { $scope.el = el; } - controller($scope, componentManager) { + controller($scope, $timeout, componentManager) { 'ngInject'; let identifier = "modal-" + $scope.component.uuid; $scope.dismiss = function() { componentManager.deregisterHandler(identifier); + componentManager.deactivateComponent($scope.component); $scope.el.remove(); } @@ -28,12 +29,27 @@ class ComponentModal { return componentManager.urlForComponent($scope.component); } - componentManager.registerHandler({identifier: identifier, areas: ["modal"], - actionHandler: function(component, action, data){ + componentManager.registerHandler({identifier: identifier, areas: ["modal"], activationHandler: (component) => { + if(component.active) { + $timeout(function(){ + var iframe = componentManager.iframeForComponent(component); + console.log("iframe", iframe, component); + if(iframe) { + iframe.onload = function() { + componentManager.registerComponentWindow(component, iframe.contentWindow); + }.bind(this); + } + }.bind(this)); + } + }, + actionHandler: function(component, action, data) { if(action == "set-size") { + console.log("componentModalReceivedAction SetSize", component); componentManager.handleSetSizeEvent(component, data); } }.bind(this)}); + + componentManager.activateComponent($scope.component); } } diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index 4d671b109..ab4c78ffa 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,4 +1,4 @@ .room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"} %span {{room.name}} - .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "room-{{room.uuid}}"} + .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} %iframe.room-iframe{"ng-src" => "{{componentManager.urlForComponent(room) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{room.uuid}}"} From 2702d88bc23bca575273841fc6d2cbb14144347f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 25 Dec 2017 12:16:06 -0600 Subject: [PATCH 07/99] Components autoupdate --- .../app/frontend/models/app/component.js | 2 ++ .../javascripts/app/services/componentManager.js | 14 ++++++++++++-- .../javascripts/app/services/desktopManager.js | 13 ++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index 202073279..d9e736ad4 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -20,6 +20,7 @@ class Component extends Item { super.mapContentToLocalProperties(content) this.url = content.url; this.name = content.name; + this.autoupdate = content.autoupdate; this.package_info = content.package_info; @@ -49,6 +50,7 @@ class Component extends Item { permissions: this.permissions, active: this.active, local: this.local, + autoupdate: this.autoupdate, componentData: this.componentData, disassociatedItemIds: this.disassociatedItemIds, associatedItemIds: this.associatedItemIds, diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 5ade3a822..4e1e1c00c 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -44,8 +44,14 @@ class ComponentManager { var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" }); - // Ensure any component in our data is installed by the system - this.desktopManager.syncComponentsInstallation(syncedComponents); + /* 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) { + console.log("Web, Syncing Components", syncedComponents, "source", source); + // Ensure any component in our data is installed by the system + this.desktopManager.syncComponentsInstallation(syncedComponents); + } for(var component of syncedComponents) { var activeComponent = _.find(this.activeComponents, {uuid: component.uuid}); @@ -61,6 +67,10 @@ class ComponentManager { return observer.contentTypes.indexOf(item.content_type) !== -1; }) + if(relevantItems.length == 0) { + continue; + } + var requiredPermissions = [ { name: "stream-items", diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index ac3b3b380..3bdfdfae2 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -2,10 +2,11 @@ class DesktopManager { - constructor($rootScope, modelManager, authManager, passcodeManager) { + constructor($rootScope, modelManager, syncManager, authManager, passcodeManager) { this.passcodeManager = passcodeManager; this.modelManager = modelManager; this.authManager = authManager; + this.syncManager = syncManager; this.$rootScope = $rootScope; this.isDesktop = isDesktopApplication(); @@ -46,12 +47,22 @@ class DesktopManager { // All `components` should be installed syncComponentsInstallation(components) { if(!this.isDesktop) return; + + /* Allows us to look up component on desktop_updateComponentComplete */ + this.syncingComponents = components; + var data = components.map((component) => { return this.convertComponentForTransmission(component); }) this.installationSyncHandler(data); } + desktop_updateComponentComplete(componentData) { + var component = this.syncingComponents.filter((c) => {return c.uuid == componentData.uuid})[0]; + component.setDirty(true); + this.syncManager.sync(); + } + /* Used to resolve "sn://" */ desktop_setApplicationDataPath(path) { this.applicationDataPath = path; From 8569098e8a4d3dfd85df712a92ef815363266d5a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 25 Dec 2017 15:39:00 -0600 Subject: [PATCH 08/99] Room remeber size + component permissions --- .../app/frontend/models/app/component.js | 8 + .../app/services/componentManager.js | 211 +++++++++++------- .../app/services/directives/views/roomBar.js | 25 ++- .../app/services/packageManager.js | 1 + app/assets/stylesheets/app/_footer.scss | 15 ++ .../frontend/directives/room-bar.html.haml | 3 +- config/application.rb | 2 +- 7 files changed, 175 insertions(+), 90 deletions(-) diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index d9e736ad4..eb515ce32 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -76,6 +76,14 @@ class Component extends Item { return this.getAppDataItem("defaultEditor") == true; } + setRoomLastSize(size) { + this.setAppDataItem("lastRoomSize", size); + } + + getRoomLastSize() { + return this.getAppDataItem("lastRoomSize"); + } + /* An associative component depends on being explicitly activated for a given item, compared to a dissaciative component, diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 4e1e1c00c..3bd723017 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -225,89 +225,24 @@ class ComponentManager { if(message.action === "stream-items") { this.handleStreamItemsMessage(component, message); - } - - else if(message.action === "stream-context-item") { + } else if(message.action === "stream-context-item") { this.handleStreamContextItemMessage(component, message); - } - - else if(message.action === "set-component-data") { - component.componentData = message.data.componentData; - component.setDirty(true); - this.syncManager.sync(); - } - - else if(message.action === "delete-items") { - var items = message.data.items; - var noun = items.length == 1 ? "item" : "items"; - if(confirm(`Are you sure you want to delete ${items.length} ${noun}?`)) { - for(var item of items) { - var model = this.modelManager.findItem(item.uuid); - this.modelManager.setItemToBeDeleted(model); - } - - this.syncManager.sync(); - } - } - - else if(message.action === "create-item") { - var responseItem = message.data.item; - this.removePrivatePropertiesFromResponseItems([responseItem]); - var item = this.modelManager.createItem(responseItem); - if(responseItem.clientData) { - item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); - } - this.modelManager.addItem(item); - this.modelManager.resolveReferencesForItem(item); - item.setDirty(true); - this.syncManager.sync(); - this.replyToMessage(component, message, {item: this.jsonForItem(item, component)}) - } - - else if(message.action === "save-items") { - var responseItems = message.data.items; - - this.removePrivatePropertiesFromResponseItems(responseItems); - - /* - 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); - - for(var item of localItems) { - var responseItem = _.find(responseItems, {uuid: item.uuid}); - _.merge(item.content, responseItem.content); - if(responseItem.clientData) { - item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); - } - item.setDirty(true); - } - this.syncManager.sync((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.handleMessage(component, saveMessage); - }); - } - - else if(message.action === "install-local-component") { - console.log("Received install-local-component event"); - this.desktopManager.installOfflineComponentFromData(message.data, (response) => { - console.log("componentManager: installed component:", response); - var component = this.modelManager.mapResponseItemsToLocalModels([response], ModelManager.MappingSourceComponentRetrieved)[0]; - // Save updated URL - component.setDirty(true); - this.syncManager.sync(); - }) - } - - else if(message.action === "open-component") { + } else if(message.action === "set-component-data") { + this.handleSetComponentDataMessage(component, message); + } else if(message.action === "delete-items") { + this.handleDeleteItemsMessage(component, message); + } else if(message.action === "create-item") { + this.handleCreateItemMessage(component, message); + } else if(message.action === "save-items") { + this.handleSaveItemsMessage(component, message); + } else if(message.action === "install-local-component") { + this.handleInstallLocalComponentMessage(component, message); + } else if(message.action === "open-component") { let openComponent = this.modelManager.findItem(message.data.uuid); - console.log("Received open-component event", openComponent); this.openModalComponent(openComponent); } + // Notify observers for(let handler of this.handlers) { if(handler.areas.includes(component.area)) { this.timeout(function(){ @@ -390,6 +325,124 @@ class ComponentManager { }.bind(this)) } + handleSaveItemsMessage(component, message) { + var requiredContentTypes = _.uniq(message.data.items.map((i) => {return i.content_type})).sort(); + var requiredPermissions = [ + { + name: "stream-items", + content_types: requiredContentTypes + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + var responseItems = message.data.items; + + this.removePrivatePropertiesFromResponseItems(responseItems); + + /* + 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); + + for(var item of localItems) { + var responseItem = _.find(responseItems, {uuid: item.uuid}); + _.merge(item.content, responseItem.content); + if(responseItem.clientData) { + item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); + } + item.setDirty(true); + } + this.syncManager.sync((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.handleMessage(component, saveMessage); + }); + }); + } + + handleCreateItemMessage(component, message) { + var requiredPermissions = [ + { + name: "stream-items", + content_types: [message.data.item.content_type] + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + var responseItem = message.data.item; + this.removePrivatePropertiesFromResponseItems([responseItem]); + var item = this.modelManager.createItem(responseItem); + if(responseItem.clientData) { + item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); + } + this.modelManager.addItem(item); + this.modelManager.resolveReferencesForItem(item); + item.setDirty(true); + this.syncManager.sync(); + this.replyToMessage(component, message, {item: this.jsonForItem(item, component)}) + }); + } + + handleDeleteItemsMessage(component, message) { + var requiredContentTypes = _.uniq(message.data.items.map((i) => {return i.content_type})).sort(); + var requiredPermissions = [ + { + name: "stream-items", + content_types: requiredContentTypes + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + var items = message.data.items; + var noun = items.length == 1 ? "item" : "items"; + if(confirm(`Are you sure you want to delete ${items.length} ${noun}?`)) { + for(var item of items) { + var model = this.modelManager.findItem(item.uuid); + this.modelManager.setItemToBeDeleted(model); + } + + this.syncManager.sync(); + } + }); + } + + handleInstallLocalComponentMessage(component, message) { + var requiredPermissions = [ + { + name: "stream-items", + content_types: [message.data.content_type] + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + console.log("Received install-local-component event"); + this.desktopManager.installOfflineComponentFromData(message.data, (response) => { + console.log("componentManager: installed component:", response); + var component = this.modelManager.mapResponseItemsToLocalModels([response], ModelManager.MappingSourceComponentRetrieved)[0]; + // Save updated URL + component.setDirty(true); + this.syncManager.sync(); + }) + }); + } + + handleSetComponentDataMessage(component, message) { + var requiredPermissions = [ + { + name: "stream-items", + content_types: component.content_type + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + component.componentData = message.data.componentData; + component.setDirty(true); + this.syncManager.sync(); + }); + } + runWithPermissions(component, requiredPermissions, requestedPermissions, runFunction) { var acquiredPermissions = component.permissions; @@ -407,7 +460,7 @@ class ComponentManager { if(!requestedMatchesRequired) { // Error with Component permissions request console.error("You are requesting permissions", requestedPermissions, "when you need to be requesting", requiredPermissions, ". Component:", component); - return; + return false; } if(!component.permissions) { diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index 4d9c552cb..b03eab93c 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -11,29 +11,36 @@ class RoomBar { 'ngInject'; $scope.componentManager = componentManager; + $scope.rooms = []; - $rootScope.$on("initial-data-loaded", () => { - $timeout(() => { - $scope.rooms = componentManager.componentsForArea("rooms"); - }) + modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { + $scope.rooms = _.uniq($scope.rooms + .concat(allItems + .filter((candidate) => {return candidate.area == "rooms"}))) + .filter((candidate) => {return !candidate.deleted}); }); - componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: function(component){ + componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: (component) => { if(component.active) { - $timeout(function(){ + $timeout(() => { var iframe = componentManager.iframeForComponent(component); if(iframe) { + var lastSize = component.getRoomLastSize(); + if(lastSize) { + componentManager.handleSetSizeEvent(component, lastSize); + } iframe.onload = function() { componentManager.registerComponentWindow(component, iframe.contentWindow); }.bind(this); } - }.bind(this)); + }); } - }.bind(this), actionHandler: function(component, action, data){ + }, actionHandler: (component, action, data) => { if(action == "set-size") { componentManager.handleSetSizeEvent(component, data); + component.setRoomLastSize(data); } - }.bind(this)}); + }}); $scope.selectRoom = function(room) { room.show = !room.show; diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js index d3a16666f..048d5b2ae 100644 --- a/app/assets/javascripts/app/services/packageManager.js +++ b/app/assets/javascripts/app/services/packageManager.js @@ -16,6 +16,7 @@ class PackageManager { } var assembled = this.modelManager.createItem(aPackage); + assembled.package_info = aPackage; this.modelManager.addItem(assembled); assembled.setDirty(true); this.syncManager.sync(); diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index 7dc364919..acd0f887a 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -95,6 +95,21 @@ h2 { .room-item { display: inline-block; position: relative; + vertical-align: middle; + + .label { + display: inline-block; + vertical-align: middle; + font-size: 11px; + margin-top: -2px; + } + + .icon { + width: 16px; + height: 16px; + display: inline-block; + vertical-align: middle; + } .room-container { max-height: 85vh; diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index ab4c78ffa..08cb5059b 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,4 +1,5 @@ .room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"} - %span {{room.name}} + %img.icon{"ng-src" => "{{room.package_info.icon_bar}}"} + .label {{room.name}} .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} %iframe.room-iframe{"ng-src" => "{{componentManager.urlForComponent(room) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{room.uuid}}"} diff --git a/config/application.rb b/config/application.rb index 16e4e7e40..ac93fc14b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -53,7 +53,7 @@ module Neeto font_src: %w(* 'self'), form_action: %w('self'), frame_ancestors: ["*"], - img_src: %w('self' data:), + img_src: %w('self' * data:), manifest_src: %w('self'), media_src: %w('self'), object_src: %w('self'), From fc4abbbaf668a959780e28f2d9e3476d533e45c2 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 26 Dec 2017 12:21:27 -0600 Subject: [PATCH 09/99] Better click outside handling --- .../app/services/componentManager.js | 9 +- .../directives/functional/click-outside.js | 105 +++++++++++++++--- .../directives/views/componentModal.js | 2 - .../services/directives/views/editorMenu.js | 2 +- .../directives/views/permissionsModal.js | 9 +- .../app/services/directives/views/roomBar.js | 6 +- .../javascripts/app/services/modelManager.js | 19 ++++ .../frontend/directives/room-bar.html.haml | 2 +- .../templates/frontend/editor.html.haml | 6 +- .../templates/frontend/footer.html.haml | 4 +- app/assets/templates/frontend/notes.html.haml | 2 +- 11 files changed, 138 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 3bd723017..46b71f3c6 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -606,7 +606,14 @@ class ComponentManager { } component.window = componentWindow; component.sessionKey = Neeto.crypto.generateUUID(); - this.sendMessageToComponent(component, {action: "component-registered", sessionKey: component.sessionKey, componentData: component.componentData}); + this.sendMessageToComponent(component, { + action: "component-registered", + sessionKey: component.sessionKey, + componentData: component.componentData, + data: { + environment: isDesktopApplication() ? "desktop" : "web" + } + }); this.postThemeToComponent(component); } diff --git a/app/assets/javascripts/app/services/directives/functional/click-outside.js b/app/assets/javascripts/app/services/directives/functional/click-outside.js index 10e40e1da..b30e749da 100644 --- a/app/assets/javascripts/app/services/directives/functional/click-outside.js +++ b/app/assets/javascripts/app/services/directives/functional/click-outside.js @@ -1,27 +1,102 @@ +// via https://github.com/IamAdamJowett/angular-click-outside + angular .module('app.frontend') - .directive('clickOutside', ['$document', function($document) { + .directive('clickOutside', ['$document', '$parse', '$timeout', function($document, $parse, $timeout) { return { restrict: 'A', replace: false, - link : function($scope, $element, attrs) { + link: function($scope, elem, attr) { + $timeout(() => { + // postpone linking to next digest to allow for unique id generation + var classList = (attr.outsideIfNot !== undefined) ? attr.outsideIfNot.split(/[ ,]+/) : [], + fn; - var didApplyClickOutside = false; + function eventHandler(e) { + var i, + element, + r, + id, + classNames, + l; - $element.bind('click', function(e) { - didApplyClickOutside = false; - if (attrs.isOpen) { - e.stopPropagation(); + // check if our element already hidden and abort if so + if (angular.element(elem).hasClass("ng-hide")) { + return; + } + + // if there is no click target, no point going on + if (!e || !e.target) { + return; + } + + // loop through the available elements, looking for classes in the class list that might match and so will eat + for (element = e.target; element; element = element.parentNode) { + // check if the element is the same element the directive is attached to and exit if so (props @CosticaPuntaru) + if (element === elem[0]) { + return; + } + + // now we have done the initial checks, start gathering id's and classes + id = element.id, + classNames = element.className, + l = classList.length; + + // Unwrap SVGAnimatedString classes + if (classNames && classNames.baseVal !== undefined) { + classNames = classNames.baseVal; + } + + // if there are no class names on the element clicked, skip the check + if (classNames || id) { + + // loop through the elements id's and classnames looking for exceptions + for (i = 0; i < l; i++) { + //prepare regex for class word matching + r = new RegExp('\\b' + classList[i] + '\\b'); + + // check for exact matches on id's or classes, but only if they exist in the first place + if ((id !== undefined && id === classList[i]) || (classNames && r.test(classNames))) { + // now let's exit out as it is an element that has been defined as being ignored for clicking outside + return; + } + } + } + } + + // if we have got this far, then we are good to go with processing the command passed in via the click-outside attribute + $timeout(function() { + fn = $parse(attr['clickOutside']); + fn($scope, { event: e }); + }); } - }); - $document.bind('click', function() { - if(!didApplyClickOutside) { - $scope.$apply(attrs.clickOutside); - didApplyClickOutside = true; + // if the devices has a touchscreen, listen for this event + if (_hasTouch()) { + $document.on('touchstart', eventHandler); } - }) - } + // still listen for the click event even if there is touch to cater for touchscreen laptops + $document.on('click', eventHandler); + + // when the scope is destroyed, clean up the documents event handlers as we don't want it hanging around + $scope.$on('$destroy', function() { + if (_hasTouch()) { + $document.off('touchstart', eventHandler); + } + + $document.off('click', eventHandler); + }); + + /** + * @description Private function to attempt to figure out if we are on a touch device + * @private + **/ + function _hasTouch() { + // works on most browsers, IE10/11 and Surface + return 'ontouchstart' in window || navigator.maxTouchPoints; + }; + }); } - }]); + } +}]); diff --git a/app/assets/javascripts/app/services/directives/views/componentModal.js b/app/assets/javascripts/app/services/directives/views/componentModal.js index d1235fea9..ee20de0d2 100644 --- a/app/assets/javascripts/app/services/directives/views/componentModal.js +++ b/app/assets/javascripts/app/services/directives/views/componentModal.js @@ -33,7 +33,6 @@ class ComponentModal { if(component.active) { $timeout(function(){ var iframe = componentManager.iframeForComponent(component); - console.log("iframe", iframe, component); if(iframe) { iframe.onload = function() { componentManager.registerComponentWindow(component, iframe.contentWindow); @@ -44,7 +43,6 @@ class ComponentModal { }, actionHandler: function(component, action, data) { if(action == "set-size") { - console.log("componentModalReceivedAction SetSize", component); componentManager.handleSetSizeEvent(component, data); } }.bind(this)}); diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 2092c08f1..57652641c 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -22,7 +22,7 @@ class EditorMenu { if(editor) { editor.conflict_of = null; // clear conflict if applicable if(editor.local && !isDesktopApplication()) { - alert("This editor is installed ") + alert("This editor is installed locally and is available only through Standard Notes for Desktop.") return; } } diff --git a/app/assets/javascripts/app/services/directives/views/permissionsModal.js b/app/assets/javascripts/app/services/directives/views/permissionsModal.js index e2a1f0a07..3007f362f 100644 --- a/app/assets/javascripts/app/services/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/services/directives/views/permissionsModal.js @@ -26,12 +26,19 @@ class PermissionsModal { $scope.callback(false); $scope.dismiss(); } + } + controller($scope, modelManager) { $scope.formattedPermissions = $scope.permissions.map(function(permission){ if(permission.name === "stream-items") { var title = "Access to "; var types = permission.content_types.map(function(type){ - return (type + "s").toLowerCase(); + var desc = modelManager.humanReadableDisplayForContentType(type); + if(desc) { + return desc + "s"; + } else { + return "items of type " + type; + } }) var typesString = ""; var separator = ", "; diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index b03eab93c..63f3d89e4 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -47,10 +47,14 @@ class RoomBar { if(room.show) { this.componentManager.activateComponent(room); } else { - this.componentManager.deactivateComponent(room); + $scope.hideRoom(room); } } + $scope.hideRoom = function(room) { + room.show = false; + this.componentManager.deactivateComponent(room); + } } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 73a49d80b..70438bc0b 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -400,6 +400,25 @@ class ModelManager { return JSON.stringify(data, null, 2 /* pretty print */); } + + + /* + Misc + */ + + humanReadableDisplayForContentType(contentType) { + return { + "Note" : "note", + "Tag" : "tag", + "Extension" : "action-based extension", + "SN|Component" : "component", + "SN|Editor" : "editor", + "SN|Theme" : "theme", + "SF|Extension" : "server extension", + "SF|MFA" : "server multi-factor authentication setting" + }[contentType]; + } + } angular.module('app.frontend').service('modelManager', ModelManager); diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index 08cb5059b..d941c3f29 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,4 +1,4 @@ -.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"} +.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)", "click-outside" => "hideRoom(room)"} %img.icon{"ng-src" => "{{room.package_info.icon_bar}}"} .label {{room.name}} .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 9b9ddf714..0a812b61b 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -13,7 +13,7 @@ %input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} %ul.section-menu-bar{"ng-if" => "ctrl.note"} - %li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} + %li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;"} %label{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;"} Menu %ul.dropdown-menu.sectioned-menu{"ng-if" => "ctrl.showMenu"} @@ -37,11 +37,11 @@ %li{"ng-if" => "ctrl.hasDisabledStackComponents()"} %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledStackComponents()"} Restore Disabled Components - %li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} + %li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;"} %label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"} - %li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} + %li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;"} %label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index b0943a8f9..42452128b 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -1,10 +1,10 @@ #footer-bar .pull-left - .footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} + .footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;"} %a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"} - .footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} + .footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;"} %a{"ng-click" => "ctrl.toggleExtensions()"} Extensions %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index 810763227..76c75fa52 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -13,7 +13,7 @@ %label Options .subtitle {{ctrl.optionsSubtitle()}} - .sectioned-menu.dropdown-menu{"ng-if" => "ctrl.showMenu"} + .sectioned-menu.dropdown-menu{"ng-if" => "ctrl.showMenu", "click-outside" => "ctrl.showMenu = false;"} %ul .header .title Sort by From b124332619c93bebe427e537ee6624460ba14655 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 26 Dec 2017 15:09:02 -0600 Subject: [PATCH 10/99] Singleton manager + Revert back to old outside click handling. New system unable cope with internal clicks with ng-if --- .../app/frontend/controllers/home.js | 5 + .../javascripts/app/services/authManager.js | 26 ++++- .../directives/functional/click-outside.js | 105 +++--------------- .../app/services/singletonManager.js | 101 +++++++++++++++++ .../javascripts/app/services/syncManager.js | 8 +- .../frontend/directives/room-bar.html.haml | 2 +- .../templates/frontend/editor.html.haml | 6 +- .../templates/frontend/footer.html.haml | 4 +- app/assets/templates/frontend/notes.html.haml | 4 +- 9 files changed, 156 insertions(+), 105 deletions(-) create mode 100644 app/assets/javascripts/app/services/singletonManager.js diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 830479df3..97216078a 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -8,6 +8,11 @@ angular.module('app.frontend') $rootScope.$broadcast('new-update-available', version); } + /* Used to avoid circular dependencies where syncManager cannot be imported but rootScope can */ + $rootScope.sync = function() { + syncManager.sync(); + } + $rootScope.lockApplication = function() { // Reloading wipes current objects from memory window.location.reload(); diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index e5126ab76..ef29e12f9 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -7,11 +7,11 @@ angular.module('app.frontend') return domain; } - this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager) { - return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager); + this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) { + return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager); } - function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager) { + function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) { this.loadInitialData = function() { var userData = storageManager.getItem("user"); @@ -290,5 +290,23 @@ angular.module('app.frontend') this._authParams = null; } - } + + /* User Preferences */ + + let prefsContentType = "SN|UserPreferences"; + + singletonManager.registerSingleton({content_type: prefsContentType}, (resolvedSingleton) => { + console.log("AuthManager received resolved", resolvedSingleton); + this.userPreferences = resolvedSingleton; + }, () => { + // Safe to create. Create and return object. + var prefs = new Item({content_type: prefsContentType}); + modelManager.addItem(prefs); + prefs.setDirty(true); + console.log("Created new prefs", prefs); + $rootScope.sync(); + return prefs; + }); + + } }); diff --git a/app/assets/javascripts/app/services/directives/functional/click-outside.js b/app/assets/javascripts/app/services/directives/functional/click-outside.js index b30e749da..10e40e1da 100644 --- a/app/assets/javascripts/app/services/directives/functional/click-outside.js +++ b/app/assets/javascripts/app/services/directives/functional/click-outside.js @@ -1,102 +1,27 @@ -// via https://github.com/IamAdamJowett/angular-click-outside - angular .module('app.frontend') - .directive('clickOutside', ['$document', '$parse', '$timeout', function($document, $parse, $timeout) { + .directive('clickOutside', ['$document', function($document) { return { restrict: 'A', replace: false, - link: function($scope, elem, attr) { - $timeout(() => { - // postpone linking to next digest to allow for unique id generation - var classList = (attr.outsideIfNot !== undefined) ? attr.outsideIfNot.split(/[ ,]+/) : [], - fn; + link : function($scope, $element, attrs) { - function eventHandler(e) { - var i, - element, - r, - id, - classNames, - l; + var didApplyClickOutside = false; - // check if our element already hidden and abort if so - if (angular.element(elem).hasClass("ng-hide")) { - return; - } - - // if there is no click target, no point going on - if (!e || !e.target) { - return; - } - - // loop through the available elements, looking for classes in the class list that might match and so will eat - for (element = e.target; element; element = element.parentNode) { - // check if the element is the same element the directive is attached to and exit if so (props @CosticaPuntaru) - if (element === elem[0]) { - return; - } - - // now we have done the initial checks, start gathering id's and classes - id = element.id, - classNames = element.className, - l = classList.length; - - // Unwrap SVGAnimatedString classes - if (classNames && classNames.baseVal !== undefined) { - classNames = classNames.baseVal; - } - - // if there are no class names on the element clicked, skip the check - if (classNames || id) { - - // loop through the elements id's and classnames looking for exceptions - for (i = 0; i < l; i++) { - //prepare regex for class word matching - r = new RegExp('\\b' + classList[i] + '\\b'); - - // check for exact matches on id's or classes, but only if they exist in the first place - if ((id !== undefined && id === classList[i]) || (classNames && r.test(classNames))) { - // now let's exit out as it is an element that has been defined as being ignored for clicking outside - return; - } - } - } - } - - // if we have got this far, then we are good to go with processing the command passed in via the click-outside attribute - $timeout(function() { - fn = $parse(attr['clickOutside']); - fn($scope, { event: e }); - }); + $element.bind('click', function(e) { + didApplyClickOutside = false; + if (attrs.isOpen) { + e.stopPropagation(); } + }); - // if the devices has a touchscreen, listen for this event - if (_hasTouch()) { - $document.on('touchstart', eventHandler); + $document.bind('click', function() { + if(!didApplyClickOutside) { + $scope.$apply(attrs.clickOutside); + didApplyClickOutside = true; } + }) - // still listen for the click event even if there is touch to cater for touchscreen laptops - $document.on('click', eventHandler); - - // when the scope is destroyed, clean up the documents event handlers as we don't want it hanging around - $scope.$on('$destroy', function() { - if (_hasTouch()) { - $document.off('touchstart', eventHandler); - } - - $document.off('click', eventHandler); - }); - - /** - * @description Private function to attempt to figure out if we are on a touch device - * @private - **/ - function _hasTouch() { - // works on most browsers, IE10/11 and Surface - return 'ontouchstart' in window || navigator.maxTouchPoints; - }; - }); + } } - } -}]); + }]); diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js new file mode 100644 index 000000000..07e892c82 --- /dev/null +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -0,0 +1,101 @@ +/* + The SingletonManager allows controllers to register an item as a singleton, which means only one instance of that model + should exist, both on the server and on the client. When the SingletonManager detects multiple items matching the singleton predicate, + the oldest ones will be deleted, leaving the newest ones +*/ + +class SingletonManager { + + constructor($rootScope, modelManager) { + this.$rootScope = $rootScope; + this.modelManager = modelManager; + this.singletonHandlers = []; + + $rootScope.$on("initial-data-loaded", (event, data) => { + this.resolveSingletons(modelManager.allItems, true); + }) + + $rootScope.$on("sync:completed", (event, data) => { + console.log("Sync completed", data); + this.resolveSingletons(data.retrievedItems || []); + }) + + setTimeout(function () { + var userPrefsTotal = modelManager.itemsForContentType("SN|UserPreferences"); + console.log("All extant prefs", userPrefsTotal); + }, 1000); + } + + registerSingleton(predicate, 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, + resolutionCallback: resolveCallback, + createBlock: createBlock + }); + } + + resolveSingletons(retrievedItems, initialLoad) { + for(var singletonHandler of this.singletonHandlers) { + var predicate = singletonHandler.predicate; + var singletonItems = this.filterItemsWithPredicate(retrievedItems, predicate); + if(singletonItems.length > 0) { + // Check local inventory and make sure only 1 similar item exists. If more than 1, delete oldest + var allExtantItemsMatchingPredicate = this.filterItemsWithPredicate(this.modelManager.allItems, predicate); + + if(allExtantItemsMatchingPredicate.length >= 2) { + // Purge old ones + var sorted = allExtantItemsMatchingPredicate.sort((a, b) => { + return a.updated_at < b.updated_at; + }) + + var toDelete = sorted.slice(1, sorted.length); + for(var d of toDelete) { + this.modelManager.setItemToBeDeleted(d); + } + + this.$rootScope.sync(); + // Send remaining item to callback + var singleton = sorted[0]; + singletonHandler.singleton = singleton; + singletonHandler.resolutionCallback(singleton); + } else if(allExtantItemsMatchingPredicate.length == 1) { + if(!singletonHandler.singleton) { + // Not yet notified interested parties of object + var singleton = allExtantItemsMatchingPredicate[0]; + singletonHandler.singleton = singleton; + singletonHandler.resolutionCallback(singleton); + + } + } + } else { + // Retrieved items does not include any items of interest. If we don't have a singleton registered to this handler, + // we need to create one. Only do this on actual sync completetions and not on initial data load. Because we want + // to get the latest from the server before making the decision to create a new item + if(!singletonHandler.singleton && !initialLoad) { + var item = singletonHandler.createBlock(); + singletonHandler.singleton = item; + singletonHandler.resolutionCallback(item); + } + } + } + } + + filterItemsWithPredicate(items, predicate) { + return items.filter((candidate) => { + for(var key in predicate) { + if(candidate[key] != predicate[key]) { + return false; + } + } + return true; + }) + } + +} + +angular.module('app.frontend').service('singletonManager', SingletonManager); diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 8e5bc90e6..17f13fd78 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -62,6 +62,8 @@ class SyncManager { } } + this.$rootScope.$broadcast("sync:completed", {}); + if(callback) { callback({success: true}); } @@ -319,10 +321,10 @@ class SyncManager { this.$rootScope.$broadcast("major-data-change"); } - this.allRetreivedItems = []; - this.callQueuedCallbacksAndCurrent(callback, response); - this.$rootScope.$broadcast("sync:completed"); + this.$rootScope.$broadcast("sync:completed", {retrievedItems: this.allRetreivedItems}); + + this.allRetreivedItems = []; } }.bind(this); diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index d941c3f29..76002f265 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,4 +1,4 @@ -.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)", "click-outside" => "hideRoom(room)"} +.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)", "click-outside" => "hideRoom(room)", "is-open" => "room.show && room.active"} %img.icon{"ng-src" => "{{room.package_info.icon_bar}}"} .label {{room.name}} .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 0a812b61b..9b9ddf714 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -13,7 +13,7 @@ %input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} %ul.section-menu-bar{"ng-if" => "ctrl.note"} - %li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;"} + %li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} %label{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;"} Menu %ul.dropdown-menu.sectioned-menu{"ng-if" => "ctrl.showMenu"} @@ -37,11 +37,11 @@ %li{"ng-if" => "ctrl.hasDisabledStackComponents()"} %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledStackComponents()"} Restore Disabled Components - %li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;"} + %li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} %label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"} - %li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;"} + %li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} %label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index 42452128b..b0943a8f9 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -1,10 +1,10 @@ #footer-bar .pull-left - .footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;"} + .footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} %a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"} - .footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;"} + .footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} %a{"ng-click" => "ctrl.toggleExtensions()"} Extensions %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index 76c75fa52..c5514fa91 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -9,11 +9,11 @@ #search-clear-button{"ng-if" => "ctrl.noteFilter.text", "ng-click" => "ctrl.noteFilter.text = ''; ctrl.filterTextChanged()"} ✕ %ul.section-menu-bar#notes-menu-bar %li.item-with-subtitle{"ng-class" => "{'selected' : ctrl.showMenu}"} - .wrapper{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"} + .wrapper{"ng-click" => "ctrl.showMenu = !ctrl.showMenu", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} %label Options .subtitle {{ctrl.optionsSubtitle()}} - .sectioned-menu.dropdown-menu{"ng-if" => "ctrl.showMenu", "click-outside" => "ctrl.showMenu = false;"} + .sectioned-menu.dropdown-menu{"ng-if" => "ctrl.showMenu"} %ul .header .title Sort by From 38f2e345d935f0095415babb7506fa5a5afe24ed Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 27 Dec 2017 11:09:49 -0600 Subject: [PATCH 11/99] Updated SingletonManager --- .../javascripts/app/services/authManager.js | 2 +- .../app/services/componentManager.js | 1 - .../app/services/singletonManager.js | 42 +++++++++++++++---- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index ef29e12f9..4e7f1936f 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -296,7 +296,7 @@ angular.module('app.frontend') let prefsContentType = "SN|UserPreferences"; singletonManager.registerSingleton({content_type: prefsContentType}, (resolvedSingleton) => { - console.log("AuthManager received resolved", resolvedSingleton); + console.log("AuthManager received resolved UserPreferences", resolvedSingleton); this.userPreferences = resolvedSingleton; }, () => { // Safe to create. Create and return object. diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 46b71f3c6..8fc753043 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -48,7 +48,6 @@ class ComponentManager { recursion caused by the component being modified and saved after it is updated. */ if(syncedComponents.length > 0 && source != ModelManager.MappingSourceRemoteSaved) { - console.log("Web, Syncing Components", syncedComponents, "source", source); // Ensure any component in our data is installed by the system this.desktopManager.syncComponentsInstallation(syncedComponents); } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 07e892c82..3f6d260e7 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -1,7 +1,11 @@ /* The SingletonManager allows controllers to register an item as a singleton, which means only one instance of that model should exist, both on the server and on the client. When the SingletonManager detects multiple items matching the singleton predicate, - the oldest ones will be deleted, leaving the newest ones + the oldest ones will be deleted, leaving the newest ones. + + We will treat the model most recently arrived from the server as the most recent one. The reason for this is, if you're offline, + a singleton can be created, as in the case of UserPreferneces. Then when you sign in, you'll retrieve your actual user preferences. + In that case, even though the offline singleton has a more recent updated_at, the server retreived value is the one we care more about. */ class SingletonManager { @@ -16,13 +20,14 @@ class SingletonManager { }) $rootScope.$on("sync:completed", (event, data) => { - console.log("Sync completed", data); this.resolveSingletons(data.retrievedItems || []); }) + // Testing code to make sure only 1 exists setTimeout(function () { - var userPrefsTotal = modelManager.itemsForContentType("SN|UserPreferences"); - console.log("All extant prefs", userPrefsTotal); + var userPrefs = modelManager.itemsForContentType("SN|UserPreferences"); + console.assert(userPrefs.length == 1); + console.log("All extant prefs", userPrefs); }, 1000); } @@ -44,25 +49,46 @@ class SingletonManager { var predicate = singletonHandler.predicate; var singletonItems = this.filterItemsWithPredicate(retrievedItems, predicate); if(singletonItems.length > 0) { - // Check local inventory and make sure only 1 similar item exists. If more than 1, delete oldest + /* + Check local inventory and make sure only 1 similar item exists. If more than 1, delete oldest + Note that this local inventory will also contain whatever is in retrievedItems. + However, as stated in the header comment, retrievedItems take precendence over existing items, + even if they have a lower updated_at value + */ var allExtantItemsMatchingPredicate = this.filterItemsWithPredicate(this.modelManager.allItems, predicate); + /* + If there are more than 1 matches, delete everything not in `singletonItems`, + then delete all but the latest in `singletonItems` + */ if(allExtantItemsMatchingPredicate.length >= 2) { - // Purge old ones - var sorted = allExtantItemsMatchingPredicate.sort((a, b) => { + var toDelete = []; + for(var extantItem of allExtantItemsMatchingPredicate) { + if(!singletonItems.includes(extantItem)) { + // Delete it + toDelete.push(extantItem); + } + } + + // Sort incoming singleton items by most recently updated first, then delete all the rest + var sorted = singletonItems.sort((a, b) => { return a.updated_at < b.updated_at; }) - var toDelete = sorted.slice(1, sorted.length); + // Delete everything but the first one + toDelete = toDelete.concat(sorted.slice(1, sorted.length)); + for(var d of toDelete) { this.modelManager.setItemToBeDeleted(d); } this.$rootScope.sync(); + // Send remaining item to callback var singleton = sorted[0]; singletonHandler.singleton = singleton; singletonHandler.resolutionCallback(singleton); + } else if(allExtantItemsMatchingPredicate.length == 1) { if(!singletonHandler.singleton) { // Not yet notified interested parties of object From 8c15438d00ebef3fb352585d91889408b584f4ae Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 27 Dec 2017 12:17:29 -0600 Subject: [PATCH 12/99] Resizable panels and user prefs --- app/assets/javascripts/app/app.frontend.js | 4 + .../app/frontend/controllers/editor.js | 83 +++++++- .../app/frontend/controllers/notes.js | 95 +++++++-- .../app/frontend/controllers/tags.js | 22 +- .../javascripts/app/services/authManager.js | 24 +++ .../services/directives/views/panelResizer.js | 201 ++++++++++++++++++ app/assets/stylesheets/app/_editor.scss | 29 +-- app/assets/stylesheets/app/_main.scss | 90 +++++--- app/assets/stylesheets/app/_menus.scss | 30 ++- app/assets/stylesheets/app/_notes.scss | 29 +-- app/assets/stylesheets/app/_tags.scss | 25 +-- .../directives/panel-resizer.html.haml | 1 + .../templates/frontend/editor.html.haml | 57 ++--- app/assets/templates/frontend/home.html.haml | 2 +- app/assets/templates/frontend/notes.html.haml | 67 +++--- app/assets/templates/frontend/tags.html.haml | 7 +- 16 files changed, 603 insertions(+), 163 deletions(-) create mode 100644 app/assets/javascripts/app/services/directives/views/panelResizer.js create mode 100644 app/assets/templates/frontend/directives/panel-resizer.html.haml diff --git a/app/assets/javascripts/app/app.frontend.js b/app/assets/javascripts/app/app.frontend.js index 80a607ee3..6affad2d1 100644 --- a/app/assets/javascripts/app/app.frontend.js +++ b/app/assets/javascripts/app/app.frontend.js @@ -36,3 +36,7 @@ function parametersFromURL(url) { function isDesktopApplication() { return window && window.process && window.process.type && window.process.versions["electron"]; } + +function isMacApplication() { + return window && window.process && window.process.type && window.process.platform == "darwin"; +} diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index b976d90fb..047264ffa 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -50,13 +50,20 @@ angular.module('app.frontend') this.showMenu = false; this.loadTagsString(); + let onReady = () => { + this.noteReady = true; + $timeout(() => { + this.loadPreferences(); + }) + } + let associatedEditor = this.editorForNote(note); if(associatedEditor) { // setting note to not ready will remove the editor from view in a flash, // so we only want to do this if switching between external editors this.noteReady = false; } else { - this.noteReady = true; + onReady(); } if(this.editorComponent && this.editorComponent != associatedEditor) { @@ -71,10 +78,10 @@ angular.module('app.frontend') $timeout(() => { this.enableComponent(associatedEditor); this.editorComponent = associatedEditor; - this.noteReady = true; + onReady(); }) } else { - this.noteReady = true; + onReady(); } if(note.safeText().length == 0 && note.dummy) { @@ -124,6 +131,10 @@ angular.module('app.frontend') this.note.setAppDataItem("prefersPlainEditor", true); this.note.setDirty(true); syncManager.sync(); + + $timeout(() => { + this.reloadFont(); + }) } this.editorComponent = editorComponent; @@ -322,7 +333,73 @@ angular.module('app.frontend') } + /* Resizability */ + this.resizeControl = {}; + + this.onPanelResizeFinish = function(width, left, isMaxWidth) { + if(isMaxWidth) { + authManager.setUserPrefValue("editorWidth", null); + } else { + if(width !== undefined && width !== null) { + authManager.setUserPrefValue("editorWidth", width); + } + } + + if(left !== undefined && left !== null) { + authManager.setUserPrefValue("editorLeft", left); + } + authManager.syncUserPreferences(); + } + + $rootScope.$on("user-preferences-changed", () => { + this.loadPreferences(); + }); + + this.loadPreferences = function() { + this.monospaceFont = authManager.getUserPrefValue("monospaceFont", "monospace"); + + if(!document.getElementById("editor-content")) { + // Elements have not yet loaded due to ng-if around wrapper + return; + } + + this.reloadFont(); + + let width = authManager.getUserPrefValue("editorWidth", null); + if(width !== null) { + this.resizeControl.setWidth(width); + } + + let left = authManager.getUserPrefValue("editorLeft", null); + if(left !== null) { + this.resizeControl.setLeft(left); + } + } + + this.reloadFont = function() { + var editable = document.getElementById("note-text-editor"); + + if(!editable) { + return; + } + + if(this.monospaceFont) { + if(isMacApplication()) { + editable.style.fontFamily = "Menlo, Consolas, 'DejaVu Sans Mono', monospace"; + } else { + editable.style.fontFamily = "monospace"; + } + } else { + editable.style.fontFamily = "inherit"; + } + } + + this.toggleKey = function(key) { + this[key] = !this[key]; + authManager.setUserPrefValue(key, this[key], true); + this.reloadFont(); + } diff --git a/app/assets/javascripts/app/frontend/controllers/notes.js b/app/assets/javascripts/app/frontend/controllers/notes.js index 2f35f5d98..c5660f93d 100644 --- a/app/assets/javascripts/app/frontend/controllers/notes.js +++ b/app/assets/javascripts/app/frontend/controllers/notes.js @@ -33,21 +33,61 @@ angular.module('app.frontend') }) .controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager, storageManager) { - this.sortBy = storageManager.getItem("sortBy") || "created_at"; - this.showArchived = storageManager.getBooleanValue("showArchived") || false; - this.sortDescending = this.sortBy != "title"; + this.panelController = {}; + + $rootScope.$on("user-preferences-changed", () => { + this.loadPreferences(); + }); + + this.loadPreferences = function() { + this.sortBy = authManager.getUserPrefValue("sortBy", "created_at"); + this.sortDescending = this.sortBy != "title"; + + this.showArchived = authManager.getUserPrefValue("showArchived", false); + this.hidePinned = authManager.getUserPrefValue("hidePinned", false); + this.hideNotePreview = authManager.getUserPrefValue("hideNotePreview", false); + this.hideDate = authManager.getUserPrefValue("hideDate", false); + this.hideTags = authManager.getUserPrefValue("hideTags", false); + + let width = authManager.getUserPrefValue("notesPanelWidth"); + if(width) { + this.panelController.setWidth(width); + } + } + + this.loadPreferences(); + + this.onPanelResize = function(newWidth) { + authManager.setUserPrefValue("notesPanelWidth", newWidth); + authManager.syncUserPreferences(); + } + + angular.element(document).ready(() => { + this.loadPreferences(); + }); $rootScope.$on("editorFocused", function(){ this.showMenu = false; }.bind(this)) $rootScope.$on("noteDeleted", function() { - this.selectFirstNote(false); + $timeout(this.onNoteRemoval.bind(this)); }.bind(this)) $rootScope.$on("noteArchived", function() { - this.selectFirstNote(false); - }.bind(this)) + $timeout(this.onNoteRemoval.bind(this)); + }.bind(this)); + + + // When a note is removed from the list + this.onNoteRemoval = function() { + let visibleNotes = this.visibleNotes(); + if(this.selectedIndex < visibleNotes.length) { + this.selectNote(visibleNotes[this.selectedIndex]); + } else { + this.selectNote(visibleNotes[visibleNotes.length - 1]); + } + } this.DefaultNotesToDisplayValue = 20; @@ -56,26 +96,39 @@ angular.module('app.frontend') this.notesToDisplay += this.DefaultNotesToDisplayValue } + this.panelTitle = function() { + if(this.noteFilter.text.length) { + return `${this.tag.notes.filter((i) => {return i.visible;}).length} search results`; + } else if(this.tag) { + return `${this.tag.title} notes`; + } + } + this.optionsSubtitle = function() { - var base = "Sorting by"; + var base = ""; if(this.sortBy == "created_at") { - base += " date added"; + base += " Date Added"; } else if(this.sortBy == "updated_at") { - base += " date modifed"; + base += " Date Modifed"; } else if(this.sortBy == "title") { - base += " title"; + base += " Title"; } if(this.showArchived && (!this.tag || !this.tag.archiveTag)) { - base += " | Including archived" + base += " | + Archived" + } + + if(this.hidePinned) { + base += " | – Pinned" } return base; } - this.toggleShowArchived = function() { - this.showArchived = !this.showArchived; - storageManager.setBooleanValue("showArchived", this.showArchived); + this.toggleKey = function(key) { + this[key] = !this[key]; + authManager.setUserPrefValue(key, this[key]); + authManager.syncUserPreferences(); } this.tagDidChange = function(tag, oldTag) { @@ -108,10 +161,14 @@ angular.module('app.frontend') this.selectFirstNote(createNew); } - this.selectFirstNote = function(createNew) { - var visibleNotes = this.sortedNotes.filter(function(note){ + this.visibleNotes = function() { + return this.sortedNotes.filter(function(note){ return note.visible; }); + } + + this.selectFirstNote = function(createNew) { + var visibleNotes = this.visibleNotes(); if(visibleNotes.length > 0) { this.selectNote(visibleNotes[0]); @@ -121,6 +178,7 @@ angular.module('app.frontend') } this.selectNote = function(note) { + if(!note) { return; } this.selectedNote = note; note.conflict_of = null; // clear conflict this.selectionMade()(note); @@ -142,7 +200,7 @@ angular.module('app.frontend') return note.visible; } - if(note.archived && !this.showArchived) { + if((note.archived && !this.showArchived) || (note.pinned && this.hidePinned)) { note.visible = false; return note.visible; } @@ -188,7 +246,8 @@ angular.module('app.frontend') this.setSortBy = function(type) { this.sortBy = type; - storageManager.setItem("sortBy", type); + authManager.setUserPrefValue("sortBy", this.sortBy); + authManager.syncUserPreferences(); } }); diff --git a/app/assets/javascripts/app/frontend/controllers/tags.js b/app/assets/javascripts/app/frontend/controllers/tags.js index e7f686b8e..549649f80 100644 --- a/app/assets/javascripts/app/frontend/controllers/tags.js +++ b/app/assets/javascripts/app/frontend/controllers/tags.js @@ -34,10 +34,30 @@ angular.module('app.frontend') } } }) - .controller('TagsCtrl', function ($rootScope, modelManager, $timeout, componentManager) { + .controller('TagsCtrl', function ($rootScope, modelManager, $timeout, componentManager, authManager) { var initialLoad = true; + this.panelController = {}; + + $rootScope.$on("user-preferences-changed", () => { + this.loadPreferences(); + }); + + this.loadPreferences = function() { + let width = authManager.getUserPrefValue("tagsPanelWidth"); + if(width) { + this.panelController.setWidth(width); + } + } + + this.loadPreferences(); + + this.onPanelResize = function(newWidth) { + authManager.setUserPrefValue("tagsPanelWidth", newWidth); + authManager.syncUserPreferences(); + } + this.componentManager = componentManager; componentManager.registerHandler({identifier: "tags", areas: ["tags-list"], activationHandler: function(component){ diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 4e7f1936f..2c6d4c7e9 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -298,6 +298,7 @@ angular.module('app.frontend') singletonManager.registerSingleton({content_type: prefsContentType}, (resolvedSingleton) => { console.log("AuthManager received resolved UserPreferences", resolvedSingleton); this.userPreferences = resolvedSingleton; + this.userPreferencesDidChange(); }, () => { // Safe to create. Create and return object. var prefs = new Item({content_type: prefsContentType}); @@ -308,5 +309,28 @@ angular.module('app.frontend') return prefs; }); + this.userPreferencesDidChange = function() { + $rootScope.$broadcast("user-preferences-changed"); + } + + this.syncUserPreferences = function() { + this.userPreferences.setDirty(true); + $rootScope.sync(); + } + + this.getUserPrefValue = function(key, defaultValue) { + if(!this.userPreferences) { return; } + 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/directives/views/panelResizer.js b/app/assets/javascripts/app/services/directives/views/panelResizer.js new file mode 100644 index 000000000..3b6d9e87e --- /dev/null +++ b/app/assets/javascripts/app/services/directives/views/panelResizer.js @@ -0,0 +1,201 @@ +class PanelResizer { + + constructor() { + this.restrict = "E"; + this.templateUrl = "frontend/directives/panel-resizer.html"; + this.scope = { + index: "=", + panelId: "=", + onResize: "&", + onResizeFinish: "&", + control: "=", + alwaysVisible: "=", + minWidth: "=", + property: "=", + hoverable: "=", + collapsable: "=" + }; + } + + link(scope, elem, attrs, ctrl) { + scope.elem = elem; + + scope.control.setWidth = function(value) { + scope.setWidth(value, true); + } + + scope.control.setLeft = function(value) { + scope.setLeft(value); + } + } + + controller($scope, $element, modelManager, extensionManager) { + 'ngInject'; + + let panel = document.getElementById($scope.panelId); + if(!panel) { + console.log("Panel not found for", $scope.panelId); + } + let resizerColumn = $element[0]; + let resizerWidth = resizerColumn.offsetWidth; + let minWidth = $scope.minWidth || resizerWidth; + + function getParentRect() { + return panel.parentNode.getBoundingClientRect(); + } + + var pressed = false; + var startWidth = panel.scrollWidth, startX, lastDownX, collapsed, lastWidth = startWidth, startLeft, lastLeft; + let appFrame = document.getElementById("app").getBoundingClientRect(); + + if($scope.alwaysVisible) { + console.log("Adding always visible", $scope.alwaysVisible); + resizerColumn.classList.add("always-visible"); + } + + if($scope.hoverable) { + resizerColumn.classList.add("hoverable"); + } + + $scope.setWidth = function(width, finish) { + if(width < minWidth) { + width = minWidth; + } + + let parentRect = getParentRect(); + + if(width > parentRect.width) { + width = parentRect.width; + } + + if(width == parentRect.width) { + panel.style.width = "100%"; + panel.style.flexBasis = "100%"; + } else { + panel.style.flexBasis = width + "px"; + panel.style.width = width + "px"; + } + + + lastWidth = width; + + if(finish) { + $scope.finishSettingWidth(); + } + } + + $scope.setLeft = function(left) { + panel.style.left = left + "px"; + lastLeft = left; + } + + $scope.finishSettingWidth = function() { + if(!$scope.collapsable) { + return; + } + + + if(lastWidth <= minWidth) { + collapsed = true; + } else { + collapsed = false; + } + if(collapsed) { + resizerColumn.classList.add("collapsed"); + } else { + resizerColumn.classList.remove("collapsed"); + } + } + + resizerColumn.addEventListener("mousedown", function(event){ + pressed = true; + lastDownX = event.clientX; + startWidth = panel.scrollWidth; + startLeft = panel.offsetLeft; + panel.classList.add("no-selection"); + + if($scope.hoverable) { + resizerColumn.classList.add("dragging"); + } + }) + + document.addEventListener("mousemove", function(event){ + if(!pressed) { + return; + } + + event.preventDefault(); + + if($scope.property && $scope.property == 'left') { + handleLeftEvent(event); + } else { + handleWidthEvent(event); + } + }) + + function handleWidthEvent(event) { + var rect = panel.getBoundingClientRect(); + var panelMaxX = rect.left + (startWidth || panel.style.maxWidth); + + var x = event.clientX; + + let deltaX = x - lastDownX; + var newWidth = startWidth + deltaX; + + $scope.setWidth(newWidth, false); + + if($scope.onResize()) { + $scope.onResize()(lastWidth, panel); + } + } + + function handleLeftEvent(event) { + var panelRect = panel.getBoundingClientRect(); + var x = event.clientX; + let deltaX = x - lastDownX; + var newLeft = startLeft + deltaX; + if(newLeft < 0) { + newLeft = 0; + deltaX = -startLeft; + } + + let parentRect = getParentRect(); + + var newWidth = startWidth - deltaX; + if(newWidth < minWidth) { + newWidth = minWidth; + } + + if(newWidth > parentRect.width) { + newWidth = parentRect.width; + } + + + if(newLeft + newWidth > parentRect.width) { + newLeft = parentRect.width - newWidth; + } + + $scope.setLeft(newLeft, false); + $scope.setWidth(newWidth, false); + } + + document.addEventListener("mouseup", function(event){ + if(pressed) { + pressed = false; + resizerColumn.classList.remove("dragging"); + panel.classList.remove("no-selection"); + + let isMaxWidth = lastWidth == getParentRect().width; + + if($scope.onResizeFinish) { + $scope.onResizeFinish()(lastWidth, lastLeft, isMaxWidth); + } + + $scope.finishSettingWidth(); + } + }) + } + +} + +angular.module('app.frontend').directive('panelResizer', () => new PanelResizer); diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index 0860d57be..98b896f70 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -1,21 +1,11 @@ $heading-height: 75px; .editor { flex: 1 50%; - min-width: 300px; display: flex; flex-direction: column; overflow-y: hidden; background-color: white; - &.fullscreen { - width: 100%; - position: absolute; - left: 0px; - top: 0px; - z-index: 200; - padding: 0; - } - .section-menu-bar { flex: 1 0 28px; max-height: 28px; @@ -37,10 +27,6 @@ $heading-height: 75px; height: auto; overflow: visible; - &.fullscreen { - position: relative; - } - > .title { font-size: 18px; font-weight: bold; @@ -103,7 +89,7 @@ $heading-height: 75px; } } -.editor-content { +.editor-content, #editor-content { flex: 1; z-index: 10; overflow-y: hidden; @@ -111,9 +97,7 @@ $heading-height: 75px; display: flex; background-color: white; - &.fullscreen { - padding-top: 0px; - } + position: relative; #editor-iframe { flex: 1; @@ -122,7 +106,6 @@ $heading-height: 75px; .editable { font-family: monospace; - flex: 1; overflow-y: scroll; width: 100%; @@ -132,14 +115,6 @@ $heading-height: 75px; padding-top: 11px; font-size: 17px; resize: none; - - &.fullscreen { - padding: 85px 10%; - max-width: 1200px; - display: inline-block; - margin-left: auto; - margin-right: auto; - } } } diff --git a/app/assets/stylesheets/app/_main.scss b/app/assets/stylesheets/app/_main.scss index 83d9f0bad..39ab8bdb1 100644 --- a/app/assets/stylesheets/app/_main.scss +++ b/app/assets/stylesheets/app/_main.scss @@ -110,10 +110,7 @@ p { $footer-height: 25px; -$section-header-height: 70px; - .app { - // height: 100%; height: calc(100% - #{$footer-height}); width: 100%; display: flex; @@ -136,11 +133,48 @@ $section-header-height: 70px; } } + panel-resizer { + top: 0; + right: 0; + z-index: 1; + width: 8px; + height: 100%; + position: absolute; + cursor: col-resize; + background-color: rgb(224, 224, 224); + opacity: 0; + + &.left { + left: 0; + right: none; + } + + &.always-visible { + opacity: 1; + } + + &.collapsed { + opacity: 1; + } + + &.dragging { + opacity: 1; + } + + &.hoverable { + &:hover { + opacity: 1; + } + } + } + .section { padding-bottom: 0px; height: 100%; max-height: calc(100vh - #{$footer-height}); font-size: 17px; + position: relative; + overflow: hidden; .scrollable { overflow-y: auto; @@ -155,34 +189,40 @@ $section-header-height: 70px; } .section-title-bar { - padding: 20px; - height: $section-header-height; font-weight: bold; font-size: 14px; - > .title { - float: left; - white-space: nowrap; - text-overflow: ellipsis; - width: 80%; - overflow: hidden; + .padded { + padding: 0 14px; } - > .add-button { - float: right; - font-size: 18px; - width: 45px; - height: 24px; - cursor: pointer; - background-color: #e9e9e9; - border-radius: 4px; - font-weight: normal; - text-align: center; - position: absolute; - right: 12px; + .section-title-bar-header { + display: flex; + justify-content: space-between; + align-items: center; + overflow: hidden; - &:hover { - background-color: rgba(#e9e9e9, 0.8); + > .title { + white-space: nowrap; + text-overflow: ellipsis; + width: 80%; + overflow: hidden; + } + + > .add-button { + + font-size: 18px; + width: 45px; + height: 24px; + cursor: pointer; + background-color: #e9e9e9; + border-radius: 4px; + font-weight: normal; + text-align: center; + + &:hover { + background-color: rgba(#e9e9e9, 0.8); + } } } } diff --git a/app/assets/stylesheets/app/_menus.scss b/app/assets/stylesheets/app/_menus.scss index 8e851aad6..4bbfab8af 100644 --- a/app/assets/stylesheets/app/_menus.scss +++ b/app/assets/stylesheets/app/_menus.scss @@ -4,6 +4,11 @@ ul.section-menu-bar { padding-left: 6px; padding-right: 21px; + &.no-h-padding { + padding-left: 0px; + padding-right: 0px; + } + user-select: none; background-color: #f1f1f1; @@ -29,6 +34,7 @@ ul.section-menu-bar { &.full-width { width: 100%; + padding-left: 14px; } &.item-with-subtitle { @@ -43,14 +49,14 @@ ul.section-menu-bar { font-weight: normal; font-size: 12px; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; /* number of lines to show */ - $line-height: 18px; - line-height: $line-height; /* fallback */ - max-height: calc(#{$line-height} * 1); /* fallback */ + // overflow: hidden; + // text-overflow: ellipsis; + // display: -webkit-box; + // -webkit-box-orient: vertical; + // -webkit-line-clamp: 1; /* number of lines to show */ + // $line-height: 18px; + // line-height: $line-height; /* fallback */ + // max-height: calc(#{$line-height} * 1); /* fallback */ } } @@ -79,9 +85,15 @@ ul.section-menu-bar { border: none; width: 280px; + &.full-width { + width: 100%; + } + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 50px rgba(0, 0, 0, 0.175); background-clip: padding-box; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; background-color: white; color: $selected-text-color; diff --git a/app/assets/stylesheets/app/_notes.scss b/app/assets/stylesheets/app/_notes.scss index 7dd46dfd7..7075a8b87 100644 --- a/app/assets/stylesheets/app/_notes.scss +++ b/app/assets/stylesheets/app/_notes.scss @@ -1,21 +1,23 @@ -.notes { +#notes-column, .notes { border-left: 1px solid #dddddd; border-right: 1px solid #dddddd; - flex: 1 20%; - max-width: 350px; - min-width: 170px; - + width: 350px; + flex-grow: 0; user-select: none; - $notes-title-bar-height: 148px; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + + .content { + display: flex; + flex-direction: column; + } #notes-title-bar { color: rgba(black, 0.40); padding-top: 16px; - padding-left: 14px; - padding-right: 14px; - height: $notes-title-bar-height; font-weight: normal; font-size: 18px; @@ -25,21 +27,20 @@ } #notes-add-button { - right: 14px; + } #notes-menu-bar { position: relative; - margin: 0 -14px; margin-top: 14px; - height: 45px; + height: auto; width: auto; } .filter-section { clear: left; height: 32px; - margin-top: 20px; + margin-top: 14px; position: relative; .filter-bar { @@ -80,7 +81,7 @@ } .scrollable { - height: calc(100vh - (#{$notes-title-bar-height} + #{$footer-height})); + height: 100%; } .infinite-scroll { diff --git a/app/assets/stylesheets/app/_tags.scss b/app/assets/stylesheets/app/_tags.scss index d0e1e31db..79f0fb0b9 100644 --- a/app/assets/stylesheets/app/_tags.scss +++ b/app/assets/stylesheets/app/_tags.scss @@ -1,21 +1,20 @@ .tags { - flex: 1 10%; - max-width: 180px; - min-width: 100px; width: 180px; - background-color: #f6f6f6; - - user-select: none; + flex-grow: 0; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; $tags-title-bar-height: 55px; #tags-title-bar { - color: black; - height: $tags-title-bar-height; - padding-left: 12px; - padding-right: 12px; - font-size: 12px; - color: rgba(black, 0.8); + color: black; + height: $tags-title-bar-height; + padding-top: 14px; + padding-left: 12px; + padding-right: 12px; + font-size: 12px; + color: rgba(black, 0.8); } #tags-content { @@ -23,9 +22,7 @@ } #tag-add-button { - margin-top: -6px; background-color: #d7d7d7; - float: right; &:hover { background-color: rgba(#d7d7d7, 0.8); diff --git a/app/assets/templates/frontend/directives/panel-resizer.html.haml b/app/assets/templates/frontend/directives/panel-resizer.html.haml new file mode 100644 index 000000000..b2822c8c3 --- /dev/null +++ b/app/assets/templates/frontend/directives/panel-resizer.html.haml @@ -0,0 +1 @@ +.panel-resizer-column diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 9b9ddf714..447265d35 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -1,8 +1,8 @@ -.section.editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen}"} - #editor-title-bar.section-title-bar{"ng-show" => "ctrl.note && !ctrl.note.errorDecrypting", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"} +.section.editor#editor-column + #editor-title-bar.section-title-bar{"ng-show" => "ctrl.note && !ctrl.note.errorDecrypting"} .title %input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)", - "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", + "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", "ng-blur" => "ctrl.onNameBlur()", "select-on-click" => "true"} #save-status{"ng-class" => "{'red bold': ctrl.saveError, 'orange bold': ctrl.syncTakingTooLong}", "ng-bind-html" => "ctrl.noteStatus"} @@ -16,26 +16,31 @@ %li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} %label{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;"} Menu - %ul.dropdown-menu.sectioned-menu{"ng-if" => "ctrl.showMenu"} - %li - %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} - %i.icon.ion-ios-flag - {{ctrl.note.pinned ? "Unpin" : "Pin"}} - %li - %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} - %i.icon.ion-ios-box - {{ctrl.note.archived ? "Unarchive" : "Archive"}} - %li - %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} - %i.icon.ion-trash-b - Delete - %li - %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleFullScreen()"} - %i.icon.ion-arrow-expand - Toggle Fullscreen - - %li{"ng-if" => "ctrl.hasDisabledStackComponents()"} - %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledStackComponents()"} Restore Disabled Components + .dropdown-menu.sectioned-menu{"ng-if" => "ctrl.showMenu"} + %ul + .header + .title Note + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} + %label + %i.icon.ion-ios-flag + {{ctrl.note.pinned ? "Unpin" : "Pin"}} + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} + %label + %i.icon.ion-ios-box + {{ctrl.note.archived ? "Unarchive" : "Archive"}} + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} + %label + %i.icon.ion-trash-b + Delete + %li{"ng-if" => "ctrl.hasDisabledComponents()"} + %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledComponents()"} Restore Disabled Components + %ul{"ng-if" => "!ctrl.editor"} + .header + .title Display + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('monospaceFont')"} + %label + %span.top.mt-5.mr-5{"ng-if" => "ctrl.monospaceFont == true"} ✓ + Monospace Font %li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} %label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor @@ -45,12 +50,14 @@ %label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} - .editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"} + .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"} %iframe#editor-iframe{"ng-if" => "ctrl.editorComponent && ctrl.editorComponent.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.editorComponent) | trusted}}", "data-component-id" => "{{ctrl.editorComponent.uuid}}", "frameBorder" => "0", "style" => "width: 100%;"} Loading - %textarea.editable#note-text-editor{"ng-if" => "!ctrl.editorComponent", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text", + %textarea.editable#note-text-editor{"ng-if" => "!ctrl.editorComponent", "ng-model" => "ctrl.note.text", "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} {{ctrl.onSystemEditorLoad()}} + %panel-resizer{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "hoverable" => "true"} %section.section{"ng-if" => "ctrl.note.errorDecrypting"} diff --git a/app/assets/templates/frontend/home.html.haml b/app/assets/templates/frontend/home.html.haml index bf98088c2..eb2b1f7b4 100644 --- a/app/assets/templates/frontend/home.html.haml +++ b/app/assets/templates/frontend/home.html.haml @@ -1,6 +1,6 @@ .main-ui-view %lock-screen{"ng-if" => "needsUnlock", "on-success" => "onSuccessfulUnlock"} - .app{"ng-if" => "!needsUnlock"} + .app#app{"ng-if" => "!needsUnlock"} %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "will-select" => "tagsWillMakeSelection", "selection-made" => "tagsSelectionMade", "all-tag" => "allTag", "archive-tag" => "archiveTag", "tags" => "tags", "remove-tag" => "removeTag"} diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index c5514fa91..1baa45463 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -1,41 +1,58 @@ -.section.notes +.section.notes#notes-column .content .section-title-bar#notes-title-bar - .title {{ctrl.tag.title}} notes - .add-button#notes-add-button{"ng-click" => "ctrl.createNewNote()"} + - %br - .filter-section - %input.filter-bar{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Search", "ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true"} - #search-clear-button{"ng-if" => "ctrl.noteFilter.text", "ng-click" => "ctrl.noteFilter.text = ''; ctrl.filterTextChanged()"} ✕ - %ul.section-menu-bar#notes-menu-bar - %li.item-with-subtitle{"ng-class" => "{'selected' : ctrl.showMenu}"} - .wrapper{"ng-click" => "ctrl.showMenu = !ctrl.showMenu", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} - %label Options - .subtitle {{ctrl.optionsSubtitle()}} - - .sectioned-menu.dropdown-menu{"ng-if" => "ctrl.showMenu"} + .padded + .section-title-bar-header + .title {{ctrl.panelTitle()}} + .add-button#notes-add-button{"ng-click" => "ctrl.createNewNote()"} + + .filter-section + %input.filter-bar#search-bar.mousetrap{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Search", "ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true"} + #search-clear-button{"ng-if" => "ctrl.noteFilter.text", "ng-click" => "ctrl.noteFilter.text = ''; ctrl.filterTextChanged()"} ✕ + %ul.section-menu-bar#notes-menu-bar.no-h-padding + %li.item-with-subtitle.full-width{"ng-class" => "{'selected' : ctrl.showMenu}"} + .wrapper{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"} + %label + Options + %span.subtitle {{ctrl.optionsSubtitle()}} + .sectioned-menu.dropdown-menu.full-width{"ng-if" => "ctrl.showMenu"} %ul .header - .title Sort by + .title Sort By %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByCreated()"} %label %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'created_at'"} ✓ - By date added + By Date Added %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByUpdated()"} %label %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'updated_at'"} ✓ - By date modified + By Date Modified %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByTitle()"} %label %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'title'"} ✓ - By title + By Title %ul{"ng-if" => "!ctrl.tag.archiveTag"} .header - .title Archives - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleShowArchived()"} + .title Display + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('showArchived')"} %label %span.top.mt-5.mr-5{"ng-if" => "ctrl.showArchived == true"} ✓ - Show archived notes + Show Archived Notes + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hidePinned')"} + %label + %span.top.mt-5.mr-5{"ng-if" => "ctrl.hidePinned == true"} ✓ + Hide Pinned Notes + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideNotePreview')"} + %label + %span.top.mt-5.mr-5{"ng-if" => "ctrl.hideNotePreview == true"} ✓ + Hide Note Preview + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideDate')"} + %label + %span.top.mt-5.mr-5{"ng-if" => "ctrl.hideDate == true"} ✓ + Hide Date + %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideTags')"} + %label + %span.top.mt-5.mr-5{"ng-if" => "ctrl.hideTags == true"} ✓ + Hide Tags .scrollable .infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"} @@ -52,13 +69,15 @@ %i.icon.ion-ios-box %strong.medium Archived - .tags-string{"ng-if" => "ctrl.tag.all"} + .tags-string{"ng-if" => "ctrl.tag.all && !ctrl.hideTags"} .faded {{note.tagsString()}} .name{"ng-if" => "note.title"} {{note.title}} - .note-preview + .note-preview{"ng-if" => "!ctrl.hideNotePreview"} {{note.text}} - .date.faded + .date.faded{"ng-if" => "!ctrl.hideDate"} %span{"ng-if" => "ctrl.sortBy == 'updated_at'"} Modified {{note.updatedAtString() || 'Now'}} %span{"ng-if" => "ctrl.sortBy != 'updated_at'"} {{note.createdAtString() || 'Now'}} + + %panel-resizer{"panel-id" => "'notes-column'", "on-resize-finish" => "ctrl.onPanelResize", "control" => "ctrl.panelController", "hoverable" => "true", "collapsable" => "true"} diff --git a/app/assets/templates/frontend/tags.html.haml b/app/assets/templates/frontend/tags.html.haml index 5a5a4a807..d9cea08ee 100644 --- a/app/assets/templates/frontend/tags.html.haml +++ b/app/assets/templates/frontend/tags.html.haml @@ -2,8 +2,9 @@ %iframe#tags-list-iframe{"ng-if" => "ctrl.component && ctrl.component.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.component) | trusted}}", "frameBorder" => "0", "style" => "width: 100%; height: 100%;", "sandbox" => "allow-scripts"} #tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"} #tags-title-bar.section-title-bar - .title Tags - .add-button#tag-add-button{"ng-click" => "ctrl.clickedAddNewTag()"} + + .section-title-bar-header + .title Tags + .add-button#tag-add-button{"ng-click" => "ctrl.createNewTag()"} + .scrollable .tag{"ng-if" => "ctrl.allTag", "ng-click" => "ctrl.selectTag(ctrl.allTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.allTag}"} @@ -27,3 +28,5 @@ .tag.faded{"ng-if" => "ctrl.archiveTag", "ng-click" => "ctrl.selectTag(ctrl.archiveTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.archiveTag}"} .info %input.title{"ng-disabled" => "true", "ng-model" => "ctrl.archiveTag.title"} + + %panel-resizer{"panel-id" => "'tags-column'", "on-resize-finish" => "ctrl.onPanelResize", "control" => "ctrl.panelController", "hoverable" => "true", "collapsable" => "true"} From f2d88d287d82e5759399b81254560562f86f5230 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 28 Dec 2017 11:30:36 -0600 Subject: [PATCH 13/99] Default prolink installation --- .../javascripts/app/services/authManager.js | 4 +-- .../app/services/directives/views/roomBar.js | 18 +++++++--- .../javascripts/app/services/modelManager.js | 5 +++ .../app/services/singletonManager.js | 35 +++++++++++++------ .../javascripts/app/services/syncManager.js | 3 ++ app/views/application/frontend.html.erb | 1 + 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 2c6d4c7e9..6c2be7612 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -299,14 +299,14 @@ angular.module('app.frontend') console.log("AuthManager received resolved UserPreferences", resolvedSingleton); this.userPreferences = resolvedSingleton; this.userPreferencesDidChange(); - }, () => { + }, (valueCallback) => { // Safe to create. Create and return object. var prefs = new Item({content_type: prefsContentType}); modelManager.addItem(prefs); prefs.setDirty(true); console.log("Created new prefs", prefs); $rootScope.sync(); - return prefs; + valueCallback(prefs); }); this.userPreferencesDidChange = function() { diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index 63f3d89e4..f237f1aa7 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -7,16 +7,14 @@ class RoomBar { }; } - controller($rootScope, $scope, desktopManager, syncManager, modelManager, componentManager, $timeout) { + controller($rootScope, $scope, desktopManager, syncManager, modelManager, componentManager, $timeout, singletonManager, packageManager) { 'ngInject'; $scope.componentManager = componentManager; $scope.rooms = []; modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { - $scope.rooms = _.uniq($scope.rooms - .concat(allItems - .filter((candidate) => {return candidate.area == "rooms"}))) + $scope.rooms = _.uniq($scope.rooms.concat(allItems.filter((candidate) => {return candidate.area == "rooms"}))) .filter((candidate) => {return !candidate.deleted}); }); @@ -55,6 +53,18 @@ class RoomBar { room.show = false; this.componentManager.deactivateComponent(room); } + + // Handle singleton ProLink instance + singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: "org.standardnotes.prolink"}}, (resolvedSingleton) => { + console.log("Roombar received resolved ProLink", resolvedSingleton); + }, (valueCallback) => { + console.log("Creating prolink"); + // Safe to create. Create and return object. + let url = window._prolink_package_url; + packageManager.installPackage(url, (component) => { + valueCallback(component); + }) + }); } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 70438bc0b..c4f475a7b 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -3,6 +3,7 @@ class ModelManager { constructor(storageManager) { ModelManager.MappingSourceRemoteRetrieved = "MappingSourceRemoteRetrieved"; ModelManager.MappingSourceRemoteSaved = "MappingSourceRemoteSaved"; + ModelManager.MappingSourceLocalSaved = "MappingSourceLocalSaved"; ModelManager.MappingSourceLocalRetrieved = "MappingSourceLocalRetrieved"; ModelManager.MappingSourceComponentRetrieved = "MappingSourceComponentRetrieved"; ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */ @@ -103,6 +104,10 @@ class ModelManager { return tag; } + didSyncModelsOffline(items) { + this.notifySyncObserversOfModels(items, ModelManager.MappingSourceLocalSaved); + } + mapResponseItemsToLocalModels(items, source) { return this.mapResponseItemsToLocalModelsOmittingFields(items, null, source); } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 3f6d260e7..70f92fd96 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -45,7 +45,7 @@ class SingletonManager { } resolveSingletons(retrievedItems, initialLoad) { - for(var singletonHandler of this.singletonHandlers) { + for(let singletonHandler of this.singletonHandlers) { var predicate = singletonHandler.predicate; var singletonItems = this.filterItemsWithPredicate(retrievedItems, predicate); if(singletonItems.length > 0) { @@ -63,7 +63,7 @@ class SingletonManager { */ if(allExtantItemsMatchingPredicate.length >= 2) { var toDelete = []; - for(var extantItem of allExtantItemsMatchingPredicate) { + for(let extantItem of allExtantItemsMatchingPredicate) { if(!singletonItems.includes(extantItem)) { // Delete it toDelete.push(extantItem); @@ -102,10 +102,13 @@ class SingletonManager { // Retrieved items does not include any items of interest. If we don't have a singleton registered to this handler, // we need to create one. Only do this on actual sync completetions and not on initial data load. Because we want // to get the latest from the server before making the decision to create a new item - if(!singletonHandler.singleton && !initialLoad) { - var item = singletonHandler.createBlock(); - singletonHandler.singleton = item; - singletonHandler.resolutionCallback(item); + if(!singletonHandler.singleton && !initialLoad && !singletonHandler.pendingCreateBlockCallback) { + singletonHandler.pendingCreateBlockCallback = true; + singletonHandler.createBlock((created) => { + singletonHandler.singleton = created; + singletonHandler.pendingCreateBlockCallback = false; + singletonHandler.resolutionCallback(created); + }); } } } @@ -113,13 +116,25 @@ class SingletonManager { filterItemsWithPredicate(items, predicate) { return items.filter((candidate) => { - for(var key in predicate) { - if(candidate[key] != predicate[key]) { + return this.itemSatisfiesPredicate(candidate, predicate); + }) + } + + itemSatisfiesPredicate(candidate, predicate) { + for(var key in predicate) { + var predicateValue = predicate[key]; + var candidateValue = candidate[key]; + if(typeof predicateValue == 'object') { + // Check nested properties + if(!this.itemSatisfiesPredicate(candidateValue, predicateValue)) { return false; } } - return true; - }) + else if(candidateValue != predicateValue) { + return false; + } + } + return true; } } diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 17f13fd78..571aac588 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -64,6 +64,9 @@ class SyncManager { this.$rootScope.$broadcast("sync:completed", {}); + // Required in order for modelManager to notify sync observers + this.modelManager.didSyncModelsOffline(items); + if(callback) { callback({success: true}); } diff --git a/app/views/application/frontend.html.erb b/app/views/application/frontend.html.erb index c1dea5432..28d3fb2c9 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/frontend.html.erb @@ -30,6 +30,7 @@ <% if Rails.env.development? %> From 095a0b29b9d24b6100c279a110453725fac1bb5c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 1 Jan 2018 10:17:09 -0600 Subject: [PATCH 14/99] Updates --- app/assets/javascripts/app/app.frontend.js | 4 +++ .../app/services/componentManager.js | 26 ++++++++++++++++--- .../directives/views/permissionsModal.js | 3 ++- .../app/services/singletonManager.js | 5 ++++ app/views/application/frontend.html.erb | 2 +- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/app/app.frontend.js b/app/assets/javascripts/app/app.frontend.js index 6affad2d1..0bdfe017b 100644 --- a/app/assets/javascripts/app/app.frontend.js +++ b/app/assets/javascripts/app/app.frontend.js @@ -40,3 +40,7 @@ function isDesktopApplication() { function isMacApplication() { return window && window.process && window.process.type && window.process.platform == "darwin"; } + +Array.prototype.containsSubset = function(array) { + return !array.some(val => this.indexOf(val) === -1); +} diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 8fc753043..c0dbc2df0 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -431,7 +431,7 @@ class ComponentManager { var requiredPermissions = [ { name: "stream-items", - content_types: component.content_type + content_types: [component.content_type] } ]; @@ -450,9 +450,28 @@ class ComponentManager { for(var required of requiredPermissions) { var matching = _.find(requestedPermissions, required); + var matching = requestedPermissions.filter((p) => { + var matchesContentTypes = true; + if(p.content_types) { + matchesContentTypes = JSON.stringify(p.content_types.sort()) == JSON.stringify(required.content_types.sort()); + } + return p.name == required.name && matchesContentTypes; + })[0]; + console.log("required", required, "requested", requestedPermissions, "matching", matching); if(!matching) { - requestedMatchesRequired = false; - break; + /* Required permissions can be 1 content type, and requestedPermisisons may send an array of content types. + In the case of an array, we can just check to make sure that requiredPermissions content type is found in the array + */ + matching = requestedPermissions.filter((requested) => { + return Array.isArray(requested.content_types) && requested.content_types.containsSubset(required.content_types); + }); + + console.log("Matching 2nd chance", matching); + + if(!matching) { + requestedMatchesRequired = false; + break; + } } } @@ -462,6 +481,7 @@ class ComponentManager { return false; } + if(!component.permissions) { component.permissions = []; } diff --git a/app/assets/javascripts/app/services/directives/views/permissionsModal.js b/app/assets/javascripts/app/services/directives/views/permissionsModal.js index 3007f362f..51604dcb1 100644 --- a/app/assets/javascripts/app/services/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/services/directives/views/permissionsModal.js @@ -29,6 +29,7 @@ class PermissionsModal { } controller($scope, modelManager) { + console.log("permissions", $scope.permissions); $scope.formattedPermissions = $scope.permissions.map(function(permission){ if(permission.name === "stream-items") { var title = "Access to "; @@ -51,7 +52,7 @@ class PermissionsModal { } else if(i == types.length - 1) { // last element if(types.length > 2) { - typesString += separator + "and " + typesString; + typesString += separator + "and " + type; } else if(types.length == 2) { typesString = typesString + " and " + type; } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 70f92fd96..29836eca0 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -126,6 +126,11 @@ class SingletonManager { 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; } diff --git a/app/views/application/frontend.html.erb b/app/views/application/frontend.html.erb index 28d3fb2c9..471176b47 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/frontend.html.erb @@ -30,7 +30,7 @@ <% if Rails.env.development? %> From ca40fc695472db620c2a76325f301d04730e622d Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 2 Jan 2018 16:53:12 -0600 Subject: [PATCH 15/99] Modal css --- app/assets/stylesheets/app/_modals.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index a1bb020c5..d22afe91e 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -84,6 +84,14 @@ } } +.component-modal { + .content { + width: auto; + padding: 0; + padding-bottom: 0; + } +} + .modal-iframe-container { .modal-iframe { From 3a80d16f4b237c1f40b60b77d1fa36e4b25710da Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 4 Jan 2018 21:00:59 -0600 Subject: [PATCH 16/99] StyleKit wip/ --- app/assets/stylesheets/app/_footer.scss | 225 ++++++----------- app/assets/stylesheets/app/_main.scss | 4 +- app/assets/stylesheets/app/_ui.scss | 44 ++-- .../directives/account-menu.html.haml | 228 +++++++++--------- .../directives/permissions-modal.html.haml | 2 +- .../templates/frontend/footer.html.haml | 14 +- app/views/application/frontend.html.erb | 1 + config/initializers/assets.rb | 2 +- vendor/assets/stylesheets/stylekit.css | 201 +++++++++++++++ 9 files changed, 429 insertions(+), 292 deletions(-) create mode 100644 vendor/assets/stylesheets/stylekit.css diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index acd0f887a..6cb0c071d 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -16,54 +16,15 @@ h2 { position: relative; width: 100%; padding: 5px; - background-color: #f1f1f1; - border-top: 1px solid rgba(black, 0.04); + background-color: #F6F6F6; + border-top: 1px solid #D3D3D3; height: $footer-height; max-height: $footer-height; z-index: 100; - font-size: 10px; - color: $dark-gray; - .medium-text { - font-size: 14px; - } - - a { - font-weight: bold; - cursor: pointer; - - &.gray { - color: $dark-gray; - } - - &.block { - display: block; - } - } - - p { - margin: 2px 0px; - font-size: 12px; - } - - label { - font-weight: bold; - margin-bottom: 4px; - } - - h1 { - font-size: 16px; - } - - h3 { - font-size: 14px; - margin-top: 4px; - margin-bottom: 3px; - } - - h4 { - margin-bottom: 4px; - font-size: 13px; + .pull-left, .pull-right { + height: 100%; + display: flex; } section { @@ -80,133 +41,57 @@ h2 { } input { - // margin-bottom: 10px; border-radius: 0px; } - - #room-bar { - display: inline-block; - border-left: 1px solid gray; - padding-left: 10px; - padding-right: 10px; - margin-left: 5px; - position: relative; - - .room-item { - display: inline-block; - position: relative; - vertical-align: middle; - - .label { - display: inline-block; - vertical-align: middle; - font-size: 11px; - margin-top: -2px; - } - - .icon { - width: 16px; - height: 16px; - display: inline-block; - vertical-align: middle; - } - - .room-container { - max-height: 85vh; - position: absolute; - right: 0px; - bottom: 20px; - min-width: 300px; - z-index: 1000; - margin-top: 15px; - - box-shadow: 0px 0px 15px rgba(black, 0.2); - border: none; - cursor: default; - overflow: auto; - background-color: white; - } - - .room-iframe { - width: 100%; - height: 100%; - } - } - } } #footer-bar .footer-bar-link { - font-size: 11px; - font-weight: bold; - margin-left: 8px; - color: #515263; + margin-left: 10px; + &:not(:first-child) { + margin-left: 12px; + } z-index: 1000; - display: inline-block; position: relative; - cursor: pointer; - + height: 100%; user-select: none; - > a { + display: flex; + align-items: center; + justify-content: center; + + > .label { + font-size: 12px; + font-weight: bold; color: #515263; + cursor: pointer; + + &:hover { + text-decoration: none; + color: $blue-color; + } } } .footer-bar-link .panel { - font-weight: normal; - font-size: 12px; - max-height: 85vh; position: absolute; right: 0px; - bottom: 20px; + left: 10px; + bottom: 40px; min-width: 300px; z-index: 1000; margin-top: 15px; - - box-shadow: 0px 0px 15px rgba(black, 0.2); - border: none; - cursor: default; - overflow: auto; background-color: white; } -button.light { - font-weight: bold; - margin-bottom: 0px; - font-size: 12px; - height: 30px; - padding-top: 3px; - text-align: center; - margin-bottom: 6px; - background-color: white; - display: block; - width: 100%; - border: 1px solid rgba(gray, 0.15); - cursor: pointer; +#account-panel { + width: 400px; - &:hover { - background-color: rgba(gray, 0.10); - } -} - -.half-button { - $spacing: 2px; - width: calc(50% - #{$spacing}); - margin-left: $spacing/2.0; - margin-right: $spacing/2.0; - float: left; -} - -.gray-bg { - background-color: #f6f6f6; - border: 1px solid #f2f2f2; -} - -.white-bg { - background-color: white; - border: 1px solid rgba(gray, 0.2); + #signout-button { + text-align: right; + display: block; + } } a.disabled { @@ -247,7 +132,55 @@ a.disabled { +#room-bar { + display: inline-block; + border-left: 1px solid rgba(black, 0.1); + padding-left: 15px; + padding-right: 10px; + margin-left: 15px; + position: relative; + .room-item { + display: inline-block; + position: relative; + vertical-align: middle; + + .label { + display: inline-block; + vertical-align: middle; + font-size: 11px; + margin-top: -2px; + } + + .icon { + width: 16px; + height: 16px; + display: inline-block; + vertical-align: middle; + } + + .room-container { + max-height: 85vh; + position: absolute; + right: 0px; + bottom: 20px; + min-width: 300px; + z-index: 1000; + margin-top: 15px; + + box-shadow: 0px 0px 15px rgba(black, 0.2); + border: none; + cursor: default; + overflow: auto; + background-color: white; + } + + .room-iframe { + width: 100%; + height: 100%; + } + } +} diff --git a/app/assets/stylesheets/app/_main.scss b/app/assets/stylesheets/app/_main.scss index 39ab8bdb1..24a1c2366 100644 --- a/app/assets/stylesheets/app/_main.scss +++ b/app/assets/stylesheets/app/_main.scss @@ -66,7 +66,7 @@ body { -webkit-font-smoothing: antialiased; min-height: 100%; height: 100%; - font-size: 20px; + font-size: 14px; margin: 0; background-color: $bg-color; } @@ -108,7 +108,7 @@ p { background-color: $bg-color; } -$footer-height: 25px; +$footer-height: 32px; .app { height: calc(100% - #{$footer-height}); diff --git a/app/assets/stylesheets/app/_ui.scss b/app/assets/stylesheets/app/_ui.scss index bbad720ee..5d6327b73 100644 --- a/app/assets/stylesheets/app/_ui.scss +++ b/app/assets/stylesheets/app/_ui.scss @@ -81,34 +81,34 @@ $screen-md-max: ($screen-lg-min - 1) !default; button:focus {outline:0;} - -.button-group { - clear: both; - height: 36px; -} - -.ui-button { - background-color: $blue-color; - border: 0; - color: white; - font-weight: bold; - min-height: 36px; - font-size: 14px; - - &.block { - width: 100%; - } -} +// +// .button-group { +// clear: both; +// height: 36px; +// } +// +// .ui-button { +// background-color: $blue-color; +// border: 0; +// color: white; +// font-weight: bold; +// min-height: 36px; +// font-size: 14px; +// +// &.block { +// width: 100%; +// } +// } .panel { position: absolute; right: 0px; min-width: 300px; z-index: 1000; - margin-top: 10px; - box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2); - border: none; - background-color: white; + // margin-top: 10px; + // box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2); + // border: none; + // background-color: white; } .panel-right { diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 6b5311743..dac816181 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -1,19 +1,19 @@ -.panel.panel-default.panel-right.account-data-menu - .panel-body.large-padding - %div{"ng-if" => "!user"} - - -# Account Section - .mb-10 - +.sn-component + .panel.panel-right#account-panel + .header + %h1.title Account + %a.close-button Close + .content + .panel-section.hero{"ng-if" => "!user"} .step-one{"ng-if" => "!formData.showLogin && !formData.showRegister && !formData.mfa"} - %h3 Sign in or register to enable sync and end-to-end encryption. - .small-v-space - - .button-group.mt-5 - %button.ui-button.half-button{"ng-click" => "formData.showLogin = true"} - %span Sign In - %button.ui-button.half-button{"ng-click" => "formData.showRegister = true"} - %span Register + %h1.title Sign in or register to enable sync and end-to-end encryption. + .button-group.stretch + .button.info.featured{"ng-click" => "formData.showLogin = true"} + .label Sign In + .button.info.featured{"ng-click" => "formData.showRegister = true"} + .label Register + %p + Standard Notes is free on every platform, and comes standard with sync and encryption. .step-two{"ng-if" => "formData.showLogin || formData.showRegister"} .float-group.h20 @@ -52,122 +52,124 @@ %em.block.center-align.mt-10{"ng-if" => "formData.status", "style" => "font-size: 14px;"} {{formData.status}} - -# End account section - %div{"ng-if" => "user"} - %h2 {{user.email}} - %p {{server}} - %div.bold.mt-10.tinted{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"} - .spinner.inline.mr-5.tinted - {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}} - %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}} - %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} + .panel-section{"ng-if" => "user"} + %h2 {{user.email}} + %p {{server}} + %div.bold.mt-10.tinted{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"} + .spinner.inline.mr-5.tinted + {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}} + %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}} + %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} - %a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password - %section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"} - %p.bold Change Password (Beta) - %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. - %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. - %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. - %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. - %div.mt-10{"ng-if" => "!newPasswordData.status"} - %a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue - %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel - %div.mt-10{"ng-if" => "newPasswordData.showForm"} - %form - %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} - %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} - %button.ui-button.block{"ng-click" => "submitPasswordChange()"} Submit - %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} + %a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password + %section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"} + %p.bold Change Password (Beta) + %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. + %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. + %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. + %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. + %div.mt-10{"ng-if" => "!newPasswordData.status"} + %a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue + %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel + %div.mt-10{"ng-if" => "newPasswordData.showForm"} + %form + %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} + %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} + %button.ui-button.block{"ng-click" => "submitPasswordChange()"} Submit + %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} - %a.block.mt-5{"ng-click" => "showAdvanced = !showAdvanced"} Advanced - %div{"ng-if" => "showAdvanced"} - %a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard - %a.block.mt-5{"ng-click" => "reencryptPressed()"} Re-encrypt All Items - %a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials - %section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"} - %label.block - Encryption key: - .wrap.normal.mt-1.selectable {{encryptionKey()}} - %label.block.mt-5.mb-0 - Server password: - .wrap.normal.mt-1.selectable {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}} - %label.block.mt-5.mb-0 - Authentication key: - .wrap.normal.mt-1.selectable {{authKey() ? authKey() : 'Not available. Sign out then sign back in to compute.'}} + %a.block.mt-5{"ng-click" => "showAdvanced = !showAdvanced"} Advanced + %div{"ng-if" => "showAdvanced"} + %a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard + %a.block.mt-5{"ng-click" => "reencryptPressed()"} Re-encrypt All Items + %a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials + %section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"} + %label.block + Encryption key: + .wrap.normal.mt-1.selectable {{encryptionKey()}} + %label.block.mt-5.mb-0 + Server password: + .wrap.normal.mt-1.selectable {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}} + %label.block.mt-5.mb-0 + Authentication key: + .wrap.normal.mt-1.selectable {{authKey() ? authKey() : 'Not available. Sign out then sign back in to compute.'}} - %div{"ng-if" => "securityUpdateAvailable()"} - %a.block.mt-5{"ng-click" => "clickedSecurityUpdate()"} Security Update Available - %section.gray-bg.mt-10.medium-padding{"ng-if" => "securityUpdateData.showForm"} - %p - %a{"href" => "https://standardnotes.org/help/security-update", "target" => "_blank"} Learn more. - %div.mt-10{"ng-if" => "!securityUpdateData.processing"} - %p.bold Enter your password to update: - %form.mt-5 - %input.form-control{:type => 'password', "ng-model" => "securityUpdateData.password", "placeholder" => "Enter password"} - %button.ui-button.block{"ng-click" => "submitSecurityUpdateForm()"} Update - %div.mt-5{"ng-if" => "securityUpdateData.processing"} - %p.tinted Processing... + %div{"ng-if" => "securityUpdateAvailable()"} + %a.block.mt-5{"ng-click" => "clickedSecurityUpdate()"} Security Update Available + %section.gray-bg.mt-10.medium-padding{"ng-if" => "securityUpdateData.showForm"} + %p + %a{"href" => "https://standardnotes.org/help/security-update", "target" => "_blank"} Learn more. + %div.mt-10{"ng-if" => "!securityUpdateData.processing"} + %p.bold Enter your password to update: + %form.mt-5 + %input.form-control{:type => 'password', "ng-model" => "securityUpdateData.password", "placeholder" => "Enter password"} + %button.ui-button.block{"ng-click" => "submitSecurityUpdateForm()"} Update + %div.mt-5{"ng-if" => "securityUpdateData.processing"} + %p.tinted Processing... - .mt-25 - %h4 Encryption Status - %p - {{encryptionStatusString()}} - %div.mt-5{"ng-if" => "encryptionEnabled()"} - %i {{encryptionStatusForNotes()}} - - .mt-25 - %h4 Passcode Lock - %div{"ng-if" => "!hasPasscode() && passcodeOptionAvailable()"} - %p Add an app passcode to lock the app and encrypt on-device key storage. - %a.block.mt-5{"ng-click" => "addPasscodeClicked()", "ng-if" => "!formData.showPasscodeForm"} Add Passcode - - %form.mt-5{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} - %p.bold Choose a passcode: - %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "autofocus" => "true"} - %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} - %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Set Passcode - %div{"ng-if" => "hasPasscode()"} + .panel-section + %h3.title Encryption %p - Passcode lock is enabled. - %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. - %a.block.mt-5{"ng-click" => "removePasscodePressed()"} Remove Passcode - %div{"ng-if" => "!passcodeOptionAvailable()"} - %p Passcode lock is only available to permanent sessions. (You chose not to stay signed in.) + {{encryptionStatusString()}} + %div.mt-5{"ng-if" => "encryptionEnabled()"} + %i {{encryptionStatusForNotes()}} + + .panel-section + %h3.title Passcode Lock + %div{"ng-if" => "!hasPasscode() && passcodeOptionAvailable()"} + .button.info{"ng-click" => "addPasscodeClicked()", "ng-if" => "!formData.showPasscodeForm"} + .label Add Passcode + + %form.mt-5{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} + %p.bold Choose a passcode: + %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "autofocus" => "true"} + %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} + %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Set Passcode + %p Add an app passcode to lock the app and encrypt on-device key storage. + %div{"ng-if" => "hasPasscode()"} + %p + Passcode lock is enabled. + %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. + %a.block.mt-5{"ng-click" => "removePasscodePressed()"} Remove Passcode + %div{"ng-if" => "!passcodeOptionAvailable()"} + %p Passcode lock is only available to permanent sessions. (You chose not to stay signed in.) - .mt-25{"ng-if" => "!importData.loading"} - %h4 Data Archives - .mt-5{"ng-if" => "encryptedBackupsAvailable()"} - %label.normal.inline - %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} - Encrypted - %label.normal.inline - %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} - Decrypted + .panel-section{"ng-if" => "!importData.loading"} + %h3.title Data Backups + %div{"ng-if" => "encryptedBackupsAvailable()"} + %label.normal.inline + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} + Encrypted + %label.normal.inline + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} + Decrypted - %a.block.mt-5{"ng-click" => "downloadDataArchive()", "ng-class" => "{'mt-5' : !user}"} Download Data Archive + .button-group + .button.info{"ng-click" => "downloadDataArchive()", "ng-class" => "{'mt-5' : !user}"} + .label Download Backup - %label.block.mt-5 - %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"} - .fake-link.tinted Import Data from Archive + %label.button.info + %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"} + .label Import From Backup - %div{"ng-if" => "importData.requestPassword"} - %form{"ng-submit" => "submitImportPassword()"} - %p Enter the account password associated with the import file. - %input.form-control.mt-5{:type => 'password', "ng-model" => "importData.password", "autofocus" => "true"} - %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Decrypt & Import + %div{"ng-if" => "importData.requestPassword"} + %form{"ng-submit" => "submitImportPassword()"} + %p Enter the account password associated with the import file. + %input.form-control.mt-5{:type => 'password', "ng-model" => "importData.password", "autofocus" => "true"} + %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Decrypt & Import - %p.mt-5{"ng-if" => "user"} Notes are downloaded in the Standard File format, which allows you to re-import back into this app easily. To download as plain text files, choose "Decrypted". + %p.mt-5{"ng-if" => "user"} Notes are downloaded in the Standard File format, which allows you to re-import back into this app easily. To download as plain text files, choose "Decrypted". - .spinner.mt-10{"ng-if" => "importData.loading"} - - %a.block.mt-25.red{"ng-click" => "destroyLocalData()"} {{ user ? "Sign out and clear local data" : "Clear all local data" }} + .spinner.mt-10{"ng-if" => "importData.loading"} + .footer + %a#signout-button{"ng-click" => "destroyLocalData()"} {{ user ? "Sign out and clear local data" : "Clear all local data" }} diff --git a/app/assets/templates/frontend/directives/permissions-modal.html.haml b/app/assets/templates/frontend/directives/permissions-modal.html.haml index 0ca1c7f70..e17fb7f75 100644 --- a/app/assets/templates/frontend/directives/permissions-modal.html.haml +++ b/app/assets/templates/frontend/directives/permissions-modal.html.haml @@ -1,4 +1,4 @@ -.background{"ng-click" => "dismiss()"} +.background{"ng-click" => "deny()"} .content %h3 The following extension has requested these permissions: diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index b0943a8f9..5fb62a127 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -1,15 +1,15 @@ #footer-bar .pull-left .footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} - %a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account + %a.label{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"} .footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} - %a{"ng-click" => "ctrl.toggleExtensions()"} Extensions + %a.label{"ng-click" => "ctrl.toggleExtensions()"} Extensions %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} .footer-bar-link - %a{"href" => "https://standardnotes.org/help", "target" => "_blank"} + %a.label{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help %room-bar#room-bar @@ -21,13 +21,13 @@ .footer-bar-link{"style" => "margin-right: 5px;"} %span{"ng-if" => "ctrl.lastSyncDate", "style" => "float: left; font-weight: normal; margin-right: 8px;"} - %span{"ng-if" => "!ctrl.isRefreshing"} + %span.label{"ng-if" => "!ctrl.isRefreshing"} Last refreshed {{ctrl.lastSyncDate | appDateTime}} %span{"ng-if" => "ctrl.isRefreshing"} .spinner{"style" => "margin-top: 2px;"} - %strong{"ng-if" => "ctrl.offline"} Offline - %a{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} Refresh + %strong.label{"ng-if" => "ctrl.offline"} Offline + %a.label{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} Refresh - %span{"ng-if" => "ctrl.hasPasscode()"} + %span.label{"ng-if" => "ctrl.hasPasscode()"} %i.icon.ion-locked{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"} diff --git a/app/views/application/frontend.html.erb b/app/views/application/frontend.html.erb index 471176b47..217bd89e2 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/frontend.html.erb @@ -39,6 +39,7 @@ <%= javascript_include_tag "compiled.min.js", debug: false %> <% end %> <%= stylesheet_link_tag "app", media: "all", debug: false %> + <%= stylesheet_link_tag 'stylekit.css', media: 'all', 'data-turbolinks-track' => true %> diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index bd097f99c..4a7e35fa7 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -13,7 +13,7 @@ Rails.application.config.assets.precompile << /\.(?:svg|eot|woff|ttf)\z/ # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # Rails.application.config.assets.precompile += %w( search.js ) -Rails.application.config.assets.precompile += %w( app.css compiled.min.js compiled.js ) +Rails.application.config.assets.precompile += %w( stylekit.css app.css compiled.min.js compiled.js ) # zip library Rails.application.config.assets.precompile += %w( zip/zip.js zip/z-worker.js zip/inflate.js zip/deflate.js ) diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css new file mode 100644 index 000000000..8ed3df59f --- /dev/null +++ b/vendor/assets/stylesheets/stylekit.css @@ -0,0 +1,201 @@ +.sn-component { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + user-select: none; +} +.sn-component .panel { + box-shadow: 0px 2px 13px #C8C8C8; + border-radius: 0.7rem; + overflow: hidden; +} +.sn-component .panel .header { + display: flex; + justify-content: space-between; + padding: 1.1rem 1.8rem; + border-bottom: 1px solid #E1E1E1; + background-color: #F6F6F6; + align-items: center; +} +.sn-component .panel .header .close-button { + font-weight: bold; +} +.sn-component .panel .footer { + padding: 1rem 1.8rem; + border-top: 1px solid #F1F1F1; +} +.sn-component .panel .content { + padding: 1.4rem 1.8rem; + padding-bottom: 0; +} +.sn-component .panel .content p { + color: #454545; + line-height: 1.3; +} +.sn-component .panel .content .label, .sn-component .panel .content .panel-section .subtitle { + font-weight: bold; +} +.sn-component .panel .content .panel-section { + padding-bottom: 1.4rem; +} +.sn-component .panel .content .panel-section.hero { + text-align: center; +} +.sn-component .panel .content .panel-section.hero .title { + margin-bottom: 1.1rem; +} +.sn-component .panel .content .panel-section p:last-child { + margin-bottom: 0; +} +.sn-component .panel .content .panel-section:not(:last-child) { + margin-bottom: 1.5rem; + border-bottom: 1px solid #DDDDDD; +} +.sn-component .panel .content .panel-section:last-child { + margin-bottom: 0.5rem; +} +.sn-component .panel .content .panel-section .outer-title { + border-bottom: 1px solid #DDDDDD; + padding-bottom: 0.9rem; + margin-top: 2.1rem; + margin-bottom: 15px; +} +.sn-component .panel .content .panel-section .title { + margin-bottom: 12px; +} +.sn-component .panel .content .panel-section .subtitle { + color: #086DD6; + margin-top: -4px; +} +.sn-component .red { + color: #F80324; +} +.sn-component .tinted { + color: #086DD6; +} +.sn-component h1, .sn-component h2, .sn-component h3, .sn-component h4 { + margin: 0; + padding: 0; +} +.sn-component h1 { + font-weight: 500; + font-size: 1.3rem; +} +.sn-component h2 { + font-size: 1.2rem; +} +.sn-component h3 { + font-weight: normal; + font-size: 1.2rem; +} +.sn-component h4 { + font-weight: bold; + font-size: 0.85rem; +} +.sn-component a { + color: #086DD6; + cursor: pointer; +} +.sn-component p { + margin: 0.5rem 0; +} +.sn-component .button-group.stretch { + display: flex; +} +.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box { + display: block; + flex-grow: 1; + text-align: center; +} +.sn-component .button-group .button, .sn-component .button-group .box { + display: inline-block; + margin-bottom: 5px; +} +.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child) { + margin-right: 5px; +} +.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured { + margin-right: 8px; +} +.sn-component .box-group .box { + display: inline-block; + margin-bottom: 5px; +} +.sn-component .box-group .box:not(:last-child) { + margin-right: 5px; +} +.sn-component .button, .sn-component .box { + display: table; + border-radius: 3px; + padding: 0.5rem 0.7rem; + font-size: 0.9rem; + cursor: pointer; + text-align: center; + border: 1px solid; +} +.sn-component .button .label, .sn-component .box .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle { + font-weight: bold; + display: block; + text-align: center; +} +.sn-component .box { + padding: 2.5rem 1.5rem; +} +.sn-component .info { + background-color: rgba(8, 109, 214, 0.1); + border-color: #086DD6; + color: #086DD6; +} +.sn-component .info:hover { + background-color: #d5e9fd; + color: #0975e5; +} +.sn-component .info.featured { + background-color: #086DD6; + border: none; + color: white; + padding: 0.75rem 0; + font-size: 1.1rem; +} +.sn-component .info.featured:hover { + background-color: #1181f6; +} +.sn-component .warning { + background-color: rgba(214, 173, 8, 0.1); + border-color: #D6AD08; + color: #D6AD08; +} +.sn-component .warning:hover { + background-color: #fdf5d5; + color: #e5b909; +} +.sn-component .warning.featured { + background-color: #D6AD08; + border: none; + color: white; + padding: 0.75rem 0; + font-size: 1.1rem; +} +.sn-component .warning.featured:hover { + background-color: #f6c811; +} +.sn-component .danger { + background-color: rgba(248, 3, 36, 0.1); + border-color: #F80324; + color: #F80324; +} +.sn-component .danger:hover { + background-color: #fff1f3; + color: #fc0e2e; +} +.sn-component .danger.featured { + background-color: #F80324; + border: none; + color: white; + padding: 0.75rem 0; + font-size: 1.1rem; +} +.sn-component .danger.featured:hover { + background-color: #fc2744; +} + +/*# sourceMappingURL=stylekit.css.map */ From a01067220cb78fca69357c86a0283d2a2bbbb82f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 5 Jan 2018 15:02:54 -0600 Subject: [PATCH 17/99] Styling wip --- app/assets/stylesheets/app/_footer.scss | 15 +- .../directives/account-menu.html.haml | 278 +++++++++--------- vendor/assets/stylesheets/stylekit.css | 149 +++++++--- 3 files changed, 260 insertions(+), 182 deletions(-) diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index 6cb0c071d..9934f5355 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -39,10 +39,6 @@ h2 { } } - - input { - border-radius: 0px; - } } #footer-bar .footer-bar-link { @@ -85,13 +81,14 @@ h2 { background-color: white; } +.panel .close-button { + &:hover { + text-decoration: none; + } +} + #account-panel { width: 400px; - - #signout-button { - text-align: right; - display: block; - } } a.disabled { diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index dac816181..bf99e0e65 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -4,172 +4,176 @@ %h1.title Account %a.close-button Close .content - .panel-section.hero{"ng-if" => "!user"} - .step-one{"ng-if" => "!formData.showLogin && !formData.showRegister && !formData.mfa"} - %h1.title Sign in or register to enable sync and end-to-end encryption. - .button-group.stretch - .button.info.featured{"ng-click" => "formData.showLogin = true"} - .label Sign In - .button.info.featured{"ng-click" => "formData.showRegister = true"} - .label Register - %p - Standard Notes is free on every platform, and comes standard with sync and encryption. - .step-two{"ng-if" => "formData.showLogin || formData.showRegister"} - .float-group.h20 - %h3.pull-left {{formData.showLogin ? "Sign In" : "Register (free)"}} - %a.pull-right.pt-5{"ng-click" => "formData.showLogin = false; formData.showRegister = false;"} Cancel + .panel-section.hero{"ng-if" => "!user && !formData.showLogin && !formData.showRegister && !formData.mfa"} + %h1.title Sign in or register to enable sync and end-to-end encryption. + .button-group.stretch + .button.info.featured{"ng-click" => "formData.showLogin = true"} + .label Sign In + .button.info.featured{"ng-click" => "formData.showRegister = true"} + .label Register + %p + Standard Notes is free on every platform, and comes standard with sync and encryption. - %form.mt-5 - %input.form-control.mt-10{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'formData.email'} - %input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'} - %input.form-control{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf'} + .panel-section{"ng-if" => "formData.showLogin || formData.showRegister"} + %h3.title + {{formData.showLogin ? "Sign In" : "Register (free)"}} - %a.block{"ng-click" => "formData.showAdvanced = !formData.showAdvanced"} Advanced Options - .advanced-options.mt-10{"ng-if" => "formData.showAdvanced"} - .float-group - %label.pull-left Sync Server Domain - %input.form-control.mt-5{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'} + %form.panel-form + %input{:placeholder => 'Email', :autofocus => 'autofocus', :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email'} + %input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'} + %input{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf'} - .checkbox.mt-10 - %p - %input{"type" => "checkbox", "ng-model" => "formData.ephemeral", "ng-true-value" => "false", "ng-false-value" => "true"} - Stay signed in - .checkbox.mt-10{"ng-if" => "notesAndTagsCount() > 0"} - %p - %input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"} - Merge local data ({{notesAndTagsCount()}} notes and tags) - %button.ui-button.block.mt-10{"ng-click" => "submitAuthForm()"} {{formData.showLogin ? "Sign In" : "Register"}} + %a.panel-row{"ng-click" => "formData.showAdvanced = !formData.showAdvanced"} + Advanced Options + .notification.info{"ng-if" => "formData.showRegister"} + %h2.title No Password Reset. + .text Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password. + .advanced-options.panel-row{"ng-if" => "formData.showAdvanced"} + .float-group + %label.pull-left Sync Server Domain + %input.form-control.mt-5{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'} + + .button-group.stretch.panel-row.form-submit + .button.info.featured{"ng-click" => "submitAuthForm()"} + {{formData.showLogin ? "Sign In" : "Register"}} + + %label + %input{"type" => "checkbox", "ng-model" => "formData.ephemeral", "ng-true-value" => "false", "ng-false-value" => "true"} + Stay signed in + %label + %input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"} + Merge local data ({{notesAndTagsCount()}} notes and tags) %form.mt-5{"ng-if" => "formData.mfa"} %p {{formData.mfa.message}} %input.form-control.mt-10{:autofocus => "true", :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'} %button.ui-button.block.mt-10{"ng-click" => "submitMfaForm()"} {{"Sign In"}} - .mt-15{"ng-if" => "formData.showRegister"} - %h3 No Password Reset. - %p.mt-5 Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password. + %em.block.center-align.mt-10{"ng-if" => "formData.status", "style" => "font-size: 14px;"} + {{formData.status}} - %em.block.center-align.mt-10{"ng-if" => "formData.status", "style" => "font-size: 14px;"} {{formData.status}} - - - .panel-section{"ng-if" => "user"} - %h2 {{user.email}} - %p {{server}} - %div.bold.mt-10.tinted{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"} - .spinner.inline.mr-5.tinted - {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}} - %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}} - %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} + %div{"ng-if" => "!formData.showLogin && !formData.showRegister"} + .panel-section{"ng-if" => "user"} + %h2 {{user.email}} + %p {{server}} + %div.bold.mt-10.tinted{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"} + .spinner.inline.mr-5.tinted + {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}} + %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}} + %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} - %a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password - %section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"} - %p.bold Change Password (Beta) - %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. - %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. - %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. - %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. - %div.mt-10{"ng-if" => "!newPasswordData.status"} - %a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue - %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel - %div.mt-10{"ng-if" => "newPasswordData.showForm"} - %form - %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} - %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} - %button.ui-button.block{"ng-click" => "submitPasswordChange()"} Submit - %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} + %a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password + %section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"} + %p.bold Change Password (Beta) + %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. + %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. + %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. + %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. + %div.mt-10{"ng-if" => "!newPasswordData.status"} + %a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue + %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel + %div.mt-10{"ng-if" => "newPasswordData.showForm"} + %form + %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} + %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} + %button.ui-button.block{"ng-click" => "submitPasswordChange()"} Submit + %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} - %a.block.mt-5{"ng-click" => "showAdvanced = !showAdvanced"} Advanced - %div{"ng-if" => "showAdvanced"} - %a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard - %a.block.mt-5{"ng-click" => "reencryptPressed()"} Re-encrypt All Items - %a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials - %section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"} - %label.block - Encryption key: - .wrap.normal.mt-1.selectable {{encryptionKey()}} - %label.block.mt-5.mb-0 - Server password: - .wrap.normal.mt-1.selectable {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}} - %label.block.mt-5.mb-0 - Authentication key: - .wrap.normal.mt-1.selectable {{authKey() ? authKey() : 'Not available. Sign out then sign back in to compute.'}} + %a.block.mt-5{"ng-click" => "showAdvanced = !showAdvanced"} Advanced + %div{"ng-if" => "showAdvanced"} + %a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard + %a.block.mt-5{"ng-click" => "reencryptPressed()"} Re-encrypt All Items + %a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials + %section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"} + %label.block + Encryption key: + .wrap.normal.mt-1.selectable {{encryptionKey()}} + %label.block.mt-5.mb-0 + Server password: + .wrap.normal.mt-1.selectable {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}} + %label.block.mt-5.mb-0 + Authentication key: + .wrap.normal.mt-1.selectable {{authKey() ? authKey() : 'Not available. Sign out then sign back in to compute.'}} - %div{"ng-if" => "securityUpdateAvailable()"} - %a.block.mt-5{"ng-click" => "clickedSecurityUpdate()"} Security Update Available - %section.gray-bg.mt-10.medium-padding{"ng-if" => "securityUpdateData.showForm"} - %p - %a{"href" => "https://standardnotes.org/help/security-update", "target" => "_blank"} Learn more. - %div.mt-10{"ng-if" => "!securityUpdateData.processing"} - %p.bold Enter your password to update: - %form.mt-5 - %input.form-control{:type => 'password', "ng-model" => "securityUpdateData.password", "placeholder" => "Enter password"} - %button.ui-button.block{"ng-click" => "submitSecurityUpdateForm()"} Update - %div.mt-5{"ng-if" => "securityUpdateData.processing"} - %p.tinted Processing... + %div{"ng-if" => "securityUpdateAvailable()"} + %a.block.mt-5{"ng-click" => "clickedSecurityUpdate()"} Security Update Available + %section.gray-bg.mt-10.medium-padding{"ng-if" => "securityUpdateData.showForm"} + %p + %a{"href" => "https://standardnotes.org/help/security-update", "target" => "_blank"} Learn more. + %div.mt-10{"ng-if" => "!securityUpdateData.processing"} + %p.bold Enter your password to update: + %form.mt-5 + %input.form-control{:type => 'password', "ng-model" => "securityUpdateData.password", "placeholder" => "Enter password"} + %button.ui-button.block{"ng-click" => "submitSecurityUpdateForm()"} Update + %div.mt-5{"ng-if" => "securityUpdateData.processing"} + %p.tinted Processing... - .panel-section - %h3.title Encryption - %p - {{encryptionStatusString()}} - %div.mt-5{"ng-if" => "encryptionEnabled()"} - %i {{encryptionStatusForNotes()}} - - .panel-section - %h3.title Passcode Lock - %div{"ng-if" => "!hasPasscode() && passcodeOptionAvailable()"} - .button.info{"ng-click" => "addPasscodeClicked()", "ng-if" => "!formData.showPasscodeForm"} - .label Add Passcode - - %form.mt-5{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} - %p.bold Choose a passcode: - %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "autofocus" => "true"} - %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} - %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Set Passcode - %p Add an app passcode to lock the app and encrypt on-device key storage. - %div{"ng-if" => "hasPasscode()"} + .panel-section + %h3.title Encryption %p - Passcode lock is enabled. - %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. - %a.block.mt-5{"ng-click" => "removePasscodePressed()"} Remove Passcode - %div{"ng-if" => "!passcodeOptionAvailable()"} - %p Passcode lock is only available to permanent sessions. (You chose not to stay signed in.) + {{encryptionStatusString()}} + %div.mt-5{"ng-if" => "encryptionEnabled()"} + %i {{encryptionStatusForNotes()}} + + .panel-section + %h3.title Passcode Lock + %div{"ng-if" => "!hasPasscode() && passcodeOptionAvailable()"} + .button.info{"ng-click" => "addPasscodeClicked()", "ng-if" => "!formData.showPasscodeForm"} + .label Add Passcode + + %form.mt-5{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} + %p.bold Choose a passcode: + %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "autofocus" => "true"} + %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} + %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Set Passcode + %p Add an app passcode to lock the app and encrypt on-device key storage. + %div{"ng-if" => "hasPasscode()"} + %p + Passcode lock is enabled. + %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. + %a.block.mt-5{"ng-click" => "removePasscodePressed()"} Remove Passcode + %div{"ng-if" => "!passcodeOptionAvailable()"} + %p Passcode lock is only available to permanent sessions. (You chose not to stay signed in.) - .panel-section{"ng-if" => "!importData.loading"} - %h3.title Data Backups - %div{"ng-if" => "encryptedBackupsAvailable()"} - %label.normal.inline - %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} - Encrypted - %label.normal.inline - %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} - Decrypted + .panel-section{"ng-if" => "!importData.loading"} + %h3.title Data Backups + %div{"ng-if" => "encryptedBackupsAvailable()"} + %label.normal.inline + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} + Encrypted + %label.normal.inline + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} + Decrypted - .button-group - .button.info{"ng-click" => "downloadDataArchive()", "ng-class" => "{'mt-5' : !user}"} - .label Download Backup + .button-group + .button.info{"ng-click" => "downloadDataArchive()", "ng-class" => "{'mt-5' : !user}"} + .label Download Backup - %label.button.info - %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"} - .label Import From Backup + %label.button.info + %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"} + .label Import From Backup - %div{"ng-if" => "importData.requestPassword"} - %form{"ng-submit" => "submitImportPassword()"} - %p Enter the account password associated with the import file. - %input.form-control.mt-5{:type => 'password', "ng-model" => "importData.password", "autofocus" => "true"} - %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Decrypt & Import + %div{"ng-if" => "importData.requestPassword"} + %form{"ng-submit" => "submitImportPassword()"} + %p Enter the account password associated with the import file. + %input.form-control.mt-5{:type => 'password', "ng-model" => "importData.password", "autofocus" => "true"} + %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Decrypt & Import - %p.mt-5{"ng-if" => "user"} Notes are downloaded in the Standard File format, which allows you to re-import back into this app easily. To download as plain text files, choose "Decrypted". + %p.mt-5{"ng-if" => "user"} Notes are downloaded in the Standard File format, which allows you to re-import back into this app easily. To download as plain text files, choose "Decrypted". - .spinner.mt-10{"ng-if" => "importData.loading"} + .spinner.mt-10{"ng-if" => "importData.loading"} .footer - %a#signout-button{"ng-click" => "destroyLocalData()"} {{ user ? "Sign out and clear local data" : "Clear all local data" }} + %a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"} + Cancel + %a.right{"ng-if" => "!formData.showLogin && !formData.showRegister", "ng-click" => "destroyLocalData()"} + {{ user ? "Sign out and clear local data" : "Clear all local data" }} diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css index 8ed3df59f..d169ed8f3 100644 --- a/vendor/assets/stylesheets/stylekit.css +++ b/vendor/assets/stylesheets/stylekit.css @@ -6,12 +6,12 @@ .sn-component .panel { box-shadow: 0px 2px 13px #C8C8C8; border-radius: 0.7rem; - overflow: hidden; + overflow: scroll; } .sn-component .panel .header { display: flex; justify-content: space-between; - padding: 1.1rem 1.8rem; + padding: 1.1rem 2rem; border-bottom: 1px solid #E1E1E1; background-color: #F6F6F6; align-items: center; @@ -20,11 +20,19 @@ font-weight: bold; } .sn-component .panel .footer { - padding: 1rem 1.8rem; + padding: 1rem 2rem; border-top: 1px solid #F1F1F1; } +.sn-component .panel .footer .left { + text-align: right; + display: block; +} +.sn-component .panel .footer .right { + text-align: right; + display: block; +} .sn-component .panel .content { - padding: 1.4rem 1.8rem; + padding: 1.9rem 2rem; padding-bottom: 0; } .sn-component .panel .content p { @@ -35,7 +43,7 @@ font-weight: bold; } .sn-component .panel .content .panel-section { - padding-bottom: 1.4rem; + padding-bottom: 1.6rem; } .sn-component .panel .content .panel-section.hero { text-align: center; @@ -66,6 +74,19 @@ color: #086DD6; margin-top: -4px; } +.sn-component .panel .content .panel-section .panel-row { + display: block; + padding: 0.4rem 0; +} +.sn-component .panel .content .panel-form { + width: 100%; +} +.sn-component .panel .content .panel-form.half { + width: 50%; +} +.sn-component .panel .content .panel-form .form-submit { + margin-top: 0.15rem; +} .sn-component .red { color: #F80324; } @@ -98,32 +119,72 @@ .sn-component p { margin: 0.5rem 0; } +.sn-component input { + box-sizing: border-box; + padding: 0.75rem 0.8rem; + margin: 0.30rem 0; + border-radius: 3px; + border: 1px solid #e1e1e1; + font-size: 1.1rem; + width: 100%; + outline: 0; +} +.sn-component input.info { + border-color: #086DD6; + background-color: transparent; +} +.sn-component label { + margin: 0.7rem 0; + display: block; + font-size: 1.1rem; +} +.sn-component label input[type='checkbox'] { + width: auto; + margin-right: 0.5rem; + /* Space after checkbox */ +} +.sn-component .checkbox-group { + padding-top: 0.5rem; + padding-bottom: 0.3rem; +} +.sn-component ::placeholder { + /* Chrome, Firefox, Opera, Safari 10.1+ */ + color: #c4c4c4; +} +.sn-component :-ms-input-placeholder { + /* Internet Explorer 10-11 */ + color: #c4c4c4; +} +.sn-component ::-ms-input-placeholder { + /* Microsoft Edge */ + color: #c4c4c4; +} .sn-component .button-group.stretch { display: flex; } -.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box { +.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box, .sn-component .button-group.stretch .notification { display: block; flex-grow: 1; text-align: center; } -.sn-component .button-group .button, .sn-component .button-group .box { +.sn-component .button-group .button, .sn-component .button-group .box, .sn-component .button-group .notification { display: inline-block; margin-bottom: 5px; } -.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child) { +.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child), .sn-component .button-group .notification:not(:last-child) { margin-right: 5px; } -.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured { +.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured, .sn-component .button-group .notification:not(:last-child).featured { margin-right: 8px; } -.sn-component .box-group .box { +.sn-component .box-group .box, .sn-component .box-group .notification { display: inline-block; margin-bottom: 5px; } -.sn-component .box-group .box:not(:last-child) { +.sn-component .box-group .box:not(:last-child), .sn-component .box-group .notification:not(:last-child) { margin-right: 5px; } -.sn-component .button, .sn-component .box { +.sn-component .button, .sn-component .box, .sn-component .notification { display: table; border-radius: 3px; padding: 0.5rem 0.7rem; @@ -132,70 +193,86 @@ text-align: center; border: 1px solid; } -.sn-component .button .label, .sn-component .box .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle { +.sn-component .button .label, .sn-component .box .label, .sn-component .notification .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle, .sn-component .notification .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .notification .subtitle { font-weight: bold; display: block; text-align: center; } -.sn-component .box { +.sn-component .box, .sn-component .notification { padding: 2.5rem 1.5rem; } -.sn-component .info { +.sn-component .button.info, .sn-component .info.box, .sn-component .info.notification, .sn-component .box.info, .sn-component .info.notification, .sn-component .notification.info { background-color: rgba(8, 109, 214, 0.1); border-color: #086DD6; color: #086DD6; } -.sn-component .info:hover { +.sn-component .button.info:hover, .sn-component .info.box:hover, .sn-component .info.notification:hover, .sn-component .box.info:hover, .sn-component .info.notification:hover, .sn-component .notification.info:hover { background-color: #d5e9fd; color: #0975e5; } -.sn-component .info.featured { +.sn-component .button.info.featured, .sn-component .info.featured.box, .sn-component .info.featured.notification, .sn-component .box.info.featured, .sn-component .info.featured.notification, .sn-component .notification.info.featured { background-color: #086DD6; border: none; color: white; - padding: 0.75rem 0; + padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .info.featured:hover { +.sn-component .button.info.featured:hover, .sn-component .info.featured.box:hover, .sn-component .info.featured.notification:hover, .sn-component .box.info.featured:hover, .sn-component .info.featured.notification:hover, .sn-component .notification.info.featured:hover { background-color: #1181f6; } -.sn-component .warning { - background-color: rgba(214, 173, 8, 0.1); - border-color: #D6AD08; - color: #D6AD08; +.sn-component .button.warning, .sn-component .warning.box, .sn-component .warning.notification, .sn-component .box.warning, .sn-component .warning.notification, .sn-component .notification.warning { + background-color: rgba(246, 162, 0, 0.1); + border-color: #f6a200; + color: #f6a200; } -.sn-component .warning:hover { - background-color: #fdf5d5; - color: #e5b909; +.sn-component .button.warning:hover, .sn-component .warning.box:hover, .sn-component .warning.notification:hover, .sn-component .box.warning:hover, .sn-component .warning.notification:hover, .sn-component .notification.warning:hover { + background-color: #fff8ec; + color: #ffaa06; } -.sn-component .warning.featured { - background-color: #D6AD08; +.sn-component .button.warning.featured, .sn-component .warning.featured.box, .sn-component .warning.featured.notification, .sn-component .box.warning.featured, .sn-component .warning.featured.notification, .sn-component .notification.warning.featured { + background-color: #f6a200; border: none; color: white; - padding: 0.75rem 0; + padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .warning.featured:hover { - background-color: #f6c811; +.sn-component .button.warning.featured:hover, .sn-component .warning.featured.box:hover, .sn-component .warning.featured.notification:hover, .sn-component .box.warning.featured:hover, .sn-component .warning.featured.notification:hover, .sn-component .notification.warning.featured:hover { + background-color: #ffb320; } -.sn-component .danger { +.sn-component .button.danger, .sn-component .danger.box, .sn-component .danger.notification, .sn-component .box.danger, .sn-component .danger.notification, .sn-component .notification.danger { background-color: rgba(248, 3, 36, 0.1); border-color: #F80324; color: #F80324; } -.sn-component .danger:hover { +.sn-component .button.danger:hover, .sn-component .danger.box:hover, .sn-component .danger.notification:hover, .sn-component .box.danger:hover, .sn-component .danger.notification:hover, .sn-component .notification.danger:hover { background-color: #fff1f3; color: #fc0e2e; } -.sn-component .danger.featured { +.sn-component .button.danger.featured, .sn-component .danger.featured.box, .sn-component .danger.featured.notification, .sn-component .box.danger.featured, .sn-component .danger.featured.notification, .sn-component .notification.danger.featured { background-color: #F80324; border: none; color: white; - padding: 0.75rem 0; + padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .danger.featured:hover { +.sn-component .button.danger.featured:hover, .sn-component .danger.featured.box:hover, .sn-component .danger.featured.notification:hover, .sn-component .box.danger.featured:hover, .sn-component .danger.featured.notification:hover, .sn-component .notification.danger.featured:hover { background-color: #fc2744; } +.sn-component .notification { + padding: 1.1rem 1rem; + margin: 1.4rem 0; + text-align: left; + border-radius: 0.2rem; + cursor: default; +} +.sn-component .notification.stretch { + width: 100%; +} +.sn-component .notification .text { + line-height: 1.5rem; + font-size: 1.05rem; + text-align: left; + font-weight: normal; +} /*# sourceMappingURL=stylekit.css.map */ From aabdb73c772e6c094e7703c2cde9f6669ae29cf2 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 5 Jan 2018 19:35:02 -0600 Subject: [PATCH 18/99] Menus wip --- .../services/directives/views/editorMenu.js | 4 + .../app/services/directives/views/menuRow.js | 20 ++ app/assets/stylesheets/app/_main.scss | 66 +++---- app/assets/stylesheets/app/_menus.scss | 180 +----------------- app/assets/stylesheets/app/_ui.scss | 102 ---------- .../directives/contextual-menu.html.haml | 35 ++-- .../frontend/directives/editor-menu.html.haml | 25 +-- .../frontend/directives/menu-row.html.haml | 10 + .../templates/frontend/editor.html.haml | 39 ++-- app/assets/templates/frontend/notes.html.haml | 61 +++--- vendor/assets/stylesheets/stylekit.css | 166 ++++++++++++++-- 11 files changed, 278 insertions(+), 430 deletions(-) create mode 100644 app/assets/javascripts/app/services/directives/views/menuRow.js create mode 100644 app/assets/templates/frontend/directives/menu-row.html.haml diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 57652641c..b800d7e98 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -29,6 +29,10 @@ class EditorMenu { $scope.callback()(editor); } + $scope.moreEditors = function() { + + } + } } diff --git a/app/assets/javascripts/app/services/directives/views/menuRow.js b/app/assets/javascripts/app/services/directives/views/menuRow.js new file mode 100644 index 000000000..40e3acbcd --- /dev/null +++ b/app/assets/javascripts/app/services/directives/views/menuRow.js @@ -0,0 +1,20 @@ +class MenuRow { + + constructor() { + this.restrict = "E"; + this.transclude = true; + this.templateUrl = "frontend/directives/menu-row.html"; + this.scope = { + circle: "=", + title: "=", + subtite: "=" + }; + } + + controller($scope, componentManager) { + 'ngInject'; + + } +} + +angular.module('app.frontend').directive('menuRow', () => new MenuRow); diff --git a/app/assets/stylesheets/app/_main.scss b/app/assets/stylesheets/app/_main.scss index 24a1c2366..a6cd228f9 100644 --- a/app/assets/stylesheets/app/_main.scss +++ b/app/assets/stylesheets/app/_main.scss @@ -6,27 +6,35 @@ $selection-color: $bg-color; $selected-text-color: black; $blue-color: #086dd6; -@mixin MQ-Small() { - @media (max-width: $screen-xs-max) { - @content; - } - - @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { - @content; - } +html, +body { + font-family: -apple-system, BlinkMacSystemFont, + "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; + color: $main-text-color; + -webkit-font-smoothing: antialiased; + min-height: 100%; + height: 100%; + font-size: 14px; + margin: 0; + background-color: $bg-color; } -@mixin MQ-Medium() { - @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { - @content; - } +* { + box-sizing: border-box; } +*:focus {outline:0;} -@mixin MQ-Large() { - @media (min-width: $screen-lg-min) { - @content; - } +button:focus { + outline:0; +} + +input, button, select, textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; } .tinted { @@ -56,21 +64,6 @@ $blue-color: #086dd6; } } -html, -body { - font-family: -apple-system, BlinkMacSystemFont, - "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", - "Droid Sans", "Helvetica Neue", sans-serif; - color: $main-text-color; - -webkit-font-smoothing: antialiased; - min-height: 100%; - height: 100%; - font-size: 14px; - margin: 0; - background-color: $bg-color; -} - .dark-button { background-color: #2e2e2e; border: 0; @@ -92,8 +85,17 @@ body { a { color: $blue-color; text-decoration: none; + + &.no-decoration { + color: inherit; + } + &:hover { - text-decoration: underline;; + text-decoration: underline; + + &.no-decoration { + text-decoration: none; + } } } diff --git a/app/assets/stylesheets/app/_menus.scss b/app/assets/stylesheets/app/_menus.scss index 4bbfab8af..27d2df09b 100644 --- a/app/assets/stylesheets/app/_menus.scss +++ b/app/assets/stylesheets/app/_menus.scss @@ -39,7 +39,6 @@ ul.section-menu-bar { &.item-with-subtitle { label { - // float: left; margin-right: 8px; } @@ -48,15 +47,6 @@ ul.section-menu-bar { opacity: 0.5; font-weight: normal; font-size: 12px; - - // overflow: hidden; - // text-overflow: ellipsis; - // display: -webkit-box; - // -webkit-box-orient: vertical; - // -webkit-line-clamp: 1; /* number of lines to show */ - // $line-height: 18px; - // line-height: $line-height; /* fallback */ - // max-height: calc(#{$line-height} * 1); /* fallback */ } } @@ -85,176 +75,8 @@ ul.section-menu-bar { border: none; width: 280px; - &.full-width { - width: 100%; - } - - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - box-shadow: 0 6px 50px rgba(0, 0, 0, 0.175); - background-clip: padding-box; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; + max-height: calc(85vh - 90px); background-color: white; color: $selected-text-color; - - li:hover { - background-color: $blue-color; - color: white; - } - - > li { - width: 100%; - height: 40px; - padding-top: 3px; - overflow: hidden; - cursor: pointer; - - border-bottom: 1px solid rgba(black, 0.1); - - color: $selected-text-color; - float: left; - - label { - padding: 10px; - padding-top: 8px; - width: 100%; - height: 100%; - display: block; - cursor: pointer; - } - - .shortcut { - float: right; - font-size: 12px; - font-weight: normal; - opacity: 0.5; - margin-top: 10px; - padding-right: 10px; - } - } - - .sublabel { - font-size: 12px; - font-weight: normal; - margin-top: 5px; - } -} - - - - - - - - - -.dropdown-menu.sectioned-menu { - overflow-y: scroll; - max-height: calc(85vh - 90px); - - ul { - margin-top: 0px; - margin-bottom: 0px; - padding-left:0; - position: relative; - - li { - cursor: pointer; - height: auto; - - padding: 10px; - border-bottom: 1px solid rgba(black, 0.1); - background-color: rgba(white, 0.9); - height: auto; - - .left-side { - left: 0; - width: 60%; - display: inline-block; - vertical-align: top; - user-select: text; - } - - .right-side { - right: 12px; - width: 30%; - display: inline-block; - vertical-align: top; - text-align: right; - position: absolute; - } - - &:hover { - background-color: $blue-color; - - - .tinted { - color: white; - } - - &.nested-hover { - color: black; - background-color: $light-bg-color; - } - } - - &.nested-hover { - color: black; - background-color: white; - } - - .menu-item-title { - font-weight: bold; - font-size: 14px; - margin-bottom: 3px; - } - - .menu-item-subtitle { - font-weight: normal; - opacity: 0.5; - margin-top: 1px; - font-size: 12px; - } - } - } - - .header { - background-color: #ededed; - border-bottom: 1px solid #d3d3d3; - position: relative; - padding-top: 12px; - padding-left: 10px; - padding-bottom: 10px; - cursor: pointer; - user-select: none; - - - > .title { - font-size: 14px; - font-weight: bold; - } - - > .subtitle { - font-size: 12px; - opacity: 0.5; - font-weight: normal; - margin-top: 2px; - } - - > .loading { - position: absolute; - height: 15px; - width: 15px; - right: 10px; - top: 20px; - } - } - - .footer { - background-color: #ededed; - border-top: 1px solid #d3d3d3; - position: relative; - padding: 10px; - } } diff --git a/app/assets/stylesheets/app/_ui.scss b/app/assets/stylesheets/app/_ui.scss index 5d6327b73..1a48b6ca2 100644 --- a/app/assets/stylesheets/app/_ui.scss +++ b/app/assets/stylesheets/app/_ui.scss @@ -56,105 +56,3 @@ $screen-md-max: ($screen-lg-min - 1) !default; @content; } } - -* { - box-sizing: border-box; -} - -*:focus {outline:0;} - -.float-group { - height: 15px; - &.h10 { - height: 10px; - } - - &.h20 { - height: 20px; - } - - &.h30 { - height: 30px; - } - clear: both; -} - -button:focus {outline:0;} - -// -// .button-group { -// clear: both; -// height: 36px; -// } -// -// .ui-button { -// background-color: $blue-color; -// border: 0; -// color: white; -// font-weight: bold; -// min-height: 36px; -// font-size: 14px; -// -// &.block { -// width: 100%; -// } -// } - -.panel { - position: absolute; - right: 0px; - min-width: 300px; - z-index: 1000; - // margin-top: 10px; - // box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2); - // border: none; - // background-color: white; -} - -.panel-right { - left: 0px; -} - -.panel-body { - padding: 15px; -} - -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857; - color: #555555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: 0; -} - -input, button, select, textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -button { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.checkbox { - font-size: 14px; - font-weight: bold; - margin-left: auto; - margin-right: auto; - margin-bottom: 10px; - - input { - margin-left: 0px; - } -} diff --git a/app/assets/templates/frontend/directives/contextual-menu.html.haml b/app/assets/templates/frontend/directives/contextual-menu.html.haml index 416bd3c24..3f3c35a7f 100644 --- a/app/assets/templates/frontend/directives/contextual-menu.html.haml +++ b/app/assets/templates/frontend/directives/contextual-menu.html.haml @@ -1,18 +1,17 @@ -%ul.dropdown-menu.sectioned-menu - .extension{"ng-repeat" => "extension in extensions"} - .header{"ng-click" => "extension.hide = !extension.hide"} - .title {{extension.name}} - .subtitle - Will submit your note - %strong {{accessTypeForExtension(extension)}} - .spinner.loading{"ng-if" => "extension.loading"} - %div{"ng-if" => "extension.hide"} … - %ul{"ng-if" => "!extension.hide"} - %li.menu-item{"ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension);", - "ng-class" => "{'faded' : !isActionEnabled(action, extension)}"} - %label.menu-item-title {{action.label}} - .menu-item-subtitle {{action.desc}} +.sn-component + .menu-panel.dropdown-menu + .section{"ng-repeat" => "extension in extensions"} + .header{"ng-click" => "extension.hide = !extension.hide"} + .column + %h4.title {{extension.name}} + .subtitle + Will submit your note + %strong {{accessTypeForExtension(extension)}} + .spinner.loading{"ng-if" => "extension.loading"} + %div{"ng-if" => "extension.hide"} … + %menu-row{"ng-if" => "!extension.hide", "ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension);", + "ng-class" => "{'faded' : !isActionEnabled(action, extension)}", "title" => "action.label", "subtitle" => "action.desc"} .small.normal{"ng-if" => "!isActionEnabled(action, extension)"} Requires {{action.access_type}} access to this note. @@ -27,7 +26,7 @@ %span{"ng-if" => "action.running"} .spinner{"style" => "margin-top: 3px;"} -.extension-render-modal{"ng-if" => "renderData.showRenderModal", "ng-click" => "renderData.showRenderModal = false"} - .content - %h2 {{renderData.title}} - %p.normal{"style" => "white-space: pre-wrap; font-family: monospace; font-size: 16px;"} {{renderData.text}} + .extension-render-modal{"ng-if" => "renderData.showRenderModal", "ng-click" => "renderData.showRenderModal = false"} + .content + %h2 {{renderData.title}} + %p.normal{"style" => "white-space: pre-wrap; font-family: monospace; font-size: 16px;"} {{renderData.text}} diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 97e11de66..3dda51ef7 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -1,20 +1,13 @@ -%ul.dropdown-menu.sectioned-menu - .header - .title System Editor - %ul - %li.menu-item{"ng-click" => "selectEditor($event, null)"} - %span.pull-left.mr-10{"ng-if" => "selectedEditor == null"} ✓ - %label.menu-item-title.pull-left Plain - - %div{"ng-if" => "editors.length > 0"} - .header - .title External Editors - %ul - %li.menu-item{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)"} +.sn-component + .menu-panel.dropdown-menu + .section + .header + %h4.title Note Editor + %menu-row{"title" => "'Plain Editor'", "circle" => "selectedEditor == null && 'success'", "ng-click" => "selectEditor($event, null)"} + %menu-row{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)", "title" => "editor.name", "circle" => "selectedEditor === editor && 'success'"} %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy - %label.menu-item-title - %span.inline.tinted.mr-10{"ng-if" => "selectedEditor === editor"} ✓ - {{editor.name}} .sublabel{"ng-if" => "editor.local"} Locally Installed .sublabel.faded{"ng-if" => "editor.local && !isDesktop"} Unavailable in Web Browser + %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} + %menu-row{"title" => "'Download More Editors'", "ng-click" => "moreEditors()"} diff --git a/app/assets/templates/frontend/directives/menu-row.html.haml b/app/assets/templates/frontend/directives/menu-row.html.haml new file mode 100644 index 000000000..1bbc934b6 --- /dev/null +++ b/app/assets/templates/frontend/directives/menu-row.html.haml @@ -0,0 +1,10 @@ +.row + .column{"ng-if" => "circle"} + .circle.small{"ng-class" => "circle"} + .column + .label + {{title}} + .sublabel{"ng-if" => "subtitle"} + {{subtitle}} + + %ng-transclude diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 447265d35..c89419dc6 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -16,31 +16,20 @@ %li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} %label{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;"} Menu - .dropdown-menu.sectioned-menu{"ng-if" => "ctrl.showMenu"} - %ul - .header - .title Note - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} - %label - %i.icon.ion-ios-flag - {{ctrl.note.pinned ? "Unpin" : "Pin"}} - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} - %label - %i.icon.ion-ios-box - {{ctrl.note.archived ? "Unarchive" : "Archive"}} - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} - %label - %i.icon.ion-trash-b - Delete - %li{"ng-if" => "ctrl.hasDisabledComponents()"} - %label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledComponents()"} Restore Disabled Components - %ul{"ng-if" => "!ctrl.editor"} - .header - .title Display - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('monospaceFont')"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.monospaceFont == true"} ✓ - Monospace Font + .sn-component + .menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"} + .section + .header + %h4.title Note + %menu-row{"title" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} + %menu-row{"title" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} + %menu-row{"title" => "'Delete'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} + %menu-row{"ng-if" => "ctrl.hasDisabledComponents()", "title" => "'Restore Disabled Components'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledComponents()"} + + .section{"ng-if" => "!ctrl.editor"} + .header + %h4.title Display + %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('monospaceFont')"} %li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} %label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index 1baa45463..e5ae42b1a 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -14,45 +14,28 @@ %label Options %span.subtitle {{ctrl.optionsSubtitle()}} - .sectioned-menu.dropdown-menu.full-width{"ng-if" => "ctrl.showMenu"} - %ul - .header - .title Sort By - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByCreated()"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'created_at'"} ✓ - By Date Added - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByUpdated()"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'updated_at'"} ✓ - By Date Modified - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByTitle()"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'title'"} ✓ - By Title - %ul{"ng-if" => "!ctrl.tag.archiveTag"} - .header - .title Display - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('showArchived')"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.showArchived == true"} ✓ - Show Archived Notes - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hidePinned')"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.hidePinned == true"} ✓ - Hide Pinned Notes - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideNotePreview')"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.hideNotePreview == true"} ✓ - Hide Note Preview - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideDate')"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.hideDate == true"} ✓ - Hide Date - %li{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideTags')"} - %label - %span.top.mt-5.mr-5{"ng-if" => "ctrl.hideTags == true"} ✓ - Hide Tags + + .sn-component + .menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"} + .section + .header + %h4.title Sort By + + %menu-row{"title" => "'Date Added'", "circle" => "ctrl.sortBy == 'created_at' && 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByCreated()"} + %menu-row{"title" => "'Date Modified'", "circle" => "ctrl.sortBy == 'updated_at' && 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByUpdated()"} + %menu-row{"title" => "'Title'", "circle" => "ctrl.sortBy == 'title' && 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.selectedSortByTitle()"} + + .section{"ng-if" => "!ctrl.tag.archiveTag"} + .header + %h4.title Display + + %menu-row{"title" => "'Archived Notes'", "circle" => "ctrl.showArchived ? 'success' : 'danger'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('showArchived')"} + %menu-row{"title" => "'Pinned Notes'", "circle" => "ctrl.hidePinned ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hidePinned')"} + + %menu-row{"title" => "'Note Preview'", "circle" => "ctrl.hideNotePreview ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideNotePreview')"} + %menu-row{"title" => "'Date'", "circle" => "ctrl.hideDate ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideDate')"} + %menu-row{"title" => "'Tags'", "circle" => "ctrl.hideTags ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideTags')"} + .scrollable .infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"} diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css index d169ed8f3..2fb57a16b 100644 --- a/vendor/assets/stylesheets/stylekit.css +++ b/vendor/assets/stylesheets/stylekit.css @@ -87,6 +87,56 @@ .sn-component .panel .content .panel-form .form-submit { margin-top: 0.15rem; } +.sn-component .menu-panel { + box-shadow: 0px 4px 4px #C8C8C8; + border-radius: 0.6rem; + overflow: scroll; +} +.sn-component .menu-panel .header { + padding: 0.8rem 1rem; + border-bottom: 1px solid #E1E1E1; + background-color: #F6F6F6; + display: flex; + justify-content: space-between; + align-items: center; +} +.sn-component .menu-panel .header .subtitle { + font-size: 0.95rem; + margin-top: 0.2rem; + opacity: 0.6; +} +.sn-component .menu-panel .row { + padding: 1rem 1rem; + border-bottom: 1px solid #DDDDDD; + cursor: pointer; + display: flex; + flex-direction: row; +} +.sn-component .menu-panel .row .column { + display: flex; + justify-content: center; + flex-direction: column; +} +.sn-component .menu-panel .row .column:not(:first-child) { + padding-left: 1.0rem; + padding-right: 0.15rem; +} +.sn-component .menu-panel .row .button .label, .sn-component .menu-panel .row .box .label, .sn-component .menu-panel .row .notification .label, .sn-component .menu-panel .row .circle .label, .sn-component .menu-panel .row .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .button .subtitle, .sn-component .menu-panel .row .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .box .subtitle, .sn-component .menu-panel .row .notification .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .notification .subtitle, .sn-component .menu-panel .row .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .circle .subtitle { + font-size: 0.8rem; + font-weight: normal; +} +.sn-component .menu-panel .row:hover { + background-color: #efefef; +} +.sn-component .menu-panel .row .label, .sn-component .menu-panel .row .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .subtitle { + font-size: 1rem; + font-weight: bold; +} +.sn-component .menu-panel .row .sublabel { + font-size: 0.9rem; + margin-top: 0.2rem; + opacity: 0.6; +} .sn-component .red { color: #F80324; } @@ -109,13 +159,28 @@ font-size: 1.2rem; } .sn-component h4 { + font-weight: bold; + font-size: 0.95rem; +} +.sn-component h5 { font-weight: bold; font-size: 0.85rem; } .sn-component a { - color: #086DD6; cursor: pointer; } +.sn-component *.info { + color: #086DD6; +} +.sn-component *.warning { + color: #f6a200; +} +.sn-component *.danger { + color: #F80324; +} +.sn-component *.success { + color: #2B9612; +} .sn-component p { margin: 0.5rem 0; } @@ -162,19 +227,19 @@ .sn-component .button-group.stretch { display: flex; } -.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box, .sn-component .button-group.stretch .notification { +.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box, .sn-component .button-group.stretch .notification, .sn-component .button-group.stretch .circle { display: block; flex-grow: 1; text-align: center; } -.sn-component .button-group .button, .sn-component .button-group .box, .sn-component .button-group .notification { +.sn-component .button-group .button, .sn-component .button-group .box, .sn-component .button-group .notification, .sn-component .button-group .circle { display: inline-block; margin-bottom: 5px; } -.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child), .sn-component .button-group .notification:not(:last-child) { +.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child), .sn-component .button-group .notification:not(:last-child), .sn-component .button-group .circle:not(:last-child) { margin-right: 5px; } -.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured, .sn-component .button-group .notification:not(:last-child).featured { +.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured, .sn-component .button-group .notification:not(:last-child).featured, .sn-component .button-group .circle:not(:last-child).featured { margin-right: 8px; } .sn-component .box-group .box, .sn-component .box-group .notification { @@ -184,7 +249,7 @@ .sn-component .box-group .box:not(:last-child), .sn-component .box-group .notification:not(:last-child) { margin-right: 5px; } -.sn-component .button, .sn-component .box, .sn-component .notification { +.sn-component .button, .sn-component .box, .sn-component .notification, .sn-component .circle { display: table; border-radius: 3px; padding: 0.5rem 0.7rem; @@ -193,7 +258,7 @@ text-align: center; border: 1px solid; } -.sn-component .button .label, .sn-component .box .label, .sn-component .notification .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle, .sn-component .notification .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .notification .subtitle { +.sn-component .button .label, .sn-component .box .label, .sn-component .notification .label, .sn-component .circle .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle, .sn-component .notification .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .notification .subtitle, .sn-component .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .circle .subtitle { font-weight: bold; display: block; text-align: center; @@ -201,63 +266,82 @@ .sn-component .box, .sn-component .notification { padding: 2.5rem 1.5rem; } -.sn-component .button.info, .sn-component .info.box, .sn-component .info.notification, .sn-component .box.info, .sn-component .info.notification, .sn-component .notification.info { +.sn-component .button.info, .sn-component .info.box, .sn-component .info.notification, .sn-component .info.circle, .sn-component .box.info, .sn-component .info.notification, .sn-component .notification.info, .sn-component .circle.info { background-color: rgba(8, 109, 214, 0.1); border-color: #086DD6; color: #086DD6; } -.sn-component .button.info:hover, .sn-component .info.box:hover, .sn-component .info.notification:hover, .sn-component .box.info:hover, .sn-component .info.notification:hover, .sn-component .notification.info:hover { +.sn-component .button.info:hover, .sn-component .info.box:hover, .sn-component .info.notification:hover, .sn-component .info.circle:hover, .sn-component .box.info:hover, .sn-component .info.notification:hover, .sn-component .notification.info:hover, .sn-component .circle.info:hover { background-color: #d5e9fd; color: #0975e5; } -.sn-component .button.info.featured, .sn-component .info.featured.box, .sn-component .info.featured.notification, .sn-component .box.info.featured, .sn-component .info.featured.notification, .sn-component .notification.info.featured { +.sn-component .button.info.featured, .sn-component .info.featured.box, .sn-component .info.featured.notification, .sn-component .info.featured.circle, .sn-component .box.info.featured, .sn-component .info.featured.notification, .sn-component .notification.info.featured, .sn-component .circle.info.featured { background-color: #086DD6; border: none; color: white; padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .button.info.featured:hover, .sn-component .info.featured.box:hover, .sn-component .info.featured.notification:hover, .sn-component .box.info.featured:hover, .sn-component .info.featured.notification:hover, .sn-component .notification.info.featured:hover { +.sn-component .button.info.featured:hover, .sn-component .info.featured.box:hover, .sn-component .info.featured.notification:hover, .sn-component .info.featured.circle:hover, .sn-component .box.info.featured:hover, .sn-component .info.featured.notification:hover, .sn-component .notification.info.featured:hover, .sn-component .circle.info.featured:hover { background-color: #1181f6; } -.sn-component .button.warning, .sn-component .warning.box, .sn-component .warning.notification, .sn-component .box.warning, .sn-component .warning.notification, .sn-component .notification.warning { +.sn-component .button.warning, .sn-component .warning.box, .sn-component .warning.notification, .sn-component .warning.circle, .sn-component .box.warning, .sn-component .warning.notification, .sn-component .notification.warning { background-color: rgba(246, 162, 0, 0.1); border-color: #f6a200; color: #f6a200; } -.sn-component .button.warning:hover, .sn-component .warning.box:hover, .sn-component .warning.notification:hover, .sn-component .box.warning:hover, .sn-component .warning.notification:hover, .sn-component .notification.warning:hover { +.sn-component .button.warning:hover, .sn-component .warning.box:hover, .sn-component .warning.notification:hover, .sn-component .warning.circle:hover, .sn-component .box.warning:hover, .sn-component .warning.notification:hover, .sn-component .notification.warning:hover { background-color: #fff8ec; color: #ffaa06; } -.sn-component .button.warning.featured, .sn-component .warning.featured.box, .sn-component .warning.featured.notification, .sn-component .box.warning.featured, .sn-component .warning.featured.notification, .sn-component .notification.warning.featured { +.sn-component .button.warning.featured, .sn-component .warning.featured.box, .sn-component .warning.featured.notification, .sn-component .warning.featured.circle, .sn-component .box.warning.featured, .sn-component .warning.featured.notification, .sn-component .notification.warning.featured { background-color: #f6a200; border: none; color: white; padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .button.warning.featured:hover, .sn-component .warning.featured.box:hover, .sn-component .warning.featured.notification:hover, .sn-component .box.warning.featured:hover, .sn-component .warning.featured.notification:hover, .sn-component .notification.warning.featured:hover { +.sn-component .button.warning.featured:hover, .sn-component .warning.featured.box:hover, .sn-component .warning.featured.notification:hover, .sn-component .warning.featured.circle:hover, .sn-component .box.warning.featured:hover, .sn-component .warning.featured.notification:hover, .sn-component .notification.warning.featured:hover { background-color: #ffb320; } -.sn-component .button.danger, .sn-component .danger.box, .sn-component .danger.notification, .sn-component .box.danger, .sn-component .danger.notification, .sn-component .notification.danger { +.sn-component .button.danger, .sn-component .danger.box, .sn-component .danger.notification, .sn-component .danger.circle, .sn-component .box.danger, .sn-component .danger.notification, .sn-component .notification.danger { background-color: rgba(248, 3, 36, 0.1); border-color: #F80324; color: #F80324; } -.sn-component .button.danger:hover, .sn-component .danger.box:hover, .sn-component .danger.notification:hover, .sn-component .box.danger:hover, .sn-component .danger.notification:hover, .sn-component .notification.danger:hover { +.sn-component .button.danger:hover, .sn-component .danger.box:hover, .sn-component .danger.notification:hover, .sn-component .danger.circle:hover, .sn-component .box.danger:hover, .sn-component .danger.notification:hover, .sn-component .notification.danger:hover { background-color: #fff1f3; color: #fc0e2e; } -.sn-component .button.danger.featured, .sn-component .danger.featured.box, .sn-component .danger.featured.notification, .sn-component .box.danger.featured, .sn-component .danger.featured.notification, .sn-component .notification.danger.featured { +.sn-component .button.danger.featured, .sn-component .danger.featured.box, .sn-component .danger.featured.notification, .sn-component .danger.featured.circle, .sn-component .box.danger.featured, .sn-component .danger.featured.notification, .sn-component .notification.danger.featured { background-color: #F80324; border: none; color: white; padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .button.danger.featured:hover, .sn-component .danger.featured.box:hover, .sn-component .danger.featured.notification:hover, .sn-component .box.danger.featured:hover, .sn-component .danger.featured.notification:hover, .sn-component .notification.danger.featured:hover { +.sn-component .button.danger.featured:hover, .sn-component .danger.featured.box:hover, .sn-component .danger.featured.notification:hover, .sn-component .danger.featured.circle:hover, .sn-component .box.danger.featured:hover, .sn-component .danger.featured.notification:hover, .sn-component .notification.danger.featured:hover { background-color: #fc2744; } +.sn-component .button.success, .sn-component .success.box, .sn-component .success.notification, .sn-component .success.circle, .sn-component .box.success, .sn-component .success.notification, .sn-component .notification.success { + background-color: rgba(43, 150, 18, 0.1); + border-color: #2B9612; + color: #2B9612; +} +.sn-component .button.success:hover, .sn-component .success.box:hover, .sn-component .success.notification:hover, .sn-component .success.circle:hover, .sn-component .box.success:hover, .sn-component .success.notification:hover, .sn-component .notification.success:hover { + background-color: #b7f5a8; + color: #2fa414; +} +.sn-component .button.success.featured, .sn-component .success.featured.box, .sn-component .success.featured.notification, .sn-component .success.featured.circle, .sn-component .box.success.featured, .sn-component .success.featured.notification, .sn-component .notification.success.featured { + background-color: #2B9612; + border: none; + color: white; + padding: 0.75rem 1.25rem; + font-size: 1.1rem; +} +.sn-component .button.success.featured:hover, .sn-component .success.featured.box:hover, .sn-component .success.featured.notification:hover, .sn-component .success.featured.circle:hover, .sn-component .box.success.featured:hover, .sn-component .success.featured.notification:hover, .sn-component .notification.success.featured:hover { + background-color: #35ba16; +} .sn-component .notification { padding: 1.1rem 1rem; margin: 1.4rem 0; @@ -274,5 +358,49 @@ text-align: left; font-weight: normal; } +.sn-component .circle { + border-color: #DDDDDD; + background-color: #F6F6F6; + padding: 0; + border-radius: 50%; +} +.sn-component .circle.small { + width: 12px; + height: 12px; +} +.sn-component .spinner { + border: 1px solid #515263; + border-radius: 50%; + animation: rotate 0.8s infinite linear; + border-right-color: transparent; +} +.sn-component .spinner.small { + width: 12px; + height: 12px; +} +.sn-component .spinner.info { + border-color: #086DD6; + border-right-color: transparent; +} +.sn-component .spinner.warning { + border-color: #f6a200; + border-right-color: transparent; +} +.sn-component .spinner.danger { + border-color: #F80324; + border-right-color: transparent; +} +.sn-component .spinner.success { + border-color: #2B9612; + border-right-color: transparent; +} +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} /*# sourceMappingURL=stylekit.css.map */ From 2f6fe0e64e178eb9deb32c05c82a083d3b883eab Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 5 Jan 2018 21:33:53 -0600 Subject: [PATCH 19/99] Menu buttons --- .../services/directives/views/editorMenu.js | 32 +++++++++++++- .../directives/views/globalExtensionsMenu.js | 16 ------- .../app/services/directives/views/menuRow.js | 11 ++++- .../frontend/directives/editor-menu.html.haml | 18 +++++--- .../frontend/directives/menu-row.html.haml | 20 +++++---- vendor/assets/stylesheets/stylekit.css | 42 ++++++++++++++++++- 6 files changed, 105 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index b800d7e98..292e090a4 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -9,7 +9,7 @@ class EditorMenu { }; } - controller($scope, componentManager) { + controller($scope, componentManager, syncManager) { 'ngInject'; $scope.formData = {}; @@ -18,6 +18,8 @@ class EditorMenu { $scope.isDesktop = isDesktopApplication(); + $scope.defaultEditor = $scope.editors.filter((e) => {return e.isDefaultEditor()})[0]; + $scope.selectEditor = function($event, editor) { if(editor) { editor.conflict_of = null; // clear conflict if applicable @@ -29,8 +31,34 @@ class EditorMenu { $scope.callback()(editor); } - $scope.moreEditors = function() { + $scope.toggleDefaultForEditor = function(editor) { + console.log("Toggling editor", editor); + if($scope.defaultEditor == editor) { + $scope.removeEditorDefault(editor); + } else { + $scope.makeEditorDefault(editor); + } + } + $scope.makeEditorDefault = function(component) { + var currentDefault = componentManager.componentsForArea("editor-editor").filter((e) => {return e.isDefaultEditor()})[0]; + if(currentDefault) { + currentDefault.setAppDataItem("defaultEditor", false); + currentDefault.setDirty(true); + } + component.setAppDataItem("defaultEditor", true); + component.setDirty(true); + syncManager.sync(); + + $scope.defaultEditor = component; + } + + $scope.removeEditorDefault = function(component) { + component.setAppDataItem("defaultEditor", false); + component.setDirty(true); + syncManager.sync(); + + $scope.defaultEditor = null; } } diff --git a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js index c5780abfe..01653a67c 100644 --- a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js @@ -133,22 +133,6 @@ class GlobalExtensionsMenu { } } - $scope.makeEditorDefault = function(component) { - var currentDefault = componentManager.componentsForArea("editor-editor").filter((e) => {return e.isDefaultEditor()})[0]; - if(currentDefault) { - currentDefault.setAppDataItem("defaultEditor", false); - currentDefault.setDirty(true); - } - component.setAppDataItem("defaultEditor", true); - component.setDirty(true); - syncManager.sync(); - } - - $scope.removeEditorDefault = function(component) { - component.setAppDataItem("defaultEditor", false); - component.setDirty(true); - syncManager.sync(); - } // Installation diff --git a/app/assets/javascripts/app/services/directives/views/menuRow.js b/app/assets/javascripts/app/services/directives/views/menuRow.js index 40e3acbcd..c3a1061ef 100644 --- a/app/assets/javascripts/app/services/directives/views/menuRow.js +++ b/app/assets/javascripts/app/services/directives/views/menuRow.js @@ -7,13 +7,22 @@ class MenuRow { this.scope = { circle: "=", title: "=", - subtite: "=" + subtite: "=", + hasButton: "=", + buttonText: "=", + buttonClass: "=", + buttonAction: "&" }; } controller($scope, componentManager) { 'ngInject'; + $scope.clickButton = function($event) { + $event.stopPropagation(); + $scope.buttonAction(); + } + } } diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 3dda51ef7..9209a4b0b 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -4,10 +4,18 @@ .header %h4.title Note Editor %menu-row{"title" => "'Plain Editor'", "circle" => "selectedEditor == null && 'success'", "ng-click" => "selectEditor($event, null)"} - %menu-row{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)", "title" => "editor.name", "circle" => "selectedEditor === editor && 'success'"} - %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy - .sublabel{"ng-if" => "editor.local"} - Locally Installed - .sublabel.faded{"ng-if" => "editor.local && !isDesktop"} Unavailable in Web Browser + + %menu-row{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)", "title" => "editor.name", + "circle" => "selectedEditor === editor && 'success'", + "has-button" => "selectedEditor == editor || defaultEditor == editor", "button-text" => "defaultEditor == editor ? 'Undefault' : 'Set Default'", + "button-action" => "toggleDefaultForEditor(editor)", "button-class" => "defaultEditor == editor ? 'warning' : 'info'"} + .row + .column + %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy + .sublabel{"ng-if" => "editor.local"} + Locally Installed + + .sublabel.faded{"ng-if" => "editor.local && !isDesktop"} Unavailable in Web Browser + %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download More Editors'", "ng-click" => "moreEditors()"} diff --git a/app/assets/templates/frontend/directives/menu-row.html.haml b/app/assets/templates/frontend/directives/menu-row.html.haml index 1bbc934b6..df8d0dea3 100644 --- a/app/assets/templates/frontend/directives/menu-row.html.haml +++ b/app/assets/templates/frontend/directives/menu-row.html.haml @@ -1,10 +1,14 @@ .row - .column{"ng-if" => "circle"} - .circle.small{"ng-class" => "circle"} .column - .label - {{title}} - .sublabel{"ng-if" => "subtitle"} - {{subtitle}} - - %ng-transclude + .left + .column{"ng-if" => "circle"} + .circle.small{"ng-class" => "circle"} + .column + .label + {{title}} + .sublabel{"ng-if" => "subtitle"} + {{subtitle}} + %ng-transclude + .column{"ng-if" => "hasButton"} + .button.info{"ng-click" => "clickButton($event)", "ng-class" => "buttonClass"} + {{buttonText}} diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css index 2fb57a16b..c46bf1db7 100644 --- a/vendor/assets/stylesheets/stylekit.css +++ b/vendor/assets/stylesheets/stylekit.css @@ -111,16 +111,25 @@ cursor: pointer; display: flex; flex-direction: row; + justify-content: space-between; } .sn-component .menu-panel .row .column { display: flex; justify-content: center; flex-direction: column; + /* Nested row */ } .sn-component .menu-panel .row .column:not(:first-child) { padding-left: 1.0rem; padding-right: 0.15rem; } +.sn-component .menu-panel .row .column .row { + padding: 0; + border-bottom: none; +} +.sn-component .menu-panel .row .column .left { + display: flex; +} .sn-component .menu-panel .row .button .label, .sn-component .menu-panel .row .box .label, .sn-component .menu-panel .row .notification .label, .sn-component .menu-panel .row .circle .label, .sn-component .menu-panel .row .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .button .subtitle, .sn-component .menu-panel .row .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .box .subtitle, .sn-component .menu-panel .row .notification .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .notification .subtitle, .sn-component .menu-panel .row .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .circle .subtitle { font-size: 0.8rem; font-weight: normal; @@ -365,8 +374,8 @@ border-radius: 50%; } .sn-component .circle.small { - width: 12px; - height: 12px; + width: 11px; + height: 11px; } .sn-component .spinner { border: 1px solid #515263; @@ -402,5 +411,34 @@ transform: rotate(360deg); } } +.sn-component .app-bar { + display: flex; + width: 100%; + height: 2rem; + padding: 0.1rem 0.8rem; + background-color: #F6F6F6; + justify-content: space-between; + align-items: center; + border: 1px solid #DDDDDD; +} +.sn-component .app-bar .left, .sn-component .app-bar .right { + display: flex; +} +.sn-component .app-bar .item { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +.sn-component .app-bar .item:not(:first-child) { + margin-left: 1rem; +} +.sn-component .app-bar .item .label, .sn-component .app-bar .item .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .app-bar .item .subtitle { + font-weight: bold; + font-size: 0.9rem; +} +.sn-component .app-bar .item .label:hover, .sn-component .app-bar .item .panel .content .panel-section .subtitle:hover, .sn-component .panel .content .panel-section .app-bar .item .subtitle:hover { + color: #086DD6; +} /*# sourceMappingURL=stylekit.css.map */ From 5a995014924eb3c2ec69c7046c402495883d8fa4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 6 Jan 2018 01:50:48 -0600 Subject: [PATCH 20/99] App bar --- app/assets/stylesheets/app/_editor.scss | 5 - app/assets/stylesheets/app/_footer.scss | 122 ++++--------- app/assets/stylesheets/app/_menus.scss | 65 +------ app/assets/stylesheets/app/_notes.scss | 3 +- .../directives/account-menu.html.haml | 57 +++--- .../templates/frontend/editor.html.haml | 45 ++--- .../templates/frontend/footer.html.haml | 57 +++--- app/assets/templates/frontend/notes.html.haml | 19 +- vendor/assets/stylesheets/stylekit.css | 169 ++++++++++++++---- 9 files changed, 272 insertions(+), 270 deletions(-) diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index 98b896f70..7b4c08d2b 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -5,11 +5,6 @@ $heading-height: 75px; flex-direction: column; overflow-y: hidden; background-color: white; - - .section-menu-bar { - flex: 1 0 28px; - max-height: 28px; - } } #editor-title-bar { diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index 9934f5355..c705c0a69 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -1,90 +1,34 @@ -.fake-link { - font-weight: bold; - cursor: pointer; - - &:hover { - text-decoration: underline; - } -} - -h2 { - margin-bottom: 0px; - margin-top: 0px; -} - #footer-bar { position: relative; width: 100%; - padding: 5px; - background-color: #F6F6F6; - border-top: 1px solid #D3D3D3; height: $footer-height; max-height: $footer-height; z-index: 100; - - .pull-left, .pull-right { - height: 100%; - display: flex; - } - - section { - padding: 5px; - padding-bottom: 2px; - margin-top: 5px; - - &.inline-h { - padding-top: 5px; - padding-left: 0; - padding-right: 0; - } - - } } -#footer-bar .footer-bar-link { - margin-left: 10px; - &:not(:first-child) { - margin-left: 12px; - } - +#footer-bar .item { z-index: 1000; position: relative; - height: 100%; user-select: none; - display: flex; - align-items: center; - justify-content: center; + .panel { + max-height: 85vh; + position: absolute; + right: 0px; + left: 10px; + bottom: 40px; + min-width: 300px; + z-index: 1000; + margin-top: 15px; + background-color: white; - > .label { - font-size: 12px; - font-weight: bold; - color: #515263; - cursor: pointer; - - &:hover { - text-decoration: none; - color: $blue-color; + .close-button { + &:hover { + text-decoration: none; + } } } -} -.footer-bar-link .panel { - max-height: 85vh; - position: absolute; - right: 0px; - left: 10px; - bottom: 40px; - min-width: 300px; - z-index: 1000; - margin-top: 15px; - background-color: white; -} - -.panel .close-button { - &:hover { - text-decoration: none; - } } #account-panel { @@ -181,21 +125,21 @@ a.disabled { -.spinner { - height: 10px; - width: 10px; - animation: rotate 0.8s infinite linear; - border: 1px solid #515263; - border-right-color: transparent; - border-radius: 50%; - - &.tinted { - border: 1px solid $blue-color; - border-right-color: transparent; - } -} - -@keyframes rotate { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} +// .spinner { +// height: 10px; +// width: 10px; +// animation: rotate 0.8s infinite linear; +// border: 1px solid #515263; +// border-right-color: transparent; +// border-radius: 50%; +// +// &.tinted { +// border: 1px solid $blue-color; +// border-right-color: transparent; +// } +// } +// +// @keyframes rotate { +// 0% { transform: rotate(0deg); } +// 100% { transform: rotate(360deg); } +// } diff --git a/app/assets/stylesheets/app/_menus.scss b/app/assets/stylesheets/app/_menus.scss index 27d2df09b..3defb1d9e 100644 --- a/app/assets/stylesheets/app/_menus.scss +++ b/app/assets/stylesheets/app/_menus.scss @@ -1,61 +1,6 @@ -ul.section-menu-bar { - width: 100%; - padding-top: 0px; - padding-left: 6px; - padding-right: 21px; - - &.no-h-padding { - padding-left: 0px; - padding-right: 0px; - } - - user-select: none; - - background-color: #f1f1f1; - color: $selected-text-color; - height: 28px; - cursor: default; - - margin-top: 0px; - margin-bottom: 0; - list-style: none; - font-weight: bold; - font-size: 0; /* trick to remove gaps between li inline-block elements */ - - > li { - padding: 6px 8px; - text-align: left; - display: inline-block; +.app-bar { + .item { position: relative; - - font-size: 13px; - font-weight: bold; - user-select: none; - - &.full-width { - width: 100%; - padding-left: 14px; - } - - &.item-with-subtitle { - label { - margin-right: 8px; - } - - .subtitle { - margin-top: 1px; - opacity: 0.5; - font-weight: normal; - font-size: 12px; - } - } - - - &.selected { - background-color: $blue-color; - border-radius: 1px; - color: white; - } } } @@ -67,12 +12,8 @@ ul.section-menu-bar { min-width: 160px; z-index: 100; - list-style: none; - font-size: 14px; - text-align: left; + margin-top: 5px; - padding: 0 0; - border: none; width: 280px; max-height: calc(85vh - 90px); diff --git a/app/assets/stylesheets/app/_notes.scss b/app/assets/stylesheets/app/_notes.scss index 7075a8b87..468c999da 100644 --- a/app/assets/stylesheets/app/_notes.scss +++ b/app/assets/stylesheets/app/_notes.scss @@ -16,12 +16,12 @@ } #notes-title-bar { - color: rgba(black, 0.40); padding-top: 16px; font-weight: normal; font-size: 18px; .title { + color: rgba(black, 0.40); width: calc(90% - 45px); } } @@ -31,6 +31,7 @@ } #notes-menu-bar { + color: default; position: relative; margin-top: 14px; height: auto; diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index bf99e0e65..19da283c1 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -65,13 +65,14 @@ - %a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password - %section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"} - %p.bold Change Password (Beta) - %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. - %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. - %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. - %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. + %a.panel-row{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password + .notification.default{"ng-if" => "newPasswordData.changePassword"} + %h1.title Change Password (Beta) + .text + %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. + %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. + %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. + %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. %div.mt-10{"ng-if" => "!newPasswordData.status"} %a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel @@ -79,17 +80,18 @@ %form %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} - %button.ui-button.block{"ng-click" => "submitPasswordChange()"} Submit + .button-group.stretch.panel-row.form-submit + .button.info{"type" => "submit", "ng-click" => "submitPasswordChange()"} + .label Submit %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} - - %a.block.mt-5{"ng-click" => "showAdvanced = !showAdvanced"} Advanced + %a.panel-row{"ng-click" => "showAdvanced = !showAdvanced"} Advanced %div{"ng-if" => "showAdvanced"} - %a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard - %a.block.mt-5{"ng-click" => "reencryptPressed()"} Re-encrypt All Items - %a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials + %a.panel-row{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard + %a.panel-row{"ng-click" => "reencryptPressed()"} Re-encrypt All Items + %a.panel-row{"ng-click" => "showCredentials = !showCredentials"} Show Credentials %section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"} %label.block Encryption key: @@ -118,10 +120,10 @@ .panel-section %h3.title Encryption + %h5.subtitle{"ng-if" => "encryptionEnabled()"} + {{encryptionStatusForNotes()}} %p {{encryptionStatusString()}} - %div.mt-5{"ng-if" => "encryptionEnabled()"} - %i {{encryptionStatusForNotes()}} .panel-section %h3.title Passcode Lock @@ -129,12 +131,14 @@ .button.info{"ng-click" => "addPasscodeClicked()", "ng-if" => "!formData.showPasscodeForm"} .label Add Passcode + %p Add an app passcode to lock the app and encrypt on-device key storage. + %form.mt-5{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} - %p.bold Choose a passcode: %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "autofocus" => "true"} %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} - %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Set Passcode - %p Add an app passcode to lock the app and encrypt on-device key storage. + .button-group.stretch.panel-row.form-submit + .button.info{"type" => "submit"} + .label Set Passcode %div{"ng-if" => "hasPasscode()"} %p Passcode lock is enabled. @@ -147,13 +151,14 @@ .panel-section{"ng-if" => "!importData.loading"} %h3.title Data Backups - %div{"ng-if" => "encryptedBackupsAvailable()"} - %label.normal.inline - %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} - Encrypted - %label.normal.inline - %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} - Decrypted + %form.panel-form{"ng-if" => "encryptedBackupsAvailable()"} + .input-group + %label + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} + Encrypted + %label + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} + Decrypted .button-group .button.info{"ng-click" => "downloadDataArchive()", "ng-class" => "{'mt-5' : !user}"} @@ -164,7 +169,7 @@ .label Import From Backup %div{"ng-if" => "importData.requestPassword"} - %form{"ng-submit" => "submitImportPassword()"} + %form.panel-form{"ng-submit" => "submitImportPassword()"} %p Enter the account password associated with the import file. %input.form-control.mt-5{:type => 'password', "ng-model" => "importData.password", "autofocus" => "true"} %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Decrypt & Import diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index c89419dc6..b63d1f280 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -12,32 +12,33 @@ %iframe#note-tags-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.tagsComponent) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts", "data-component-id" => "{{ctrl.tagsComponent.uuid}}"} %input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} - %ul.section-menu-bar{"ng-if" => "ctrl.note"} - %li{"ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} - %label{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;"} Menu - .sn-component - .menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"} - .section - .header - %h4.title Note - %menu-row{"title" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} - %menu-row{"title" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} - %menu-row{"title" => "'Delete'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} - %menu-row{"ng-if" => "ctrl.hasDisabledComponents()", "title" => "'Restore Disabled Components'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledComponents()"} + .sn-component{"ng-if" => "ctrl.note"} + .app-bar.no-edges + .left + .item{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} + .label Menu + .menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"} + .section + .header + %h4.title Note + %menu-row{"title" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} + %menu-row{"title" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} + %menu-row{"title" => "'Delete'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} + %menu-row{"ng-if" => "ctrl.hasDisabledComponents()", "title" => "'Restore Disabled Components'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledComponents()"} - .section{"ng-if" => "!ctrl.editor"} - .header - %h4.title Display - %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('monospaceFont')"} + .section{"ng-if" => "!ctrl.editor"} + .header + %h4.title Display + %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('monospaceFont')"} - %li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} - %label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor - %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"} + .item{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} + .label Editor + %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"} - %li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} - %label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions - %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} + .item{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} + .label Actions + %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "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/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index 5fb62a127..4b772f60e 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -1,33 +1,40 @@ -#footer-bar - .pull-left - .footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} - %a.label{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account - %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"} +.sn-component + #footer-bar.app-bar.no-edges + .left + .item{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} + .column + .circle.small.info + .column + .label.title{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account + %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"} - .footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} - %a.label{"ng-click" => "ctrl.toggleExtensions()"} Extensions - %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} + .item{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} + .label.title{"ng-click" => "ctrl.toggleExtensions()"} Extensions + %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} - .footer-bar-link - %a.label{"href" => "https://standardnotes.org/help", "target" => "_blank"} - Help + .item + .label.title{"href" => "https://standardnotes.org/help", "target" => "_blank"} + Help - %room-bar#room-bar + %room-bar#room-bar - .pull-right + .right - .footer-bar-link{"ng-if" => "ctrl.newUpdateAvailable", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"} - %span.tinted.normal New update downloaded. Installs on app restart. + .item{"ng-if" => "ctrl.newUpdateAvailable", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"} + %span.tinted.normal New update downloaded. Installs on app restart. - .footer-bar-link{"style" => "margin-right: 5px;"} - %span{"ng-if" => "ctrl.lastSyncDate", "style" => "float: left; font-weight: normal; margin-right: 8px;"} - %span.label{"ng-if" => "!ctrl.isRefreshing"} - Last refreshed {{ctrl.lastSyncDate | appDateTime}} - %span{"ng-if" => "ctrl.isRefreshing"} - .spinner{"style" => "margin-top: 2px;"} + .item{"ng-if" => "ctrl.lastSyncDate"} + .label + %span{"ng-if" => "!ctrl.isRefreshing"} + Last refreshed {{ctrl.lastSyncDate | appDateTime}} + %span{"ng-if" => "ctrl.isRefreshing"} + .spinner - %strong.label{"ng-if" => "ctrl.offline"} Offline - %a.label{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} Refresh + .item{"ng-if" => "ctrl.offline"} + .label Offline + .item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} + .label Refresh - %span.label{"ng-if" => "ctrl.hasPasscode()"} - %i.icon.ion-locked{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"} + .item{"ng-if" => "ctrl.hasPasscode()"} + .label + %i.icon.ion-locked{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"} diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index e5ae42b1a..5574d5a1c 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -8,15 +8,18 @@ .filter-section %input.filter-bar#search-bar.mousetrap{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Search", "ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true"} #search-clear-button{"ng-if" => "ctrl.noteFilter.text", "ng-click" => "ctrl.noteFilter.text = ''; ctrl.filterTextChanged()"} ✕ - %ul.section-menu-bar#notes-menu-bar.no-h-padding - %li.item-with-subtitle.full-width{"ng-class" => "{'selected' : ctrl.showMenu}"} - .wrapper{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"} - %label - Options - %span.subtitle {{ctrl.optionsSubtitle()}} + .sn-component#notes-menu-bar + .app-bar.no-edges + .left + .item{"ng-click" => "ctrl.showMenu = !ctrl.showMenu", "ng-class" => "{'selected' : ctrl.showMenu}"} + .column + .label + Options + .column + .sublabel {{ctrl.optionsSubtitle()}} - .sn-component - .menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"} + .sn-component{"ng-if" => "ctrl.showMenu"} + .menu-panel.dropdown-menu .section .header %h4.title Sort By diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css index c46bf1db7..2a06681db 100644 --- a/vendor/assets/stylesheets/stylekit.css +++ b/vendor/assets/stylesheets/stylekit.css @@ -130,7 +130,7 @@ .sn-component .menu-panel .row .column .left { display: flex; } -.sn-component .menu-panel .row .button .label, .sn-component .menu-panel .row .box .label, .sn-component .menu-panel .row .notification .label, .sn-component .menu-panel .row .circle .label, .sn-component .menu-panel .row .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .button .subtitle, .sn-component .menu-panel .row .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .box .subtitle, .sn-component .menu-panel .row .notification .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .notification .subtitle, .sn-component .menu-panel .row .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .circle .subtitle { +.sn-component .menu-panel .row .button .label, .sn-component .menu-panel .row .box .label, .sn-component .menu-panel .row .circle .label, .sn-component .menu-panel .row .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .button .subtitle, .sn-component .menu-panel .row .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .box .subtitle, .sn-component .menu-panel .row .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .circle .subtitle { font-size: 0.8rem; font-weight: normal; } @@ -152,7 +152,7 @@ .sn-component .tinted { color: #086DD6; } -.sn-component h1, .sn-component h2, .sn-component h3, .sn-component h4 { +.sn-component h1, .sn-component h2, .sn-component h3, .sn-component h4, .sn-component h5 { margin: 0; padding: 0; } @@ -212,11 +212,17 @@ display: block; font-size: 1.1rem; } -.sn-component label input[type='checkbox'] { +.sn-component label input[type='checkbox'], .sn-component input[type='radio'] { width: auto; - margin-right: 0.5rem; + margin-right: 0.45rem; /* Space after checkbox */ } +.sn-component .input-group > * { + display: inline-block; +} +.sn-component .input-group > *:not(:first-child) { + margin-left: 0.9rem; +} .sn-component .checkbox-group { padding-top: 0.5rem; padding-bottom: 0.3rem; @@ -236,29 +242,29 @@ .sn-component .button-group.stretch { display: flex; } -.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box, .sn-component .button-group.stretch .notification, .sn-component .button-group.stretch .circle { +.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box, .sn-component .button-group.stretch .circle { display: block; flex-grow: 1; text-align: center; } -.sn-component .button-group .button, .sn-component .button-group .box, .sn-component .button-group .notification, .sn-component .button-group .circle { +.sn-component .button-group .button, .sn-component .button-group .box, .sn-component .button-group .circle { display: inline-block; margin-bottom: 5px; } -.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child), .sn-component .button-group .notification:not(:last-child), .sn-component .button-group .circle:not(:last-child) { +.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child), .sn-component .button-group .circle:not(:last-child) { margin-right: 5px; } -.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured, .sn-component .button-group .notification:not(:last-child).featured, .sn-component .button-group .circle:not(:last-child).featured { +.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured, .sn-component .button-group .circle:not(:last-child).featured { margin-right: 8px; } -.sn-component .box-group .box, .sn-component .box-group .notification { +.sn-component .box-group .box { display: inline-block; margin-bottom: 5px; } -.sn-component .box-group .box:not(:last-child), .sn-component .box-group .notification:not(:last-child) { +.sn-component .box-group .box:not(:last-child) { margin-right: 5px; } -.sn-component .button, .sn-component .box, .sn-component .notification, .sn-component .circle { +.sn-component .button, .sn-component .box, .sn-component .circle { display: table; border-radius: 3px; padding: 0.5rem 0.7rem; @@ -267,91 +273,175 @@ text-align: center; border: 1px solid; } -.sn-component .button .label, .sn-component .box .label, .sn-component .notification .label, .sn-component .circle .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle, .sn-component .notification .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .notification .subtitle, .sn-component .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .circle .subtitle { +.sn-component .button .label, .sn-component .box .label, .sn-component .circle .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle, .sn-component .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .circle .subtitle { font-weight: bold; display: block; text-align: center; } -.sn-component .box, .sn-component .notification { +.sn-component .box { padding: 2.5rem 1.5rem; } -.sn-component .button.info, .sn-component .info.box, .sn-component .info.notification, .sn-component .info.circle, .sn-component .box.info, .sn-component .info.notification, .sn-component .notification.info, .sn-component .circle.info { +.sn-component .button.default, .sn-component .default.box, .sn-component .default.circle, .sn-component .box.default, .sn-component .circle.default { + background-color: rgba(139, 139, 139, 0.1); + border-color: #8b8b8b; + color: #8b8b8b; +} +.sn-component .button.default:hover, .sn-component .default.box:hover, .sn-component .default.circle:hover, .sn-component .box.default:hover, .sn-component .circle.default:hover { + background-color: white; + color: #939393; +} +.sn-component .button.default.featured, .sn-component .default.featured.box, .sn-component .default.featured.circle, .sn-component .box.default.featured, .sn-component .circle.default.featured { + background-color: #8b8b8b; + border: none; + color: white; + padding: 0.75rem 1.25rem; + font-size: 1.1rem; +} +.sn-component .button.default.featured:hover, .sn-component .default.featured.box:hover, .sn-component .default.featured.circle:hover, .sn-component .box.default.featured:hover, .sn-component .circle.default.featured:hover { + background-color: #9f9f9f; +} +.sn-component .button.info, .sn-component .info.box, .sn-component .info.circle, .sn-component .box.info, .sn-component .circle.info { background-color: rgba(8, 109, 214, 0.1); border-color: #086DD6; color: #086DD6; } -.sn-component .button.info:hover, .sn-component .info.box:hover, .sn-component .info.notification:hover, .sn-component .info.circle:hover, .sn-component .box.info:hover, .sn-component .info.notification:hover, .sn-component .notification.info:hover, .sn-component .circle.info:hover { +.sn-component .button.info:hover, .sn-component .info.box:hover, .sn-component .info.circle:hover, .sn-component .box.info:hover, .sn-component .circle.info:hover { background-color: #d5e9fd; color: #0975e5; } -.sn-component .button.info.featured, .sn-component .info.featured.box, .sn-component .info.featured.notification, .sn-component .info.featured.circle, .sn-component .box.info.featured, .sn-component .info.featured.notification, .sn-component .notification.info.featured, .sn-component .circle.info.featured { +.sn-component .button.info.featured, .sn-component .info.featured.box, .sn-component .info.featured.circle, .sn-component .box.info.featured, .sn-component .circle.info.featured { background-color: #086DD6; border: none; color: white; padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .button.info.featured:hover, .sn-component .info.featured.box:hover, .sn-component .info.featured.notification:hover, .sn-component .info.featured.circle:hover, .sn-component .box.info.featured:hover, .sn-component .info.featured.notification:hover, .sn-component .notification.info.featured:hover, .sn-component .circle.info.featured:hover { +.sn-component .button.info.featured:hover, .sn-component .info.featured.box:hover, .sn-component .info.featured.circle:hover, .sn-component .box.info.featured:hover, .sn-component .circle.info.featured:hover { background-color: #1181f6; } -.sn-component .button.warning, .sn-component .warning.box, .sn-component .warning.notification, .sn-component .warning.circle, .sn-component .box.warning, .sn-component .warning.notification, .sn-component .notification.warning { +.sn-component .button.warning, .sn-component .warning.box, .sn-component .warning.circle, .sn-component .box.warning, .sn-component .circle.warning { background-color: rgba(246, 162, 0, 0.1); border-color: #f6a200; color: #f6a200; } -.sn-component .button.warning:hover, .sn-component .warning.box:hover, .sn-component .warning.notification:hover, .sn-component .warning.circle:hover, .sn-component .box.warning:hover, .sn-component .warning.notification:hover, .sn-component .notification.warning:hover { +.sn-component .button.warning:hover, .sn-component .warning.box:hover, .sn-component .warning.circle:hover, .sn-component .box.warning:hover, .sn-component .circle.warning:hover { background-color: #fff8ec; color: #ffaa06; } -.sn-component .button.warning.featured, .sn-component .warning.featured.box, .sn-component .warning.featured.notification, .sn-component .warning.featured.circle, .sn-component .box.warning.featured, .sn-component .warning.featured.notification, .sn-component .notification.warning.featured { +.sn-component .button.warning.featured, .sn-component .warning.featured.box, .sn-component .warning.featured.circle, .sn-component .box.warning.featured, .sn-component .circle.warning.featured { background-color: #f6a200; border: none; color: white; padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .button.warning.featured:hover, .sn-component .warning.featured.box:hover, .sn-component .warning.featured.notification:hover, .sn-component .warning.featured.circle:hover, .sn-component .box.warning.featured:hover, .sn-component .warning.featured.notification:hover, .sn-component .notification.warning.featured:hover { +.sn-component .button.warning.featured:hover, .sn-component .warning.featured.box:hover, .sn-component .warning.featured.circle:hover, .sn-component .box.warning.featured:hover, .sn-component .circle.warning.featured:hover { background-color: #ffb320; } -.sn-component .button.danger, .sn-component .danger.box, .sn-component .danger.notification, .sn-component .danger.circle, .sn-component .box.danger, .sn-component .danger.notification, .sn-component .notification.danger { +.sn-component .button.danger, .sn-component .danger.box, .sn-component .danger.circle, .sn-component .box.danger, .sn-component .circle.danger { background-color: rgba(248, 3, 36, 0.1); border-color: #F80324; color: #F80324; } -.sn-component .button.danger:hover, .sn-component .danger.box:hover, .sn-component .danger.notification:hover, .sn-component .danger.circle:hover, .sn-component .box.danger:hover, .sn-component .danger.notification:hover, .sn-component .notification.danger:hover { +.sn-component .button.danger:hover, .sn-component .danger.box:hover, .sn-component .danger.circle:hover, .sn-component .box.danger:hover, .sn-component .circle.danger:hover { background-color: #fff1f3; color: #fc0e2e; } -.sn-component .button.danger.featured, .sn-component .danger.featured.box, .sn-component .danger.featured.notification, .sn-component .danger.featured.circle, .sn-component .box.danger.featured, .sn-component .danger.featured.notification, .sn-component .notification.danger.featured { +.sn-component .button.danger.featured, .sn-component .danger.featured.box, .sn-component .danger.featured.circle, .sn-component .box.danger.featured, .sn-component .circle.danger.featured { background-color: #F80324; border: none; color: white; padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .button.danger.featured:hover, .sn-component .danger.featured.box:hover, .sn-component .danger.featured.notification:hover, .sn-component .danger.featured.circle:hover, .sn-component .box.danger.featured:hover, .sn-component .danger.featured.notification:hover, .sn-component .notification.danger.featured:hover { +.sn-component .button.danger.featured:hover, .sn-component .danger.featured.box:hover, .sn-component .danger.featured.circle:hover, .sn-component .box.danger.featured:hover, .sn-component .circle.danger.featured:hover { background-color: #fc2744; } -.sn-component .button.success, .sn-component .success.box, .sn-component .success.notification, .sn-component .success.circle, .sn-component .box.success, .sn-component .success.notification, .sn-component .notification.success { +.sn-component .button.success, .sn-component .success.box, .sn-component .success.circle, .sn-component .box.success, .sn-component .circle.success { background-color: rgba(43, 150, 18, 0.1); border-color: #2B9612; color: #2B9612; } -.sn-component .button.success:hover, .sn-component .success.box:hover, .sn-component .success.notification:hover, .sn-component .success.circle:hover, .sn-component .box.success:hover, .sn-component .success.notification:hover, .sn-component .notification.success:hover { +.sn-component .button.success:hover, .sn-component .success.box:hover, .sn-component .success.circle:hover, .sn-component .box.success:hover, .sn-component .circle.success:hover { background-color: #b7f5a8; color: #2fa414; } -.sn-component .button.success.featured, .sn-component .success.featured.box, .sn-component .success.featured.notification, .sn-component .success.featured.circle, .sn-component .box.success.featured, .sn-component .success.featured.notification, .sn-component .notification.success.featured { +.sn-component .button.success.featured, .sn-component .success.featured.box, .sn-component .success.featured.circle, .sn-component .box.success.featured, .sn-component .circle.success.featured { background-color: #2B9612; border: none; color: white; padding: 0.75rem 1.25rem; font-size: 1.1rem; } -.sn-component .button.success.featured:hover, .sn-component .success.featured.box:hover, .sn-component .success.featured.notification:hover, .sn-component .success.featured.circle:hover, .sn-component .box.success.featured:hover, .sn-component .success.featured.notification:hover, .sn-component .notification.success.featured:hover { +.sn-component .button.success.featured:hover, .sn-component .success.featured.box:hover, .sn-component .success.featured.circle:hover, .sn-component .box.success.featured:hover, .sn-component .circle.success.featured:hover { + background-color: #35ba16; +} +.sn-component .notification.default { + background-color: #F6F6F6; + border-color: #c4c4c4; +} +.sn-component .notification.info { + background-color: rgba(8, 109, 214, 0.1); + border-color: #086DD6; + color: #086DD6; +} +.sn-component .notification.info.featured { + background-color: #086DD6; + border: none; + color: white; + padding: 0.75rem 1.25rem; + font-size: 1.1rem; +} +.sn-component .notification.info.featured:hover { + background-color: #1181f6; +} +.sn-component .notification.warning { + background-color: rgba(246, 162, 0, 0.1); + border-color: #f6a200; + color: #f6a200; +} +.sn-component .notification.warning.featured { + background-color: #f6a200; + border: none; + color: white; + padding: 0.75rem 1.25rem; + font-size: 1.1rem; +} +.sn-component .notification.warning.featured:hover { + background-color: #ffb320; +} +.sn-component .notification.danger { + background-color: rgba(248, 3, 36, 0.1); + border-color: #F80324; + color: #F80324; +} +.sn-component .notification.danger.featured { + background-color: #F80324; + border: none; + color: white; + padding: 0.75rem 1.25rem; + font-size: 1.1rem; +} +.sn-component .notification.danger.featured:hover { + background-color: #fc2744; +} +.sn-component .notification.success { + background-color: rgba(43, 150, 18, 0.1); + border-color: #2B9612; + color: #2B9612; +} +.sn-component .notification.success.featured { + background-color: #2B9612; + border: none; + color: white; + padding: 0.75rem 1.25rem; + font-size: 1.1rem; +} +.sn-component .notification.success.featured:hover { background-color: #35ba16; } .sn-component .notification { + border: 1px solid; padding: 1.1rem 1rem; margin: 1.4rem 0; text-align: left; @@ -421,10 +511,16 @@ align-items: center; border: 1px solid #DDDDDD; } +.sn-component .app-bar.no-edges { + border-left: 0; + border-right: 0; +} .sn-component .app-bar .left, .sn-component .app-bar .right { display: flex; + height: 100%; } .sn-component .app-bar .item { + flex-grow: 1; cursor: pointer; display: flex; align-items: center; @@ -433,12 +529,21 @@ .sn-component .app-bar .item:not(:first-child) { margin-left: 1rem; } -.sn-component .app-bar .item .label, .sn-component .app-bar .item .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .app-bar .item .subtitle { +.sn-component .app-bar .item > .column:not(:first-child) { + margin-left: 0.5rem; +} +.sn-component .app-bar .item:hover > .label, .sn-component .app-bar .panel .content .panel-section .item:hover > .subtitle, .sn-component .panel .content .panel-section .app-bar .item:hover > .subtitle, .sn-component .app-bar .item:hover > .sublabel, .sn-component .app-bar .item:hover > .column > .label, .sn-component .app-bar .panel .content .panel-section .item:hover > .column > .subtitle, .sn-component .panel .content .panel-section .app-bar .item:hover > .column > .subtitle, .sn-component .app-bar .item:hover > .column > .sublabel { + color: #086DD6; +} +.sn-component .app-bar .item > .label, .sn-component .app-bar .panel .content .panel-section .item > .subtitle, .sn-component .panel .content .panel-section .app-bar .item > .subtitle, .sn-component .app-bar .item > .column > .label, .sn-component .app-bar .panel .content .panel-section .item > .column > .subtitle, .sn-component .panel .content .panel-section .app-bar .item > .column > .subtitle { font-weight: bold; font-size: 0.9rem; + white-space: nowrap; } -.sn-component .app-bar .item .label:hover, .sn-component .app-bar .item .panel .content .panel-section .subtitle:hover, .sn-component .panel .content .panel-section .app-bar .item .subtitle:hover { - color: #086DD6; +.sn-component .app-bar .item > .sublabel, .sn-component .app-bar .item > .column > .sublabel { + font-size: 0.9rem; + font-weight: normal; + white-space: nowrap; } /*# sourceMappingURL=stylekit.css.map */ From 3cf5a9ad45c09c15e821f197f563cf53a6d356a7 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 6 Jan 2018 10:14:56 -0600 Subject: [PATCH 21/99] Menu updates --- .../app/frontend/controllers/footer.js | 62 +++++++++++++++- .../app/services/directives/views/roomBar.js | 73 ------------------- app/assets/stylesheets/app/_footer.scss | 56 +------------- app/assets/stylesheets/app/_notes.scss | 2 +- .../directives/account-menu.html.haml | 53 ++++++-------- .../frontend/directives/room-bar.html.haml | 5 -- .../templates/frontend/editor.html.haml | 2 +- .../templates/frontend/footer.html.haml | 27 ++++--- vendor/assets/stylesheets/stylekit.css | 56 ++++++++++---- 9 files changed, 150 insertions(+), 186 deletions(-) delete mode 100644 app/assets/javascripts/app/services/directives/views/roomBar.js delete mode 100644 app/assets/templates/frontend/directives/room-bar.html.haml diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index e59321187..6028de680 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -22,7 +22,8 @@ angular.module('app.frontend') } } }) - .controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager, syncManager, storageManager, passcodeManager) { + .controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager, + syncManager, storageManager, passcodeManager, componentManager, singletonManager) { this.user = authManager.user; @@ -106,4 +107,63 @@ angular.module('app.frontend') this.newUpdateAvailable = false; alert("A new update is ready to install. Updates address performance and security issues, as well as bug fixes and feature enhancements. Simply quit Standard Notes and re-open it for the update to be applied.") } + + + /* Rooms */ + + this.componentManager = componentManager; + this.rooms = []; + + modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { + this.rooms = _.uniq(this.rooms.concat(allItems.filter((candidate) => {return candidate.area == "rooms"}))) + .filter((candidate) => {return !candidate.deleted}); + }); + + componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: (component) => { + if(component.active) { + $timeout(() => { + var iframe = componentManager.iframeForComponent(component); + if(iframe) { + var lastSize = component.getRoomLastSize(); + if(lastSize) { + componentManager.handleSetSizeEvent(component, lastSize); + } + iframe.onload = function() { + componentManager.registerComponentWindow(component, iframe.contentWindow); + }.bind(this); + } + }); + } + }, actionHandler: (component, action, data) => { + if(action == "set-size") { + componentManager.handleSetSizeEvent(component, data); + component.setRoomLastSize(data); + } + }}); + + this.selectRoom = function(room) { + room.show = !room.show; + if(room.show) { + this.componentManager.activateComponent(room); + } else { + this.hideRoom(room); + } + } + + this.hideRoom = function(room) { + room.show = false; + this.componentManager.deactivateComponent(room); + } + + // Handle singleton ProLink instance + singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: "org.standardnotes.prolink"}}, (resolvedSingleton) => { + console.log("Roombar received resolved ProLink", resolvedSingleton); + }, (valueCallback) => { + console.log("Creating prolink"); + // Safe to create. Create and return object. + let url = window._prolink_package_url; + packageManager.installPackage(url, (component) => { + valueCallback(component); + }) + }); }); diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js deleted file mode 100644 index f237f1aa7..000000000 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ /dev/null @@ -1,73 +0,0 @@ -class RoomBar { - - constructor() { - this.restrict = "E"; - this.templateUrl = "frontend/directives/room-bar.html"; - this.scope = { - }; - } - - controller($rootScope, $scope, desktopManager, syncManager, modelManager, componentManager, $timeout, singletonManager, packageManager) { - 'ngInject'; - - $scope.componentManager = componentManager; - $scope.rooms = []; - - modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { - $scope.rooms = _.uniq($scope.rooms.concat(allItems.filter((candidate) => {return candidate.area == "rooms"}))) - .filter((candidate) => {return !candidate.deleted}); - }); - - componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: (component) => { - if(component.active) { - $timeout(() => { - var iframe = componentManager.iframeForComponent(component); - if(iframe) { - var lastSize = component.getRoomLastSize(); - if(lastSize) { - componentManager.handleSetSizeEvent(component, lastSize); - } - iframe.onload = function() { - componentManager.registerComponentWindow(component, iframe.contentWindow); - }.bind(this); - } - }); - } - }, actionHandler: (component, action, data) => { - if(action == "set-size") { - componentManager.handleSetSizeEvent(component, data); - component.setRoomLastSize(data); - } - }}); - - $scope.selectRoom = function(room) { - room.show = !room.show; - if(room.show) { - this.componentManager.activateComponent(room); - } else { - $scope.hideRoom(room); - } - } - - $scope.hideRoom = function(room) { - room.show = false; - this.componentManager.deactivateComponent(room); - } - - // Handle singleton ProLink instance - singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: "org.standardnotes.prolink"}}, (resolvedSingleton) => { - console.log("Roombar received resolved ProLink", resolvedSingleton); - }, (valueCallback) => { - console.log("Creating prolink"); - // Safe to create. Create and return object. - let url = window._prolink_package_url; - packageManager.installPackage(url, (component) => { - valueCallback(component); - }) - }); - } - - -} - -angular.module('app.frontend').directive('roomBar', () => new RoomBar); diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index c705c0a69..c9110a733 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -33,6 +33,7 @@ #account-panel { width: 400px; + cursor: default; } a.disabled { @@ -67,60 +68,11 @@ a.disabled { // padding-bottom: 6px; } } - } - - - -#room-bar { - display: inline-block; - border-left: 1px solid rgba(black, 0.1); - padding-left: 15px; - padding-right: 10px; - margin-left: 15px; - position: relative; - - .room-item { - display: inline-block; - position: relative; - vertical-align: middle; - - .label { - display: inline-block; - vertical-align: middle; - font-size: 11px; - margin-top: -2px; - } - - .icon { - width: 16px; - height: 16px; - display: inline-block; - vertical-align: middle; - } - - .room-container { - max-height: 85vh; - position: absolute; - right: 0px; - bottom: 20px; - min-width: 300px; - z-index: 1000; - margin-top: 15px; - - box-shadow: 0px 0px 15px rgba(black, 0.2); - border: none; - cursor: default; - overflow: auto; - background-color: white; - } - - .room-iframe { - width: 100%; - height: 100%; - } - } +.room-iframe { + width: 100%; + height: 100%; } diff --git a/app/assets/stylesheets/app/_notes.scss b/app/assets/stylesheets/app/_notes.scss index 468c999da..88fce804f 100644 --- a/app/assets/stylesheets/app/_notes.scss +++ b/app/assets/stylesheets/app/_notes.scss @@ -20,7 +20,7 @@ font-weight: normal; font-size: 18px; - .title { + .section-title-bar-header .title { color: rgba(black, 0.40); width: calc(90% - 45px); } diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 19da283c1..3c674d434 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -55,17 +55,20 @@ %div{"ng-if" => "!formData.showLogin && !formData.showRegister"} .panel-section{"ng-if" => "user"} - %h2 {{user.email}} - %p {{server}} - %div.bold.mt-10.tinted{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"} - .spinner.inline.mr-5.tinted - {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}} - %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}} - %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} + .panel-row + %h2.title.wrap {{user.email}} + .horizontal-group{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"} + .spinner.small.info + .sublabel + {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}} + %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}} + + .subtitle.danger.panel-row{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} + + .subtitle.subtle.panel-row {{server}} - - %a.panel-row{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password + %a.panel-row.condensed{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password .notification.default{"ng-if" => "newPasswordData.changePassword"} %h1.title Change Password (Beta) .text @@ -87,21 +90,10 @@ - %a.panel-row{"ng-click" => "showAdvanced = !showAdvanced"} Advanced + %a.panel-row.condensed{"ng-click" => "showAdvanced = !showAdvanced"} Advanced %div{"ng-if" => "showAdvanced"} %a.panel-row{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard %a.panel-row{"ng-click" => "reencryptPressed()"} Re-encrypt All Items - %a.panel-row{"ng-click" => "showCredentials = !showCredentials"} Show Credentials - %section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"} - %label.block - Encryption key: - .wrap.normal.mt-1.selectable {{encryptionKey()}} - %label.block.mt-5.mb-0 - Server password: - .wrap.normal.mt-1.selectable {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}} - %label.block.mt-5.mb-0 - Authentication key: - .wrap.normal.mt-1.selectable {{authKey() ? authKey() : 'Not available. Sign out then sign back in to compute.'}} %div{"ng-if" => "securityUpdateAvailable()"} @@ -119,19 +111,21 @@ .panel-section - %h3.title Encryption - %h5.subtitle{"ng-if" => "encryptionEnabled()"} + %h3.title.panel-row Encryption + %h5.subtitle.info.panel-row{"ng-if" => "encryptionEnabled()"} {{encryptionStatusForNotes()}} %p {{encryptionStatusString()}} .panel-section - %h3.title Passcode Lock + %h3.title.panel-row Passcode Lock %div{"ng-if" => "!hasPasscode() && passcodeOptionAvailable()"} - .button.info{"ng-click" => "addPasscodeClicked()", "ng-if" => "!formData.showPasscodeForm"} - .label Add Passcode + .panel-row + .button.info{"ng-click" => "addPasscodeClicked(); $event.stopPropagation();", "ng-if" => "!formData.showPasscodeForm"} + .label Add Passcode - %p Add an app passcode to lock the app and encrypt on-device key storage. + .panel-row + %p Add an app passcode to lock the app and encrypt on-device key storage. %form.mt-5{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "autofocus" => "true"} @@ -139,12 +133,13 @@ .button-group.stretch.panel-row.form-submit .button.info{"type" => "submit"} .label Set Passcode - %div{"ng-if" => "hasPasscode()"} + %a.panel-row{"ng-click" => "formData.showPasscodeForm = false"} Cancel + .panel-row{"ng-if" => "hasPasscode()"} %p Passcode lock is enabled. %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. %a.block.mt-5{"ng-click" => "removePasscodePressed()"} Remove Passcode - %div{"ng-if" => "!passcodeOptionAvailable()"} + .panel-row{"ng-if" => "!passcodeOptionAvailable()"} %p Passcode lock is only available to permanent sessions. (You chose not to stay signed in.) diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml deleted file mode 100644 index 76002f265..000000000 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -.room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)", "click-outside" => "hideRoom(room)", "is-open" => "room.show && room.active"} - %img.icon{"ng-src" => "{{room.package_info.icon_bar}}"} - .label {{room.name}} - .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} - %iframe.room-iframe{"ng-src" => "{{componentManager.urlForComponent(room) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{room.uuid}}"} diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index b63d1f280..574fe2f88 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -21,7 +21,7 @@ .menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"} .section .header - %h4.title Note + %h4.title Note Options %menu-row{"title" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} %menu-row{"title" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} %menu-row{"title" => "'Delete'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index 4b772f60e..54634cdec 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -4,31 +4,38 @@ .item{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} .column .circle.small.info - .column - .label.title{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account + .column{"ng-click" => "ctrl.accountMenuPressed()"} + .label.title{"ng-class" => "{red: ctrl.error}"} Account %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"} .item{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} - .label.title{"ng-click" => "ctrl.toggleExtensions()"} Extensions + .column{"ng-click" => "ctrl.toggleExtensions()"} + .label.title Extensions %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} .item .label.title{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help - %room-bar#room-bar + .item.border + + .item{"ng-repeat" => "room in ctrl.rooms", "ng-click" => "ctrl.selectRoom(room)", "click-outside" => "ctrl.hideRoom(room)", "is-open" => "room.show && room.active"} + .label {{room.name}} + .sn-component + .panel-right.panel{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} + %iframe.room-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(room) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{room.uuid}}"} + .right .item{"ng-if" => "ctrl.newUpdateAvailable", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"} %span.tinted.normal New update downloaded. Installs on app restart. - .item{"ng-if" => "ctrl.lastSyncDate"} - .label - %span{"ng-if" => "!ctrl.isRefreshing"} - Last refreshed {{ctrl.lastSyncDate | appDateTime}} - %span{"ng-if" => "ctrl.isRefreshing"} - .spinner + .item.no-pointer{"ng-if" => "ctrl.lastSyncDate && !ctrl.isRefreshing"} + .label.subtle + Last refreshed {{ctrl.lastSyncDate | appDateTime}} + .item{"ng-if" => "ctrl.lastSyncDate && ctrl.isRefreshing"} + .spinner.small .item{"ng-if" => "ctrl.offline"} .label Offline diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css index 2a06681db..07729e74c 100644 --- a/vendor/assets/stylesheets/stylekit.css +++ b/vendor/assets/stylesheets/stylekit.css @@ -48,9 +48,6 @@ .sn-component .panel .content .panel-section.hero { text-align: center; } -.sn-component .panel .content .panel-section.hero .title { - margin-bottom: 1.1rem; -} .sn-component .panel .content .panel-section p:last-child { margin-bottom: 0; } @@ -67,16 +64,29 @@ margin-top: 2.1rem; margin-bottom: 15px; } -.sn-component .panel .content .panel-section .title { - margin-bottom: 12px; -} .sn-component .panel .content .panel-section .subtitle { - color: #086DD6; margin-top: -4px; } +.sn-component .panel .content .panel-section .subtitle.subtle { + font-weight: normal; + opacity: 0.6; +} .sn-component .panel .content .panel-section .panel-row { - display: block; - padding: 0.4rem 0; + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 0.4rem; +} +.sn-component .panel .content .panel-section .panel-row:not(:last-child) { + padding-bottom: 0.4rem; +} +.sn-component .panel .content .panel-section .panel-row:not(:last-child).condensed { + padding-top: 0.2rem; + padding-bottom: 0.2rem; +} +.sn-component .panel .content .panel-section .panel-row p { + margin: 0; + padding: 0; } .sn-component .panel .content .panel-form { width: 100%; @@ -178,6 +188,9 @@ .sn-component a { cursor: pointer; } +.sn-component .wrap { + word-wrap: break-word; +} .sn-component *.info { color: #086DD6; } @@ -217,10 +230,11 @@ margin-right: 0.45rem; /* Space after checkbox */ } -.sn-component .input-group > * { +.sn-component .horizontal-group > *, .sn-component .input-group > * { display: inline-block; + vertical-align: middle; } -.sn-component .input-group > *:not(:first-child) { +.sn-component .horizontal-group > *:not(:first-child), .sn-component .input-group > *:not(:first-child) { margin-left: 0.9rem; } .sn-component .checkbox-group { @@ -241,6 +255,7 @@ } .sn-component .button-group.stretch { display: flex; + width: 100%; } .sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box, .sn-component .button-group.stretch .circle { display: block; @@ -249,7 +264,6 @@ } .sn-component .button-group .button, .sn-component .button-group .box, .sn-component .button-group .circle { display: inline-block; - margin-bottom: 5px; } .sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child), .sn-component .button-group .circle:not(:last-child) { margin-right: 5px; @@ -259,7 +273,6 @@ } .sn-component .box-group .box { display: inline-block; - margin-bottom: 5px; } .sn-component .box-group .box:not(:last-child) { margin-right: 5px; @@ -529,10 +542,21 @@ .sn-component .app-bar .item:not(:first-child) { margin-left: 1rem; } +.sn-component .app-bar .item.border { + border-left: 1px solid #DDDDDD; +} +.sn-component .app-bar .item > .column { + height: 100%; + display: flex; + align-items: center; +} .sn-component .app-bar .item > .column:not(:first-child) { margin-left: 0.5rem; } -.sn-component .app-bar .item:hover > .label, .sn-component .app-bar .panel .content .panel-section .item:hover > .subtitle, .sn-component .panel .content .panel-section .app-bar .item:hover > .subtitle, .sn-component .app-bar .item:hover > .sublabel, .sn-component .app-bar .item:hover > .column > .label, .sn-component .app-bar .panel .content .panel-section .item:hover > .column > .subtitle, .sn-component .panel .content .panel-section .app-bar .item:hover > .column > .subtitle, .sn-component .app-bar .item:hover > .column > .sublabel { +.sn-component .app-bar .item.no-pointer { + cursor: default; +} +.sn-component .app-bar .item:hover > .label:not(.subtle), .sn-component .app-bar .panel .content .panel-section .item:hover > .subtitle:not(.subtle), .sn-component .panel .content .panel-section .app-bar .item:hover > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .sublabel:not(.subtle), .sn-component .app-bar .item:hover > .column > .label:not(.subtle), .sn-component .app-bar .panel .content .panel-section .item:hover > .column > .subtitle:not(.subtle), .sn-component .panel .content .panel-section .app-bar .item:hover > .column > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .column > .sublabel:not(.subtle) { color: #086DD6; } .sn-component .app-bar .item > .label, .sn-component .app-bar .panel .content .panel-section .item > .subtitle, .sn-component .panel .content .panel-section .app-bar .item > .subtitle, .sn-component .app-bar .item > .column > .label, .sn-component .app-bar .panel .content .panel-section .item > .column > .subtitle, .sn-component .panel .content .panel-section .app-bar .item > .column > .subtitle { @@ -545,5 +569,9 @@ font-weight: normal; white-space: nowrap; } +.sn-component .app-bar .item .subtle { + font-weight: normal; + opacity: 0.6; +} /*# sourceMappingURL=stylekit.css.map */ From 4bda20a8d9d6d3d1eacea0792f5b20030d5516f0 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 7 Jan 2018 23:29:37 -0600 Subject: [PATCH 22/99] Wip --- .../app/frontend/controllers/editor.js | 47 ++----- .../app/frontend/controllers/footer.js | 21 +-- .../app/frontend/models/app/component.js | 9 +- .../app/frontend/models/app/theme.js | 4 +- .../app/services/componentManager.js | 39 +++--- .../app/services/desktopManager.js | 3 +- .../directives/views/componentModal.js | 10 +- .../directives/views/componentView.js | 71 ++++++++++ .../services/directives/views/editorMenu.js | 10 +- .../directives/views/permissionsModal.js | 7 +- .../app/services/packageManager.js | 8 +- app/assets/stylesheets/app/_footer.scss | 3 + app/assets/stylesheets/app/_modals.scss | 98 +++++--------- .../directives/account-menu.html.haml | 4 +- .../directives/component-modal.html.haml | 12 +- .../directives/component-view.html.haml | 6 + .../frontend/directives/editor-menu.html.haml | 6 +- .../global-extensions-menu.html.haml | 2 +- .../directives/permissions-modal.html.haml | 43 +++--- .../templates/frontend/editor.html.haml | 9 +- .../templates/frontend/footer.html.haml | 9 +- vendor/assets/stylesheets/stylekit.css | 127 +++++++++++++++--- 22 files changed, 335 insertions(+), 213 deletions(-) create mode 100644 app/assets/javascripts/app/services/directives/views/componentView.js create mode 100644 app/assets/templates/frontend/directives/component-view.html.haml diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index 047264ffa..326674086 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -66,21 +66,15 @@ angular.module('app.frontend') onReady(); } - if(this.editorComponent && this.editorComponent != associatedEditor) { - // Deactivate old editor - componentManager.deactivateComponent(this.editorComponent); - this.editorComponent = null; - } - // Activate new editor if it's different from the one currently activated - if(associatedEditor && associatedEditor != this.editorComponent) { + if(associatedEditor) { // switch after timeout, so that note data isnt posted to current editor $timeout(() => { - this.enableComponent(associatedEditor); - this.editorComponent = associatedEditor; + this.selectedEditor = associatedEditor; onReady(); }) } else { + this.selectedEditor = null; onReady(); } @@ -111,23 +105,19 @@ angular.module('app.frontend') } } - this.selectedEditor = function(editorComponent) { + this.selectEditor = function(editor) { + console.log("selectEditor", editor); this.showEditorMenu = false; - if(this.editorComponent && this.editorComponent !== editorComponent) { - // This disassociates the editor from the note, but the component itself still needs to be deactivated - this.disableComponentForCurrentItem(this.editorComponent); - // Now deactivate the component - componentManager.deactivateComponent(this.editorComponent); - } - - if(editorComponent) { + if(editor) { this.note.setAppDataItem("prefersPlainEditor", false); this.note.setDirty(true); - this.enableComponent(editorComponent); - this.associateComponentWithCurrentItem(editorComponent); + componentManager.associateComponentWithItem(editor, this.note); } else { // Note prefers plain editor + if(this.selectedEditor) { + componentManager.disassociateComponentWithItem(this.selectedEditor, this.note); + } this.note.setAppDataItem("prefersPlainEditor", true); this.note.setDirty(true); syncManager.sync(); @@ -137,7 +127,7 @@ angular.module('app.frontend') }) } - this.editorComponent = editorComponent; + this.selectedEditor = editor; }.bind(this) this.hasAvailableExtensions = function() { @@ -423,10 +413,10 @@ angular.module('app.frontend') } } else { // Editor - if(component.active && this.note && component.isActiveForItem(this.note)) { - this.editorComponent = component; + if(component.active && this.note && (component.isActiveForItem(this.note) || component.isDefaultEditor())) { + this.selectedEditor = component; } else { - this.editorComponent = null; + this.selectedEditor = null; } } @@ -512,15 +502,6 @@ angular.module('app.frontend') componentManager.contextItemDidChangeInArea("editor-editor"); } - this.enableComponent = function(component) { - componentManager.activateComponent(component); - componentManager.setEventFlowForComponent(component, 1); - } - - this.associateComponentWithCurrentItem = function(component) { - componentManager.associateComponentWithItem(component, this.note); - } - let alertKey = "displayed-component-disable-alert"; this.disableComponentForCurrentItem = function(component, showAlert) { componentManager.disassociateComponentWithItem(component, this.note); diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index 6028de680..07001176c 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -23,7 +23,7 @@ angular.module('app.frontend') } }) .controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager, - syncManager, storageManager, passcodeManager, componentManager, singletonManager) { + syncManager, storageManager, passcodeManager, componentManager, singletonManager, packageManager) { this.user = authManager.user; @@ -143,16 +143,17 @@ angular.module('app.frontend') this.selectRoom = function(room) { room.show = !room.show; - if(room.show) { - this.componentManager.activateComponent(room); - } else { - this.hideRoom(room); - } - } - this.hideRoom = function(room) { - room.show = false; - this.componentManager.deactivateComponent(room); + // Allows us to send messages to component modal directive + if(!room.directiveController) { + room.directiveController = {}; + } + + if(!room.show) { + room.directiveController.dismiss(); + } + + console.log("Show", room.show); } // Handle singleton ProLink instance diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index eb515ce32..5287f412e 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -18,7 +18,12 @@ class Component extends Item { mapContentToLocalProperties(content) { super.mapContentToLocalProperties(content) + /* Legacy */ this.url = content.url; + /* New */ + this.local_url = content.local_url; + this.hosted_url = content.hosted_url; + this.name = content.name; this.autoupdate = content.autoupdate; @@ -29,7 +34,6 @@ class Component extends Item { this.permissions = content.permissions; this.active = content.active; - this.local = content.local; // custom data that a component can store in itself this.componentData = content.componentData || {}; @@ -44,12 +48,13 @@ class Component extends Item { structureParams() { var params = { url: this.url, + hosted_url: this.hosted_url, + local_url: this.local_url, name: this.name, area: this.area, package_info: this.package_info, permissions: this.permissions, active: this.active, - local: this.local, autoupdate: this.autoupdate, componentData: this.componentData, disassociatedItemIds: this.disassociatedItemIds, diff --git a/app/assets/javascripts/app/frontend/models/app/theme.js b/app/assets/javascripts/app/frontend/models/app/theme.js index d69eaad76..38ced50ce 100644 --- a/app/assets/javascripts/app/frontend/models/app/theme.js +++ b/app/assets/javascripts/app/frontend/models/app/theme.js @@ -8,14 +8,12 @@ class Theme extends Item { super.mapContentToLocalProperties(content) this.url = content.url; this.name = content.name; - this.local = content.local; } structureParams() { var params = { url: this.url, - name: this.name, - local: this.local + name: this.name }; _.merge(params, super.structureParams()); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index c0dbc2df0..2b73b1580 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -143,7 +143,8 @@ 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(); - params.clientData = item.getDomainDataItem(component.url, ClientDataDomain) || {}; + /* 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) || {}; /* 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 @@ -186,7 +187,7 @@ class ComponentManager { componentForUrl(url) { return this.components.filter(function(component){ - return component.url === url; + return component.url === url || component.hosted_url === url; })[0]; } @@ -251,9 +252,12 @@ class ComponentManager { } } - removePrivatePropertiesFromResponseItems(responseItems) { + removePrivatePropertiesFromResponseItems(responseItems, includeUrls) { // Don't allow component to overwrite these properties. - let privateProperties = ["appData"]; + var privateProperties = ["appData", "autoupdate", "permissions", "active"]; + if(includeUrls) { + privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"]); + } for(var responseItem of responseItems) { // Do not pass in actual items here, otherwise that would be destructive. @@ -275,10 +279,10 @@ class ComponentManager { ]; this.runWithPermissions(component, requiredPermissions, message.permissions, function(){ - if(!_.find(this.streamObservers, {identifier: component.url})) { + if(!_.find(this.streamObservers, {identifier: component.uuid})) { // for pushing laster as changes come in this.streamObservers.push({ - identifier: component.url, + identifier: component.uuid, component: component, originalMessage: message, contentTypes: message.data.content_types @@ -304,10 +308,10 @@ class ComponentManager { ]; this.runWithPermissions(component, requiredPermissions, message.permissions, function(){ - if(!_.find(this.contextStreamObservers, {identifier: component.url})) { + if(!_.find(this.contextStreamObservers, {identifier: component.uuid})) { // for pushing laster as changes come in this.contextStreamObservers.push({ - identifier: component.url, + identifier: component.uuid, component: component, originalMessage: message }) @@ -336,7 +340,7 @@ class ComponentManager { this.runWithPermissions(component, requiredPermissions, message.permissions, () => { var responseItems = message.data.items; - this.removePrivatePropertiesFromResponseItems(responseItems); + this.removePrivatePropertiesFromResponseItems(responseItems, {includeUrls: true}); /* We map the items here because modelManager is what updates the UI. If you were to instead get the items directly, @@ -348,7 +352,7 @@ class ComponentManager { var responseItem = _.find(responseItems, {uuid: item.uuid}); _.merge(item.content, responseItem.content); if(responseItem.clientData) { - item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); + item.setDomainDataItem(component.url || component.uuid, responseItem.clientData, ClientDataDomain); } item.setDirty(true); } @@ -374,7 +378,7 @@ class ComponentManager { this.removePrivatePropertiesFromResponseItems([responseItem]); var item = this.modelManager.createItem(responseItem); if(responseItem.clientData) { - item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); + item.setDomainDataItem(component.url || component.uuid, responseItem.clientData, ClientDataDomain); } this.modelManager.addItem(item); this.modelManager.resolveReferencesForItem(item); @@ -457,7 +461,7 @@ class ComponentManager { } return p.name == required.name && matchesContentTypes; })[0]; - console.log("required", required, "requested", requestedPermissions, "matching", matching); + if(!matching) { /* Required permissions can be 1 content type, and requestedPermisisons may send an array of content types. In the case of an array, we can just check to make sure that requiredPermissions content type is found in the array @@ -503,7 +507,6 @@ class ComponentManager { // since these calls are asyncronous, multiple dialogs may be requested at the same time. We only want to present one and trigger all callbacks based on one modal result var existingDialog = _.find(this.permissionDialogs, {component: component}); - component.trusted = component.url.startsWith("https://standardnotes.org") || component.url.startsWith("https://extensions.standardnotes.org"); var scope = this.$rootScope.$new(true); scope.component = component; scope.permissions = requestedPermissions; @@ -531,7 +534,7 @@ class ComponentManager { this.permissionDialogs.push(scope); if(!existingDialog) { - var el = this.$compile( "" )(scope); + var el = this.$compile( "" )(scope); angular.element(document.body).append(el); } else { console.log("Existing dialog, not presenting."); @@ -541,7 +544,7 @@ class ComponentManager { openModalComponent(component) { var scope = this.$rootScope.$new(true); scope.component = component; - var el = this.$compile( "" )(scope); + var el = this.$compile( "" )(scope); angular.element(document.body).append(el); } @@ -711,10 +714,10 @@ class ComponentManager { } urlForComponent(component) { - if(isDesktopApplication() && component.local && component.url.startsWith("sn://")) { - return component.url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); + if(isDesktopApplication() && component.local_url) { + return component.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); } else { - return component.url; + return component.url || component.hosted_url; } } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 3bdfdfae2..a89dd1514 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -28,8 +28,7 @@ class DesktopManager { /* Can handle components and themes */ installOfflineComponentFromData(componentData, callback) { this.componentInstallationHandler(componentData, (installedComponent) => { - componentData.content.url = installedComponent.content.url; - componentData.content.local = true; + componentData.content.local_url = installedComponent.content.local_url; callback(componentData); }); } diff --git a/app/assets/javascripts/app/services/directives/views/componentModal.js b/app/assets/javascripts/app/services/directives/views/componentModal.js index ee20de0d2..72502e854 100644 --- a/app/assets/javascripts/app/services/directives/views/componentModal.js +++ b/app/assets/javascripts/app/services/directives/views/componentModal.js @@ -6,6 +6,7 @@ class ComponentModal { this.scope = { show: "=", component: "=", + controller: "=", callback: "=" }; } @@ -19,12 +20,17 @@ class ComponentModal { let identifier = "modal-" + $scope.component.uuid; - $scope.dismiss = function() { - componentManager.deregisterHandler(identifier); + $scope.component.directiveController.dismiss = function() { + $scope.component.show = false; componentManager.deactivateComponent($scope.component); + componentManager.deregisterHandler(identifier); $scope.el.remove(); } + $scope.dismiss = function() { + $scope.component.directiveController.dismiss(); + } + $scope.url = function() { return componentManager.urlForComponent($scope.component); } diff --git a/app/assets/javascripts/app/services/directives/views/componentView.js b/app/assets/javascripts/app/services/directives/views/componentView.js new file mode 100644 index 000000000..d3f75fa5e --- /dev/null +++ b/app/assets/javascripts/app/services/directives/views/componentView.js @@ -0,0 +1,71 @@ +class ComponentView { + + constructor() { + this.restrict = "E"; + this.templateUrl = "frontend/directives/component-view.html"; + this.scope = { + component: "=" + }; + } + + link($scope, el, attrs, ctrl) { + $scope.el = el; + + $scope.$watch('component', function(component, prevComponent){ + console.log("Component View Setting Component", component); + ctrl.componentValueChanging(component, prevComponent); + }); + } + + controller($scope, $timeout, componentManager, desktopManager) { + 'ngInject'; + + this.componentValueChanging = (component, prevComponent) => { + if(prevComponent && component !== prevComponent) { + // Deactive old component + console.log("DEACTIVATING OLD COMPONENT", prevComponent); + componentManager.deactivateComponent(prevComponent); + } + + if(component) { + componentManager.activateComponent(component); + componentManager.setEventFlowForComponent(component, 1); + } + } + + let identifier = "component-view-" + Math.random(); + + $scope.url = function() { + if($scope.component.offlineOnly) { + return $scope.component.local_url; + } + + if(desktopManager.isDesktop && $scope.component.local_url) { + return $scope.component.local_url; + } + + return $scope.component.hosted_url || $scope.component.url; + } + + componentManager.registerHandler({identifier: identifier, areas: ["*"], activationHandler: (component) => { + if(component.active) { + $timeout(function(){ + var iframe = componentManager.iframeForComponent(component); + if(iframe) { + iframe.onload = function() { + componentManager.registerComponentWindow(component, iframe.contentWindow); + }.bind(this); + } + }.bind(this)); + } + }, + actionHandler: function(component, action, data) { + if(action == "set-size") { + componentManager.handleSetSizeEvent(component, data); + } + }.bind(this)}); + } + +} + +angular.module('app.frontend').directive('componentView', () => new ComponentView); diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 292e090a4..78e4005ab 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -9,7 +9,7 @@ class EditorMenu { }; } - controller($scope, componentManager, syncManager) { + controller($scope, componentManager, syncManager, $timeout) { 'ngInject'; $scope.formData = {}; @@ -23,12 +23,10 @@ class EditorMenu { $scope.selectEditor = function($event, editor) { if(editor) { editor.conflict_of = null; // clear conflict if applicable - if(editor.local && !isDesktopApplication()) { - alert("This editor is installed locally and is available only through Standard Notes for Desktop.") - return; - } } - $scope.callback()(editor); + $timeout(() => { + $scope.callback()(editor); + }) } $scope.toggleDefaultForEditor = function(editor) { diff --git a/app/assets/javascripts/app/services/directives/views/permissionsModal.js b/app/assets/javascripts/app/services/directives/views/permissionsModal.js index 51604dcb1..d08de104e 100644 --- a/app/assets/javascripts/app/services/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/services/directives/views/permissionsModal.js @@ -29,10 +29,11 @@ class PermissionsModal { } controller($scope, modelManager) { + console.log("permissions", $scope.permissions); + $scope.formattedPermissions = $scope.permissions.map(function(permission){ if(permission.name === "stream-items") { - var title = "Access to "; var types = permission.content_types.map(function(type){ var desc = modelManager.humanReadableDisplayForContentType(type); if(desc) { @@ -61,14 +62,14 @@ class PermissionsModal { } } - return title + typesString; + return typesString; } else if(permission.name === "stream-context-item") { var mapping = { "editor-stack" : "working note", "note-tags" : "working note", "editor-editor": "working note" } - return "Access to " + mapping[$scope.component.area]; + return mapping[$scope.component.area]; } }) } diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js index 048d5b2ae..103ab5096 100644 --- a/app/assets/javascripts/app/services/packageManager.js +++ b/app/assets/javascripts/app/services/packageManager.js @@ -1,12 +1,12 @@ class PackageManager { - constructor(httpManager, modelManager, syncManager) { + constructor(httpManager, modelManager, syncManager, componentManager) { this.httpManager = httpManager; this.modelManager = modelManager; this.syncManager = syncManager; + this.componentManager = componentManager; } - installPackage(url, callback) { this.httpManager.getAbsolute(url, {}, function(aPackage){ console.log("Got package data", aPackage); @@ -15,7 +15,11 @@ class PackageManager { return; } + // Remove private properties + this.componentManager.removePrivatePropertiesFromResponseItems([aPackage]); + var assembled = this.modelManager.createItem(aPackage); + assembled.package_info = aPackage; this.modelManager.addItem(assembled); assembled.setDirty(true); diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index c9110a733..0694c4e2e 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -33,6 +33,9 @@ #account-panel { width: 400px; +} + +.panel { cursor: default; } diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index d22afe91e..e4dfa9c71 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -1,4 +1,18 @@ -.permissions-modal, .component-modal { +#permissions-modal { + width: 350px; + .panel { + border-radius: 0; + background-color: white; + } + .content { + padding-top: 1.1rem; + } + .footer { + padding-bottom: 1.4rem; + } +} + +.modal { position: fixed; margin-left: auto; margin-right: auto; @@ -11,9 +25,9 @@ height: 100vh; background-color: rgba(gray, 0.3); color: black; - font-size: 16px; display: flex; align-items: center; + justify-content: center; .background { position: absolute; @@ -22,70 +36,8 @@ height: 100%; } - .content { - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - background-color: white; - width: 700px; - // height: 500px; - margin: auto; - padding: 10px 30px; - padding-bottom: 30px; - // position: absolute; - // top: 0; left: 0; bottom: 0; right: 0; + > .content { overflow-y: scroll; - - p { - margin-bottom: 8px; - margin-top: 0; - } - - h3 { - margin-bottom: 0; - } - - h4 { - margin-bottom: 6px; - } - } - - .learn-more { - margin-top: 20px; - line-height: 1.3; - } - - .status { - color: orange; - } - - .buttons { - margin-top: 35px; - } - - button.standard { - border-radius: 1px; - font-weight: bold; - padding: 6px 20px; - display: inline-block; - - &:hover { - text-decoration: underline; - } - - &.tinted { - background-color: $blue-color; - color: white; - } - - &.white { - color: black; - background-color: white; - border: 1px solid gray; - } - } -} - -.component-modal { - .content { width: auto; padding: 0; padding-bottom: 0; @@ -93,10 +45,20 @@ } .modal-iframe-container { + flex-grow: 1; + display: flex; - .modal-iframe { + iframe { + flex-grow: 1; + width: 100%; + } +} + +.component-view { + flex-grow: 1; + display: flex; + iframe { + flex: 1; width: 100%; - height: 100%; } - } diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 3c674d434..38db58dec 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -1,5 +1,5 @@ .sn-component - .panel.panel-right#account-panel + .panel#account-panel .header %h1.title Account %a.close-button Close @@ -169,8 +169,6 @@ %input.form-control.mt-5{:type => 'password', "ng-model" => "importData.password", "autofocus" => "true"} %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Decrypt & Import - %p.mt-5{"ng-if" => "user"} Notes are downloaded in the Standard File format, which allows you to re-import back into this app easily. To download as plain text files, choose "Decrypted". - .spinner.mt-10{"ng-if" => "importData.loading"} .footer %a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"} diff --git a/app/assets/templates/frontend/directives/component-modal.html.haml b/app/assets/templates/frontend/directives/component-modal.html.haml index 4ce5aaf62..2d02ce18a 100644 --- a/app/assets/templates/frontend/directives/component-modal.html.haml +++ b/app/assets/templates/frontend/directives/component-modal.html.haml @@ -1,5 +1,7 @@ -.background{"ng-click" => "dismiss()"} - -.content - .modal-iframe-container{"ng-attr-id" => "component-{{component.uuid}}"} - %iframe.modal-iframe{"ng-src" => "{{url() | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} +.sn-component + .panel{"ng-attr-id" => "component-{{component.uuid}}"} + .header + %h1.title {{component.name}} + %a.close-button.info{"ng-click" => "dismiss()"} Close + .modal-iframe-container{"ng-attr-id" => "component-{{component.uuid}}"} + %iframe{"ng-src" => "{{url() | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} diff --git a/app/assets/templates/frontend/directives/component-view.html.haml b/app/assets/templates/frontend/directives/component-view.html.haml new file mode 100644 index 000000000..60ffbab5d --- /dev/null +++ b/app/assets/templates/frontend/directives/component-view.html.haml @@ -0,0 +1,6 @@ +%iframe{"ng-if" => "component", +"ng-attr-id" => "component-{{component.uuid}}", +"ng-src" => "{{url() | trusted}}", "frameBorder" => "0", +"sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", +"data-component-id" => "{{component.uuid}}"} + Loading diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 9209a4b0b..6324f53f4 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -12,10 +12,8 @@ .row .column %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy - .sublabel{"ng-if" => "editor.local"} - Locally Installed - - .sublabel.faded{"ng-if" => "editor.local && !isDesktop"} Unavailable in Web Browser + .sublabel{"ng-if" => "editor.local_url"} + Available Offline %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download More Editors'", "ng-click" => "moreEditors()"} diff --git a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml index fe8965304..276a370c5 100644 --- a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml +++ b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default.account-panel.panel-right#global-ext-menu +.panel#global-ext-menu .panel-body .container .float-group.h20 diff --git a/app/assets/templates/frontend/directives/permissions-modal.html.haml b/app/assets/templates/frontend/directives/permissions-modal.html.haml index e17fb7f75..cfd7d8661 100644 --- a/app/assets/templates/frontend/directives/permissions-modal.html.haml +++ b/app/assets/templates/frontend/directives/permissions-modal.html.haml @@ -1,25 +1,24 @@ .background{"ng-click" => "deny()"} -.content - %h3 The following extension has requested these permissions: +.content#permissions-modal + .sn-component + .panel + .header + %h1.title Activate Extension + %a.close-button.info Cancel + .content + .panel-section + .panel-row + %h3 + %strong {{component.name}} + would like to interact with your + %span{"ng-repeat" => "permission in formattedPermissions"} + {{permission}}. + -# %p.wrap URL: {{component.runningUrl}} - %h4 Extension - %p Name: {{component.name}} - %p.wrap URL: {{component.url}} - - %h4 Permissions - .permission{"ng-repeat" => "permission in formattedPermissions"} - %p {{permission}} - - %h4 Status - %p.status{"ng-class" => "{'trusted tinted' : component.trusted}"} {{component.trusted ? 'Trusted' : 'Untrusted'}} - - .learn-more - %h4 Details - %p - Extensions use an offline messaging system to communicate. With Trusted extensions, data is never sent remotely without your consent. Learn more about extension permissions at - %a{"href" => "https://standardnotes.org/permissions", "target" => "_blank"} https://standardnotes.org/permissions. - - .buttons - %button.standard.white{"ng-click" => "deny()"} Deny - %button.standard.tinted{"ng-click" => "accept()"} Accept + .panel-row + %p + Extensions use an offline messaging system to communicate. Learn more at + %a{"href" => "https://standardnotes.org/permissions", "target" => "_blank"} https://standardnotes.org/permissions. + .footer + .button.info.big.block.bold{"ng-click" => "accept()"} Continue diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 574fe2f88..9541746bf 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -34,7 +34,7 @@ .item{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} .label Editor - %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"} + %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectEditor", "selected-editor" => "ctrl.selectedEditor"} .item{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} .label Actions @@ -42,14 +42,13 @@ .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"} - %iframe#editor-iframe{"ng-if" => "ctrl.editorComponent && ctrl.editorComponent.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.editorComponent) | trusted}}", "data-component-id" => "{{ctrl.editorComponent.uuid}}", "frameBorder" => "0", "style" => "width: 100%;"} - Loading - %textarea.editable#note-text-editor{"ng-if" => "!ctrl.editorComponent", "ng-model" => "ctrl.note.text", + -# ng-show is required here (as opposed to ng-if) in order for the component-view to receive events such as nulling ctrl.selectorEditor + %component-view{"ng-show" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor", "class" => "component-view"} + %textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} {{ctrl.onSystemEditorLoad()}} %panel-resizer{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "hoverable" => "true"} - %section.section{"ng-if" => "ctrl.note.errorDecrypting"} %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index 54634cdec..dc8b87d7f 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -19,11 +19,10 @@ .item.border - .item{"ng-repeat" => "room in ctrl.rooms", "ng-click" => "ctrl.selectRoom(room)", "click-outside" => "ctrl.hideRoom(room)", "is-open" => "room.show && room.active"} - .label {{room.name}} - .sn-component - .panel-right.panel{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} - %iframe.room-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(room) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{room.uuid}}"} + .item{"ng-repeat" => "room in ctrl.rooms"} + .column{"ng-click" => "ctrl.selectRoom(room)"} + .label {{room.name}} + %component-modal{"ng-if" => "room.show", "component" => "room"} .right diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css index 07729e74c..906073401 100644 --- a/vendor/assets/stylesheets/stylekit.css +++ b/vendor/assets/stylesheets/stylekit.css @@ -7,8 +7,17 @@ box-shadow: 0px 2px 13px #C8C8C8; border-radius: 0.7rem; overflow: scroll; + display: flex; + flex-direction: column; +} +.sn-component .panel.static { + box-shadow: none; + border: none; + border-radius: 0; } .sn-component .panel .header { + flex-shrink: 0; + /* Don't allow to condense in height */ display: flex; justify-content: space-between; padding: 1.1rem 2rem; @@ -32,8 +41,9 @@ display: block; } .sn-component .panel .content { - padding: 1.9rem 2rem; + padding: 1.6rem 2rem; padding-bottom: 0; + flex-grow: 1; } .sn-component .panel .content p { color: #454545; @@ -45,6 +55,29 @@ .sn-component .panel .content .panel-section { padding-bottom: 1.6rem; } +.sn-component .panel .content .panel-section .panel-row { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 0.4rem; +} +.sn-component .panel .content .panel-section .panel-row.centered { + justify-content: center; +} +.sn-component .panel .content .panel-section .panel-row .panel-column { + width: 100%; +} +.sn-component .panel .content .panel-section .panel-row:not(:last-child) { + padding-bottom: 0.4rem; +} +.sn-component .panel .content .panel-section .panel-row:not(:last-child).condensed { + padding-top: 0.2rem; + padding-bottom: 0.2rem; +} +.sn-component .panel .content .panel-section .panel-row p { + margin: 0; + padding: 0; +} .sn-component .panel .content .panel-section.hero { text-align: center; } @@ -65,29 +98,12 @@ margin-bottom: 15px; } .sn-component .panel .content .panel-section .subtitle { - margin-top: -4px; + margin-top: -0.5rem; } .sn-component .panel .content .panel-section .subtitle.subtle { font-weight: normal; opacity: 0.6; } -.sn-component .panel .content .panel-section .panel-row { - display: flex; - justify-content: space-between; - align-items: center; - padding-top: 0.4rem; -} -.sn-component .panel .content .panel-section .panel-row:not(:last-child) { - padding-bottom: 0.4rem; -} -.sn-component .panel .content .panel-section .panel-row:not(:last-child).condensed { - padding-top: 0.2rem; - padding-bottom: 0.2rem; -} -.sn-component .panel .content .panel-section .panel-row p { - margin: 0; - padding: 0; -} .sn-component .panel .content .panel-form { width: 100%; } @@ -169,17 +185,21 @@ .sn-component h1 { font-weight: 500; font-size: 1.3rem; + line-height: 1.9rem; } .sn-component h2 { font-size: 1.2rem; + line-height: 1.8rem; } .sn-component h3 { font-weight: normal; font-size: 1.2rem; + line-height: 1.7rem; } .sn-component h4 { font-weight: bold; font-size: 0.95rem; + line-height: 1.4rem; } .sn-component h5 { font-weight: bold; @@ -203,6 +223,14 @@ .sn-component *.success { color: #2B9612; } +.sn-component *.clear { + background-color: transparent; + border: none; +} +.sn-component .center-text { + text-align: center; + justify-content: center; +} .sn-component p { margin: 0.5rem 0; } @@ -215,10 +243,15 @@ font-size: 1.1rem; width: 100%; outline: 0; + resize: none; } .sn-component input.info { border-color: #086DD6; background-color: transparent; + color: #086DD6; +} +.sn-component input.info::-webkit-input-placeholder { + color: rgba(8, 109, 214, 0.5); } .sn-component label { margin: 0.7rem 0; @@ -277,6 +310,9 @@ .sn-component .box-group .box:not(:last-child) { margin-right: 5px; } +.sn-component a.button, .sn-component a.box, .sn-component a.circle { + text-decoration: none; +} .sn-component .button, .sn-component .box, .sn-component .circle { display: table; border-radius: 3px; @@ -291,6 +327,10 @@ display: block; text-align: center; } +.sn-component .button.big, .sn-component .big.box, .sn-component .big.circle { + font-size: 1.1rem; + padding: 0.7rem 2.5rem; +} .sn-component .box { padding: 2.5rem 1.5rem; } @@ -461,9 +501,15 @@ border-radius: 0.2rem; cursor: default; } +.sn-component .notification.one-line { + padding: 0rem 0.4rem; +} .sn-component .notification.stretch { width: 100%; } +.sn-component .notification.dashed { + border-style: dashed; +} .sn-component .notification .text { line-height: 1.5rem; font-size: 1.05rem; @@ -573,5 +619,48 @@ font-weight: normal; opacity: 0.6; } +.sn-component .panel-table { + display: flex; + flex-wrap: wrap; + padding-left: 1px; + padding-top: 1px; +} +.sn-component .panel-table .table-item { + flex: 45%; + flex-flow: wrap; + border: 1px solid #DDDDDD; + padding: 1rem; + margin-left: -1px; + margin-top: -1px; + display: flex; + flex-direction: column; + justify-content: space-between; +} +.sn-component .panel-table .table-item img { + max-width: 100%; + margin-bottom: 1rem; +} +.sn-component .panel-table .table-item .item-content { + display: flex; + flex-direction: row; +} +.sn-component .panel-table .table-item .item-column { + align-items: center; +} +.sn-component .panel-table .table-item .item-column:not(:first-child) { + padding-left: 0.75rem; +} +.sn-component .panel-table .table-item .item-column.quarter { + flex-basis: 25%; +} +.sn-component .panel-table .table-item .item-column.three-quarters { + flex-basis: 75%; +} +.sn-component .panel-table .table-item .item-footer { + margin-top: 1.25rem; +} +.sn-component .panel-table .table-item.no-border { + border: none; +} /*# sourceMappingURL=stylekit.css.map */ From ae29e502cb610b930681c46576c631010bb17bb8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 10 Jan 2018 12:25:34 -0600 Subject: [PATCH 23/99] Updates --- .../app/frontend/controllers/editor.js | 40 +-- .../app/frontend/controllers/footer.js | 39 +-- .../app/frontend/controllers/tags.js | 11 - .../app/frontend/models/app/component.js | 8 + .../app/frontend/models/app/theme.js | 22 +- .../javascripts/app/services/authManager.js | 2 - .../app/services/componentManager.js | 263 ++++++++++-------- .../directives/views/componentModal.js | 53 ++-- .../directives/views/componentView.js | 67 ++--- .../services/directives/views/editorMenu.js | 1 - .../directives/views/permissionsModal.js | 2 - .../javascripts/app/services/themeManager.js | 65 +++-- app/assets/stylesheets/app/_editor.scss | 28 +- app/assets/stylesheets/app/_modals.scss | 14 +- app/assets/stylesheets/app/_stylekit-sub.scss | 7 + app/assets/stylesheets/frontend.css.scss | 1 + .../directives/component-modal.html.haml | 16 +- .../directives/component-view.html.haml | 2 +- .../global-extensions-menu.html.haml | 4 +- .../directives/permissions-modal.html.haml | 3 +- .../templates/frontend/editor.html.haml | 11 +- .../templates/frontend/footer.html.haml | 2 +- app/assets/templates/frontend/tags.html.haml | 3 +- vendor/assets/stylesheets/stylekit.css | 106 ++++--- 24 files changed, 380 insertions(+), 390 deletions(-) create mode 100644 app/assets/stylesheets/app/_stylekit-sub.scss diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index 326674086..79af1fa4f 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -58,22 +58,20 @@ angular.module('app.frontend') } let associatedEditor = this.editorForNote(note); - if(associatedEditor) { + if(associatedEditor && associatedEditor != this.selectedEditor) { // setting note to not ready will remove the editor from view in a flash, // so we only want to do this if switching between external editors this.noteReady = false; - } else { - onReady(); - } - - // Activate new editor if it's different from the one currently activated - if(associatedEditor) { - // switch after timeout, so that note data isnt posted to current editor + // switch after timeout, so that note data isnt posted to current editor $timeout(() => { this.selectedEditor = associatedEditor; onReady(); }) + } else if(associatedEditor) { + // Same editor as currently active + onReady(); } else { + // No editor this.selectedEditor = null; onReady(); } @@ -106,7 +104,6 @@ angular.module('app.frontend') } this.selectEditor = function(editor) { - console.log("selectEditor", editor); this.showEditorMenu = false; if(editor) { @@ -419,18 +416,6 @@ angular.module('app.frontend') this.selectedEditor = null; } } - - if(component.active) { - $timeout(function(){ - var iframe = componentManager.iframeForComponent(component); - if(iframe) { - iframe.onload = function() { - componentManager.registerComponentWindow(component, iframe.contentWindow); - }.bind(this); - } - }.bind(this)); - } - }.bind(this), contextRequestHandler: function(component){ return this.note; }.bind(this), actionHandler: function(component, action, data){ @@ -441,21 +426,10 @@ angular.module('app.frontend') element.setAttribute("style", `width:${widthString}; height:${heightString}; `); } - if(data.type === "content") { - var iframe = componentManager.iframeForComponent(component); - var width = data.width; - var height = data.height; - iframe.width = width; - iframe.height = height; - - setSize(iframe, data); - } else { + if(data.type == "container") { if(component.area == "note-tags") { var container = document.getElementById("note-tags-component-container"); setSize(container, data); - } else { - var container = document.getElementById("component-" + component.uuid); - setSize(container, data); } } } diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index 07001176c..315272cd3 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -121,46 +121,49 @@ angular.module('app.frontend') componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: (component) => { if(component.active) { + // Show room, if it was not activated manually (in the event of event from componentManager) + if(!component.showRoom) { + this.selectRoom(component); + } $timeout(() => { - var iframe = componentManager.iframeForComponent(component); - if(iframe) { - var lastSize = component.getRoomLastSize(); - if(lastSize) { - componentManager.handleSetSizeEvent(component, lastSize); - } - iframe.onload = function() { - componentManager.registerComponentWindow(component, iframe.contentWindow); - }.bind(this); + var lastSize = component.getRoomLastSize(); + if(lastSize) { + componentManager.handleSetSizeEvent(component, lastSize); } }); } }, actionHandler: (component, action, data) => { if(action == "set-size") { - componentManager.handleSetSizeEvent(component, data); component.setRoomLastSize(data); } }}); this.selectRoom = function(room) { - room.show = !room.show; // Allows us to send messages to component modal directive if(!room.directiveController) { - room.directiveController = {}; + room.directiveController = {onDismiss: () => { + room.showRoom = false; + }}; } - if(!room.show) { - room.directiveController.dismiss(); - } + // Make sure to call dismiss() before setting new showRoom value + // This way the directive stays alive long enough to deactivate the associated component + // (The directive's life is at the mercy of "ng-if" => "room.showRoom") + if(room.showRoom) { + room.directiveController.dismiss(() => { - console.log("Show", room.show); + }); + } else { + room.showRoom = true; + } } // Handle singleton ProLink instance singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: "org.standardnotes.prolink"}}, (resolvedSingleton) => { - console.log("Roombar received resolved ProLink", resolvedSingleton); + }, (valueCallback) => { - console.log("Creating prolink"); + // Safe to create. Create and return object. let url = window._prolink_package_url; packageManager.installPackage(url, (component) => { diff --git a/app/assets/javascripts/app/frontend/controllers/tags.js b/app/assets/javascripts/app/frontend/controllers/tags.js index 549649f80..e053b5b4e 100644 --- a/app/assets/javascripts/app/frontend/controllers/tags.js +++ b/app/assets/javascripts/app/frontend/controllers/tags.js @@ -63,19 +63,9 @@ angular.module('app.frontend') componentManager.registerHandler({identifier: "tags", areas: ["tags-list"], activationHandler: function(component){ this.component = component; - if(component.active) { - $timeout(function(){ - var iframe = document.getElementById("tags-list-iframe"); - iframe.onload = function() { - componentManager.registerComponentWindow(this.component, iframe.contentWindow); - }.bind(this); - }.bind(this)); - } - }.bind(this), contextRequestHandler: function(component){ return null; }.bind(this), actionHandler: function(component, action, data){ - if(action === "select-item") { var tag = modelManager.findItem(data.item.uuid); if(tag) { @@ -86,7 +76,6 @@ angular.module('app.frontend') else if(action === "clear-selection") { this.selectTag(this.allTag); } - }.bind(this)}); this.setAllTag = function(allTag) { diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index 5287f412e..42781cfa7 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -73,6 +73,14 @@ class Component extends Item { return "SN|Component"; } + computedUrl() { + if(this.offlineOnly || (isDesktopApplication() && this.local_url)) { + return this.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); + } else { + return this.url || this.hosted_url; + } + } + isEditor() { return this.area == "editor-editor"; } diff --git a/app/assets/javascripts/app/frontend/models/app/theme.js b/app/assets/javascripts/app/frontend/models/app/theme.js index 38ced50ce..7d03cb2c3 100644 --- a/app/assets/javascripts/app/frontend/models/app/theme.js +++ b/app/assets/javascripts/app/frontend/models/app/theme.js @@ -1,27 +1,9 @@ -class Theme extends Item { +class Theme extends Component { constructor(json_obj) { super(json_obj); - } - mapContentToLocalProperties(content) { - super.mapContentToLocalProperties(content) - this.url = content.url; - this.name = content.name; - } - - structureParams() { - var params = { - url: this.url, - name: this.name - }; - - _.merge(params, super.structureParams()); - return params; - } - - toJSON() { - return {uuid: this.uuid} + this.area = "themes"; } get content_type() { diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 6c2be7612..46cff40fc 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -296,7 +296,6 @@ angular.module('app.frontend') let prefsContentType = "SN|UserPreferences"; singletonManager.registerSingleton({content_type: prefsContentType}, (resolvedSingleton) => { - console.log("AuthManager received resolved UserPreferences", resolvedSingleton); this.userPreferences = resolvedSingleton; this.userPreferencesDidChange(); }, (valueCallback) => { @@ -304,7 +303,6 @@ angular.module('app.frontend') var prefs = new Item({content_type: prefsContentType}); modelManager.addItem(prefs); prefs.setDirty(true); - console.log("Created new prefs", prefs); $rootScope.sync(); valueCallback(prefs); }); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 2b73b1580..916e44df6 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -3,12 +3,11 @@ let ClientDataDomain = "org.standardnotes.sn.components"; class ComponentManager { - constructor($rootScope, modelManager, syncManager, desktopManager, themeManager, $timeout, $compile) { + constructor($rootScope, modelManager, syncManager, desktopManager, $timeout, $compile) { this.$compile = $compile; this.$rootScope = $rootScope; this.modelManager = modelManager; this.syncManager = syncManager; - this.themeManager = themeManager; this.desktopManager = desktopManager; this.timeout = $timeout; this.streamObservers = []; @@ -21,9 +20,9 @@ class ComponentManager { this.handlers = []; - $rootScope.$on("theme-changed", function(){ - this.postThemeToComponents(); - }.bind(this)) + // $rootScope.$on("theme-changed", function(){ + // this.postThemeToAllComponents(); + // }.bind(this)) window.addEventListener("message", function(event){ if(this.loggingEnabled) { @@ -32,7 +31,7 @@ class ComponentManager { this.handleMessage(this.componentForSessionKey(event.data.sessionKey), event.data); }.bind(this), false); - this.modelManager.addItemSyncObserver("component-manager", "*", function(allItems, validItems, deletedItems, source) { + this.modelManager.addItemSyncObserver("component-manager", "*", (allItems, validItems, deletedItems, source) => { /* 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 @@ -42,7 +41,9 @@ class ComponentManager { return; } - var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" }); + var syncedComponents = allItems.filter(function(item) { + return item.content_type === "SN|Component" || item.content_type == "SN|Theme" + }); /* 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. @@ -77,9 +78,10 @@ class ComponentManager { } ]; - this.runWithPermissions(observer.component, requiredPermissions, observer.originalMessage.permissions, function(){ + this.runWithPermissions(observer.component, requiredPermissions, () => { + console.log("Stream observer, sending items", relevantItems); this.sendItemsInReply(observer.component, relevantItems, observer.originalMessage); - }.bind(this)) + }) } var requiredContextPermissions = [ @@ -89,36 +91,43 @@ class ComponentManager { ]; for(let observer of this.contextStreamObservers) { - this.runWithPermissions(observer.component, requiredContextPermissions, observer.originalMessage.permissions, function(){ - for(let handler of this.handlers) { - if(!handler.areas.includes(observer.component.area)) { - continue; - } + for(let handler of this.handlers) { + if(!handler.areas.includes(observer.component.area) && !handler.areas.includes("*")) { + continue; + } + if(handler.contextRequestHandler) { var itemInContext = handler.contextRequestHandler(observer.component); if(itemInContext) { var matchingItem = _.find(allItems, {uuid: itemInContext.uuid}); if(matchingItem) { - this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage, source); + this.runWithPermissions(observer.component, requiredContextPermissions, () => { + this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage, source); + }) } } } - }.bind(this)) + } } - }.bind(this)) + }); } - postThemeToComponents() { + postThemeToAllComponents() { for(var component of this.components) { - if(!component.active || !component.window) { + if(component.area == "themes" || !component.active || !component.window) { continue; } this.postThemeToComponent(component); } } + getActiveTheme() { + return this.componentsForArea("themes").find((theme) => {return theme.active}); + } + postThemeToComponent(component) { + var activeTheme = this.getActiveTheme(); var data = { - themes: [this.themeManager.currentTheme ? this.themeManager.currentTheme.url : null] + themes: [activeTheme ? activeTheme.computedUrl() : null] } this.sendMessageToComponent(component, {action: "themes", data: data}) @@ -126,7 +135,7 @@ class ComponentManager { contextItemDidChangeInArea(area) { for(let handler of this.handlers) { - if(handler.areas.includes(area) === false) { + if(handler.areas.includes(area) === false && !handler.areas.includes("*")) { continue; } var observers = this.contextStreamObservers.filter(function(observer){ @@ -134,8 +143,10 @@ class ComponentManager { }) for(let observer of observers) { - var itemInContext = handler.contextRequestHandler(observer.component); - this.sendContextItemInReply(observer.component, itemInContext, observer.originalMessage); + if(handler.contextRequestHandler) { + var itemInContext = handler.contextRequestHandler(observer.component); + this.sendContextItemInReply(observer.component, itemInContext, observer.originalMessage); + } } } } @@ -176,7 +187,7 @@ class ComponentManager { } get components() { - return this.modelManager.itemsForContentType("SN|Component"); + return this.modelManager.allItemsMatchingTypes(["SN|Component", "SN|Theme"]); } componentsForArea(area) { @@ -220,7 +231,7 @@ class ComponentManager { save-context-client-data get-context-client-data install-local-component - open-component + toggle-activate-component */ if(message.action === "stream-items") { @@ -237,14 +248,14 @@ class ComponentManager { this.handleSaveItemsMessage(component, message); } else if(message.action === "install-local-component") { this.handleInstallLocalComponentMessage(component, message); - } else if(message.action === "open-component") { - let openComponent = this.modelManager.findItem(message.data.uuid); - this.openModalComponent(openComponent); + } else if(message.action === "toggle-activate-component") { + let componentToToggle = this.modelManager.findItem(message.data.uuid); + this.handleToggleComponentMessage(component, componentToToggle, message); } // Notify observers for(let handler of this.handlers) { - if(handler.areas.includes(component.area)) { + if(handler.areas.includes(component.area) || handler.areas.includes("*")) { this.timeout(function(){ handler.actionHandler(component, message.action, message.data); }) @@ -254,7 +265,7 @@ class ComponentManager { removePrivatePropertiesFromResponseItems(responseItems, includeUrls) { // Don't allow component to overwrite these properties. - var privateProperties = ["appData", "autoupdate", "permissions", "active"]; + var privateProperties = ["appData", "autoupdate", "permissions", "active", "encrypted"]; if(includeUrls) { privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"]); } @@ -278,7 +289,7 @@ class ComponentManager { } ]; - this.runWithPermissions(component, requiredPermissions, message.permissions, function(){ + this.runWithPermissions(component, requiredPermissions, () => { if(!_.find(this.streamObservers, {identifier: component.uuid})) { // for pushing laster as changes come in this.streamObservers.push({ @@ -289,14 +300,13 @@ class ComponentManager { }) } - // push immediately now var items = []; for(var contentType of message.data.content_types) { items = items.concat(this.modelManager.itemsForContentType(contentType)); } this.sendItemsInReply(component, items, message); - }.bind(this)); + }); } handleStreamContextItemMessage(component, message) { @@ -307,7 +317,7 @@ class ComponentManager { } ]; - this.runWithPermissions(component, requiredPermissions, message.permissions, function(){ + this.runWithPermissions(component, requiredPermissions, function(){ if(!_.find(this.contextStreamObservers, {identifier: component.uuid})) { // for pushing laster as changes come in this.contextStreamObservers.push({ @@ -318,27 +328,49 @@ class ComponentManager { } // push immediately now - for(let handler of this.handlers) { - if(handler.areas.includes(component.area) === false) { - continue; - } + for(let handler of this.handlersForArea(component.area)) { var itemInContext = handler.contextRequestHandler(component); this.sendContextItemInReply(component, itemInContext, message); } }.bind(this)) } - handleSaveItemsMessage(component, message) { - var requiredContentTypes = _.uniq(message.data.items.map((i) => {return i.content_type})).sort(); - var requiredPermissions = [ - { - name: "stream-items", - content_types: requiredContentTypes + isItemWithinComponentContextJurisdiction(item, component) { + for(let handler of this.handlersForArea(component.area)) { + var itemInContext = handler.contextRequestHandler(component); + if(itemInContext.uuid == item.uuid) { + return true; } - ]; + } + return false; + } - this.runWithPermissions(component, requiredPermissions, message.permissions, () => { - var responseItems = message.data.items; + handlersForArea(area) { + return this.handlers.filter((candidate) => {return candidate.areas.includes(area)}); + } + + handleSaveItemsMessage(component, message) { + var responseItems = message.data.items; + var requiredPermissions; + + // Check if you're just trying to save the context item, which requires only stream-context-item permissions + if(responseItems.length == 1 && this.isItemWithinComponentContextJurisdiction(responseItems[0], component)) { + requiredPermissions = [ + { + name: "stream-context-item" + } + ]; + } else { + var requiredContentTypes = _.uniq(responseItems.map((i) => {return i.content_type})).sort(); + requiredPermissions = [ + { + name: "stream-items", + content_types: requiredContentTypes + } + ]; + } + + this.runWithPermissions(component, requiredPermissions, () => { this.removePrivatePropertiesFromResponseItems(responseItems, {includeUrls: true}); @@ -373,7 +405,7 @@ class ComponentManager { } ]; - this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + this.runWithPermissions(component, requiredPermissions, () => { var responseItem = message.data.item; this.removePrivatePropertiesFromResponseItems([responseItem]); var item = this.modelManager.createItem(responseItem); @@ -397,7 +429,7 @@ class ComponentManager { } ]; - this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + this.runWithPermissions(component, requiredPermissions, () => { var items = message.data.items; var noun = items.length == 1 ? "item" : "items"; if(confirm(`Are you sure you want to delete ${items.length} ${noun}?`)) { @@ -419,10 +451,8 @@ class ComponentManager { } ]; - this.runWithPermissions(component, requiredPermissions, message.permissions, () => { - console.log("Received install-local-component event"); + this.runWithPermissions(component, requiredPermissions, () => { this.desktopManager.installOfflineComponentFromData(message.data, (response) => { - console.log("componentManager: installed component:", response); var component = this.modelManager.mapResponseItemsToLocalModels([response], ModelManager.MappingSourceComponentRetrieved)[0]; // Save updated URL component.setDirty(true); @@ -432,68 +462,73 @@ class ComponentManager { } handleSetComponentDataMessage(component, message) { - var requiredPermissions = [ - { - name: "stream-items", - content_types: [component.content_type] - } - ]; - - this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + // A component setting its own data does not require special permissions + this.runWithPermissions(component, [], () => { component.componentData = message.data.componentData; component.setDirty(true); this.syncManager.sync(); }); } - runWithPermissions(component, requiredPermissions, requestedPermissions, runFunction) { - var acquiredPermissions = component.permissions; - - var requestedMatchesRequired = true; - - for(var required of requiredPermissions) { - var matching = _.find(requestedPermissions, required); - var matching = requestedPermissions.filter((p) => { - var matchesContentTypes = true; - if(p.content_types) { - matchesContentTypes = JSON.stringify(p.content_types.sort()) == JSON.stringify(required.content_types.sort()); - } - return p.name == required.name && matchesContentTypes; - })[0]; - - if(!matching) { - /* Required permissions can be 1 content type, and requestedPermisisons may send an array of content types. - In the case of an array, we can just check to make sure that requiredPermissions content type is found in the array - */ - matching = requestedPermissions.filter((requested) => { - return Array.isArray(requested.content_types) && requested.content_types.containsSubset(required.content_types); - }); - - console.log("Matching 2nd chance", matching); - - if(!matching) { - requestedMatchesRequired = false; - break; + handleToggleComponentMessage(sourceComponent, targetComponent, message) { + if(targetComponent.area == "modal") { + this.openModalComponent(targetComponent); + } else { + 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); + } } + this.activateComponent(targetComponent); } } + } - if(!requestedMatchesRequired) { - // Error with Component permissions request - console.error("You are requesting permissions", requestedPermissions, "when you need to be requesting", requiredPermissions, ". Component:", component); - return false; - } - + runWithPermissions(component, requiredPermissions, runFunction) { if(!component.permissions) { component.permissions = []; } - var acquiredMatchesRequested = angular.toJson(component.permissions.sort()) === angular.toJson(requestedPermissions.sort()); + var acquiredPermissions = component.permissions; + var acquiredMatchesRequired = true; - if(!acquiredMatchesRequested) { - this.promptForPermissions(component, requestedPermissions, function(approved){ + for(var required of requiredPermissions) { + var matching = acquiredPermissions.find((candidate) => { + var matchesContentTypes = true; + if(candidate.content_types && required.content_types) { + matchesContentTypes = JSON.stringify(candidate.content_types.sort()) == JSON.stringify(required.content_types.sort()); + } + return candidate.name == required.name && matchesContentTypes; + }); + + if(!matching) { + /* Required permissions can be 1 content type, and requestedPermisisons may send an array of content types. + In the case of an array, we can just check to make sure that requiredPermissions content type is found in the array + */ + matching = acquiredPermissions.find((candidate) => { + return Array.isArray(candidate.content_types) + && Array.isArray(required.content_types) + && candidate.content_types.containsSubset(required.content_types); + }); + + if(!matching) { + acquiredMatchesRequired = false; + break; + } + } + } + + // var acquiredMatchesRequested = angular.toJson(component.permissions.sort()) === angular.toJson(requestedPermissions.sort()); + + if(!acquiredMatchesRequired) { + this.promptForPermissions(component, requiredPermissions, function(approved){ if(approved) { runFunction(); } @@ -503,18 +538,22 @@ class ComponentManager { } } - promptForPermissions(component, requestedPermissions, callback) { + promptForPermissions(component, permissions, callback) { // since these calls are asyncronous, multiple dialogs may be requested at the same time. We only want to present one and trigger all callbacks based on one modal result var existingDialog = _.find(this.permissionDialogs, {component: component}); var scope = this.$rootScope.$new(true); scope.component = component; - scope.permissions = requestedPermissions; + scope.permissions = permissions; scope.actionBlock = callback; scope.callback = function(approved) { if(approved) { - component.permissions = requestedPermissions; + for(var permission of permissions) { + if(!component.permissions.includes(permission)) { + component.permissions.push(permission); + } + } component.setDirty(true); this.syncManager.sync(); } @@ -544,6 +583,9 @@ class ComponentManager { openModalComponent(component) { var scope = this.$rootScope.$new(true); scope.component = component; + scope.onDismiss = () => { + + } var el = this.$compile( "" )(scope); angular.element(document.body).append(el); } @@ -591,7 +633,7 @@ class ComponentManager { component.active = true; for(var handler of this.handlers) { - if(handler.areas.includes(component.area)) { + if(handler.areas.includes(component.area) || handler.areas.includes("*")) { handler.activationHandler(component); } } @@ -604,6 +646,10 @@ class ComponentManager { if(!this.activeComponents.includes(component)) { this.activeComponents.push(component); } + + if(component.area == "themes") { + this.postThemeToAllComponents(); + } } registerHandler(handler) { @@ -640,12 +686,13 @@ class ComponentManager { } deactivateComponent(component) { + console.log("Deactivating component", component); var didChange = component.active != false; component.active = false; component.sessionKey = null; for(var handler of this.handlers) { - if(handler.areas.includes(component.area)) { + if(handler.areas.includes(component.area) || handler.areas.includes("*")) { handler.activationHandler(component); } } @@ -664,6 +711,10 @@ class ComponentManager { this.contextStreamObservers = this.contextStreamObservers.filter(function(o){ return o.component !== component; }) + + if(component.area == "themes") { + this.postThemeToAllComponents(); + } } deleteComponent(component) { @@ -713,14 +764,6 @@ class ComponentManager { component.ignoreEvents = !on; } - urlForComponent(component) { - if(isDesktopApplication() && component.local_url) { - return component.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); - } else { - return component.url || component.hosted_url; - } - } - iframeForComponent(component) { for(var frame of document.getElementsByTagName("iframe")) { var componentId = frame.dataset.componentId; diff --git a/app/assets/javascripts/app/services/directives/views/componentModal.js b/app/assets/javascripts/app/services/directives/views/componentModal.js index 72502e854..a02cb1f00 100644 --- a/app/assets/javascripts/app/services/directives/views/componentModal.js +++ b/app/assets/javascripts/app/services/directives/views/componentModal.js @@ -6,8 +6,8 @@ class ComponentModal { this.scope = { show: "=", component: "=", - controller: "=", - callback: "=" + callback: "=", + onDismiss: "&" }; } @@ -18,42 +18,23 @@ class ComponentModal { controller($scope, $timeout, componentManager) { 'ngInject'; - let identifier = "modal-" + $scope.component.uuid; - - $scope.component.directiveController.dismiss = function() { - $scope.component.show = false; - componentManager.deactivateComponent($scope.component); - componentManager.deregisterHandler(identifier); - $scope.el.remove(); - } - - $scope.dismiss = function() { - $scope.component.directiveController.dismiss(); - } - - $scope.url = function() { - return componentManager.urlForComponent($scope.component); - } - - componentManager.registerHandler({identifier: identifier, areas: ["modal"], activationHandler: (component) => { - if(component.active) { - $timeout(function(){ - var iframe = componentManager.iframeForComponent(component); - if(iframe) { - iframe.onload = function() { - componentManager.registerComponentWindow(component, iframe.contentWindow); - }.bind(this); - } - }.bind(this)); + if($scope.component.directiveController) { + $scope.component.directiveController.dismiss = function(callback) { + $scope.dismiss(callback); } - }, - actionHandler: function(component, action, data) { - if(action == "set-size") { - componentManager.handleSetSizeEvent(component, data); - } - }.bind(this)}); + } + + $scope.dismiss = function(callback) { + var onDismiss = $scope.component.directiveController && $scope.component.directiveController.onDismiss(); + // Setting will null out compinent-view's component, which will handle deactivation + $scope.component = null; + $timeout(() => { + $scope.el.remove(); + onDismiss && onDismiss(); + callback && callback(); + }) + } - componentManager.activateComponent($scope.component); } } diff --git a/app/assets/javascripts/app/services/directives/views/componentView.js b/app/assets/javascripts/app/services/directives/views/componentView.js index d3f75fa5e..f6ddf5db7 100644 --- a/app/assets/javascripts/app/services/directives/views/componentView.js +++ b/app/assets/javascripts/app/services/directives/views/componentView.js @@ -1,18 +1,41 @@ class ComponentView { - constructor() { + constructor(componentManager, $timeout) { this.restrict = "E"; this.templateUrl = "frontend/directives/component-view.html"; this.scope = { component: "=" }; + + this.componentManager = componentManager; + this.timeout = $timeout; } link($scope, el, attrs, ctrl) { $scope.el = el; + let identifier = "component-view-" + Math.random(); + + this.componentManager.registerHandler({identifier: identifier, areas: ["*"], activationHandler: (component) => { + if(component.active) { + this.timeout(function(){ + var iframe = this.componentManager.iframeForComponent(component); + if(iframe) { + iframe.onload = function() { + this.componentManager.registerComponentWindow(component, iframe.contentWindow); + }.bind(this); + } + }.bind(this)); + } + }, + actionHandler: function(component, action, data) { + if(action == "set-size") { + this.componentManager.handleSetSizeEvent(component, data); + } + }.bind(this)}); + $scope.$watch('component', function(component, prevComponent){ - console.log("Component View Setting Component", component); + // console.log("Component View Setting Component", component); ctrl.componentValueChanging(component, prevComponent); }); } @@ -20,6 +43,8 @@ class ComponentView { controller($scope, $timeout, componentManager, desktopManager) { 'ngInject'; + console.log("Creating New Component View"); + this.componentValueChanging = (component, prevComponent) => { if(prevComponent && component !== prevComponent) { // Deactive old component @@ -33,39 +58,15 @@ class ComponentView { } } - let identifier = "component-view-" + Math.random(); - - $scope.url = function() { - if($scope.component.offlineOnly) { - return $scope.component.local_url; + $scope.$on("$destroy", function() { + console.log("DESTROY COMPONENT VIEW"); + componentManager.deregisterHandler($scope.identifier); + if($scope.component) { + componentManager.deactivateComponent($scope.component); } - - if(desktopManager.isDesktop && $scope.component.local_url) { - return $scope.component.local_url; - } - - return $scope.component.hosted_url || $scope.component.url; - } - - componentManager.registerHandler({identifier: identifier, areas: ["*"], activationHandler: (component) => { - if(component.active) { - $timeout(function(){ - var iframe = componentManager.iframeForComponent(component); - if(iframe) { - iframe.onload = function() { - componentManager.registerComponentWindow(component, iframe.contentWindow); - }.bind(this); - } - }.bind(this)); - } - }, - actionHandler: function(component, action, data) { - if(action == "set-size") { - componentManager.handleSetSizeEvent(component, data); - } - }.bind(this)}); + }); } } -angular.module('app.frontend').directive('componentView', () => new ComponentView); +angular.module('app.frontend').directive('componentView', (componentManager, $timeout) => new ComponentView(componentManager, $timeout)); diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 78e4005ab..63a95af1c 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -30,7 +30,6 @@ class EditorMenu { } $scope.toggleDefaultForEditor = function(editor) { - console.log("Toggling editor", editor); if($scope.defaultEditor == editor) { $scope.removeEditorDefault(editor); } else { diff --git a/app/assets/javascripts/app/services/directives/views/permissionsModal.js b/app/assets/javascripts/app/services/directives/views/permissionsModal.js index d08de104e..5988bae86 100644 --- a/app/assets/javascripts/app/services/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/services/directives/views/permissionsModal.js @@ -30,8 +30,6 @@ class PermissionsModal { controller($scope, modelManager) { - console.log("permissions", $scope.permissions); - $scope.formattedPermissions = $scope.permissions.map(function(permission){ if(permission.name === "stream-items") { var types = permission.content_types.map(function(type){ diff --git a/app/assets/javascripts/app/services/themeManager.js b/app/assets/javascripts/app/services/themeManager.js index 0e799ba08..248a67cab 100644 --- a/app/assets/javascripts/app/services/themeManager.js +++ b/app/assets/javascripts/app/services/themeManager.js @@ -1,54 +1,65 @@ class ThemeManager { - constructor(modelManager, syncManager, $rootScope, storageManager) { + constructor(modelManager, syncManager, $rootScope, storageManager, componentManager) { this.syncManager = syncManager; this.modelManager = modelManager; this.$rootScope = $rootScope; this.storageManager = storageManager; + + componentManager.registerHandler({identifier: "themeManager", areas: ["themes"], activationHandler: (component) => { + if(component.active) { + this.activateTheme(component); + } else { + this.deactivateTheme(component); + } + }}); } get themes() { return this.modelManager.itemsForContentType("SN|Theme"); } + /* activeTheme: computed property that returns saved theme currentTheme: stored variable that allows other classes to watch changes */ - get activeTheme() { - var activeThemeId = this.storageManager.getItem("activeTheme"); - if(!activeThemeId) { - return null; - } - - var theme = _.find(this.themes, {uuid: activeThemeId}); - return theme; - } + // get activeTheme() { + // var activeThemeId = this.storageManager.getItem("activeTheme"); + // if(!activeThemeId) { + // return null; + // } + // + // var theme = _.find(this.themes, {uuid: activeThemeId}); + // return theme; + // } activateInitialTheme() { - var theme = this.activeTheme; - if(theme) { - this.activateTheme(theme); - } + // var theme = this.activeTheme; + // if(theme) { + // this.activateTheme(theme); + // } } - submitTheme(url) { - var name = this.displayNameForThemeFile(this.fileNameFromPath(url)); - var theme = this.modelManager.createItem({content_type: "SN|Theme", url: url, name: name}); - this.modelManager.addItem(theme); - theme.setDirty(true); - this.syncManager.sync(); - } + // submitTheme(url) { + // var name = this.displayNameForThemeFile(this.fileNameFromPath(url)); + // var theme = this.modelManager.createItem({content_type: "SN|Theme", url: url, name: name}); + // this.modelManager.addItem(theme); + // theme.setDirty(true); + // this.syncManager.sync(); + // } activateTheme(theme) { - var activeTheme = this.activeTheme; - if(activeTheme) { - this.deactivateTheme(activeTheme); + if(this.activeTheme && this.activeTheme !== theme) { + this.deactivateTheme(this.activeTheme); } + + var url = theme.computedUrl(); + var link = document.createElement("link"); - link.href = theme.url; + link.href = url; link.type = "text/css"; link.rel = "stylesheet"; link.media = "screen,print"; @@ -72,10 +83,6 @@ class ThemeManager { this.$rootScope.$broadcast("theme-changed"); } - isThemeActive(theme) { - return this.storageManager.getItem("activeTheme") === theme.uuid; - } - fileNameFromPath(filePath) { return filePath.replace(/^.*[\\\/]/, ''); } diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index 7b4c08d2b..cca66e8ee 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -68,7 +68,7 @@ $heading-height: 75px; #note-tags-component-container { height: 50px; - #note-tags-iframe { + iframe { height: 50px; width: 100%; position: absolute; @@ -116,8 +116,8 @@ $heading-height: 75px; #editor-pane-component-stack { width: 100%; - .component { - height: 50px; + .component-stack-item { + // height: 50px; width: 100%; position: relative; &:not(:last-child) { @@ -128,28 +128,6 @@ $heading-height: 75px; border-top: 1px solid $bg-color; } - .exit-button { - width: 15px; - height: 100%; - position: absolute; - right: 0; - background-color: transparent; - cursor: pointer; - display: flex; - align-items: center; - color: rgba(black, 0.7); - text-align: center; - padding-left: 2px; - - .content { - - } - - &:hover { - background-color: rgba(gray, 0.3); - } - } - iframe { width: 100%; } diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index e4dfa9c71..3e3ff1a59 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -12,6 +12,10 @@ } } +.panel { + background-color: white; +} + .modal { position: fixed; margin-left: auto; @@ -44,14 +48,12 @@ } } -.modal-iframe-container { +// Optionally use if .component-view container is not flex-based +.component-view-container { flex-grow: 1; display: flex; - - iframe { - flex-grow: 1; - width: 100%; - } + height: 100%; + width: 100%; } .component-view { diff --git a/app/assets/stylesheets/app/_stylekit-sub.scss b/app/assets/stylesheets/app/_stylekit-sub.scss new file mode 100644 index 000000000..4b7bd0603 --- /dev/null +++ b/app/assets/stylesheets/app/_stylekit-sub.scss @@ -0,0 +1,7 @@ +.panel { + color: black; + + a { + color: $blue-color; + } +} diff --git a/app/assets/stylesheets/frontend.css.scss b/app/assets/stylesheets/frontend.css.scss index 529b6ed35..2097406ef 100644 --- a/app/assets/stylesheets/frontend.css.scss +++ b/app/assets/stylesheets/frontend.css.scss @@ -11,5 +11,6 @@ $dark-gray: #2e2e2e; @import "app/menus"; @import "app/modals"; @import "app/lock-screen"; +@import "app/stylekit-sub"; @import "ionicons"; diff --git a/app/assets/templates/frontend/directives/component-modal.html.haml b/app/assets/templates/frontend/directives/component-modal.html.haml index 2d02ce18a..4ab3a5a8a 100644 --- a/app/assets/templates/frontend/directives/component-modal.html.haml +++ b/app/assets/templates/frontend/directives/component-modal.html.haml @@ -1,7 +1,9 @@ -.sn-component - .panel{"ng-attr-id" => "component-{{component.uuid}}"} - .header - %h1.title {{component.name}} - %a.close-button.info{"ng-click" => "dismiss()"} Close - .modal-iframe-container{"ng-attr-id" => "component-{{component.uuid}}"} - %iframe{"ng-src" => "{{url() | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} +.background{"ng-click" => "dismiss()"} + +.content + .sn-component + .panel{"ng-attr-id" => "component-{{component.uuid}}"} + .header + %h1.title {{component.name}} + %a.close-button.info{"ng-click" => "dismiss()"} Close + %component-view.component-view{"component" => "component"} diff --git a/app/assets/templates/frontend/directives/component-view.html.haml b/app/assets/templates/frontend/directives/component-view.html.haml index 60ffbab5d..6025d9e26 100644 --- a/app/assets/templates/frontend/directives/component-view.html.haml +++ b/app/assets/templates/frontend/directives/component-view.html.haml @@ -1,6 +1,6 @@ %iframe{"ng-if" => "component", "ng-attr-id" => "component-{{component.uuid}}", -"ng-src" => "{{url() | trusted}}", "frameBorder" => "0", +"ng-src" => "{{component.computedUrl() | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} Loading diff --git a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml index 276a370c5..2a6b89a39 100644 --- a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml +++ b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml @@ -24,8 +24,8 @@ %h3 %input.bold{"ng-if" => "theme.rename", "ng-model" => "theme.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(theme);", "mb-autofocus" => "true", "should-focus" => "true"} %span{"ng-if" => "!theme.rename"} {{theme.name}} - %a{"ng-if" => "!themeManager.isThemeActive(theme)", "ng-click" => "themeManager.activateTheme(theme); $event.stopPropagation();"} Activate - %a{"ng-if" => "themeManager.isThemeActive(theme)", "ng-click" => "themeManager.deactivateTheme(theme); $event.stopPropagation();"} Deactivate + -# %a{"ng-if" => "!themeManager.isThemeActive(theme)", "ng-click" => "themeManager.activateTheme(theme); $event.stopPropagation();"} Activate + -# %a{"ng-if" => "themeManager.isThemeActive(theme)", "ng-click" => "themeManager.deactivateTheme(theme); $event.stopPropagation();"} Deactivate .mt-3{"ng-if" => "theme.showDetails"} .link-group %a{"ng-click" => "renameExtension(theme); $event.stopPropagation();"} Rename diff --git a/app/assets/templates/frontend/directives/permissions-modal.html.haml b/app/assets/templates/frontend/directives/permissions-modal.html.haml index cfd7d8661..d88280a3a 100644 --- a/app/assets/templates/frontend/directives/permissions-modal.html.haml +++ b/app/assets/templates/frontend/directives/permissions-modal.html.haml @@ -5,7 +5,7 @@ .panel .header %h1.title Activate Extension - %a.close-button.info Cancel + %a.close-button.info{"ng-click" => "deny()"} Cancel .content .panel-section .panel-row @@ -14,7 +14,6 @@ would like to interact with your %span{"ng-repeat" => "permission in formattedPermissions"} {{permission}}. - -# %p.wrap URL: {{component.runningUrl}} .panel-row %p diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 9541746bf..45a54105f 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -8,8 +8,8 @@ #save-status{"ng-class" => "{'red bold': ctrl.saveError, 'orange bold': ctrl.syncTakingTooLong}", "ng-bind-html" => "ctrl.noteStatus"} .editor-tags - #note-tags-component-container{"ng-if" => "ctrl.tagsComponent && ctrl.tagsComponent.active"} - %iframe#note-tags-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.tagsComponent) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts", "data-component-id" => "{{ctrl.tagsComponent.uuid}}"} + #note-tags-component-container{"ng-if" => "ctrl.tagsComponent"} + %component-view.component-view{ "component" => "ctrl.tagsComponent"} %input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} @@ -42,8 +42,7 @@ .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"} - -# ng-show is required here (as opposed to ng-if) in order for the component-view to receive events such as nulling ctrl.selectorEditor - %component-view{"ng-show" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor", "class" => "component-view"} + %component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor"} %textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} {{ctrl.onSystemEditorLoad()}} @@ -53,6 +52,4 @@ %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. #editor-pane-component-stack - .component.component-stack-border{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.ignoreEvents", "id" => "{{'component-' + component.uuid}}", "ng-mouseover" => "component.showExit = true", "ng-mouseleave" => "component.showExit = false"} - .exit-button.body-text-color{"ng-if" => "component.showExit", "ng-click" => "ctrl.disableComponentForCurrentItem(component, true)"} × - %iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(component) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} + %component-view.component-view.component-stack-item{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "component" => "component"} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index dc8b87d7f..4ed1d1de5 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -22,7 +22,7 @@ .item{"ng-repeat" => "room in ctrl.rooms"} .column{"ng-click" => "ctrl.selectRoom(room)"} .label {{room.name}} - %component-modal{"ng-if" => "room.show", "component" => "room"} + %component-modal{"ng-if" => "room.showRoom", "component" => "room", "controller" => "room.directiveController"} .right diff --git a/app/assets/templates/frontend/tags.html.haml b/app/assets/templates/frontend/tags.html.haml index d9cea08ee..e751732f8 100644 --- a/app/assets/templates/frontend/tags.html.haml +++ b/app/assets/templates/frontend/tags.html.haml @@ -1,5 +1,6 @@ .section.tags#tags-column - %iframe#tags-list-iframe{"ng-if" => "ctrl.component && ctrl.component.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.component) | trusted}}", "frameBorder" => "0", "style" => "width: 100%; height: 100%;", "sandbox" => "allow-scripts"} + .component-view-container{"ng-if" => "ctrl.component"} + %component-view.component-view{"component" => "ctrl.component"} #tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"} #tags-title-bar.section-title-bar .section-title-bar-header diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css index 906073401..326f4b600 100644 --- a/vendor/assets/stylesheets/stylekit.css +++ b/vendor/assets/stylesheets/stylekit.css @@ -6,9 +6,12 @@ .sn-component .panel { box-shadow: 0px 2px 13px #C8C8C8; border-radius: 0.7rem; - overflow: scroll; display: flex; flex-direction: column; + overflow: hidden; +} +.sn-component .panel a:hover { + text-decoration: underline; } .sn-component .panel.static { box-shadow: none; @@ -28,15 +31,16 @@ .sn-component .panel .header .close-button { font-weight: bold; } -.sn-component .panel .footer { +.sn-component .panel .footer, .sn-component .panel .panel-footer { padding: 1rem 2rem; - border-top: 1px solid #F1F1F1; + border-top: 1px solid #E1E1E1; + box-sizing: border-box; } -.sn-component .panel .footer .left { +.sn-component .panel .footer .left, .sn-component .panel .panel-footer .left { text-align: right; display: block; } -.sn-component .panel .footer .right { +.sn-component .panel .footer .right, .sn-component .panel .panel-footer .right { text-align: right; display: block; } @@ -44,75 +48,88 @@ padding: 1.6rem 2rem; padding-bottom: 0; flex-grow: 1; + overflow: scroll; } .sn-component .panel .content p { color: #454545; line-height: 1.3; } -.sn-component .panel .content .label, .sn-component .panel .content .panel-section .subtitle { +.sn-component .panel .content .label, .sn-component .panel .content .panel-section .subtitle, .sn-component .panel-section .panel .content .subtitle { font-weight: bold; } -.sn-component .panel .content .panel-section { +.sn-component .panel-section { padding-bottom: 1.6rem; -} -.sn-component .panel .content .panel-section .panel-row { display: flex; - justify-content: space-between; - align-items: center; - padding-top: 0.4rem; + flex-direction: column; } -.sn-component .panel .content .panel-section .panel-row.centered { - justify-content: center; +.sn-component .panel-section.no-bottom-pad { + padding-bottom: 0; } -.sn-component .panel .content .panel-section .panel-row .panel-column { - width: 100%; -} -.sn-component .panel .content .panel-section .panel-row:not(:last-child) { - padding-bottom: 0.4rem; -} -.sn-component .panel .content .panel-section .panel-row:not(:last-child).condensed { - padding-top: 0.2rem; - padding-bottom: 0.2rem; -} -.sn-component .panel .content .panel-section .panel-row p { - margin: 0; - padding: 0; -} -.sn-component .panel .content .panel-section.hero { +.sn-component .panel-section.hero { text-align: center; } -.sn-component .panel .content .panel-section p:last-child { +.sn-component .panel-section p:last-child { margin-bottom: 0; } -.sn-component .panel .content .panel-section:not(:last-child) { +.sn-component .panel-section:not(:last-child) { margin-bottom: 1.5rem; border-bottom: 1px solid #DDDDDD; } -.sn-component .panel .content .panel-section:last-child { +.sn-component .panel-section:not(:last-child).no-border { + border-bottom: none; +} +.sn-component .panel-section:last-child { margin-bottom: 0.5rem; } -.sn-component .panel .content .panel-section .outer-title { +.sn-component .panel-section .outer-title { border-bottom: 1px solid #DDDDDD; padding-bottom: 0.9rem; margin-top: 2.1rem; margin-bottom: 15px; } -.sn-component .panel .content .panel-section .subtitle { +.sn-component .panel-section .subtitle { margin-top: -0.5rem; } -.sn-component .panel .content .panel-section .subtitle.subtle { +.sn-component .panel-section .subtitle.subtle { font-weight: normal; opacity: 0.6; } -.sn-component .panel .content .panel-form { +.sn-component .panel-row { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 0.4rem; +} +.sn-component .panel-row.centered { + justify-content: center; +} +.sn-component .panel-row .panel-column { width: 100%; } -.sn-component .panel .content .panel-form.half { +.sn-component .panel-row.default-padding, .sn-component .panel-row:not(:last-child) { + padding-bottom: 0.4rem; +} +.sn-component .panel-row.default-padding.condensed, .sn-component .panel-row:not(:last-child).condensed { + padding-top: 0.2rem; + padding-bottom: 0.2rem; +} +.sn-component .panel-row p { + margin: 0; + padding: 0; +} +.sn-component .panel-form { + width: 100%; +} +.sn-component .panel-form.half { width: 50%; } -.sn-component .panel .content .panel-form .form-submit { +.sn-component .panel-form .form-submit { margin-top: 0.15rem; } +.sn-component .right-aligned { + justify-content: flex-end; + text-align: right; +} .sn-component .menu-panel { box-shadow: 0px 4px 4px #C8C8C8; border-radius: 0.6rem; @@ -156,14 +173,14 @@ .sn-component .menu-panel .row .column .left { display: flex; } -.sn-component .menu-panel .row .button .label, .sn-component .menu-panel .row .box .label, .sn-component .menu-panel .row .circle .label, .sn-component .menu-panel .row .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .button .subtitle, .sn-component .menu-panel .row .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .box .subtitle, .sn-component .menu-panel .row .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .circle .subtitle { +.sn-component .menu-panel .row .button .label, .sn-component .menu-panel .row .box .label, .sn-component .menu-panel .row .circle .label, .sn-component .menu-panel .row .button .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .button .subtitle, .sn-component .menu-panel .row .box .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .box .subtitle, .sn-component .menu-panel .row .circle .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .circle .subtitle { font-size: 0.8rem; font-weight: normal; } .sn-component .menu-panel .row:hover { background-color: #efefef; } -.sn-component .menu-panel .row .label, .sn-component .menu-panel .row .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .menu-panel .row .subtitle { +.sn-component .menu-panel .row .label, .sn-component .menu-panel .row .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .subtitle { font-size: 1rem; font-weight: bold; } @@ -270,6 +287,9 @@ .sn-component .horizontal-group > *:not(:first-child), .sn-component .input-group > *:not(:first-child) { margin-left: 0.9rem; } +.sn-component .border-bottom { + border-bottom: 1px solid #DDDDDD; +} .sn-component .checkbox-group { padding-top: 0.5rem; padding-bottom: 0.3rem; @@ -322,7 +342,7 @@ text-align: center; border: 1px solid; } -.sn-component .button .label, .sn-component .box .label, .sn-component .circle .label, .sn-component .button .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .button .subtitle, .sn-component .box .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .box .subtitle, .sn-component .circle .panel .content .panel-section .subtitle, .sn-component .panel .content .panel-section .circle .subtitle { +.sn-component .button .label, .sn-component .box .label, .sn-component .circle .label, .sn-component .button .panel-section .subtitle, .sn-component .panel-section .button .subtitle, .sn-component .box .panel-section .subtitle, .sn-component .panel-section .box .subtitle, .sn-component .circle .panel-section .subtitle, .sn-component .panel-section .circle .subtitle { font-weight: bold; display: block; text-align: center; @@ -602,10 +622,10 @@ .sn-component .app-bar .item.no-pointer { cursor: default; } -.sn-component .app-bar .item:hover > .label:not(.subtle), .sn-component .app-bar .panel .content .panel-section .item:hover > .subtitle:not(.subtle), .sn-component .panel .content .panel-section .app-bar .item:hover > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .sublabel:not(.subtle), .sn-component .app-bar .item:hover > .column > .label:not(.subtle), .sn-component .app-bar .panel .content .panel-section .item:hover > .column > .subtitle:not(.subtle), .sn-component .panel .content .panel-section .app-bar .item:hover > .column > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .column > .sublabel:not(.subtle) { +.sn-component .app-bar .item:hover > .label:not(.subtle), .sn-component .app-bar .panel-section .item:hover > .subtitle:not(.subtle), .sn-component .panel-section .app-bar .item:hover > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .sublabel:not(.subtle), .sn-component .app-bar .item:hover > .column > .label:not(.subtle), .sn-component .app-bar .panel-section .item:hover > .column > .subtitle:not(.subtle), .sn-component .panel-section .app-bar .item:hover > .column > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .column > .sublabel:not(.subtle) { color: #086DD6; } -.sn-component .app-bar .item > .label, .sn-component .app-bar .panel .content .panel-section .item > .subtitle, .sn-component .panel .content .panel-section .app-bar .item > .subtitle, .sn-component .app-bar .item > .column > .label, .sn-component .app-bar .panel .content .panel-section .item > .column > .subtitle, .sn-component .panel .content .panel-section .app-bar .item > .column > .subtitle { +.sn-component .app-bar .item > .label, .sn-component .app-bar .panel-section .item > .subtitle, .sn-component .panel-section .app-bar .item > .subtitle, .sn-component .app-bar .item > .column > .label, .sn-component .app-bar .panel-section .item > .column > .subtitle, .sn-component .panel-section .app-bar .item > .column > .subtitle { font-weight: bold; font-size: 0.9rem; white-space: nowrap; From 5db97b0a4c7930a473e9d9dbf221f4ceb553267a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 10 Jan 2018 12:57:51 -0600 Subject: [PATCH 24/99] Editor stack associate/disassociate --- .../app/frontend/controllers/editor.js | 112 ++++++++++-------- .../app/services/componentManager.js | 32 ----- .../directives/views/componentView.js | 1 - .../services/directives/views/editorMenu.js | 10 +- .../frontend/directives/editor-menu.html.haml | 14 ++- .../templates/frontend/editor.html.haml | 7 +- 6 files changed, 85 insertions(+), 91 deletions(-) diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index 79af1fa4f..9e9415a62 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -103,28 +103,42 @@ angular.module('app.frontend') } } - this.selectEditor = function(editor) { - this.showEditorMenu = false; + this.onEditorMenuClick = function() { + // App bar menu item click + this.showEditorMenu = !this.showEditorMenu; + this.showMenu = false; + this.showExtensions = false; + } - if(editor) { - this.note.setAppDataItem("prefersPlainEditor", false); - this.note.setDirty(true); - componentManager.associateComponentWithItem(editor, this.note); - } else { - // Note prefers plain editor - if(this.selectedEditor) { - componentManager.disassociateComponentWithItem(this.selectedEditor, this.note); + this.editorMenuOnSelect = function(component) { + if(!component || component.area == "editor-editor") { + // if plain editor or other editor + this.showEditorMenu = false; + var editor = component; + if(editor) { + this.note.setAppDataItem("prefersPlainEditor", false); + this.note.setDirty(true); + componentManager.associateComponentWithItem(editor, this.note); + } else { + // Note prefers plain editor + if(this.selectedEditor) { + this.disableComponentForCurrentItem(this.selectedEditor); + } + this.note.setAppDataItem("prefersPlainEditor", true); + this.note.setDirty(true); + syncManager.sync(); + + $timeout(() => { + this.reloadFont(); + }) } - this.note.setAppDataItem("prefersPlainEditor", true); - this.note.setDirty(true); - syncManager.sync(); - $timeout(() => { - this.reloadFont(); - }) + this.selectedEditor = editor; + } else if(component.area == "editor-stack") { + // If component stack item + this.toggleStackComponentForCurrentItem(component); } - this.selectedEditor = editor; }.bind(this) this.hasAvailableExtensions = function() { @@ -467,51 +481,53 @@ angular.module('app.frontend') }.bind(this)}); this.reloadComponentContext = function() { - for(var component of this.componentStack) { - componentManager.setEventFlowForComponent(component, component.isActiveForItem(this.note)); - } - componentManager.contextItemDidChangeInArea("note-tags"); componentManager.contextItemDidChangeInArea("editor-stack"); componentManager.contextItemDidChangeInArea("editor-editor"); - } - let alertKey = "displayed-component-disable-alert"; - this.disableComponentForCurrentItem = function(component, showAlert) { - componentManager.disassociateComponentWithItem(component, this.note); - componentManager.setEventFlowForComponent(component, 0); - if(showAlert && !storageManager.getItem(alertKey)) { - alert("This component will be disabled for this note. You can re-enable this component in the 'Menu' of the editor pane."); - storageManager.setItem(alertKey, true); - } - } - - this.hasDisabledStackComponents = function() { - for(var component of this.componentStack) { - if(component.ignoreEvents) { - return true; + var stack = componentManager.componentsForArea("editor-stack"); + for(var component of stack) { + var activeForItem = component.isActiveForItem(this.note); + if(activeForItem) { + if(!component.active) { + componentManager.activateComponent(component); + } + } else { + if(component.active) { + componentManager.deactivateComponent(component); + } } } - - return false; } - this.restoreDisabledStackComponents = function() { - var relevantComponents = this.componentStack.filter(function(component){ - return component.ignoreEvents; - }) - - componentManager.enableComponentsForItem(relevantComponents, this.note); - - for(var component of relevantComponents) { - componentManager.setEventFlowForComponent(component, true); - componentManager.contextItemDidChangeInArea("editor-stack"); + this.toggleStackComponentForCurrentItem = function(component) { + if(component.isActiveForItem(this.note)) { + this.disableComponentForCurrentItem(component); + } else { + this.enableComponentForCurrentItem(component); } } + this.disableComponentForCurrentItem = function(component) { + componentManager.deactivateComponent(component); + _.pull(component.associatedItemIds, this.note.uuid); + if(component.disassociatedItemIds.indexOf(this.note.uuid) !== -1) { + return; + } + component.disassociatedItemIds.push(this.note.uuid); + component.setDirty(true); + syncManager.sync(); + } + this.enableComponentForCurrentItem = function(component) { + componentManager.activateComponent(component); + componentManager.contextItemDidChangeInArea("editor-stack"); + _.pull(component.disassociatedItemIds, this.note.uuid); + component.setDirty(true); + syncManager.sync(); + } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 916e44df6..29a1ec549 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -601,12 +601,6 @@ class ComponentManager { } sendMessageToComponent(component, message) { - if(component.ignoreEvents && message.action !== "component-registered") { - if(this.loggingEnabled) { - console.log("Component disabled for current item, not sending any messages.", component.name); - } - return; - } if(this.loggingEnabled) { console.log("Web|sendMessageToComponent", component, message); } @@ -686,7 +680,6 @@ class ComponentManager { } deactivateComponent(component) { - console.log("Deactivating component", component); var didChange = component.active != false; component.active = false; component.sessionKey = null; @@ -726,19 +719,6 @@ class ComponentManager { return component.active; } - disassociateComponentWithItem(component, item) { - _.pull(component.associatedItemIds, item.uuid); - - if(component.disassociatedItemIds.indexOf(item.uuid) !== -1) { - return; - } - - component.disassociatedItemIds.push(item.uuid); - - component.setDirty(true); - this.syncManager.sync(); - } - associateComponentWithItem(component, item) { _.pull(component.disassociatedItemIds, item.uuid); @@ -752,18 +732,6 @@ class ComponentManager { this.syncManager.sync(); } - enableComponentsForItem(components, item) { - for(var component of components) { - _.pull(component.disassociatedItemIds, item.uuid); - component.setDirty(true); - } - this.syncManager.sync(); - } - - setEventFlowForComponent(component, on) { - component.ignoreEvents = !on; - } - iframeForComponent(component) { for(var frame of document.getElementsByTagName("iframe")) { var componentId = frame.dataset.componentId; diff --git a/app/assets/javascripts/app/services/directives/views/componentView.js b/app/assets/javascripts/app/services/directives/views/componentView.js index f6ddf5db7..a3ac65527 100644 --- a/app/assets/javascripts/app/services/directives/views/componentView.js +++ b/app/assets/javascripts/app/services/directives/views/componentView.js @@ -54,7 +54,6 @@ class ComponentView { if(component) { componentManager.activateComponent(component); - componentManager.setEventFlowForComponent(component, 1); } } diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 63a95af1c..0e6b4fc81 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -15,17 +15,19 @@ class EditorMenu { $scope.formData = {}; $scope.editors = componentManager.componentsForArea("editor-editor"); + $scope.stack = componentManager.componentsForArea("editor-stack"); $scope.isDesktop = isDesktopApplication(); $scope.defaultEditor = $scope.editors.filter((e) => {return e.isDefaultEditor()})[0]; - $scope.selectEditor = function($event, editor) { - if(editor) { - editor.conflict_of = null; // clear conflict if applicable + $scope.selectComponent = function($event, component) { + $event.stopPropagation(); + if(component) { + component.conflict_of = null; // clear conflict if applicable } $timeout(() => { - $scope.callback()(editor); + $scope.callback()(component); }) } diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 6324f53f4..49e222f67 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -3,9 +3,9 @@ .section .header %h4.title Note Editor - %menu-row{"title" => "'Plain Editor'", "circle" => "selectedEditor == null && 'success'", "ng-click" => "selectEditor($event, null)"} + %menu-row{"title" => "'Plain Editor'", "circle" => "selectedEditor == null && 'success'", "ng-click" => "selectComponent($event, null)"} - %menu-row{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)", "title" => "editor.name", + %menu-row{"ng-repeat" => "editor in editors", "ng-click" => "selectComponent($event, editor)", "title" => "editor.name", "circle" => "selectedEditor === editor && 'success'", "has-button" => "selectedEditor == editor || defaultEditor == editor", "button-text" => "defaultEditor == editor ? 'Undefault' : 'Set Default'", "button-action" => "toggleDefaultForEditor(editor)", "button-class" => "defaultEditor == editor ? 'warning' : 'info'"} @@ -17,3 +17,13 @@ %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download More Editors'", "ng-click" => "moreEditors()"} + .section + .header + %h4.title Editor Stack + %menu-row{"ng-repeat" => "component in stack", "ng-click" => "selectComponent($event, component)", "title" => "component.name", + "circle" => "component.active ? 'success' : 'danger'"} + .row + .column + %strong.red.medium{"ng-if" => "component.conflict_of"} Conflicted copy + .sublabel{"ng-if" => "component.local_url"} + Available Offline diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 45a54105f..cb2ac39c1 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -25,16 +25,15 @@ %menu-row{"title" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} %menu-row{"title" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} %menu-row{"title" => "'Delete'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} - %menu-row{"ng-if" => "ctrl.hasDisabledComponents()", "title" => "'Restore Disabled Components'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledComponents()"} - .section{"ng-if" => "!ctrl.editor"} + .section{"ng-if" => "!ctrl.selectedEditor"} .header %h4.title Display %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('monospaceFont')"} - .item{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} + .item{"ng-click" => "ctrl.onEditorMenuClick()", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} .label Editor - %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectEditor", "selected-editor" => "ctrl.selectedEditor"} + %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.editorMenuOnSelect", "selected-editor" => "ctrl.selectedEditor"} .item{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} .label Actions From c7ce308a19b737b1cc584183bfe574239d55152f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 12 Jan 2018 14:36:17 -0600 Subject: [PATCH 25/99] Editor updates --- Gruntfile.js | 3 +- .../app/frontend/controllers/editor.js | 53 +- .../app/frontend/controllers/footer.js | 5 + .../app/frontend/models/app/component.js | 14 +- .../app/frontend/models/local/itemParams.js | 8 +- .../app/services/componentManager.js | 60 +- .../app/services/desktopManager.js | 25 +- .../directives/views/componentView.js | 13 +- .../services/directives/views/editorMenu.js | 4 + .../javascripts/app/services/modelManager.js | 14 +- .../app/services/packageManager.js | 5 +- .../app/services/singletonManager.js | 1 - .../javascripts/app/services/themeManager.js | 4 +- app/assets/stylesheets/app/_modals.scss | 4 + app/assets/stylesheets/app/_tags.scss | 2 +- .../directives/component-modal.html.haml | 4 +- .../directives/component-view.html.haml | 4 +- .../frontend/directives/editor-menu.html.haml | 2 +- app/views/application/frontend.html.erb | 1 - package-lock.json | 6539 +++++++++++++++++ package.json | 5 +- vendor/assets/stylesheets/stylekit.css | 686 -- 22 files changed, 6662 insertions(+), 794 deletions(-) create mode 100644 package-lock.json delete mode 100644 vendor/assets/stylesheets/stylekit.css diff --git a/Gruntfile.js b/Gruntfile.js index 7aecbdebc..3c13c4654 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -96,7 +96,8 @@ module.exports = function(grunt) { css: { src: [ - 'vendor/assets/stylesheets/app.css' + 'vendor/assets/stylesheets/app.css', + 'node_modules/sn-stylekit/dist/stylekit.css' ], dest: 'vendor/assets/stylesheets/app.css' } diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index 9e9415a62..556c6ef60 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -115,19 +115,21 @@ angular.module('app.frontend') // if plain editor or other editor this.showEditorMenu = false; var editor = component; + if(this.selectedEditor && editor !== this.selectedEditor) { + this.disassociateComponentWithCurrentNote(this.selectedEditor); + } if(editor) { - this.note.setAppDataItem("prefersPlainEditor", false); - this.note.setDirty(true); - componentManager.associateComponentWithItem(editor, this.note); + if(this.note.getAppDataItem("prefersPlainEditor") == true) { + this.note.setAppDataItem("prefersPlainEditor", false); + this.note.setDirty(true); + } + this.associateComponentWithCurrentNote(editor); } else { // Note prefers plain editor - if(this.selectedEditor) { - this.disableComponentForCurrentItem(this.selectedEditor); + if(!this.note.getAppDataItem("prefersPlainEditor")) { + this.note.setAppDataItem("prefersPlainEditor", true); + this.note.setDirty(true); } - this.note.setAppDataItem("prefersPlainEditor", true); - this.note.setDirty(true); - syncManager.sync(); - $timeout(() => { this.reloadFont(); }) @@ -139,6 +141,8 @@ angular.module('app.frontend') this.toggleStackComponentForCurrentItem(component); } + // Lots of dirtying can happen above, so we'll sync + syncManager.sync(); }.bind(this) this.hasAvailableExtensions = function() { @@ -502,31 +506,34 @@ angular.module('app.frontend') this.toggleStackComponentForCurrentItem = function(component) { if(component.isActiveForItem(this.note)) { - this.disableComponentForCurrentItem(component); + componentManager.deactivateComponent(component); + this.disassociateComponentWithCurrentNote(component); } else { - this.enableComponentForCurrentItem(component); + componentManager.activateComponent(component); + componentManager.contextItemDidChangeInArea("editor-stack"); + this.associateComponentWithCurrentNote(component); } } - this.disableComponentForCurrentItem = function(component) { - componentManager.deactivateComponent(component); - _.pull(component.associatedItemIds, this.note.uuid); - if(component.disassociatedItemIds.indexOf(this.note.uuid) !== -1) { - return; + this.disassociateComponentWithCurrentNote = function(component) { + component.associatedItemIds = component.associatedItemIds.filter((id) => {return id !== this.note.uuid}); + + // Only disassociative components should modify the disassociatedItemIds + if(!component.isAssociative() && !component.disassociatedItemIds.includes(this.note.uuid)) { + component.disassociatedItemIds.push(this.note.uuid); } - component.disassociatedItemIds.push(this.note.uuid); component.setDirty(true); - syncManager.sync(); } - this.enableComponentForCurrentItem = function(component) { - componentManager.activateComponent(component); - componentManager.contextItemDidChangeInArea("editor-stack"); + this.associateComponentWithCurrentNote = function(component) { + component.disassociatedItemIds = component.disassociatedItemIds.filter((id) => {return id !== this.note.uuid}); + + if(component.isAssociative() && !component.associatedItemIds.includes(this.note.uuid)) { + component.associatedItemIds.push(this.note.uuid); + } - _.pull(component.disassociatedItemIds, this.note.uuid); component.setDirty(true); - syncManager.sync(); } diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index 315272cd3..218469abb 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -166,6 +166,11 @@ angular.module('app.frontend') // Safe to create. Create and return object. let url = window._prolink_package_url; + console.log("Installing ProLink from URL", url); + if(!url) { + console.error("window._prolink_package_url must be set."); + return; + } packageManager.installPackage(url, (component) => { valueCallback(component); }) diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index 42781cfa7..c9accbd03 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -23,9 +23,10 @@ class Component extends Item { /* New */ this.local_url = content.local_url; this.hosted_url = content.hosted_url; + this.offlineOnly = content.offlineOnly; this.name = content.name; - this.autoupdate = content.autoupdate; + this.autoupdateDisabled = content.autoupdateDisabled; this.package_info = content.package_info; @@ -50,12 +51,13 @@ class Component extends Item { url: this.url, hosted_url: this.hosted_url, local_url: this.local_url, + offlineOnly: this.offlineOnly, name: this.name, area: this.area, package_info: this.package_info, permissions: this.permissions, active: this.active, - autoupdate: this.autoupdate, + autoupdateDisabled: this.autoupdateDisabled, componentData: this.componentData, disassociatedItemIds: this.disassociatedItemIds, associatedItemIds: this.associatedItemIds, @@ -73,14 +75,6 @@ class Component extends Item { return "SN|Component"; } - computedUrl() { - if(this.offlineOnly || (isDesktopApplication() && this.local_url)) { - return this.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); - } else { - return this.url || this.hosted_url; - } - } - isEditor() { return this.area == "editor-editor"; } diff --git a/app/assets/javascripts/app/frontend/models/local/itemParams.js b/app/assets/javascripts/app/frontend/models/local/itemParams.js index 7cb25214c..deb6d249c 100644 --- a/app/assets/javascripts/app/frontend/models/local/itemParams.js +++ b/app/assets/javascripts/app/frontend/models/local/itemParams.js @@ -6,10 +6,14 @@ class ItemParams { this.version = version || "002"; } - paramsForExportFile() { + paramsForExportFile(includeDeleted) { this.additionalFields = ["updated_at"]; this.forExportFile = true; - return _.omit(this.__params(), ["deleted"]); + if(includeDeleted) { + return this.__params(); + } else { + return _.omit(this.__params(), ["deleted"]); + } } paramsForExtension() { diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 29a1ec549..28e51a70b 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -127,7 +127,7 @@ class ComponentManager { postThemeToComponent(component) { var activeTheme = this.getActiveTheme(); var data = { - themes: [activeTheme ? activeTheme.computedUrl() : null] + themes: [activeTheme ? this.urlForComponent(activeTheme) : null] } this.sendMessageToComponent(component, {action: "themes", data: data}) @@ -196,6 +196,14 @@ class ComponentManager { }) } + urlForComponent(component) { + if(component.offlineOnly || (isDesktopApplication() && component.local_url)) { + return component.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); + } else { + return component.url || component.hosted_url; + } + } + componentForUrl(url) { return this.components.filter(function(component){ return component.url === url || component.hosted_url === url; @@ -246,8 +254,6 @@ class ComponentManager { this.handleCreateItemMessage(component, message); } else if(message.action === "save-items") { this.handleSaveItemsMessage(component, message); - } else if(message.action === "install-local-component") { - this.handleInstallLocalComponentMessage(component, message); } else if(message.action === "toggle-activate-component") { let componentToToggle = this.modelManager.findItem(message.data.uuid); this.handleToggleComponentMessage(component, componentToToggle, message); @@ -265,7 +271,7 @@ class ComponentManager { removePrivatePropertiesFromResponseItems(responseItems, includeUrls) { // Don't allow component to overwrite these properties. - var privateProperties = ["appData", "autoupdate", "permissions", "active", "encrypted"]; + var privateProperties = ["appData", "autoupdateDisabled", "permissions", "active", "encrypted"]; if(includeUrls) { privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"]); } @@ -329,17 +335,21 @@ class ComponentManager { // push immediately now for(let handler of this.handlersForArea(component.area)) { - var itemInContext = handler.contextRequestHandler(component); - this.sendContextItemInReply(component, itemInContext, message); + if(handler.contextRequestHandler) { + var itemInContext = handler.contextRequestHandler(component); + this.sendContextItemInReply(component, itemInContext, message); + } } }.bind(this)) } isItemWithinComponentContextJurisdiction(item, component) { for(let handler of this.handlersForArea(component.area)) { - var itemInContext = handler.contextRequestHandler(component); - if(itemInContext.uuid == item.uuid) { - return true; + if(handler.contextRequestHandler) { + var itemInContext = handler.contextRequestHandler(component); + if(itemInContext.uuid == item.uuid) { + return true; + } } } return false; @@ -388,6 +398,7 @@ class ComponentManager { } item.setDirty(true); } + this.syncManager.sync((response) => { // Allow handlers to be notified when a save begins and ends, to update the UI var saveMessage = Object.assign({}, message); @@ -443,24 +454,6 @@ class ComponentManager { }); } - handleInstallLocalComponentMessage(component, message) { - var requiredPermissions = [ - { - name: "stream-items", - content_types: [message.data.content_type] - } - ]; - - this.runWithPermissions(component, requiredPermissions, () => { - this.desktopManager.installOfflineComponentFromData(message.data, (response) => { - var component = this.modelManager.mapResponseItemsToLocalModels([response], ModelManager.MappingSourceComponentRetrieved)[0]; - // Save updated URL - component.setDirty(true); - this.syncManager.sync(); - }) - }); - } - handleSetComponentDataMessage(component, message) { // A component setting its own data does not require special permissions this.runWithPermissions(component, [], () => { @@ -719,19 +712,6 @@ class ComponentManager { return component.active; } - associateComponentWithItem(component, item) { - _.pull(component.disassociatedItemIds, item.uuid); - - if(component.associatedItemIds.includes(item.uuid)) { - return; - } - - component.associatedItemIds.push(item.uuid); - - component.setDirty(true); - this.syncManager.sync(); - } - iframeForComponent(component) { for(var frame of document.getElementsByTagName("iframe")) { var componentId = frame.dataset.componentId; diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index a89dd1514..ebe16d93c 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -25,14 +25,6 @@ class DesktopManager { }) } - /* Can handle components and themes */ - installOfflineComponentFromData(componentData, callback) { - this.componentInstallationHandler(componentData, (installedComponent) => { - componentData.content.local_url = installedComponent.content.local_url; - callback(componentData); - }); - } - getApplicationDataPath() { console.assert(this.applicationDataPath, "applicationDataPath is null"); return this.applicationDataPath; @@ -40,24 +32,31 @@ class DesktopManager { /* Sending a component in its raw state is really slow for the desktop app */ convertComponentForTransmission(component) { - return new ItemParams(component).paramsForExportFile(); + return new ItemParams(component).paramsForExportFile(true); } // All `components` should be installed syncComponentsInstallation(components) { + // console.log("Web Syncing Components", components); if(!this.isDesktop) return; - /* Allows us to look up component on desktop_updateComponentComplete */ - this.syncingComponents = components; - var data = components.map((component) => { + console.log("Web Sycying Component", this.convertComponentForTransmission(component)); return this.convertComponentForTransmission(component); }) this.installationSyncHandler(data); } + desktop_onComponentInstallationComplete(componentData) { + console.log("Web|Component Installation Complete", componentData); + var component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; + component.setDirty(true); + this.syncManager.sync(); + } + desktop_updateComponentComplete(componentData) { - var component = this.syncingComponents.filter((c) => {return c.uuid == componentData.uuid})[0]; + console.log("Web|Component Update Complete", componentData); + var component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; component.setDirty(true); this.syncManager.sync(); } diff --git a/app/assets/javascripts/app/services/directives/views/componentView.js b/app/assets/javascripts/app/services/directives/views/componentView.js index a3ac65527..2cbd6e586 100644 --- a/app/assets/javascripts/app/services/directives/views/componentView.js +++ b/app/assets/javascripts/app/services/directives/views/componentView.js @@ -35,7 +35,6 @@ class ComponentView { }.bind(this)}); $scope.$watch('component', function(component, prevComponent){ - // console.log("Component View Setting Component", component); ctrl.componentValueChanging(component, prevComponent); }); } @@ -43,22 +42,26 @@ class ComponentView { controller($scope, $timeout, componentManager, desktopManager) { 'ngInject'; - console.log("Creating New Component View"); - this.componentValueChanging = (component, prevComponent) => { if(prevComponent && component !== prevComponent) { // Deactive old component - console.log("DEACTIVATING OLD COMPONENT", prevComponent); componentManager.deactivateComponent(prevComponent); } if(component) { componentManager.activateComponent(component); + component.runningLocally = $scope.getUrl + console.log("Loading", $scope.component.name, $scope.getUrl()); } } + $scope.getUrl = function() { + var url = componentManager.urlForComponent($scope.component); + $scope.component.runningLocally = url !== ($scope.component.url || $scope.component.hosted_url); + return url; + } + $scope.$on("$destroy", function() { - console.log("DESTROY COMPONENT VIEW"); componentManager.deregisterHandler($scope.identifier); if($scope.component) { componentManager.deactivateComponent($scope.component); diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 0e6b4fc81..2daea9aea 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -39,6 +39,10 @@ class EditorMenu { } } + $scope.offlineAvailableForComponent = function(component) { + return component.local_url && isDesktopApplication(); + } + $scope.makeEditorDefault = function(component) { var currentDefault = componentManager.componentsForArea("editor-editor").filter((e) => {return e.isDefaultEditor()})[0]; if(currentDefault) { diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index c4f475a7b..a72488668 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -6,6 +6,7 @@ class ModelManager { 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"; @@ -147,7 +148,7 @@ class ModelManager { } if(!item) { - item = this.createItem(json_obj); + item = this.createItem(json_obj, true); } this.addItem(item); @@ -200,7 +201,7 @@ class ModelManager { } } - createItem(json_obj) { + createItem(json_obj, dontNotifyObservers) { var item; if(json_obj.content_type == "Note") { item = new Note(json_obj); @@ -224,6 +225,15 @@ class ModelManager { 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)); diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js index 103ab5096..4ff78561c 100644 --- a/app/assets/javascripts/app/services/packageManager.js +++ b/app/assets/javascripts/app/services/packageManager.js @@ -18,10 +18,11 @@ class PackageManager { // Remove private properties this.componentManager.removePrivatePropertiesFromResponseItems([aPackage]); - var assembled = this.modelManager.createItem(aPackage); + aPackage.package_info = Object.assign({}, aPackage); - assembled.package_info = aPackage; + var assembled = this.modelManager.createItem(aPackage);; this.modelManager.addItem(assembled); + assembled.setDirty(true); this.syncManager.sync(); diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 29836eca0..f2f2c015e 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -27,7 +27,6 @@ class SingletonManager { setTimeout(function () { var userPrefs = modelManager.itemsForContentType("SN|UserPreferences"); console.assert(userPrefs.length == 1); - console.log("All extant prefs", userPrefs); }, 1000); } diff --git a/app/assets/javascripts/app/services/themeManager.js b/app/assets/javascripts/app/services/themeManager.js index 248a67cab..2a475ac62 100644 --- a/app/assets/javascripts/app/services/themeManager.js +++ b/app/assets/javascripts/app/services/themeManager.js @@ -5,6 +5,7 @@ class ThemeManager { this.modelManager = modelManager; this.$rootScope = $rootScope; this.storageManager = storageManager; + this.componentManager = componentManager; componentManager.registerHandler({identifier: "themeManager", areas: ["themes"], activationHandler: (component) => { if(component.active) { @@ -55,8 +56,7 @@ class ThemeManager { this.deactivateTheme(this.activeTheme); } - - var url = theme.computedUrl(); + var url = this.componentManager.urlForComponent(theme); var link = document.createElement("link"); link.href = url; diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index 3e3ff1a59..79666daeb 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -16,6 +16,10 @@ background-color: white; } +.header .subtitle { + font-size: 1.1rem; +} + .modal { position: fixed; margin-left: auto; diff --git a/app/assets/stylesheets/app/_tags.scss b/app/assets/stylesheets/app/_tags.scss index 79f0fb0b9..283a0cc8e 100644 --- a/app/assets/stylesheets/app/_tags.scss +++ b/app/assets/stylesheets/app/_tags.scss @@ -17,7 +17,7 @@ color: rgba(black, 0.8); } - #tags-content { + &, #tags-content { background-color: #f6f6f6; } diff --git a/app/assets/templates/frontend/directives/component-modal.html.haml b/app/assets/templates/frontend/directives/component-modal.html.haml index 4ab3a5a8a..25126a337 100644 --- a/app/assets/templates/frontend/directives/component-modal.html.haml +++ b/app/assets/templates/frontend/directives/component-modal.html.haml @@ -4,6 +4,8 @@ .sn-component .panel{"ng-attr-id" => "component-{{component.uuid}}"} .header - %h1.title {{component.name}} + %h1.title + {{component.name}} + %span.subtle.subtitle{"ng-if" => "component.runningLocally"} | Desktop Mode %a.close-button.info{"ng-click" => "dismiss()"} Close %component-view.component-view{"component" => "component"} diff --git a/app/assets/templates/frontend/directives/component-view.html.haml b/app/assets/templates/frontend/directives/component-view.html.haml index 6025d9e26..3e1b2b3d2 100644 --- a/app/assets/templates/frontend/directives/component-view.html.haml +++ b/app/assets/templates/frontend/directives/component-view.html.haml @@ -1,6 +1,6 @@ %iframe{"ng-if" => "component", "ng-attr-id" => "component-{{component.uuid}}", -"ng-src" => "{{component.computedUrl() | trusted}}", "frameBorder" => "0", -"sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", +"ng-src" => "{{getUrl() | trusted}}", "frameBorder" => "0", +"sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals", "data-component-id" => "{{component.uuid}}"} Loading diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 49e222f67..5f6df4192 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -12,7 +12,7 @@ .row .column %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy - .sublabel{"ng-if" => "editor.local_url"} + .sublabel{"ng-if" => "offlineAvailableForComponent(editor)"} Available Offline %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} diff --git a/app/views/application/frontend.html.erb b/app/views/application/frontend.html.erb index 217bd89e2..471176b47 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/frontend.html.erb @@ -39,7 +39,6 @@ <%= javascript_include_tag "compiled.min.js", debug: false %> <% end %> <%= stylesheet_link_tag "app", media: "all", debug: false %> - <%= stylesheet_link_tag 'stylekit.css', media: 'all', 'data-turbolinks-track' => true %> diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..949c34b89 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6539 @@ +{ + "name": "neeto", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "JSONStream": { + "version": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "dev": true, + "requires": { + "jsonparse": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + } + }, + "abbrev": { + "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true + }, + "accepts": { + "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "dev": true, + "requires": { + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "negotiator": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" + } + }, + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + }, + "after": { + "version": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "align-text": { + "version": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "longest": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "repeat-string": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + } + }, + "alter": { + "version": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz", + "integrity": "sha1-x1iICGF1cgNKrmJICvJrHU0cs80=", + "dev": true, + "requires": { + "stable": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz" + } + }, + "amdefine": { + "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "angular": { + "version": "https://registry.npmjs.org/angular/-/angular-1.6.6.tgz", + "integrity": "sha1-/Vo8+0N844LYVO4BEgeXl4Uny2Q=", + "dev": true + }, + "angular-mocks": { + "version": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.6.6.tgz", + "integrity": "sha1-yTAY54OMbcXOrxprz5vhPIMOpRU=", + "dev": true + }, + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "anymatch": { + "version": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", + "dev": true, + "requires": { + "micromatch": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "normalize-path": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz" + } + }, + "argparse": { + "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + } + }, + "arr-diff": { + "version": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz" + } + }, + "arr-flatten": { + "version": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true + }, + "array-filter": { + "version": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, + "array-find-index": { + "version": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-map": { + "version": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, + "array-slice": { + "version": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "array-unique": { + "version": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arraybuffer.slice": { + "version": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", + "dev": true + }, + "asn1": { + "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "asn1.js": { + "version": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz" + } + }, + "assert": { + "version": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "https://registry.npmjs.org/util/-/util-0.10.3.tgz" + } + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "astw": { + "version": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", + "dev": true, + "requires": { + "acorn": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz" + } + }, + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "asynckit": { + "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "babel-cli": { + "version": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", + "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", + "dev": true, + "requires": { + "babel-core": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "babel-polyfill": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "babel-register": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "chokidar": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "commander": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "convert-source-map": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "fs-readdir-recursive": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "output-file-sync": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "slash": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "v8flags": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz" + }, + "dependencies": { + "babel-code-frame": { + "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz" + } + }, + "babel-core": { + "version": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "babel-generator": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", + "babel-helpers": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-register": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "convert-source-map": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "json5": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "private": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "slash": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + } + }, + "babel-generator": { + "version": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", + "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", + "dev": true, + "requires": { + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "detect-indent": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "jsesc": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "trim-right": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" + } + }, + "babel-register": { + "version": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", + "home-or-tmp": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "source-map-support": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz" + }, + "dependencies": { + "core-js": { + "version": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", + "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=", + "dev": true + } + } + }, + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz" + } + }, + "babel-template": { + "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-traverse": { + "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "globals": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "to-fast-properties": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz" + } + }, + "babylon": { + "version": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=", + "dev": true + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=", + "dev": true + } + } + }, + "babel-code-frame": { + "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz" + } + }, + "babel-core": { + "version": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz", + "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "babel-generator": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz", + "babel-helpers": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-register": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", + "convert-source-map": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "json5": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "private": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "slash": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + } + }, + "babel-generator": { + "version": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz", + "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=", + "dev": true, + "requires": { + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "detect-indent": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "jsesc": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "trim-right": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-call-delegate": { + "version": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-define-map": { + "version": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + }, + "dependencies": { + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz" + } + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "to-fast-properties": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz" + } + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=", + "dev": true + } + } + }, + "babel-helper-explode-assignable-expression": { + "version": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-function-name": { + "version": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-get-function-arity": { + "version": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-hoist-variables": { + "version": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-optimise-call-expression": { + "version": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-regex": { + "version": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + }, + "dependencies": { + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz" + } + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "to-fast-properties": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz" + } + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=", + "dev": true + } + } + }, + "babel-helper-remap-async-to-generator": { + "version": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helper-replace-supers": { + "version": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-helpers": { + "version": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz" + } + }, + "babel-messages": { + "version": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-to-generator": { + "version": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "babel-plugin-syntax-async-functions": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + }, + "dependencies": { + "babel-code-frame": { + "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz" + } + }, + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz" + } + }, + "babel-template": { + "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-traverse": { + "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "globals": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "to-fast-properties": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz" + } + }, + "babylon": { + "version": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=", + "dev": true + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "babel-helper-function-name": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "babel-helper-optimise-call-expression": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "babel-helper-replace-supers": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz" + }, + "dependencies": { + "babel-code-frame": { + "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz" + } + }, + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz" + } + }, + "babel-template": { + "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-traverse": { + "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "globals": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "to-fast-properties": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz" + } + }, + "babylon": { + "version": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=", + "dev": true + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "babel-helper-get-function-arity": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "regexpu-core": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "babel-plugin-syntax-exponentiation-operator": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz" + } + }, + "babel-plugin-transform-regenerator": { + "version": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz" + } + }, + "babel-polyfill": { + "version": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz" + }, + "dependencies": { + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz" + }, + "dependencies": { + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=", + "dev": true + } + } + }, + "core-js": { + "version": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", + "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=", + "dev": true + } + } + }, + "babel-preset-env": { + "version": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.0.tgz", + "integrity": "sha1-LeHHgqeAoKXWBdGZyVdZbaQ8ROQ=", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "babel-plugin-syntax-trailing-function-commas": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "babel-plugin-transform-async-to-generator": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "babel-plugin-transform-es2015-arrow-functions": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "babel-plugin-transform-es2015-block-scoped-functions": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "babel-plugin-transform-es2015-block-scoping": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "babel-plugin-transform-es2015-classes": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "babel-plugin-transform-es2015-computed-properties": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "babel-plugin-transform-es2015-destructuring": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "babel-plugin-transform-es2015-duplicate-keys": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "babel-plugin-transform-es2015-for-of": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "babel-plugin-transform-es2015-function-name": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "babel-plugin-transform-es2015-literals": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "babel-plugin-transform-es2015-modules-amd": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "babel-plugin-transform-es2015-modules-commonjs": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "babel-plugin-transform-es2015-modules-systemjs": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "babel-plugin-transform-es2015-modules-umd": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "babel-plugin-transform-es2015-object-super": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "babel-plugin-transform-es2015-parameters": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "babel-plugin-transform-es2015-shorthand-properties": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "babel-plugin-transform-es2015-spread": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "babel-plugin-transform-es2015-sticky-regex": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "babel-plugin-transform-es2015-template-literals": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "babel-plugin-transform-es2015-typeof-symbol": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "babel-plugin-transform-es2015-unicode-regex": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "babel-plugin-transform-exponentiation-operator": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "babel-plugin-transform-regenerator": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "browserslist": "https://registry.npmjs.org/browserslist/-/browserslist-2.4.0.tgz", + "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "semver": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz" + } + }, + "babel-preset-es2016": { + "version": "https://registry.npmjs.org/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz", + "integrity": "sha1-+QC/k+LrwNJ235uKtZck6/2Vn4s=", + "dev": true, + "requires": { + "babel-plugin-transform-exponentiation-operator": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz" + } + }, + "babel-register": { + "version": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", + "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=", + "dev": true, + "requires": { + "babel-core": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "home-or-tmp": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "source-map-support": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz" + } + }, + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", + "dev": true, + "requires": { + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz" + } + }, + "babel-template": { + "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-traverse": { + "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "babylon": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "globals": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "to-fast-properties": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz" + } + }, + "babylon": { + "version": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", + "integrity": "sha1-Pot0AriNIsNCPhN6FXeIOxX/hpo=", + "dev": true + }, + "backo2": { + "version": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-arraybuffer": { + "version": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha1-qRlH2h9KUW6jjltOwOw3c2deCIY=", + "dev": true + }, + "base64id": { + "version": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + } + }, + "better-assert": { + "version": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz" + } + }, + "binary-extensions": { + "version": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", + "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", + "dev": true + }, + "blob": { + "version": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "dev": true + }, + "bluebird": { + "version": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", + "dev": true + }, + "bn.js": { + "version": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=", + "dev": true + }, + "body-parser": { + "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", + "dev": true, + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", + "raw-body": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + } + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", + "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=", + "dev": true + } + } + }, + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + } + }, + "bower": { + "version": "https://registry.npmjs.org/bower/-/bower-1.8.2.tgz", + "integrity": "sha1-rfU1KcjUrwLvJPuNU0HBQZ0z4vc=", + "dev": true + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + }, + "braces": { + "version": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "preserve": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "repeat-element": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" + } + }, + "brorand": { + "version": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.2.tgz", + "integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=", + "dev": true, + "requires": { + "JSONStream": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "combine-source-map": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", + "defined": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "umd": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz" + } + }, + "browser-resolve": { + "version": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", + "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", + "dev": true, + "requires": { + "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + } + }, + "browserify": { + "version": "https://registry.npmjs.org/browserify/-/browserify-14.4.0.tgz", + "integrity": "sha1-CJo0Y69Y0OSNjNQHCz90ZU1avKk=", + "dev": true, + "requires": { + "JSONStream": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "assert": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "browser-pack": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.2.tgz", + "browser-resolve": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", + "browserify-zlib": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "buffer": "https://registry.npmjs.org/buffer/-/buffer-5.0.7.tgz", + "cached-path-relative": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", + "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "console-browserify": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "constants-browserify": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "crypto-browserify": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", + "defined": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "deps-sort": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "domain-browser": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "duplexer2": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "events": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "has": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "htmlescape": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "https-browserify": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "insert-module-globals": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", + "labeled-stream-splicer": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", + "module-deps": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", + "os-browserify": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "parents": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "path-browserify": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "process": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "querystring-es3": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "read-only-stream": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "shasum": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "shell-quote": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "stream-browserify": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "stream-http": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "subarg": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "syntax-error": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "timers-browserify": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "tty-browserify": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "url": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "util": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "vm-browserify": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "browserify-aes": { + "version": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.8.tgz", + "integrity": "sha1-yPo7G3WFu3unfFVgtgmW3extUwk=", + "dev": true, + "requires": { + "buffer-xor": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "evp_bytestokey": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "browserify-cache-api": { + "version": "https://registry.npmjs.org/browserify-cache-api/-/browserify-cache-api-3.0.1.tgz", + "integrity": "sha1-liR+hT8Gj9bg1FzHPwuyzZd47wI=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "browserify-cipher": { + "version": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "dev": true, + "requires": { + "browserify-aes": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.8.tgz", + "browserify-des": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "evp_bytestokey": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz" + } + }, + "browserify-des": { + "version": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "dev": true, + "requires": { + "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "des.js": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + } + }, + "browserify-incremental": { + "version": "https://registry.npmjs.org/browserify-incremental/-/browserify-incremental-3.1.1.tgz", + "integrity": "sha1-BxPLdYckemMqnwjPG9FpuHi2Koo=", + "dev": true, + "requires": { + "JSONStream": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz", + "browserify-cache-api": "https://registry.npmjs.org/browserify-cache-api/-/browserify-cache-api-3.0.1.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + }, + "dependencies": { + "JSONStream": { + "version": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz", + "integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=", + "dev": true, + "requires": { + "jsonparse": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + } + }, + "jsonparse": { + "version": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + } + } + }, + "browserify-rsa": { + "version": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz" + } + }, + "browserify-sign": { + "version": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "browserify-rsa": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "create-hmac": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "elliptic": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "parse-asn1": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz" + } + }, + "browserify-zlib": { + "version": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz" + } + }, + "browserslist": { + "version": "https://registry.npmjs.org/browserslist/-/browserslist-2.4.0.tgz", + "integrity": "sha1-aT7pPQHmZGimNI2lSY4BH1ePh/g=", + "dev": true, + "requires": { + "caniuse-lite": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000739.tgz", + "electron-to-chromium": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.23.tgz" + } + }, + "buffer": { + "version": "https://registry.npmjs.org/buffer/-/buffer-5.0.7.tgz", + "integrity": "sha1-VwopC2Jc8mAykMEUkiPSfM8E25c=", + "dev": true, + "requires": { + "base64-js": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "ieee754": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz" + } + }, + "buffer-xor": { + "version": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=", + "dev": true + }, + "cached-path-relative": { + "version": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", + "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", + "dev": true + }, + "callsite": { + "version": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camel-case": { + "version": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + } + }, + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "map-obj": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" + } + }, + "caniuse-lite": { + "version": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000739.tgz", + "integrity": "sha1-nujHAW9cUi27DAhj1Vxh77RTrpU=", + "dev": true + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "center-align": { + "version": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "lazy-cache": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" + } + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "has-ansi": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + } + }, + "change-case": { + "version": "https://registry.npmjs.org/change-case/-/change-case-3.0.1.tgz", + "integrity": "sha1-7l9a0EFa0a2egHLPSc1M+nZgpVQ=", + "dev": true, + "requires": { + "camel-case": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "constant-case": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", + "dot-case": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", + "header-case": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", + "is-lower-case": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "is-upper-case": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "lower-case": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "lower-case-first": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "param-case": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "pascal-case": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", + "path-case": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz", + "sentence-case": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", + "snake-case": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "swap-case": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "title-case": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "upper-case-first": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz" + } + }, + "chokidar": { + "version": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "async-each": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "fsevents": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", + "glob-parent": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "is-binary-path": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "is-glob": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "readdirp": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz" + } + }, + "cipher-base": { + "version": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "clean-css": { + "version": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", + "integrity": "sha1-re91sxwWD/pdcvTeZ5ZuJmDBolU=", + "dev": true, + "requires": { + "commander": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" + }, + "dependencies": { + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "dev": true, + "requires": { + "graceful-readlink": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + } + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" + } + } + } + }, + "cliui": { + "version": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "right-align": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "wordwrap": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" + } + }, + "coffee-script": { + "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", + "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=", + "dev": true + }, + "colors": { + "version": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combine-lists": { + "version": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", + "dev": true, + "requires": { + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + }, + "combine-source-map": { + "version": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", + "integrity": "sha1-CHAxKFazB6h8xKxIbzqaYq7MwJ4=", + "dev": true, + "requires": { + "convert-source-map": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "inline-source-map": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "lodash.memoize": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + }, + "dependencies": { + "convert-source-map": { + "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + } + } + }, + "combined-stream": { + "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + } + }, + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", + "dev": true + }, + "component-bind": { + "version": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", + "dev": true + }, + "component-inherit": { + "version": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "typedarray": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + }, + "dependencies": { + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "connect": { + "version": "https://registry.npmjs.org/connect/-/connect-3.6.5.tgz", + "integrity": "sha1-+43ee6B2OHfQ7J352sC0tA5yx9o=", + "dev": true, + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "finalhandler": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "utils-merge": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + } + } + }, + "console-browserify": { + "version": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz" + } + }, + "constant-case": { + "version": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", + "integrity": "sha1-QXV2TTidP6nI7NKRhu1gBSQ7akY=", + "dev": true, + "requires": { + "snake-case": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + } + }, + "constants-browserify": { + "version": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-type": { + "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=", + "dev": true + }, + "convert-source-map": { + "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", + "dev": true + }, + "cookie": { + "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "core-js": { + "version": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=", + "dev": true + }, + "core-util-is": { + "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-ecdh": { + "version": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "elliptic": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz" + } + }, + "create-hash": { + "version": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "dev": true, + "requires": { + "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "ripemd160": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "sha.js": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz" + } + }, + "create-hmac": { + "version": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "dev": true, + "requires": { + "cipher-base": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "ripemd160": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "sha.js": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz" + } + }, + "cross-spawn": { + "version": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-0.2.9.tgz", + "integrity": "sha1-vWf5bAfvtjA7f+lMHpefiEeOCjk=", + "dev": true, + "requires": { + "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + } + }, + "cryptiles": { + "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + } + }, + "crypto-browserify": { + "version": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", + "integrity": "sha1-lIlF78Z1ekANbl5a9HGU0QBkJ58=", + "dev": true, + "requires": { + "browserify-cipher": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "browserify-sign": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "create-ecdh": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "create-hmac": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "diffie-hellman": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "pbkdf2": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", + "public-encrypt": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz" + } + }, + "currently-unhandled": { + "version": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz" + } + }, + "custom-event": { + "version": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "dargs": { + "version": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", + "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", + "dev": true, + "requires": { + "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + } + }, + "dashdash": { + "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "date-now": { + "version": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "meow": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz" + } + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "decamelize": { + "version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "object-keys": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz" + } + }, + "defined": { + "version": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "delayed-stream": { + "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", + "dev": true + }, + "deps-sort": { + "version": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", + "dev": true, + "requires": { + "JSONStream": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "shasum": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "subarg": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz" + } + }, + "des.js": { + "version": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz" + } + }, + "detect-indent": { + "version": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz" + } + }, + "detective": { + "version": "https://registry.npmjs.org/detective/-/detective-4.5.0.tgz", + "integrity": "sha1-blqMaybmx6JUsca210kNmOyR7dE=", + "dev": true, + "requires": { + "acorn": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "defined": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz" + } + }, + "di": { + "version": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diffie-hellman": { + "version": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "miller-rabin": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz" + } + }, + "dom-serialize": { + "version": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "ent": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "void-elements": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz" + } + }, + "domain-browser": { + "version": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "dot-case": { + "version": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", + "integrity": "sha1-NNzzf1Co6TwrO8qLt/uRVcfaO+4=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz" + } + }, + "duplexer2": { + "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" + } + }, + "ecc-jsbn": { + "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" + } + }, + "ee-first": { + "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.23.tgz", + "integrity": "sha1-5maKsYy2mvuPV3yKn8I9ACeIvnQ=", + "dev": true + }, + "elliptic": { + "version": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "brorand": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "hash.js": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "hmac-drbg": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "minimalistic-crypto-utils": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" + } + }, + "encodeurl": { + "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", + "dev": true + }, + "engine.io": { + "version": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz", + "integrity": "sha1-jef5eJXSDTm4X4ju7nd7K9QrE9Q=", + "dev": true, + "requires": { + "accepts": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "base64id": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "engine.io-parser": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", + "ws": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-client": { + "version": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz", + "integrity": "sha1-F5jtk0USRkU9TG9jXXogH+lA1as=", + "dev": true, + "requires": { + "component-emitter": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "component-inherit": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "engine.io-parser": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", + "has-cors": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "indexof": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "parsejson": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "parseqs": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "parseuri": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "ws": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", + "xmlhttprequest-ssl": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "yeast": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz" + }, + "dependencies": { + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", + "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=", + "dev": true, + "requires": { + "after": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "arraybuffer.slice": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "base64-arraybuffer": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "blob": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "has-binary": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "wtf-8": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz" + } + }, + "ent": { + "version": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "error-ex": { + "version": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + } + }, + "es6-promise": { + "version": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", + "integrity": "sha1-eILzCt3lskDM+n99eMVIMwlRrkI=", + "dev": true + }, + "escape-html": { + "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esutils": { + "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "eventemitter2": { + "version": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "eventemitter3": { + "version": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "dev": true + }, + "events": { + "version": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", + "dev": true, + "requires": { + "md5.js": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "exit": { + "version": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-braces": { + "version": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", + "dev": true, + "requires": { + "array-slice": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "array-unique": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "braces": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz" + }, + "dependencies": { + "braces": { + "version": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "dev": true, + "requires": { + "expand-range": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz" + } + }, + "expand-range": { + "version": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "dev": true, + "requires": { + "is-number": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "repeat-string": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz" + } + }, + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + }, + "repeat-string": { + "version": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "dev": true + } + } + }, + "expand-brackets": { + "version": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz" + } + }, + "expand-range": { + "version": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz" + } + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extglob": { + "version": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" + } + }, + "extract-zip": { + "version": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", + "integrity": "sha1-ksz22B73Cp+kwXRxFMzvbYaIpsQ=", + "dev": true, + "requires": { + "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "yauzl": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz" + }, + "dependencies": { + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "integrity": "sha1-U/fUPFHF5D+ByP3QMyHGMb5o1hE=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "typedarray": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + } + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", + "dev": true + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "extsprintf": { + "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "dev": true + }, + "faye-websocket": { + "version": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz" + } + }, + "fd-slicer": { + "version": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" + } + }, + "figures": { + "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + } + }, + "file-sync-cmp": { + "version": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", + "dev": true + }, + "filename-regex": { + "version": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "isobject": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "randomatic": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "repeat-element": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "repeat-string": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + } + }, + "finalhandler": { + "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", + "dev": true, + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + } + } + }, + "find-up": { + "version": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "findup-sync": { + "version": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + } + } + }, + "for-in": { + "version": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" + } + }, + "foreach": { + "version": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "forever-agent": { + "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "requires": { + "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz" + } + }, + "fs-extra": { + "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "jsonfile": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "klaw": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz" + } + }, + "fs-readdir-recursive": { + "version": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz", + "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA=", + "dev": true + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", + "integrity": "sha1-MoK3E/s62A7eDp/PRhG1qm/AM/Q=", + "dev": true, + "optional": true, + "requires": { + "nan": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "node-pre-gyp": "0.6.36" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "aproba": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "optional": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "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" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": 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" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "optional": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.36", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", + "dev": true, + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==", + "dev": 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", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", + "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "optional": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } + }, + "function-bind": { + "version": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", + "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", + "dev": true + }, + "gaze": { + "version": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", + "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", + "dev": true, + "requires": { + "globule": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz" + } + }, + "generate-function": { + "version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + } + }, + "get-stdin": { + "version": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "getobject": { + "version": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "getpass": { + "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "glob-base": { + "version": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "is-glob": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" + } + }, + "glob-parent": { + "version": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" + } + }, + "globals": { + "version": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "dev": true + }, + "globule": { + "version": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", + "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + } + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "grunt": { + "version": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz", + "integrity": "sha1-6HeHZOlEsY8yuw8QuQeEdcnftWs=", + "dev": true, + "requires": { + "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", + "dateformat": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "eventemitter2": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "findup-sync": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "grunt-cli": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "grunt-known-options": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", + "grunt-legacy-log": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", + "grunt-legacy-util": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "js-yaml": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "nopt": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "grunt-cli": { + "version": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", + "dev": true, + "requires": { + "findup-sync": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "grunt-known-options": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", + "nopt": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + } + } + } + }, + "grunt-angular-templates": { + "version": "https://registry.npmjs.org/grunt-angular-templates/-/grunt-angular-templates-1.1.0.tgz", + "integrity": "sha1-EJYDorlf8BAZtxjHA0EmjwnYvhk=", + "dev": true, + "requires": { + "html-minifier": "https://registry.npmjs.org/html-minifier/-/html-minifier-2.1.7.tgz" + } + }, + "grunt-babel": { + "version": "https://registry.npmjs.org/grunt-babel/-/grunt-babel-6.0.0.tgz", + "integrity": "sha1-N4GJtIfeEWjExKn8iN1gBbNd+WA=", + "dev": true, + "requires": { + "babel-core": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz" + } + }, + "grunt-browserify": { + "version": "https://registry.npmjs.org/grunt-browserify/-/grunt-browserify-5.2.0.tgz", + "integrity": "sha1-N65E+9x199gelyUmThGwXmJTfEY=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "browserify": "https://registry.npmjs.org/browserify/-/browserify-14.4.0.tgz", + "browserify-incremental": "https://registry.npmjs.org/browserify-incremental/-/browserify-incremental-3.1.1.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "watchify": "https://registry.npmjs.org/watchify/-/watchify-3.9.0.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "integrity": "sha1-hDGQ/WtzV6C54clW7d3V7IRitU0=", + "dev": true, + "requires": { + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + } + } + }, + "grunt-contrib-concat": { + "version": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-1.0.1.tgz", + "integrity": "sha1-YVCYYwhOhx1+ht5IwBUlntl3Rb0=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + } + }, + "grunt-contrib-copy": { + "version": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "file-sync-cmp": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz" + } + }, + "grunt-contrib-cssmin": { + "version": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-1.0.2.tgz", + "integrity": "sha1-FzTL09hMpzZHWLflj/GOUqpgu3Y=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "clean-css": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", + "maxmin": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz" + } + }, + "grunt-contrib-sass": { + "version": "https://registry.npmjs.org/grunt-contrib-sass/-/grunt-contrib-sass-1.0.0.tgz", + "integrity": "sha1-gGg4JRy8DhqU1k1RXN00z2dNcBs=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "cross-spawn": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-0.2.9.tgz", + "dargs": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", + "which": "https://registry.npmjs.org/which/-/which-1.2.14.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + } + } + }, + "grunt-contrib-uglify": { + "version": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-2.3.0.tgz", + "integrity": "sha1-s9AmDr3WzvoS/y+Onh4ln33kIW8=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "maxmin": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz", + "object.assign": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", + "uglify-js": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "uri-path": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz" + }, + "dependencies": { + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "uglify-to-browserify": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "yargs": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz" + } + } + } + }, + "grunt-contrib-watch": { + "version": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", + "integrity": "sha1-hKGnodar0m7VaEE0lscxM+mQAY8=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "gaze": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "tiny-lr": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "grunt-haml2html": { + "version": "https://registry.npmjs.org/grunt-haml2html/-/grunt-haml2html-0.3.1.tgz", + "integrity": "sha1-b7W9R+JQ3Q9VzS76kj7kMQFEFCM=", + "dev": true, + "requires": { + "dargs": "https://registry.npmjs.org/dargs/-/dargs-0.1.0.tgz" + }, + "dependencies": { + "dargs": { + "version": "https://registry.npmjs.org/dargs/-/dargs-0.1.0.tgz", + "integrity": "sha1-I2Stn0Qfl23NX+mWHiFxVmWl48M=", + "dev": true + } + } + }, + "grunt-known-options": { + "version": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", + "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", + "dev": true + }, + "grunt-legacy-log": { + "version": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", + "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", + "dev": true, + "requires": { + "colors": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "grunt-legacy-log-utils": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", + "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", + "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", + "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "getobject": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", + "which": "https://registry.npmjs.org/which/-/which-1.2.14.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", + "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", + "dev": true + } + } + }, + "grunt-newer": { + "version": "https://registry.npmjs.org/grunt-newer/-/grunt-newer-1.3.0.tgz", + "integrity": "sha1-g8y3od2ny9irI7BZAk6+YUrS80I=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz" + }, + "dependencies": { + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" + } + } + } + }, + "grunt-ng-annotate": { + "version": "https://registry.npmjs.org/grunt-ng-annotate/-/grunt-ng-annotate-3.0.0.tgz", + "integrity": "sha1-dqKiGha6Y+Vve+G3XuXErZMCeTk=", + "dev": true, + "requires": { + "lodash.clonedeep": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "ng-annotate": "https://registry.npmjs.org/ng-annotate/-/ng-annotate-1.2.2.tgz" + } + }, + "gzip-size": { + "version": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", + "integrity": "sha1-Zs+LEBBHInuVus5uodoMF37Vwi8=", + "dev": true, + "requires": { + "browserify-zlib": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz" + } + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "commander": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "is-my-json-valid": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "has": { + "version": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz" + } + }, + "has-ansi": { + "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + } + }, + "has-binary": { + "version": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", + "dev": true, + "requires": { + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "has-cors": { + "version": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "hash-base": { + "version": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + } + }, + "hash.js": { + "version": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha1-NA3tvmKQGHFRweodd3o0SJNd+EY=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz" + } + }, + "hasha": { + "version": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", + "dev": true, + "requires": { + "is-stream": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "hawk": { + "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "sntp": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + } + }, + "he": { + "version": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "header-case": { + "version": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", + "integrity": "sha1-lTWXMZfBRLCWE81l0xfvGZY70C0=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + } + }, + "hmac-drbg": { + "version": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "minimalistic-assert": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "minimalistic-crypto-utils": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" + } + }, + "hoek": { + "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "home-or-tmp": { + "version": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "os-tmpdir": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + } + }, + "hooker": { + "version": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "hosted-git-info": { + "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=", + "dev": true + }, + "html-minifier": { + "version": "https://registry.npmjs.org/html-minifier/-/html-minifier-2.1.7.tgz", + "integrity": "sha1-kFHW/LvPIU7TB+GtdPQyu5rWVcw=", + "dev": true, + "requires": { + "change-case": "https://registry.npmjs.org/change-case/-/change-case-3.0.1.tgz", + "clean-css": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", + "commander": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "he": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "ncname": "https://registry.npmjs.org/ncname/-/ncname-1.0.0.tgz", + "relateurl": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "uglify-js": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.4.tgz" + }, + "dependencies": { + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + } + } + } + }, + "htmlescape": { + "version": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", + "dev": true + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" + } + }, + "http-proxy": { + "version": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", + "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", + "dev": true, + "requires": { + "eventemitter3": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "requires-port": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + } + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "jsprim": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz" + } + }, + "https-browserify": { + "version": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=", + "dev": true + }, + "ieee754": { + "version": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "indent-string": { + "version": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz" + } + }, + "indexof": { + "version": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inline-source-map": { + "version": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "dev": true, + "requires": { + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + } + }, + "insert-module-globals": { + "version": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", + "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=", + "dev": true, + "requires": { + "JSONStream": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "combine-source-map": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", + "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "lexical-scope": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "process": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "invariant": { + "version": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "dev": true, + "requires": { + "loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz" + } + }, + "is-arrayish": { + "version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz" + } + }, + "is-buffer": { + "version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "dev": true + }, + "is-builtin-module": { + "version": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" + } + }, + "is-dotfile": { + "version": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" + } + }, + "is-extendable": { + "version": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + } + }, + "is-glob": { + "version": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" + } + }, + "is-lower-case": { + "version": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "integrity": "sha1-fhR75HaNxGbbO/shzGCzHmrWk5M=", + "dev": true, + "requires": { + "lower-case": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz" + } + }, + "is-my-json-valid": { + "version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", + "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=", + "dev": true, + "requires": { + "generate-function": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "generate-object-property": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "jsonpointer": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" + } + }, + "is-posix-bracket": { + "version": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-property": { + "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-stream": { + "version": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-upper-case": { + "version": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "integrity": "sha1-jQsfp+eTOh5YSDYA7H2WYcuvdW8=", + "dev": true, + "requires": { + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + } + }, + "is-utf8": { + "version": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", + "dev": true + }, + "isexe": { + "version": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + } + }, + "isstream": { + "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jasmine": { + "version": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "jasmine-core": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz" + } + }, + "jasmine-core": { + "version": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + }, + "js-tokens": { + "version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", + "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", + "dev": true, + "requires": { + "argparse": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "esprima": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz" + } + }, + "jsbn": { + "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jsesc": { + "version": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-schema": { + "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-stable-stringify": { + "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "requires": { + "jsonify": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + } + }, + "json-stringify-safe": { + "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonfile": { + "version": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz" + } + }, + "jsonify": { + "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsonpointer": { + "version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dev": true, + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "json-schema": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "verror": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "karma": { + "version": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", + "integrity": "sha1-hcwI6eCiLXzpzKN8ShvoJPaisa4=", + "dev": true, + "requires": { + "bluebird": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "body-parser": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "chokidar": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "colors": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "combine-lists": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "connect": "https://registry.npmjs.org/connect/-/connect-3.6.5.tgz", + "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "di": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "dom-serialize": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "expand-braces": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "http-proxy": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", + "isbinaryfile": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "log4js": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "mime": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "optimist": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "qjobs": "https://registry.npmjs.org/qjobs/-/qjobs-1.1.5.tgz", + "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "socket.io": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "tmp": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "useragent": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz" + }, + "dependencies": { + "body-parser": { + "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "raw-body": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" + } + }, + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "content-type": { + "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" + } + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=", + "dev": true + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=", + "dev": true + }, + "raw-body": { + "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + } + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" + } + } + } + }, + "karma-cli": { + "version": "https://registry.npmjs.org/karma-cli/-/karma-cli-1.0.1.tgz", + "integrity": "sha1-rmw8WKMTodALRRZMRVubhs4X+WA=", + "dev": true, + "requires": { + "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + } + }, + "karma-jasmine": { + "version": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.0.tgz", + "integrity": "sha1-IuTAa/mhguUpTR9wXjczgRuBCs8=", + "dev": true + }, + "karma-phantomjs-launcher": { + "version": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz", + "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=", + "dev": true, + "requires": { + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "phantomjs-prebuilt": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.14.tgz" + } + }, + "kew": { + "version": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", + "dev": true + }, + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz" + } + }, + "klaw": { + "version": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz" + } + }, + "labeled-stream-splicer": { + "version": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", + "integrity": "sha1-pS4dE4AkwAuGscDJH2d5GLiuClk=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "stream-splicer": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz" + }, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "lazy-cache": { + "version": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lexical-scope": { + "version": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", + "dev": true, + "requires": { + "astw": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz" + } + }, + "livereload-js": { + "version": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz", + "integrity": "sha1-bIclfmSKtHW8JOoldFftzB+NC8I=", + "dev": true + }, + "load-json-file": { + "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "parse-json": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "strip-bom": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz" + } + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "lodash.clonedeep": { + "version": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.memoize": { + "version": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "log4js": { + "version": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", + "dev": true, + "requires": { + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "semver": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz" + }, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "longest": { + "version": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "requires": { + "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz" + } + }, + "loud-rejection": { + "version": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "signal-exit": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz" + } + }, + "lower-case": { + "version": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lower-case-first": { + "version": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "integrity": "sha1-5dp8JvKacHO+AtUrrJmA5ZIq36E=", + "dev": true, + "requires": { + "lower-case": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz" + } + }, + "lru-cache": { + "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "map-obj": { + "version": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "maxmin": { + "version": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz", + "integrity": "sha1-cTZehKmd2Piz99X94vANHn9zvmE=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "figures": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "gzip-size": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", + "pretty-bytes": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz" + } + }, + "md5.js": { + "version": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "dev": true, + "requires": { + "hash-base": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + }, + "dependencies": { + "hash-base": { + "version": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + } + } + }, + "media-typer": { + "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "meow": { + "version": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "decamelize": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "loud-rejection": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "map-obj": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "minimist": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "normalize-package-data": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "read-pkg-up": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "redent": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "trim-newlines": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" + }, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "micromatch": { + "version": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "array-unique": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "braces": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "expand-brackets": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "extglob": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "filename-regex": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "is-extglob": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "is-glob": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "normalize-path": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "object.omit": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "parse-glob": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "regex-cache": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz" + } + }, + "miller-rabin": { + "version": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "brorand": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" + } + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=", + "dev": true + }, + "mime-db": { + "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", + "dev": true + }, + "mime-types": { + "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "dev": true, + "requires": { + "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz" + } + }, + "minimalistic-assert": { + "version": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" + } + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "module-deps": { + "version": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", + "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", + "dev": true, + "requires": { + "JSONStream": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "browser-resolve": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", + "cached-path-relative": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", + "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "defined": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "detective": "https://registry.npmjs.org/detective/-/detective-4.5.0.tgz", + "duplexer2": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "parents": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "stream-combiner2": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "subarg": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", + "dev": true, + "optional": true + }, + "ncname": { + "version": "https://registry.npmjs.org/ncname/-/ncname-1.0.0.tgz", + "integrity": "sha1-W1etGLHKCShk72Kwse2BlPODtxw=", + "dev": true, + "requires": { + "xml-char-classes": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz" + } + }, + "negotiator": { + "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "ng-annotate": { + "version": "https://registry.npmjs.org/ng-annotate/-/ng-annotate-1.2.2.tgz", + "integrity": "sha1-3D/FG6Cy+LOF2+BH9NoG9YCh/WE=", + "dev": true, + "requires": { + "acorn": "https://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz", + "alter": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz", + "convert-source-map": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "optimist": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "ordered-ast-traverse": "https://registry.npmjs.org/ordered-ast-traverse/-/ordered-ast-traverse-1.1.1.tgz", + "simple-fmt": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz", + "simple-is": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "stable": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz", + "stringmap": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz", + "stringset": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz", + "tryor": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz" + }, + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz", + "integrity": "sha1-6x9FtKQ/ox0DcBpexG87Umc+kO4=", + "dev": true + }, + "convert-source-map": { + "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + } + } + }, + "no-case": { + "version": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "integrity": "sha1-euuhxzpSGEJlVUt9wDuvcg34AIE=", + "dev": true, + "requires": { + "lower-case": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz" + } + }, + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz" + } + }, + "normalize-package-data": { + "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", + "dev": true, + "requires": { + "hosted-git-info": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "is-builtin-module": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "semver": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "validate-npm-package-license": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz" + } + }, + "normalize-path": { + "version": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz" + } + }, + "number-is-nan": { + "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "dev": true + }, + "object.assign": { + "version": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", + "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=", + "dev": true, + "requires": { + "define-properties": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "function-bind": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", + "object-keys": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz" + } + }, + "object.omit": { + "version": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "is-extendable": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" + } + }, + "on-finished": { + "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + } + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "wordwrap": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" + } + }, + "options": { + "version": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "dev": true + }, + "ordered-ast-traverse": { + "version": "https://registry.npmjs.org/ordered-ast-traverse/-/ordered-ast-traverse-1.1.1.tgz", + "integrity": "sha1-aEOhcLwO7otSDMjdwd3TqjD6BXw=", + "dev": true, + "requires": { + "ordered-esprima-props": "https://registry.npmjs.org/ordered-esprima-props/-/ordered-esprima-props-1.1.0.tgz" + } + }, + "ordered-esprima-props": { + "version": "https://registry.npmjs.org/ordered-esprima-props/-/ordered-esprima-props-1.1.0.tgz", + "integrity": "sha1-qYJwht9fAQqmDpvQK24DNc6i/8s=", + "dev": true + }, + "os-browserify": { + "version": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "os-homedir": { + "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "outpipe": { + "version": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "integrity": "sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I=", + "dev": true, + "requires": { + "shell-quote": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz" + } + }, + "output-file-sync": { + "version": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", + "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + } + }, + "pako": { + "version": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "param-case": { + "version": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz" + } + }, + "parents": { + "version": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz" + } + }, + "parse-asn1": { + "version": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "dev": true, + "requires": { + "asn1.js": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "browserify-aes": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.8.tgz", + "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "evp_bytestokey": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "pbkdf2": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz" + } + }, + "parse-glob": { + "version": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "is-dotfile": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "is-extglob": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "is-glob": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" + } + }, + "parse-json": { + "version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz" + } + }, + "parsejson": { + "version": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "dev": true, + "requires": { + "better-assert": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz" + } + }, + "parseqs": { + "version": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz" + } + }, + "parseuri": { + "version": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz" + } + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=", + "dev": true + }, + "pascal-case": { + "version": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", + "integrity": "sha1-LVeNNFX2YNpl7KGO+VtODekSdh4=", + "dev": true, + "requires": { + "camel-case": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "upper-case-first": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz" + } + }, + "path-browserify": { + "version": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-case": { + "version": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz", + "integrity": "sha1-lLgDfDctP+KQbkZbtF4l0ibo7qU=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz" + } + }, + "path-exists": { + "version": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-platform": { + "version": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "path-type": { + "version": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "pbkdf2": { + "version": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", + "integrity": "sha1-o14TxkeZsGzhUyD0WcIw5o5zut4=", + "dev": true, + "requires": { + "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "create-hmac": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "ripemd160": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "sha.js": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz" + } + }, + "pend": { + "version": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "phantomjs-prebuilt": { + "version": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.14.tgz", + "integrity": "sha1-1T0xH8+30dCN2yQBRVjxGIxRbaA=", + "dev": true, + "requires": { + "es6-promise": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", + "extract-zip": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", + "fs-extra": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "hasha": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "kew": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "progress": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "request": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "request-progress": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "which": "https://registry.npmjs.org/which/-/which-1.2.14.tgz" + } + }, + "pify": { + "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + } + }, + "preserve": { + "version": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-bytes": { + "version": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "meow": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz" + } + }, + "private": { + "version": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=", + "dev": true + }, + "process": { + "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "public-encrypt": { + "version": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "dev": true, + "requires": { + "bn.js": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "browserify-rsa": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "create-hash": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "parse-asn1": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "randombytes": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz" + } + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qjobs": { + "version": "https://registry.npmjs.org/qjobs/-/qjobs-1.1.5.tgz", + "integrity": "sha1-ZZ3p8s+NzCehSBJ28gU3cnI4LnM=", + "dev": true + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", + "integrity": "sha1-TZMuXH6kEcynajEtOaYGIA/VDNk=", + "dev": true + }, + "querystring": { + "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randomatic": { + "version": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", + "dev": true, + "requires": { + "is-number": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz" + }, + "dependencies": { + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" + }, + "dependencies": { + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz" + } + } + } + }, + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz" + } + } + } + }, + "randombytes": { + "version": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", + "integrity": "sha1-3ACaJGuNCaF3tLegrne8Vw9LG3k=", + "dev": true, + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "range-parser": { + "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "dev": true, + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "dependencies": { + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + } + } + }, + "read-only-stream": { + "version": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "dev": true, + "requires": { + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" + } + }, + "read-pkg": { + "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "normalize-package-data": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "path-type": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" + } + }, + "read-pkg-up": { + "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "read-pkg": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz" + } + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "dev": true, + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "readdirp": { + "version": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "set-immediate-shim": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz" + } + }, + "redent": { + "version": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "strip-indent": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz" + } + }, + "regenerate": { + "version": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha1-DDNtOYBVPXVcObWGrjsgqknIK38=", + "dev": true + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + }, + "regenerator-transform": { + "version": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha1-HkmWg3Ix2ot/PPQRTXG1aRoGgN0=", + "dev": true, + "requires": { + "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "private": "https://registry.npmjs.org/private/-/private-0.1.7.tgz" + } + }, + "regex-cache": { + "version": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", + "dev": true, + "requires": { + "is-equal-shallow": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" + } + }, + "regexpu-core": { + "version": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "regjsgen": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "regjsparser": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz" + } + }, + "regjsgen": { + "version": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + }, + "dependencies": { + "jsesc": { + "version": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "relateurl": { + "version": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz" + } + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "dev": true, + "requires": { + "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" + }, + "dependencies": { + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "dev": true + } + } + }, + "request-progress": { + "version": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", + "dev": true, + "requires": { + "throttleit": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz" + } + }, + "requires-port": { + "version": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "right-align": { + "version": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz" + } + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + }, + "ripemd160": { + "version": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "dev": true, + "requires": { + "hash-base": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + } + }, + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", + "dev": true + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "sentence-case": { + "version": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", + "integrity": "sha1-H24t2jnBaL+S0T+G1KkYkz9mftQ=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "upper-case-first": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz" + } + }, + "set-immediate-shim": { + "version": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + }, + "sha.js": { + "version": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", + "integrity": "sha1-mPZIgEdLdPSji42p08Dy0QRjPn0=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "shasum": { + "version": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "requires": { + "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "sha.js": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz" + } + }, + "shell-quote": { + "version": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, + "requires": { + "array-filter": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "array-map": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "array-reduce": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "jsonify": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + } + }, + "signal-exit": { + "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-fmt": { + "version": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz", + "integrity": "sha1-GRv1ZqWeZTBILLJatTtKjchcOms=", + "dev": true + }, + "simple-is": { + "version": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz", + "integrity": "sha1-Krt1qt453rXMgVzhDmGRFkhQuvA=", + "dev": true + }, + "slash": { + "version": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "sn-stylekit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1.tgz", + "integrity": "sha512-tmtTUI5Vahsl6WjGL8rvCzi4BIqKz3F7e7y7MgLXM0zvxi3+IObgwDJXEUSHboXkC1RGSEM0mq+yJlyt2DY0+A==" + }, + "snake-case": { + "version": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "integrity": "sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz" + } + }, + "sntp": { + "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + } + }, + "socket.io": { + "version": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz", + "integrity": "sha1-uK+cq6AJSeVo42nxMn6pvp6iRhs=", + "dev": true, + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "engine.io": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz", + "has-binary": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "socket.io-adapter": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", + "socket.io-client": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz", + "socket.io-parser": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", + "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", + "dev": true, + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "socket.io-parser": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-client": { + "version": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz", + "integrity": "sha1-sw6GqhDV7zVGYBwJzeR2Xjgdo3c=", + "dev": true, + "requires": { + "backo2": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "component-bind": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "component-emitter": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "engine.io-client": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz", + "has-binary": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "indexof": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "object-component": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "parseuri": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "socket.io-parser": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", + "to-array": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz" + }, + "dependencies": { + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", + "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", + "dev": true, + "requires": { + "component-emitter": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "json3": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + } + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + }, + "source-map-support": { + "version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", + "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", + "dev": true, + "requires": { + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + } + }, + "spdx-correct": { + "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz" + } + }, + "spdx-expression-parse": { + "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "sprintf-js": { + "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "requires": { + "asn1": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "bcrypt-pbkdf": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "dashdash": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "ecc-jsbn": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "getpass": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "stable": { + "version": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz", + "integrity": "sha1-kQ9dKu17Ugxud3SZwfMuE5/eyxA=", + "dev": true + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + }, + "stream-browserify": { + "version": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" + } + }, + "stream-combiner2": { + "version": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" + } + }, + "stream-http": { + "version": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", + "integrity": "sha1-QKBQ7I3DtTsz2ZCUFcAsC/Gr+60=", + "dev": true, + "requires": { + "builtin-status-codes": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "to-arraybuffer": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "stream-splicer": { + "version": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "dev": true, + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "stringmap": { + "version": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz", + "integrity": "sha1-VWwTeyWPlCuHdvWy71gqoGnX0bE=", + "dev": true + }, + "stringset": { + "version": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz", + "integrity": "sha1-7yWcTjSTRDd/zRyRPdLoSMnAQrU=", + "dev": true + }, + "stringstream": { + "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + } + }, + "strip-bom": { + "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" + } + }, + "strip-indent": { + "version": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" + } + }, + "subarg": { + "version": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + }, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "swap-case": { + "version": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "integrity": "sha1-w5IDpFhzhfrTyFCgvRvK+ggZdOM=", + "dev": true, + "requires": { + "lower-case": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + } + }, + "syntax-error": { + "version": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz", + "integrity": "sha1-HtkmbE1AvnXcVb+bsct3Biu5bKE=", + "dev": true, + "requires": { + "acorn": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz" + } + }, + "throttleit": { + "version": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, + "through": { + "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "timers-browserify": { + "version": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "https://registry.npmjs.org/process/-/process-0.11.10.tgz" + } + }, + "tiny-lr": { + "version": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", + "integrity": "sha1-s/26gC5dVqM8L28QeUsy5Hescp0=", + "dev": true, + "requires": { + "body-parser": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "faye-websocket": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "livereload-js": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz" + }, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "title-case": { + "version": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", + "integrity": "sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o=", + "dev": true, + "requires": { + "no-case": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + } + }, + "tmp": { + "version": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + } + }, + "to-array": { + "version": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "tough-cookie": { + "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dev": true, + "requires": { + "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + } + }, + "trim-newlines": { + "version": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tryor": { + "version": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz", + "integrity": "sha1-gUXkynyv9ArN48z5Rui4u3W0Fys=", + "dev": true + }, + "tty-browserify": { + "version": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true + }, + "tweetnacl": { + "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-is": { + "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dev": true, + "requires": { + "media-typer": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz" + } + }, + "typedarray": { + "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.4.tgz", + "integrity": "sha1-ZeovswWck5RpLxX+2HwrNsFrmt8=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "uglify-to-browserify": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "yargs": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + } + } + }, + "uglify-to-browserify": { + "version": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "ultron": { + "version": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "dev": true + }, + "umd": { + "version": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz", + "integrity": "sha1-iuVW4RAR9jwllnCKiDclnwGz1g4=", + "dev": true + }, + "underscore.string": { + "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", + "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", + "dev": true + }, + "unpipe": { + "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "upper-case": { + "version": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "upper-case-first": { + "version": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=", + "dev": true, + "requires": { + "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + } + }, + "uri-path": { + "version": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", + "integrity": "sha1-l0fwGDWJM8Md4PzP2C0TjmcmLjI=", + "dev": true + }, + "url": { + "version": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "querystring": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" + }, + "dependencies": { + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "user-home": { + "version": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "useragent": { + "version": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz", + "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", + "dev": true, + "requires": { + "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "tmp": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz" + }, + "dependencies": { + "lru-cache": { + "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=", + "dev": true + } + } + }, + "util": { + "version": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "dependencies": { + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=", + "dev": true + }, + "v8flags": { + "version": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz" + } + }, + "validate-npm-package-license": { + "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "spdx-expression-parse": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz" + } + }, + "verror": { + "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "dev": true, + "requires": { + "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" + } + }, + "vm-browserify": { + "version": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz" + } + }, + "void-elements": { + "version": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchify": { + "version": "https://registry.npmjs.org/watchify/-/watchify-3.9.0.tgz", + "integrity": "sha1-8HX9LoqGrN6Eztum5cKgvt1SPZ4=", + "dev": true, + "requires": { + "anymatch": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "browserify": "https://registry.npmjs.org/browserify/-/browserify-14.4.0.tgz", + "chokidar": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "defined": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "outpipe": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "websocket-driver": { + "version": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true, + "requires": { + "websocket-extensions": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz" + } + }, + "websocket-extensions": { + "version": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", + "integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=", + "dev": true + }, + "which": { + "version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + } + }, + "window-size": { + "version": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", + "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", + "dev": true, + "requires": { + "options": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "ultron": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz" + } + }, + "wtf-8": { + "version": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", + "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", + "dev": true + }, + "xml-char-classes": { + "version": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz", + "integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "dev": true + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "cliui": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "decamelize": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "window-size": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" + }, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + } + } + }, + "yauzl": { + "version": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz" + } + }, + "yeast": { + "version": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + } + } +} diff --git a/package.json b/package.json index 6e3a91bc1..7a14ae8d6 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,8 @@ "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2" }, - "license": "GPL-3.0" + "license": "GPL-3.0", + "dependencies": { + "sn-stylekit": "^1.0.1" + } } diff --git a/vendor/assets/stylesheets/stylekit.css b/vendor/assets/stylesheets/stylekit.css deleted file mode 100644 index 326f4b600..000000000 --- a/vendor/assets/stylesheets/stylekit.css +++ /dev/null @@ -1,686 +0,0 @@ -.sn-component { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - user-select: none; -} -.sn-component .panel { - box-shadow: 0px 2px 13px #C8C8C8; - border-radius: 0.7rem; - display: flex; - flex-direction: column; - overflow: hidden; -} -.sn-component .panel a:hover { - text-decoration: underline; -} -.sn-component .panel.static { - box-shadow: none; - border: none; - border-radius: 0; -} -.sn-component .panel .header { - flex-shrink: 0; - /* Don't allow to condense in height */ - display: flex; - justify-content: space-between; - padding: 1.1rem 2rem; - border-bottom: 1px solid #E1E1E1; - background-color: #F6F6F6; - align-items: center; -} -.sn-component .panel .header .close-button { - font-weight: bold; -} -.sn-component .panel .footer, .sn-component .panel .panel-footer { - padding: 1rem 2rem; - border-top: 1px solid #E1E1E1; - box-sizing: border-box; -} -.sn-component .panel .footer .left, .sn-component .panel .panel-footer .left { - text-align: right; - display: block; -} -.sn-component .panel .footer .right, .sn-component .panel .panel-footer .right { - text-align: right; - display: block; -} -.sn-component .panel .content { - padding: 1.6rem 2rem; - padding-bottom: 0; - flex-grow: 1; - overflow: scroll; -} -.sn-component .panel .content p { - color: #454545; - line-height: 1.3; -} -.sn-component .panel .content .label, .sn-component .panel .content .panel-section .subtitle, .sn-component .panel-section .panel .content .subtitle { - font-weight: bold; -} -.sn-component .panel-section { - padding-bottom: 1.6rem; - display: flex; - flex-direction: column; -} -.sn-component .panel-section.no-bottom-pad { - padding-bottom: 0; -} -.sn-component .panel-section.hero { - text-align: center; -} -.sn-component .panel-section p:last-child { - margin-bottom: 0; -} -.sn-component .panel-section:not(:last-child) { - margin-bottom: 1.5rem; - border-bottom: 1px solid #DDDDDD; -} -.sn-component .panel-section:not(:last-child).no-border { - border-bottom: none; -} -.sn-component .panel-section:last-child { - margin-bottom: 0.5rem; -} -.sn-component .panel-section .outer-title { - border-bottom: 1px solid #DDDDDD; - padding-bottom: 0.9rem; - margin-top: 2.1rem; - margin-bottom: 15px; -} -.sn-component .panel-section .subtitle { - margin-top: -0.5rem; -} -.sn-component .panel-section .subtitle.subtle { - font-weight: normal; - opacity: 0.6; -} -.sn-component .panel-row { - display: flex; - justify-content: space-between; - align-items: center; - padding-top: 0.4rem; -} -.sn-component .panel-row.centered { - justify-content: center; -} -.sn-component .panel-row .panel-column { - width: 100%; -} -.sn-component .panel-row.default-padding, .sn-component .panel-row:not(:last-child) { - padding-bottom: 0.4rem; -} -.sn-component .panel-row.default-padding.condensed, .sn-component .panel-row:not(:last-child).condensed { - padding-top: 0.2rem; - padding-bottom: 0.2rem; -} -.sn-component .panel-row p { - margin: 0; - padding: 0; -} -.sn-component .panel-form { - width: 100%; -} -.sn-component .panel-form.half { - width: 50%; -} -.sn-component .panel-form .form-submit { - margin-top: 0.15rem; -} -.sn-component .right-aligned { - justify-content: flex-end; - text-align: right; -} -.sn-component .menu-panel { - box-shadow: 0px 4px 4px #C8C8C8; - border-radius: 0.6rem; - overflow: scroll; -} -.sn-component .menu-panel .header { - padding: 0.8rem 1rem; - border-bottom: 1px solid #E1E1E1; - background-color: #F6F6F6; - display: flex; - justify-content: space-between; - align-items: center; -} -.sn-component .menu-panel .header .subtitle { - font-size: 0.95rem; - margin-top: 0.2rem; - opacity: 0.6; -} -.sn-component .menu-panel .row { - padding: 1rem 1rem; - border-bottom: 1px solid #DDDDDD; - cursor: pointer; - display: flex; - flex-direction: row; - justify-content: space-between; -} -.sn-component .menu-panel .row .column { - display: flex; - justify-content: center; - flex-direction: column; - /* Nested row */ -} -.sn-component .menu-panel .row .column:not(:first-child) { - padding-left: 1.0rem; - padding-right: 0.15rem; -} -.sn-component .menu-panel .row .column .row { - padding: 0; - border-bottom: none; -} -.sn-component .menu-panel .row .column .left { - display: flex; -} -.sn-component .menu-panel .row .button .label, .sn-component .menu-panel .row .box .label, .sn-component .menu-panel .row .circle .label, .sn-component .menu-panel .row .button .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .button .subtitle, .sn-component .menu-panel .row .box .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .box .subtitle, .sn-component .menu-panel .row .circle .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .circle .subtitle { - font-size: 0.8rem; - font-weight: normal; -} -.sn-component .menu-panel .row:hover { - background-color: #efefef; -} -.sn-component .menu-panel .row .label, .sn-component .menu-panel .row .panel-section .subtitle, .sn-component .panel-section .menu-panel .row .subtitle { - font-size: 1rem; - font-weight: bold; -} -.sn-component .menu-panel .row .sublabel { - font-size: 0.9rem; - margin-top: 0.2rem; - opacity: 0.6; -} -.sn-component .red { - color: #F80324; -} -.sn-component .tinted { - color: #086DD6; -} -.sn-component h1, .sn-component h2, .sn-component h3, .sn-component h4, .sn-component h5 { - margin: 0; - padding: 0; -} -.sn-component h1 { - font-weight: 500; - font-size: 1.3rem; - line-height: 1.9rem; -} -.sn-component h2 { - font-size: 1.2rem; - line-height: 1.8rem; -} -.sn-component h3 { - font-weight: normal; - font-size: 1.2rem; - line-height: 1.7rem; -} -.sn-component h4 { - font-weight: bold; - font-size: 0.95rem; - line-height: 1.4rem; -} -.sn-component h5 { - font-weight: bold; - font-size: 0.85rem; -} -.sn-component a { - cursor: pointer; -} -.sn-component .wrap { - word-wrap: break-word; -} -.sn-component *.info { - color: #086DD6; -} -.sn-component *.warning { - color: #f6a200; -} -.sn-component *.danger { - color: #F80324; -} -.sn-component *.success { - color: #2B9612; -} -.sn-component *.clear { - background-color: transparent; - border: none; -} -.sn-component .center-text { - text-align: center; - justify-content: center; -} -.sn-component p { - margin: 0.5rem 0; -} -.sn-component input { - box-sizing: border-box; - padding: 0.75rem 0.8rem; - margin: 0.30rem 0; - border-radius: 3px; - border: 1px solid #e1e1e1; - font-size: 1.1rem; - width: 100%; - outline: 0; - resize: none; -} -.sn-component input.info { - border-color: #086DD6; - background-color: transparent; - color: #086DD6; -} -.sn-component input.info::-webkit-input-placeholder { - color: rgba(8, 109, 214, 0.5); -} -.sn-component label { - margin: 0.7rem 0; - display: block; - font-size: 1.1rem; -} -.sn-component label input[type='checkbox'], .sn-component input[type='radio'] { - width: auto; - margin-right: 0.45rem; - /* Space after checkbox */ -} -.sn-component .horizontal-group > *, .sn-component .input-group > * { - display: inline-block; - vertical-align: middle; -} -.sn-component .horizontal-group > *:not(:first-child), .sn-component .input-group > *:not(:first-child) { - margin-left: 0.9rem; -} -.sn-component .border-bottom { - border-bottom: 1px solid #DDDDDD; -} -.sn-component .checkbox-group { - padding-top: 0.5rem; - padding-bottom: 0.3rem; -} -.sn-component ::placeholder { - /* Chrome, Firefox, Opera, Safari 10.1+ */ - color: #c4c4c4; -} -.sn-component :-ms-input-placeholder { - /* Internet Explorer 10-11 */ - color: #c4c4c4; -} -.sn-component ::-ms-input-placeholder { - /* Microsoft Edge */ - color: #c4c4c4; -} -.sn-component .button-group.stretch { - display: flex; - width: 100%; -} -.sn-component .button-group.stretch .button, .sn-component .button-group.stretch .box, .sn-component .button-group.stretch .circle { - display: block; - flex-grow: 1; - text-align: center; -} -.sn-component .button-group .button, .sn-component .button-group .box, .sn-component .button-group .circle { - display: inline-block; -} -.sn-component .button-group .button:not(:last-child), .sn-component .button-group .box:not(:last-child), .sn-component .button-group .circle:not(:last-child) { - margin-right: 5px; -} -.sn-component .button-group .button:not(:last-child).featured, .sn-component .button-group .box:not(:last-child).featured, .sn-component .button-group .circle:not(:last-child).featured { - margin-right: 8px; -} -.sn-component .box-group .box { - display: inline-block; -} -.sn-component .box-group .box:not(:last-child) { - margin-right: 5px; -} -.sn-component a.button, .sn-component a.box, .sn-component a.circle { - text-decoration: none; -} -.sn-component .button, .sn-component .box, .sn-component .circle { - display: table; - border-radius: 3px; - padding: 0.5rem 0.7rem; - font-size: 0.9rem; - cursor: pointer; - text-align: center; - border: 1px solid; -} -.sn-component .button .label, .sn-component .box .label, .sn-component .circle .label, .sn-component .button .panel-section .subtitle, .sn-component .panel-section .button .subtitle, .sn-component .box .panel-section .subtitle, .sn-component .panel-section .box .subtitle, .sn-component .circle .panel-section .subtitle, .sn-component .panel-section .circle .subtitle { - font-weight: bold; - display: block; - text-align: center; -} -.sn-component .button.big, .sn-component .big.box, .sn-component .big.circle { - font-size: 1.1rem; - padding: 0.7rem 2.5rem; -} -.sn-component .box { - padding: 2.5rem 1.5rem; -} -.sn-component .button.default, .sn-component .default.box, .sn-component .default.circle, .sn-component .box.default, .sn-component .circle.default { - background-color: rgba(139, 139, 139, 0.1); - border-color: #8b8b8b; - color: #8b8b8b; -} -.sn-component .button.default:hover, .sn-component .default.box:hover, .sn-component .default.circle:hover, .sn-component .box.default:hover, .sn-component .circle.default:hover { - background-color: white; - color: #939393; -} -.sn-component .button.default.featured, .sn-component .default.featured.box, .sn-component .default.featured.circle, .sn-component .box.default.featured, .sn-component .circle.default.featured { - background-color: #8b8b8b; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .button.default.featured:hover, .sn-component .default.featured.box:hover, .sn-component .default.featured.circle:hover, .sn-component .box.default.featured:hover, .sn-component .circle.default.featured:hover { - background-color: #9f9f9f; -} -.sn-component .button.info, .sn-component .info.box, .sn-component .info.circle, .sn-component .box.info, .sn-component .circle.info { - background-color: rgba(8, 109, 214, 0.1); - border-color: #086DD6; - color: #086DD6; -} -.sn-component .button.info:hover, .sn-component .info.box:hover, .sn-component .info.circle:hover, .sn-component .box.info:hover, .sn-component .circle.info:hover { - background-color: #d5e9fd; - color: #0975e5; -} -.sn-component .button.info.featured, .sn-component .info.featured.box, .sn-component .info.featured.circle, .sn-component .box.info.featured, .sn-component .circle.info.featured { - background-color: #086DD6; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .button.info.featured:hover, .sn-component .info.featured.box:hover, .sn-component .info.featured.circle:hover, .sn-component .box.info.featured:hover, .sn-component .circle.info.featured:hover { - background-color: #1181f6; -} -.sn-component .button.warning, .sn-component .warning.box, .sn-component .warning.circle, .sn-component .box.warning, .sn-component .circle.warning { - background-color: rgba(246, 162, 0, 0.1); - border-color: #f6a200; - color: #f6a200; -} -.sn-component .button.warning:hover, .sn-component .warning.box:hover, .sn-component .warning.circle:hover, .sn-component .box.warning:hover, .sn-component .circle.warning:hover { - background-color: #fff8ec; - color: #ffaa06; -} -.sn-component .button.warning.featured, .sn-component .warning.featured.box, .sn-component .warning.featured.circle, .sn-component .box.warning.featured, .sn-component .circle.warning.featured { - background-color: #f6a200; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .button.warning.featured:hover, .sn-component .warning.featured.box:hover, .sn-component .warning.featured.circle:hover, .sn-component .box.warning.featured:hover, .sn-component .circle.warning.featured:hover { - background-color: #ffb320; -} -.sn-component .button.danger, .sn-component .danger.box, .sn-component .danger.circle, .sn-component .box.danger, .sn-component .circle.danger { - background-color: rgba(248, 3, 36, 0.1); - border-color: #F80324; - color: #F80324; -} -.sn-component .button.danger:hover, .sn-component .danger.box:hover, .sn-component .danger.circle:hover, .sn-component .box.danger:hover, .sn-component .circle.danger:hover { - background-color: #fff1f3; - color: #fc0e2e; -} -.sn-component .button.danger.featured, .sn-component .danger.featured.box, .sn-component .danger.featured.circle, .sn-component .box.danger.featured, .sn-component .circle.danger.featured { - background-color: #F80324; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .button.danger.featured:hover, .sn-component .danger.featured.box:hover, .sn-component .danger.featured.circle:hover, .sn-component .box.danger.featured:hover, .sn-component .circle.danger.featured:hover { - background-color: #fc2744; -} -.sn-component .button.success, .sn-component .success.box, .sn-component .success.circle, .sn-component .box.success, .sn-component .circle.success { - background-color: rgba(43, 150, 18, 0.1); - border-color: #2B9612; - color: #2B9612; -} -.sn-component .button.success:hover, .sn-component .success.box:hover, .sn-component .success.circle:hover, .sn-component .box.success:hover, .sn-component .circle.success:hover { - background-color: #b7f5a8; - color: #2fa414; -} -.sn-component .button.success.featured, .sn-component .success.featured.box, .sn-component .success.featured.circle, .sn-component .box.success.featured, .sn-component .circle.success.featured { - background-color: #2B9612; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .button.success.featured:hover, .sn-component .success.featured.box:hover, .sn-component .success.featured.circle:hover, .sn-component .box.success.featured:hover, .sn-component .circle.success.featured:hover { - background-color: #35ba16; -} -.sn-component .notification.default { - background-color: #F6F6F6; - border-color: #c4c4c4; -} -.sn-component .notification.info { - background-color: rgba(8, 109, 214, 0.1); - border-color: #086DD6; - color: #086DD6; -} -.sn-component .notification.info.featured { - background-color: #086DD6; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .notification.info.featured:hover { - background-color: #1181f6; -} -.sn-component .notification.warning { - background-color: rgba(246, 162, 0, 0.1); - border-color: #f6a200; - color: #f6a200; -} -.sn-component .notification.warning.featured { - background-color: #f6a200; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .notification.warning.featured:hover { - background-color: #ffb320; -} -.sn-component .notification.danger { - background-color: rgba(248, 3, 36, 0.1); - border-color: #F80324; - color: #F80324; -} -.sn-component .notification.danger.featured { - background-color: #F80324; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .notification.danger.featured:hover { - background-color: #fc2744; -} -.sn-component .notification.success { - background-color: rgba(43, 150, 18, 0.1); - border-color: #2B9612; - color: #2B9612; -} -.sn-component .notification.success.featured { - background-color: #2B9612; - border: none; - color: white; - padding: 0.75rem 1.25rem; - font-size: 1.1rem; -} -.sn-component .notification.success.featured:hover { - background-color: #35ba16; -} -.sn-component .notification { - border: 1px solid; - padding: 1.1rem 1rem; - margin: 1.4rem 0; - text-align: left; - border-radius: 0.2rem; - cursor: default; -} -.sn-component .notification.one-line { - padding: 0rem 0.4rem; -} -.sn-component .notification.stretch { - width: 100%; -} -.sn-component .notification.dashed { - border-style: dashed; -} -.sn-component .notification .text { - line-height: 1.5rem; - font-size: 1.05rem; - text-align: left; - font-weight: normal; -} -.sn-component .circle { - border-color: #DDDDDD; - background-color: #F6F6F6; - padding: 0; - border-radius: 50%; -} -.sn-component .circle.small { - width: 11px; - height: 11px; -} -.sn-component .spinner { - border: 1px solid #515263; - border-radius: 50%; - animation: rotate 0.8s infinite linear; - border-right-color: transparent; -} -.sn-component .spinner.small { - width: 12px; - height: 12px; -} -.sn-component .spinner.info { - border-color: #086DD6; - border-right-color: transparent; -} -.sn-component .spinner.warning { - border-color: #f6a200; - border-right-color: transparent; -} -.sn-component .spinner.danger { - border-color: #F80324; - border-right-color: transparent; -} -.sn-component .spinner.success { - border-color: #2B9612; - border-right-color: transparent; -} -@keyframes rotate { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} -.sn-component .app-bar { - display: flex; - width: 100%; - height: 2rem; - padding: 0.1rem 0.8rem; - background-color: #F6F6F6; - justify-content: space-between; - align-items: center; - border: 1px solid #DDDDDD; -} -.sn-component .app-bar.no-edges { - border-left: 0; - border-right: 0; -} -.sn-component .app-bar .left, .sn-component .app-bar .right { - display: flex; - height: 100%; -} -.sn-component .app-bar .item { - flex-grow: 1; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; -} -.sn-component .app-bar .item:not(:first-child) { - margin-left: 1rem; -} -.sn-component .app-bar .item.border { - border-left: 1px solid #DDDDDD; -} -.sn-component .app-bar .item > .column { - height: 100%; - display: flex; - align-items: center; -} -.sn-component .app-bar .item > .column:not(:first-child) { - margin-left: 0.5rem; -} -.sn-component .app-bar .item.no-pointer { - cursor: default; -} -.sn-component .app-bar .item:hover > .label:not(.subtle), .sn-component .app-bar .panel-section .item:hover > .subtitle:not(.subtle), .sn-component .panel-section .app-bar .item:hover > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .sublabel:not(.subtle), .sn-component .app-bar .item:hover > .column > .label:not(.subtle), .sn-component .app-bar .panel-section .item:hover > .column > .subtitle:not(.subtle), .sn-component .panel-section .app-bar .item:hover > .column > .subtitle:not(.subtle), .sn-component .app-bar .item:hover > .column > .sublabel:not(.subtle) { - color: #086DD6; -} -.sn-component .app-bar .item > .label, .sn-component .app-bar .panel-section .item > .subtitle, .sn-component .panel-section .app-bar .item > .subtitle, .sn-component .app-bar .item > .column > .label, .sn-component .app-bar .panel-section .item > .column > .subtitle, .sn-component .panel-section .app-bar .item > .column > .subtitle { - font-weight: bold; - font-size: 0.9rem; - white-space: nowrap; -} -.sn-component .app-bar .item > .sublabel, .sn-component .app-bar .item > .column > .sublabel { - font-size: 0.9rem; - font-weight: normal; - white-space: nowrap; -} -.sn-component .app-bar .item .subtle { - font-weight: normal; - opacity: 0.6; -} -.sn-component .panel-table { - display: flex; - flex-wrap: wrap; - padding-left: 1px; - padding-top: 1px; -} -.sn-component .panel-table .table-item { - flex: 45%; - flex-flow: wrap; - border: 1px solid #DDDDDD; - padding: 1rem; - margin-left: -1px; - margin-top: -1px; - display: flex; - flex-direction: column; - justify-content: space-between; -} -.sn-component .panel-table .table-item img { - max-width: 100%; - margin-bottom: 1rem; -} -.sn-component .panel-table .table-item .item-content { - display: flex; - flex-direction: row; -} -.sn-component .panel-table .table-item .item-column { - align-items: center; -} -.sn-component .panel-table .table-item .item-column:not(:first-child) { - padding-left: 0.75rem; -} -.sn-component .panel-table .table-item .item-column.quarter { - flex-basis: 25%; -} -.sn-component .panel-table .table-item .item-column.three-quarters { - flex-basis: 75%; -} -.sn-component .panel-table .table-item .item-footer { - margin-top: 1.25rem; -} -.sn-component .panel-table .table-item.no-border { - border: none; -} - -/*# sourceMappingURL=stylekit.css.map */ From a679f82859130fd3fd123a1ead48cc03dd4125d8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 12 Jan 2018 16:38:26 -0600 Subject: [PATCH 26/99] Fixes altnernate uuids on sign in to notify observers, fixes dummy note becoming undummy --- .../app/frontend/controllers/editor.js | 1 - .../app/frontend/controllers/footer.js | 6 ++---- .../javascripts/app/services/modelManager.js | 11 ++++++----- .../app/services/singletonManager.js | 8 ++++---- .../javascripts/app/services/syncManager.js | 4 +++- .../frontend/directives/account-menu.html.haml | 18 ++++++++++-------- app/assets/templates/frontend/footer.html.haml | 2 +- package.json | 2 +- 8 files changed, 27 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index 556c6ef60..4dddf3867 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -245,7 +245,6 @@ angular.module('app.frontend') this.onNameBlur = function() { this.editingName = false; - this.updateTagsFromTagsString() } this.toggleFullScreen = function() { diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index 218469abb..66447688a 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -115,8 +115,8 @@ angular.module('app.frontend') this.rooms = []; modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { - this.rooms = _.uniq(this.rooms.concat(allItems.filter((candidate) => {return candidate.area == "rooms"}))) - .filter((candidate) => {return !candidate.deleted}); + var incomingRooms = allItems.filter((candidate) => {return candidate.area == "rooms"}); + this.rooms = _.uniq(this.rooms.concat(incomingRooms)).filter((candidate) => {return !candidate.deleted}); }); componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: (component) => { @@ -161,9 +161,7 @@ angular.module('app.frontend') // Handle singleton ProLink instance singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: "org.standardnotes.prolink"}}, (resolvedSingleton) => { - }, (valueCallback) => { - // Safe to create. Create and return object. let url = window._prolink_package_url; console.log("Installing ProLink from URL", url); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index a72488668..0e8bdcc8b 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -62,9 +62,10 @@ class ModelManager { } if(removeOriginal) { - this.removeItemLocally(item, function(){ - block(); - }); + // 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(); } @@ -81,13 +82,13 @@ class ModelManager { } allItemsMatchingTypes(contentTypes) { - return this.items.filter(function(item){ + return this.allItems.filter(function(item){ return (_.includes(contentTypes, item.content_type) || _.includes(contentTypes, "*")) && !item.dummy; }) } itemsForContentType(contentType) { - return this.items.filter(function(item){ + return this.allItems.filter(function(item){ return item.content_type == contentType; }); } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index f2f2c015e..2da203229 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -24,10 +24,10 @@ class SingletonManager { }) // Testing code to make sure only 1 exists - setTimeout(function () { - var userPrefs = modelManager.itemsForContentType("SN|UserPreferences"); - console.assert(userPrefs.length == 1); - }, 1000); + // setTimeout(function () { + // var userPrefs = modelManager.itemsForContentType("SN|UserPreferences"); + // console.assert(userPrefs.length == 1); + // }, 1000); } registerSingleton(predicate, resolveCallback, createBlock) { diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 571aac588..c9571b36d 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -84,6 +84,8 @@ class SyncManager { // use a copy, as alternating uuid will affect array var originalItems = this.modelManager.allItems.slice(); + console.log("markAllItemsDirtyAndSaveOffline", originalItems); + var block = () => { var allItems = this.modelManager.allItems; for(var item of allItems) { @@ -97,7 +99,7 @@ class SyncManager { let alternateNextItem = () => { if(index >= originalItems.length) { - // We don't use originalItems as altnerating UUID will have deleted them. + // We don't use originalItems as alternating UUID will have deleted them. block(); return; } diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 38db58dec..d70e3d12f 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -7,11 +7,13 @@ .panel-section.hero{"ng-if" => "!user && !formData.showLogin && !formData.showRegister && !formData.mfa"} %h1.title Sign in or register to enable sync and end-to-end encryption. - .button-group.stretch - .button.info.featured{"ng-click" => "formData.showLogin = true"} - .label Sign In - .button.info.featured{"ng-click" => "formData.showRegister = true"} - .label Register + .panel-row + .panel-row + .button-group.stretch + .button.info.featured{"ng-click" => "formData.showLogin = true"} + .label Sign In + .button.info.featured{"ng-click" => "formData.showRegister = true"} + .label Register %p Standard Notes is free on every platform, and comes standard with sync and encryption. @@ -30,9 +32,9 @@ %h2.title No Password Reset. .text Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password. .advanced-options.panel-row{"ng-if" => "formData.showAdvanced"} - .float-group + .panel-column %label.pull-left Sync Server Domain - %input.form-control.mt-5{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'} + %input.form-control.mt-5{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'} .button-group.stretch.panel-row.form-submit .button.info.featured{"ng-click" => "submitAuthForm()"} @@ -41,7 +43,7 @@ %label %input{"type" => "checkbox", "ng-model" => "formData.ephemeral", "ng-true-value" => "false", "ng-false-value" => "true"} Stay signed in - %label + %label{"ng-if" => "notesAndTagsCount() > 0"} %input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"} Merge local data ({{notesAndTagsCount()}} notes and tags) diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index 4ed1d1de5..a859448e2 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -19,7 +19,7 @@ .item.border - .item{"ng-repeat" => "room in ctrl.rooms"} + .item{"ng-repeat" => "room in ctrl.rooms track by room.uuid"} .column{"ng-click" => "ctrl.selectRoom(room)"} .label {{room.name}} %component-modal{"ng-if" => "room.showRoom", "component" => "room", "controller" => "room.directiveController"} diff --git a/package.json b/package.json index 7a14ae8d6..4ece22493 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "neeto", + "name": "standard-notes", "version": "1.0.0", "repository": { "type": "git", From e5df0dcb748de8aefb51946a4ba1c9570746aba0 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 16 Jan 2018 10:31:22 -0600 Subject: [PATCH 27/99] System exts wip --- .gitmodules | 9 +++ .../app/frontend/controllers/footer.js | 63 ++++++++++++++++--- .../frontend/directives/editor-menu.html.haml | 2 +- app/extensions/extensions-manager | 1 + app/views/application/frontend.html.erb | 2 +- public/extensions/extensions-manager | 1 + vendor/extensions/extensions-manager | 1 + 7 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 .gitmodules create mode 160000 app/extensions/extensions-manager create mode 160000 public/extensions/extensions-manager create mode 160000 vendor/extensions/extensions-manager diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..d280a2850 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "vendor/extensions/extensions-manager"] + path = vendor/extensions/extensions-manager + url = https://github.com/sn-extensions/extensions-manager.git +[submodule "app/extensions/extensions-manager"] + path = app/extensions/extensions-manager + url = https://github.com/sn-extensions/extensions-manager.git +[submodule "public/extensions/extensions-manager"] + path = public/extensions/extensions-manager + url = https://github.com/sn-extensions/extensions-manager.git diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index 66447688a..b4d7b30bd 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -159,18 +159,67 @@ angular.module('app.frontend') } } + let extensionsIdentifier = "org.standardnotes.extensions-manager"; + // Handle singleton ProLink instance - singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: "org.standardnotes.prolink"}}, (resolvedSingleton) => { + singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: extensionsIdentifier}}, (resolvedSingleton) => { + // Resolved Singleton + console.log("Resolved extensions-manager", resolvedSingleton); + var needsSync = false; + if(isDesktopApplication()) { + if(!resolvedSingleton.local_url) { + resolvedSingleton.local_url = window._extensions_manager_location; + needsSync = true; + } + } else { + if(!resolvedSingleton.hosted_url) { + resolvedSingleton.hosted_url = window._extensions_manager_location; + needsSync = true; + } + } + + if(needsSync) { + resolvedSingleton.setDirty(true); + syncManager.sync(); + } }, (valueCallback) => { // Safe to create. Create and return object. - let url = window._prolink_package_url; - console.log("Installing ProLink from URL", url); + let url = window._extensions_manager_location; + console.log("Installing Extensions Manager from URL", url); if(!url) { - console.error("window._prolink_package_url must be set."); + console.error("window._extensions_manager_location must be set."); return; } - packageManager.installPackage(url, (component) => { - valueCallback(component); - }) + + var item = { + content_type: "SN|Component", + content: { + name: "Extensions", + area: "rooms", + package_info: { + identifier: extensionsIdentifier + }, + permissions: [ + { + name: "stream-items", + content_types: ["SN|Component", "SN|Theme", "SF|Extension", "Extension", "SF|MFA"] + } + ] + } + } + + if(isDesktopApplication()) { + item.content.local_url = window._extensions_manager_location; + } else { + item.content.hosted_url = window._extensions_manager_location; + } + + var component = modelManager.createItem(item); + modelManager.addItem(component); + + component.setDirty(true); + syncManager.sync(); + + valueCallback(component); }); }); diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 5f6df4192..73fe7b68c 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -17,7 +17,7 @@ %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download More Editors'", "ng-click" => "moreEditors()"} - .section + .section{"ng-if" => "stack.length > 0"} .header %h4.title Editor Stack %menu-row{"ng-repeat" => "component in stack", "ng-click" => "selectComponent($event, component)", "title" => "component.name", diff --git a/app/extensions/extensions-manager b/app/extensions/extensions-manager new file mode 160000 index 000000000..f6fce7688 --- /dev/null +++ b/app/extensions/extensions-manager @@ -0,0 +1 @@ +Subproject commit f6fce768881c827704bb76510c0183b0cbd6f8ff diff --git a/app/views/application/frontend.html.erb b/app/views/application/frontend.html.erb index 471176b47..192df1a16 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/frontend.html.erb @@ -30,7 +30,7 @@ <% if Rails.env.development? %> diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager new file mode 160000 index 000000000..f6fce7688 --- /dev/null +++ b/public/extensions/extensions-manager @@ -0,0 +1 @@ +Subproject commit f6fce768881c827704bb76510c0183b0cbd6f8ff diff --git a/vendor/extensions/extensions-manager b/vendor/extensions/extensions-manager new file mode 160000 index 000000000..f6fce7688 --- /dev/null +++ b/vendor/extensions/extensions-manager @@ -0,0 +1 @@ +Subproject commit f6fce768881c827704bb76510c0183b0cbd6f8ff From 7b57d0ab6042c74331f706b7fd16ab7b7367a546 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 16 Jan 2018 10:31:53 -0600 Subject: [PATCH 28/99] Submodule cleanup --- app/extensions/extensions-manager | 1 - vendor/extensions/extensions-manager | 1 - 2 files changed, 2 deletions(-) delete mode 160000 app/extensions/extensions-manager delete mode 160000 vendor/extensions/extensions-manager diff --git a/app/extensions/extensions-manager b/app/extensions/extensions-manager deleted file mode 160000 index f6fce7688..000000000 --- a/app/extensions/extensions-manager +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f6fce768881c827704bb76510c0183b0cbd6f8ff diff --git a/vendor/extensions/extensions-manager b/vendor/extensions/extensions-manager deleted file mode 160000 index f6fce7688..000000000 --- a/vendor/extensions/extensions-manager +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f6fce768881c827704bb76510c0183b0cbd6f8ff From 47f81e84adc978cfca098baff9c2cccc4508c46c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 16 Jan 2018 11:51:50 -0600 Subject: [PATCH 29/99] Updates --- .../app/frontend/controllers/footer.js | 64 -------------- .../app/frontend/controllers/home.js | 6 +- .../app/services/componentManager.js | 2 +- .../javascripts/app/services/modelManager.js | 2 +- .../app/services/singletonManager.js | 12 ++- .../javascripts/app/services/syncManager.js | 11 ++- .../javascripts/app/services/sysExtManager.js | 83 +++++++++++++++++++ public/extensions/extensions-manager | 2 +- 8 files changed, 107 insertions(+), 75 deletions(-) create mode 100644 app/assets/javascripts/app/services/sysExtManager.js diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index b4d7b30bd..b03c7e822 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -158,68 +158,4 @@ angular.module('app.frontend') room.showRoom = true; } } - - let extensionsIdentifier = "org.standardnotes.extensions-manager"; - - // Handle singleton ProLink instance - singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: extensionsIdentifier}}, (resolvedSingleton) => { - // Resolved Singleton - console.log("Resolved extensions-manager", resolvedSingleton); - var needsSync = false; - if(isDesktopApplication()) { - if(!resolvedSingleton.local_url) { - resolvedSingleton.local_url = window._extensions_manager_location; - needsSync = true; - } - } else { - if(!resolvedSingleton.hosted_url) { - resolvedSingleton.hosted_url = window._extensions_manager_location; - needsSync = true; - } - } - - if(needsSync) { - resolvedSingleton.setDirty(true); - syncManager.sync(); - } - }, (valueCallback) => { - // Safe to create. Create and return object. - let url = window._extensions_manager_location; - console.log("Installing Extensions Manager from URL", url); - if(!url) { - console.error("window._extensions_manager_location must be set."); - return; - } - - var item = { - content_type: "SN|Component", - content: { - name: "Extensions", - area: "rooms", - package_info: { - identifier: extensionsIdentifier - }, - permissions: [ - { - name: "stream-items", - content_types: ["SN|Component", "SN|Theme", "SF|Extension", "Extension", "SF|MFA"] - } - ] - } - } - - if(isDesktopApplication()) { - item.content.local_url = window._extensions_manager_location; - } else { - item.content.hosted_url = window._extensions_manager_location; - } - - var component = modelManager.createItem(item); - modelManager.addItem(component); - - component.setDirty(true); - syncManager.sync(); - - valueCallback(component); - }); }); diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 97216078a..739d2bd3f 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -64,9 +64,9 @@ angular.module('app.frontend') syncManager.sync(null); // refresh every 30s - setInterval(function () { - syncManager.sync(null); - }, 30000); + // setInterval(function () { + // syncManager.sync(null); + // }, 30000); }); } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 28e51a70b..b169ef766 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -3,7 +3,7 @@ let ClientDataDomain = "org.standardnotes.sn.components"; class ComponentManager { - constructor($rootScope, modelManager, syncManager, desktopManager, $timeout, $compile) { + constructor($rootScope, modelManager, syncManager, desktopManager, sysExtManager, $timeout, $compile) { this.$compile = $compile; this.$rootScope = $rootScope; this.modelManager = modelManager; diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 0e8bdcc8b..ef2b09abc 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -243,7 +243,7 @@ class ModelManager { } createDuplicateItem(itemResponse, sourceItem) { - var dup = this.createItem(itemResponse); + var dup = this.createItem(itemResponse, true); this.resolveReferencesForItem(dup); return dup; } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 2da203229..dc8c6583f 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -16,11 +16,13 @@ class SingletonManager { this.singletonHandlers = []; $rootScope.$on("initial-data-loaded", (event, data) => { - this.resolveSingletons(modelManager.allItems, true); + this.resolveSingletons(modelManager.allItems, null, true); }) $rootScope.$on("sync:completed", (event, data) => { - this.resolveSingletons(data.retrievedItems || []); + // The reason we also need to consider savedItems in consolidating singletons is in case of sync conflicts, + // a new item can be created, but is never processed through "retrievedItems" since it is only created locally then saved. + this.resolveSingletons(data.retrievedItems, data.savedItems); }) // Testing code to make sure only 1 exists @@ -43,10 +45,12 @@ class SingletonManager { }); } - resolveSingletons(retrievedItems, initialLoad) { + resolveSingletons(retrievedItems, savedItems, initialLoad) { + retrievedItems = retrievedItems || []; + savedItems = savedItems || []; for(let singletonHandler of this.singletonHandlers) { var predicate = singletonHandler.predicate; - var singletonItems = this.filterItemsWithPredicate(retrievedItems, predicate); + var singletonItems = this.filterItemsWithPredicate(_.uniq(retrievedItems.concat(savedItems)), predicate); if(singletonItems.length > 0) { /* Check local inventory and make sure only 1 similar item exists. If more than 1, delete oldest diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index c9571b36d..cc8a6d1b6 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -248,6 +248,11 @@ class SyncManager { 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(); @@ -288,6 +293,9 @@ class SyncManager { var saved = this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved); + // Append items to master list of saved items for this ongoing sync operation + this.allSavedItems = this.allSavedItems.concat(saved); + // Create copies of items or alternate their uuids if neccessary var unsaved = response.unsaved; this.handleUnsavedItemsResponse(unsaved) @@ -327,9 +335,10 @@ class SyncManager { } this.callQueuedCallbacksAndCurrent(callback, response); - this.$rootScope.$broadcast("sync:completed", {retrievedItems: this.allRetreivedItems}); + this.$rootScope.$broadcast("sync:completed", {retrievedItems: this.allRetreivedItems, savedItems: this.allSavedItems}); this.allRetreivedItems = []; + this.allSavedItems = []; } }.bind(this); diff --git a/app/assets/javascripts/app/services/sysExtManager.js b/app/assets/javascripts/app/services/sysExtManager.js new file mode 100644 index 000000000..876506ace --- /dev/null +++ b/app/assets/javascripts/app/services/sysExtManager.js @@ -0,0 +1,83 @@ +/* A class for handling installation of system extensions */ + +class SysExtManager { + + constructor(modelManager, syncManager, singletonManager) { + this.modelManager = modelManager; + this.syncManager = syncManager; + this.singletonManager = singletonManager; + + this.resolveExtensionsManager(); + } + + resolveExtensionsManager() { + let extensionsIdentifier = "org.standardnotes.extensions-manager"; + + this.singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: extensionsIdentifier}}, (resolvedSingleton) => { + // Resolved Singleton + console.log("Resolved extensions-manager", resolvedSingleton); + var needsSync = false; + if(isDesktopApplication()) { + if(!resolvedSingleton.local_url) { + resolvedSingleton.local_url = window._extensions_manager_location; + needsSync = true; + } + } else { + if(!resolvedSingleton.hosted_url) { + resolvedSingleton.hosted_url = window._extensions_manager_location; + needsSync = true; + } + } + + if(needsSync) { + resolvedSingleton.setDirty(true); + this.syncManager.sync(); + } + }, (valueCallback) => { + // Safe to create. Create and return object. + let url = window._extensions_manager_location; + console.log("Installing Extensions Manager from URL", url); + if(!url) { + console.error("window._extensions_manager_location must be set."); + return; + } + + let packageInfo = { + name: "Extensions", + identifier: extensionsIdentifier + } + + var item = { + content_type: "SN|Component", + content: { + name: packageInfo.name, + area: "rooms", + package_info: packageInfo, + permissions: [ + { + name: "stream-items", + content_types: ["SN|Component", "SN|Theme", "SF|Extension", "Extension", "SF|MFA", "SN|Editor"] + } + ] + } + } + + if(isDesktopApplication()) { + item.content.local_url = window._extensions_manager_location; + } else { + item.content.hosted_url = window._extensions_manager_location; + } + + var component = this.modelManager.createItem(item); + this.modelManager.addItem(component); + + component.setDirty(true); + this.syncManager.sync(); + + valueCallback(component); + }); + } + +} + +angular.module('app.frontend').service('sysExtManager', SysExtManager); diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager index f6fce7688..1f13b404e 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit f6fce768881c827704bb76510c0183b0cbd6f8ff +Subproject commit 1f13b404e3f657f4b5da1b8b9bb79712deafb460 From 0b430d4d0882024404fa6c5be8af4b995c6c30f4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 16 Jan 2018 13:42:58 -0600 Subject: [PATCH 30/99] Updates --- Gruntfile.js | 3 +++ .../app/frontend/controllers/footer.js | 17 ++++--------- .../app/frontend/controllers/home.js | 6 ++--- .../services/directives/views/accountMenu.js | 24 +++++-------------- .../javascripts/app/services/syncManager.js | 4 ---- .../directives/account-menu.html.haml | 8 +++---- .../templates/frontend/footer.html.haml | 4 ++-- 7 files changed, 22 insertions(+), 44 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 3c13c4654..939abb961 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -95,6 +95,9 @@ module.exports = function(grunt) { }, css: { + options: { + separator: '', + }, src: [ 'vendor/assets/stylesheets/app.css', 'node_modules/sn-stylekit/dist/stylekit.css' diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index b03c7e822..519443f86 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -45,27 +45,18 @@ angular.module('app.frontend') this.showAccountMenu = false; }.bind(this) + this.closeAccountMenu = () => { + this.showAccountMenu = false; + } + this.accountMenuPressed = function() { - this.serverData = {}; this.showAccountMenu = !this.showAccountMenu; - this.showFaq = false; - this.showNewPasswordForm = false; - this.showExtensionsMenu = false; - this.showIOMenu = false; } this.toggleExtensions = function() { - this.showAccountMenu = false; - this.showIOMenu = false; this.showExtensionsMenu = !this.showExtensionsMenu; } - this.toggleIO = function() { - this.showIOMenu = !this.showIOMenu; - this.showExtensionsMenu = false; - this.showAccountMenu = false; - } - this.hasPasscode = function() { return passcodeManager.hasPasscode(); } diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 739d2bd3f..97216078a 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -64,9 +64,9 @@ angular.module('app.frontend') syncManager.sync(null); // refresh every 30s - // setInterval(function () { - // syncManager.sync(null); - // }, 30000); + setInterval(function () { + syncManager.sync(null); + }, 30000); }); } diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index 4782899bd..5777a05da 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -4,7 +4,8 @@ class AccountMenu { this.restrict = "E"; this.templateUrl = "frontend/directives/account-menu.html"; this.scope = { - "onSuccessfulAuth" : "&" + "onSuccessfulAuth" : "&", + "closeFunction" : "&" }; } @@ -15,28 +16,15 @@ class AccountMenu { $scope.user = authManager.user; $scope.server = syncManager.serverURL; + $scope.close = function() { + $scope.closeFunction()(); + } + $scope.encryptedBackupsAvailable = function() { return authManager.user || passcodeManager.hasPasscode(); } $scope.syncStatus = syncManager.syncStatus; - - $scope.encryptionKey = function() { - return authManager.keys().mk; - } - - $scope.authKey = function() { - return authManager.keys().ak; - } - - $scope.serverPassword = function() { - return syncManager.serverPassword; - } - - $scope.dashboardURL = function() { - return `${$scope.server}/dashboard/#server=${$scope.server}&id=${encodeURIComponent($scope.user.email)}&pw=${$scope.serverPassword()}`; - } - $scope.newPasswordData = {}; $scope.showPasswordChangeForm = function() { diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index cc8a6d1b6..e24dd23d7 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -21,10 +21,6 @@ class SyncManager { return this.storageManager.getItem("mk"); } - get serverPassword() { - return this.storageManager.getItem("pw"); - } - writeItemsToLocalStorage(items, offlineOnly, callback) { if(items.length == 0) { callback && callback(); diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index d70e3d12f..90e08f5d1 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -2,7 +2,7 @@ .panel#account-panel .header %h1.title Account - %a.close-button Close + %a.close-button{"ng-click" => "close()"} Close .content .panel-section.hero{"ng-if" => "!user && !formData.showLogin && !formData.showRegister && !formData.mfa"} @@ -67,8 +67,9 @@ .subtitle.danger.panel-row{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} - .subtitle.subtle.panel-row {{server}} + .subtitle.subtle.normal {{server}} + .panel-row %a.panel-row.condensed{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password .notification.default{"ng-if" => "newPasswordData.changePassword"} @@ -94,8 +95,7 @@ %a.panel-row.condensed{"ng-click" => "showAdvanced = !showAdvanced"} Advanced %div{"ng-if" => "showAdvanced"} - %a.panel-row{"href" => "{{dashboardURL()}}", "target" => "_blank"} Data Dashboard - %a.panel-row{"ng-click" => "reencryptPressed()"} Re-encrypt All Items + %a.panel-row{"ng-click" => "reencryptPressed()"} Resync All Items %div{"ng-if" => "securityUpdateAvailable()"} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index a859448e2..b36357d6f 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -3,10 +3,10 @@ .left .item{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} .column - .circle.small.info + .circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.user ? 'info' : 'default')"} .column{"ng-click" => "ctrl.accountMenuPressed()"} .label.title{"ng-class" => "{red: ctrl.error}"} Account - %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess"} + %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess", "close-function" => "ctrl.closeAccountMenu"} .item{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} .column{"ng-click" => "ctrl.toggleExtensions()"} From 0cf13ad28b9e7a4fd18395567a83fba248cc1fe9 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 17 Jan 2018 12:04:29 -0600 Subject: [PATCH 31/99] CSS cleanups --- .../app/frontend/controllers/footer.js | 12 +- .../app/frontend/models/app/component.js | 8 +- .../app/services/componentManager.js | 6 +- .../directives/functional/autofocus.js | 2 +- .../services/directives/views/accountMenu.js | 24 +- .../javascripts/app/services/modelManager.js | 2 +- .../javascripts/app/services/syncManager.js | 2 - app/assets/stylesheets/app/_lock-screen.scss | 28 +- app/assets/stylesheets/app/_modals.scss | 1 + app/assets/stylesheets/app/_standard.scss | 250 ------------------ app/assets/stylesheets/app/_stylekit-sub.scss | 12 + .../directives/account-menu.html.haml | 111 ++++---- .../directives/component-view.html.haml | 2 +- .../global-extensions-menu.html.haml | 6 +- .../templates/frontend/editor.html.haml | 2 +- .../templates/frontend/footer.html.haml | 2 +- .../templates/frontend/lock-screen.html.haml | 23 +- app/assets/templates/frontend/tags.html.haml | 2 +- package-lock.json | 8 +- package.json | 2 +- 20 files changed, 136 insertions(+), 369 deletions(-) diff --git a/app/assets/javascripts/app/frontend/controllers/footer.js b/app/assets/javascripts/app/frontend/controllers/footer.js index 519443f86..b1e38b60d 100644 --- a/app/assets/javascripts/app/frontend/controllers/footer.js +++ b/app/assets/javascripts/app/frontend/controllers/footer.js @@ -25,7 +25,9 @@ angular.module('app.frontend') .controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager, syncManager, storageManager, passcodeManager, componentManager, singletonManager, packageManager) { - this.user = authManager.user; + this.getUser = function() { + return authManager.user; + } this.updateOfflineStatus = function() { this.offline = authManager.offline(); @@ -110,14 +112,14 @@ angular.module('app.frontend') this.rooms = _.uniq(this.rooms.concat(incomingRooms)).filter((candidate) => {return !candidate.deleted}); }); - componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: (component) => { + componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => { if(component.active) { // Show room, if it was not activated manually (in the event of event from componentManager) - if(!component.showRoom) { + if(component.area == "rooms" && !component.showRoom) { this.selectRoom(component); } $timeout(() => { - var lastSize = component.getRoomLastSize(); + var lastSize = component.getLastSize(); if(lastSize) { componentManager.handleSetSizeEvent(component, lastSize); } @@ -125,7 +127,7 @@ angular.module('app.frontend') } }, actionHandler: (component, action, data) => { if(action == "set-size") { - component.setRoomLastSize(data); + component.setLastSize(data); } }}); diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index c9accbd03..844e88fcb 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -83,12 +83,12 @@ class Component extends Item { return this.getAppDataItem("defaultEditor") == true; } - setRoomLastSize(size) { - this.setAppDataItem("lastRoomSize", size); + setLastSize(size) { + this.setAppDataItem("lastSize", size); } - getRoomLastSize() { - return this.getAppDataItem("lastRoomSize"); + getLastSize() { + return this.getAppDataItem("lastSize"); } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index b169ef766..a18670b10 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -738,7 +738,11 @@ class ComponentManager { setSize(iframe, data); } else { var container = document.getElementById("component-" + component.uuid); - setSize(container, data); + if(container) { + // in the case of Modals, sometimes they may be "active" because they were so in another session, + // but no longer actually visible. So check to make sure the container exists + setSize(container, data); + } } } diff --git a/app/assets/javascripts/app/services/directives/functional/autofocus.js b/app/assets/javascripts/app/services/directives/functional/autofocus.js index 49c733ecc..cbc093a5f 100644 --- a/app/assets/javascripts/app/services/directives/functional/autofocus.js +++ b/app/assets/javascripts/app/services/directives/functional/autofocus.js @@ -1,6 +1,6 @@ angular .module('app.frontend') - .directive('mbAutofocus', ['$timeout', function($timeout) { + .directive('snAutofocus', ['$timeout', function($timeout) { return { restrict: 'A', scope: { diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index 5777a05da..39028041d 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -33,7 +33,13 @@ class AccountMenu { $scope.submitPasswordChange = function() { - if($scope.newPasswordData.newPassword != $scope.newPasswordData.newPasswordConfirmation) { + let newPass = $scope.newPasswordData.newPassword; + + if(!newPass || newPass.length == 0) { + return; + } + + if(newPass != $scope.newPasswordData.newPasswordConfirmation) { alert("Your new password does not match its confirmation."); $scope.newPasswordData.status = null; return; @@ -51,7 +57,7 @@ class AccountMenu { // 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(function(response){ - authManager.changePassword(email, $scope.newPasswordData.newPassword, function(response){ + authManager.changePassword(email, newPass, function(response){ if(response.error) { alert("There was an error changing your password. Please try again."); $scope.newPasswordData.status = null; @@ -84,6 +90,10 @@ class AccountMenu { } $scope.submitAuthForm = function() { + console.log("Submitting auth form"); + if(!$scope.formData.email || !$scope.formData.user_password) { + return; + } if($scope.formData.showLogin) { $scope.login(); } else { @@ -92,6 +102,7 @@ class AccountMenu { } $scope.login = function(extraParams) { + console.log("Logging in"); $scope.formData.status = "Generating Login Keys..."; $timeout(function(){ authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, extraParams, @@ -99,7 +110,7 @@ class AccountMenu { if(!response || response.error) { $scope.formData.status = null; var error = response ? response.error : {message: "An unknown error occured."} - if(error.tag == "mfa-required") { + if(error.tag == "mfa-required" || error.tag == "mfa-invalid") { $timeout(() => { $scope.formData.showLogin = false; $scope.formData.mfa = error; @@ -539,13 +550,6 @@ class AccountMenu { $scope.formData.showPasscodeForm = false; var offline = authManager.offline(); - // Allow UI to update before showing alert - setTimeout(function () { - var message = "You've succesfully set an app passcode."; - if(offline) { message += " Your items will now be encrypted using this passcode."; } - alert(message); - }, 10); - if(offline) { // Allows desktop to make backup file $rootScope.$broadcast("major-data-change"); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index ef2b09abc..879ffede9 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -431,7 +431,7 @@ class ModelManager { "SN|Editor" : "editor", "SN|Theme" : "theme", "SF|Extension" : "server extension", - "SF|MFA" : "server multi-factor authentication setting" + "SF|MFA" : "two-factor authentication setting" }[contentType]; } diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index e24dd23d7..a47cbbb27 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -80,8 +80,6 @@ class SyncManager { // use a copy, as alternating uuid will affect array var originalItems = this.modelManager.allItems.slice(); - console.log("markAllItemsDirtyAndSaveOffline", originalItems); - var block = () => { var allItems = this.modelManager.allItems; for(var item of allItems) { diff --git a/app/assets/stylesheets/app/_lock-screen.scss b/app/assets/stylesheets/app/_lock-screen.scss index 4fef13adb..7fffb7097 100644 --- a/app/assets/stylesheets/app/_lock-screen.scss +++ b/app/assets/stylesheets/app/_lock-screen.scss @@ -14,6 +14,7 @@ font-size: 16px; display: flex; align-items: center; + justify-content: center; .background { position: absolute; @@ -22,30 +23,11 @@ height: 100%; } - .content { - // box-shadow: 0 3px 3px rgba(0, 0, 0, 0.175); - border: 1px solid rgba(black, 0.1); - background-color: white; - width: 300px; - // height: 500px; - margin: auto; - padding: 10px 30px; - padding-bottom: 30px; - // position: absolute; - // top: 0; left: 0; bottom: 0; right: 0; - overflow-y: scroll; + .panel { + width: 315px; - p { - margin-bottom: 8px; - margin-top: 0; - } - - h3 { - margin-bottom: 0; - } - - h4 { - margin-bottom: 6px; + .header { + justify-content: center; } } } diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index 79666daeb..fb0ab7435 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -49,6 +49,7 @@ width: auto; padding: 0; padding-bottom: 0; + min-width: 300px; } } diff --git a/app/assets/stylesheets/app/_standard.scss b/app/assets/stylesheets/app/_standard.scss index c111f54d2..134609878 100644 --- a/app/assets/stylesheets/app/_standard.scss +++ b/app/assets/stylesheets/app/_standard.scss @@ -1,31 +1,3 @@ -.selectable { - user-select: text !important; -} - -.clear { - clear: both; -} - -.pull-left { - float: left !important; -} - -.pull-right { - float: right !important; -} - -.mt-1 { - margin-top: 1px !important; -} - -.mt-2 { - margin-top: 2px !important; -} - -.mt-3 { - margin-top: 3px !important; -} - .mt-5 { margin-top: 5px !important; } @@ -34,66 +6,6 @@ margin-top: 10px !important; } -.mt-15 { - margin-top: 15px !important; -} - -.mt-20 { - margin-top: 20px !important; -} - -.mt-25 { - margin-top: 25px !important; -} - -.mt-50 { - margin-top: 50px !important; -} - -.mt-100 { - margin-top: 100px !important; -} - -.mb-0 { - margin-bottom: 0px !important; -} - -.mb-5 { - margin-bottom: 5px !important; -} - -.mb-10 { - margin-bottom: 10px !important; -} - -.mr-5 { - margin-right: 5px; -} - -.mr-10 { - margin-right: 10px; -} - -.mr-15 { - margin-right: 15px; -} - -.mr-20 { - margin-right: 20px; -} - -.ml-2 { - margin-left: 2px; -} - -.pb-0 { - padding-bottom: 0px !important; -} - -.pt-5 { - padding-top: 5px; -} - .faded { opacity: 0.5; } @@ -102,11 +14,6 @@ text-align: center !important; } -.center { - margin-left: auto !important; - margin-right: auto !important; -} - .block { display: block !important; } @@ -116,59 +23,14 @@ word-break: break-all; } -.one-line-overflow { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} - -.small-v-space { - height: 6px; - display: block; -} - -.medium-v-space { - height: 12px; - display: block; -} - -.large-v-space { - height: 24px; - display: block; -} - -.small-padding { - padding: 5px !important; -} - .medium-padding { padding: 10px !important; } -.pb-4 { - padding-bottom: 4px !important; -} - -.pb-6 { - padding-bottom: 6px !important; -} - -.pb-10 { - padding-bottom: 10px !important; -} - -.large-padding { - padding: 22px !important; -} - .red { color: red !important; } -.orange { - color: orange !important; -} - .bold { font-weight: bold !important; } @@ -184,115 +46,3 @@ .medium { font-size: 14px !important; } - -.inline { - display: inline-block !important; - - &.top { - vertical-align: top; - } - - &.middle { - vertical-align: middle; - } -} - -button { - cursor: pointer; -} - -input.form-control { - margin-bottom: 10px; - border-radius: 0px; - min-height: 39px; - font-size: 14px; - padding-left: 6px; -} - -button { - border: none; - - @mixin wide-button() { - font-weight: bold; - text-align: center; - padding: 10px; - font-size: 16px; - // min-width: 200px; - - &:hover { - text-decoration: underline; - } - } - - &.black { - @include wide-button(); - background-color: black; - color: white; - } - - &.white { - @include wide-button(); - background-color: white; - color: black; - border: 1px solid rgba(gray, 0.2); - } -} - - -.gray-bg { - background-color: #f6f6f6; - border: 1px solid #f2f2f2; -} - -.white-bg { - background-color: white; - border: 1px solid rgba(gray, 0.2); -} - -.col-container { - // white-space: nowrap; -} - -@mixin col() { - display: inline-block; - vertical-align: top; - white-space: normal; -} - -.col-10 { - width: 10%; - @include col(); -} - -.col-15 { - width: 15%; - @include col(); -} - -.col-20 { - width: 20%; - @include col(); -} - -.col-45 { - width: 45%; - @include col(); -} - -.col-50 { - width: 50%; - @include col(); -} - -.col-80 { - width: 80%; - @include col(); -} - -.relative { - position: relative !important; -} - -.absolute { - position: absolute !important; -} diff --git a/app/assets/stylesheets/app/_stylekit-sub.scss b/app/assets/stylesheets/app/_stylekit-sub.scss index 4b7bd0603..15fd3da56 100644 --- a/app/assets/stylesheets/app/_stylekit-sub.scss +++ b/app/assets/stylesheets/app/_stylekit-sub.scss @@ -1,6 +1,18 @@ .panel { color: black; + input { + min-height: 39px; + } + + + .button-group.stretch { + .button:not(.featured) { + // Default buttons that are not featured and stretched should have larger vertical padding + padding: 9px; + } + } + a { color: $blue-color; } diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 90e08f5d1..de8ac2623 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -18,10 +18,10 @@ Standard Notes is free on every platform, and comes standard with sync and encryption. .panel-section{"ng-if" => "formData.showLogin || formData.showRegister"} - %h3.title + %h3.title.panel-row {{formData.showLogin ? "Sign In" : "Register (free)"}} - %form.panel-form + %form.panel-form{"ng-submit" => "submitAuthForm()"} %input{:placeholder => 'Email', :autofocus => 'autofocus', :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email'} %input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'} %input{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf'} @@ -32,13 +32,13 @@ %h2.title No Password Reset. .text Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password. .advanced-options.panel-row{"ng-if" => "formData.showAdvanced"} - .panel-column - %label.pull-left Sync Server Domain + .panel-column.stretch + %label Sync Server Domain %input.form-control.mt-5{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'} .button-group.stretch.panel-row.form-submit - .button.info.featured{"ng-click" => "submitAuthForm()"} - {{formData.showLogin ? "Sign In" : "Register"}} + %button.button.info.featured{"type" => "submit"} + .label {{formData.showLogin ? "Sign In" : "Register"}} %label %input{"type" => "checkbox", "ng-model" => "formData.ephemeral", "ng-true-value" => "false", "ng-false-value" => "true"} @@ -47,15 +47,18 @@ %input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"} Merge local data ({{notesAndTagsCount()}} notes and tags) - %form.mt-5{"ng-if" => "formData.mfa"} - %p {{formData.mfa.message}} - %input.form-control.mt-10{:autofocus => "true", :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'} - %button.ui-button.block.mt-10{"ng-click" => "submitMfaForm()"} {{"Sign In"}} - %em.block.center-align.mt-10{"ng-if" => "formData.status", "style" => "font-size: 14px;"} {{formData.status}} - %div{"ng-if" => "!formData.showLogin && !formData.showRegister"} + .panel-section{"ng-if" => "formData.mfa"} + %form{"ng-submit" => "submitMfaForm()"} + %p {{formData.mfa.message}} + %input.form-control.mt-10{:placeholder => "Enter Code", "sn-autofocus" => "true", "should-focus" => "true", :autofocus => "true", :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'} + .button-group.stretch.panel-row.form-submit + %button.button.info.featured{"type" => "submit"} + .label Sign In + + %div{"ng-if" => "!formData.showLogin && !formData.showRegister && !formData.mfa"} .panel-section{"ng-if" => "user"} .panel-row %h2.title.wrap {{user.email}} @@ -72,23 +75,26 @@ .panel-row %a.panel-row.condensed{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password - .notification.default{"ng-if" => "newPasswordData.changePassword"} - %h1.title Change Password (Beta) + .notification.warning{"ng-if" => "newPasswordData.changePassword"} + %h1.title Change Password .text - %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. - %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. - %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. - %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. - %div.mt-10{"ng-if" => "!newPasswordData.status"} - %a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue - %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel - %div.mt-10{"ng-if" => "newPasswordData.showForm"} - %form - %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} - %input.form-control{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} + %p Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. + %p If you have thousands of items, this can take several minutes — you must keep the application window open during this process. + %p After changing your password, you must log out of all other applications currently signed in to your account. + %p.bold It is highly recommended you download a backup of your data before proceeding. + .panel-row{"ng-if" => "!newPasswordData.status"} + .horizontal-group{"ng-if" => "!newPasswordData.showForm"} + %a.red{"ng-click" => "showPasswordChangeForm()"} Continue + %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel + .panel-row{"ng-if" => "newPasswordData.showForm"} + %form.panel-form.stretch + %input{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} + %input{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} .button-group.stretch.panel-row.form-submit .button.info{"type" => "submit", "ng-click" => "submitPasswordChange()"} .label Submit + %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel + %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} @@ -98,18 +104,18 @@ %a.panel-row{"ng-click" => "reencryptPressed()"} Resync All Items - %div{"ng-if" => "securityUpdateAvailable()"} - %a.block.mt-5{"ng-click" => "clickedSecurityUpdate()"} Security Update Available - %section.gray-bg.mt-10.medium-padding{"ng-if" => "securityUpdateData.showForm"} - %p - %a{"href" => "https://standardnotes.org/help/security-update", "target" => "_blank"} Learn more. - %div.mt-10{"ng-if" => "!securityUpdateData.processing"} - %p.bold Enter your password to update: - %form.mt-5 - %input.form-control{:type => 'password', "ng-model" => "securityUpdateData.password", "placeholder" => "Enter password"} - %button.ui-button.block{"ng-click" => "submitSecurityUpdateForm()"} Update - %div.mt-5{"ng-if" => "securityUpdateData.processing"} - %p.tinted Processing... + %a.panel-row.condensed{"ng-if" => "securityUpdateAvailable()", "ng-click" => "clickedSecurityUpdate()"} Security Update Available + .notification.default{"ng-if" => "securityUpdateData.showForm"} + %p + %a{"href" => "https://standardnotes.org/help/security-update", "target" => "_blank"} Learn more. + %form.panel-form.stretch{"ng-if" => "!securityUpdateData.processing", "ng-submit" => "submitSecurityUpdateForm()"} + %p Enter your password to update: + %input.panel-row{:type => 'password', "ng-model" => "securityUpdateData.password", "placeholder" => "Enter password"} + .button-group.stretch.panel-row.form-submit + %button.button.info{"ng-type" => "submit"} + .label Update + .panel-row{"ng-if" => "securityUpdateData.processing"} + %p.info Processing... .panel-section @@ -122,25 +128,26 @@ .panel-section %h3.title.panel-row Passcode Lock %div{"ng-if" => "!hasPasscode() && passcodeOptionAvailable()"} - .panel-row - .button.info{"ng-click" => "addPasscodeClicked(); $event.stopPropagation();", "ng-if" => "!formData.showPasscodeForm"} + .panel-row{"ng-if" => "!formData.showPasscodeForm"} + .button.info{"ng-click" => "addPasscodeClicked(); $event.stopPropagation();"} .label Add Passcode - .panel-row - %p Add an app passcode to lock the app and encrypt on-device key storage. + %p Add an app passcode to lock the app and encrypt on-device key storage. - %form.mt-5{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} - %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "autofocus" => "true"} - %input.form-control.mt-10{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} + %form{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} + %input.form-control{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "sn-autofocus" => "true", "should-focus" => "true"} + %input.form-control{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} .button-group.stretch.panel-row.form-submit - .button.info{"type" => "submit"} + %button.button.info{"type" => "submit"} .label Set Passcode %a.panel-row{"ng-click" => "formData.showPasscodeForm = false"} Cancel + .panel-row{"ng-if" => "hasPasscode()"} %p Passcode lock is enabled. %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. - %a.block.mt-5{"ng-click" => "removePasscodePressed()"} Remove Passcode + %a.block.danger{"ng-click" => "removePasscodePressed()"} Remove Passcode + .panel-row{"ng-if" => "!passcodeOptionAvailable()"} %p Passcode lock is only available to permanent sessions. (You chose not to stay signed in.) @@ -166,12 +173,14 @@ .label Import From Backup %div{"ng-if" => "importData.requestPassword"} - %form.panel-form{"ng-submit" => "submitImportPassword()"} + %form.panel-form.stretch{"ng-submit" => "submitImportPassword()"} %p Enter the account password associated with the import file. - %input.form-control.mt-5{:type => 'password', "ng-model" => "importData.password", "autofocus" => "true"} - %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Decrypt & Import - - .spinner.mt-10{"ng-if" => "importData.loading"} + %input.form-control.mt-5{:type => 'password', "placeholder" => "Enter File Account Password", "ng-model" => "importData.password", "autofocus" => "true"} + .button-group.stretch.panel-row.form-submit + %button.button.info{"type" => "submit"} + .label Decrypt & Import + .panel-row + .spinner.small.info{"ng-if" => "importData.loading"} .footer %a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"} Cancel diff --git a/app/assets/templates/frontend/directives/component-view.html.haml b/app/assets/templates/frontend/directives/component-view.html.haml index 3e1b2b3d2..2f7c2bcc8 100644 --- a/app/assets/templates/frontend/directives/component-view.html.haml +++ b/app/assets/templates/frontend/directives/component-view.html.haml @@ -1,6 +1,6 @@ %iframe{"ng-if" => "component", "ng-attr-id" => "component-{{component.uuid}}", "ng-src" => "{{getUrl() | trusted}}", "frameBorder" => "0", -"sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals", +"sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms", "data-component-id" => "{{component.uuid}}"} Loading diff --git a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml index 2a6b89a39..e663efc81 100644 --- a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml +++ b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml @@ -22,7 +22,7 @@ %li{"ng-repeat" => "theme in themeManager.themes | orderBy: 'name'", "ng-click" => "clickedExtension(theme)"} .container %h3 - %input.bold{"ng-if" => "theme.rename", "ng-model" => "theme.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(theme);", "mb-autofocus" => "true", "should-focus" => "true"} + %input.bold{"ng-if" => "theme.rename", "ng-model" => "theme.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(theme);", "sn-autofocus" => "true", "should-focus" => "true"} %span{"ng-if" => "!theme.rename"} {{theme.name}} -# %a{"ng-if" => "!themeManager.isThemeActive(theme)", "ng-click" => "themeManager.activateTheme(theme); $event.stopPropagation();"} Activate -# %a{"ng-if" => "themeManager.isThemeActive(theme)", "ng-click" => "themeManager.deactivateTheme(theme); $event.stopPropagation();"} Deactivate @@ -44,7 +44,7 @@ %li{"ng-repeat" => "extension in extensionManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}", "ng-click" => "clickedExtension(extension)"} .container %h3 - %input.bold{"ng-if" => "extension.rename", "ng-model" => "extension.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(extension);", "mb-autofocus" => "true", "should-focus" => "true"} + %input.bold{"ng-if" => "extension.rename", "ng-model" => "extension.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(extension);", "sn-autofocus" => "true", "should-focus" => "true"} %span{"ng-if" => "!extension.rename"} {{extension.name}} %p.small{"ng-if" => "extension.description"} {{extension.description}} %div{"ng-if" => "extension.showDetails"} @@ -97,7 +97,7 @@ %li{"ng-repeat" => "component in componentManager.components | orderBy: 'name'", "ng-click" => "clickedExtension(component)"} .container %h3 - %input.bold{"ng-if" => "component.rename", "ng-model" => "component.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(component);", "mb-autofocus" => "true", "should-focus" => "true"} + %input.bold{"ng-if" => "component.rename", "ng-model" => "component.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(component);", "sn-autofocus" => "true", "should-focus" => "true"} %span{"ng-if" => "!component.rename"} {{component.name}} %div{"ng-if" => "component.isEditor()"} diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index cb2ac39c1..b62442073 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -5,7 +5,7 @@ "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", "ng-blur" => "ctrl.onNameBlur()", "select-on-click" => "true"} - #save-status{"ng-class" => "{'red bold': ctrl.saveError, 'orange bold': ctrl.syncTakingTooLong}", "ng-bind-html" => "ctrl.noteStatus"} + #save-status{"ng-class" => "{'red bold': ctrl.saveError, 'warning bold': ctrl.syncTakingTooLong}", "ng-bind-html" => "ctrl.noteStatus"} .editor-tags #note-tags-component-container{"ng-if" => "ctrl.tagsComponent"} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index b36357d6f..0cdeb3796 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -3,7 +3,7 @@ .left .item{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} .column - .circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.user ? 'info' : 'default')"} + .circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'default')"} .column{"ng-click" => "ctrl.accountMenuPressed()"} .label.title{"ng-class" => "{red: ctrl.error}"} Account %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess", "close-function" => "ctrl.closeAccountMenu"} diff --git a/app/assets/templates/frontend/lock-screen.html.haml b/app/assets/templates/frontend/lock-screen.html.haml index e79c7cbef..08c2b4472 100644 --- a/app/assets/templates/frontend/lock-screen.html.haml +++ b/app/assets/templates/frontend/lock-screen.html.haml @@ -1,9 +1,14 @@ -#lock-screen - .content - %h3.center-align Passcode Required - - %form.mt-20{"ng-submit" => "submitPasscodeForm()"} - %input.form-control.mt-10{:type => 'password', - "ng-model" => "formData.passcode", "autofocus" => "true", - "placeholder" => "Enter Passcode", "autocomplete" => "new-password"} - %button.standard.ui-button.block.tinted.mt-5{"type" => "submit"} Unlock +#lock-screen.sn-component + .panel + .header + %h1.title Passcode Required + .content + .panel-section + %form.panel-form.panel-row{"ng-submit" => "submitPasscodeForm()"} + .panel-column.stretch + %input.panel-row{:type => 'password', + "ng-model" => "formData.passcode", "autofocus" => "true", "sn-autofocus" => "true", "should-focus" => "true", + "placeholder" => "Enter Passcode", "autocomplete" => "new-password"} + .button-group.stretch.panel-row.form-submit + %button.button.info{"type" => "submit"} + .label Unlock diff --git a/app/assets/templates/frontend/tags.html.haml b/app/assets/templates/frontend/tags.html.haml index e751732f8..cbbc9d214 100644 --- a/app/assets/templates/frontend/tags.html.haml +++ b/app/assets/templates/frontend/tags.html.haml @@ -15,7 +15,7 @@ .tag{"ng-repeat" => "tag in ctrl.tags track by tag.uuid", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}"} .info %input.title{"ng-attr-id" => "tag-{{tag.uuid}}", "ng-click" => "ctrl.selectTag(tag)", "ng-model" => "tag.title", - "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTag($event, tag)", "mb-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag", + "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTag($event, tag)", "sn-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag", "ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"} .count {{ctrl.noteCount(tag)}} diff --git a/package-lock.json b/package-lock.json index 949c34b89..8ba4e4043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "neeto", + "name": "standard-notes", "version": "1.0.0", "lockfileVersion": 1, "requires": true, @@ -5781,9 +5781,9 @@ "dev": true }, "sn-stylekit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1.tgz", - "integrity": "sha512-tmtTUI5Vahsl6WjGL8rvCzi4BIqKz3F7e7y7MgLXM0zvxi3+IObgwDJXEUSHboXkC1RGSEM0mq+yJlyt2DY0+A==" + "version": "1.0.115", + "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.115.tgz", + "integrity": "sha512-NsOS+sJoLBexantCSU/kwFWoRquAVDWs/+lxPQ1UHhEFh2Gj8G7q3omZmmbesTnHvL0vf+XK55Ne8sr50ZWJag==" }, "snake-case": { "version": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", diff --git a/package.json b/package.json index 4ece22493..67c79db5e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,6 @@ }, "license": "GPL-3.0", "dependencies": { - "sn-stylekit": "^1.0.1" + "sn-stylekit": "^1.0.115" } } From 59fb649bd4b8bd08b7cd2e13179805f47637b808 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 18 Jan 2018 12:47:05 -0600 Subject: [PATCH 32/99] Rename extensionsManager to actionsManager --- .../app/frontend/controllers/editor.js | 4 ++-- ...{extensionManager.js => actionsManager.js} | 6 +++--- ...extualExtensionsMenu.js => actionsMenu.js} | 20 ++++++++----------- .../directives/views/globalExtensionsMenu.js | 12 +++++------ .../services/directives/views/panelResizer.js | 2 +- ...-menu.html.haml => actions-menu.html.haml} | 14 ++++++++----- .../frontend/directives/editor-menu.html.haml | 2 +- .../global-extensions-menu.html.haml | 10 +++++----- .../templates/frontend/editor.html.haml | 4 ++-- 9 files changed, 37 insertions(+), 37 deletions(-) rename app/assets/javascripts/app/services/{extensionManager.js => actionsManager.js} (97%) rename app/assets/javascripts/app/services/directives/views/{contextualExtensionsMenu.js => actionsMenu.js} (70%) rename app/assets/templates/frontend/directives/{contextual-menu.html.haml => actions-menu.html.haml} (73%) diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index 4dddf3867..ddc6e83ef 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -23,7 +23,7 @@ angular.module('app.frontend') } } }) - .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, extensionManager, syncManager, modelManager, themeManager, componentManager, storageManager) { + .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager) { this.componentManager = componentManager; this.componentStack = []; @@ -146,7 +146,7 @@ angular.module('app.frontend') }.bind(this) this.hasAvailableExtensions = function() { - return extensionManager.extensionsInContextOfItem(this.note).length > 0; + return actionsManager.extensionsInContextOfItem(this.note).length > 0; } this.focusEditor = function(delay) { diff --git a/app/assets/javascripts/app/services/extensionManager.js b/app/assets/javascripts/app/services/actionsManager.js similarity index 97% rename from app/assets/javascripts/app/services/extensionManager.js rename to app/assets/javascripts/app/services/actionsManager.js index ff7b66cd1..f976dd2ae 100644 --- a/app/assets/javascripts/app/services/extensionManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -1,4 +1,4 @@ -class ExtensionManager { +class ActionsManager { constructor(httpManager, modelManager, authManager, syncManager, storageManager) { this.httpManager = httpManager; @@ -8,7 +8,7 @@ class ExtensionManager { this.syncManager = syncManager; this.storageManager = storageManager; - modelManager.addItemSyncObserver("extensionManager", "Extension", function(allItems, validItems, deletedItems){ + modelManager.addItemSyncObserver("actionsManager", "Extension", function(allItems, validItems, deletedItems){ for (var ext of validItems) { for (var action of ext.actions) { if(_.includes(this.enabledRepeatActionUrls, action.url)) { @@ -334,4 +334,4 @@ class ExtensionManager { } -angular.module('app.frontend').service('extensionManager', ExtensionManager); +angular.module('app.frontend').service('actionsManager', ActionsManager); diff --git a/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/actionsMenu.js similarity index 70% rename from app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js rename to app/assets/javascripts/app/services/directives/views/actionsMenu.js index 3825aed6e..d395dd69f 100644 --- a/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/actionsMenu.js @@ -1,27 +1,23 @@ -class ContextualExtensionsMenu { +class ActionsMenu { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/contextual-menu.html"; + this.templateUrl = "frontend/directives/actions-menu.html"; this.scope = { item: "=" }; } - controller($scope, modelManager, extensionManager) { + controller($scope, modelManager, actionsManager) { 'ngInject'; $scope.renderData = {}; - $scope.extensions = _.map(extensionManager.extensionsInContextOfItem($scope.item), function(ext){ - // why are we cloning deep? commenting out because we want original reference so that extension.hide is saved between menu opens - // return _.cloneDeep(ext); - return ext; - }); + $scope.extensions = actionsManager.extensions; for(let ext of $scope.extensions) { ext.loading = true; - extensionManager.loadExtensionInContextOfItem(ext, $scope.item, function(scopedExtension) { + actionsManager.loadExtensionInContextOfItem(ext, $scope.item, function(scopedExtension) { ext.loading = false; }) } @@ -36,12 +32,12 @@ class ContextualExtensionsMenu { return; } action.running = true; - extensionManager.executeAction(action, extension, $scope.item, function(response){ + actionsManager.executeAction(action, extension, $scope.item, function(response){ action.running = false; $scope.handleActionResponse(action, response); // reload extension actions - extensionManager.loadExtensionInContextOfItem(extension, $scope.item, function(ext){ + actionsManager.loadExtensionInContextOfItem(extension, $scope.item, function(ext){ // keep nested state if(parentAction) { var matchingAction = _.find(ext.actions, {label: parentAction.label}); @@ -83,4 +79,4 @@ class ContextualExtensionsMenu { } -angular.module('app.frontend').directive('contextualExtensionsMenu', () => new ContextualExtensionsMenu); +angular.module('app.frontend').directive('actionsMenu', () => new ActionsMenu); diff --git a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js index 01653a67c..f2dc67cef 100644 --- a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js @@ -7,19 +7,19 @@ class GlobalExtensionsMenu { }; } - controller($scope, extensionManager, syncManager, modelManager, themeManager, componentManager, packageManager) { + controller($scope, actionsManager, syncManager, modelManager, themeManager, componentManager, packageManager) { 'ngInject'; $scope.formData = {}; - $scope.extensionManager = extensionManager; + $scope.actionsManager = actionsManager; $scope.themeManager = themeManager; $scope.componentManager = componentManager; $scope.serverExtensions = modelManager.itemsForContentType("SF|Extension"); $scope.selectedAction = function(action, extension) { - extensionManager.executeAction(action, extension, null, function(response){ + actionsManager.executeAction(action, extension, null, function(response){ if(response && response.error) { action.error = true; alert("There was an error performing this action. Please try again."); @@ -38,13 +38,13 @@ class GlobalExtensionsMenu { $scope.deleteActionExtension = function(extension) { if(confirm("Are you sure you want to delete this extension?")) { - extensionManager.deleteExtension(extension); + actionsManager.deleteExtension(extension); } } $scope.reloadExtensionsPressed = function() { if(confirm("For your security, reloading extensions will disable any currently enabled repeat actions.")) { - extensionManager.refreshExtensionsFromServer(); + actionsManager.refreshExtensionsFromServer(); } } @@ -198,7 +198,7 @@ class GlobalExtensionsMenu { $scope.handleActionLink = function(link, completion) { if(link) { - extensionManager.addExtension(link, function(response){ + actionsManager.addExtension(link, function(response){ if(!response) { alert("Unable to register this extension. Make sure the link is valid and try again."); } else { diff --git a/app/assets/javascripts/app/services/directives/views/panelResizer.js b/app/assets/javascripts/app/services/directives/views/panelResizer.js index 3b6d9e87e..62226c107 100644 --- a/app/assets/javascripts/app/services/directives/views/panelResizer.js +++ b/app/assets/javascripts/app/services/directives/views/panelResizer.js @@ -29,7 +29,7 @@ class PanelResizer { } } - controller($scope, $element, modelManager, extensionManager) { + controller($scope, $element, modelManager, actionsManager) { 'ngInject'; let panel = document.getElementById($scope.panelId); diff --git a/app/assets/templates/frontend/directives/contextual-menu.html.haml b/app/assets/templates/frontend/directives/actions-menu.html.haml similarity index 73% rename from app/assets/templates/frontend/directives/contextual-menu.html.haml rename to app/assets/templates/frontend/directives/actions-menu.html.haml index 3f3c35a7f..59eb519c7 100644 --- a/app/assets/templates/frontend/directives/contextual-menu.html.haml +++ b/app/assets/templates/frontend/directives/actions-menu.html.haml @@ -1,16 +1,20 @@ .sn-component .menu-panel.dropdown-menu + + %a.no-decoration{"ng-if" => "extensions.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} + %menu-row{"title" => "'Download Actions'"} + .section{"ng-repeat" => "extension in extensions"} - .header{"ng-click" => "extension.hide = !extension.hide"} + .header{"ng-click" => "extension.hide = !extension.hide; $event.stopPropagation();"} .column %h4.title {{extension.name}} .subtitle Will submit your note %strong {{accessTypeForExtension(extension)}} - .spinner.loading{"ng-if" => "extension.loading"} + .spinner.small.loading{"ng-if" => "extension.loading"} %div{"ng-if" => "extension.hide"} … - %menu-row{"ng-if" => "!extension.hide", "ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension);", + %menu-row{"ng-if" => "!extension.hide", "ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension); $event.stopPropagation();", "ng-class" => "{'faded' : !isActionEnabled(action, extension)}", "title" => "action.label", "subtitle" => "action.desc"} .small.normal{"ng-if" => "!isActionEnabled(action, extension)"} Requires {{action.access_type}} access to this note. @@ -21,10 +25,10 @@ %label.menu-item-title {{subaction.label}} .menu-item-subtitle {{subaction.desc}} %span{"ng-if" => "subaction.running"} - .spinner{"style" => "margin-top: 3px;"} + .spinner.small{"style" => "margin-top: 3px;"} %span{"ng-if" => "action.running"} - .spinner{"style" => "margin-top: 3px;"} + .spinner.small{"style" => "margin-top: 3px;"} .extension-render-modal{"ng-if" => "renderData.showRenderModal", "ng-click" => "renderData.showRenderModal = false"} .content diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 73fe7b68c..4e0796e6b 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -16,7 +16,7 @@ Available Offline %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} - %menu-row{"title" => "'Download More Editors'", "ng-click" => "moreEditors()"} + %menu-row{"title" => "'Download More Editors'"} .section{"ng-if" => "stack.length > 0"} .header %h4.title Editor Stack diff --git a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml index e663efc81..04af90463 100644 --- a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml +++ b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml @@ -4,7 +4,7 @@ .float-group.h20 %h1.tinted.pull-left Extensions %a.block.pull-right.dashboard-link{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} Open Dashboard - %div.clear{"ng-if" => "!extensionManager.extensions.length && !themeManager.themes.length && !componentManager.components.length"} + %div.clear{"ng-if" => "!actionsManager.extensions.length && !themeManager.themes.length && !componentManager.components.length"} %p Customize your experience with editors, themes, and actions. .tinted-box.mt-10 %h3 Available as part of the Extended subscription. @@ -35,13 +35,13 @@ {{theme.url}} - %div{"ng-if" => "extensionManager.extensions.length"} + %div{"ng-if" => "actionsManager.extensions.length"} .header.container.section-margin %h2 Actions %p{"style" => "margin-top: 3px;"} Choose "Actions" in the note editor to use installed actions. %ul - %li{"ng-repeat" => "extension in extensionManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}", "ng-click" => "clickedExtension(extension)"} + %li{"ng-repeat" => "extension in actionsManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}", "ng-click" => "clickedExtension(extension)"} .container %h3 %input.bold{"ng-if" => "extension.rename", "ng-model" => "extension.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(extension);", "sn-autofocus" => "true", "should-focus" => "true"} @@ -75,8 +75,8 @@ %div .mt-5{"ng-if" => "action.repeat_mode"} - %button.light.tinted{"ng-if" => "extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.disableRepeatAction(action, extension); $event.stopPropagation();"} Disable - %button.light.tinted{"ng-if" => "!extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.enableRepeatAction(action, extension); $event.stopPropagation();"} Enable + %button.light.tinted{"ng-if" => "actionsManager.isRepeatActionEnabled(action)", "ng-click" => "actionsManager.disableRepeatAction(action, extension); $event.stopPropagation();"} Disable + %button.light.tinted{"ng-if" => "!actionsManager.isRepeatActionEnabled(action)", "ng-click" => "actionsManager.enableRepeatAction(action, extension); $event.stopPropagation();"} Enable %button.light.mt-10{"ng-if" => "!action.running && !action.repeat_mode", "ng-click" => "selectedAction(action, extension); $event.stopPropagation();"} Perform Action .spinner.mb-5.block{"ng-if" => "action.running"} diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index b62442073..9e782e135 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -35,9 +35,9 @@ .label Editor %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.editorMenuOnSelect", "selected-editor" => "ctrl.selectedEditor"} - .item{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} + .item{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showExtensions}", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} .label Actions - %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} + %actions-menu{"ng-if" => "ctrl.showExtensions", "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"} From 5be2402f659585c8dede5cacbcd75d4247f6926f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 19 Jan 2018 12:22:17 -0600 Subject: [PATCH 33/99] Action menu updates, ionicons minimal --- .../app/frontend/models/app/extension.js | 58 +- .../app/services/actionsManager.js | 105 +- .../services/directives/views/actionsMenu.js | 40 +- .../app/services/directives/views/menuRow.js | 4 +- app/assets/stylesheets/_ionicons.scss | 2952 +---------------- app/assets/stylesheets/app/_extensions.scss | 97 - app/assets/stylesheets/app/_footer.scss | 56 - app/assets/stylesheets/app/_main.scss | 70 +- app/assets/stylesheets/app/_menus.scss | 25 +- app/assets/stylesheets/app/_modals.scss | 14 + app/assets/stylesheets/app/_standard.scss | 48 - app/assets/stylesheets/app/_stylekit-sub.scss | 12 + app/assets/stylesheets/app/_ui.scss | 54 + app/assets/stylesheets/frontend.css.scss | 4 - .../directives/actions-menu.html.haml | 40 +- .../frontend/directives/editor-menu.html.haml | 6 +- .../frontend/directives/menu-row.html.haml | 7 + .../templates/frontend/footer.html.haml | 2 +- app/assets/templates/frontend/notes.html.haml | 2 +- package-lock.json | 7 +- package.json | 8 +- vendor/assets/fonts/ionicons.eot | Bin 120724 -> 1930 bytes vendor/assets/fonts/ionicons.svg | 2216 +------------ vendor/assets/fonts/ionicons.ttf | Bin 188508 -> 1752 bytes vendor/assets/fonts/ionicons.woff | Bin 67904 -> 1260 bytes 25 files changed, 185 insertions(+), 5642 deletions(-) delete mode 100644 app/assets/stylesheets/app/_extensions.scss delete mode 100644 app/assets/stylesheets/app/_standard.scss diff --git a/app/assets/javascripts/app/frontend/models/app/extension.js b/app/assets/javascripts/app/frontend/models/app/extension.js index 445c4dbb1..4e80f484e 100644 --- a/app/assets/javascripts/app/frontend/models/app/extension.js +++ b/app/assets/javascripts/app/frontend/models/app/extension.js @@ -8,55 +8,12 @@ class Action { this.lastExecuted = new Date(this.lastExecuted); } } - - permissionsString() { - if(!this.permissions) { - return ""; - } - - var permission = this.permissions.charAt(0).toUpperCase() + this.permissions.slice(1); // capitalize first letter - permission += ": "; - for(var contentType of this.content_types) { - if(contentType == "*") { - permission += "All items"; - } else { - permission += contentType; - } - - permission += " "; - } - - return permission; - } - - encryptionModeString() { - if(this.verb != "post") { - return null; - } - var encryptionMode = "This action accepts data "; - if(this.accepts_encrypted && this.accepts_decrypted) { - encryptionMode += "encrypted or decrypted."; - } else { - if(this.accepts_encrypted) { - encryptionMode += "encrypted."; - } else { - encryptionMode += "decrypted."; - } - } - return encryptionMode; - } - } class Extension extends Item { constructor(json) { super(json); - if(this.encrypted === null || this.encrypted === undefined) { - // Default to encrypted on creation. - this.encrypted = true; - } - if(json.actions) { this.actions = json.actions.map(function(action){ return new Action(action); @@ -68,12 +25,6 @@ class Extension extends Item { } } - actionsInGlobalContext() { - return this.actions.filter(function(action){ - return action.context == "global"; - }) - } - actionsWithContextForItem(item) { return this.actions.filter(function(action){ return action.context == item.content_type || action.context == "Item"; @@ -86,12 +37,6 @@ class Extension extends Item { this.description = content.description; this.url = content.url; - if(content.encrypted !== null && content.encrypted !== undefined) { - this.encrypted = content.encrypted; - } else { - this.encrypted = true; - } - this.supported_types = content.supported_types; if(content.actions) { this.actions = content.actions.map(function(action){ @@ -114,8 +59,7 @@ class Extension extends Item { url: this.url, description: this.description, actions: this.actions, - supported_types: this.supported_types, - encrypted: this.encrypted + supported_types: this.supported_types }; _.merge(params, super.structureParams()); diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index f976dd2ae..f2926b139 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -7,16 +7,6 @@ class ActionsManager { this.enabledRepeatActionUrls = JSON.parse(storageManager.getItem("enabledRepeatActionUrls")) || []; this.syncManager = syncManager; this.storageManager = storageManager; - - modelManager.addItemSyncObserver("actionsManager", "Extension", function(allItems, validItems, deletedItems){ - for (var ext of validItems) { - for (var action of ext.actions) { - if(_.includes(this.enabledRepeatActionUrls, action.url)) { - this.enableRepeatAction(action, ext); - } - } - } - }.bind(this)) } get extensions() { @@ -40,14 +30,6 @@ class ActionsManager { } deleteExtension(extension) { - for(var action of extension.actions) { - if(action.repeat_mode) { - if(this.isRepeatActionEnabled(action)) { - this.disableRepeatAction(action); - } - } - } - this.modelManager.setItemToBeDeleted(extension); this.syncManager.sync(null); } @@ -90,7 +72,6 @@ class ActionsManager { handleExtensionLoadExternalResponseItem(url, externalResponseItem) { // Don't allow remote response to set these flags - delete externalResponseItem.encrypted; delete externalResponseItem.uuid; var extension = _.find(this.extensions, {url: url}); @@ -125,13 +106,6 @@ class ActionsManager { } refreshExtensionsFromServer() { - for (var url of this.enabledRepeatActionUrls) { - var action = this.actionWithURL(url); - if(action) { - this.disableRepeatAction(action); - } - } - for(var ext of this.extensions) { this.retrieveExtensionFromServer(ext.url, function(extension){ extension.setDirty(true); @@ -141,12 +115,6 @@ class ActionsManager { executeAction(action, extension, item, callback) { - if(extension.encrypted && this.authManager.offline()) { - alert("To send data encrypted, you must have an encryption key, and must therefore be signed in."); - callback(null); - return; - } - var customCallback = function(response) { action.running = false; callback(response); @@ -154,6 +122,8 @@ class ActionsManager { action.running = true; + let decrypted = action.access_type == "decrypted"; + switch (action.verb) { case "get": { @@ -204,12 +174,12 @@ class ActionsManager { if(action.all) { var items = this.modelManager.allItemsMatchingTypes(action.content_types); params.items = items.map(function(item){ - var params = this.outgoingParamsForItem(item, extension); + var params = this.outgoingParamsForItem(item, extension, decrypted); return params; }.bind(this)) } else { - params.items = [this.outgoingParamsForItem(item, extension)]; + params.items = [this.outgoingParamsForItem(item, extension, decrypted)]; } this.performPost(action, extension, params, function(response){ @@ -231,35 +201,6 @@ class ActionsManager { return _.includes(this.enabledRepeatActionUrls, action.url); } - disableRepeatAction(action, extension) { - _.pull(this.enabledRepeatActionUrls, action.url); - this.storageManager.setItem("enabledRepeatActionUrls", JSON.stringify(this.enabledRepeatActionUrls)); - this.modelManager.removeItemChangeObserver(action.url); - - console.assert(this.isRepeatActionEnabled(action) == false); - } - - enableRepeatAction(action, extension) { - if(!_.find(this.enabledRepeatActionUrls, action.url)) { - this.enabledRepeatActionUrls.push(action.url); - this.storageManager.setItem("enabledRepeatActionUrls", JSON.stringify(this.enabledRepeatActionUrls)); - } - - if(action.repeat_mode) { - - if(action.repeat_mode == "watch") { - this.modelManager.addItemChangeObserver(action.url, action.content_types, function(changedItems){ - this.triggerWatchAction(action, extension, changedItems); - }.bind(this)) - } - - if(action.repeat_mode == "loop") { - // todo - } - - } - } - queueAction(action, extension, delay, changedItems) { this.actionQueue = this.actionQueue || []; if(_.find(this.actionQueue, {url: action.url})) { @@ -274,38 +215,9 @@ class ActionsManager { }.bind(this), delay * 1000); } - triggerWatchAction(action, extension, changedItems) { - if(action.repeat_timeout > 0) { - var lastExecuted = action.lastExecuted; - var diffInSeconds = (new Date() - lastExecuted)/1000; - if(diffInSeconds < action.repeat_timeout) { - var delay = action.repeat_timeout - diffInSeconds; - this.queueAction(action, extension, delay, changedItems); - return; - } - } - - action.lastExecuted = new Date(); - - if(action.verb == "post") { - var params = {}; - params.items = changedItems.map(function(item){ - var params = this.outgoingParamsForItem(item, extension); - return params; - }.bind(this)) - - action.running = true; - this.performPost(action, extension, params, function(){ - action.running = false; - }); - } else { - // todo - } - } - - outgoingParamsForItem(item, extension) { + outgoingParamsForItem(item, extension, decrypted = false) { var keys = this.authManager.keys(); - if(!extension.encrypted) { + if(decrypted) { keys = null; } var itemParams = new ItemParams(item, keys, this.authManager.protocolVersion()); @@ -313,11 +225,6 @@ class ActionsManager { } performPost(action, extension, params, callback) { - - if(extension.encrypted) { - params.auth_params = this.authManager.getAuthParams(); - } - this.httpManager.postAbsolute(action.url, params, function(response){ action.error = false; if(callback) { diff --git a/app/assets/javascripts/app/services/directives/views/actionsMenu.js b/app/assets/javascripts/app/services/directives/views/actionsMenu.js index d395dd69f..f4ef1a4ae 100644 --- a/app/assets/javascripts/app/services/directives/views/actionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/actionsMenu.js @@ -23,12 +23,12 @@ class ActionsMenu { } $scope.executeAction = function(action, extension, parentAction) { - if(!$scope.isActionEnabled(action, extension)) { - alert("This action requires " + action.access_type + " access to this note. You can change this setting in the Extensions menu on the bottom of the app."); - return; - } if(action.verb == "nested") { - action.showNestedActions = !action.showNestedActions; + if(!action.subrows) { + action.subrows = $scope.subRowsForAction(action, extension); + } else { + action.subrows = null; + } return; } action.running = true; @@ -41,7 +41,7 @@ class ActionsMenu { // keep nested state if(parentAction) { var matchingAction = _.find(ext.actions, {label: parentAction.label}); - matchingAction.showNestedActions = true; + matchingAction.subrows = $scope.subRowsForAction(parentAction, extension); } }); }) @@ -60,21 +60,25 @@ class ActionsMenu { } } - $scope.isActionEnabled = function(action, extension) { - if(action.access_type) { - var extEncryptedAccess = extension.encrypted; - if(action.access_type == "decrypted" && extEncryptedAccess) { - return false; - } else if(action.access_type == "encrypted" && !extEncryptedAccess) { - return false; - } + + $scope.subRowsForAction = function(parentAction, extension) { + if(!parentAction.subactions) { + return null; } - return true; + return parentAction.subactions.map((subaction) => { + return { + onClick: ($event) => { + this.executeAction(subaction, extension, parentAction); + $event.stopPropagation(); + }, + title: subaction.label, + subtitle: subaction.desc, + spinnerClass: subaction.running ? 'info' : null + } + }) } - $scope.accessTypeForExtension = function(extension) { - return extension.encrypted ? "encrypted" : "decrypted"; - } + } } diff --git a/app/assets/javascripts/app/services/directives/views/menuRow.js b/app/assets/javascripts/app/services/directives/views/menuRow.js index c3a1061ef..1809a2505 100644 --- a/app/assets/javascripts/app/services/directives/views/menuRow.js +++ b/app/assets/javascripts/app/services/directives/views/menuRow.js @@ -11,7 +11,9 @@ class MenuRow { hasButton: "=", buttonText: "=", buttonClass: "=", - buttonAction: "&" + buttonAction: "&", + spinnerClass: "=", + subRows: "=" }; } diff --git a/app/assets/stylesheets/_ionicons.scss b/app/assets/stylesheets/_ionicons.scss index 47fd1b15c..884e1f781 100644 --- a/app/assets/stylesheets/_ionicons.scss +++ b/app/assets/stylesheets/_ionicons.scss @@ -1,6 +1,6 @@ @charset "UTF-8"; /*! - Ionicons, v2.0.0 + Ionicons, v2.0.1 Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ https://twitter.com/benjsperry https://twitter.com/ionicframework MIT License: https://github.com/driftyco/ionicons @@ -10,2957 +10,15 @@ used under CC BY http://creativecommons.org/licenses/by/4.0/ Modified icons to fit ionicon’s grid from original. */ -@font-face { - font-family: "Ionicons"; - src: url("../assets/ionicons.eot?v=2.0.0"); - src: url("../assets/ionicons.eot?v=2.0.0#iefix") format("embedded-opentype"), url("../assets/ionicons.ttf?v=2.0.0") format("truetype"), url("../assets/ionicons.woff?v=2.0.0") format("woff"), url("../assets/ionicons.svg?v=2.0.0#Ionicons") format("svg"); - font-weight: normal; - font-style: normal; -} -.ion, .ionicons, .ion-alert:before, .ion-alert-circled:before, .ion-android-add:before, .ion-android-add-circle:before, .ion-android-alarm-clock:before, .ion-android-alert:before, .ion-android-apps:before, .ion-android-archive:before, .ion-android-arrow-back:before, .ion-android-arrow-down:before, .ion-android-arrow-dropdown:before, .ion-android-arrow-dropdown-circle:before, .ion-android-arrow-dropleft:before, .ion-android-arrow-dropleft-circle:before, .ion-android-arrow-dropright:before, .ion-android-arrow-dropright-circle:before, .ion-android-arrow-dropup:before, .ion-android-arrow-dropup-circle:before, .ion-android-arrow-forward:before, .ion-android-arrow-up:before, .ion-android-attach:before, .ion-android-bar:before, .ion-android-bicycle:before, .ion-android-boat:before, .ion-android-bookmark:before, .ion-android-bulb:before, .ion-android-bus:before, .ion-android-calendar:before, .ion-android-call:before, .ion-android-camera:before, .ion-android-cancel:before, .ion-android-car:before, .ion-android-cart:before, .ion-android-chat:before, .ion-android-checkbox:before, .ion-android-checkbox-blank:before, .ion-android-checkbox-outline:before, .ion-android-checkbox-outline-blank:before, .ion-android-checkmark-circle:before, .ion-android-clipboard:before, .ion-android-close:before, .ion-android-cloud:before, .ion-android-cloud-circle:before, .ion-android-cloud-done:before, .ion-android-cloud-outline:before, .ion-android-color-palette:before, .ion-android-compass:before, .ion-android-contact:before, .ion-android-contacts:before, .ion-android-contract:before, .ion-android-create:before, .ion-android-delete:before, .ion-android-desktop:before, .ion-android-document:before, .ion-android-done:before, .ion-android-done-all:before, .ion-android-download:before, .ion-android-drafts:before, .ion-android-exit:before, .ion-android-expand:before, .ion-android-favorite:before, .ion-android-favorite-outline:before, .ion-android-film:before, .ion-android-folder:before, .ion-android-folder-open:before, .ion-android-funnel:before, .ion-android-globe:before, .ion-android-hand:before, .ion-android-hangout:before, .ion-android-happy:before, .ion-android-home:before, .ion-android-image:before, .ion-android-laptop:before, .ion-android-list:before, .ion-android-locate:before, .ion-android-lock:before, .ion-android-mail:before, .ion-android-map:before, .ion-android-menu:before, .ion-android-microphone:before, .ion-android-microphone-off:before, .ion-android-more-horizontal:before, .ion-android-more-vertical:before, .ion-android-navigate:before, .ion-android-notifications:before, .ion-android-notifications-none:before, .ion-android-notifications-off:before, .ion-android-open:before, .ion-android-options:before, .ion-android-people:before, .ion-android-person:before, .ion-android-person-add:before, .ion-android-phone-landscape:before, .ion-android-phone-portrait:before, .ion-android-pin:before, .ion-android-plane:before, .ion-android-playstore:before, .ion-android-print:before, .ion-android-radio-button-off:before, .ion-android-radio-button-on:before, .ion-android-refresh:before, .ion-android-remove:before, .ion-android-remove-circle:before, .ion-android-restaurant:before, .ion-android-sad:before, .ion-android-search:before, .ion-android-send:before, .ion-android-settings:before, .ion-android-share:before, .ion-android-share-alt:before, .ion-android-star:before, .ion-android-star-half:before, .ion-android-star-outline:before, .ion-android-stopwatch:before, .ion-android-subway:before, .ion-android-sunny:before, .ion-android-sync:before, .ion-android-textsms:before, .ion-android-time:before, .ion-android-train:before, .ion-android-unlock:before, .ion-android-upload:before, .ion-android-volume-down:before, .ion-android-volume-mute:before, .ion-android-volume-off:before, .ion-android-volume-up:before, .ion-android-walk:before, .ion-android-warning:before, .ion-android-watch:before, .ion-android-wifi:before, .ion-aperture:before, .ion-archive:before, .ion-arrow-down-a:before, .ion-arrow-down-b:before, .ion-arrow-down-c:before, .ion-arrow-expand:before, .ion-arrow-graph-down-left:before, .ion-arrow-graph-down-right:before, .ion-arrow-graph-up-left:before, .ion-arrow-graph-up-right:before, .ion-arrow-left-a:before, .ion-arrow-left-b:before, .ion-arrow-left-c:before, .ion-arrow-move:before, .ion-arrow-resize:before, .ion-arrow-return-left:before, .ion-arrow-return-right:before, .ion-arrow-right-a:before, .ion-arrow-right-b:before, .ion-arrow-right-c:before, .ion-arrow-shrink:before, .ion-arrow-swap:before, .ion-arrow-up-a:before, .ion-arrow-up-b:before, .ion-arrow-up-c:before, .ion-asterisk:before, .ion-at:before, .ion-backspace:before, .ion-backspace-outline:before, .ion-bag:before, .ion-battery-charging:before, .ion-battery-empty:before, .ion-battery-full:before, .ion-battery-half:before, .ion-battery-low:before, .ion-beaker:before, .ion-beer:before, .ion-bluetooth:before, .ion-bonfire:before, .ion-bookmark:before, .ion-bowtie:before, .ion-briefcase:before, .ion-bug:before, .ion-calculator:before, .ion-calendar:before, .ion-camera:before, .ion-card:before, .ion-cash:before, .ion-chatbox:before, .ion-chatbox-working:before, .ion-chatboxes:before, .ion-chatbubble:before, .ion-chatbubble-working:before, .ion-chatbubbles:before, .ion-checkmark:before, .ion-checkmark-circled:before, .ion-checkmark-round:before, .ion-chevron-down:before, .ion-chevron-left:before, .ion-chevron-right:before, .ion-chevron-up:before, .ion-clipboard:before, .ion-clock:before, .ion-close:before, .ion-close-circled:before, .ion-close-round:before, .ion-closed-captioning:before, .ion-cloud:before, .ion-code:before, .ion-code-download:before, .ion-code-working:before, .ion-coffee:before, .ion-compass:before, .ion-compose:before, .ion-connection-bars:before, .ion-contrast:before, .ion-crop:before, .ion-cube:before, .ion-disc:before, .ion-document:before, .ion-document-text:before, .ion-drag:before, .ion-earth:before, .ion-easel:before, .ion-edit:before, .ion-egg:before, .ion-eject:before, .ion-email:before, .ion-email-unread:before, .ion-erlenmeyer-flask:before, .ion-erlenmeyer-flask-bubbles:before, .ion-eye:before, .ion-eye-disabled:before, .ion-female:before, .ion-filing:before, .ion-film-marker:before, .ion-fireball:before, .ion-flag:before, .ion-flame:before, .ion-flash:before, .ion-flash-off:before, .ion-folder:before, .ion-fork:before, .ion-fork-repo:before, .ion-forward:before, .ion-funnel:before, .ion-gear-a:before, .ion-gear-b:before, .ion-grid:before, .ion-hammer:before, .ion-happy:before, .ion-happy-outline:before, .ion-headphone:before, .ion-heart:before, .ion-heart-broken:before, .ion-help:before, .ion-help-buoy:before, .ion-help-circled:before, .ion-home:before, .ion-icecream:before, .ion-image:before, .ion-images:before, .ion-information:before, .ion-information-circled:before, .ion-ionic:before, .ion-ios-alarm:before, .ion-ios-alarm-outline:before, .ion-ios-albums:before, .ion-ios-albums-outline:before, .ion-ios-americanfootball:before, .ion-ios-americanfootball-outline:before, .ion-ios-analytics:before, .ion-ios-analytics-outline:before, .ion-ios-arrow-back:before, .ion-ios-arrow-down:before, .ion-ios-arrow-forward:before, .ion-ios-arrow-left:before, .ion-ios-arrow-right:before, .ion-ios-arrow-thin-down:before, .ion-ios-arrow-thin-left:before, .ion-ios-arrow-thin-right:before, .ion-ios-arrow-thin-up:before, .ion-ios-arrow-up:before, .ion-ios-at:before, .ion-ios-at-outline:before, .ion-ios-barcode:before, .ion-ios-barcode-outline:before, .ion-ios-baseball:before, .ion-ios-baseball-outline:before, .ion-ios-basketball:before, .ion-ios-basketball-outline:before, .ion-ios-bell:before, .ion-ios-bell-outline:before, .ion-ios-body:before, .ion-ios-body-outline:before, .ion-ios-bolt:before, .ion-ios-bolt-outline:before, .ion-ios-book:before, .ion-ios-book-outline:before, .ion-ios-bookmarks:before, .ion-ios-bookmarks-outline:before, .ion-ios-box:before, .ion-ios-box-outline:before, .ion-ios-briefcase:before, .ion-ios-briefcase-outline:before, .ion-ios-browsers:before, .ion-ios-browsers-outline:before, .ion-ios-calculator:before, .ion-ios-calculator-outline:before, .ion-ios-calendar:before, .ion-ios-calendar-outline:before, .ion-ios-camera:before, .ion-ios-camera-outline:before, .ion-ios-cart:before, .ion-ios-cart-outline:before, .ion-ios-chatboxes:before, .ion-ios-chatboxes-outline:before, .ion-ios-chatbubble:before, .ion-ios-chatbubble-outline:before, .ion-ios-checkmark:before, .ion-ios-checkmark-empty:before, .ion-ios-checkmark-outline:before, .ion-ios-circle-filled:before, .ion-ios-circle-outline:before, .ion-ios-clock:before, .ion-ios-clock-outline:before, .ion-ios-close:before, .ion-ios-close-empty:before, .ion-ios-close-outline:before, .ion-ios-cloud:before, .ion-ios-cloud-download:before, .ion-ios-cloud-download-outline:before, .ion-ios-cloud-outline:before, .ion-ios-cloud-upload:before, .ion-ios-cloud-upload-outline:before, .ion-ios-cloudy:before, .ion-ios-cloudy-night:before, .ion-ios-cloudy-night-outline:before, .ion-ios-cloudy-outline:before, .ion-ios-cog:before, .ion-ios-cog-outline:before, .ion-ios-color-filter:before, .ion-ios-color-filter-outline:before, .ion-ios-color-wand:before, .ion-ios-color-wand-outline:before, .ion-ios-compose:before, .ion-ios-compose-outline:before, .ion-ios-contact:before, .ion-ios-contact-outline:before, .ion-ios-copy:before, .ion-ios-copy-outline:before, .ion-ios-crop:before, .ion-ios-crop-strong:before, .ion-ios-download:before, .ion-ios-download-outline:before, .ion-ios-drag:before, .ion-ios-email:before, .ion-ios-email-outline:before, .ion-ios-eye:before, .ion-ios-eye-outline:before, .ion-ios-fastforward:before, .ion-ios-fastforward-outline:before, .ion-ios-filing:before, .ion-ios-filing-outline:before, .ion-ios-film:before, .ion-ios-film-outline:before, .ion-ios-flag:before, .ion-ios-flag-outline:before, .ion-ios-flame:before, .ion-ios-flame-outline:before, .ion-ios-flask:before, .ion-ios-flask-outline:before, .ion-ios-flower:before, .ion-ios-flower-outline:before, .ion-ios-folder:before, .ion-ios-folder-outline:before, .ion-ios-football:before, .ion-ios-football-outline:before, .ion-ios-game-controller-a:before, .ion-ios-game-controller-a-outline:before, .ion-ios-game-controller-b:before, .ion-ios-game-controller-b-outline:before, .ion-ios-gear:before, .ion-ios-gear-outline:before, .ion-ios-glasses:before, .ion-ios-glasses-outline:before, .ion-ios-grid-view:before, .ion-ios-grid-view-outline:before, .ion-ios-heart:before, .ion-ios-heart-outline:before, .ion-ios-help:before, .ion-ios-help-empty:before, .ion-ios-help-outline:before, .ion-ios-home:before, .ion-ios-home-outline:before, .ion-ios-infinite:before, .ion-ios-infinite-outline:before, .ion-ios-information:before, .ion-ios-information-empty:before, .ion-ios-information-outline:before, .ion-ios-ionic-outline:before, .ion-ios-keypad:before, .ion-ios-keypad-outline:before, .ion-ios-lightbulb:before, .ion-ios-lightbulb-outline:before, .ion-ios-list:before, .ion-ios-list-outline:before, .ion-ios-location:before, .ion-ios-location-outline:before, .ion-ios-locked:before, .ion-ios-locked-outline:before, .ion-ios-loop:before, .ion-ios-loop-strong:before, .ion-ios-medical:before, .ion-ios-medical-outline:before, .ion-ios-medkit:before, .ion-ios-medkit-outline:before, .ion-ios-mic:before, .ion-ios-mic-off:before, .ion-ios-mic-outline:before, .ion-ios-minus:before, .ion-ios-minus-empty:before, .ion-ios-minus-outline:before, .ion-ios-monitor:before, .ion-ios-monitor-outline:before, .ion-ios-moon:before, .ion-ios-moon-outline:before, .ion-ios-more:before, .ion-ios-more-outline:before, .ion-ios-musical-note:before, .ion-ios-musical-notes:before, .ion-ios-navigate:before, .ion-ios-navigate-outline:before, .ion-ios-nutrition:before, .ion-ios-nutrition-outline:before, .ion-ios-paper:before, .ion-ios-paper-outline:before, .ion-ios-paperplane:before, .ion-ios-paperplane-outline:before, .ion-ios-partlysunny:before, .ion-ios-partlysunny-outline:before, .ion-ios-pause:before, .ion-ios-pause-outline:before, .ion-ios-paw:before, .ion-ios-paw-outline:before, .ion-ios-people:before, .ion-ios-people-outline:before, .ion-ios-person:before, .ion-ios-person-outline:before, .ion-ios-personadd:before, .ion-ios-personadd-outline:before, .ion-ios-photos:before, .ion-ios-photos-outline:before, .ion-ios-pie:before, .ion-ios-pie-outline:before, .ion-ios-pint:before, .ion-ios-pint-outline:before, .ion-ios-play:before, .ion-ios-play-outline:before, .ion-ios-plus:before, .ion-ios-plus-empty:before, .ion-ios-plus-outline:before, .ion-ios-pricetag:before, .ion-ios-pricetag-outline:before, .ion-ios-pricetags:before, .ion-ios-pricetags-outline:before, .ion-ios-printer:before, .ion-ios-printer-outline:before, .ion-ios-pulse:before, .ion-ios-pulse-strong:before, .ion-ios-rainy:before, .ion-ios-rainy-outline:before, .ion-ios-recording:before, .ion-ios-recording-outline:before, .ion-ios-redo:before, .ion-ios-redo-outline:before, .ion-ios-refresh:before, .ion-ios-refresh-empty:before, .ion-ios-refresh-outline:before, .ion-ios-reload:before, .ion-ios-reverse-camera:before, .ion-ios-reverse-camera-outline:before, .ion-ios-rewind:before, .ion-ios-rewind-outline:before, .ion-ios-rose:before, .ion-ios-rose-outline:before, .ion-ios-search:before, .ion-ios-search-strong:before, .ion-ios-settings:before, .ion-ios-settings-strong:before, .ion-ios-shuffle:before, .ion-ios-shuffle-strong:before, .ion-ios-skipbackward:before, .ion-ios-skipbackward-outline:before, .ion-ios-skipforward:before, .ion-ios-skipforward-outline:before, .ion-ios-snowy:before, .ion-ios-speedometer:before, .ion-ios-speedometer-outline:before, .ion-ios-star:before, .ion-ios-star-half:before, .ion-ios-star-outline:before, .ion-ios-stopwatch:before, .ion-ios-stopwatch-outline:before, .ion-ios-sunny:before, .ion-ios-sunny-outline:before, .ion-ios-telephone:before, .ion-ios-telephone-outline:before, .ion-ios-tennisball:before, .ion-ios-tennisball-outline:before, .ion-ios-thunderstorm:before, .ion-ios-thunderstorm-outline:before, .ion-ios-time:before, .ion-ios-time-outline:before, .ion-ios-timer:before, .ion-ios-timer-outline:before, .ion-ios-toggle:before, .ion-ios-toggle-outline:before, .ion-ios-trash:before, .ion-ios-trash-outline:before, .ion-ios-undo:before, .ion-ios-undo-outline:before, .ion-ios-unlocked:before, .ion-ios-unlocked-outline:before, .ion-ios-upload:before, .ion-ios-upload-outline:before, .ion-ios-videocam:before, .ion-ios-videocam-outline:before, .ion-ios-volume-high:before, .ion-ios-volume-low:before, .ion-ios-wineglass:before, .ion-ios-wineglass-outline:before, .ion-ios-world:before, .ion-ios-world-outline:before, .ion-ipad:before, .ion-iphone:before, .ion-ipod:before, .ion-jet:before, .ion-key:before, .ion-knife:before, .ion-laptop:before, .ion-leaf:before, .ion-levels:before, .ion-lightbulb:before, .ion-link:before, .ion-load-a:before, .ion-load-b:before, .ion-load-c:before, .ion-load-d:before, .ion-location:before, .ion-lock-combination:before, .ion-locked:before, .ion-log-in:before, .ion-log-out:before, .ion-loop:before, .ion-magnet:before, .ion-male:before, .ion-man:before, .ion-map:before, .ion-medkit:before, .ion-merge:before, .ion-mic-a:before, .ion-mic-b:before, .ion-mic-c:before, .ion-minus:before, .ion-minus-circled:before, .ion-minus-round:before, .ion-model-s:before, .ion-monitor:before, .ion-more:before, .ion-mouse:before, .ion-music-note:before, .ion-navicon:before, .ion-navicon-round:before, .ion-navigate:before, .ion-network:before, .ion-no-smoking:before, .ion-nuclear:before, .ion-outlet:before, .ion-paintbrush:before, .ion-paintbucket:before, .ion-paper-airplane:before, .ion-paperclip:before, .ion-pause:before, .ion-person:before, .ion-person-add:before, .ion-person-stalker:before, .ion-pie-graph:before, .ion-pin:before, .ion-pinpoint:before, .ion-pizza:before, .ion-plane:before, .ion-planet:before, .ion-play:before, .ion-playstation:before, .ion-plus:before, .ion-plus-circled:before, .ion-plus-round:before, .ion-podium:before, .ion-pound:before, .ion-power:before, .ion-pricetag:before, .ion-pricetags:before, .ion-printer:before, .ion-pull-request:before, .ion-qr-scanner:before, .ion-quote:before, .ion-radio-waves:before, .ion-record:before, .ion-refresh:before, .ion-reply:before, .ion-reply-all:before, .ion-ribbon-a:before, .ion-ribbon-b:before, .ion-sad:before, .ion-sad-outline:before, .ion-scissors:before, .ion-search:before, .ion-settings:before, .ion-share:before, .ion-shuffle:before, .ion-skip-backward:before, .ion-skip-forward:before, .ion-social-android:before, .ion-social-android-outline:before, .ion-social-angular:before, .ion-social-angular-outline:before, .ion-social-apple:before, .ion-social-apple-outline:before, .ion-social-bitcoin:before, .ion-social-bitcoin-outline:before, .ion-social-buffer:before, .ion-social-buffer-outline:before, .ion-social-chrome:before, .ion-social-chrome-outline:before, .ion-social-codepen:before, .ion-social-codepen-outline:before, .ion-social-css3:before, .ion-social-css3-outline:before, .ion-social-designernews:before, .ion-social-designernews-outline:before, .ion-social-dribbble:before, .ion-social-dribbble-outline:before, .ion-social-dropbox:before, .ion-social-dropbox-outline:before, .ion-social-euro:before, .ion-social-euro-outline:before, .ion-social-facebook:before, .ion-social-facebook-outline:before, .ion-social-foursquare:before, .ion-social-foursquare-outline:before, .ion-social-freebsd-devil:before, .ion-social-github:before, .ion-social-github-outline:before, .ion-social-google:before, .ion-social-google-outline:before, .ion-social-googleplus:before, .ion-social-googleplus-outline:before, .ion-social-hackernews:before, .ion-social-hackernews-outline:before, .ion-social-html5:before, .ion-social-html5-outline:before, .ion-social-instagram:before, .ion-social-instagram-outline:before, .ion-social-javascript:before, .ion-social-javascript-outline:before, .ion-social-linkedin:before, .ion-social-linkedin-outline:before, .ion-social-markdown:before, .ion-social-nodejs:before, .ion-social-octocat:before, .ion-social-pinterest:before, .ion-social-pinterest-outline:before, .ion-social-python:before, .ion-social-reddit:before, .ion-social-reddit-outline:before, .ion-social-rss:before, .ion-social-rss-outline:before, .ion-social-sass:before, .ion-social-skype:before, .ion-social-skype-outline:before, .ion-social-snapchat:before, .ion-social-snapchat-outline:before, .ion-social-tumblr:before, .ion-social-tumblr-outline:before, .ion-social-tux:before, .ion-social-twitch:before, .ion-social-twitch-outline:before, .ion-social-twitter:before, .ion-social-twitter-outline:before, .ion-social-usd:before, .ion-social-usd-outline:before, .ion-social-vimeo:before, .ion-social-vimeo-outline:before, .ion-social-whatsapp:before, .ion-social-whatsapp-outline:before, .ion-social-windows:before, .ion-social-windows-outline:before, .ion-social-wordpress:before, .ion-social-wordpress-outline:before, .ion-social-yahoo:before, .ion-social-yahoo-outline:before, .ion-social-yen:before, .ion-social-yen-outline:before, .ion-social-youtube:before, .ion-social-youtube-outline:before, .ion-soup-can:before, .ion-soup-can-outline:before, .ion-speakerphone:before, .ion-speedometer:before, .ion-spoon:before, .ion-star:before, .ion-stats-bars:before, .ion-steam:before, .ion-stop:before, .ion-thermometer:before, .ion-thumbsdown:before, .ion-thumbsup:before, .ion-toggle:before, .ion-toggle-filled:before, .ion-transgender:before, .ion-trash-a:before, .ion-trash-b:before, .ion-trophy:before, .ion-tshirt:before, .ion-tshirt-outline:before, .ion-umbrella:before, .ion-university:before, .ion-unlocked:before, .ion-upload:before, .ion-usb:before, .ion-videocamera:before, .ion-volume-high:before, .ion-volume-low:before, .ion-volume-medium:before, .ion-volume-mute:before, .ion-wand:before, .ion-waterdrop:before, .ion-wifi:before, .ion-wineglass:before, .ion-woman:before, .ion-wrench:before, .ion-xbox:before { - display: inline-block; - font-family: "Ionicons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - text-rendering: auto; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} +@font-face { font-family: "Ionicons"; src: url("../assets/ionicons.eot?v=2.0.0"); src: url("../assets/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("../assets/ionicons.ttf?v=2.0.1") format("truetype"), url("../assets/ionicons.woff?v=2.0.1") format("woff"), url("../assets/ionicons.svg?v=2.0.1#Ionicons") format("svg"); font-weight: normal; font-style: normal; } +.ion, .ionicons, .ion-ios-box:before, .ion-bookmark:before, .ion-locked:before { display: inline-block; font-family: "Ionicons"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; text-rendering: auto; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -.ion-alert:before { - content: "\f101"; -} +.ion-ios-box:before { content: "\f3ec"; } -.ion-alert-circled:before { - content: "\f100"; -} - -.ion-android-add:before { - content: "\f2c7"; -} - -.ion-android-add-circle:before { - content: "\f359"; -} - -.ion-android-alarm-clock:before { - content: "\f35a"; -} - -.ion-android-alert:before { - content: "\f35b"; -} - -.ion-android-apps:before { - content: "\f35c"; -} - -.ion-android-archive:before { - content: "\f2c9"; -} - -.ion-android-arrow-back:before { - content: "\f2ca"; -} - -.ion-android-arrow-down:before { - content: "\f35d"; -} - -.ion-android-arrow-dropdown:before { - content: "\f35f"; -} - -.ion-android-arrow-dropdown-circle:before { - content: "\f35e"; -} - -.ion-android-arrow-dropleft:before { - content: "\f361"; -} - -.ion-android-arrow-dropleft-circle:before { - content: "\f360"; -} - -.ion-android-arrow-dropright:before { - content: "\f363"; -} - -.ion-android-arrow-dropright-circle:before { - content: "\f362"; -} - -.ion-android-arrow-dropup:before { - content: "\f365"; -} - -.ion-android-arrow-dropup-circle:before { - content: "\f364"; -} - -.ion-android-arrow-forward:before { - content: "\f30f"; -} - -.ion-android-arrow-up:before { - content: "\f366"; -} - -.ion-android-attach:before { - content: "\f367"; -} - -.ion-android-bar:before { - content: "\f368"; -} - -.ion-android-bicycle:before { - content: "\f369"; -} - -.ion-android-boat:before { - content: "\f36a"; -} - -.ion-android-bookmark:before { - content: "\f36b"; -} - -.ion-android-bulb:before { - content: "\f36c"; -} - -.ion-android-bus:before { - content: "\f36d"; -} - -.ion-android-calendar:before { - content: "\f2d1"; -} - -.ion-android-call:before { - content: "\f2d2"; -} - -.ion-android-camera:before { - content: "\f2d3"; -} - -.ion-android-cancel:before { - content: "\f36e"; -} - -.ion-android-car:before { - content: "\f36f"; -} - -.ion-android-cart:before { - content: "\f370"; -} - -.ion-android-chat:before { - content: "\f2d4"; -} - -.ion-android-checkbox:before { - content: "\f374"; -} - -.ion-android-checkbox-blank:before { - content: "\f371"; -} - -.ion-android-checkbox-outline:before { - content: "\f373"; -} - -.ion-android-checkbox-outline-blank:before { - content: "\f372"; -} - -.ion-android-checkmark-circle:before { - content: "\f375"; -} - -.ion-android-clipboard:before { - content: "\f376"; -} - -.ion-android-close:before { - content: "\f2d7"; -} - -.ion-android-cloud:before { - content: "\f37a"; -} - -.ion-android-cloud-circle:before { - content: "\f377"; -} - -.ion-android-cloud-done:before { - content: "\f378"; -} - -.ion-android-cloud-outline:before { - content: "\f379"; -} - -.ion-android-color-palette:before { - content: "\f37b"; -} - -.ion-android-compass:before { - content: "\f37c"; -} - -.ion-android-contact:before { - content: "\f2d8"; -} - -.ion-android-contacts:before { - content: "\f2d9"; -} - -.ion-android-contract:before { - content: "\f37d"; -} - -.ion-android-create:before { - content: "\f37e"; -} - -.ion-android-delete:before { - content: "\f37f"; -} - -.ion-android-desktop:before { - content: "\f380"; -} - -.ion-android-document:before { - content: "\f381"; -} - -.ion-android-done:before { - content: "\f383"; -} - -.ion-android-done-all:before { - content: "\f382"; -} - -.ion-android-download:before { - content: "\f2dd"; -} - -.ion-android-drafts:before { - content: "\f384"; -} - -.ion-android-exit:before { - content: "\f385"; -} - -.ion-android-expand:before { - content: "\f386"; -} - -.ion-android-favorite:before { - content: "\f388"; -} - -.ion-android-favorite-outline:before { - content: "\f387"; -} - -.ion-android-film:before { - content: "\f389"; -} - -.ion-android-folder:before { - content: "\f2e0"; -} - -.ion-android-folder-open:before { - content: "\f38a"; -} - -.ion-android-funnel:before { - content: "\f38b"; -} - -.ion-android-globe:before { - content: "\f38c"; -} - -.ion-android-hand:before { - content: "\f2e3"; -} - -.ion-android-hangout:before { - content: "\f38d"; -} - -.ion-android-happy:before { - content: "\f38e"; -} - -.ion-android-home:before { - content: "\f38f"; -} - -.ion-android-image:before { - content: "\f2e4"; -} - -.ion-android-laptop:before { - content: "\f390"; -} - -.ion-android-list:before { - content: "\f391"; -} - -.ion-android-locate:before { - content: "\f2e9"; -} - -.ion-android-lock:before { - content: "\f392"; -} - -.ion-android-mail:before { - content: "\f2eb"; -} - -.ion-android-map:before { - content: "\f393"; -} - -.ion-android-menu:before { - content: "\f394"; -} - -.ion-android-microphone:before { - content: "\f2ec"; -} - -.ion-android-microphone-off:before { - content: "\f395"; -} - -.ion-android-more-horizontal:before { - content: "\f396"; -} - -.ion-android-more-vertical:before { - content: "\f397"; -} - -.ion-android-navigate:before { - content: "\f398"; -} - -.ion-android-notifications:before { - content: "\f39b"; -} - -.ion-android-notifications-none:before { - content: "\f399"; -} - -.ion-android-notifications-off:before { - content: "\f39a"; -} - -.ion-android-open:before { - content: "\f39c"; -} - -.ion-android-options:before { - content: "\f39d"; -} - -.ion-android-people:before { - content: "\f39e"; -} - -.ion-android-person:before { - content: "\f3a0"; -} - -.ion-android-person-add:before { - content: "\f39f"; -} - -.ion-android-phone-landscape:before { - content: "\f3a1"; -} - -.ion-android-phone-portrait:before { - content: "\f3a2"; -} - -.ion-android-pin:before { - content: "\f3a3"; -} - -.ion-android-plane:before { - content: "\f3a4"; -} - -.ion-android-playstore:before { - content: "\f2f0"; -} - -.ion-android-print:before { - content: "\f3a5"; -} - -.ion-android-radio-button-off:before { - content: "\f3a6"; -} - -.ion-android-radio-button-on:before { - content: "\f3a7"; -} - -.ion-android-refresh:before { - content: "\f3a8"; -} - -.ion-android-remove:before { - content: "\f2f4"; -} - -.ion-android-remove-circle:before { - content: "\f3a9"; -} - -.ion-android-restaurant:before { - content: "\f3aa"; -} - -.ion-android-sad:before { - content: "\f3ab"; -} - -.ion-android-search:before { - content: "\f2f5"; -} - -.ion-android-send:before { - content: "\f2f6"; -} - -.ion-android-settings:before { - content: "\f2f7"; -} - -.ion-android-share:before { - content: "\f2f8"; -} - -.ion-android-share-alt:before { - content: "\f3ac"; -} - -.ion-android-star:before { - content: "\f2fc"; -} - -.ion-android-star-half:before { - content: "\f3ad"; -} - -.ion-android-star-outline:before { - content: "\f3ae"; -} - -.ion-android-stopwatch:before { - content: "\f2fd"; -} - -.ion-android-subway:before { - content: "\f3af"; -} - -.ion-android-sunny:before { - content: "\f3b0"; -} - -.ion-android-sync:before { - content: "\f3b1"; -} - -.ion-android-textsms:before { - content: "\f3b2"; -} - -.ion-android-time:before { - content: "\f3b3"; -} - -.ion-android-train:before { - content: "\f3b4"; -} - -.ion-android-unlock:before { - content: "\f3b5"; -} - -.ion-android-upload:before { - content: "\f3b6"; -} - -.ion-android-volume-down:before { - content: "\f3b7"; -} - -.ion-android-volume-mute:before { - content: "\f3b8"; -} - -.ion-android-volume-off:before { - content: "\f3b9"; -} - -.ion-android-volume-up:before { - content: "\f3ba"; -} - -.ion-android-walk:before { - content: "\f3bb"; -} - -.ion-android-warning:before { - content: "\f3bc"; -} - -.ion-android-watch:before { - content: "\f3bd"; -} - -.ion-android-wifi:before { - content: "\f305"; -} - -.ion-aperture:before { - content: "\f313"; -} - -.ion-archive:before { - content: "\f102"; -} - -.ion-arrow-down-a:before { - content: "\f103"; -} - -.ion-arrow-down-b:before { - content: "\f104"; -} - -.ion-arrow-down-c:before { - content: "\f105"; -} - -.ion-arrow-expand:before { - content: "\f25e"; -} - -.ion-arrow-graph-down-left:before { - content: "\f25f"; -} - -.ion-arrow-graph-down-right:before { - content: "\f260"; -} - -.ion-arrow-graph-up-left:before { - content: "\f261"; -} - -.ion-arrow-graph-up-right:before { - content: "\f262"; -} - -.ion-arrow-left-a:before { - content: "\f106"; -} - -.ion-arrow-left-b:before { - content: "\f107"; -} - -.ion-arrow-left-c:before { - content: "\f108"; -} - -.ion-arrow-move:before { - content: "\f263"; -} - -.ion-arrow-resize:before { - content: "\f264"; -} - -.ion-arrow-return-left:before { - content: "\f265"; -} - -.ion-arrow-return-right:before { - content: "\f266"; -} - -.ion-arrow-right-a:before { - content: "\f109"; -} - -.ion-arrow-right-b:before { - content: "\f10a"; -} - -.ion-arrow-right-c:before { - content: "\f10b"; -} - -.ion-arrow-shrink:before { - content: "\f267"; -} - -.ion-arrow-swap:before { - content: "\f268"; -} - -.ion-arrow-up-a:before { - content: "\f10c"; -} - -.ion-arrow-up-b:before { - content: "\f10d"; -} - -.ion-arrow-up-c:before { - content: "\f10e"; -} - -.ion-asterisk:before { - content: "\f314"; -} - -.ion-at:before { - content: "\f10f"; -} - -.ion-backspace:before { - content: "\f3bf"; -} - -.ion-backspace-outline:before { - content: "\f3be"; -} - -.ion-bag:before { - content: "\f110"; -} - -.ion-battery-charging:before { - content: "\f111"; -} - -.ion-battery-empty:before { - content: "\f112"; -} - -.ion-battery-full:before { - content: "\f113"; -} - -.ion-battery-half:before { - content: "\f114"; -} - -.ion-battery-low:before { - content: "\f115"; -} - -.ion-beaker:before { - content: "\f269"; -} - -.ion-beer:before { - content: "\f26a"; -} - -.ion-bluetooth:before { - content: "\f116"; -} - -.ion-bonfire:before { - content: "\f315"; -} +.ion-locked:before { content: "\f200"; } .ion-bookmark:before { content: "\f26b"; } -.ion-bowtie:before { - content: "\f3c0"; -} - -.ion-briefcase:before { - content: "\f26c"; -} - -.ion-bug:before { - content: "\f2be"; -} - -.ion-calculator:before { - content: "\f26d"; -} - -.ion-calendar:before { - content: "\f117"; -} - -.ion-camera:before { - content: "\f118"; -} - -.ion-card:before { - content: "\f119"; -} - -.ion-cash:before { - content: "\f316"; -} - -.ion-chatbox:before { - content: "\f11b"; -} - -.ion-chatbox-working:before { - content: "\f11a"; -} - -.ion-chatboxes:before { - content: "\f11c"; -} - -.ion-chatbubble:before { - content: "\f11e"; -} - -.ion-chatbubble-working:before { - content: "\f11d"; -} - -.ion-chatbubbles:before { - content: "\f11f"; -} - -.ion-checkmark:before { - content: "\f122"; -} - -.ion-checkmark-circled:before { - content: "\f120"; -} - -.ion-checkmark-round:before { - content: "\f121"; -} - -.ion-chevron-down:before { - content: "\f123"; -} - -.ion-chevron-left:before { - content: "\f124"; -} - -.ion-chevron-right:before { - content: "\f125"; -} - -.ion-chevron-up:before { - content: "\f126"; -} - -.ion-clipboard:before { - content: "\f127"; -} - -.ion-clock:before { - content: "\f26e"; -} - -.ion-close:before { - content: "\f12a"; -} - -.ion-close-circled:before { - content: "\f128"; -} - -.ion-close-round:before { - content: "\f129"; -} - -.ion-closed-captioning:before { - content: "\f317"; -} - -.ion-cloud:before { - content: "\f12b"; -} - -.ion-code:before { - content: "\f271"; -} - -.ion-code-download:before { - content: "\f26f"; -} - -.ion-code-working:before { - content: "\f270"; -} - -.ion-coffee:before { - content: "\f272"; -} - -.ion-compass:before { - content: "\f273"; -} - -.ion-compose:before { - content: "\f12c"; -} - -.ion-connection-bars:before { - content: "\f274"; -} - -.ion-contrast:before { - content: "\f275"; -} - -.ion-crop:before { - content: "\f3c1"; -} - -.ion-cube:before { - content: "\f318"; -} - -.ion-disc:before { - content: "\f12d"; -} - -.ion-document:before { - content: "\f12f"; -} - -.ion-document-text:before { - content: "\f12e"; -} - -.ion-drag:before { - content: "\f130"; -} - -.ion-earth:before { - content: "\f276"; -} - -.ion-easel:before { - content: "\f3c2"; -} - -.ion-edit:before { - content: "\f2bf"; -} - -.ion-egg:before { - content: "\f277"; -} - -.ion-eject:before { - content: "\f131"; -} - -.ion-email:before { - content: "\f132"; -} - -.ion-email-unread:before { - content: "\f3c3"; -} - -.ion-erlenmeyer-flask:before { - content: "\f3c5"; -} - -.ion-erlenmeyer-flask-bubbles:before { - content: "\f3c4"; -} - -.ion-eye:before { - content: "\f133"; -} - -.ion-eye-disabled:before { - content: "\f306"; -} - -.ion-female:before { - content: "\f278"; -} - -.ion-filing:before { - content: "\f134"; -} - -.ion-film-marker:before { - content: "\f135"; -} - -.ion-fireball:before { - content: "\f319"; -} - -.ion-flag:before { - content: "\f279"; -} - -.ion-flame:before { - content: "\f31a"; -} - -.ion-flash:before { - content: "\f137"; -} - -.ion-flash-off:before { - content: "\f136"; -} - -.ion-folder:before { - content: "\f139"; -} - -.ion-fork:before { - content: "\f27a"; -} - -.ion-fork-repo:before { - content: "\f2c0"; -} - -.ion-forward:before { - content: "\f13a"; -} - -.ion-funnel:before { - content: "\f31b"; -} - -.ion-gear-a:before { - content: "\f13d"; -} - -.ion-gear-b:before { - content: "\f13e"; -} - -.ion-grid:before { - content: "\f13f"; -} - -.ion-hammer:before { - content: "\f27b"; -} - -.ion-happy:before { - content: "\f31c"; -} - -.ion-happy-outline:before { - content: "\f3c6"; -} - -.ion-headphone:before { - content: "\f140"; -} - -.ion-heart:before { - content: "\f141"; -} - -.ion-heart-broken:before { - content: "\f31d"; -} - -.ion-help:before { - content: "\f143"; -} - -.ion-help-buoy:before { - content: "\f27c"; -} - -.ion-help-circled:before { - content: "\f142"; -} - -.ion-home:before { - content: "\f144"; -} - -.ion-icecream:before { - content: "\f27d"; -} - -.ion-image:before { - content: "\f147"; -} - -.ion-images:before { - content: "\f148"; -} - -.ion-information:before { - content: "\f14a"; -} - -.ion-information-circled:before { - content: "\f149"; -} - -.ion-ionic:before { - content: "\f14b"; -} - -.ion-ios-alarm:before { - content: "\f3c8"; -} - -.ion-ios-alarm-outline:before { - content: "\f3c7"; -} - -.ion-ios-albums:before { - content: "\f3ca"; -} - -.ion-ios-albums-outline:before { - content: "\f3c9"; -} - -.ion-ios-americanfootball:before { - content: "\f3cc"; -} - -.ion-ios-americanfootball-outline:before { - content: "\f3cb"; -} - -.ion-ios-analytics:before { - content: "\f3ce"; -} - -.ion-ios-analytics-outline:before { - content: "\f3cd"; -} - -.ion-ios-arrow-back:before { - content: "\f3cf"; -} - -.ion-ios-arrow-down:before { - content: "\f3d0"; -} - -.ion-ios-arrow-forward:before { - content: "\f3d1"; -} - -.ion-ios-arrow-left:before { - content: "\f3d2"; -} - -.ion-ios-arrow-right:before { - content: "\f3d3"; -} - -.ion-ios-arrow-thin-down:before { - content: "\f3d4"; -} - -.ion-ios-arrow-thin-left:before { - content: "\f3d5"; -} - -.ion-ios-arrow-thin-right:before { - content: "\f3d6"; -} - -.ion-ios-arrow-thin-up:before { - content: "\f3d7"; -} - -.ion-ios-arrow-up:before { - content: "\f3d8"; -} - -.ion-ios-at:before { - content: "\f3da"; -} - -.ion-ios-at-outline:before { - content: "\f3d9"; -} - -.ion-ios-barcode:before { - content: "\f3dc"; -} - -.ion-ios-barcode-outline:before { - content: "\f3db"; -} - -.ion-ios-baseball:before { - content: "\f3de"; -} - -.ion-ios-baseball-outline:before { - content: "\f3dd"; -} - -.ion-ios-basketball:before { - content: "\f3e0"; -} - -.ion-ios-basketball-outline:before { - content: "\f3df"; -} - -.ion-ios-bell:before { - content: "\f3e2"; -} - -.ion-ios-bell-outline:before { - content: "\f3e1"; -} - -.ion-ios-body:before { - content: "\f3e4"; -} - -.ion-ios-body-outline:before { - content: "\f3e3"; -} - -.ion-ios-bolt:before { - content: "\f3e6"; -} - -.ion-ios-bolt-outline:before { - content: "\f3e5"; -} - -.ion-ios-book:before { - content: "\f3e8"; -} - -.ion-ios-book-outline:before { - content: "\f3e7"; -} - -.ion-ios-bookmarks:before { - content: "\f3ea"; -} - -.ion-ios-bookmarks-outline:before { - content: "\f3e9"; -} - -.ion-ios-box:before { - content: "\f3ec"; -} - -.ion-ios-box-outline:before { - content: "\f3eb"; -} - -.ion-ios-briefcase:before { - content: "\f3ee"; -} - -.ion-ios-briefcase-outline:before { - content: "\f3ed"; -} - -.ion-ios-browsers:before { - content: "\f3f0"; -} - -.ion-ios-browsers-outline:before { - content: "\f3ef"; -} - -.ion-ios-calculator:before { - content: "\f3f2"; -} - -.ion-ios-calculator-outline:before { - content: "\f3f1"; -} - -.ion-ios-calendar:before { - content: "\f3f4"; -} - -.ion-ios-calendar-outline:before { - content: "\f3f3"; -} - -.ion-ios-camera:before { - content: "\f3f6"; -} - -.ion-ios-camera-outline:before { - content: "\f3f5"; -} - -.ion-ios-cart:before { - content: "\f3f8"; -} - -.ion-ios-cart-outline:before { - content: "\f3f7"; -} - -.ion-ios-chatboxes:before { - content: "\f3fa"; -} - -.ion-ios-chatboxes-outline:before { - content: "\f3f9"; -} - -.ion-ios-chatbubble:before { - content: "\f3fc"; -} - -.ion-ios-chatbubble-outline:before { - content: "\f3fb"; -} - -.ion-ios-checkmark:before { - content: "\f3ff"; -} - -.ion-ios-checkmark-empty:before { - content: "\f3fd"; -} - -.ion-ios-checkmark-outline:before { - content: "\f3fe"; -} - -.ion-ios-circle-filled:before { - content: "\f400"; -} - -.ion-ios-circle-outline:before { - content: "\f401"; -} - -.ion-ios-clock:before { - content: "\f403"; -} - -.ion-ios-clock-outline:before { - content: "\f402"; -} - -.ion-ios-close:before { - content: "\f406"; -} - -.ion-ios-close-empty:before { - content: "\f404"; -} - -.ion-ios-close-outline:before { - content: "\f405"; -} - -.ion-ios-cloud:before { - content: "\f40c"; -} - -.ion-ios-cloud-download:before { - content: "\f408"; -} - -.ion-ios-cloud-download-outline:before { - content: "\f407"; -} - -.ion-ios-cloud-outline:before { - content: "\f409"; -} - -.ion-ios-cloud-upload:before { - content: "\f40b"; -} - -.ion-ios-cloud-upload-outline:before { - content: "\f40a"; -} - -.ion-ios-cloudy:before { - content: "\f410"; -} - -.ion-ios-cloudy-night:before { - content: "\f40e"; -} - -.ion-ios-cloudy-night-outline:before { - content: "\f40d"; -} - -.ion-ios-cloudy-outline:before { - content: "\f40f"; -} - -.ion-ios-cog:before { - content: "\f412"; -} - -.ion-ios-cog-outline:before { - content: "\f411"; -} - -.ion-ios-color-filter:before { - content: "\f414"; -} - -.ion-ios-color-filter-outline:before { - content: "\f413"; -} - -.ion-ios-color-wand:before { - content: "\f416"; -} - -.ion-ios-color-wand-outline:before { - content: "\f415"; -} - -.ion-ios-compose:before { - content: "\f418"; -} - -.ion-ios-compose-outline:before { - content: "\f417"; -} - -.ion-ios-contact:before { - content: "\f41a"; -} - -.ion-ios-contact-outline:before { - content: "\f419"; -} - -.ion-ios-copy:before { - content: "\f41c"; -} - -.ion-ios-copy-outline:before { - content: "\f41b"; -} - -.ion-ios-crop:before { - content: "\f41e"; -} - -.ion-ios-crop-strong:before { - content: "\f41d"; -} - -.ion-ios-download:before { - content: "\f420"; -} - -.ion-ios-download-outline:before { - content: "\f41f"; -} - -.ion-ios-drag:before { - content: "\f421"; -} - -.ion-ios-email:before { - content: "\f423"; -} - -.ion-ios-email-outline:before { - content: "\f422"; -} - -.ion-ios-eye:before { - content: "\f425"; -} - -.ion-ios-eye-outline:before { - content: "\f424"; -} - -.ion-ios-fastforward:before { - content: "\f427"; -} - -.ion-ios-fastforward-outline:before { - content: "\f426"; -} - -.ion-ios-filing:before { - content: "\f429"; -} - -.ion-ios-filing-outline:before { - content: "\f428"; -} - -.ion-ios-film:before { - content: "\f42b"; -} - -.ion-ios-film-outline:before { - content: "\f42a"; -} - -.ion-ios-flag:before { - content: "\f42d"; -} - -.ion-ios-flag-outline:before { - content: "\f42c"; -} - -.ion-ios-flame:before { - content: "\f42f"; -} - -.ion-ios-flame-outline:before { - content: "\f42e"; -} - -.ion-ios-flask:before { - content: "\f431"; -} - -.ion-ios-flask-outline:before { - content: "\f430"; -} - -.ion-ios-flower:before { - content: "\f433"; -} - -.ion-ios-flower-outline:before { - content: "\f432"; -} - -.ion-ios-folder:before { - content: "\f435"; -} - -.ion-ios-folder-outline:before { - content: "\f434"; -} - -.ion-ios-football:before { - content: "\f437"; -} - -.ion-ios-football-outline:before { - content: "\f436"; -} - -.ion-ios-game-controller-a:before { - content: "\f439"; -} - -.ion-ios-game-controller-a-outline:before { - content: "\f438"; -} - -.ion-ios-game-controller-b:before { - content: "\f43b"; -} - -.ion-ios-game-controller-b-outline:before { - content: "\f43a"; -} - -.ion-ios-gear:before { - content: "\f43d"; -} - -.ion-ios-gear-outline:before { - content: "\f43c"; -} - -.ion-ios-glasses:before { - content: "\f43f"; -} - -.ion-ios-glasses-outline:before { - content: "\f43e"; -} - -.ion-ios-grid-view:before { - content: "\f441"; -} - -.ion-ios-grid-view-outline:before { - content: "\f440"; -} - -.ion-ios-heart:before { - content: "\f443"; -} - -.ion-ios-heart-outline:before { - content: "\f442"; -} - -.ion-ios-help:before { - content: "\f446"; -} - -.ion-ios-help-empty:before { - content: "\f444"; -} - -.ion-ios-help-outline:before { - content: "\f445"; -} - -.ion-ios-home:before { - content: "\f448"; -} - -.ion-ios-home-outline:before { - content: "\f447"; -} - -.ion-ios-infinite:before { - content: "\f44a"; -} - -.ion-ios-infinite-outline:before { - content: "\f449"; -} - -.ion-ios-information:before { - content: "\f44d"; -} - -.ion-ios-information-empty:before { - content: "\f44b"; -} - -.ion-ios-information-outline:before { - content: "\f44c"; -} - -.ion-ios-ionic-outline:before { - content: "\f44e"; -} - -.ion-ios-keypad:before { - content: "\f450"; -} - -.ion-ios-keypad-outline:before { - content: "\f44f"; -} - -.ion-ios-lightbulb:before { - content: "\f452"; -} - -.ion-ios-lightbulb-outline:before { - content: "\f451"; -} - -.ion-ios-list:before { - content: "\f454"; -} - -.ion-ios-list-outline:before { - content: "\f453"; -} - -.ion-ios-location:before { - content: "\f456"; -} - -.ion-ios-location-outline:before { - content: "\f455"; -} - -.ion-ios-locked:before { - content: "\f458"; -} - -.ion-ios-locked-outline:before { - content: "\f457"; -} - -.ion-ios-loop:before { - content: "\f45a"; -} - -.ion-ios-loop-strong:before { - content: "\f459"; -} - -.ion-ios-medical:before { - content: "\f45c"; -} - -.ion-ios-medical-outline:before { - content: "\f45b"; -} - -.ion-ios-medkit:before { - content: "\f45e"; -} - -.ion-ios-medkit-outline:before { - content: "\f45d"; -} - -.ion-ios-mic:before { - content: "\f461"; -} - -.ion-ios-mic-off:before { - content: "\f45f"; -} - -.ion-ios-mic-outline:before { - content: "\f460"; -} - -.ion-ios-minus:before { - content: "\f464"; -} - -.ion-ios-minus-empty:before { - content: "\f462"; -} - -.ion-ios-minus-outline:before { - content: "\f463"; -} - -.ion-ios-monitor:before { - content: "\f466"; -} - -.ion-ios-monitor-outline:before { - content: "\f465"; -} - -.ion-ios-moon:before { - content: "\f468"; -} - -.ion-ios-moon-outline:before { - content: "\f467"; -} - -.ion-ios-more:before { - content: "\f46a"; -} - -.ion-ios-more-outline:before { - content: "\f469"; -} - -.ion-ios-musical-note:before { - content: "\f46b"; -} - -.ion-ios-musical-notes:before { - content: "\f46c"; -} - -.ion-ios-navigate:before { - content: "\f46e"; -} - -.ion-ios-navigate-outline:before { - content: "\f46d"; -} - -.ion-ios-nutrition:before { - content: "\f470"; -} - -.ion-ios-nutrition-outline:before { - content: "\f46f"; -} - -.ion-ios-paper:before { - content: "\f472"; -} - -.ion-ios-paper-outline:before { - content: "\f471"; -} - -.ion-ios-paperplane:before { - content: "\f474"; -} - -.ion-ios-paperplane-outline:before { - content: "\f473"; -} - -.ion-ios-partlysunny:before { - content: "\f476"; -} - -.ion-ios-partlysunny-outline:before { - content: "\f475"; -} - -.ion-ios-pause:before { - content: "\f478"; -} - -.ion-ios-pause-outline:before { - content: "\f477"; -} - -.ion-ios-paw:before { - content: "\f47a"; -} - -.ion-ios-paw-outline:before { - content: "\f479"; -} - -.ion-ios-people:before { - content: "\f47c"; -} - -.ion-ios-people-outline:before { - content: "\f47b"; -} - -.ion-ios-person:before { - content: "\f47e"; -} - -.ion-ios-person-outline:before { - content: "\f47d"; -} - -.ion-ios-personadd:before { - content: "\f480"; -} - -.ion-ios-personadd-outline:before { - content: "\f47f"; -} - -.ion-ios-photos:before { - content: "\f482"; -} - -.ion-ios-photos-outline:before { - content: "\f481"; -} - -.ion-ios-pie:before { - content: "\f484"; -} - -.ion-ios-pie-outline:before { - content: "\f483"; -} - -.ion-ios-pint:before { - content: "\f486"; -} - -.ion-ios-pint-outline:before { - content: "\f485"; -} - -.ion-ios-play:before { - content: "\f488"; -} - -.ion-ios-play-outline:before { - content: "\f487"; -} - -.ion-ios-plus:before { - content: "\f48b"; -} - -.ion-ios-plus-empty:before { - content: "\f489"; -} - -.ion-ios-plus-outline:before { - content: "\f48a"; -} - -.ion-ios-pricetag:before { - content: "\f48d"; -} - -.ion-ios-pricetag-outline:before { - content: "\f48c"; -} - -.ion-ios-pricetags:before { - content: "\f48f"; -} - -.ion-ios-pricetags-outline:before { - content: "\f48e"; -} - -.ion-ios-printer:before { - content: "\f491"; -} - -.ion-ios-printer-outline:before { - content: "\f490"; -} - -.ion-ios-pulse:before { - content: "\f493"; -} - -.ion-ios-pulse-strong:before { - content: "\f492"; -} - -.ion-ios-rainy:before { - content: "\f495"; -} - -.ion-ios-rainy-outline:before { - content: "\f494"; -} - -.ion-ios-recording:before { - content: "\f497"; -} - -.ion-ios-recording-outline:before { - content: "\f496"; -} - -.ion-ios-redo:before { - content: "\f499"; -} - -.ion-ios-redo-outline:before { - content: "\f498"; -} - -.ion-ios-refresh:before { - content: "\f49c"; -} - -.ion-ios-refresh-empty:before { - content: "\f49a"; -} - -.ion-ios-refresh-outline:before { - content: "\f49b"; -} - -.ion-ios-reload:before { - content: "\f49d"; -} - -.ion-ios-reverse-camera:before { - content: "\f49f"; -} - -.ion-ios-reverse-camera-outline:before { - content: "\f49e"; -} - -.ion-ios-rewind:before { - content: "\f4a1"; -} - -.ion-ios-rewind-outline:before { - content: "\f4a0"; -} - -.ion-ios-rose:before { - content: "\f4a3"; -} - -.ion-ios-rose-outline:before { - content: "\f4a2"; -} - -.ion-ios-search:before { - content: "\f4a5"; -} - -.ion-ios-search-strong:before { - content: "\f4a4"; -} - -.ion-ios-settings:before { - content: "\f4a7"; -} - -.ion-ios-settings-strong:before { - content: "\f4a6"; -} - -.ion-ios-shuffle:before { - content: "\f4a9"; -} - -.ion-ios-shuffle-strong:before { - content: "\f4a8"; -} - -.ion-ios-skipbackward:before { - content: "\f4ab"; -} - -.ion-ios-skipbackward-outline:before { - content: "\f4aa"; -} - -.ion-ios-skipforward:before { - content: "\f4ad"; -} - -.ion-ios-skipforward-outline:before { - content: "\f4ac"; -} - -.ion-ios-snowy:before { - content: "\f4ae"; -} - -.ion-ios-speedometer:before { - content: "\f4b0"; -} - -.ion-ios-speedometer-outline:before { - content: "\f4af"; -} - -.ion-ios-star:before { - content: "\f4b3"; -} - -.ion-ios-star-half:before { - content: "\f4b1"; -} - -.ion-ios-star-outline:before { - content: "\f4b2"; -} - -.ion-ios-stopwatch:before { - content: "\f4b5"; -} - -.ion-ios-stopwatch-outline:before { - content: "\f4b4"; -} - -.ion-ios-sunny:before { - content: "\f4b7"; -} - -.ion-ios-sunny-outline:before { - content: "\f4b6"; -} - -.ion-ios-telephone:before { - content: "\f4b9"; -} - -.ion-ios-telephone-outline:before { - content: "\f4b8"; -} - -.ion-ios-tennisball:before { - content: "\f4bb"; -} - -.ion-ios-tennisball-outline:before { - content: "\f4ba"; -} - -.ion-ios-thunderstorm:before { - content: "\f4bd"; -} - -.ion-ios-thunderstorm-outline:before { - content: "\f4bc"; -} - -.ion-ios-time:before { - content: "\f4bf"; -} - -.ion-ios-time-outline:before { - content: "\f4be"; -} - -.ion-ios-timer:before { - content: "\f4c1"; -} - -.ion-ios-timer-outline:before { - content: "\f4c0"; -} - -.ion-ios-toggle:before { - content: "\f4c3"; -} - -.ion-ios-toggle-outline:before { - content: "\f4c2"; -} - -.ion-ios-trash:before { - content: "\f4c5"; -} - -.ion-ios-trash-outline:before { - content: "\f4c4"; -} - -.ion-ios-undo:before { - content: "\f4c7"; -} - -.ion-ios-undo-outline:before { - content: "\f4c6"; -} - -.ion-ios-unlocked:before { - content: "\f4c9"; -} - -.ion-ios-unlocked-outline:before { - content: "\f4c8"; -} - -.ion-ios-upload:before { - content: "\f4cb"; -} - -.ion-ios-upload-outline:before { - content: "\f4ca"; -} - -.ion-ios-videocam:before { - content: "\f4cd"; -} - -.ion-ios-videocam-outline:before { - content: "\f4cc"; -} - -.ion-ios-volume-high:before { - content: "\f4ce"; -} - -.ion-ios-volume-low:before { - content: "\f4cf"; -} - -.ion-ios-wineglass:before { - content: "\f4d1"; -} - -.ion-ios-wineglass-outline:before { - content: "\f4d0"; -} - -.ion-ios-world:before { - content: "\f4d3"; -} - -.ion-ios-world-outline:before { - content: "\f4d2"; -} - -.ion-ipad:before { - content: "\f1f9"; -} - -.ion-iphone:before { - content: "\f1fa"; -} - -.ion-ipod:before { - content: "\f1fb"; -} - -.ion-jet:before { - content: "\f295"; -} - -.ion-key:before { - content: "\f296"; -} - -.ion-knife:before { - content: "\f297"; -} - -.ion-laptop:before { - content: "\f1fc"; -} - -.ion-leaf:before { - content: "\f1fd"; -} - -.ion-levels:before { - content: "\f298"; -} - -.ion-lightbulb:before { - content: "\f299"; -} - -.ion-link:before { - content: "\f1fe"; -} - -.ion-load-a:before { - content: "\f29a"; -} - -.ion-load-b:before { - content: "\f29b"; -} - -.ion-load-c:before { - content: "\f29c"; -} - -.ion-load-d:before { - content: "\f29d"; -} - -.ion-location:before { - content: "\f1ff"; -} - -.ion-lock-combination:before { - content: "\f4d4"; -} - -.ion-locked:before { - content: "\f200"; -} - -.ion-log-in:before { - content: "\f29e"; -} - -.ion-log-out:before { - content: "\f29f"; -} - -.ion-loop:before { - content: "\f201"; -} - -.ion-magnet:before { - content: "\f2a0"; -} - -.ion-male:before { - content: "\f2a1"; -} - -.ion-man:before { - content: "\f202"; -} - -.ion-map:before { - content: "\f203"; -} - -.ion-medkit:before { - content: "\f2a2"; -} - -.ion-merge:before { - content: "\f33f"; -} - -.ion-mic-a:before { - content: "\f204"; -} - -.ion-mic-b:before { - content: "\f205"; -} - -.ion-mic-c:before { - content: "\f206"; -} - -.ion-minus:before { - content: "\f209"; -} - -.ion-minus-circled:before { - content: "\f207"; -} - -.ion-minus-round:before { - content: "\f208"; -} - -.ion-model-s:before { - content: "\f2c1"; -} - -.ion-monitor:before { - content: "\f20a"; -} - -.ion-more:before { - content: "\f20b"; -} - -.ion-mouse:before { - content: "\f340"; -} - -.ion-music-note:before { - content: "\f20c"; -} - -.ion-navicon:before { - content: "\f20e"; -} - -.ion-navicon-round:before { - content: "\f20d"; -} - -.ion-navigate:before { - content: "\f2a3"; -} - -.ion-network:before { - content: "\f341"; -} - -.ion-no-smoking:before { - content: "\f2c2"; -} - -.ion-nuclear:before { - content: "\f2a4"; -} - -.ion-outlet:before { - content: "\f342"; -} - -.ion-paintbrush:before { - content: "\f4d5"; -} - -.ion-paintbucket:before { - content: "\f4d6"; -} - -.ion-paper-airplane:before { - content: "\f2c3"; -} - -.ion-paperclip:before { - content: "\f20f"; -} - -.ion-pause:before { - content: "\f210"; -} - -.ion-person:before { - content: "\f213"; -} - -.ion-person-add:before { - content: "\f211"; -} - -.ion-person-stalker:before { - content: "\f212"; -} - -.ion-pie-graph:before { - content: "\f2a5"; -} - -.ion-pin:before { - content: "\f2a6"; -} - -.ion-pinpoint:before { - content: "\f2a7"; -} - -.ion-pizza:before { - content: "\f2a8"; -} - -.ion-plane:before { - content: "\f214"; -} - -.ion-planet:before { - content: "\f343"; -} - -.ion-play:before { - content: "\f215"; -} - -.ion-playstation:before { - content: "\f30a"; -} - -.ion-plus:before { - content: "\f218"; -} - -.ion-plus-circled:before { - content: "\f216"; -} - -.ion-plus-round:before { - content: "\f217"; -} - -.ion-podium:before { - content: "\f344"; -} - -.ion-pound:before { - content: "\f219"; -} - -.ion-power:before { - content: "\f2a9"; -} - -.ion-pricetag:before { - content: "\f2aa"; -} - -.ion-pricetags:before { - content: "\f2ab"; -} - -.ion-printer:before { - content: "\f21a"; -} - -.ion-pull-request:before { - content: "\f345"; -} - -.ion-qr-scanner:before { - content: "\f346"; -} - -.ion-quote:before { - content: "\f347"; -} - -.ion-radio-waves:before { - content: "\f2ac"; -} - -.ion-record:before { - content: "\f21b"; -} - -.ion-refresh:before { - content: "\f21c"; -} - -.ion-reply:before { - content: "\f21e"; -} - -.ion-reply-all:before { - content: "\f21d"; -} - -.ion-ribbon-a:before { - content: "\f348"; -} - -.ion-ribbon-b:before { - content: "\f349"; -} - -.ion-sad:before { - content: "\f34a"; -} - -.ion-sad-outline:before { - content: "\f4d7"; -} - -.ion-scissors:before { - content: "\f34b"; -} - -.ion-search:before { - content: "\f21f"; -} - -.ion-settings:before { - content: "\f2ad"; -} - -.ion-share:before { - content: "\f220"; -} - -.ion-shuffle:before { - content: "\f221"; -} - -.ion-skip-backward:before { - content: "\f222"; -} - -.ion-skip-forward:before { - content: "\f223"; -} - -.ion-social-android:before { - content: "\f225"; -} - -.ion-social-android-outline:before { - content: "\f224"; -} - -.ion-social-angular:before { - content: "\f4d9"; -} - -.ion-social-angular-outline:before { - content: "\f4d8"; -} - -.ion-social-apple:before { - content: "\f227"; -} - -.ion-social-apple-outline:before { - content: "\f226"; -} - -.ion-social-bitcoin:before { - content: "\f2af"; -} - -.ion-social-bitcoin-outline:before { - content: "\f2ae"; -} - -.ion-social-buffer:before { - content: "\f229"; -} - -.ion-social-buffer-outline:before { - content: "\f228"; -} - -.ion-social-chrome:before { - content: "\f4db"; -} - -.ion-social-chrome-outline:before { - content: "\f4da"; -} - -.ion-social-codepen:before { - content: "\f4dd"; -} - -.ion-social-codepen-outline:before { - content: "\f4dc"; -} - -.ion-social-css3:before { - content: "\f4df"; -} - -.ion-social-css3-outline:before { - content: "\f4de"; -} - -.ion-social-designernews:before { - content: "\f22b"; -} - -.ion-social-designernews-outline:before { - content: "\f22a"; -} - -.ion-social-dribbble:before { - content: "\f22d"; -} - -.ion-social-dribbble-outline:before { - content: "\f22c"; -} - -.ion-social-dropbox:before { - content: "\f22f"; -} - -.ion-social-dropbox-outline:before { - content: "\f22e"; -} - -.ion-social-euro:before { - content: "\f4e1"; -} - -.ion-social-euro-outline:before { - content: "\f4e0"; -} - -.ion-social-facebook:before { - content: "\f231"; -} - -.ion-social-facebook-outline:before { - content: "\f230"; -} - -.ion-social-foursquare:before { - content: "\f34d"; -} - -.ion-social-foursquare-outline:before { - content: "\f34c"; -} - -.ion-social-freebsd-devil:before { - content: "\f2c4"; -} - -.ion-social-github:before { - content: "\f233"; -} - -.ion-social-github-outline:before { - content: "\f232"; -} - -.ion-social-google:before { - content: "\f34f"; -} - -.ion-social-google-outline:before { - content: "\f34e"; -} - -.ion-social-googleplus:before { - content: "\f235"; -} - -.ion-social-googleplus-outline:before { - content: "\f234"; -} - -.ion-social-hackernews:before { - content: "\f237"; -} - -.ion-social-hackernews-outline:before { - content: "\f236"; -} - -.ion-social-html5:before { - content: "\f4e3"; -} - -.ion-social-html5-outline:before { - content: "\f4e2"; -} - -.ion-social-instagram:before { - content: "\f351"; -} - -.ion-social-instagram-outline:before { - content: "\f350"; -} - -.ion-social-javascript:before { - content: "\f4e5"; -} - -.ion-social-javascript-outline:before { - content: "\f4e4"; -} - -.ion-social-linkedin:before { - content: "\f239"; -} - -.ion-social-linkedin-outline:before { - content: "\f238"; -} - -.ion-social-markdown:before { - content: "\f4e6"; -} - -.ion-social-nodejs:before { - content: "\f4e7"; -} - -.ion-social-octocat:before { - content: "\f4e8"; -} - -.ion-social-pinterest:before { - content: "\f2b1"; -} - -.ion-social-pinterest-outline:before { - content: "\f2b0"; -} - -.ion-social-python:before { - content: "\f4e9"; -} - -.ion-social-reddit:before { - content: "\f23b"; -} - -.ion-social-reddit-outline:before { - content: "\f23a"; -} - -.ion-social-rss:before { - content: "\f23d"; -} - -.ion-social-rss-outline:before { - content: "\f23c"; -} - -.ion-social-sass:before { - content: "\f4ea"; -} - -.ion-social-skype:before { - content: "\f23f"; -} - -.ion-social-skype-outline:before { - content: "\f23e"; -} - -.ion-social-snapchat:before { - content: "\f4ec"; -} - -.ion-social-snapchat-outline:before { - content: "\f4eb"; -} - -.ion-social-tumblr:before { - content: "\f241"; -} - -.ion-social-tumblr-outline:before { - content: "\f240"; -} - -.ion-social-tux:before { - content: "\f2c5"; -} - -.ion-social-twitch:before { - content: "\f4ee"; -} - -.ion-social-twitch-outline:before { - content: "\f4ed"; -} - -.ion-social-twitter:before { - content: "\f243"; -} - -.ion-social-twitter-outline:before { - content: "\f242"; -} - -.ion-social-usd:before { - content: "\f353"; -} - -.ion-social-usd-outline:before { - content: "\f352"; -} - -.ion-social-vimeo:before { - content: "\f245"; -} - -.ion-social-vimeo-outline:before { - content: "\f244"; -} - -.ion-social-whatsapp:before { - content: "\f4f0"; -} - -.ion-social-whatsapp-outline:before { - content: "\f4ef"; -} - -.ion-social-windows:before { - content: "\f247"; -} - -.ion-social-windows-outline:before { - content: "\f246"; -} - -.ion-social-wordpress:before { - content: "\f249"; -} - -.ion-social-wordpress-outline:before { - content: "\f248"; -} - -.ion-social-yahoo:before { - content: "\f24b"; -} - -.ion-social-yahoo-outline:before { - content: "\f24a"; -} - -.ion-social-yen:before { - content: "\f4f2"; -} - -.ion-social-yen-outline:before { - content: "\f4f1"; -} - -.ion-social-youtube:before { - content: "\f24d"; -} - -.ion-social-youtube-outline:before { - content: "\f24c"; -} - -.ion-soup-can:before { - content: "\f4f4"; -} - -.ion-soup-can-outline:before { - content: "\f4f3"; -} - -.ion-speakerphone:before { - content: "\f2b2"; -} - -.ion-speedometer:before { - content: "\f2b3"; -} - -.ion-spoon:before { - content: "\f2b4"; -} - -.ion-star:before { - content: "\f24e"; -} - -.ion-stats-bars:before { - content: "\f2b5"; -} - -.ion-steam:before { - content: "\f30b"; -} - -.ion-stop:before { - content: "\f24f"; -} - -.ion-thermometer:before { - content: "\f2b6"; -} - -.ion-thumbsdown:before { - content: "\f250"; -} - -.ion-thumbsup:before { - content: "\f251"; -} - -.ion-toggle:before { - content: "\f355"; -} - -.ion-toggle-filled:before { - content: "\f354"; -} - -.ion-transgender:before { - content: "\f4f5"; -} - -.ion-trash-a:before { - content: "\f252"; -} - -.ion-trash-b:before { - content: "\f253"; -} - -.ion-trophy:before { - content: "\f356"; -} - -.ion-tshirt:before { - content: "\f4f7"; -} - -.ion-tshirt-outline:before { - content: "\f4f6"; -} - -.ion-umbrella:before { - content: "\f2b7"; -} - -.ion-university:before { - content: "\f357"; -} - -.ion-unlocked:before { - content: "\f254"; -} - -.ion-upload:before { - content: "\f255"; -} - -.ion-usb:before { - content: "\f2b8"; -} - -.ion-videocamera:before { - content: "\f256"; -} - -.ion-volume-high:before { - content: "\f257"; -} - -.ion-volume-low:before { - content: "\f258"; -} - -.ion-volume-medium:before { - content: "\f259"; -} - -.ion-volume-mute:before { - content: "\f25a"; -} - -.ion-wand:before { - content: "\f358"; -} - -.ion-waterdrop:before { - content: "\f25b"; -} - -.ion-wifi:before { - content: "\f25c"; -} - -.ion-wineglass:before { - content: "\f2b9"; -} - -.ion-woman:before { - content: "\f25d"; -} - -.ion-wrench:before { - content: "\f2ba"; -} - -.ion-xbox:before { - content: "\f30c"; -} - /*# sourceMappingURL=ionicons.css.map */ diff --git a/app/assets/stylesheets/app/_extensions.scss b/app/assets/stylesheets/app/_extensions.scss deleted file mode 100644 index 71edefcdb..000000000 --- a/app/assets/stylesheets/app/_extensions.scss +++ /dev/null @@ -1,97 +0,0 @@ -.extension-render-modal { - position: fixed; - margin-left: auto; - margin-right: auto; - left: 0; - right: 0; - top: 0; - bottom: 0; - z-index: 10000; - width: 100vw; - height: 100vh; - background-color: rgba(gray, 0.3); - color: black; - - .content { - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - background-color: white; - width: 700px; - height: 500px; - margin: auto; - padding: 25px; - position: absolute; - top: 0; left: 0; bottom: 0; right: 0; - overflow-y: scroll; - } -} - -#global-ext-menu { - color: black; - .panel-body { - padding: 0; - } - - .container { - padding: 13px 18px; - - &.no-bottom { - padding-bottom: 0; - } - } - - p { - font-size: 14px; - - &.small { - font-size: 12px; - } - } - - .link-group { - a { - margin-right: 2px; - } - } - - .dashboard-link { - padding-top: 12px; - font-weight: normal; - } - - .section-margin { - margin-top: 20px; - } - - input { - border: 1px solid $blue-color; - border-radius: 2px; - } - - .header { - padding-bottom: 12px; - } - - ul { - border-top: 1px solid $light-bg-color; - border-bottom: 1px solid $light-bg-color; - margin: 0; - padding: 0; - - li { - cursor: pointer; - background-color: rgba($light-bg-color, 0.2); - &:hover { - background-color: rgba($light-bg-color, 0.4); - } - &:not(:last-child) { - border-bottom: 1px solid $light-bg-color; - } - } - } - -} - -ul { - margin: 0; - padding: 0; -} diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index 0694c4e2e..5eb9851d0 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -21,12 +21,6 @@ z-index: 1000; margin-top: 15px; background-color: white; - - .close-button { - &:hover { - text-decoration: none; - } - } } } @@ -43,58 +37,8 @@ a.disabled { pointer-events: none; } - .icon.ion-locked { margin-left: 5px; border-left: 1px solid gray; padding-left: 8px; } - - - - -/* Global Ext Menu */ - -.ext-section { - - min-height: 50px; - - h1 { - margin: 0; - padding: 0; - padding-top: 5px; - } - - &.opened { - h1 { - padding-top: 0px; - // padding-bottom: 6px; - } - } -} - -.room-iframe { - width: 100%; - height: 100%; -} - - - -// .spinner { -// height: 10px; -// width: 10px; -// animation: rotate 0.8s infinite linear; -// border: 1px solid #515263; -// border-right-color: transparent; -// border-radius: 50%; -// -// &.tinted { -// border: 1px solid $blue-color; -// border-right-color: transparent; -// } -// } -// -// @keyframes rotate { -// 0% { transform: rotate(0deg); } -// 100% { transform: rotate(360deg); } -// } diff --git a/app/assets/stylesheets/app/_main.scss b/app/assets/stylesheets/app/_main.scss index a6cd228f9..8dc27c5a5 100644 --- a/app/assets/stylesheets/app/_main.scss +++ b/app/assets/stylesheets/app/_main.scss @@ -22,7 +22,15 @@ body { } * { - box-sizing: border-box; + box-sizing: border-box; +} + +.tinted { + color: $blue-color; +} + +.tinted-selected { + color: white; } *:focus {outline:0;} @@ -37,51 +45,6 @@ input, button, select, textarea { line-height: inherit; } -.tinted { - color: $blue-color; -} - -.tinted-selected { - color: white; -} - -.tinted-box { - background-color: $blue-color; - color: white; - border-radius: 4px; - padding: 16px 20px; - - button { - background-color: white; - color: $blue-color; - border-radius: 3px; - font-weight: bold; - padding: 6px 20px; - width: 100%; - &:hover { - text-decoration: underline; - } - } -} - -.dark-button { - background-color: #2e2e2e; - border: 0; - padding: 6px 18px; - font-size: 16px; - cursor: pointer; - color: #fff; - border-radius: 2px; - border: 1px solid transparent; - -webkit-appearance: none; - -webkit-font-smoothing: antialiased; - -webkit-tap-highlight-color: transparent; - - &:hover { - background-color: black; - } -} - a { color: $blue-color; text-decoration: none; @@ -120,21 +83,6 @@ $footer-height: 32px; overflow: hidden; position: relative; - .light-button { - background-color: $bg-color; - font-weight: bold; - color: $main-text-color; - font-size: 16px; - text-align: center; - height: 35px; - border-radius: 4px; - padding-top: 6px; - - &:hover { - background-color: #cdcdcd; - } - } - panel-resizer { top: 0; right: 0; diff --git a/app/assets/stylesheets/app/_menus.scss b/app/assets/stylesheets/app/_menus.scss index 3defb1d9e..362eaf767 100644 --- a/app/assets/stylesheets/app/_menus.scss +++ b/app/assets/stylesheets/app/_menus.scss @@ -5,19 +5,16 @@ } .dropdown-menu { - position: absolute; - top: 100%; - left: 0; - float: left; - min-width: 160px; - z-index: 100; + position: absolute; + top: 100%; + left: 0; + float: left; + min-width: 160px; + z-index: 100; - margin-top: 5px; - - width: 280px; - - max-height: calc(85vh - 90px); - - background-color: white; - color: $selected-text-color; + margin-top: 5px; + width: 280px; + max-height: calc(85vh - 90px); + background-color: white; + color: $selected-text-color; } diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index fb0ab7435..1bf064a03 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -37,6 +37,20 @@ align-items: center; justify-content: center; + .sn-component { + height: 100%; + .panel { + height: 100%; + } + } + + &.medium { + > .content { + width: 700px; + height: 500px; + } + } + .background { position: absolute; z-index: -1; diff --git a/app/assets/stylesheets/app/_standard.scss b/app/assets/stylesheets/app/_standard.scss deleted file mode 100644 index 134609878..000000000 --- a/app/assets/stylesheets/app/_standard.scss +++ /dev/null @@ -1,48 +0,0 @@ -.mt-5 { - margin-top: 5px !important; -} - -.mt-10 { - margin-top: 10px !important; -} - -.faded { - opacity: 0.5; -} - -.center-align { - text-align: center !important; -} - -.block { - display: block !important; -} - -.wrap { - word-wrap: break-word; - word-break: break-all; -} - -.medium-padding { - padding: 10px !important; -} - -.red { - color: red !important; -} - -.bold { - font-weight: bold !important; -} - -.normal { - font-weight: normal !important; -} - -.small { - font-size: 10px; -} - -.medium { - font-size: 14px !important; -} diff --git a/app/assets/stylesheets/app/_stylekit-sub.scss b/app/assets/stylesheets/app/_stylekit-sub.scss index 15fd3da56..5fb32e5bc 100644 --- a/app/assets/stylesheets/app/_stylekit-sub.scss +++ b/app/assets/stylesheets/app/_stylekit-sub.scss @@ -1,6 +1,18 @@ +.sn-component { + +} + .panel { color: black; + .header { + .close-button { + &:hover { + text-decoration: none; + } + } + } + input { min-height: 39px; } diff --git a/app/assets/stylesheets/app/_ui.scss b/app/assets/stylesheets/app/_ui.scss index 1a48b6ca2..91b0be017 100644 --- a/app/assets/stylesheets/app/_ui.scss +++ b/app/assets/stylesheets/app/_ui.scss @@ -56,3 +56,57 @@ $screen-md-max: ($screen-lg-min - 1) !default; @content; } } + +.selectable { + user-select: text !important; + cursor: text; +} + +.mt-5 { + margin-top: 5px !important; +} + +.mt-10 { + margin-top: 10px !important; +} + +.faded { + opacity: 0.5; +} + +.center-align { + text-align: center !important; +} + +.block { + display: block !important; +} + +.wrap { + word-wrap: break-word; + word-break: break-all; +} + +.medium-padding { + padding: 10px !important; +} + +.red { + color: red !important; +} + +.bold { + font-weight: bold !important; +} + +.normal { + font-weight: normal !important; +} + +.small { + font-size: 10px; +} + +.medium { + font-size: 14px !important; +} diff --git a/app/assets/stylesheets/frontend.css.scss b/app/assets/stylesheets/frontend.css.scss index 2097406ef..f33649227 100644 --- a/app/assets/stylesheets/frontend.css.scss +++ b/app/assets/stylesheets/frontend.css.scss @@ -1,13 +1,9 @@ -$dark-gray: #2e2e2e; - -@import "app/standard"; @import "app/main"; @import "app/ui"; @import "app/footer"; @import "app/tags"; @import "app/notes"; @import "app/editor"; -@import "app/extensions"; @import "app/menus"; @import "app/modals"; @import "app/lock-screen"; diff --git a/app/assets/templates/frontend/directives/actions-menu.html.haml b/app/assets/templates/frontend/directives/actions-menu.html.haml index 59eb519c7..52470f972 100644 --- a/app/assets/templates/frontend/directives/actions-menu.html.haml +++ b/app/assets/templates/frontend/directives/actions-menu.html.haml @@ -4,33 +4,29 @@ %a.no-decoration{"ng-if" => "extensions.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download Actions'"} - .section{"ng-repeat" => "extension in extensions"} + %div{"ng-repeat" => "extension in extensions"} .header{"ng-click" => "extension.hide = !extension.hide; $event.stopPropagation();"} .column %h4.title {{extension.name}} - .subtitle - Will submit your note - %strong {{accessTypeForExtension(extension)}} .spinner.small.loading{"ng-if" => "extension.loading"} %div{"ng-if" => "extension.hide"} … - %menu-row{"ng-if" => "!extension.hide", "ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension); $event.stopPropagation();", - "ng-class" => "{'faded' : !isActionEnabled(action, extension)}", "title" => "action.label", "subtitle" => "action.desc"} - .small.normal{"ng-if" => "!isActionEnabled(action, extension)"} - Requires {{action.access_type}} access to this note. + %menu-row{"ng-if" => "!extension.hide", "ng-repeat" => "action in extension.actionsWithContextForItem(item)", + "ng-click" => "executeAction(action, extension); $event.stopPropagation();", "title" => "action.label", "subtitle" => "action.desc", + "spinner-class" => "action.running ? 'info' : null", "sub-rows" => "action.subrows"} + .sublabel{"ng-if" => "action.access_type"} + Uses + %strong {{action.access_type}} + access to this note. - %div{"ng-if" => "action.showNestedActions"} - %ul.mt-10 - %li.menu-item.white-bg.nested-hover{"ng-repeat" => "subaction in action.subactions", "ng-click" => "executeAction(subaction, extension, action); $event.stopPropagation();", "style" => "margin-top: -1px;"} - %label.menu-item-title {{subaction.label}} - .menu-item-subtitle {{subaction.desc}} - %span{"ng-if" => "subaction.running"} - .spinner.small{"style" => "margin-top: 3px;"} - %span{"ng-if" => "action.running"} - .spinner.small{"style" => "margin-top: 3px;"} - - .extension-render-modal{"ng-if" => "renderData.showRenderModal", "ng-click" => "renderData.showRenderModal = false"} - .content - %h2 {{renderData.title}} - %p.normal{"style" => "white-space: pre-wrap; font-family: monospace; font-size: 16px;"} {{renderData.text}} +.modal.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-family: monospace; font-size: 16px;"} {{renderData.text}} diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 4e0796e6b..f6508d3af 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -9,7 +9,7 @@ "circle" => "selectedEditor === editor && 'success'", "has-button" => "selectedEditor == editor || defaultEditor == editor", "button-text" => "defaultEditor == editor ? 'Undefault' : 'Set Default'", "button-action" => "toggleDefaultForEditor(editor)", "button-class" => "defaultEditor == editor ? 'warning' : 'info'"} - .row + .row{"ng-if" => "component.conflict_of || offlineAvailableForComponent(editor)"} .column %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy .sublabel{"ng-if" => "offlineAvailableForComponent(editor)"} @@ -22,8 +22,8 @@ %h4.title Editor Stack %menu-row{"ng-repeat" => "component in stack", "ng-click" => "selectComponent($event, component)", "title" => "component.name", "circle" => "component.active ? 'success' : 'danger'"} - .row + .row{"ng-if" => "component.conflict_of || offlineAvailableForComponent(component)"} .column %strong.red.medium{"ng-if" => "component.conflict_of"} Conflicted copy - .sublabel{"ng-if" => "component.local_url"} + .sublabel{"ng-if" => "offlineAvailableForComponent(component)"} Available Offline diff --git a/app/assets/templates/frontend/directives/menu-row.html.haml b/app/assets/templates/frontend/directives/menu-row.html.haml index df8d0dea3..f710800cc 100644 --- a/app/assets/templates/frontend/directives/menu-row.html.haml +++ b/app/assets/templates/frontend/directives/menu-row.html.haml @@ -9,6 +9,13 @@ .sublabel{"ng-if" => "subtitle"} {{subtitle}} %ng-transclude + .subrows{"ng-if" => "subRows && subRows.length > 0"} + %menu-row{"ng-repeat" => "row in subRows", "ng-click" => "row.onClick($event); $event.stopPropagation();", + "title" => "row.title", "subtitle" => "row.subtitle", "spinner-class" => "row.spinnerClass"} + .column{"ng-if" => "hasButton"} .button.info{"ng-click" => "clickButton($event)", "ng-class" => "buttonClass"} {{buttonText}} + + .column{"ng-if" => "spinnerClass"} + .spinner.small{"ng-class" => "spinnerClass"} diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/frontend/footer.html.haml index 0cdeb3796..8712d3b83 100644 --- a/app/assets/templates/frontend/footer.html.haml +++ b/app/assets/templates/frontend/footer.html.haml @@ -28,7 +28,7 @@ .right .item{"ng-if" => "ctrl.newUpdateAvailable", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"} - %span.tinted.normal New update downloaded. Installs on app restart. + %span.info.normal New update downloaded. Installs on app restart. .item.no-pointer{"ng-if" => "ctrl.lastSyncDate && !ctrl.isRefreshing"} .label.subtle diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index 5574d5a1c..e9a62fdfc 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -48,7 +48,7 @@ %strong.red.medium{"ng-if" => "note.errorDecrypting"} Error decrypting .pinned.tinted{"ng-if" => "note.pinned", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"} - %i.icon.ion-ios-flag + %i.icon.ion-bookmark %strong.medium Pinned .archived.tinted{"ng-if" => "note.archived && !ctrl.tag.archiveTag", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"} diff --git a/package-lock.json b/package-lock.json index 8ba4e4043..fe2b9754a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5781,9 +5781,10 @@ "dev": true }, "sn-stylekit": { - "version": "1.0.115", - "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.115.tgz", - "integrity": "sha512-NsOS+sJoLBexantCSU/kwFWoRquAVDWs/+lxPQ1UHhEFh2Gj8G7q3omZmmbesTnHvL0vf+XK55Ne8sr50ZWJag==" + "version": "1.0.1191", + "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1191.tgz", + "integrity": "sha512-Xez1FNz822zw7NsG9krfxiSXklYZQNwQRSaTbxnYXmOjqCvcsGiWAgEyUzSo5p3g2nVIC/8LzKIrQw4/b3ORXw==", + "dev": true }, "snake-case": { "version": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", diff --git a/package.json b/package.json index 67c79db5e..03cc10c4f 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,8 @@ "karma": "^1.4.0", "karma-cli": "^1.0.1", "karma-jasmine": "^1.1.0", - "karma-phantomjs-launcher": "^1.0.2" + "karma-phantomjs-launcher": "^1.0.2", + "sn-stylekit": "^1.0.1191" }, - "license": "GPL-3.0", - "dependencies": { - "sn-stylekit": "^1.0.115" - } + "license": "GPL-3.0" } diff --git a/vendor/assets/fonts/ionicons.eot b/vendor/assets/fonts/ionicons.eot index 92a3f20a39267ae7f45144f412a995a663730360..9edec390724f6b60699890755bc10e13069a594a 100644 GIT binary patch delta 954 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

@FHUiu&=5w(f-3S1h*&)&h2 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 - + -Created by FontForge 20120731 at Thu Dec 4 09:51:48 2014 - By Adam Bradley -Created by Adam Bradley with FontForge 2.0 (http://fontforge.sf.net) +Created by FontForge 20170925 at Fri Jan 19 12:18:20 2018 + By mo +Copyright (c) 2018, mo - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vendor/assets/fonts/ionicons.ttf b/vendor/assets/fonts/ionicons.ttf index c4e4632486d863337c1c73478ddb3c20726c55a0..01db5bbde6fa8d93db391190fc81b876258dffc1 100644 GIT binary patch literal 1752 zcmds1&re)c6#mY6cU`EfF4$C~QWrFhnwY`wzIouJCjJ55mwV55zH{z5 z=l*yX0w9PA3?vhYOBdg)U408Mzarc5;!CIPr@y$;2FNSa7t*;@aqpdU?pImECKN4=6)0+7>V0CnK~sgYN;yf%&2oE(J76+G#<0tIwh`$7BeEPn(QCxi^O{Tt!7(OFxX@qn>&Ag&VjYx z!yi4`aeQFlc!vr$eb*H1nx3DZ?sA}Xb#>V%q_a~_xTjg={qf@{aLn$M$jETq(AC`v zR0DzPf>Z)yV}V*ltlI4beX{y?Su!a}q9b|_o*LWk9SMp0EEJjy zg{m?cs_lfVYIReoN|H01@yX%+YU7_$zdyno`Lc^WtflB;1HJgh#a{aS>0%#3((7Uq z0lC0h#M{L9gHDAS3qqK7c@Oh0xY)pRSa-3PK0mqG2gReQ$1xdy1XC!mk`0uwj4YN= zhQa{SI7w;qPl{oT`4n>W!&ITTQCiL}mDND{q_VBp*r3Xhf1buX1Ed|fN}`ZoP8ae- zv&b;+8di{E4$NkqRJ5pjWLc(Vqh5GNsk!LS9)`ENWR6nTLyO zSnuKM!B?%F=k)K!*M{fj5sw(+^{?fZ6Sig5<>)hxj89KF(}eF<{mrHN{cj)K)NJFs F{x_L{{{a91 literal 188508 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

8E#xmG!w{$tO5=<$!EHPzZXo}IBc1_sqA&q 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 diff --git a/vendor/assets/fonts/ionicons.woff b/vendor/assets/fonts/ionicons.woff index 5f3a14e0a5ca6d20cc4fac708979e807b0d51bc3..20532652fd7c2f2ad0fcbd0551a7461534a4fbe1 100644 GIT binary patch literal 1260 zcmZuxc~BEq9DbXPD`2>+LlZC@ErS&mHbJ0JPJ!SNAPUGRST>sj9IjNk9OXt4X)O$b z5YbSlX*E=#h((~lR*^#rgiASej9OX*IRtF$R83!KXZlay&i8)veee6;dwaZ13*qqq z0$`)R0EW7Xjp_RTYdl_95cH|xm<7bf+;?PNNT?$h0OV`PcS8`qH>1r&2|^hFG(E@z z;n=>Vlg}wpYO*!d1W==|!W8k35gvs;dN<@cubx&5(3tpRQs@)IoH@jHzGqZWtVF2e zIP`EJTnuZC!eXHaHK+qNge@vSVqz1L)1kHqIa3Gv6fliX5wbhzSBbb>5^{ zy1P7iLZXq9ASZ5a;8VZnt_+6O3U|RBz!5hlH3U*T46|VH-%Qk^7Rj>|p{NPi#lylV z3`jphoBkUoL~1D^1&E!sR16B;@aY^n&~pPhsSY2|}7purMBGvWP4(e>!1?q1Js!KA*u*Fuf#Mnaf)5+`C;re^MH_UbT+F z9A|2xc-5B0UK^-x`~?CgGK^RN!1oGgwO_qRa01A6bF+(EgYV!b*$brH%GdUSOTguz z(vf|k93S&d%6)!i%H!u#XVh0~DS1hR&(=o=cG~g0Y3PT3(b<)2);ztG{B4LLxNSKH zV}MJa8PXZIwwcb?-}^Xk;nqlG^nri|>?_;djpwd2*jYCW%HHSK3Gn_)mVX?NdQi&< zSe9dpX&p3UnV~C)xHE{_4L6nBg>nj891OzU-2(%AaQ4d{wQS|OWYX+#l37uo{;bvS zN5&Vv=xS|Cid2-(ZEKuVrtMhqaI~zRed^N}w}TvfqoGx^Te^q+O>RDF*&Gx)Ux&vy z-EhEF-l?h!+gZZ1HK+PV4d;g`{ep{bo%+UT!P=_JTHfK{s~uJM1I>3GTYO8tGA^C+ zSfhJ38E3ywyp1#S2W$k*Vf>5Q9K?40c!@o-#XcbdYc>nmdz#MS`r12=7EJf9u$o_y z4~^KT&%KGxR4f=#Y_DqBvH22g|MW9wS-!JxXd`?mA~>#(ree3 zjR*HLH5^&T!$ykj@yt`k?~Bkl_y^=1U&*3q2sW0=}t{t6; znZ#yqv$sSR-M1S(*z4SA)f=*>L3d_Ikf&YCv}M&HF7QYYq?i;-#X`rv-ee~(o~@P! t6BA>jEYMx4R}ViqfWZF;O_w7GG03f1_~tuo@WF(l_0&)jAhh~I@HaYu&bt5r literal 67904 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

  • F(S(gioucm()OHA z)b@(jQUpS^seEF$P)@-V5-6YaHZMG1Xf)O_^cq&d&E8I*uWi0bNX5HbxxlC`BHhJd4Un?R*f*rb~t@PU2Uk#?X-Br)AB=b#Yu*GAI}F7 z4x~ttqjF3u-t0CP34%>@YGif2|Mw4>tjSrTA#nyPV88$`r6i7zt42px0~KKdIhnBP z;bfkHz7d$;{7qSuWF{e&)tYgVWmxt$!4L@CMd153gsi=|zTQjuL}nyTLKJdVAUyOf z$1<8AW+g!rh{y@tE5y0r{X}3T!ibXaM+#3YJ|U*$n=ljTNlr%0TbVnF2~?mpphwAr zKyzoJXw7(3OZ0tdHM3+0D~3J+tth)L1zdxZuz10ku0|Ad9pV>gF>vN&8Y@h|Fm+Et2VCwigZcn4FQg`OiO3DYAKqf}_2LqjV- zNR$0!2p5l323`1Rpb&YjyuaVyFVD^S77?@uHJ+I6euH~)&`aLV2?)r*v6Aq=c$vNS z&J(98G7C#!2F?jw05g|_U-P;7nWl3=wj>Av#B9W{+8-$?yuN?X6b!>QKv^V(|PhELvBbRQB0R4=JM%BOerv zQmtMxL?S11xs}%7L2o0v)zR3sgA9Z;_Xg(iM))F> ztwhR9p+iJ~NlSss#)B?!Q>X&-D%~mtWaT0|w;O~m^kQhwTkpsVlVe(W{?MWMvNkrk zAa}f(5}!?W)QN_o$#WBGCz<6-GyR{vQFk*Ha>LD-BrUj6sASx8*4iqG&CB!GTroGT zOwP?sD${dI$KgPbEjEZVujVmYM&R=5yh9qrEV&+NZ9Qdt?vb-+A2~~IzuquXbwbXZ zw+6IzQ0ImJSZzh>%}Uv3&`l*n_D;bMsC_B*So;I z&&GDe?tpu9Eavbl)n-`gJ{aZ+t3s=}ak>|IQ)8?-LIF@rt1xBv_7C;ty5v*qlB4;$ z2GcQB`{b$N-hUSahCDPemRp!TuDMSLYP>YqA=eJh-Ivj_!N@0`RS)buGB`^19$}NkoFyg{%Tx*u!;$klN)2}llvw^Jm&c-6rjF=0Nsh@mX@)7lDl|zEa9BoS zp=ePrLqX##Q?cU=vCV0gZ0F+~ig*yEm<*Q@ni`77sFAIN1vJZ0%OAE;d}k9~yy;<< zA;v4OtY@D=$)tb*)Cv(pgj zu}-GljScs0q~JgOwExt=A8SyK-3{j8D!9Aw5b5F9dTiS@eDtjD=2K5W3FP?O-saLr zZNDaTkEf3y$LRwRvot#IA)Y{(GcUxmqKWIfoi8xVX#%|C8DQ;>SVx2hVgE&hVV2?d z0g{!Z{$CythIPu(a%h$o>PHRINwEnGQV_rSa`)Y~qwpQ&00USo&9{oRF!2SIEdN3f z4)p(0l1TOd+(b)cq}v2~yC#ICG283q3bVxafTa%sxTqZJePPEfgt=V7v7P-@sfGPT zTX>Z=_dsX45PN}K6?;wWebk#{5CeBPD5TLqJv<$Kp_elzW2SJ3ag;oeYY0O;yPYjG ztnv=rtHF815u*`0BNur)FZ+SZil%Dou44tMz>8~&s<>7;RNE%}xyi-J={ZwXG+MNI+zQ;`kVNo2gZX)21OIJWPlY#pQDtQC>-IdrTb z9Ivp3VM-!PcotYnkTEz#;>3rDpc@pfUlMIE6_hjSz_m14k_G{~n?{ff3U;uquKTX#mIVwk8w{=+v`Lhz{wIK z->(answgst{pVzYaxOXOKP}-V1KY|-vP3Yu3C~HQCc~H`ao&boSW&x+moW9W$Z4u( z+HuX5L4bs@XL^1j?IsOf5<#>vXL+LwHNaGxM1(#DnU#2!fWY=JWmzti=VLwKDSDBA z=TM$+qc^7C@~NL#t6k>9d?_@>2>t2M=W4mvBe7-J_MbKU(vh3WgF^+Z1AT zkU(&-p&^|3&6F_%Lv{Pv(GBKl+Ww4@4f&9C_tluq2L7a#2Z(s*)sh9@*4m3uZuEGd z0W5FJu3{OQX~EFcSWFZrnu4d0{?D$xh-2e?z+g%OPxckdR&6bA7zU%Tx+0k(C#&o? zmu``CUp6JrH+3U0T-7x-UyjRSl237D=_WBQ!=-_%8@g{o2^NedPZk3{!Tbuj`c9EN zup));$XtE51iuWyd(j$EJ;4+WQIa%Ww=GjM4b_otMEGYt-o-ouFIr0y(Gn$#ky#zs zBO6~2Ud4lpcs9WYFdBbHylIJWX+9~af$SUjcD8Hirk#*1DXB=eEHO;L$3@wd9Ub4! z_TW9;Kr|&~#GY!$PR5=ba6xmF2|^)juH!bL6Hw;5>_+gLIkb#*sWymy4>qB8DCl}! z48M)8hkoBtLiAM<F?9!8S;PUw@GA5Ti~tsT3Wt`C?_xWK zuCp>@Nzkk0ogCp+hH-h1MJGjDc2oo3#)UOSl0+P&sz;P0^kV|bHlSbOWei^qR3XVz z-;ym`cWDH+9xzL=pbp2iRYPy`~W@kuU*fV~O zXYYNTm*^fAvPYJnxZ3zEcgUQCiE*HvDv5c+eCuvfEhd6$sdb#2N@+_wZSdEQv;@r* z5X^UIWHHvqL*%j$-cu-3j4G@mYS!t%rU(H7QnU_5y25#Ydyxk!IiBCGdfkJhoXdK7 zU*d>cyCG(43<;6Z!fp70Gw^pC#q7a$cO;&4|qWoTC^p#@MNx(M%Pbtk=}JlPt2V$~sJv^w(L7*LmQs80MVe z8Rz<8{f@{dm@Xoy}l^RmSZ9Q~lhPg&@cu zI$E}GNjgq!n?DZqIK9kUG5_9s-2Kx$`yq#j$kg&cu}_CC}TBcn6e~H%!is|Ae}JjLa4ZT z8XEr{Ov8AGhDp8)jH>W5u-ClC@Cw8DtjjAZQvkL`(C)zNKoD4#q;sx{d}{$%4Mv2k zSzcBdRpwd2XMhp{4^rSnkf%v#*)P#J`|F^&Oug!pk1IA&<@@udB;D^3TlqNgtGWqi z3zn7e>n{6o9#cf#&jE9+s=(gx|@98#5`PRm;vATfNy>*sd;1#+0gQ@_&k7>Cg1Gi^y*2d)*Tuai`fi~Wi2cKA`4=6 zakR=?ND>qPt<7K-u-YAv7IjxwbxbWQs})=sNROYBP9h15@Huo6&j%n@yn@NC;OPKP z>Gw101!B7qhFK|K?Q~{vQwmL?Q+VRUo+uN6O*ON+o>@h^pNii2RP@8^o9ls%GI^HF zXCeK%pQ=ypkKe64)xdpE~f|YfE zY=^rjHM__wT&$g`@+M!+?65e6U!1+%G7@OMSeU0#Vn_R1Y_Bw*Y43`o{!|&UgMW`q zmy*fSw1h+nm8WKB2cH~-=xjEdL4$uQepf@STow)fWv00GoipH_i#D9db{eMAU)+2CZmh0ixGHJR9DM5a25GCM_D(-W?ak_`BigeYz~`d0l$9E(xb2M;u9V zy1HAdg(aOgPU9}pH?+YEnu$N6QBy-1%}~|BCs9iX>w^~r1$9qoJg=P?{0YkFPq^+A zVPWv!6=lpXr~?o7F9^i+dDOXhHSi6mpKp6~Z4kus?i<$@g*?!$RNDq5I?<>*+x1s)0(jW^JW zu6K1E%sIQT+0X}g9^t-7fqm#%*VTQy(6!##v%SH&jg7NIy`WznLwtXT9@6tmD|k7p zkSpLk&XAm8AsS~{+t^rl-F|NZSI{QNp;h7)lOqs{O&^O3_c44XGzsf0pVUHNKZIu} zYqh~@&8?;H)Qp4%fajv}ENq`^M_a#b$`l*DHu~@HN61)spj_3de5CCVHGIAB@Q;uor8V1>$*Yd_`onS zTVhlRz%PPajW-CUdBh6~)dpvgJ2(d+U}0j1Pj%b`_?#Tnx?A5*@INEDL_v#OfaEz*Uo^ap3pQR>Lo9m)h`tC^8j=sN0Vhz~VSJ-kKKs3PBn7@oaaZDLnD zdzOqOBVxbZKU-U?_3?jyyIR|h=LM8y0ar8E@<#7K%a2kvbQ8lr9j*3O2mMt%)9+yp ztiqJPhk9N7V&OB_bqA+$&!f6voZY=p8F;>5kL2QL*s(2QazKv2>jE;y6MV9K04BFk z8RCJ-llQ!j-}7Y9bB^G?bG|;Os8JesN&ZFUyP=HAWwld{yW~8v7!DA5t z-4TjJLYQ}^sfF^PgeBe}T8S&QWr1S^KEen$(41gMc(WVi4$R&^JVqF}LiF6Z zh$1sH3@fH{8lIU{l)+iIra>x9Td+rNzn01Cnilt@VQpPOq(P_BXrTk-sJ2Q=~!4 zBM2c5*jnM~5@1sJMsh>o!@!`)E5Mp*vycxnGpI}!Tk8(2$s+0LyOUUM4S9G@CyK0$ zXt#W@)p+qE4?onKacQ2#Uw?>f*`OIlq9Y7qGpKjXj&gxUw50G;5f)4Na>eWHxc#Q? z$}1qAByA&F+<^eq9)78^S1(oyl<^4l4HPE$JfNllIEF*4%q|-9 zZ-~5>h5h5kL>r0(sTXuierkG~F)S(?Z}lIaBU9=(0SMF@3L8(7yr>s_&&&UZfJ1GW z*Xxt`Nd$t#ZurykGjBECFmEhkKKnE4^(T$T;ru0h2aIiC!KWsOc>|a_93T3C`jmX1 z*gss%kHOgD#}HI$%k$^?F`=JC6chqq>Pr2n4C_Vtd?R-~+<*TG*#R)c5n zeosF#)G1MbeXW4k{5as_=hUM|hkwYOIN45f66X*M3&tq&&Nv@bhH_8+(t{7KKltD$ zCNuTvI5l_#?yNtEGLwaD!yI#hhBqBs4sT>JlRs zoy@GhP)g59R))_M_SHtZ=^^cvAYL0LeFX)y7x1j@0`z^Gkf;1H(DzooU>(>>Na+6i zfe!UMn2;x*++OI|_bCCmsU?3D$(>&eFI=2)%^Z3NN+0~gsOaz_E*hbvS594kSxEo0 zS|Q(PmoYImCZ=>*4bjg`Eqd1U4i~s&M=+O4vFseR4TBHblt6@m<#GV^5j1MSyVOQa zqmL?w4yzsg7cbtXb_&|GyfDeiFDQo(E4W?lMj>CZI0Y${qss-W{?zQM97 zJ08#5wondBl02KzMS)Wb#l~hSuHvt*rRlPgu4E9cS(>I766Lm+wM`d^ZuJgUmOWlj z`6yELLT)-{Mk@@RoRU~1sNng?bd3&iw%io-q7+fn|2}!zlECH$J7h%d_dbO!ZIjn6TsPjnJse3+}3|O zH%B%LK#%VMd474@vURuSFqV;5s6O>J>OIu^sgGiwhrwP&2!3Hym`4+)S@Wp10_ZKB z;J1RBSu|m+1zt=ms5Or!jJfc<1`iSf_%Zp}hLHshcoX(r%@s7ta+G$S7*b%tR zG2Mk8D*#qp*q3X*FITuQPy!@!yIxg4Klaz=zpr?7~f6Gb71?DniGrBJvVStgM~pn3@CnHqyvk*goDfjTv&l412oYJpm!4&a&mE!4du-ztL6NZzzzs2k680^A$NM`m>v2wo%T z`pQJfhd=6|6oWy+1|?SqklgtOF}MXKJb}~%vtgc?G+_s-pknZj%cRx?cQP~6Wu}E{~+%! zSNG=gd#mN$vc^~U?yd5;ccFY9acX68@t5&2K1#=8O9)nR8AHvWYD6_cRkV@G6nRZw z6q`9fCC)X)MP1{Ie(aDDzxHEJ-9X(=-S-l_0;6Jho8jx^o0z;{8At*O+5B|xV&k&J0PFR@6%sf&J0SlKA zu(O^aj3oHE;%I$y8-8vS2?)PU^C+-m=>#?{AcqkF6~BOeMCD(kI$u-1R?&66S9y`D z2x&D{Nh_%jrPMTd(w69n6s;n zQ?*aw{VfEd%=ae?V%i(T`?u8rWG$f(0SDj$fTfXGWAoro=AfEB*Z^alk9rWi&$<6F z%DdmkS@*ktjW2GDbDf+!Esp1b#k{Z4AIT}5R-42GZUUG-kB3-W*n*QDsJF8pS(~dg z7?xut4x;>WX4EshsLAmRVi-Ja=Xkm?GZ)Og?p-@_S#VpSLzA(4;`mKC*f9Aix{ZB9fRk5Z7gFz8IJzoV{V^OoI_P zOcR4?o1~OwI0@H)X;gNMCeN2e!n7plvaOm1P=@JD&`ccYq*dKgO$Aqi){M(WYp*WR z32KxH{;U3qRL6-B)UQ^wRfrTKrIuHSY;=32!?kk&%#El$??$Vz_I-Hp?={q`4NkjA zADpfI)84_^EipF)k}pM8;3LgQ)QQZXNXsFh+^)WH&i&29wT-h~-E~oKu!(v&KjhZd z^loqa+OS08yn`MfjfjDnLomHCb-Av4=B#_JHt3_S3z^hv=iIYrahB8o)&MS+PXH&# zjq}mZf?XQP2-gNO0vokWa5=*{)g~|k6=L~H*1>Vb@YBL@{taEazlPkk+wg2!Idq6< zIp9q}`YoLw{ivfJIy8}wN^t%Bd_3PKA3~HrGuU_r9#%TK?q~3Ecu?(poPbO)1Y>#? zQRiV4!D{>}thB?guj`BCZ#%9L4;%oE%nHg0xt71K0rCY2?D_G*?}X6mtM}7KYPBQu z{#S!x-D_UI`K=qSq7Rfx2k5ItxCDNFDy$EcaxEf%5TOL;1vx!2%;E|0x#zk+JTZL! zg!a@^L?-It=WA4iL_?~%0(v7B ze^TwXcX_#l2U4$AKYo1n>RQ{LUMO7dB}%3F2tn}25)#BE)4`&^RLy82x`7{z7V~-h z*kU}R&)3^ps*q1i#j|G!u>b9V*|3l>!PG%F5@s~u>97uQ;!%Ab{iD@)0ss{d%U^)Z zHxjxIS(+jeqWG-@Z4e@sHH#+{h!b%WX!4d(pkz+d9sHeY2@ywsu5xCBz;~7Y@U9bA zCvQ8^@An^j3{j5_vChe}JXs%u2R2FK?kEvQ(yWp+fS6-bIf7)2r}G`o16>>unVPLI z!mkjXiskVrW;K!9J#(guLUU(p5UAno+#J&HAXu_@tfTJg`ughX{{6ik#GUD>`%m`) zNwz=kPf(r=tGsPMDzT;Q8O(y|S$Ot=3~u;(sx@AP2p;NtKfssIZsaYG(8ZSyAcd699G;M3Te)@d6e0>4Ll+KX3H*S|SgefCa z6)ZFYLaT3--E6d-R(^CnxOR?_8T^;y)0G`U*3ub!CRy1jNEw7Ol3A<7vX<$}JLaQ2 z>LH|m7$HUq3GaT_$7n{86!ydKWEDwxJ0FrpdH|o6pF+vSi@i`OL(F9A#UN(xZk&Gx z2^s_5G7HO0GzgfB=Lrt^V4H$Q2PWpkG(Cvqi~p})zAAG0nY`lOhGIEai&y-hx~~&z z`FcG9rm&8r8C6A@izryckKQeH*3`-500&Vr0E!>07X*cTktXdRWPZK*BjFVFkY%tzC$G zkgee_3}+-JUykUI=IJG(Xt#t;6JkTopghE&66!ozv}}N@RvrC}f(iidQ9>#R%%Ie* z)#z}2=PiaFP9Q23;-%Lx_|7*EzW<<1&%RLot0(tKs_}v*3F6guELke1?AVvzs1r{@ zS7+F`{()lBv1Q$ge3^Yh*WdWnM<3NAm5P4yAx%+LL9jz(e|0}DeV!^)(?nmnOt2P& zU`?k}C!}wwHdGZ15`^G~tKD)I=25|iRBxcmMM2VDFjQ&ZlMhc;Ze3p^wc8`@{EU3? z1D7k+7iRZ_Y(Y>}MSJLu$*R2ZA>EJ21%F?S=#M@c(E$T7G4k!Yj#{Per(R1vLA{gu zMd}mOXQ|(&{u}jm>O0h5QvXQ(1hFWLo2QHx&=NX~UV-jH4+HL*Z#6q|vcP>ePbR|r z&}kTs3;6|@PVOZJ`}{e0_WrSP8oTDTp=%t0AX*4 zo}Glw>UhNKtkEIgcko1GKuB-;k~|N67mLNd*j^64q5;iq+^NBu7hXdfPc;e90wIW0 z0;jUuD=4T8O!hHBpwcYaLYWWyE^Pvt{sJ`(VHvF;jdq50UnoOprKnBd0+wlyq{dm8 zw$&EEORh=_fv>gWn-CZBg;7M(jrYa61$_U=*%TDv>09ti*zP2 z=ZI`{ccMYli>2$Y&+TT=LJShcW+xKO#voB_0WrIC*RRjG8JklS&d#JPJ4So9BMZi7 z=$P#pg6!CBcV<2XDMGZ}gZs0J5IKWyS0aKkdkd$~3P*E%h#VjuixQyPe_hz586yf# zATbTkFJu&Ygh4b)T5uymBNe=aUS;G}H6Jk|QBK$)auE^VW9SfiOq_;0IYW<{dH-67 zm8AbyO9(tn_8pz2BQaS;g35h5qJ|QJfN&}^$_g9XnIRks?#@O-%-#5Ag$YGx5rf%3 z?=xB%yxz{jN!G^o{unVpds{<91;VW*#bM=$XrTv;yrLHLrii-0IK>fvkH#F1#dt** zWDau}eLNC}7Kq2VB0gg|>3=9mNyL0nJtT@jy)E>!VpU2>5~t4yUe%QGb&VBvDhW{p zX2+vS1oKqo**7GjGN%$|(?vws&rIMoUV%sQ+o@MkucMx%eu4Tp9yh2`!z=^?1ApGA zaG@dLya8c29E`@6wr@Z|-b~(hhF)JCcsqv!2)vM!?_YHM(m6Ia#5;g@Zp#~n?!W%} z;Jqp@O}sBl<@4SsbGP3kNSE1d25-~eSbWs)uM4`miM#z`(8s563!Wvtnk7B_zi;W# z&rkGYIx_e^>ARtzC!@=@^xNEidH!0(H_kUWjhAyO{L+@m!bACVcp{w->W+K=CmtHFM3yl&HOcDxpz z=QnKllrX#iUzjZ2X*r%?In9n!_TXa&Vb?hu77=L&mRKQtSUE2+c&zpu01@(6b^euR z>6m$+83+Fv!}@KTp1F2M&gSZuUwz;*;~wL6G%S;Mc3$2de34BbuuN3{ivwx4-7xpf z+cjqf5B26pX52-3xbSn1?s~}ELvtuush57vp6}Am?wxPmxf2~c$Tcdh19h{qb9ZdH zIfcBdko|LLs#$fe+H`q-@HJG@8x6gLm>?$Rm?v2*5dlYDiVuVb7pnLLYkJhL_&G}S5h|s-X-oaGsps;h9y+U6ARfESjnbQ5IuRp zgE5c5Hu`&Sgy$IG_7nNEwKZJe$2ktmC0qgTIGg)Jh#ntxqEWoRm23<6rz?pUyv0EfP9&3w>8S;M`FOW`e0dme z%de*rSp9V&nk||jjJ%tH?Qg-4{Ypg9CD4U876{m<Sv}&pp^&n(y;BK=T#w;JR?&+I z70*d33l%K(tBInnup&>ha!A*0JF*;Gj@Y)Yhh+B0q)eA=uXOT`lP{L%Ca&f7-Y78( zF-eLoFw%{C4;+x#aF~@2e3waIHn(p@WH?#o7;$Ca++}G7shXMMIgU@6no8aWpU+It zPgbbQF`p^?dgZxG;2wLC$T8a^${a11(eX>*%;2{OPyc9wp_3f{QJKF;Wv?fUVMyXd zpH&mbTi1u7Pu)iS{P}ph9bT{lxr2x{{>r)&c$<$WG9bzX-sSDl3O$i{h*DuZHL@K) z+&tc!pa3JGq46Gqv$uU!n%4&PnjeWC?;GzY(4i}2fiE5oV=*=yO-fk)6(M$)$O){J zjD{JS4u?54Vp_V&{j^lNWk%H7+_)CgELGG+*Toyv!l!Xp-LGEkMgUHFk}M|vF(-(+ z8IGb+hPYT14Vz%IF)VbPVo2DQ6iHFf#xyo4l+|KczsU11ZuJQO>wcnl&fhytIKks7!)(Mtq-Sm4(q17av2=^!7 z{0NU%0mUQAKN}7+iDaSN)=j-#E+i8STu2n%x~S$=vF_qaC%bcV-MQZ-+pihEoOG z%ea7Hh5Bo2YkFT_3wC`7=`=dS|4#S&+k8<9!#FOH9BPe1(*)~ILFV{+zyBgdVl=Gw za8|GgOm=-J0bixYmtYrQtpaXpoJ8v#;pBu-V7x_GwT#9}#$jZiky?@Xf`iQ&y1cc2 zssV7~!-eMV(@K+I6SPMYOiu?lkJ2 zZZV8d^y;|ZFQ?-1)SuePm_47=IAP9l<^)bl&fBr1opR$TMM=fop>6b#{+FQsB9vXM zWWPPHrTSRj5I|Er#L(^lbiy#e5$_Zhh=tu@O_nt|qVp(<($Sa^i=*iNME+VCuHPHh z?Q$Yw8&=F-PREmzm<%v(gJ<{o7Q&4H2xLoP_62O1zZ@?lwiLWXh}mjQ{KZT>5z}=G z(_tAlrsH=AY3JE0dl&eE_{Ceza)1!Q3(fYzavud=YY`w8B-Q~Y+W*9F9a_LTtXjDr!y5V^SGVQ*^Xo9 z^7c$<8m{2$UIkyrSMYUw*T0U~^wsH9>M`4Px{hs6Yg4Jzc~=NsH=whq0vd@+qpQ6z zLDgro*-UjV^9D1UF)ho?WKA#b!4-Ucu8OZ`vXu(H=U+u~_EJ)S5OuPzqeHx{d^Xh(Y=iGo=ctyD2YLQ`(WOU1HRGz`Yl z0Nh5?!b$w3S1y*~kWU!V6Ved8D#qx2k%&e&cb*^OtEivBGnSvF-c9`?u{ic;i|t`8 zw)kV;Sftps=O zCzTGK7-GUg%#|p$1J`d4t|5g?ACsZr3nheZ!GH4z_(c}xyZ9^OH68GqZQ?Hqu{x?R z2tjms_#rowVvT-ObTZe!_WC-XtQ7AlR+4yDXX$Pv>t-XaZiza2yxUt{T}9EU>9UY* zB2>?5+3BgB*IoB>VyRfK6pNL5v6K+BqPN!VuEDQd(riS(K!801Y)^Jnk8za2Q7t+J z0BQFM@R1tVh&hi|*VSDh%MEBgvz4-bx7&x`n8Lx&PtqW6ktw&>-VHHMoxuJMet;s) z2S2n1RyF#d-v_{|4e;Q`|H#!faO%EC*H_8kbFRBe{(|+X3zbK-`R&F+ej)fxYVb*X zF+aW$qDWzIrRR3>|8EaVndGM$M4JX25MO0BcHax5D&0CSgv|2~96FGT$8!h1aPsKE zVo|U4UQ29ZW-G~LC5cWSYVSR{BcI=Ka&P;lqq`f=G@JHiA8+hti5bilNk}yj3rGcf zC}X0y(JH|(_t*RRu21B3zn{wx^A+Np`W~YNVoO6zFMX?3aBPO`z}vVz3|n4qkrg5& zE)F&pmyRuMoM|3v{@q~n%_$?M$8aavJa_Kk%+k`#!Gon{bMPBy&#oIV>Og{|)xe2eTRZ@d*J?FRJy82wN56p1FR;P^PAODG6Vf+;K!p%VSMve1%eRHP&H;8o2V;#zXGbM#SEf&F!l z()jvA`10U47nG@KWkYGTl&L9AK!SGFgA21_ZSm3Tj^pjmT*si{)vrR52@Hz|@VgX& zwjw?ae%vxI@P7l_GyMMe&U9cfgdTY9Yu(p&@sI4}eqZmqecj(7cK`mx%ijL>%NB$E z^M?+l4!!ric;oL!F;@LDFHqvP{LjmA0>c6P=Vz$}uwLyH0B*w~rW0Tt(E_U~Ya9ry z<9pZ>NCyk10J9uKzJ|#jE4-M z50zd@IcvOj2P95nnTiyL&-OeDTqrc5jXda=Tk-Stn(m84=u{B;%Y< z*$D4EMlg6}5~ZhxFQN|eK-J^$9q4_{VVp!vk`RcXlqX7tH9SEkkov22;b=%%*b#L`urG*myt3~!T z0#a_;gVyVX4;1RK`Qissl}ZXW_w^cAuoGl-=Z5%1J`Dvj2OkB=xmc+mC8fHP#T_4& z_V5MDfd2h?y!k5BxC9a409k?heB`{jar8odyg6%fF1~_^-7@b!5Q)50+_2%(sQ2RV z!Q1c?y2v;KlA|(B`0qHUK+ZN3Q-NHHx=4el~jk(@^Tx z90onKKAFq8%}sx-xr+KWYU1%a589*-KMejcbc3^-LZd@qWr+jHmlavy^6lFGxuU3v zjMAMuR8H3S@$t;ApJfzbYG2J;QfOM`?f6``5@Qe6Z9}c6r*;y;hh<%)r1LXBKTo0~ z0`$zyQT$ub08s61Gl%UniQ=~NUj`5&*(JsyH%uI(%EZZ^$Z!%P77y%2j)(0rNz-}p z;fJ#L(BVbQhB9bW1rGdY3Ex|G;vTb!xS5}kD z3sO{Jf0q_*V`agyHv9E(B*<~y{V2^uqNxyK=J5GH7N`roKf|bF!#bDn#|mXW^vEM0 zdSvkT{+m!+#j@ffaOutDeULZ%7khu4j?fHz1Xefu!Ta;A&DEe>h>2Lo^VPl|M?(NK zKtJ|H(7kZ)@-<@f0Dw9?^p{Ko5?iB1R#hMdjsVexXjoKf6}&KV>b@~X-Qxv~AxON9 z733AloqD_BG_m;UU}*-+FJU~Am*r5iQ)U<%v0VDqs-zh#o3m(^K@1m_mb4%Q#LDIS%0)aTE$6OtB;&D>kcY8ZU{WARty?X+-Occ$b0jMMFTT8(t}L zcSkO#HD=}JZ^*G&V)?>R(a;ouZAECrVA16cJHyZnUrFooks>Pr@-8k=6ft>p z21Rgj9FGj|fELkso>LL8@_@~&bBAoW65F$`F}ov=u_F6WD`*?w^)$$IBTi4Sd4X_u z$mJ1>J0uV#3jGfgi(`$&Wxn^7M?* z;7cLH)g>P7<)y({z1HuaJGZfct37xYLTIA8KKKh>!uPL%k35!5dc(ho@+ZBVCd$YE zTTWm^IYjoIC={2qQ9E~TqO1X)a^OjzzD{OA>p`pu7r)1=#J54Bs8)#~?va9nx=-&2 zPraJ{$#u_vV6gFO?z(@aUsoL4HN!JB(AX*o9mYf5Z++|Oh37x;)vpdV?iuQ=`y}7t z+^t0Iqh8_rkC1sZG$HZR$K~-t5Iu*eweU;v@_olo>+2mwc9r)kuJRQ*t4#5{H=WMf z3KNbw?mI8lk9oS?zE9Wh$C)7f`y(Z*BsjsGNl(W)CX%yDQv2I zL<tPV=PCZ1Tw5oMgPJklwVkr?b3z>N)b zO9I59@OaQOf$9!y8w$MbDnk#?#H@i3ZrRBn)hi8>n(M8rQ% zgAnBoZZ|Li0M(#|(Cg)pmE41Zb7R2jM$&9SJPm=l>;G?R?-O#CSR#1v^pHDm5RR7n+MxX+Z*75t4t zj~W>}i)c|V*cpVLJ>JP_D#M6KmPuGOE!TO4-Qy#jipo=uh+Ucl*P=<`Z<9?FEv+AoLHraW-gWKL6ob%fMBJJJT)}H6L-{7ye zH(2w$7LqR5BA?sF^(|a3O|sV5#?SNxIQaUloC?MlAHRd5c2lpXK27~0^{)uL5OG(> zQ_+Pj(EdCYwaboQT_<2m+XhvqhQKSDV5h}n5vENfK-@+qyiFt~3>FUYGmc#VkdJZ)CdZv}J9xVx z^pJ(~q6H49o<^ieJ1yUkWSIsV6fnfX#f}fS;*}G^GkZz&5Ei*7v*}TB}aaV*QQ4F`X)z>AedD#XT zh1F9`$Y8@XGDKVrE}q1a9n0gA7(qh4zYq&{F)B!m%tjfSl|>ziA!H*qY)7IdLUvS( z;{vgONDBgwYzc`-k!4&_S5okIlf{CjA}BU~H8@p6oTv)4Vi-x))Uz}RmB*@>1GHdp zm^j)pc#%c;Ay<->jEu?EX|QyO7FDAp6<8&N>B3A?c)~r7j)W8;!_jJ0b>dP(qSHDL zUb?!VhM-LtmeBxiOcHJ|L#oT#s=?#$6=IN%Fl|WABo&Uwyf>0)ln0j)_z~9M@cme! zUQ2xvj~(^@!n@%$3AVA{!D-yTar)Lv_wa(yY-6dmaLJt+Exr7{`V~Bd(q!m$8Qkk= z6Z+JJyPbxc5#MWOdzaS39{ykMKC-Bg4#0~Ng2#0wgIv=`<;hnOv`nkhT0#EP%K%K> zX~U&urnTS?B$kO=(}K3epE=Vex}hwxMsae;|NB1LT3>I8E)#))LJ)BK;99Z>RSr}T zhv!7lmW)L65yrjq?JO%9s>W~}?eN4f9y1-cq6nZ5Ji%np^i*08kwY@j&>}>nWH=h} zyd(2!$iT1PzkJMeT@&I6uv4<=s=Usz915kAQ&Y)lB+&_6t|J;H+|ZoI)4TwYNkl=6 znb6KWBO7=}bC`Y^$mJzfz*4OqvISM*p*=WO<8c#VDOpzVyDrNy{4}PK6>x4tpF(Jh z8kxPlyk+$FRO2Djm;P#^KpXl%m5TA!8GU+6S}p*dJpyW z!MaQ0H0q!`Kx;L;)wkl+5#J)n1%N&Z5&!A#;EW&Mv0odUgP9dO@;@@E3^fIQ1t^Wb zQNs{Sz$x-U^Ud=iaEEUe&7V z&F9@(sTi-SpVPmNWu935h8^gkZ>a2CG?LIu;qXGC==B!s&Z$qI>aEnz(Yl^#Ur~0e z|M!my%WZj?`)O7PJysoa-1 zm1dPw3j0AMNLZBr5*Fws0nwp8{^~VvDO-C&QALib`>gUU%~zB|p>j6fY{v0RHnS<~Gg5r*JGV|VqSzjM)t|RxEw++s{4OPf8dd3a(TkAyc21vFPwa_rV zCeF4vG$X3HJ(Ax-%rs+g>aIan>n5C2TVfT14P&4|M@@?XMQ{_X&`WfI0X%$R8A@S7YSOVBJ zHuyx7lA~TTO^4}GEo?B{IHR8n{o|SS@2R-dMo!HpaS9 zf4y>kgs?pWxa5Tq%685w;d2Jpur&0BBUhUZ)CU`Y+KW^T_AY_Tr}Rxgf>|e)fHfV~ zIG@#wM;XF~>oF(0gNY`po|aQIpM`0*_|T7NAo3%Hs&Tw;zy8gPa*0fMwoQ?uu* z?{H~u4wg%EgUxKnjA{hkV-;c~ggz3o9)H}zJ0o~EaNpd-eX|_cOaZiVem;ox!oaT8 zB=dtP3GUcjM(7N{YUI?W%IFP7MIpL5$As4ITw+y?UYKKIOuEd)Ka;Gav&#IhDk@s| zE!F+!kT!V!gNky!79E!R6Wo4pBUTq%%AF{6ve0$ks$vgMJ3DBV-r1)4)ti?6BbCnV zq77(5hdcN@7EEsXF0cPPFAP5ZC62pU(T4c1E}ly*LIkO3V5@>l1v`s^u;pOlLKSlr zJi>iL>PFk~(P0IDHIGlzpdJZUckACxRpMrcWktGL$ig=|UTWbTQ!TnIBhqm@lQGd; z$(`Y8Jk!i(Ek4Zfkw`4*u^bmR6;*pZ3Qec&qS(WEuzP1&5wV3US&@fBn$4r!Yzd1( zbg|~8(PP=^934)Ev`SKyv4oTe>xw!Y?}l~#U$5!Qsm#luI#~DV;XYR*`u=gW2Z|#7 zAm@)2_(KQL=!YIhL%*b-rp-kAZ|Qccga4>@ySH>(Pq%*Dxw_rH8nIV*@WDeQRM8<~ z>GbJ|V^rI7d=LE1FXwX0^FpuF>2d-8)d$K2vgZcUPdR({;0qyKIEbfcZ z{A)f@<-j5f==^K49n;y(X1ntb`6Gte|zrZ9Jl2%@hyG;tZj)`K@*hg++wb1N%1t(e`z_Z^hfPA}>T2uoP#1f+MG9S9?9Lv(Z)i)jL3DA%TF%#;_+7Z!Xm@Fb$ zZGgB3u=HiG&~XYG1fEk(!E1sMS6~F+`DuYM@?-^d6dD5-jYgp{{)txT*xm-4DSKIV zY9a6VN0ns4ggd#y8~wNvA!}QR44%Pr8k#p4;ReJhVF^#G_+gZcH{ns-s=AFvy!3Xn z)NCe85x9cCSZ6}!wpbZ>w#ZuynMnr0GKT9a!`K+pWH$B^aWRCBM>y2O?%tLua0QZu86;oxphAb=TKUoVYD{-(!!xNImvgpE?i6 z%YVj)@fs%*iJa6WGW#6n$wBH`>Q?Fm^#Fbx*1O?-9hv46?^u9hX?L{1zTdY(!T(F( zMntA)et1&?6Ds&X*Ss(cwFuWdf_3dpTq%2Pl%gfNU600vIm4C|t?=4{rbvpw^NqZi zIP&Hr)p;+}Y-b1m8KEJrsm97YqteOV-g+z)Q#C%5RK8+|ytpduDso(cU)jlMac8gf zK75N8wPYb3Hs*90i^Vk39VyHv;@PTWIU&~#Iaall%yybHhpx954<1}xSV`J^Oqb+* zoeeRVFbJlO^r>(vq^p9+Mx7fDGOP+dsj`xDop6$ggp*lXP0?YIj}-NaV%l7GEQbPr zBY2zz96R9a`Meu5Yl1~X-gLLJ;Lj%N{Kw;(esEaD?)G|=CJ4WAOm2RHPI^KQAIBpn z0sSL}?LLIA{~5)tk7Dct`3Ho`%*u^7K7QlA!ifV1P85po6JC{n6&Jth@f&YknSE2? zz=;Ee;vHM_4M8-FlHiRq4F{e#pbAHQ=i#Xi$usw7-j2myZ>{Gz59e-#9NSa0htGS9 zI|A%b@MlwqB{0>>fklZ`z+z#p1$s;@0ORArG zZY=i86WaiDZG&L0DHC4lW1kO-!t<5UCuQQeJaG&Z;kkMlGZ4)2Ob$~uJjb%S84b=o z)4zgxuA3pVklOIz^zh&aiVVJw&~50NYfuLN@DV2##-XYhbEY%b_6_EKFzeIYIRwt3+qrELjEQ=oQm&i!~ZzoYT2tGd2@HdTng_ zUDqCs&)k-)Hl~l&imqYpR&3D!^J6R%=!-dAuY(Xz9uurF!q#dnvwoEv69PdmQKjJ1aS9IQ4dYa%hlw3q9hr(D(PaT;&qbvD593K%}jM!KK8E&h{TtY9j}rp zZX7#?pSWokf`Cwm58vPMXB<*>=3wtro4AQCPlXe~6-Xb4Yaftx=71H(C4c_al1 zBmAQOfWYW!27eR;n&rAt2x!Wwk}j#7%7>zRKvM`}OIl=DAuWaknh_$QJ$sa>NaJ@% zNKZV^aC@R5UJV|H;HS|peqN{PiN|TtRJz@e0gs2oCm2y+BdUteL|ZFf!6{I_Oy@)~ zN6VbbDcluBJu2bbsv2PhkM&AY@_~kO_Wpln2fbGBFI1 z73GjVk%yobi#QKKq&abnuK@CDoVwhP!)FZratX2%mJGu*NCT|}uH6BrMc^eC8A;#^ zJQ%_kwIS;xi0AVGd>IBm}nf+$W91wAmD3MBR?e&>lrTA*c=DU^?uvGgkAvqe1p zr(KuU)MS$9P%@p(P3c~)7{_PHr0U-bo88~(ntSNOQVODgD=OY=p()>_wEx7dN4vC0 z%XBKN>){kW5b5sGTYoKA(JQ%3hGZ1d=yI9X^cb9FGNg>0)9pHcg^sGanUYjRg}r!i zXyD3?cvPkKQpcz@>NOByz6lj3m*+=ydozK%DT$mEK@@`(%)hp2+F=gti3N7&CfbZR|&+iv@ z967qQd-u}OBRgaS`DZeaJ$k6y9j}#ckQixUh=J4W;He81i=&o4UM9%R9$Rvx0{DLPGz~XWPo^L}=ZJfu{ z)DsWP-2Vf}2Iuwcw@w{??6JeAt{kn6`FV|VaweW1JvCu-3tpo;M*gw>QVV=;tab+$ zond-KKBxscum-fuj_(NMo6R-R)s2Tg@^BLwqBG@4jlHzHr^ZRnlr0(GJbl+)2QPo+ zZ^mMx7z;}}n(JI~^+OL`eYlN8r-BpI*>ahm;i8gVam2x&o_N=ZyAE7_qY!fh!w?+V zVBR$1kx%RVrBDjiz+S6h!9rISy##%4iD79p91~xfu3r_LnDC}IF@}uEqWt)mL;KCG zK6%7`-H&)WM&r-I=mUuS0WN-f8z}TI8isM(;$UO<=kjXqlX*4&*_@jHLSD)ByR)<1 z*@ar6P%8iy?%*f8cSB;Mg%f=NCvt)Z+*wF@a{*EYex?_r?~cAD{-lc`JVGKJrQP-9KR}ZwGGq1n&c^qI^)Kf)CB-k&k3xbS z1Nsd9WBg7b_}0Jyg2+O<1ciPtojNA3-fq@;Ksp6Nd8-Y< z6@1h!FE4x(amP*p*}R7L!rfQ!^82Ash2u_Mk3N0s*pMED`-5+y^;5@mkq~=~#Z=!X zqOU`B(Df%dPPy|G-VEzHTGuqN+{i=B_Kwggbeh62>Rof&>iibt>tTGfV1@hlZb&bL z59*6xdn}4zP0fQXweCz9St|-LN_JDz{F(l0uXbm-?M2d+4A@JD0PTzRZKxwdgXBVD z3ylKt9cto%08{BTgnAC?yIxt}imU#!yUsv!9_Hq&hcXhmaNd?P0#DEk5S_X8yMu)}% z1pM0g=LUNt*t7Uop4dY=EUy#M9E9~Umbn{PX2XBz=(48k+A@*b#)miRV5UDftslfY zh$-T`TJ`h|{kW*fF`r~Q~a*HSlP{+yUZ$S@mxV<8Xd!aM}N^2!2l zcgSH`Cua_L9m999&I6QdHjseq`%W~z4amwOI(c1@^~Vk z?SM%GkN?4};3%%2CUR04Ut&ufhw#9nAN5lxSX#%a_F;bECj|{IvNmRp&$^p2BkqQD zqVRdhO60GB1dKNL9r6GZ*FPYH#V)>t*#lN=m{*RU1a*KChW&eyknvfZ2Tpa1zD0j+ zfRA~{t1@jEMeAmh=tA0g>Jh?JZS&d0g`1LwUZVb+n+DzQ= ziN4aOW}Q&@hv#1!#+Y`Aem;wP4 zVH5Ty{-+Io73Bwijz3Sv5Bz_@d~irxfN}vVfEOUm$2GuLnWk3&-=}7$Ae`-8h#po` zw9B>K7bZj-d?_e>tKKgOGDm?(LzdSet11gsT)yD6uVTW}V_KDxtp6bEmNyFegK zV9glN9-bb8IUM|nkGY`xAPpyx4yWCdxeI{Z8O_Q)bKl;%`KEgyypk~WQ z8*l^?6G$f^g%tn!kNxPHYe-ZevQE=QA46TtUBAzb?NtK15b#(Z#ul|C+hBeYNy=px4$KO$31!GK1H^>wwyY6<>JO-Zx|E0k6d^|q4tH= zPW;&4`FDqQdK*MfmM3TkmLGq{x3C8d_bvqWgOy}r-R2ee08MrY6#{6hdk*$`2P>+a zaJyYMA*+=ollu^7wm6xV?2uru+d{~ee(r<1{z1o;R54EV&0ul2=pLQ#Vt$ zQ+HFZ8d^lwebiwK;wySYD_8(Epao#5%>sc>6`X<(tqR+g1-F|dl2Z#V6q>%;&?6BV zZK5n}6P(sIV>=MZfn;(POfBIj9!7GaTyo@GL@&l-MK2+vD{rBtbf&`Gd^1zY$0ZHP z**x6SBRSbAxvq>3-$KjD+$cr4(scCHhg)U67~WTD#>44no0V8<2z++x^;cHQyhii+ z8BV%_r*)byS6c-!Qrn~NsTo3{RV@oN&Xd1F;%4$Z9YJEbTKC2YuBfQ7hO|emNNR2H zor;>M$z8P)k{V#~Fye6x&#?+rg`jKu9KI4uqc>2uVQKV!>JjSXRxU?~q)^WjFo3A- z6k2Ux*V^t_WfGhb7zWBkh|TC&M6uYyzox~L8xB!CGDP2qkDLA@yuk1ldBRskl2qjp z!$A`3J+yXYuSoAtByyJRUTzk$pNKn}UB^OOB2{Akofcw+qqG>y9~S8Sg+jrMW$U(< zGK+7v!Wkp~X^}q4lu~!Pwwb?CxegzOapH7|X&9NX)ffdge;5me5B2a&HJY8uBFo)MRd@rN^;?!^VOYm zVV36h$cUdU;^TX&c|o7s`G_o4t&opzzJb4C8$=HZm`U%Ueqmw_tTRL$YBzk84`Z9n z4%Y_r&jqq@y=_w<*eN_H%C>L7aMXnOtoHWJ5^92a!h;r?+z`a6Zg*{c4Xj``t0$^h z0?NGQ`0=+KKXLNt(UV6JOa{0(8|OuZRj_!gghLw3b74(46qaYTP*@I!B!!g)A;!fS zo?~NdOb{ei!inXuAu$507`iEOB(V~f6`WY&tR{>*p7PZ@ZnYh|APgp zlgEL8KRF5n+-fL7C?cq$E{nXViI!-}Y={nVA)XJ>j7qDlY>Ji?5;;*aL{*SYK{0T8 zLlQWFq*oY)Rv3Eg1EMDIq8b*}&c{CHkIO$qzm48bsZ&5p9{lpH@(OA@9qzSLJ0?S{CB z6DoR(|5CL-qe(jfi2#)wuzXeLm=An?hyw#Q&)``OU-%9J6B@@8xUrUh%|+3jAAkOt z4?p-IIDvxIIzQs@{h~Pt@qkMGgWxA0;=pjyr$sT6A%u75YhhvT3(_{u8%aX`3 zqQK~!)R6o+du)cg}}D5oQt12r2{phh{@k*kGg( zKH_+WQNfvl4X2Dh_Gj*Y1zD}H-5A1ME3Wc_07=898QyBItKhnWUA>S+E68&g!DBeD zAQY0wYkHes@P6SJyf184fBo01Xfb&zS?TxxkE7G5f7##mdVhOYZ*XpP73t>|7g22R zcdIv!v3ekH8qd|j#QG`7wyB_Ir}TG^yv1+au(xd} zjpWs_-#Bz{=m9r`)=9!w?7uZTHC6itr1AFcze~K8pL??MH}@VYWzyxCHOVu9a`Bg9 zAC_VRAUDt$@By%Sy+zz6n(zgHC`-Z+zud9CR^U!dZsKn-fOvhFQ1!3O9aow%euKqV zhTIP^;0*H}EqwKIb)O;YcPAdf^SyHWRu_d-HH38e>u#O@1fld^!#r{-!h&K8cPzb>rT*OR;205&n##0r<@jCfw?<#1tVldhV~x7lXN^-h)mpL)LY98 zD`Z6Gl^K@w@xfn_*XPHqc#X(~7V!|KwKY!cNu1V(@odp+Bbu{0nocZFSvEMfS{%dV zD&{>c#NcCu5_-a6!6}!HB+ymM35FRa`91O;ohC1puYU;mC*$`fOYq~`RFAp|e91cy z3dQr;1%pS2tTJ~1Jc}?Ce}4FRL<}F=_;~ag3phylh51kTOcHtRED~Cz4ZP0(#Vlt% zni25^i$u3Fg1eu-wL)XInH7&=yoiYT!@N3^vHj8He!;vBI6LO?wWfH4Wy-fQVrD6m zInAKfl$Ah;vv$%nYcDsU)=3(*R}z>-ww}cI5(-cAh`}A}p9u@zdX2~u7Kbp4rJjgc ztO1KdoXao?KMQ1<6S3-a%%=qC?B*dndVK{IJUqAdnxz~b6-v=u)Io(i?+|KitIe&k zuVbHMe=zt8>K8uzVU)@Yp3l7Tn*Xo0tBH~0xXxYGf7Sp0-80kkxAWKgJDi=~nWn@a zjg>V;TB1ZljgRRUdsXj!ui1Ql)o~WneEY?N z&u?wj$mN5-9y}5c57*3NXn&E@Ec!{V!U>bn3DWNnglt4|Li|gh{ZT=hXpAcry@!5Mmr6dPpuK}zpkkU^%2 zTCRFSNg_NebTkZpvczED`KK2KD{imn;<++-VLOv&=}sKEpA)aS?w9CS+$hOgxtwhm zxmILby|1#j|4_ZLGR_JuFTOd2gWODOQ^Rwe37qM)ma@JY`0JMAdJ{P5&=1r;bIp%w zt(G{MOw3*j#Nitayu5MbbAa_7;_CQg3Ys6U&bzz}!;^YdhHn6zuLBQc&!^4z z^UB_$w0(0x|9VFFEMk|GpbweK_pD~{bfXCtu6x1*-aEonel#0uhuet5`4QH@NRXA` z1=7HUT9jXIPdu+<*i!7|^_Kg3r^cLGy>^gyno+dcxBM_@buzWHcDkKpYKY5#-RobH;vqn5>CXr*B zUevH{r)#IRtQ{t{ZZzE3wv4Xsx(hF`oxtv%8=sDzibif|^maF&T-q6yZDaBkwd3tj z{Oa1>IjqoQihK32jE^sXco9TUbU4edja((?I zoPT<5{p32?_w9!QCKGsnDBRuMy}Li3@56EAe|VF81Ln!k2||qE=D5z#A@LNXTX^Cj z0Yp9+79eTPBO8_Ru`nlmg-jj3EP$5&BaYq}wMp3`=;i@D!PjYKJOmJKKVqx>qt9kc zwGB!$B9d=V3a>vG>8c;XJl05@c+K)59Nx`fuy-!iy_^(5uUmXSdM% zKAEODk)$NnKsyQRY4=fkBpaTOi^4V329~TpIz%y0DrA&-@rbu_U+HJn)^G_g%p<;( zv0&!7SsW)~g{Q#j@z)YX2U_rJmZs~PW&4`;x0a^)mZQP(M?FO|9j~qhUPEpU7IMQ2 ztjKdrP3eKEVQuclt7}oM*R(84e0sfMhW7fDsx5^zw3 z%N{f=+k>kwj-FgkT9D?8j7n$+$z<0I{Z_JmYG~K9wsb;KJQrm1c6RWK@XR1FMJjT4 zW-T6`4g9LjRBjVF0?F%>ATtPs4f6^Io2iWA``l2ONB?P29`!Wu6~HX~k&??esGz`x zsE3@PrHki0KMOMu0vGl8=snxH6*Zl>r--BU&3(Vw2zpVFt}yv3=o_Ag2qcmkWN+?| z^YJz&!we5ML@T#^oq7Ktb)yY2Yg;y1E!EQGt9xcwhJ{;qQcR3E#yv>dH2dcWR|kRUtyTPDS}oh+voD zDvE--7td#x_JsvPvT&S>W!gMX+F~A;crqoFOoC(zj`5Vl3b&G{r8Qw01+~0Nwqtpo z*(hc4RFu5%I*BnAs6nCTRG^kFf^cLgVQ-{rP7M^}6;19cauC;4O3aXolqtm51I-5Q z%U0x2j@*{6u+(UE6y3E?t2=GgJKeo>Wi(Xe#JljUfi|Y1s@qE3-Zj;@xcVuYP82cq zr6{pPN;O4^6&4Yq88jk?#zmv`OaCXI9n8i;z!sAfH-jC!5TdP_WqGvp8s zhC@e{xKbPvChl=V*eM#m;tu{4&IiRg6&g46sbo?{+s+#6nkIE^%`k~55kyd_vCx)k zW~~ONWJhWZ(h&GSq>60FwoAy^Uvt`2qozz^?oCYc_gA=RnDmm8oXiwQ@zrUtBH>Gh212=V}oCjzk$*pl6 ztS3A|l9$zWOG9uVqDXyx{t0($hyfzcx*`)8EEord8al$;04EWw?SB)9QuzS|9wIDlJFK<^~HrZo4pD274p-e~sOiJ(f2P{?pbpTiZMA)#Pbbru@HJd+TwX_%C*7 z+u>r_7s-r&Kjvp&dfC@GriI#9L7GF{U^JW0aqk$ze|OOxZ?V`X*AMPpTI_cxwQ;!1k_(Y~ks5vmgI52>?t|?2cRy|T*D2nHHHZA*!K*CA$;&a8zZ+`pDG`lf7 zefc;Zhbb(oRk>x!<$1;Kje?mbkowf|^1MT!DN2R>LV1A=rk;zb7 ziGlEiYos7OVPi;|&TwXnMUk1Vml%UK(Hi9TVX^)`RHzI!f}zBtFxz=SsKDJ&YvP8o zp+@yqOOj1A=8+vqB#v-QU##(nN@x+U9)py?&}>-_`a_>v;(`VV%#kePn?aT$2R4~p zaQ5~t?d{p0m|VYf{rV~EwT(C4*m%u4wHxm3P4@Pp-ypZ|O*-CWe}Ce2Cii~!v-`F0 zJb3V(n!7o0_xIi5#!?4gmgVs!`M-bnQQB2ZjfeL@pTEQRa94!6uq%8V^w;0QcodJ? z<|@;95voy%qmH=cp*&O6zD>|qb(BR_jLtIzMk4rv{$BZ9?dL^;6L7BtF5)&u|IW8U ze)rmi3)e3E@SRr(d-<)GVF0M5TU4%h>oVO+rN(eJAGUV>c%F_$b76|(XMOTMWA2OB zUUV6I>7AFzq>N`#AJprE&yE@^>VtGROdp7$#u^8g90eDz73UJuZQJTT4_%_C1oO}Qu z%}||3tb0DzA61tuuDtV-Wsf`5w$TQ4tKItG=_{1Tt@c=KcAMh3-C`%k|H_!RwtjVe z&8w<+^X9=n51KbY>-ItFF-^-?JTJQ0{I5ZiNwy~MN1mrRaqM97K0|EppVzQ*CLCD3 zT63_(%YAqkU-xl;L{uB>s>|r^tnIdhDqe^d(Zt&vw7kfwliKV#yjkp6U`>( z-+J!7)pg82NM!cP!GD(_oUn|);Py)x(?AH+uo6HK<7E1^ zt421W_D^j(%8aYbN_zPhZ+8(3)?IXOhnpMZTBj??qld!7kt}sP26O1S$sRlSH?qR^ zCLF`^$Vb4$=je!dLGB(W6_I_o@jmJD8(04qDQD;50001Z+I^2bPQySDg`e}MAR&|^ zY(WtUZ`PJ8cS%r0inQq+yiP17c4Ti9rK1F{g2V|p1Dt`D6VSo4vmikcuXg8~H{&Vu-u{kf-wqcL@+#K2rO{mIV4BekVdRW%+{<|l|54Su1FK) z$jDVJuw-4phlFD6v%_$05n{lV*xux~SE6JlscPz1z_`$n(Xm(@#4wIFVpK%&S|7wd zH7ha6Gc(DHr53(aqQ5d`8x95u)ud8onaE5Vx=iaqDQ@dnQpmIPHqS`2`h_WWd>3Xq zPIru_9uac?_dBWqTzKqrxfLm((VrWJ;%6=~a6RAkl^2}6-kh@wN@-mZ^sSs_9jn`5 zu8T6wGoh3xl~UrA+cyySaH0Ts+HIF(w4LV`Mzhvxo7zg<)Xsj_vsG`_wv7}iQ`<;V z+qUi0wr$(Sy|**}ZhrI|o#wVPo0Y zHjWLnacw*s-zKmLZ6cf4Cb3CvGMn6{uqkaSo7$$aX>E{AXVcpZHlxjCGutdSs|~i< zY<8Q&=CrwNZkxyEwfSs*Tfi2yg=~mbtXP_?YBj4{!Vz#&~VM|)e+SakI z^{j78+0wR*Eo;l!^0tDlXe-&uwu-H4tJ&(dhOKF9+1j>_t!wMq`nG{>XdBtawux7kD+157Hwy|w(JKNrd*$%d&?PNRKF1D);x7}=a+r##>y=-sW$M&`TY=1kz z4zz>pU^~PPwZrUiJHn2%qwHuKVIysn9b?DZady0&U?Dj&cClSzm)d1^xm{sb+EsS7U1QhUb#}eoU^m)LcC+1Lx7uxXyWL@T z+Ff?H-D9KeUc1lkw+HM&d&nNPN9<91%pSKV>`8mdp0;P~S$oc&w-@Y1d&yq5SL{`L z&0e=R>`i;i-nMt_U3<^ow-4+?`^Y}FPwZ3s%s#g->`VK~zP4}dTl>zww;$|B`^kQ` zU+h==&3?B(>`(j4{`UX=?{^O$%g6R{e4vl(R`eI}pTXYpBmu+QeR`y4)}&*gLbJU*|_=kxmlzMwDUL%ia})8$pK zdEFb{^o4y9U(^@##eE51(p%p4j(5H1eP7C#_GNroU(T2J6?{cs$yfGOd{tk~SNAo1 zO<&8`_H}$+U(eU~4SYl2$T#*)d{f`dH}@@kOW(@3_MyIwZ|mFn_CCya@Ev_8-`RKZ zU46Lk=DYhIzNhcyd;31VukYvk`vHESALIx7A%3VI=7;+cexx7eNBamL>7)D@Kh}@) z_FZ0X&3cu2?@~izC zzt*qw>-`45(Qopb{T9E~Z}Z#z4!_gy^1J;WAMN-0eSW_`;1Bvk{;)sdkNRW&xIf`f z`cwY2KjY8(bN;-);4k`1{<6Q~ulj5Ly1(IX`dj|CzvJ)vd;Y$E;2-)&`?sZ)^SI`@hQn?fw72{+Iv1&Ho?m|CRr5_y33WzvBOu^KfwYcWg|0BcaZA zY=>c7kG$}a7Q;XvnaM|%7)JWY1s|DW80sT?`N$SSjb+_0liOw(rghCso;Jgj?Rbu1 z^%=q9V=;#1XM{f=t1+zC5#T=NWT>JQsN@{j8+B!|;MoWZu};_IjS)7}I#d^nzJ1|h zUUjj5Y0}FGwQ0RilYT}hhV@KMdK#e`)_XPSYlQN(o~=o5Bh;_;eogv&L&2=4H|g;W z6|>&6NuO^hZR=T^^jbpd_K|HHV{_yWC$--&EbE$^JpG1g+wr`O)o%o=kHu{)eLhZ*A3oB#<-H!zpmh^N?G}P+rOn$t>!p^Tl`0)}8OTW(G$6G9H z&vh6-USnZ>uCw^@9t(TD4(`W`c39|jdOzN@!)9HF_TyDMtk!jAKi;*&F0VuS@vb=4GaICnxJT-ZNq*3iRQe}d8YG9=kd<-ou@l5 zcb@FL*m<_|{?^YI{ed5{)-}dI5dVRd;e~2Gme?R~}yp9u^SNc!>Zh}S} z-_!p7;*;tvKjd`xxp%+6_z2AWp^F9P{=f5&+OM}?m;b!8zOwVb@PGAr z{x^@dKdVl18=YHXO^?!bU=Kiq$SPm7)SH0)8XKID^$FN6hk3@HF7a9nG$oBa5 z`wawO%Y7*9&lkS0w$Ha__7Ag#61 zzn!f|)dbrg5cox@`ol}NOTBLnz6ZkP>Nnf(1EGKVq;o2BJ*_5}sO^lNv|ANO{ChIv zU%_^j^6nkcNA4=U6D!=_vD>x8yTtpB?xQ@x^6nQiK@cqYOARLNUxEcfH6WWqfzYD= zqmZ5<@ap=1#&`AC>w%pV1_s9e{~4GnZY3uqB&0Aj8E@-8*ZhL}bdS2@OHBsvzieL^ E0LEz`i~s-t From 5d43697ed8f1e87a7bc4459ab155d1f81bedea89 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 19 Jan 2018 12:42:44 -0600 Subject: [PATCH 34/99] Cleaned folder heirarchy --- Gruntfile.js | 15 ++++++++------- app/assets/images/.keep | 0 app/assets/images/archive.png | Bin 2056 -> 0 bytes app/assets/images/encryption.png | Bin 3360 -> 0 bytes app/assets/images/filter.png | Bin 1808 -> 0 bytes app/assets/images/logo.png | Bin 9211 -> 0 bytes .../javascripts/app/{app.frontend.js => app.js} | 2 +- .../app/{frontend => }/controllers/editor.js | 4 ++-- .../app/{frontend => }/controllers/footer.js | 4 ++-- .../app/{frontend => }/controllers/home.js | 2 +- .../app/{frontend => }/controllers/lockScreen.js | 4 ++-- .../app/{frontend => }/controllers/notes.js | 4 ++-- .../app/{frontend => }/controllers/tags.js | 4 ++-- .../directives/functional/autofocus.js | 2 +- .../directives/functional/click-outside.js | 2 +- .../directives/functional/delay-hide.js | 2 +- .../directives/functional/file-change.js | 2 +- .../directives/functional/infiniteScroll.js | 2 +- .../directives/functional/lowercase.js | 2 +- .../directives/functional/selectOnClick.js | 2 +- .../directives/views/accountMenu.js | 4 ++-- .../directives/views/actionsMenu.js | 4 ++-- .../directives/views/componentModal.js | 4 ++-- .../directives/views/componentView.js | 4 ++-- .../directives/views/editorMenu.js | 4 ++-- .../directives/views/globalExtensionsMenu.js | 4 ++-- .../{services => }/directives/views/menuRow.js | 4 ++-- .../directives/views/panelResizer.js | 4 ++-- .../directives/views/permissionsModal.js | 4 ++-- .../app/{services => }/filters/appDate.js | 2 +- .../app/{services => }/filters/sortBy.js | 2 +- .../app/{services => }/filters/startFrom.js | 2 +- .../app/{services => }/filters/trusted.js | 2 +- .../app/{frontend => }/models/api/item.js | 0 .../app/{frontend => }/models/api/mfa.js | 0 .../app/{frontend => }/models/api/syncAdapter.js | 0 .../app/{frontend => }/models/app/component.js | 0 .../app/{frontend => }/models/app/editor.js | 0 .../app/{frontend => }/models/app/extension.js | 0 .../app/{frontend => }/models/app/note.js | 0 .../app/{frontend => }/models/app/tag.js | 0 .../app/{frontend => }/models/app/theme.js | 0 .../models/local/encryptedStorage.js | 0 .../{frontend => }/models/local/itemParams.js | 0 .../javascripts/app/{frontend => }/routes.js | 3 +-- .../javascripts/app/services/actionsManager.js | 2 +- .../javascripts/app/services/authManager.js | 2 +- .../javascripts/app/services/componentManager.js | 2 +- app/assets/javascripts/app/services/dbManager.js | 2 +- .../javascripts/app/services/desktopManager.js | 2 +- .../javascripts/app/services/httpManager.js | 2 +- .../javascripts/app/services/migrationManager.js | 2 +- .../javascripts/app/services/modelManager.js | 2 +- .../javascripts/app/services/packageManager.js | 2 +- .../javascripts/app/services/passcodeManager.js | 2 +- .../javascripts/app/services/singletonManager.js | 2 +- .../javascripts/app/services/storageManager.js | 2 +- .../javascripts/app/services/syncManager.js | 2 +- .../javascripts/app/services/sysExtManager.js | 2 +- .../javascripts/app/services/themeManager.js | 2 +- app/assets/javascripts/frontend.js | 5 ----- app/assets/javascripts/main.js | 1 + app/assets/stylesheets/{ => app}/_ionicons.scss | 0 .../{frontend.css.scss => main.css.scss} | 3 +-- .../directives/account-menu.html.haml | 0 .../directives/actions-menu.html.haml | 0 .../directives/component-modal.html.haml | 0 .../directives/component-view.html.haml | 0 .../directives/editor-menu.html.haml | 0 .../directives/global-extensions-menu.html.haml | 0 .../{frontend => }/directives/menu-row.html.haml | 0 .../directives/panel-resizer.html.haml | 0 .../directives/permissions-modal.html.haml | 0 .../templates/{frontend => }/editor.html.haml | 0 .../templates/{frontend => }/footer.html.haml | 0 .../templates/{frontend => }/home.html.haml | 0 .../{frontend => }/lock-screen.html.haml | 0 .../templates/{frontend => }/notes.html.haml | 0 .../templates/{frontend => }/tags.html.haml | 0 app/controllers/application_controller.rb | 2 +- .../{frontend.html.erb => app.html.erb} | 4 ++-- config/cable.yml | 10 ---------- config/initializers/assets.rb | 3 +-- config/routes.rb | 2 +- test/javascripts/controllers/HomeCtrl_spec.js | 4 ++-- test/javascripts/filters/DateFilter_spec.js | 2 +- 86 files changed, 75 insertions(+), 91 deletions(-) delete mode 100644 app/assets/images/.keep delete mode 100644 app/assets/images/archive.png delete mode 100644 app/assets/images/encryption.png delete mode 100644 app/assets/images/filter.png delete mode 100644 app/assets/images/logo.png rename app/assets/javascripts/app/{app.frontend.js => app.js} (97%) rename app/assets/javascripts/app/{frontend => }/controllers/editor.js (99%) rename app/assets/javascripts/app/{frontend => }/controllers/footer.js (98%) rename app/assets/javascripts/app/{frontend => }/controllers/home.js (99%) rename app/assets/javascripts/app/{frontend => }/controllers/lockScreen.js (78%) rename app/assets/javascripts/app/{frontend => }/controllers/notes.js (98%) rename app/assets/javascripts/app/{frontend => }/controllers/tags.js (98%) rename app/assets/javascripts/app/{services => }/directives/functional/autofocus.js (92%) rename app/assets/javascripts/app/{services => }/directives/functional/click-outside.js (95%) rename app/assets/javascripts/app/{services => }/directives/functional/delay-hide.js (97%) rename app/assets/javascripts/app/{services => }/directives/functional/file-change.js (92%) rename app/assets/javascripts/app/{services => }/directives/functional/infiniteScroll.js (89%) rename app/assets/javascripts/app/{services => }/directives/functional/lowercase.js (95%) rename app/assets/javascripts/app/{services => }/directives/functional/selectOnClick.js (94%) rename app/assets/javascripts/app/{services => }/directives/views/accountMenu.js (99%) rename app/assets/javascripts/app/{services => }/directives/views/actionsMenu.js (94%) rename app/assets/javascripts/app/{services => }/directives/views/componentModal.js (85%) rename app/assets/javascripts/app/{services => }/directives/views/componentView.js (91%) rename app/assets/javascripts/app/{services => }/directives/views/editorMenu.js (92%) rename app/assets/javascripts/app/{services => }/directives/views/globalExtensionsMenu.js (97%) rename app/assets/javascripts/app/{services => }/directives/views/menuRow.js (78%) rename app/assets/javascripts/app/{services => }/directives/views/panelResizer.js (96%) rename app/assets/javascripts/app/{services => }/directives/views/permissionsModal.js (91%) rename app/assets/javascripts/app/{services => }/filters/appDate.js (91%) rename app/assets/javascripts/app/{services => }/filters/sortBy.js (97%) rename app/assets/javascripts/app/{services => }/filters/startFrom.js (56%) rename app/assets/javascripts/app/{services => }/filters/trusted.js (52%) rename app/assets/javascripts/app/{frontend => }/models/api/item.js (100%) rename app/assets/javascripts/app/{frontend => }/models/api/mfa.js (100%) rename app/assets/javascripts/app/{frontend => }/models/api/syncAdapter.js (100%) rename app/assets/javascripts/app/{frontend => }/models/app/component.js (100%) rename app/assets/javascripts/app/{frontend => }/models/app/editor.js (100%) rename app/assets/javascripts/app/{frontend => }/models/app/extension.js (100%) rename app/assets/javascripts/app/{frontend => }/models/app/note.js (100%) rename app/assets/javascripts/app/{frontend => }/models/app/tag.js (100%) rename app/assets/javascripts/app/{frontend => }/models/app/theme.js (100%) rename app/assets/javascripts/app/{frontend => }/models/local/encryptedStorage.js (100%) rename app/assets/javascripts/app/{frontend => }/models/local/itemParams.js (100%) rename app/assets/javascripts/app/{frontend => }/routes.js (90%) delete mode 100644 app/assets/javascripts/frontend.js create mode 100644 app/assets/javascripts/main.js rename app/assets/stylesheets/{ => app}/_ionicons.scss (100%) rename app/assets/stylesheets/{frontend.css.scss => main.css.scss} (90%) rename app/assets/templates/{frontend => }/directives/account-menu.html.haml (100%) rename app/assets/templates/{frontend => }/directives/actions-menu.html.haml (100%) rename app/assets/templates/{frontend => }/directives/component-modal.html.haml (100%) rename app/assets/templates/{frontend => }/directives/component-view.html.haml (100%) rename app/assets/templates/{frontend => }/directives/editor-menu.html.haml (100%) rename app/assets/templates/{frontend => }/directives/global-extensions-menu.html.haml (100%) rename app/assets/templates/{frontend => }/directives/menu-row.html.haml (100%) rename app/assets/templates/{frontend => }/directives/panel-resizer.html.haml (100%) rename app/assets/templates/{frontend => }/directives/permissions-modal.html.haml (100%) rename app/assets/templates/{frontend => }/editor.html.haml (100%) rename app/assets/templates/{frontend => }/footer.html.haml (100%) rename app/assets/templates/{frontend => }/home.html.haml (100%) rename app/assets/templates/{frontend => }/lock-screen.html.haml (100%) rename app/assets/templates/{frontend => }/notes.html.haml (100%) rename app/assets/templates/{frontend => }/tags.html.haml (100%) rename app/views/application/{frontend.html.erb => app.html.erb} (95%) delete mode 100644 config/cable.yml diff --git a/Gruntfile.js b/Gruntfile.js index 939abb961..6878b6524 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -34,7 +34,7 @@ module.exports = function(grunt) { style: 'expanded' }, files: { - 'vendor/assets/stylesheets/app.css': 'app/assets/stylesheets/frontend.css.scss' + 'vendor/assets/stylesheets/app.css': 'app/assets/stylesheets/main.css.scss' } } }, @@ -59,7 +59,7 @@ module.exports = function(grunt) { src: ['**/*.html'], dest: 'vendor/assets/javascripts/templates.js', options: { - module: 'app.frontend' + module: 'app' } } }, @@ -70,12 +70,13 @@ module.exports = function(grunt) { }, app: { src: [ - 'app/assets/javascripts/app/services/encryption/*.js', + 'app/assets/javascripts/app/services/encryption/*.js', // this should come first 'app/assets/javascripts/app/*.js', - 'app/assets/javascripts/app/frontend/*.js', - 'app/assets/javascripts/app/frontend/controllers/*.js', - 'app/assets/javascripts/app/frontend/models/**/*.js', - 'app/assets/javascripts/app/services/**/*.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', ], dest: 'vendor/assets/javascripts/app.js', }, diff --git a/app/assets/images/.keep b/app/assets/images/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/assets/images/archive.png b/app/assets/images/archive.png deleted file mode 100644 index d6e8a204955502ecce1e5bf7bc08eed2131a7d27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2056 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 diff --git a/app/assets/images/filter.png b/app/assets/images/filter.png deleted file mode 100644 index b707c70b56a25be9871334b94cfe9ad1150b5381..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1808 zcmaJ?X;2eq7>>0A1{4s4qE@;Did;!HAqf(R5)+O%3B`ySPh66PEMc=DSs;L-99|rw zEf%qAQ7T|PX|+M2h@^m`crha&XhBe^H!9FdC3b_L{ZYCzyWcVI^SsaZzRx$iBO*L> zn$;{T5{WcT6eg4sBZqheQ!I#Y+tHy@#4r;Nmf=gW7(5x$!X&-|i-rLa3dO=w7*eDp zG{J!+(qvC0QijVUVy+xRX^=UFrbjgdnnVg*sMkPp6^sMXa4e$cQC?KnQUFB3qpV;{ zK#4{GE0M5NExa@}JW`&jk_RX#3l{)^dM-hL!Z-xz(Rj6vtLIThdAY>gyiKP7qanD8 zM|m$wCW!z9m=*@uG&WTZG8uq>0FA+B`};F}0Vc>`(Ln~C;YVcza9J!a2m)gdg~+B= z#Bim;kg;6Eibql6xQ0uoCnqPUK2Y3K|ZXg<<-phWWjp(y$xT8B&F zk9z-;SQnY1f$36Mhb3y|MB`%I%&9b7ffj~vOdE+|@#9sDP+~ZyQ(_uGAQ-J0;3r;JpTpts{TLiSKMse@ z4q%OAg_t}Mh1K{tR`C(bnh+c7z&IYoQq7Xg)t%D7*;W{7UqOl zIw6K`ZtuU^J-$VF$UOaEUSjdV{;-sb=tJG^$GY z3|H^PQ*$WuoOe_+a~}5OtX>nAdGNEr=ud4+?CfmgOq=gyW^Oy8kFclHoFnq)hvofU zxZRTmG#+&Rp&sO>!<)9ioxG_sc~wVxfA6xgs$7%w?y0DnI9Jn|(r%9?k zjF5Ncp@+Nr>`N&M$4=Nlp3UEMtNh*8R2$TxRaD&nsCaP~y05}*p!0(JPSePM#6X_k z$P7BEdCZ*eEcC9US`-%FZ1M3Ps@iZP-sLQ5anOm-cX9ofRE^rT3zV&sJhI~Y_ty@B zQm3qf=MH64%9(>(u!NNUmn_@3DA=V6?JkNPv5`G3DDm{3?0x+5S4F=%hQ`gIikIBq zO`E*uuQWGjhYOw3hVAL=4*U{1Qr~xf`Op_O-FfJPqQ07#cyedjhVb7dwKoio0~*6f zjCk3~*M#s!LzhSWC&O(9@=|0myQ`zGIw8lCndDDr>)YNZgh{|V%QMXSGl`V)UMae+22}!X=7!%(F<7VC3f1R&bs}! zddHh`-zWKw;ggW_`NsQ3+^+aX^7;VTZM&#Y!imGcm6T_zqUK_PM{rH) z9S^8j0LEwiAlJe}+ z7YCbLn*})qt9cEpc6s)m*n4aL%cuuA+Nhw7hmz)sMVbt!K&#)}eOD<%%ti#$wlo;M zJOT^g+CcQXQx{X{gJNK1a(~7OH1?M_D>Qtty=~F;befO1R Us{F7y=6{qZI9zyg(fZ7P0R3dqL;wH) diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png deleted file mode 100644 index 9b0c179d221b755e4ab47978e56c125f4ba0f665..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9211 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 new LockScreen); +angular.module('app').directive('lockScreen', () => new LockScreen); diff --git a/app/assets/javascripts/app/frontend/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js similarity index 98% rename from app/assets/javascripts/app/frontend/controllers/notes.js rename to app/assets/javascripts/app/controllers/notes.js index c5660f93d..4b7376f8c 100644 --- a/app/assets/javascripts/app/frontend/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -1,4 +1,4 @@ -angular.module('app.frontend') +angular.module('app') .directive("notesSection", function(){ return { scope: { @@ -7,7 +7,7 @@ angular.module('app.frontend') tag: "=" }, - templateUrl: 'frontend/notes.html', + templateUrl: 'notes.html', replace: true, controller: 'NotesCtrl', controllerAs: 'ctrl', diff --git a/app/assets/javascripts/app/frontend/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js similarity index 98% rename from app/assets/javascripts/app/frontend/controllers/tags.js rename to app/assets/javascripts/app/controllers/tags.js index e053b5b4e..76f11ddf2 100644 --- a/app/assets/javascripts/app/frontend/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -1,4 +1,4 @@ -angular.module('app.frontend') +angular.module('app') .directive("tagsSection", function(){ return { restrict: 'E', @@ -13,7 +13,7 @@ angular.module('app.frontend') updateNoteTag: "&", removeTag: "&" }, - templateUrl: 'frontend/tags.html', + templateUrl: 'tags.html', replace: true, controller: 'TagsCtrl', controllerAs: 'ctrl', diff --git a/app/assets/javascripts/app/services/directives/functional/autofocus.js b/app/assets/javascripts/app/directives/functional/autofocus.js similarity index 92% rename from app/assets/javascripts/app/services/directives/functional/autofocus.js rename to app/assets/javascripts/app/directives/functional/autofocus.js index cbc093a5f..8bcf6c1d1 100644 --- a/app/assets/javascripts/app/services/directives/functional/autofocus.js +++ b/app/assets/javascripts/app/directives/functional/autofocus.js @@ -1,5 +1,5 @@ angular - .module('app.frontend') + .module('app') .directive('snAutofocus', ['$timeout', function($timeout) { return { restrict: 'A', diff --git a/app/assets/javascripts/app/services/directives/functional/click-outside.js b/app/assets/javascripts/app/directives/functional/click-outside.js similarity index 95% rename from app/assets/javascripts/app/services/directives/functional/click-outside.js rename to app/assets/javascripts/app/directives/functional/click-outside.js index 10e40e1da..dd8296470 100644 --- a/app/assets/javascripts/app/services/directives/functional/click-outside.js +++ b/app/assets/javascripts/app/directives/functional/click-outside.js @@ -1,5 +1,5 @@ angular - .module('app.frontend') + .module('app') .directive('clickOutside', ['$document', function($document) { return { restrict: 'A', diff --git a/app/assets/javascripts/app/services/directives/functional/delay-hide.js b/app/assets/javascripts/app/directives/functional/delay-hide.js similarity index 97% rename from app/assets/javascripts/app/services/directives/functional/delay-hide.js rename to app/assets/javascripts/app/directives/functional/delay-hide.js index 6e381ed3f..fd52d70fb 100644 --- a/app/assets/javascripts/app/services/directives/functional/delay-hide.js +++ b/app/assets/javascripts/app/directives/functional/delay-hide.js @@ -1,5 +1,5 @@ angular - .module('app.frontend') + .module('app') .directive('delayHide', function($timeout) { return { restrict: 'A', diff --git a/app/assets/javascripts/app/services/directives/functional/file-change.js b/app/assets/javascripts/app/directives/functional/file-change.js similarity index 92% rename from app/assets/javascripts/app/services/directives/functional/file-change.js rename to app/assets/javascripts/app/directives/functional/file-change.js index afec10954..c133c0852 100644 --- a/app/assets/javascripts/app/services/directives/functional/file-change.js +++ b/app/assets/javascripts/app/directives/functional/file-change.js @@ -1,5 +1,5 @@ angular - .module('app.frontend') + .module('app') .directive('fileChange', function() { return { restrict: 'A', diff --git a/app/assets/javascripts/app/services/directives/functional/infiniteScroll.js b/app/assets/javascripts/app/directives/functional/infiniteScroll.js similarity index 89% rename from app/assets/javascripts/app/services/directives/functional/infiniteScroll.js rename to app/assets/javascripts/app/directives/functional/infiniteScroll.js index 372f0bf7b..3e1e633ef 100644 --- a/app/assets/javascripts/app/services/directives/functional/infiniteScroll.js +++ b/app/assets/javascripts/app/directives/functional/infiniteScroll.js @@ -1,4 +1,4 @@ -angular.module('app.frontend').directive('infiniteScroll', [ +angular.module('app').directive('infiniteScroll', [ '$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) { return { link: function(scope, elem, attrs) { diff --git a/app/assets/javascripts/app/services/directives/functional/lowercase.js b/app/assets/javascripts/app/directives/functional/lowercase.js similarity index 95% rename from app/assets/javascripts/app/services/directives/functional/lowercase.js rename to app/assets/javascripts/app/directives/functional/lowercase.js index 1899a251b..40ce7421c 100644 --- a/app/assets/javascripts/app/services/directives/functional/lowercase.js +++ b/app/assets/javascripts/app/directives/functional/lowercase.js @@ -1,5 +1,5 @@ angular - .module('app.frontend') + .module('app') .directive('lowercase', function() { return { require: 'ngModel', diff --git a/app/assets/javascripts/app/services/directives/functional/selectOnClick.js b/app/assets/javascripts/app/directives/functional/selectOnClick.js similarity index 94% rename from app/assets/javascripts/app/services/directives/functional/selectOnClick.js rename to app/assets/javascripts/app/directives/functional/selectOnClick.js index a8ad86cb1..598e45086 100644 --- a/app/assets/javascripts/app/services/directives/functional/selectOnClick.js +++ b/app/assets/javascripts/app/directives/functional/selectOnClick.js @@ -1,5 +1,5 @@ angular - .module('app.frontend') + .module('app') .directive('selectOnClick', ['$window', function ($window) { return { restrict: 'A', diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js similarity index 99% rename from app/assets/javascripts/app/services/directives/views/accountMenu.js rename to app/assets/javascripts/app/directives/views/accountMenu.js index 39028041d..2efe01744 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -2,7 +2,7 @@ class AccountMenu { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/account-menu.html"; + this.templateUrl = "directives/account-menu.html"; this.scope = { "onSuccessfulAuth" : "&", "closeFunction" : "&" @@ -584,4 +584,4 @@ class AccountMenu { } } -angular.module('app.frontend').directive('accountMenu', () => new AccountMenu); +angular.module('app').directive('accountMenu', () => new AccountMenu); diff --git a/app/assets/javascripts/app/services/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js similarity index 94% rename from app/assets/javascripts/app/services/directives/views/actionsMenu.js rename to app/assets/javascripts/app/directives/views/actionsMenu.js index f4ef1a4ae..f7842fc05 100644 --- a/app/assets/javascripts/app/services/directives/views/actionsMenu.js +++ b/app/assets/javascripts/app/directives/views/actionsMenu.js @@ -2,7 +2,7 @@ class ActionsMenu { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/actions-menu.html"; + this.templateUrl = "directives/actions-menu.html"; this.scope = { item: "=" }; @@ -83,4 +83,4 @@ class ActionsMenu { } -angular.module('app.frontend').directive('actionsMenu', () => new ActionsMenu); +angular.module('app').directive('actionsMenu', () => new ActionsMenu); diff --git a/app/assets/javascripts/app/services/directives/views/componentModal.js b/app/assets/javascripts/app/directives/views/componentModal.js similarity index 85% rename from app/assets/javascripts/app/services/directives/views/componentModal.js rename to app/assets/javascripts/app/directives/views/componentModal.js index a02cb1f00..9d73df8ad 100644 --- a/app/assets/javascripts/app/services/directives/views/componentModal.js +++ b/app/assets/javascripts/app/directives/views/componentModal.js @@ -2,7 +2,7 @@ class ComponentModal { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/component-modal.html"; + this.templateUrl = "directives/component-modal.html"; this.scope = { show: "=", component: "=", @@ -39,4 +39,4 @@ class ComponentModal { } -angular.module('app.frontend').directive('componentModal', () => new ComponentModal); +angular.module('app').directive('componentModal', () => new ComponentModal); diff --git a/app/assets/javascripts/app/services/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js similarity index 91% rename from app/assets/javascripts/app/services/directives/views/componentView.js rename to app/assets/javascripts/app/directives/views/componentView.js index 2cbd6e586..2ebc2b24a 100644 --- a/app/assets/javascripts/app/services/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -2,7 +2,7 @@ class ComponentView { constructor(componentManager, $timeout) { this.restrict = "E"; - this.templateUrl = "frontend/directives/component-view.html"; + this.templateUrl = "directives/component-view.html"; this.scope = { component: "=" }; @@ -71,4 +71,4 @@ class ComponentView { } -angular.module('app.frontend').directive('componentView', (componentManager, $timeout) => new ComponentView(componentManager, $timeout)); +angular.module('app').directive('componentView', (componentManager, $timeout) => new ComponentView(componentManager, $timeout)); diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js similarity index 92% rename from app/assets/javascripts/app/services/directives/views/editorMenu.js rename to app/assets/javascripts/app/directives/views/editorMenu.js index 2daea9aea..51d301fb0 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/directives/views/editorMenu.js @@ -2,7 +2,7 @@ class EditorMenu { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/editor-menu.html"; + this.templateUrl = "directives/editor-menu.html"; this.scope = { callback: "&", selectedEditor: "=" @@ -68,4 +68,4 @@ class EditorMenu { } -angular.module('app.frontend').directive('editorMenu', () => new EditorMenu); +angular.module('app').directive('editorMenu', () => new EditorMenu); diff --git a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js similarity index 97% rename from app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js rename to app/assets/javascripts/app/directives/views/globalExtensionsMenu.js index f2dc67cef..649c4c2fb 100644 --- a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js +++ b/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js @@ -2,7 +2,7 @@ class GlobalExtensionsMenu { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/global-extensions-menu.html"; + this.templateUrl = "directives/global-extensions-menu.html"; this.scope = { }; } @@ -212,4 +212,4 @@ class GlobalExtensionsMenu { } -angular.module('app.frontend').directive('globalExtensionsMenu', () => new GlobalExtensionsMenu); +angular.module('app').directive('globalExtensionsMenu', () => new GlobalExtensionsMenu); diff --git a/app/assets/javascripts/app/services/directives/views/menuRow.js b/app/assets/javascripts/app/directives/views/menuRow.js similarity index 78% rename from app/assets/javascripts/app/services/directives/views/menuRow.js rename to app/assets/javascripts/app/directives/views/menuRow.js index 1809a2505..20fc1653c 100644 --- a/app/assets/javascripts/app/services/directives/views/menuRow.js +++ b/app/assets/javascripts/app/directives/views/menuRow.js @@ -3,7 +3,7 @@ class MenuRow { constructor() { this.restrict = "E"; this.transclude = true; - this.templateUrl = "frontend/directives/menu-row.html"; + this.templateUrl = "directives/menu-row.html"; this.scope = { circle: "=", title: "=", @@ -28,4 +28,4 @@ class MenuRow { } } -angular.module('app.frontend').directive('menuRow', () => new MenuRow); +angular.module('app').directive('menuRow', () => new MenuRow); diff --git a/app/assets/javascripts/app/services/directives/views/panelResizer.js b/app/assets/javascripts/app/directives/views/panelResizer.js similarity index 96% rename from app/assets/javascripts/app/services/directives/views/panelResizer.js rename to app/assets/javascripts/app/directives/views/panelResizer.js index 62226c107..917ab06bf 100644 --- a/app/assets/javascripts/app/services/directives/views/panelResizer.js +++ b/app/assets/javascripts/app/directives/views/panelResizer.js @@ -2,7 +2,7 @@ class PanelResizer { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/panel-resizer.html"; + this.templateUrl = "directives/panel-resizer.html"; this.scope = { index: "=", panelId: "=", @@ -198,4 +198,4 @@ class PanelResizer { } -angular.module('app.frontend').directive('panelResizer', () => new PanelResizer); +angular.module('app').directive('panelResizer', () => new PanelResizer); diff --git a/app/assets/javascripts/app/services/directives/views/permissionsModal.js b/app/assets/javascripts/app/directives/views/permissionsModal.js similarity index 91% rename from app/assets/javascripts/app/services/directives/views/permissionsModal.js rename to app/assets/javascripts/app/directives/views/permissionsModal.js index 5988bae86..fb7609d55 100644 --- a/app/assets/javascripts/app/services/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/directives/views/permissionsModal.js @@ -2,7 +2,7 @@ class PermissionsModal { constructor() { this.restrict = "E"; - this.templateUrl = "frontend/directives/permissions-modal.html"; + this.templateUrl = "directives/permissions-modal.html"; this.scope = { show: "=", component: "=", @@ -74,4 +74,4 @@ class PermissionsModal { } -angular.module('app.frontend').directive('permissionsModal', () => new PermissionsModal); +angular.module('app').directive('permissionsModal', () => new PermissionsModal); diff --git a/app/assets/javascripts/app/services/filters/appDate.js b/app/assets/javascripts/app/filters/appDate.js similarity index 91% rename from app/assets/javascripts/app/services/filters/appDate.js rename to app/assets/javascripts/app/filters/appDate.js index 381c3ed54..9a84a796e 100644 --- a/app/assets/javascripts/app/services/filters/appDate.js +++ b/app/assets/javascripts/app/filters/appDate.js @@ -1,4 +1,4 @@ -angular.module('app.frontend') +angular.module('app') .filter('appDate', function ($filter) { return function (input) { return input ? $filter('date')(new Date(input), 'MM/dd/yyyy', 'UTC') : ''; diff --git a/app/assets/javascripts/app/services/filters/sortBy.js b/app/assets/javascripts/app/filters/sortBy.js similarity index 97% rename from app/assets/javascripts/app/services/filters/sortBy.js rename to app/assets/javascripts/app/filters/sortBy.js index c3902604b..2f19d3769 100644 --- a/app/assets/javascripts/app/services/filters/sortBy.js +++ b/app/assets/javascripts/app/filters/sortBy.js @@ -1,4 +1,4 @@ -angular.module('app.frontend') +angular.module('app') .filter('sortBy', function ($filter) { return function(items, sortBy) { let sortValueFn = (a, b, pinCheck = false) => { diff --git a/app/assets/javascripts/app/services/filters/startFrom.js b/app/assets/javascripts/app/filters/startFrom.js similarity index 56% rename from app/assets/javascripts/app/services/filters/startFrom.js rename to app/assets/javascripts/app/filters/startFrom.js index 3cf824c4c..7b64038f6 100644 --- a/app/assets/javascripts/app/services/filters/startFrom.js +++ b/app/assets/javascripts/app/filters/startFrom.js @@ -1,4 +1,4 @@ -angular.module('app.frontend').filter('startFrom', function() { +angular.module('app').filter('startFrom', function() { return function(input, start) { return input.slice(start); }; diff --git a/app/assets/javascripts/app/services/filters/trusted.js b/app/assets/javascripts/app/filters/trusted.js similarity index 52% rename from app/assets/javascripts/app/services/filters/trusted.js rename to app/assets/javascripts/app/filters/trusted.js index 7bb57b301..f2a41a3c0 100644 --- a/app/assets/javascripts/app/services/filters/trusted.js +++ b/app/assets/javascripts/app/filters/trusted.js @@ -1,4 +1,4 @@ -angular.module('app.frontend').filter('trusted', ['$sce', function ($sce) { +angular.module('app').filter('trusted', ['$sce', function ($sce) { return function(url) { return $sce.trustAsResourceUrl(url); }; diff --git a/app/assets/javascripts/app/frontend/models/api/item.js b/app/assets/javascripts/app/models/api/item.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/api/item.js rename to app/assets/javascripts/app/models/api/item.js diff --git a/app/assets/javascripts/app/frontend/models/api/mfa.js b/app/assets/javascripts/app/models/api/mfa.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/api/mfa.js rename to app/assets/javascripts/app/models/api/mfa.js diff --git a/app/assets/javascripts/app/frontend/models/api/syncAdapter.js b/app/assets/javascripts/app/models/api/syncAdapter.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/api/syncAdapter.js rename to app/assets/javascripts/app/models/api/syncAdapter.js diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/models/app/component.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/app/component.js rename to app/assets/javascripts/app/models/app/component.js diff --git a/app/assets/javascripts/app/frontend/models/app/editor.js b/app/assets/javascripts/app/models/app/editor.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/app/editor.js rename to app/assets/javascripts/app/models/app/editor.js diff --git a/app/assets/javascripts/app/frontend/models/app/extension.js b/app/assets/javascripts/app/models/app/extension.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/app/extension.js rename to app/assets/javascripts/app/models/app/extension.js diff --git a/app/assets/javascripts/app/frontend/models/app/note.js b/app/assets/javascripts/app/models/app/note.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/app/note.js rename to app/assets/javascripts/app/models/app/note.js diff --git a/app/assets/javascripts/app/frontend/models/app/tag.js b/app/assets/javascripts/app/models/app/tag.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/app/tag.js rename to app/assets/javascripts/app/models/app/tag.js diff --git a/app/assets/javascripts/app/frontend/models/app/theme.js b/app/assets/javascripts/app/models/app/theme.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/app/theme.js rename to app/assets/javascripts/app/models/app/theme.js diff --git a/app/assets/javascripts/app/frontend/models/local/encryptedStorage.js b/app/assets/javascripts/app/models/local/encryptedStorage.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/local/encryptedStorage.js rename to app/assets/javascripts/app/models/local/encryptedStorage.js diff --git a/app/assets/javascripts/app/frontend/models/local/itemParams.js b/app/assets/javascripts/app/models/local/itemParams.js similarity index 100% rename from app/assets/javascripts/app/frontend/models/local/itemParams.js rename to app/assets/javascripts/app/models/local/itemParams.js diff --git a/app/assets/javascripts/app/frontend/routes.js b/app/assets/javascripts/app/routes.js similarity index 90% rename from app/assets/javascripts/app/frontend/routes.js rename to app/assets/javascripts/app/routes.js index 5409347db..fe6dac476 100644 --- a/app/assets/javascripts/app/frontend/routes.js +++ b/app/assets/javascripts/app/routes.js @@ -1,4 +1,4 @@ -angular.module('app.frontend') +angular.module('app') .config(function ($locationProvider) { if(!isDesktopApplication()) { @@ -11,5 +11,4 @@ angular.module('app.frontend') } else { $locationProvider.html5Mode(false); } - }); diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index f2926b139..9a5b9d1cc 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -241,4 +241,4 @@ class ActionsManager { } -angular.module('app.frontend').service('actionsManager', ActionsManager); +angular.module('app').service('actionsManager', ActionsManager); diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 46cff40fc..af3f39d9c 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -1,4 +1,4 @@ -angular.module('app.frontend') +angular.module('app') .provider('authManager', function () { function domainName() { diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index a18670b10..1329c3cb2 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -749,4 +749,4 @@ class ComponentManager { } -angular.module('app.frontend').service('componentManager', ComponentManager); +angular.module('app').service('componentManager', ComponentManager); diff --git a/app/assets/javascripts/app/services/dbManager.js b/app/assets/javascripts/app/services/dbManager.js index 4c5d0341e..521fe2a1e 100644 --- a/app/assets/javascripts/app/services/dbManager.js +++ b/app/assets/javascripts/app/services/dbManager.js @@ -158,4 +158,4 @@ class DBManager { } } -angular.module('app.frontend').service('dbManager', DBManager); +angular.module('app').service('dbManager', DBManager); diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index ebe16d93c..c63bc4e4c 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -108,4 +108,4 @@ class DesktopManager { } -angular.module('app.frontend').service('desktopManager', DesktopManager); +angular.module('app').service('desktopManager', DesktopManager); diff --git a/app/assets/javascripts/app/services/httpManager.js b/app/assets/javascripts/app/services/httpManager.js index a5d157d57..71692f478 100644 --- a/app/assets/javascripts/app/services/httpManager.js +++ b/app/assets/javascripts/app/services/httpManager.js @@ -77,4 +77,4 @@ class HttpManager { } -angular.module('app.frontend').service('httpManager', HttpManager); +angular.module('app').service('httpManager', HttpManager); diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js index bb139860c..59eb9a2e1 100644 --- a/app/assets/javascripts/app/services/migrationManager.js +++ b/app/assets/javascripts/app/services/migrationManager.js @@ -58,4 +58,4 @@ class MigrationManager { } -angular.module('app.frontend').service('migrationManager', MigrationManager); +angular.module('app').service('migrationManager', MigrationManager); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 879ffede9..7c96f338d 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -437,4 +437,4 @@ class ModelManager { } -angular.module('app.frontend').service('modelManager', ModelManager); +angular.module('app').service('modelManager', ModelManager); diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js index 4ff78561c..7c61b2bac 100644 --- a/app/assets/javascripts/app/services/packageManager.js +++ b/app/assets/javascripts/app/services/packageManager.js @@ -38,4 +38,4 @@ class PackageManager { } -angular.module('app.frontend').service('packageManager', PackageManager); +angular.module('app').service('packageManager', PackageManager); diff --git a/app/assets/javascripts/app/services/passcodeManager.js b/app/assets/javascripts/app/services/passcodeManager.js index e4862fbe6..28202fdcc 100644 --- a/app/assets/javascripts/app/services/passcodeManager.js +++ b/app/assets/javascripts/app/services/passcodeManager.js @@ -1,4 +1,4 @@ -angular.module('app.frontend') +angular.module('app') .provider('passcodeManager', function () { this.$get = function($rootScope, $timeout, modelManager, dbManager, authManager, storageManager) { diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index dc8c6583f..489527239 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -147,4 +147,4 @@ class SingletonManager { } -angular.module('app.frontend').service('singletonManager', SingletonManager); +angular.module('app').service('singletonManager', SingletonManager); diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index cc8a3fa9b..0d2f02164 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -228,4 +228,4 @@ StorageManager.FixedEncrypted = "FixedEncrypted"; // encrypted memoryStorage + l StorageManager.Ephemeral = "Ephemeral"; // memoryStorage StorageManager.Fixed = "Fixed"; // localStorage -angular.module('app.frontend').service('storageManager', StorageManager); +angular.module('app').service('storageManager', StorageManager); diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index a47cbbb27..67a93ee77 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -448,4 +448,4 @@ class SyncManager { } } -angular.module('app.frontend').service('syncManager', SyncManager); +angular.module('app').service('syncManager', SyncManager); diff --git a/app/assets/javascripts/app/services/sysExtManager.js b/app/assets/javascripts/app/services/sysExtManager.js index 876506ace..b08738055 100644 --- a/app/assets/javascripts/app/services/sysExtManager.js +++ b/app/assets/javascripts/app/services/sysExtManager.js @@ -80,4 +80,4 @@ class SysExtManager { } -angular.module('app.frontend').service('sysExtManager', SysExtManager); +angular.module('app').service('sysExtManager', SysExtManager); diff --git a/app/assets/javascripts/app/services/themeManager.js b/app/assets/javascripts/app/services/themeManager.js index 2a475ac62..5f7d745c8 100644 --- a/app/assets/javascripts/app/services/themeManager.js +++ b/app/assets/javascripts/app/services/themeManager.js @@ -103,4 +103,4 @@ class ThemeManager { } -angular.module('app.frontend').service('themeManager', ThemeManager); +angular.module('app').service('themeManager', ThemeManager); diff --git a/app/assets/javascripts/frontend.js b/app/assets/javascripts/frontend.js deleted file mode 100644 index 7e4f399dd..000000000 --- a/app/assets/javascripts/frontend.js +++ /dev/null @@ -1,5 +0,0 @@ -//= require app/app.frontend.js -//= require_tree ./app/services - -//= require app/app.frontend.js -//= require_tree ./app/frontend diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js new file mode 100644 index 000000000..d7ba63e74 --- /dev/null +++ b/app/assets/javascripts/main.js @@ -0,0 +1 @@ +//= require_tree ./app diff --git a/app/assets/stylesheets/_ionicons.scss b/app/assets/stylesheets/app/_ionicons.scss similarity index 100% rename from app/assets/stylesheets/_ionicons.scss rename to app/assets/stylesheets/app/_ionicons.scss diff --git a/app/assets/stylesheets/frontend.css.scss b/app/assets/stylesheets/main.css.scss similarity index 90% rename from app/assets/stylesheets/frontend.css.scss rename to app/assets/stylesheets/main.css.scss index f33649227..9e28dc002 100644 --- a/app/assets/stylesheets/frontend.css.scss +++ b/app/assets/stylesheets/main.css.scss @@ -8,5 +8,4 @@ @import "app/modals"; @import "app/lock-screen"; @import "app/stylekit-sub"; - -@import "ionicons"; +@import "app/ionicons"; diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/account-menu.html.haml rename to app/assets/templates/directives/account-menu.html.haml diff --git a/app/assets/templates/frontend/directives/actions-menu.html.haml b/app/assets/templates/directives/actions-menu.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/actions-menu.html.haml rename to app/assets/templates/directives/actions-menu.html.haml diff --git a/app/assets/templates/frontend/directives/component-modal.html.haml b/app/assets/templates/directives/component-modal.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/component-modal.html.haml rename to app/assets/templates/directives/component-modal.html.haml diff --git a/app/assets/templates/frontend/directives/component-view.html.haml b/app/assets/templates/directives/component-view.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/component-view.html.haml rename to app/assets/templates/directives/component-view.html.haml diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/directives/editor-menu.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/editor-menu.html.haml rename to app/assets/templates/directives/editor-menu.html.haml diff --git a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml b/app/assets/templates/directives/global-extensions-menu.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/global-extensions-menu.html.haml rename to app/assets/templates/directives/global-extensions-menu.html.haml diff --git a/app/assets/templates/frontend/directives/menu-row.html.haml b/app/assets/templates/directives/menu-row.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/menu-row.html.haml rename to app/assets/templates/directives/menu-row.html.haml diff --git a/app/assets/templates/frontend/directives/panel-resizer.html.haml b/app/assets/templates/directives/panel-resizer.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/panel-resizer.html.haml rename to app/assets/templates/directives/panel-resizer.html.haml diff --git a/app/assets/templates/frontend/directives/permissions-modal.html.haml b/app/assets/templates/directives/permissions-modal.html.haml similarity index 100% rename from app/assets/templates/frontend/directives/permissions-modal.html.haml rename to app/assets/templates/directives/permissions-modal.html.haml diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/editor.html.haml similarity index 100% rename from app/assets/templates/frontend/editor.html.haml rename to app/assets/templates/editor.html.haml diff --git a/app/assets/templates/frontend/footer.html.haml b/app/assets/templates/footer.html.haml similarity index 100% rename from app/assets/templates/frontend/footer.html.haml rename to app/assets/templates/footer.html.haml diff --git a/app/assets/templates/frontend/home.html.haml b/app/assets/templates/home.html.haml similarity index 100% rename from app/assets/templates/frontend/home.html.haml rename to app/assets/templates/home.html.haml diff --git a/app/assets/templates/frontend/lock-screen.html.haml b/app/assets/templates/lock-screen.html.haml similarity index 100% rename from app/assets/templates/frontend/lock-screen.html.haml rename to app/assets/templates/lock-screen.html.haml diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/notes.html.haml similarity index 100% rename from app/assets/templates/frontend/notes.html.haml rename to app/assets/templates/notes.html.haml diff --git a/app/assets/templates/frontend/tags.html.haml b/app/assets/templates/tags.html.haml similarity index 100% rename from app/assets/templates/frontend/tags.html.haml rename to app/assets/templates/tags.html.haml diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 76def86a7..e3aa18d09 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,7 +7,7 @@ class ApplicationController < ActionController::Base layout :false - def frontend + def app end diff --git a/app/views/application/frontend.html.erb b/app/views/application/app.html.erb similarity index 95% rename from app/views/application/frontend.html.erb rename to app/views/application/app.html.erb index 192df1a16..d05065dc1 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/app.html.erb @@ -1,5 +1,5 @@ - + @@ -43,7 +43,7 @@ -
    +
    diff --git a/config/cable.yml b/config/cable.yml deleted file mode 100644 index 73739d638..000000000 --- a/config/cable.yml +++ /dev/null @@ -1,10 +0,0 @@ -development: - adapter: redis - url: redis://localhost:6379 - -test: - adapter: async - -production: - adapter: redis - url: redis://localhost:6379/1 diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 4a7e35fa7..cb2eb3fc6 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -12,8 +12,7 @@ Rails.application.config.assets.precompile << /\.(?:svg|eot|woff|ttf)\z/ # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -# Rails.application.config.assets.precompile += %w( search.js ) -Rails.application.config.assets.precompile += %w( stylekit.css app.css compiled.min.js compiled.js ) +Rails.application.config.assets.precompile += %w( app.css compiled.min.js compiled.js ) # zip library Rails.application.config.assets.precompile += %w( zip/zip.js zip/z-worker.js zip/inflate.js zip/deflate.js ) diff --git a/config/routes.rb b/config/routes.rb index 3bd46ba1b..1697cb1b1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,3 @@ Rails.application.routes.draw do - root 'application#frontend' + root 'application#app' end diff --git a/test/javascripts/controllers/HomeCtrl_spec.js b/test/javascripts/controllers/HomeCtrl_spec.js index 0ae1e1650..d93065c78 100644 --- a/test/javascripts/controllers/HomeCtrl_spec.js +++ b/test/javascripts/controllers/HomeCtrl_spec.js @@ -1,6 +1,6 @@ -describe("app.frontend", function() { +describe("app", function() { - beforeEach(module('app.frontend')); + beforeEach(module('app')); describe('Home Controller', function() { diff --git a/test/javascripts/filters/DateFilter_spec.js b/test/javascripts/filters/DateFilter_spec.js index b79c1dce9..cdfa55a55 100644 --- a/test/javascripts/filters/DateFilter_spec.js +++ b/test/javascripts/filters/DateFilter_spec.js @@ -1,5 +1,5 @@ describe("date filter", function() { - beforeEach(module('app.frontend')); + beforeEach(module('app')); var $filter; beforeEach(inject(function(_$filter_){ From 18b4af8c2bb12ec33aa85836a065912fa9b69935 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 19 Jan 2018 12:47:30 -0600 Subject: [PATCH 35/99] 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 1f13b404e..c9dbc461c 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 1f13b404e3f657f4b5da1b8b9bb79712deafb460 +Subproject commit c9dbc461ca68a29efd4580ad50ae4adf0c3d68b8 From af8cdf5a41a73f27a7b7cd4c531207e39a212d37 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 19 Jan 2018 16:59:08 -0600 Subject: [PATCH 36/99] Ability to change passcode, passcode with ephemeral sessions --- .../app/directives/views/accountMenu.js | 15 +++++---- .../javascripts/app/services/authManager.js | 3 +- .../app/services/passcodeManager.js | 9 +++-- .../app/services/storageManager.js | 7 ++-- .../directives/account-menu.html.haml | 33 ++++++++++--------- app/assets/templates/footer.html.haml | 2 +- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 2efe01744..99714b8f6 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -525,11 +525,6 @@ class AccountMenu { Passcode Lock */ - $scope.passcodeOptionAvailable = function() { - // If you're signed in with an ephemeral session, passcode lock is unavailable - return authManager.offline() || !authManager.isEphemeralSession(); - } - $scope.hasPasscode = function() { return passcodeManager.hasPasscode(); } @@ -545,7 +540,9 @@ class AccountMenu { return; } - passcodeManager.setPasscode(passcode, () => { + let fn = $scope.formData.changingPasscode ? passcodeManager.changePasscode : passcodeManager.setPasscode; + + fn(passcode, () => { $timeout(function(){ $scope.formData.showPasscodeForm = false; var offline = authManager.offline(); @@ -559,6 +556,12 @@ class AccountMenu { }) } + $scope.changePasscodePressed = function() { + $scope.formData.changingPasscode = true; + $scope.addPasscodeClicked(); + $scope.formData.changingPasscode = false; + } + $scope.removePasscodePressed = function() { var signedIn = !authManager.offline(); var message = "Are you sure you want to remove your local passcode?"; diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index af3f39d9c..db2c6a665 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -43,11 +43,10 @@ angular.module('app') this.ephemeral = ephemeral; if(ephemeral) { storageManager.setModelStorageMode(StorageManager.Ephemeral); - storageManager.setItemsMode(storageManager.hasPasscode() ? StorageManager.FixedEncrypted : 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); } } diff --git a/app/assets/javascripts/app/services/passcodeManager.js b/app/assets/javascripts/app/services/passcodeManager.js index 28202fdcc..a3a65ca86 100644 --- a/app/assets/javascripts/app/services/passcodeManager.js +++ b/app/assets/javascripts/app/services/passcodeManager.js @@ -41,7 +41,7 @@ angular.module('app') }.bind(this)); } - this.setPasscode = function(passcode, callback) { + this.setPasscode = (passcode, callback) => { var cost = Neeto.crypto.defaultPasswordGenerationCost(); var salt = Neeto.crypto.generateRandomKey(512); var defaultParams = {pw_cost: cost, pw_salt: salt, version: "002"}; @@ -60,6 +60,10 @@ angular.module('app') }.bind(this)); } + this.changePasscode = (newPasscode, callback) => { + this.setPasscode(newPasscode, callback); + } + this.clearPasscode = function() { storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral storageManager.removeItem("offlineParams", StorageManager.Fixed); @@ -70,7 +74,8 @@ angular.module('app') this.encryptLocalStorage = function(keys) { storageManager.setKeys(keys); // Switch to Ephemeral storage, wiping Fixed storage - storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted); + // Last argument is `force`, which we set to true because in the case of changing passcode + storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true); } this.decryptLocalStorage = function(keys) { diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index 0d2f02164..a81abcb0d 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -62,9 +62,9 @@ class StorageManager { return this._memoryStorage; } - setItemsMode(mode) { + setItemsMode(mode, force) { var newStorage = this.getVault(mode); - if(newStorage !== this.storage) { + if(newStorage !== this.storage || mode !== this.itemsStorageMode || force) { // transfer storages var length = this.storage.length; for(var i = 0; i < length; i++) { @@ -101,6 +101,8 @@ class StorageManager { var storage = this.getVault(vaultKey); storage.setItem(key, value); + console.log(this.itemsStorageMode); + if(vaultKey === StorageManager.FixedEncrypted || (!vaultKey && this.itemsStorageMode === StorageManager.FixedEncrypted)) { this.writeEncryptedStorageToDisk(); } @@ -161,7 +163,6 @@ class StorageManager { for(var key of Object.keys(encryptedStorage.storage)) { this.setItem(key, encryptedStorage.storage[key]); } - } hasPasscode() { diff --git a/app/assets/templates/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml index de8ac2623..9a7d3c1ac 100644 --- a/app/assets/templates/directives/account-menu.html.haml +++ b/app/assets/templates/directives/account-menu.html.haml @@ -127,29 +127,30 @@ .panel-section %h3.title.panel-row Passcode Lock - %div{"ng-if" => "!hasPasscode() && passcodeOptionAvailable()"} + %div{"ng-if" => "!hasPasscode()"} .panel-row{"ng-if" => "!formData.showPasscodeForm"} .button.info{"ng-click" => "addPasscodeClicked(); $event.stopPropagation();"} .label Add Passcode %p Add an app passcode to lock the app and encrypt on-device key storage. - %form{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} - %input.form-control{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "sn-autofocus" => "true", "should-focus" => "true"} - %input.form-control{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} - .button-group.stretch.panel-row.form-submit - %button.button.info{"type" => "submit"} - .label Set Passcode - %a.panel-row{"ng-click" => "formData.showPasscodeForm = false"} Cancel + %form{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"} + %input.form-control{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "sn-autofocus" => "true", "should-focus" => "true"} + %input.form-control{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"} + .button-group.stretch.panel-row.form-submit + %button.button.info{"type" => "submit"} + .label Set Passcode + %a.panel-row{"ng-click" => "formData.showPasscodeForm = false"} Cancel - .panel-row{"ng-if" => "hasPasscode()"} - %p - Passcode lock is enabled. - %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. - %a.block.danger{"ng-click" => "removePasscodePressed()"} Remove Passcode - - .panel-row{"ng-if" => "!passcodeOptionAvailable()"} - %p Passcode lock is only available to permanent sessions. (You chose not to stay signed in.) + %div{"ng-if" => "hasPasscode() && !formData.showPasscodeForm"} + .panel-row + %p + Passcode lock is enabled. + %span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit. + .panel-row.justify-left + .horizontal-group + %a.info{"ng-click" => "changePasscodePressed()"} Change Passcode + %a.danger{"ng-click" => "removePasscodePressed()"} Remove Passcode diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index 8712d3b83..bfc79b75b 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -2,7 +2,7 @@ #footer-bar.app-bar.no-edges .left .item{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} - .column + .column{"ng-click" => "ctrl.accountMenuPressed()"} .circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'default')"} .column{"ng-click" => "ctrl.accountMenuPressed()"} .label.title{"ng-class" => "{red: ctrl.error}"} Account From 02f77b67b09977a9b7a2be24365679d0ef38850c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 20 Jan 2018 10:39:00 -0600 Subject: [PATCH 37/99] Singleton manager updates to handle retrieved and saved items independently --- .../app/directives/views/accountMenu.js | 2 - .../app/services/componentManager.js | 1 - .../app/services/singletonManager.js | 65 +++++++++++++------ .../app/services/storageManager.js | 2 - .../directives/account-menu.html.haml | 2 +- 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 99714b8f6..7e65d07f7 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -90,7 +90,6 @@ class AccountMenu { } $scope.submitAuthForm = function() { - console.log("Submitting auth form"); if(!$scope.formData.email || !$scope.formData.user_password) { return; } @@ -102,7 +101,6 @@ class AccountMenu { } $scope.login = function(extraParams) { - console.log("Logging in"); $scope.formData.status = "Generating Login Keys..."; $timeout(function(){ authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, extraParams, diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 1329c3cb2..fea2c4cfa 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -79,7 +79,6 @@ class ComponentManager { ]; this.runWithPermissions(observer.component, requiredPermissions, () => { - console.log("Stream observer, sending items", relevantItems); this.sendItemsInReply(observer.component, relevantItems, observer.originalMessage); }) } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 489527239..4192bab7b 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -22,14 +22,15 @@ class SingletonManager { $rootScope.$on("sync:completed", (event, data) => { // The reason we also need to consider savedItems in consolidating singletons is in case of sync conflicts, // a new item can be created, but is never processed through "retrievedItems" since it is only created locally then saved. + + // HOWEVER, by considering savedItems, we are now ruining everything, especially during sign in. A singleton can be created + // offline, and upon sign in, will sync all items to the server, and by combining retrievedItems & savedItems, and only choosing + // the latest, you are now resolving to the most recent one, which is in the savedItems list and not retrieved items, defeating + // the whole purpose of this thing. + + // Updated solution: resolveSingletons will now evaluate both of these arrays separately. this.resolveSingletons(data.retrievedItems, data.savedItems); }) - - // Testing code to make sure only 1 exists - // setTimeout(function () { - // var userPrefs = modelManager.itemsForContentType("SN|UserPreferences"); - // console.assert(userPrefs.length == 1); - // }, 1000); } registerSingleton(predicate, resolveCallback, createBlock) { @@ -48,10 +49,16 @@ class SingletonManager { resolveSingletons(retrievedItems, savedItems, initialLoad) { retrievedItems = retrievedItems || []; savedItems = savedItems || []; + for(let singletonHandler of this.singletonHandlers) { var predicate = singletonHandler.predicate; - var singletonItems = this.filterItemsWithPredicate(_.uniq(retrievedItems.concat(savedItems)), predicate); - if(singletonItems.length > 0) { + let retrievedSingletonItems = this.filterItemsWithPredicate(retrievedItems, predicate); + + // 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; + if(retrievedSingletonItems.length > 0 || savedSingletonItemsCount > 0) { /* Check local inventory and make sure only 1 similar item exists. If more than 1, delete oldest Note that this local inventory will also contain whatever is in retrievedItems. @@ -61,22 +68,39 @@ class SingletonManager { var allExtantItemsMatchingPredicate = this.filterItemsWithPredicate(this.modelManager.allItems, predicate); /* - If there are more than 1 matches, delete everything not in `singletonItems`, - then delete all but the latest in `singletonItems` + If there are more than 1 matches, delete everything not in `retrievedSingletonItems`, + then delete all but the latest in `retrievedSingletonItems` */ if(allExtantItemsMatchingPredicate.length >= 2) { + + // Items that will be deleted var toDelete = []; - for(let extantItem of allExtantItemsMatchingPredicate) { - if(!singletonItems.includes(extantItem)) { - // Delete it - toDelete.push(extantItem); + // The item that will be chosen to be kept + var winningItem, sorted; + + if(retrievedSingletonItems.length > 0) { + for(let extantItem of allExtantItemsMatchingPredicate) { + if(!retrievedSingletonItems.includes(extantItem)) { + // Delete it + toDelete.push(extantItem); + } } + + // Sort incoming singleton items by most recently updated first, then delete all the rest + sorted = retrievedSingletonItems.sort((a, b) => { + return a.updated_at < b.updated_at; + }) + + } else { + // We're in here because of savedItems + // This can be the case if retrievedSingletonItems/retrievedItems length is 0, but savedSingletonItemsCount is non zero. + // In this case, we want to sort by date and delete all but the most recent one + sorted = allExtantItemsMatchingPredicate.sort((a, b) => { + return a.updated_at < b.updated_at; + }); } - // Sort incoming singleton items by most recently updated first, then delete all the rest - var sorted = singletonItems.sort((a, b) => { - return a.updated_at < b.updated_at; - }) + winningItem = sorted[0]; // Delete everything but the first one toDelete = toDelete.concat(sorted.slice(1, sorted.length)); @@ -88,9 +112,8 @@ class SingletonManager { this.$rootScope.sync(); // Send remaining item to callback - var singleton = sorted[0]; - singletonHandler.singleton = singleton; - singletonHandler.resolutionCallback(singleton); + singletonHandler.singleton = winningItem; + singletonHandler.resolutionCallback(winningItem); } else if(allExtantItemsMatchingPredicate.length == 1) { if(!singletonHandler.singleton) { diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js index a81abcb0d..e1aa26c4f 100644 --- a/app/assets/javascripts/app/services/storageManager.js +++ b/app/assets/javascripts/app/services/storageManager.js @@ -101,8 +101,6 @@ class StorageManager { var storage = this.getVault(vaultKey); storage.setItem(key, value); - console.log(this.itemsStorageMode); - if(vaultKey === StorageManager.FixedEncrypted || (!vaultKey && this.itemsStorageMode === StorageManager.FixedEncrypted)) { this.writeEncryptedStorageToDisk(); } diff --git a/app/assets/templates/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml index 9a7d3c1ac..5b13d4017 100644 --- a/app/assets/templates/directives/account-menu.html.haml +++ b/app/assets/templates/directives/account-menu.html.haml @@ -22,7 +22,7 @@ {{formData.showLogin ? "Sign In" : "Register (free)"}} %form.panel-form{"ng-submit" => "submitAuthForm()"} - %input{:placeholder => 'Email', :autofocus => 'autofocus', :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email'} + %input{:placeholder => 'Email', "sn-autofocus" => 'true', "should-focus" => "true", :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email'} %input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'} %input{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf'} From 56c247534b2886e720aad3e9e881367ba2601e2b Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 20 Jan 2018 11:41:55 -0600 Subject: [PATCH 38/99] Component expiration handling, rename SyncAdapter -> ServerExtension --- .../app/directives/views/componentView.js | 20 ++++++++- .../directives/views/globalExtensionsMenu.js | 6 +-- .../{syncAdapter.js => serverExtension.js} | 2 +- .../javascripts/app/models/app/component.js | 5 +++ .../javascripts/app/services/modelManager.js | 2 +- .../directives/component-view.html.haml | 43 ++++++++++++++++--- package-lock.json | 6 +-- package.json | 2 +- 8 files changed, 70 insertions(+), 16 deletions(-) rename app/assets/javascripts/app/models/api/{syncAdapter.js => serverExtension.js} (96%) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 2ebc2b24a..2db0ccc28 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -50,11 +50,27 @@ class ComponentView { if(component) { componentManager.activateComponent(component); - component.runningLocally = $scope.getUrl - console.log("Loading", $scope.component.name, $scope.getUrl()); + console.log("Loading", $scope.component.name, $scope.getUrl(), component.valid_until); + + $scope.reloadStatus(); } } + $scope.reloadStatus = function() { + $scope.reloading = true; + let previouslyValid = $scope.componentValid; + $scope.componentValid = !$scope.component.valid_until || ($scope.component.valid_until && $scope.component.valid_until > new Date()); + if($scope.componentValid !== previouslyValid) { + if($scope.componentValid) { + componentManager.activateComponent($scope.component); + } + } + + $timeout(() => { + $scope.reloading = false; + }, 500) + } + $scope.getUrl = function() { var url = componentManager.urlForComponent($scope.component); $scope.component.runningLocally = url !== ($scope.component.url || $scope.component.hosted_url); diff --git a/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js index 649c4c2fb..4ae490711 100644 --- a/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js +++ b/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js @@ -153,7 +153,7 @@ class GlobalExtensionsMenu { var type = getParameterByName("type", link); if(type == "sf") { - $scope.handleSyncAdapterLink(link, completion); + $scope.handleServerExtensionLink(link, completion); } else if(type == "editor") { $scope.handleEditorLink(link, completion); } else if(link.indexOf(".css") != -1 || type == "theme") { @@ -174,10 +174,10 @@ class GlobalExtensionsMenu { packageManager.installPackage(link, completion); } - $scope.handleSyncAdapterLink = function(link, completion) { + $scope.handleServerExtensionLink = function(link, completion) { var params = parametersFromURL(link); params["url"] = link; - var ext = new SyncAdapter({content: params}); + var ext = new ServerExtension({content: params}); ext.setDirty(true); modelManager.addItem(ext); diff --git a/app/assets/javascripts/app/models/api/syncAdapter.js b/app/assets/javascripts/app/models/api/serverExtension.js similarity index 96% rename from app/assets/javascripts/app/models/api/syncAdapter.js rename to app/assets/javascripts/app/models/api/serverExtension.js index 12f87d20f..8149c5a95 100644 --- a/app/assets/javascripts/app/models/api/syncAdapter.js +++ b/app/assets/javascripts/app/models/api/serverExtension.js @@ -1,4 +1,4 @@ -class SyncAdapter extends Item { +class ServerExtension extends Item { 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 844e88fcb..8d9703159 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -25,6 +25,10 @@ class Component extends Item { this.hosted_url = content.hosted_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; @@ -51,6 +55,7 @@ class Component extends Item { 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, diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 7c96f338d..6be22d990 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -217,7 +217,7 @@ class ModelManager { } else if(json_obj.content_type == "SN|Component") { item = new Component(json_obj); } else if(json_obj.content_type == "SF|Extension") { - item = new SyncAdapter(json_obj); + item = new ServerExtension(json_obj); } else if(json_obj.content_type == "SF|MFA") { item = new Mfa(json_obj); } diff --git a/app/assets/templates/directives/component-view.html.haml b/app/assets/templates/directives/component-view.html.haml index 2f7c2bcc8..23406543f 100644 --- a/app/assets/templates/directives/component-view.html.haml +++ b/app/assets/templates/directives/component-view.html.haml @@ -1,6 +1,39 @@ -%iframe{"ng-if" => "component", -"ng-attr-id" => "component-{{component.uuid}}", -"ng-src" => "{{getUrl() | trusted}}", "frameBorder" => "0", -"sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms", -"data-component-id" => "{{component.uuid}}"} +.sn-component{"ng-if" => "!componentValid"} + .panel.static + .content + %h2.title Unable to load Standard Notes Extended + %p Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}. + %p + Please visit + %a{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} dashboard.standardnotes.org + to renew your subscription, then open the "Extensions" menu via the bottom menu of the app to refresh your account data. + Afterwards, press the button below to attempt to reload this component. + .panel-row + .button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"} + .label Reload + .spinner.info.small{"ng-if" => "reloading"} + + .panel-row + .panel-row + .panel-column + %p Otherwise, please follow the steps below to disable any external editors, so you can edit your note using the plain text editor instead. + + %p + %ol + %li Click the "Editor" menu item above (under the note title). + %li Select "Plain Editor". + %li Repeat this for every note you'd like to access. You can also delete the editor completely to disable it for all notes. To do so, click "Extensions" in the lower left corner of the app, then, for every editor, click "Uninstall". + + %p + Need help? Please email us at + %a{"href" => "mailto:hello@standardnotes.org", "target" => "_blank"} hello@standardnotes.org + or check out the + %a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help + page. + +%iframe{"ng-if" => "component && componentValid", + "ng-attr-id" => "component-{{component.uuid}}", + "ng-src" => "{{getUrl() | trusted}}", "frameBorder" => "0", + "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms", + "data-component-id" => "{{component.uuid}}"} Loading diff --git a/package-lock.json b/package-lock.json index fe2b9754a..bbfd700e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5781,9 +5781,9 @@ "dev": true }, "sn-stylekit": { - "version": "1.0.1191", - "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1191.tgz", - "integrity": "sha512-Xez1FNz822zw7NsG9krfxiSXklYZQNwQRSaTbxnYXmOjqCvcsGiWAgEyUzSo5p3g2nVIC/8LzKIrQw4/b3ORXw==", + "version": "1.0.1192", + "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1192.tgz", + "integrity": "sha512-qvR1rPI1FeG+Us2+P+0XZ0kndd5D9gg93Xla8aqopNbJ3xB2vh3OXvl/3XRSL4xKNrG+4Ub7u5Xg/J5NPPNRoA==", "dev": true }, "snake-case": { diff --git a/package.json b/package.json index 03cc10c4f..f2ced494e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "karma-cli": "^1.0.1", "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", - "sn-stylekit": "^1.0.1191" + "sn-stylekit": "^1.0.1192" }, "license": "GPL-3.0" } From 1dbc394c4d4b7c4468720bffeeecfae682edac25 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 20 Jan 2018 11:42:38 -0600 Subject: [PATCH 39/99] Submodule --- 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 c9dbc461c..44462c75b 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit c9dbc461ca68a29efd4580ad50ae4adf0c3d68b8 +Subproject commit 44462c75bce27785c435a182162db2bdbb34d524 From 85cdba7a9e6c06da673df0c3fba0e4676962ae87 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 20 Jan 2018 18:31:29 -0600 Subject: [PATCH 40/99] Fixes alternateUUID callback, component stack association --- .../javascripts/app/controllers/editor.js | 28 +++++----- .../app/directives/views/componentView.js | 5 +- .../javascripts/app/models/app/component.js | 12 ++--- .../app/services/componentManager.js | 7 +++ .../javascripts/app/services/modelManager.js | 2 + .../app/services/singletonManager.js | 2 + .../javascripts/app/services/syncManager.js | 19 ++++--- app/assets/stylesheets/app/_modals.scss | 1 + .../directives/component-view.html.haml | 53 ++++++++++--------- app/assets/templates/editor.html.haml | 2 +- app/assets/templates/footer.html.haml | 8 +-- 11 files changed, 75 insertions(+), 64 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 3913fc8e5..77bf2244c 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -92,7 +92,7 @@ angular.module('app') this.editorForNote = function(note) { let editors = componentManager.componentsForArea("editor-editor"); for(var editor of editors) { - if(editor.isActiveForItem(note)) { + if(editor.isExplicitlyEnabledForItem(note)) { return editor; } } @@ -427,7 +427,7 @@ angular.module('app') } } else { // Editor - if(component.active && this.note && (component.isActiveForItem(this.note) || component.isDefaultEditor())) { + if(component.active && this.note && (component.isExplicitlyEnabledForItem(this.note) || component.isDefaultEditor())) { this.selectedEditor = component; } else { this.selectedEditor = null; @@ -490,24 +490,23 @@ angular.module('app') var stack = componentManager.componentsForArea("editor-stack"); for(var component of stack) { - var activeForItem = component.isActiveForItem(this.note); - if(activeForItem) { - if(!component.active) { - componentManager.activateComponent(component); - } - } else { - if(component.active) { - componentManager.deactivateComponent(component); + if(component.active) { + var disabledForItem = component.isExplicitlyDisabledForItem(this.note); + if(disabledForItem) { + component.hidden = true; + } else { + component.hidden = false; } } } } this.toggleStackComponentForCurrentItem = function(component) { - if(component.isActiveForItem(this.note)) { - componentManager.deactivateComponent(component); + if(component.active) { + component.hidden = true; this.disassociateComponentWithCurrentNote(component); } else { + // Inactive componentManager.activateComponent(component); componentManager.contextItemDidChangeInArea("editor-stack"); this.associateComponentWithCurrentNote(component); @@ -517,8 +516,7 @@ angular.module('app') this.disassociateComponentWithCurrentNote = function(component) { component.associatedItemIds = component.associatedItemIds.filter((id) => {return id !== this.note.uuid}); - // Only disassociative components should modify the disassociatedItemIds - if(!component.isAssociative() && !component.disassociatedItemIds.includes(this.note.uuid)) { + if(!component.disassociatedItemIds.includes(this.note.uuid)) { component.disassociatedItemIds.push(this.note.uuid); } @@ -528,7 +526,7 @@ angular.module('app') this.associateComponentWithCurrentNote = function(component) { component.disassociatedItemIds = component.disassociatedItemIds.filter((id) => {return id !== this.note.uuid}); - if(component.isAssociative() && !component.associatedItemIds.includes(this.note.uuid)) { + if(!component.associatedItemIds.includes(this.note.uuid)) { component.associatedItemIds.push(this.note.uuid); } diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 2db0ccc28..6ab682db7 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -4,7 +4,8 @@ class ComponentView { this.restrict = "E"; this.templateUrl = "directives/component-view.html"; this.scope = { - component: "=" + component: "=", + manualDealloc: "=" }; this.componentManager = componentManager; @@ -79,7 +80,7 @@ class ComponentView { $scope.$on("$destroy", function() { componentManager.deregisterHandler($scope.identifier); - if($scope.component) { + if($scope.component && !$scope.manualDealloc) { componentManager.deactivateComponent($scope.component); } }); diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index 8d9703159..84f2efacf 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -113,11 +113,11 @@ class Component extends Item { this.associatedItemIds.push(item.uuid); } - isActiveForItem(item) { - if(this.isAssociative()) { - return this.associatedItemIds.indexOf(item.uuid) !== -1; - } else { - return this.disassociatedItemIds.indexOf(item.uuid) === -1; - } + 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/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index fea2c4cfa..b12c6afe1 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -593,6 +593,13 @@ class ComponentManager { } sendMessageToComponent(component, message) { + if(component.hidden && message.action !== "component-registered") { + if(this.loggingEnabled) { + console.log("Component disabled for current item, not sending any messages.", component.name); + } + return; + } + if(this.loggingEnabled) { console.log("Web|sendMessageToComponent", component, message); } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 6be22d990..f97a3fa49 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -117,6 +117,8 @@ class ModelManager { mapResponseItemsToLocalModelsOmittingFields(items, omitFields, source) { var models = [], processedObjects = [], modelsToNotifyObserversOf = []; + // console.log("mapResponseItemsToLocalModelsOmittingFields"); + // 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) { diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 4192bab7b..eec532375 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -109,6 +109,8 @@ class SingletonManager { this.modelManager.setItemToBeDeleted(d); } + console.log("Syncing from SM"); + this.$rootScope.sync(); // Send remaining item to callback diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 67a93ee77..4e44945bf 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -191,6 +191,8 @@ class SyncManager { sync(callback, options = {}) { + console.log("Sync"); + var allDirtyItems = this.modelManager.getDirtyItems(); if(this.syncStatus.syncOpInProgress) { @@ -380,14 +382,13 @@ class SyncManager { console.log("Handle unsaved", unsaved); var i = 0; - var handleNext = function() { + var handleNext = () => { if(i >= unsaved.length) { // Handled all items this.sync(null, {additionalFields: ["created_at", "updated_at"]}); return; } - var handled = false; var mapping = unsaved[i]; var itemResponse = mapping.item; EncryptionHelper.decryptMultipleItems([itemResponse], this.authManager.keys()); @@ -403,8 +404,10 @@ class SyncManager { 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 - handled = true; - this.modelManager.alternateUUIDForItem(item, handleNext, true); + this.modelManager.alternateUUIDForItem(item, () => { + i++; + handleNext(); + }, true); } else if(error.tag === "sync_conflict") { @@ -419,15 +422,11 @@ class SyncManager { dup.conflict_of = item.uuid; dup.setDirty(true); } - } - ++i; - - if(!handled) { + i++; handleNext(); } - - }.bind(this); + } handleNext(); } diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index 1bf064a03..c63ddaa9a 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -78,6 +78,7 @@ .component-view { flex-grow: 1; display: flex; + overflow: auto; iframe { flex: 1; width: 100%; diff --git a/app/assets/templates/directives/component-view.html.haml b/app/assets/templates/directives/component-view.html.haml index 23406543f..c5a000baa 100644 --- a/app/assets/templates/directives/component-view.html.haml +++ b/app/assets/templates/directives/component-view.html.haml @@ -1,35 +1,36 @@ .sn-component{"ng-if" => "!componentValid"} .panel.static .content - %h2.title Unable to load Standard Notes Extended - %p Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}. - %p - Please visit - %a{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} dashboard.standardnotes.org - to renew your subscription, then open the "Extensions" menu via the bottom menu of the app to refresh your account data. - Afterwards, press the button below to attempt to reload this component. - .panel-row - .button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"} - .label Reload - .spinner.info.small{"ng-if" => "reloading"} + .panel-section + %h2.title Unable to load Standard Notes Extended + %p Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}. + %p + Please visit + %a{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} dashboard.standardnotes.org + to renew your subscription, then open the "Extensions" menu via the bottom menu of the app to refresh your account data. + Afterwards, press the button below to attempt to reload this component. + .panel-row + .button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"} + .label Reload + .spinner.info.small{"ng-if" => "reloading"} - .panel-row - .panel-row - .panel-column - %p Otherwise, please follow the steps below to disable any external editors, so you can edit your note using the plain text editor instead. + .panel-row + .panel-row + .panel-column + %p Otherwise, please follow the steps below to disable any external editors, so you can edit your note using the plain text editor instead. - %p - %ol - %li Click the "Editor" menu item above (under the note title). - %li Select "Plain Editor". - %li Repeat this for every note you'd like to access. You can also delete the editor completely to disable it for all notes. To do so, click "Extensions" in the lower left corner of the app, then, for every editor, click "Uninstall". + %p + %ol + %li Click the "Editor" menu item above (under the note title). + %li Select "Plain Editor". + %li Repeat this for every note you'd like to access. You can also delete the editor completely to disable it for all notes. To do so, click "Extensions" in the lower left corner of the app, then, for every editor, click "Uninstall". - %p - Need help? Please email us at - %a{"href" => "mailto:hello@standardnotes.org", "target" => "_blank"} hello@standardnotes.org - or check out the - %a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help - page. + %p + Need help? Please email us at + %a{"href" => "mailto:hello@standardnotes.org", "target" => "_blank"} hello@standardnotes.org + or check out the + %a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help + page. %iframe{"ng-if" => "component && componentValid", "ng-attr-id" => "component-{{component.uuid}}", diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 9e782e135..515293b42 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -51,4 +51,4 @@ %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. #editor-pane-component-stack - %component-view.component-view.component-stack-item{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "component" => "component"} + %component-view.component-view.component-stack-item{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index bfc79b75b..33d066153 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -1,12 +1,12 @@ .sn-component #footer-bar.app-bar.no-edges .left - .item{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} - .column{"ng-click" => "ctrl.accountMenuPressed()"} + .item{"ng-click" => "ctrl.accountMenuPressed()", "click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} + .column .circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'default')"} - .column{"ng-click" => "ctrl.accountMenuPressed()"} + .column .label.title{"ng-class" => "{red: ctrl.error}"} Account - %account-menu{"ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess", "close-function" => "ctrl.closeAccountMenu"} + %account-menu{"ng-click" => "$event.stopPropagation()", "ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess", "close-function" => "ctrl.closeAccountMenu"} .item{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} .column{"ng-click" => "ctrl.toggleExtensions()"} From b152df2af407ecf5cc8dc48b701b9ca20908e635 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 20 Jan 2018 18:55:33 -0600 Subject: [PATCH 41/99] Reselect first item on sort change --- app/assets/javascripts/app/controllers/editor.js | 7 +++++-- app/assets/javascripts/app/controllers/notes.js | 6 ++++++ .../javascripts/app/directives/views/menuRow.js | 3 ++- app/assets/javascripts/app/services/authManager.js | 2 +- .../javascripts/app/services/singletonManager.js | 2 -- app/assets/javascripts/app/services/syncManager.js | 2 -- app/assets/templates/directives/menu-row.html.haml | 2 +- app/assets/templates/editor.html.haml | 2 +- app/assets/templates/notes.html.haml | 13 ++++++------- 9 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 77bf2244c..30690f118 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -254,8 +254,11 @@ angular.module('app') } } - this.selectedMenuItem = function($event) { - this.showMenu = false; + this.selectedMenuItem = function($event, hide) { + if(hide) { + this.showMenu = false; + } + $event.stopPropagation(); } this.deleteNote = function() { diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 4b7376f8c..1fa4d95a0 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -40,7 +40,13 @@ angular.module('app') }); this.loadPreferences = function() { + let prevSortValue = this.sortBy; this.sortBy = authManager.getUserPrefValue("sortBy", "created_at"); + if(prevSortValue && prevSortValue != this.sortBy) { + $timeout(() => { + this.selectFirstNote(); + }) + } this.sortDescending = this.sortBy != "title"; this.showArchived = authManager.getUserPrefValue("showArchived", false); diff --git a/app/assets/javascripts/app/directives/views/menuRow.js b/app/assets/javascripts/app/directives/views/menuRow.js index 20fc1653c..fe7a988f0 100644 --- a/app/assets/javascripts/app/directives/views/menuRow.js +++ b/app/assets/javascripts/app/directives/views/menuRow.js @@ -13,7 +13,8 @@ class MenuRow { buttonClass: "=", buttonAction: "&", spinnerClass: "=", - subRows: "=" + subRows: "=", + faded: "=" }; } diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index db2c6a665..a20fad35b 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -316,7 +316,7 @@ angular.module('app') } this.getUserPrefValue = function(key, defaultValue) { - if(!this.userPreferences) { return; } + if(!this.userPreferences) { return defaultValue; } var value = this.userPreferences.getAppDataItem(key); return (value !== undefined && value != null) ? value : defaultValue; } diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index eec532375..4192bab7b 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -109,8 +109,6 @@ class SingletonManager { this.modelManager.setItemToBeDeleted(d); } - console.log("Syncing from SM"); - this.$rootScope.sync(); // Send remaining item to callback diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 4e44945bf..1002b9b5b 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -191,8 +191,6 @@ class SyncManager { sync(callback, options = {}) { - console.log("Sync"); - var allDirtyItems = this.modelManager.getDirtyItems(); if(this.syncStatus.syncOpInProgress) { diff --git a/app/assets/templates/directives/menu-row.html.haml b/app/assets/templates/directives/menu-row.html.haml index f710800cc..7bbff5528 100644 --- a/app/assets/templates/directives/menu-row.html.haml +++ b/app/assets/templates/directives/menu-row.html.haml @@ -3,7 +3,7 @@ .left .column{"ng-if" => "circle"} .circle.small{"ng-class" => "circle"} - .column + .column{"ng-class" => "{'faded' : faded}"} .label {{title}} .sublabel{"ng-if" => "subtitle"} diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 515293b42..62c90f0a2 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -29,7 +29,7 @@ .section{"ng-if" => "!ctrl.selectedEditor"} .header %h4.title Display - %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('monospaceFont')"} + %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event, true); ctrl.toggleKey('monospaceFont')"} .item{"ng-click" => "ctrl.onEditorMenuClick()", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} .label Editor diff --git a/app/assets/templates/notes.html.haml b/app/assets/templates/notes.html.haml index e9a62fdfc..67ac906ac 100644 --- a/app/assets/templates/notes.html.haml +++ b/app/assets/templates/notes.html.haml @@ -32,17 +32,16 @@ .header %h4.title Display - %menu-row{"title" => "'Archived Notes'", "circle" => "ctrl.showArchived ? 'success' : 'danger'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('showArchived')"} - %menu-row{"title" => "'Pinned Notes'", "circle" => "ctrl.hidePinned ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hidePinned')"} - - %menu-row{"title" => "'Note Preview'", "circle" => "ctrl.hideNotePreview ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideNotePreview')"} - %menu-row{"title" => "'Date'", "circle" => "ctrl.hideDate ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideDate')"} - %menu-row{"title" => "'Tags'", "circle" => "ctrl.hideTags ? 'danger' : 'success'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideTags')"} + %menu-row{"title" => "'Archived Notes'", "circle" => "ctrl.showArchived ? 'success' : 'danger'", "faded" => "!ctrl.showArchived", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('showArchived')"} + %menu-row{"title" => "'Pinned Notes'", "circle" => "ctrl.hidePinned ? 'danger' : 'success'", "faded" => "ctrl.hidePinned", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hidePinned')"} + %menu-row{"title" => "'Note Preview'", "circle" => "ctrl.hideNotePreview ? 'danger' : 'success'", "faded" => "ctrl.hideNotePreview", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideNotePreview')"} + %menu-row{"title" => "'Date'", "circle" => "ctrl.hideDate ? 'danger' : 'success'","faded" => "ctrl.hideDate", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideDate')"} + %menu-row{"title" => "'Tags'", "circle" => "ctrl.hideTags ? 'danger' : 'success'","faded" => "ctrl.hideTags", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleKey('hideTags')"} .scrollable .infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"} - .note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy| limitTo:ctrl.notesToDisplay)) track by note.uuid", + .note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy | limitTo:ctrl.notesToDisplay)) track by note.uuid", "ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"} %strong.red.medium{"ng-if" => "note.conflict_of"} Conflicted copy %strong.red.medium{"ng-if" => "note.errorDecrypting"} Error decrypting From 3bce8ec82d6b412f3fc683c730615dac403978a5 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 20 Jan 2018 19:05:26 -0600 Subject: [PATCH 42/99] Recognize new lines, closes #170 --- app/assets/templates/editor.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 62c90f0a2..c7ea3d828 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -43,7 +43,7 @@ %panel-resizer.left{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "property" => "'left'", "hoverable" => "true"} %component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor"} %textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", - "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} + "ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} {{ctrl.onSystemEditorLoad()}} %panel-resizer{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "hoverable" => "true"} From c3bb99307533f7daa84a22e35470a4581bb4801a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 20 Jan 2018 19:36:45 -0600 Subject: [PATCH 43/99] Spellcheck toggle option --- .../javascripts/app/controllers/editor.js | 17 ++++++++++------- .../javascripts/app/services/sysExtManager.js | 1 - app/assets/templates/editor.html.haml | 4 +++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 30690f118..a4aa8d740 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -25,6 +25,7 @@ angular.module('app') }) .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager) { + this.spellcheck = true; this.componentManager = componentManager; this.componentStack = []; @@ -247,13 +248,6 @@ angular.module('app') this.editingName = false; } - this.toggleFullScreen = function() { - this.fullscreen = !this.fullscreen; - if(this.fullscreen) { - this.focusEditor(0); - } - } - this.selectedMenuItem = function($event, hide) { if(hide) { this.showMenu = false; @@ -365,6 +359,7 @@ angular.module('app') this.loadPreferences = function() { this.monospaceFont = authManager.getUserPrefValue("monospaceFont", "monospace"); + this.spellcheck = authManager.getUserPrefValue("spellcheck", true); if(!document.getElementById("editor-content")) { // Elements have not yet loaded due to ng-if around wrapper @@ -406,6 +401,14 @@ angular.module('app') this[key] = !this[key]; authManager.setUserPrefValue(key, this[key], true); this.reloadFont(); + + if(key == "spellcheck") { + // Allows textarea to reload + this.noteReady = false; + $timeout(() => { + this.noteReady = true; + }, 0) + } } diff --git a/app/assets/javascripts/app/services/sysExtManager.js b/app/assets/javascripts/app/services/sysExtManager.js index b08738055..1ff562653 100644 --- a/app/assets/javascripts/app/services/sysExtManager.js +++ b/app/assets/javascripts/app/services/sysExtManager.js @@ -15,7 +15,6 @@ class SysExtManager { this.singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: extensionsIdentifier}}, (resolvedSingleton) => { // Resolved Singleton - console.log("Resolved extensions-manager", resolvedSingleton); var needsSync = false; if(isDesktopApplication()) { if(!resolvedSingleton.local_url) { diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index c7ea3d828..d946b4e07 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -30,6 +30,7 @@ .header %h4.title Display %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event, true); ctrl.toggleKey('monospaceFont')"} + %menu-row{"title" => "'Spellcheck'", "circle" => "ctrl.spellcheck ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event, true); ctrl.toggleKey('spellcheck')"} .item{"ng-click" => "ctrl.onEditorMenuClick()", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} .label Editor @@ -43,7 +44,8 @@ %panel-resizer.left{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "property" => "'left'", "hoverable" => "true"} %component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor"} %textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", - "ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} + "ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()", + "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}"} {{ctrl.onSystemEditorLoad()}} %panel-resizer{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "hoverable" => "true"} From 330649d2612295040aaed8a0be2929412f683b06 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 21 Jan 2018 10:25:31 -0600 Subject: [PATCH 44/99] Component offline only check --- .../app/directives/views/componentView.js | 10 ++++++-- .../app/services/componentManager.js | 5 ++-- app/assets/stylesheets/app/_modals.scss | 5 ++++ .../directives/component-view.html.haml | 25 ++++++++++++++++--- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 6ab682db7..28765d59d 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -58,15 +58,21 @@ class ComponentView { } $scope.reloadStatus = function() { + let component = $scope.component; $scope.reloading = true; let previouslyValid = $scope.componentValid; - $scope.componentValid = !$scope.component.valid_until || ($scope.component.valid_until && $scope.component.valid_until > new Date()); + + $scope.offlineRestricted = component.offlineOnly && !isDesktopApplication(); + + $scope.componentValid = !$scope.offlineRestricted && (!component.valid_until || (component.valid_until && component.valid_until > new Date())); + if($scope.componentValid !== previouslyValid) { if($scope.componentValid) { - componentManager.activateComponent($scope.component); + componentManager.activateComponent(component); } } + $timeout(() => { $scope.reloading = false; }, 500) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index b12c6afe1..71979c82e 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -197,7 +197,7 @@ class ComponentManager { urlForComponent(component) { if(component.offlineOnly || (isDesktopApplication() && component.local_url)) { - return component.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); + return component.local_url && component.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); } else { return component.url || component.hosted_url; } @@ -402,6 +402,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.handleMessage(component, saveMessage); }); }); @@ -599,7 +600,7 @@ class ComponentManager { } return; } - + if(this.loggingEnabled) { console.log("Web|sendMessageToComponent", component, message); } diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index c63ddaa9a..c1d472e04 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -79,6 +79,11 @@ flex-grow: 1; display: flex; overflow: auto; + + .sn-component { + min-width: 100%; + } + iframe { flex: 1; width: 100%; diff --git a/app/assets/templates/directives/component-view.html.haml b/app/assets/templates/directives/component-view.html.haml index c5a000baa..8489d07c2 100644 --- a/app/assets/templates/directives/component-view.html.haml +++ b/app/assets/templates/directives/component-view.html.haml @@ -1,7 +1,7 @@ -.sn-component{"ng-if" => "!componentValid"} +.sn-component{"ng-if" => "!componentValid && !offlineRestricted"} .panel.static .content - .panel-section + .panel-section.stretch %h2.title Unable to load Standard Notes Extended %p Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}. %p @@ -32,7 +32,26 @@ %a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help page. -%iframe{"ng-if" => "component && componentValid", +.sn-component{"ng-if" => "offlineRestricted"} + .panel.static + .content + .panel-section.stretch + %h2.title You have restricted this extension to be used offline only. + %p Offline extensions are not available in the Web app. + .panel-row + .panel-column + %p You can either: + %p + %ul + %li Enable the Hosted option for this extension by opening the 'Extensions' menu and toggling 'Use hosted when local is unavailable' under this extension's options. Then press Reload below. + %li Use the Desktop application. + .panel-row + .button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"} + .label Reload + .spinner.info.small{"ng-if" => "reloading"} + + +%iframe{"ng-if" => "component && componentValid && !offlineRestricted", "ng-attr-id" => "component-{{component.uuid}}", "ng-src" => "{{getUrl() | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms", From 6aa94dfca2e191f01cc28a140cad9c69f68bc54f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 21 Jan 2018 10:27:33 -0600 Subject: [PATCH 45/99] 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 44462c75b..6276445f8 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 44462c75bce27785c435a182162db2bdbb34d524 +Subproject commit 6276445f895d9b40a64bda7353330df934bda054 From 12ada8eb00b67c98852d7d0056df65ebbb4522e4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 21 Jan 2018 10:37:00 -0600 Subject: [PATCH 46/99] Cleanup themeManager --- .../javascripts/app/controllers/home.js | 1 - .../app/services/componentManager.js | 4 - .../javascripts/app/services/themeManager.js | 73 +------------------ 3 files changed, 1 insertion(+), 77 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index ab2f2e20d..427afa393 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -57,7 +57,6 @@ angular.module('app') authManager.loadInitialData(); syncManager.loadLocalItems(function(items) { $scope.allTag.didLoad = true; - themeManager.activateInitialTheme(); $scope.$apply(); $rootScope.$broadcast("initial-data-loaded"); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 71979c82e..627ad5119 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -20,10 +20,6 @@ class ComponentManager { this.handlers = []; - // $rootScope.$on("theme-changed", function(){ - // this.postThemeToAllComponents(); - // }.bind(this)) - window.addEventListener("message", function(event){ if(this.loggingEnabled) { console.log("Web app: received message", event); diff --git a/app/assets/javascripts/app/services/themeManager.js b/app/assets/javascripts/app/services/themeManager.js index 5f7d745c8..74dad17ff 100644 --- a/app/assets/javascripts/app/services/themeManager.js +++ b/app/assets/javascripts/app/services/themeManager.js @@ -1,10 +1,6 @@ class ThemeManager { - constructor(modelManager, syncManager, $rootScope, storageManager, componentManager) { - this.syncManager = syncManager; - this.modelManager = modelManager; - this.$rootScope = $rootScope; - this.storageManager = storageManager; + constructor(componentManager) { this.componentManager = componentManager; componentManager.registerHandler({identifier: "themeManager", areas: ["themes"], activationHandler: (component) => { @@ -16,48 +12,8 @@ class ThemeManager { }}); } - get themes() { - return this.modelManager.itemsForContentType("SN|Theme"); - } - - - /* - activeTheme: computed property that returns saved theme - currentTheme: stored variable that allows other classes to watch changes - */ - - // get activeTheme() { - // var activeThemeId = this.storageManager.getItem("activeTheme"); - // if(!activeThemeId) { - // return null; - // } - // - // var theme = _.find(this.themes, {uuid: activeThemeId}); - // return theme; - // } - - activateInitialTheme() { - // var theme = this.activeTheme; - // if(theme) { - // this.activateTheme(theme); - // } - } - - // submitTheme(url) { - // var name = this.displayNameForThemeFile(this.fileNameFromPath(url)); - // var theme = this.modelManager.createItem({content_type: "SN|Theme", url: url, name: name}); - // this.modelManager.addItem(theme); - // theme.setDirty(true); - // this.syncManager.sync(); - // } - activateTheme(theme) { - if(this.activeTheme && this.activeTheme !== theme) { - this.deactivateTheme(this.activeTheme); - } - var url = this.componentManager.urlForComponent(theme); - var link = document.createElement("link"); link.href = url; link.type = "text/css"; @@ -65,42 +21,15 @@ class ThemeManager { link.media = "screen,print"; link.id = theme.uuid; document.getElementsByTagName("head")[0].appendChild(link); - this.storageManager.setItem("activeTheme", theme.uuid); - - this.currentTheme = theme; - this.$rootScope.$broadcast("theme-changed"); } deactivateTheme(theme) { - this.storageManager.removeItem("activeTheme"); var element = document.getElementById(theme.uuid); if(element) { element.disabled = true; element.parentNode.removeChild(element); } - - this.currentTheme = null; - this.$rootScope.$broadcast("theme-changed"); } - - fileNameFromPath(filePath) { - return filePath.replace(/^.*[\\\/]/, ''); - } - - capitalizeString(string) { - return string.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); }); - } - - displayNameForThemeFile(fileName) { - let fromParam = getParameterByName("name", fileName); - if(fromParam) { - return fromParam; - } - let name = fileName.split(".")[0]; - let cleaned = name.split("-").join(" "); - return this.capitalizeString(cleaned); - } - } angular.module('app').service('themeManager', ThemeManager); From 614d7ba20a8d97d0f46a8c06209fa6868bdca820 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 21 Jan 2018 13:14:15 -0600 Subject: [PATCH 47/99] Permissions formatting, dialog queue --- app/assets/javascripts/app/app.js | 8 +- .../app/directives/views/permissionsModal.js | 88 ++++++++++++------- .../app/services/componentManager.js | 54 ++++++++---- .../directives/permissions-modal.html.haml | 3 +- 4 files changed, 101 insertions(+), 52 deletions(-) diff --git a/app/assets/javascripts/app/app.js b/app/assets/javascripts/app/app.js index d75836ddc..af4ac2d2e 100644 --- a/app/assets/javascripts/app/app.js +++ b/app/assets/javascripts/app/app.js @@ -41,6 +41,12 @@ function isMacApplication() { return window && window.process && window.process.type && window.process.platform == "darwin"; } -Array.prototype.containsSubset = function(array) { +/* Use with numbers and strings, not objects */ +Array.prototype.containsPrimitiveSubset = function(array) { return !array.some(val => this.indexOf(val) === -1); } + +/* Use with numbers and strings, not objects */ +Array.prototype.containsObjectSubset = function(array) { + return !array.some(val => !_.find(this, val)); +} diff --git a/app/assets/javascripts/app/directives/views/permissionsModal.js b/app/assets/javascripts/app/directives/views/permissionsModal.js index fb7609d55..0e1764910 100644 --- a/app/assets/javascripts/app/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/directives/views/permissionsModal.js @@ -30,46 +30,68 @@ class PermissionsModal { controller($scope, modelManager) { - $scope.formattedPermissions = $scope.permissions.map(function(permission){ - if(permission.name === "stream-items") { - var types = permission.content_types.map(function(type){ - var desc = modelManager.humanReadableDisplayForContentType(type); - if(desc) { - return desc + "s"; - } else { - return "items of type " + type; - } - }) - var typesString = ""; - var separator = ", "; + $scope.permissionsString = function() { + var finalString = ""; + let permissionsCount = $scope.permissions.length; - for(var i = 0;i < types.length;i++) { - var type = types[i]; - if(i == 0) { - // first element - typesString = typesString + type; - } else if(i == types.length - 1) { - // last element - if(types.length > 2) { - typesString += separator + "and " + type; - } else if(types.length == 2) { - typesString = typesString + " and " + type; + let addSeparator = (index, length) => { + if(index > 0) { + if(index == length - 1) { + if(length == 2) { + return " and "; + } else { + return ", and " } } else { - typesString += separator + type; + return ", "; } } - return typesString; - } else if(permission.name === "stream-context-item") { - var mapping = { - "editor-stack" : "working note", - "note-tags" : "working note", - "editor-editor": "working note" - } - return mapping[$scope.component.area]; + return ""; } - }) + + $scope.permissions.forEach((permission, index) => { + + if(permission.name === "stream-items") { + var types = permission.content_types.map(function(type){ + var desc = modelManager.humanReadableDisplayForContentType(type); + if(desc) { + return desc + "s"; + } else { + return "items of type " + type; + } + }) + var typesString = ""; + + for(var i = 0;i < types.length;i++) { + var type = types[i]; + typesString += addSeparator(i, types.length + permissionsCount - index - 1); + typesString += type; + } + + finalString += addSeparator(index, permissionsCount); + + finalString += typesString; + + if(types.length >= 2 && index < permissionsCount - 1) { + // If you have a list of types, and still an additional root-level permission coming up, add a comma + finalString += ", "; + } + } else if(permission.name === "stream-context-item") { + var mapping = { + "editor-stack" : "working note", + "note-tags" : "working note", + "editor-editor": "working note" + } + + finalString += addSeparator(index, permissionsCount, true); + + finalString += mapping[$scope.component.area]; + } + }) + + return finalString + "."; + } } } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 627ad5119..f28c8c4f0 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -235,6 +235,7 @@ class ComponentManager { get-context-client-data install-local-component toggle-activate-component + request-permissions */ if(message.action === "stream-items") { @@ -252,6 +253,8 @@ class ComponentManager { } else if(message.action === "toggle-activate-component") { let componentToToggle = this.modelManager.findItem(message.data.uuid); this.handleToggleComponentMessage(component, componentToToggle, message); + } else if(message.action === "request-permissions") { + this.handleRequestPermissionsMessage(component, message); } // Notify observers @@ -450,6 +453,12 @@ class ComponentManager { }); } + handleRequestPermissionsMessage(component, message) { + this.runWithPermissions(component, message.data.permissions, () => { + this.replyToMessage(component, message, {approved: true}); + }); + } + handleSetComponentDataMessage(component, message) { // A component setting its own data does not require special permissions this.runWithPermissions(component, [], () => { @@ -459,7 +468,6 @@ class ComponentManager { }); } - handleToggleComponentMessage(sourceComponent, targetComponent, message) { if(targetComponent.area == "modal") { this.openModalComponent(targetComponent); @@ -504,7 +512,7 @@ class ComponentManager { matching = acquiredPermissions.find((candidate) => { return Array.isArray(candidate.content_types) && Array.isArray(required.content_types) - && candidate.content_types.containsSubset(required.content_types); + && candidate.content_types.containsPrimitiveSubset(required.content_types); }); if(!matching) { @@ -514,8 +522,6 @@ class ComponentManager { } } - // var acquiredMatchesRequested = angular.toJson(component.permissions.sort()) === angular.toJson(requestedPermissions.sort()); - if(!acquiredMatchesRequired) { this.promptForPermissions(component, requiredPermissions, function(approved){ if(approved) { @@ -528,9 +534,6 @@ class ComponentManager { } promptForPermissions(component, permissions, callback) { - // since these calls are asyncronous, multiple dialogs may be requested at the same time. We only want to present one and trigger all callbacks based on one modal result - var existingDialog = _.find(this.permissionDialogs, {component: component}); - var scope = this.$rootScope.$new(true); scope.component = component; scope.permissions = permissions; @@ -547,28 +550,47 @@ class ComponentManager { this.syncManager.sync(); } - for(var existing of this.permissionDialogs) { - if(existing.component === component && existing.actionBlock) { - existing.actionBlock(approved); + this.permissionDialogs = this.permissionDialogs.filter((pendingDialog) => { + // Remove self + if(pendingDialog == scope) { + return false; } - } - - this.permissionDialogs = this.permissionDialogs.filter(function(dialog){ - return dialog.component !== component; + if(approved && pendingDialog.component == component) { + // remove pending dialogs that are encapsulated by already approved permissions, and run its function + if(pendingDialog.permissions == permissions || permissions.containsObjectSubset(pendingDialog.permissions)) { + pendingDialog.actionBlock && pendingDialog.actionBlock(approved); + return false; + } + } + return true; }) + if(this.permissionDialogs.length > 0) { + this.presentDialog(this.permissionDialogs[0]); + } + }.bind(this); + // since these calls are asyncronous, multiple dialogs may be requested at the same time. We only want to present one and trigger all callbacks based on one modal result + var existingDialog = _.find(this.permissionDialogs, {component: component}); + this.permissionDialogs.push(scope); if(!existingDialog) { - var el = this.$compile( "" )(scope); - angular.element(document.body).append(el); + this.presentDialog(scope); } else { console.log("Existing dialog, not presenting."); } } + presentDialog(dialog) { + var permissions = dialog.permissions; + var component = dialog.component; + var callback = dialog.callback; + var el = this.$compile( "" )(dialog); + angular.element(document.body).append(el); + } + openModalComponent(component) { var scope = this.$rootScope.$new(true); scope.component = component; diff --git a/app/assets/templates/directives/permissions-modal.html.haml b/app/assets/templates/directives/permissions-modal.html.haml index d88280a3a..63a5dc38f 100644 --- a/app/assets/templates/directives/permissions-modal.html.haml +++ b/app/assets/templates/directives/permissions-modal.html.haml @@ -12,8 +12,7 @@ %h3 %strong {{component.name}} would like to interact with your - %span{"ng-repeat" => "permission in formattedPermissions"} - {{permission}}. + {{permissionsString()}} .panel-row %p From 05af90290b77a6b4a0a511eca5beb943f1239ca0 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 22 Jan 2018 13:19:48 -0600 Subject: [PATCH 48/99] action and component manager updates --- .../app/services/actionsManager.js | 2 +- .../app/services/componentManager.js | 51 +++++++++---------- public/extensions/extensions-manager | 2 +- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index 9a5b9d1cc..bf2002df8 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -150,7 +150,7 @@ class ActionsManager { this.httpManager.getAbsolute(action.url, {}, function(response){ action.error = false; EncryptionHelper.decryptItem(response.item, this.authManager.keys()); - var item = this.modelManager.createItem(response.item); + var item = this.modelManager.createItem(response.item, true /* Dont notify observers */); customCallback({item: item}); }.bind(this), function(response){ diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index f28c8c4f0..bd7151f26 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -181,6 +181,30 @@ class ComponentManager { this.replyToMessage(component, originalMessage, response); } + replyToMessage(component, originalMessage, replyData) { + var reply = { + action: "reply", + original: originalMessage, + data: replyData + } + + this.sendMessageToComponent(component, reply); + } + + sendMessageToComponent(component, message) { + if(component.hidden && message.action !== "component-registered") { + if(this.loggingEnabled) { + console.log("Component disabled for current item, not sending any messages.", component.name); + } + return; + } + + if(this.loggingEnabled) { + console.log("Web|sendMessageToComponent", component, message); + } + component.window.postMessage(message, "*"); + } + get components() { return this.modelManager.allItemsMatchingTypes(["SN|Component", "SN|Theme"]); } @@ -231,8 +255,6 @@ class ComponentManager { create-item delete-items set-component-data - save-context-client-data - get-context-client-data install-local-component toggle-activate-component request-permissions @@ -601,30 +623,6 @@ class ComponentManager { angular.element(document.body).append(el); } - replyToMessage(component, originalMessage, replyData) { - var reply = { - action: "reply", - original: originalMessage, - data: replyData - } - - this.sendMessageToComponent(component, reply); - } - - sendMessageToComponent(component, message) { - if(component.hidden && message.action !== "component-registered") { - if(this.loggingEnabled) { - console.log("Component disabled for current item, not sending any messages.", component.name); - } - return; - } - - if(this.loggingEnabled) { - console.log("Web|sendMessageToComponent", component, message); - } - component.window.postMessage(message, "*"); - } - installComponent(url) { var name = getParameterByName("name", url); var area = getParameterByName("area", url); @@ -691,6 +689,7 @@ class ComponentManager { sessionKey: component.sessionKey, componentData: component.componentData, data: { + uuid: component.uuid, environment: isDesktopApplication() ? "desktop" : "web" } }); diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager index 6276445f8..87b260800 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 6276445f895d9b40a64bda7353330df934bda054 +Subproject commit 87b26080032d4df427015e49396895f506501bc9 From ed3f0aa620b0ad422c169c20dcdf072ce720bb66 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 23 Jan 2018 10:05:23 -0600 Subject: [PATCH 49/99] SyncManager exclude savedItems from retrievedItems, syncing source string --- .../javascripts/app/controllers/editor.js | 2 +- .../javascripts/app/controllers/footer.js | 4 +-- .../javascripts/app/controllers/home.js | 20 +++++++-------- .../app/directives/views/accountMenu.js | 8 +++--- .../app/directives/views/editorMenu.js | 4 +-- .../javascripts/app/services/authManager.js | 4 +-- .../app/services/componentManager.js | 18 ++++++------- .../app/services/desktopManager.js | 4 +-- .../app/services/migrationManager.js | 2 +- .../javascripts/app/services/modelManager.js | 2 ++ .../app/services/packageManager.js | 2 +- .../app/services/singletonManager.js | 3 ++- .../javascripts/app/services/syncManager.js | 25 ++++++++++++++++--- .../javascripts/app/services/sysExtManager.js | 4 +-- 14 files changed, 62 insertions(+), 40 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index a4aa8d740..514342e83 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -143,7 +143,7 @@ angular.module('app') } // Lots of dirtying can happen above, so we'll sync - syncManager.sync(); + syncManager.sync("editorMenuOnSelect"); }.bind(this) this.hasAvailableExtensions = function() { diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 945aaa9c9..b37b94ff1 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -69,7 +69,7 @@ angular.module('app') this.refreshData = function() { this.isRefreshing = true; - syncManager.sync(function(response){ + syncManager.sync((response) => { $timeout(function(){ this.isRefreshing = false; }.bind(this), 200) @@ -78,7 +78,7 @@ angular.module('app') } else { this.syncUpdated(); } - }.bind(this)); + }, null, "refreshData"); } this.syncUpdated = function() { diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 427afa393..e775c9c4c 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -9,8 +9,8 @@ angular.module('app') } /* Used to avoid circular dependencies where syncManager cannot be imported but rootScope can */ - $rootScope.sync = function() { - syncManager.sync(); + $rootScope.sync = function(source) { + syncManager.sync("$rootScope.sync - " + source); } $rootScope.lockApplication = function() { @@ -49,7 +49,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(); + syncManager.sync("openDatabase"); }) } @@ -61,10 +61,10 @@ angular.module('app') $rootScope.$broadcast("initial-data-loaded"); - syncManager.sync(null); + syncManager.sync("initiateSync"); // refresh every 30s setInterval(function () { - syncManager.sync(null); + syncManager.sync("timer"); }, 30000); }); } @@ -115,7 +115,7 @@ angular.module('app') } note.setDirty(true); - syncManager.sync(); + syncManager.sync("updateTagsForNote"); } /* @@ -145,7 +145,7 @@ angular.module('app') return; } tag.setDirty(true); - syncManager.sync(callback); + syncManager.sync(callback, null, "tagsSave"); $rootScope.$broadcast("tag-changed"); modelManager.resortTag(tag); } @@ -161,7 +161,7 @@ angular.module('app') syncManager.sync(function(){ // force scope tags to update on sub directives $scope.safeApply(); - }); + }, null, "removeTag"); } } @@ -199,7 +199,7 @@ angular.module('app') callback(true); } } - }) + }, null, "saveNote") } $scope.safeApply = function(fn) { @@ -240,7 +240,7 @@ angular.module('app') } else { $scope.notifyDelete(); } - }); + }, null, "deleteNote"); } diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 7e65d07f7..aa6a68476 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -80,7 +80,7 @@ class AccountMenu { }, 1000) }); }) - }) + }, null, "submitPasswordChange") } $scope.submitMfaForm = function() { @@ -162,7 +162,7 @@ class AccountMenu { var block = function() { $timeout(function(){ $scope.onSuccessfulAuth()(); - syncManager.sync(); + syncManager.sync("onAuthSuccess"); }) } @@ -280,7 +280,7 @@ class AccountMenu { syncManager.sync((response) => { callback(response, errorCount); - }, {additionalFields: ["created_at", "updated_at"]}); + }, {additionalFields: ["created_at", "updated_at"]}, "importJSONData"); }.bind(this) if(data.auth_params) { @@ -443,7 +443,7 @@ class AccountMenu { alert("Your items have been successfully re-encrypted and synced. You must sign out of all other signed in applications (mobile, desktop, web) and sign in again, or else you may corrupt your data.") $scope.newPasswordData = {}; }, 1000) - }); + }, null, "reencryptPressed"); } diff --git a/app/assets/javascripts/app/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js index 51d301fb0..78b9bb66f 100644 --- a/app/assets/javascripts/app/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/directives/views/editorMenu.js @@ -51,7 +51,7 @@ class EditorMenu { } component.setAppDataItem("defaultEditor", true); component.setDirty(true); - syncManager.sync(); + syncManager.sync("makeEditorDefault"); $scope.defaultEditor = component; } @@ -59,7 +59,7 @@ class EditorMenu { $scope.removeEditorDefault = function(component) { component.setAppDataItem("defaultEditor", false); component.setDirty(true); - syncManager.sync(); + syncManager.sync("removeEditorDefault"); $scope.defaultEditor = null; } diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index a20fad35b..a72423d01 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -302,7 +302,7 @@ angular.module('app') var prefs = new Item({content_type: prefsContentType}); modelManager.addItem(prefs); prefs.setDirty(true); - $rootScope.sync(); + $rootScope.sync("authManager singletonCreate"); valueCallback(prefs); }); @@ -312,7 +312,7 @@ angular.module('app') this.syncUserPreferences = function() { this.userPreferences.setDirty(true); - $rootScope.sync(); + $rootScope.sync("syncUserPreferences"); } this.getUserPrefValue = function(key, defaultValue) { diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index bd7151f26..317b3b4a4 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -425,7 +425,7 @@ class ComponentManager { saveMessage.action = response && response.error ? "save-error" : "save-success"; this.replyToMessage(component, message, {error: response.error}) this.handleMessage(component, saveMessage); - }); + }, null, "handleSaveItemsMessage"); }); } @@ -447,7 +447,7 @@ class ComponentManager { this.modelManager.addItem(item); this.modelManager.resolveReferencesForItem(item); item.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("handleCreateItemMessage"); this.replyToMessage(component, message, {item: this.jsonForItem(item, component)}) }); } @@ -470,7 +470,7 @@ class ComponentManager { this.modelManager.setItemToBeDeleted(model); } - this.syncManager.sync(); + this.syncManager.sync("handleDeleteItemsMessage"); } }); } @@ -486,7 +486,7 @@ class ComponentManager { this.runWithPermissions(component, [], () => { component.componentData = message.data.componentData; component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("handleSetComponentDataMessage"); }); } @@ -569,7 +569,7 @@ class ComponentManager { } } component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("promptForPermissions"); } this.permissionDialogs = this.permissionDialogs.filter((pendingDialog) => { @@ -635,7 +635,7 @@ class ComponentManager { this.modelManager.addItem(component); component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("installComponent"); } activateComponent(component) { @@ -650,7 +650,7 @@ class ComponentManager { if(didChange) { component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("activateComponent"); } if(!this.activeComponents.includes(component)) { @@ -709,7 +709,7 @@ class ComponentManager { if(didChange) { component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("deactivateComponent"); } _.pull(this.activeComponents, component); @@ -729,7 +729,7 @@ class ComponentManager { deleteComponent(component) { this.modelManager.setItemToBeDeleted(component); - this.syncManager.sync(); + this.syncManager.sync("deleteComponent"); } isComponentActive(component) { diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index c63bc4e4c..3454d6e19 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -51,14 +51,14 @@ class DesktopManager { console.log("Web|Component Installation Complete", componentData); var component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("desktop_onComponentInstallationComplete"); } desktop_updateComponentComplete(componentData) { console.log("Web|Component Update Complete", componentData); var component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("desktop_updateComponentComplete"); } /* Used to resolve "sn://" */ diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js index 59eb9a2e1..9fec09d65 100644 --- a/app/assets/javascripts/app/services/migrationManager.js +++ b/app/assets/javascripts/app/services/migrationManager.js @@ -50,7 +50,7 @@ class MigrationManager { this.modelManager.setItemToBeDeleted(editor); } - this.syncManager.sync(); + this.syncManager.sync("addEditorToComponentMigrator"); } }) } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index f97a3fa49..c1982308d 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -54,6 +54,8 @@ class ModelManager { this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid); + console.log(item.uuid, "-->", newItem.uuid); + var block = () => { this.addItem(newItem); newItem.setDirty(true); diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js index 7c61b2bac..fa0aa837e 100644 --- a/app/assets/javascripts/app/services/packageManager.js +++ b/app/assets/javascripts/app/services/packageManager.js @@ -24,7 +24,7 @@ class PackageManager { this.modelManager.addItem(assembled); assembled.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("installPackage"); console.log("Created assembled", assembled); diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 4192bab7b..2e8774729 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -58,6 +58,7 @@ class SingletonManager { // 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; + if(retrievedSingletonItems.length > 0 || savedSingletonItemsCount > 0) { /* Check local inventory and make sure only 1 similar item exists. If more than 1, delete oldest @@ -109,7 +110,7 @@ class SingletonManager { this.modelManager.setItemToBeDeleted(d); } - this.$rootScope.sync(); + this.$rootScope.sync("resolveSingletons"); // Send remaining item to callback singletonHandler.singleton = winningItem; diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 1002b9b5b..bf80cd75b 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -189,7 +189,17 @@ class SyncManager { this.$interval.cancel(this.syncStatus.checker); } - sync(callback, options = {}) { + sync(callback, options = {}, source) { + + 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(); @@ -271,6 +281,15 @@ class SyncManager { 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 var retrieved = this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved); @@ -307,12 +326,12 @@ class SyncManager { if(this.cursorToken || this.syncStatus.needsMoreSync) { setTimeout(function () { - this.sync(callback, options); + 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); + this.sync(callback, options, "onSyncSuccess repeatOnCompletion"); }.bind(this), 10); // wait 10ms to allow UI to update } else { this.writeItemsToLocalStorage(this.allRetreivedItems, false, null); diff --git a/app/assets/javascripts/app/services/sysExtManager.js b/app/assets/javascripts/app/services/sysExtManager.js index 1ff562653..7ee8c76ea 100644 --- a/app/assets/javascripts/app/services/sysExtManager.js +++ b/app/assets/javascripts/app/services/sysExtManager.js @@ -30,7 +30,7 @@ class SysExtManager { if(needsSync) { resolvedSingleton.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("resolveExtensionsManager"); } }, (valueCallback) => { // Safe to create. Create and return object. @@ -71,7 +71,7 @@ class SysExtManager { this.modelManager.addItem(component); component.setDirty(true); - this.syncManager.sync(); + this.syncManager.sync("resolveExtensionsManager createNew"); valueCallback(component); }); From 575592963731e1c1ddabdfebe9f4289d68684c14 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 23 Jan 2018 11:00:54 -0600 Subject: [PATCH 50/99] Proper component modal dealloc --- .../app/directives/views/componentModal.js | 11 ++++------- .../app/directives/views/componentView.js | 16 +++++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentModal.js b/app/assets/javascripts/app/directives/views/componentModal.js index 9d73df8ad..0ee48c915 100644 --- a/app/assets/javascripts/app/directives/views/componentModal.js +++ b/app/assets/javascripts/app/directives/views/componentModal.js @@ -26,13 +26,10 @@ class ComponentModal { $scope.dismiss = function(callback) { var onDismiss = $scope.component.directiveController && $scope.component.directiveController.onDismiss(); - // Setting will null out compinent-view's component, which will handle deactivation - $scope.component = null; - $timeout(() => { - $scope.el.remove(); - onDismiss && onDismiss(); - callback && callback(); - }) + $scope.el.remove(); + $scope.$destroy(); + onDismiss && onDismiss(); + callback && callback(); } } diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 28765d59d..73f390983 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -15,25 +15,27 @@ class ComponentView { link($scope, el, attrs, ctrl) { $scope.el = el; - let identifier = "component-view-" + Math.random(); + $scope.identifier = "component-view-" + Math.random(); - this.componentManager.registerHandler({identifier: identifier, areas: ["*"], activationHandler: (component) => { + console.log("Registering handler", $scope.identifier, $scope.component.name); + + this.componentManager.registerHandler({identifier: $scope.identifier, areas: [$scope.component.area], activationHandler: (component) => { if(component.active) { - this.timeout(function(){ + this.timeout(() => { var iframe = this.componentManager.iframeForComponent(component); if(iframe) { iframe.onload = function() { this.componentManager.registerComponentWindow(component, iframe.contentWindow); }.bind(this); } - }.bind(this)); + }); } }, - actionHandler: function(component, action, data) { + actionHandler: (component, action, data) => { if(action == "set-size") { this.componentManager.handleSetSizeEvent(component, data); } - }.bind(this)}); + }}); $scope.$watch('component', function(component, prevComponent){ ctrl.componentValueChanging(component, prevComponent); @@ -72,7 +74,6 @@ class ComponentView { } } - $timeout(() => { $scope.reloading = false; }, 500) @@ -85,6 +86,7 @@ class ComponentView { } $scope.$on("$destroy", function() { + console.log("Deregistering handler", $scope.identifier, $scope.component.name); componentManager.deregisterHandler($scope.identifier); if($scope.component && !$scope.manualDealloc) { componentManager.deactivateComponent($scope.component); From e330c273a877b8342284a732b5ba74dc92c376ff Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 23 Jan 2018 11:20:09 -0600 Subject: [PATCH 51/99] Run permissions dialog action block --- app/assets/javascripts/app/services/componentManager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 317b3b4a4..42e42073c 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -575,8 +575,10 @@ class ComponentManager { this.permissionDialogs = this.permissionDialogs.filter((pendingDialog) => { // Remove self if(pendingDialog == scope) { + pendingDialog.actionBlock && pendingDialog.actionBlock(approved); return false; } + if(approved && pendingDialog.component == component) { // remove pending dialogs that are encapsulated by already approved permissions, and run its function if(pendingDialog.permissions == permissions || permissions.containsObjectSubset(pendingDialog.permissions)) { From 87b178199a4dc2a195d6bb84c1ab706d0463ca8f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 23 Jan 2018 11:57:57 -0600 Subject: [PATCH 52/99] Sync duplication ability to ignore keys --- app/assets/javascripts/app/models/api/item.js | 20 +++++++++++++++++++ .../javascripts/app/models/app/component.js | 4 ++++ .../javascripts/app/services/syncManager.js | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js index 1c3dcff1d..d2f77577d 100644 --- a/app/assets/javascripts/app/models/api/item.js +++ b/app/assets/javascripts/app/models/api/item.js @@ -185,7 +185,27 @@ class Item { return this.getAppDataItem("archived"); } + /* + 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 []; + } + isItemContentEqualWith(otherItem) { + let omit = (obj, keys) => { + for(var key of keys) { + delete obj[key]; + } + return obj; + } + var left = omit(this.structureParams(), this.keysToIgnoreWhenCheckingContentEquality()); + var right = omit(otherItem.structureParams(), otherItem.keysToIgnoreWhenCheckingContentEquality()); + + return JSON.stringify(left) === JSON.stringify(right) + } /* Dates diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index 84f2efacf..a94ff9748 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -96,6 +96,10 @@ class Component extends Item { 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, diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index bf80cd75b..dfb8dc12b 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -434,7 +434,7 @@ class SyncManager { itemResponse.uuid = null; var dup = this.modelManager.createDuplicateItem(itemResponse, item); - if(!itemResponse.deleted && JSON.stringify(item.structureParams()) !== JSON.stringify(dup.structureParams())) { + if(!itemResponse.deleted && !item.isItemContentEqualWith(dup)) { this.modelManager.addItem(dup); dup.conflict_of = item.uuid; dup.setDirty(true); From 334a6082a9fed4aad9d8d1a017371e957c6281bf Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 23 Jan 2018 23:46:55 -0600 Subject: [PATCH 53/99] Remove globalExtensionsMenu --- .../javascripts/app/controllers/footer.js | 4 - .../directives/views/globalExtensionsMenu.js | 215 ------------------ .../global-extensions-menu.html.haml | 138 ----------- app/assets/templates/footer.html.haml | 5 - 4 files changed, 362 deletions(-) delete mode 100644 app/assets/javascripts/app/directives/views/globalExtensionsMenu.js delete mode 100644 app/assets/templates/directives/global-extensions-menu.html.haml diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index b37b94ff1..450133349 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -55,10 +55,6 @@ angular.module('app') this.showAccountMenu = !this.showAccountMenu; } - this.toggleExtensions = function() { - this.showExtensionsMenu = !this.showExtensionsMenu; - } - this.hasPasscode = function() { return passcodeManager.hasPasscode(); } diff --git a/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js deleted file mode 100644 index 4ae490711..000000000 --- a/app/assets/javascripts/app/directives/views/globalExtensionsMenu.js +++ /dev/null @@ -1,215 +0,0 @@ -class GlobalExtensionsMenu { - - constructor() { - this.restrict = "E"; - this.templateUrl = "directives/global-extensions-menu.html"; - this.scope = { - }; - } - - controller($scope, actionsManager, syncManager, modelManager, themeManager, componentManager, packageManager) { - 'ngInject'; - - $scope.formData = {}; - - $scope.actionsManager = actionsManager; - $scope.themeManager = themeManager; - $scope.componentManager = componentManager; - - $scope.serverExtensions = modelManager.itemsForContentType("SF|Extension"); - - $scope.selectedAction = function(action, extension) { - actionsManager.executeAction(action, extension, null, function(response){ - if(response && response.error) { - action.error = true; - alert("There was an error performing this action. Please try again."); - } else { - action.error = false; - syncManager.sync(null); - } - }) - } - - $scope.changeExtensionEncryptionFormat = function(encrypted, extension) { - extension.encrypted = encrypted; - extension.setDirty(true); - syncManager.sync(); - } - - $scope.deleteActionExtension = function(extension) { - if(confirm("Are you sure you want to delete this extension?")) { - actionsManager.deleteExtension(extension); - } - } - - $scope.reloadExtensionsPressed = function() { - if(confirm("For your security, reloading extensions will disable any currently enabled repeat actions.")) { - actionsManager.refreshExtensionsFromServer(); - } - } - - $scope.deleteTheme = function(theme) { - if(confirm("Are you sure you want to delete this theme?")) { - themeManager.deactivateTheme(theme); - modelManager.setItemToBeDeleted(theme); - syncManager.sync(); - } - } - - $scope.renameExtension = function(extension) { - extension.tempName = extension.name; - extension.rename = true; - } - - $scope.submitExtensionRename = function(extension) { - extension.name = extension.tempName; - extension.tempName = null; - extension.setDirty(true); - extension.rename = false; - syncManager.sync(); - } - - $scope.clickedExtension = function(extension) { - if(extension.rename) { - return; - } - - if($scope.currentlyExpandedExtension && $scope.currentlyExpandedExtension !== extension) { - $scope.currentlyExpandedExtension.showDetails = false; - $scope.currentlyExpandedExtension.rename = false; - } - - extension.showDetails = !extension.showDetails; - - if(extension.showDetails) { - $scope.currentlyExpandedExtension = extension; - } - } - - // Server extensions - - $scope.deleteServerExt = function(ext) { - if(confirm("Are you sure you want to delete and disable this extension?")) { - _.remove($scope.serverExtensions, {uuid: ext.uuid}); - modelManager.setItemToBeDeleted(ext); - syncManager.sync(); - } - } - - $scope.nameForServerExtension = function(ext) { - var url = ext.url; - if(!url) { - return "Invalid Extension"; - } - if(url.includes("gdrive")) { - return "Google Drive Sync"; - } else if(url.includes("file_attacher")) { - return "File Attacher"; - } else if(url.includes("onedrive")) { - return "OneDrive Sync"; - } else if(url.includes("backup.email_archive")) { - return "Daily Email Backups"; - } else if(url.includes("dropbox")) { - return "Dropbox Sync"; - } else if(url.includes("revisions")) { - return "Revision History"; - } else { - return null; - } - } - - - // Components - - $scope.revokePermissions = function(component) { - component.permissions = []; - component.setDirty(true); - syncManager.sync(); - } - - $scope.deleteComponent = function(component) { - if(confirm("Are you sure you want to delete this component?")) { - componentManager.deleteComponent(component); - } - } - - - // Installation - - $scope.submitInstallLink = function() { - - var fullLink = $scope.formData.installLink; - if(!fullLink) { - return; - } - - var completion = function() { - $scope.formData.installLink = ""; - $scope.formData.successfullyInstalled = true; - } - - var links = fullLink.split(","); - for(var link of links) { - var type = getParameterByName("type", link); - - if(type == "sf") { - $scope.handleServerExtensionLink(link, completion); - } else if(type == "editor") { - $scope.handleEditorLink(link, completion); - } else if(link.indexOf(".css") != -1 || type == "theme") { - $scope.handleThemeLink(link, completion); - } else if(type == "component") { - $scope.handleComponentLink(link, completion); - } else if(type == "package") { - $scope.handlePackageLink(link, completion); - } - - else { - $scope.handleActionLink(link, completion); - } - } - } - - $scope.handlePackageLink = function(link, completion) { - packageManager.installPackage(link, completion); - } - - $scope.handleServerExtensionLink = function(link, completion) { - var params = parametersFromURL(link); - params["url"] = link; - var ext = new ServerExtension({content: params}); - ext.setDirty(true); - - modelManager.addItem(ext); - syncManager.sync(); - $scope.serverExtensions.push(ext); - completion(); - } - - $scope.handleThemeLink = function(link, completion) { - themeManager.submitTheme(link); - completion(); - } - - $scope.handleComponentLink = function(link, completion) { - componentManager.installComponent(link); - completion(); - } - - $scope.handleActionLink = function(link, completion) { - if(link) { - actionsManager.addExtension(link, function(response){ - if(!response) { - alert("Unable to register this extension. Make sure the link is valid and try again."); - } else { - completion(); - } - }) - } - } - - } - -} - -angular.module('app').directive('globalExtensionsMenu', () => new GlobalExtensionsMenu); diff --git a/app/assets/templates/directives/global-extensions-menu.html.haml b/app/assets/templates/directives/global-extensions-menu.html.haml deleted file mode 100644 index 04af90463..000000000 --- a/app/assets/templates/directives/global-extensions-menu.html.haml +++ /dev/null @@ -1,138 +0,0 @@ -.panel#global-ext-menu - .panel-body - .container - .float-group.h20 - %h1.tinted.pull-left Extensions - %a.block.pull-right.dashboard-link{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} Open Dashboard - %div.clear{"ng-if" => "!actionsManager.extensions.length && !themeManager.themes.length && !componentManager.components.length"} - %p Customize your experience with editors, themes, and actions. - .tinted-box.mt-10 - %h3 Available as part of the Extended subscription. - %p.mt-5 Note history - %p.mt-5 Automated backups - %p.mt-5 Editors, themes, and actions - %a{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} - %button.mt-10 - %h3 Learn More - - %div{"ng-if" => "themeManager.themes.length > 0"} - .header.container.section-margin - %h2 Themes - %ul - %li{"ng-repeat" => "theme in themeManager.themes | orderBy: 'name'", "ng-click" => "clickedExtension(theme)"} - .container - %h3 - %input.bold{"ng-if" => "theme.rename", "ng-model" => "theme.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(theme);", "sn-autofocus" => "true", "should-focus" => "true"} - %span{"ng-if" => "!theme.rename"} {{theme.name}} - -# %a{"ng-if" => "!themeManager.isThemeActive(theme)", "ng-click" => "themeManager.activateTheme(theme); $event.stopPropagation();"} Activate - -# %a{"ng-if" => "themeManager.isThemeActive(theme)", "ng-click" => "themeManager.deactivateTheme(theme); $event.stopPropagation();"} Deactivate - .mt-3{"ng-if" => "theme.showDetails"} - .link-group - %a{"ng-click" => "renameExtension(theme); $event.stopPropagation();"} Rename - %a{"ng-click" => "theme.showLink = !theme.showLink; $event.stopPropagation();"} Show Link - %a.red{"ng-click" => "deleteTheme(theme); $event.stopPropagation();"} Delete - %p.small.selectable.wrap{"ng-if" => "theme.showLink"} - {{theme.url}} - - - %div{"ng-if" => "actionsManager.extensions.length"} - .header.container.section-margin - %h2 Actions - %p{"style" => "margin-top: 3px;"} Choose "Actions" in the note editor to use installed actions. - - %ul - %li{"ng-repeat" => "extension in actionsManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}", "ng-click" => "clickedExtension(extension)"} - .container - %h3 - %input.bold{"ng-if" => "extension.rename", "ng-model" => "extension.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(extension);", "sn-autofocus" => "true", "should-focus" => "true"} - %span{"ng-if" => "!extension.rename"} {{extension.name}} - %p.small{"ng-if" => "extension.description"} {{extension.description}} - %div{"ng-if" => "extension.showDetails"} - .mt-10 - %label.block Access Type - %label.normal.block{"ng-click" => " $event.stopPropagation();"} - %input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "true", "ng-change" => "changeExtensionEncryptionFormat(true, extension);"} - Encrypted - %label.normal.block{"ng-click" => " $event.stopPropagation();"} - %input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "false", "ng-change" => "changeExtensionEncryptionFormat(false, extension);"} - Decrypted - - .small-v-space - - %ul{"ng-repeat" => "action in extension.actionsInGlobalContext()"} - %li - %label.block {{action.label}} - %em{"style" => "font-style: italic;"} {{action.desc}} - %em{"ng-if" => "action.repeat_mode == 'watch'"} - Repeats when a change is made to your items. - %em{"ng-if" => "action.repeat_mode == 'loop'"} - Repeats at most once every {{action.repeat_timeout}} seconds - %div - %a{"ng-click" => "action.showPermissions = !action.showPermissions"} {{action.showPermissions ? "Hide permissions" : "Show permissions"}} - %div{"ng-if" => "action.showPermissions"} - {{action.permissionsString()}} - %label.block.normal {{action.encryptionModeString()}} - - %div - .mt-5{"ng-if" => "action.repeat_mode"} - %button.light.tinted{"ng-if" => "actionsManager.isRepeatActionEnabled(action)", "ng-click" => "actionsManager.disableRepeatAction(action, extension); $event.stopPropagation();"} Disable - %button.light.tinted{"ng-if" => "!actionsManager.isRepeatActionEnabled(action)", "ng-click" => "actionsManager.enableRepeatAction(action, extension); $event.stopPropagation();"} Enable - %button.light.mt-10{"ng-if" => "!action.running && !action.repeat_mode", "ng-click" => "selectedAction(action, extension); $event.stopPropagation();"} - Perform Action - .spinner.mb-5.block{"ng-if" => "action.running"} - %p.mb-5.mt-5.small{"ng-if" => "!action.error && action.lastExecuted && !action.running"} - Last run {{action.lastExecuted | appDateTime}} - %label.red{"ng-if" => "action.error"} - Error performing action. - - %a.block.mt-5{"ng-click" => "renameExtension(extension); $event.stopPropagation();"} Rename - %a.block.mt-5{"ng-click" => "extension.showURL = !extension.showURL; $event.stopPropagation();"} Show Link - %p.wrap.selectable.small{"ng-if" => "extension.showURL"} {{extension.url}} - %a.block.mt-5{"ng-click" => "deleteActionExtension(extension); $event.stopPropagation();"} Delete - - %div{"ng-if" => "componentManager.components.length > 0"} - .header.container.section-margin - %h2 Components - %ul - %li{"ng-repeat" => "component in componentManager.components | orderBy: 'name'", "ng-click" => "clickedExtension(component)"} - .container - %h3 - %input.bold{"ng-if" => "component.rename", "ng-model" => "component.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(component);", "sn-autofocus" => "true", "should-focus" => "true"} - %span{"ng-if" => "!component.rename"} {{component.name}} - - %div{"ng-if" => "component.isEditor()"} - %a{"ng-if" => "!component.isDefaultEditor()", "ng-click" => "makeEditorDefault(component); $event.stopPropagation();"} Make Default - %a{"ng-if" => "component.isDefaultEditor()", "ng-click" => "removeEditorDefault(component); $event.stopPropagation();"} Remove Default - %div{"ng-if" => "!component.isEditor()"} - %a{"ng-if" => "!componentManager.isComponentActive(component)", "ng-click" => "componentManager.activateComponent(component); $event.stopPropagation();"} Activate - %a{"ng-if" => "componentManager.isComponentActive(component)", "ng-click" => "componentManager.deactivateComponent(component); $event.stopPropagation();"} Deactivate - .mt-3{"ng-if" => "component.showDetails"} - .link-group - %a{"ng-click" => "renameExtension(component); $event.stopPropagation();"} Rename - %a{"ng-click" => "component.showLink = !component.showLink; $event.stopPropagation();"} Show Link - %a{"ng-if" => "component.permissions.length", "ng-click" => "revokePermissions(component); $event.stopPropagation();"} Revoke Permissions - %a.red{"ng-click" => "deleteComponent(component); $event.stopPropagation();"} Delete - %p.small.selectable.wrap{"ng-if" => "component.showLink"} - {{component.url}} - - %div{"ng-if" => "serverExtensions.length > 0"} - .header.container.section-margin - %h2 Server Extensions - %ul - %li{"ng-repeat" => "ext in serverExtensions", "ng-click" => "ext.showDetails = !ext.showDetails"} - .container - %strong.red.medium{"ng-if" => "ext.conflict_of"} Conflicted copy - %h3 {{nameForServerExtension(ext)}} - %div.mt-3{"ng-if" => "ext.showDetails"} - .link-group - %a{"ng-click" => "ext.showUrl = !ext.showUrl; $event.stopPropagation();"} Show Link - %a.red{ "ng-click" => "deleteServerExt(ext); $event.stopPropagation();"} Delete - .wrap.mt-5.selectable{"ng-if" => "ext.showUrl"} {{ext.url}} - - .container.section-margin - %h2.tinted Install - %p.faded Enter an install link - %form.mt-10.mb-10 - %input.form-control{:autofocus => 'autofocus', :name => 'url', :required => true, :autocomplete => "off", - :type => 'url', 'ng-model' => 'formData.installLink', "ng-keyup" => "$event.keyCode == 13 && submitInstallLink();"} - %p.tinted{"ng-if" => "formData.successfullyInstalled"} Successfully installed extension. diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index 33d066153..d719763bc 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -8,11 +8,6 @@ .label.title{"ng-class" => "{red: ctrl.error}"} Account %account-menu{"ng-click" => "$event.stopPropagation()", "ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess", "close-function" => "ctrl.closeAccountMenu"} - .item{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} - .column{"ng-click" => "ctrl.toggleExtensions()"} - .label.title Extensions - %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} - .item .label.title{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help From 2d2693597cb4ec46f32c62566652a620f1153e8a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 24 Jan 2018 11:19:47 -0600 Subject: [PATCH 54/99] Remove packageManager, clean up actionsManager, component manager check is sys ext, clean footer hide/show rooms --- .../javascripts/app/controllers/footer.js | 40 ++++---- .../app/directives/views/accountMenu.js | 4 +- .../app/directives/views/componentModal.js | 10 +- .../javascripts/app/models/app/extension.js | 10 +- .../app/services/actionsManager.js | 91 +------------------ .../app/services/componentManager.js | 23 +++-- .../app/services/packageManager.js | 41 --------- .../javascripts/app/services/sysExtManager.js | 18 +++- app/assets/templates/footer.html.haml | 2 +- 9 files changed, 55 insertions(+), 184 deletions(-) delete mode 100644 app/assets/javascripts/app/services/packageManager.js diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 450133349..fa6157410 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -23,7 +23,7 @@ angular.module('app') } }) .controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager, - syncManager, storageManager, passcodeManager, componentManager, singletonManager, packageManager) { + syncManager, storageManager, passcodeManager, componentManager, singletonManager) { this.getUser = function() { return authManager.user; @@ -47,12 +47,13 @@ angular.module('app') this.showAccountMenu = false; }.bind(this) - this.closeAccountMenu = () => { - this.showAccountMenu = false; - } - this.accountMenuPressed = function() { this.showAccountMenu = !this.showAccountMenu; + this.closeAllRooms(); + } + + this.closeAccountMenu = () => { + this.showAccountMenu = false; } this.hasPasscode = function() { @@ -112,7 +113,7 @@ angular.module('app') if(component.active) { // Show room, if it was not activated manually (in the event of event from componentManager) if(component.area == "rooms" && !component.showRoom) { - this.selectRoom(component); + component.showRoom = true; } $timeout(() => { var lastSize = component.getLastSize(); @@ -127,24 +128,17 @@ angular.module('app') } }}); - this.selectRoom = function(room) { + this.onRoomDismiss = function(room) { + room.showRoom = false; + } - // Allows us to send messages to component modal directive - if(!room.directiveController) { - room.directiveController = {onDismiss: () => { - room.showRoom = false; - }}; - } - - // Make sure to call dismiss() before setting new showRoom value - // This way the directive stays alive long enough to deactivate the associated component - // (The directive's life is at the mercy of "ng-if" => "room.showRoom") - if(room.showRoom) { - room.directiveController.dismiss(() => { - - }); - } else { - room.showRoom = true; + this.closeAllRooms = function() { + for(var room of this.rooms) { + room.showRoom = false; } } + + this.selectRoom = function(room) { + room.showRoom = !room.showRoom; + } }); diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index aa6a68476..7ec956322 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -17,7 +17,9 @@ class AccountMenu { $scope.server = syncManager.serverURL; $scope.close = function() { - $scope.closeFunction()(); + $timeout(() => { + $scope.closeFunction()(); + }) } $scope.encryptedBackupsAvailable = function() { diff --git a/app/assets/javascripts/app/directives/views/componentModal.js b/app/assets/javascripts/app/directives/views/componentModal.js index 0ee48c915..006b9b23e 100644 --- a/app/assets/javascripts/app/directives/views/componentModal.js +++ b/app/assets/javascripts/app/directives/views/componentModal.js @@ -18,20 +18,12 @@ class ComponentModal { controller($scope, $timeout, componentManager) { 'ngInject'; - if($scope.component.directiveController) { - $scope.component.directiveController.dismiss = function(callback) { - $scope.dismiss(callback); - } - } - $scope.dismiss = function(callback) { - var onDismiss = $scope.component.directiveController && $scope.component.directiveController.onDismiss(); $scope.el.remove(); $scope.$destroy(); - onDismiss && onDismiss(); + $scope.onDismiss && $scope.onDismiss()($scope.component); callback && callback(); } - } } diff --git a/app/assets/javascripts/app/models/app/extension.js b/app/assets/javascripts/app/models/app/extension.js index 4e80f484e..2a5111062 100644 --- a/app/assets/javascripts/app/models/app/extension.js +++ b/app/assets/javascripts/app/models/app/extension.js @@ -10,7 +10,7 @@ class Action { } } -class Extension extends Item { +class Extension extends Component { constructor(json) { super(json); @@ -33,9 +33,7 @@ class Extension extends Item { mapContentToLocalProperties(content) { super.mapContentToLocalProperties(content) - this.name = content.name; this.description = content.description; - this.url = content.url; this.supported_types = content.supported_types; if(content.actions) { @@ -45,18 +43,12 @@ class Extension extends Item { } } - referenceParams() { - return null; - } - get content_type() { return "Extension"; } structureParams() { var params = { - name: this.name, - url: this.url, description: this.description, actions: this.actions, supported_types: this.supported_types diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index bf2002df8..85a30253b 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -1,12 +1,10 @@ class ActionsManager { - constructor(httpManager, modelManager, authManager, syncManager, storageManager) { + constructor(httpManager, modelManager, authManager, syncManager) { this.httpManager = httpManager; this.modelManager = modelManager; this.authManager = authManager; - this.enabledRepeatActionUrls = JSON.parse(storageManager.getItem("enabledRepeatActionUrls")) || []; this.syncManager = syncManager; - this.storageManager = storageManager; } get extensions() { @@ -19,27 +17,11 @@ class ActionsManager { }) } - actionWithURL(url) { - for (var extension of this.extensions) { - return _.find(extension.actions, {url: url}) - } - } - - addExtension(url, callback) { - this.retrieveExtensionFromServer(url, callback); - } - - deleteExtension(extension) { - this.modelManager.setItemToBeDeleted(extension); - this.syncManager.sync(null); - } - /* Loads an extension in the context of a certain item. The server then has the chance to respond with actions that are relevant just to this item. The response extension is not saved, just displayed as a one-time thing. */ loadExtensionInContextOfItem(extension, item, callback) { - this.httpManager.getAbsolute(extension.url, {content_type: item.content_type, item_uuid: item.uuid}, function(response){ this.updateExtensionFromRemoteResponse(extension, response); callback && callback(extension); @@ -51,50 +33,9 @@ class ActionsManager { }.bind(this)) } - /* - Registers new extension and saves it to user's account - */ - retrieveExtensionFromServer(url, callback) { - this.httpManager.getAbsolute(url, {}, function(response){ - if(typeof response !== 'object') { - callback(null); - return; - } - var ext = this.handleExtensionLoadExternalResponseItem(url, response); - if(callback) { - callback(ext); - } - }.bind(this), function(response){ - console.error("Error registering extension", response); - callback(null); - }) - } - - handleExtensionLoadExternalResponseItem(url, externalResponseItem) { - // Don't allow remote response to set these flags - delete externalResponseItem.uuid; - - var extension = _.find(this.extensions, {url: url}); - if(extension) { - this.updateExtensionFromRemoteResponse(extension, externalResponseItem); - } else { - extension = new Extension(externalResponseItem); - extension.url = url; - extension.setDirty(true); - this.modelManager.addItem(extension); - this.syncManager.sync(null); - } - - return extension; - } - updateExtensionFromRemoteResponse(extension, response) { - if(response.description) { - extension.description = response.description; - } - if(response.supported_types) { - extension.supported_types = response.supported_types; - } + if(response.description) { extension.description = response.description; } + if(response.supported_types) { extension.supported_types = response.supported_types; } if(response.actions) { extension.actions = response.actions.map(function(action){ @@ -105,14 +46,6 @@ class ActionsManager { } } - refreshExtensionsFromServer() { - for(var ext of this.extensions) { - this.retrieveExtensionFromServer(ext.url, function(extension){ - extension.setDirty(true); - }); - } - } - executeAction(action, extension, item, callback) { var customCallback = function(response) { @@ -197,24 +130,6 @@ class ActionsManager { action.lastExecuted = new Date(); } - isRepeatActionEnabled(action) { - return _.includes(this.enabledRepeatActionUrls, action.url); - } - - queueAction(action, extension, delay, changedItems) { - this.actionQueue = this.actionQueue || []; - if(_.find(this.actionQueue, {url: action.url})) { - return; - } - - this.actionQueue.push(action); - - setTimeout(function () { - this.triggerWatchAction(action, extension, changedItems); - _.pull(this.actionQueue, action); - }.bind(this), delay * 1000); - } - outgoingParamsForItem(item, extension, decrypted = false) { var keys = this.authManager.keys(); if(decrypted) { diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 42e42073c..5e1db02cf 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -9,6 +9,7 @@ class ComponentManager { this.modelManager = modelManager; this.syncManager = syncManager; this.desktopManager = desktopManager; + this.sysExtManager = sysExtManager; this.timeout = $timeout; this.streamObservers = []; this.contextStreamObservers = []; @@ -160,7 +161,7 @@ class ComponentManager { if(source && source == ModelManager.MappingSourceRemoteSaved) { params.isMetadataUpdate = true; } - this.removePrivatePropertiesFromResponseItems([params]); + this.removePrivatePropertiesFromResponseItems([params], component); return params; } @@ -289,11 +290,17 @@ class ComponentManager { } } - removePrivatePropertiesFromResponseItems(responseItems, includeUrls) { + removePrivatePropertiesFromResponseItems(responseItems, component, options = {}) { + if(component) { + // System extensions can bypass this step + if(this.sysExtManager.isSystemExtension(component)) { + return; + } + } // Don't allow component to overwrite these properties. - var privateProperties = ["appData", "autoupdateDisabled", "permissions", "active", "encrypted"]; - if(includeUrls) { - privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"]); + var privateProperties = ["appData", "autoupdateDisabled", "permissions", "active"]; + if(options) { + if(options.includeUrls) { privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"])} } for(var responseItem of responseItems) { @@ -302,7 +309,7 @@ class ComponentManager { console.assert(typeof responseItem.setDirty !== 'function'); for(var prop of privateProperties) { - delete responseItem[prop]; + delete responseItem.content[prop]; } } } @@ -402,7 +409,7 @@ class ComponentManager { this.runWithPermissions(component, requiredPermissions, () => { - this.removePrivatePropertiesFromResponseItems(responseItems, {includeUrls: true}); + this.removePrivatePropertiesFromResponseItems(responseItems, component, {includeUrls: true}); /* We map the items here because modelManager is what updates the UI. If you were to instead get the items directly, @@ -439,7 +446,7 @@ class ComponentManager { this.runWithPermissions(component, requiredPermissions, () => { var responseItem = message.data.item; - this.removePrivatePropertiesFromResponseItems([responseItem]); + this.removePrivatePropertiesFromResponseItems([responseItem], component); var item = this.modelManager.createItem(responseItem); if(responseItem.clientData) { item.setDomainDataItem(component.url || component.uuid, responseItem.clientData, ClientDataDomain); diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js deleted file mode 100644 index fa0aa837e..000000000 --- a/app/assets/javascripts/app/services/packageManager.js +++ /dev/null @@ -1,41 +0,0 @@ -class PackageManager { - - constructor(httpManager, modelManager, syncManager, componentManager) { - this.httpManager = httpManager; - this.modelManager = modelManager; - this.syncManager = syncManager; - this.componentManager = componentManager; - } - - installPackage(url, callback) { - this.httpManager.getAbsolute(url, {}, function(aPackage){ - console.log("Got package data", aPackage); - if(typeof aPackage !== 'object') { - callback(null); - return; - } - - // Remove private properties - this.componentManager.removePrivatePropertiesFromResponseItems([aPackage]); - - aPackage.package_info = Object.assign({}, aPackage); - - var assembled = this.modelManager.createItem(aPackage);; - this.modelManager.addItem(assembled); - - assembled.setDirty(true); - this.syncManager.sync("installPackage"); - - console.log("Created assembled", assembled); - - callback && callback(assembled); - }.bind(this), function(response){ - console.error("Error retrieving package", response); - callback(null); - }) - } - - -} - -angular.module('app').service('packageManager', PackageManager); diff --git a/app/assets/javascripts/app/services/sysExtManager.js b/app/assets/javascripts/app/services/sysExtManager.js index 7ee8c76ea..c822a9f2d 100644 --- a/app/assets/javascripts/app/services/sysExtManager.js +++ b/app/assets/javascripts/app/services/sysExtManager.js @@ -7,14 +7,22 @@ class SysExtManager { this.syncManager = syncManager; this.singletonManager = singletonManager; + this.extensionsIdentifier = "org.standardnotes.extensions-manager"; + this.systemExtensions = []; + this.resolveExtensionsManager(); } - resolveExtensionsManager() { - let extensionsIdentifier = "org.standardnotes.extensions-manager"; + isSystemExtension(extension) { + return this.systemExtensions.includes(extension.uuid); + } - this.singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: extensionsIdentifier}}, (resolvedSingleton) => { + resolveExtensionsManager() { + + this.singletonManager.registerSingleton({content_type: "SN|Component", package_info: {identifier: this.extensionsIdentifier}}, (resolvedSingleton) => { // Resolved Singleton + this.systemExtensions.push(resolvedSingleton.uuid); + var needsSync = false; if(isDesktopApplication()) { if(!resolvedSingleton.local_url) { @@ -43,7 +51,7 @@ class SysExtManager { let packageInfo = { name: "Extensions", - identifier: extensionsIdentifier + identifier: this.extensionsIdentifier } var item = { @@ -73,6 +81,8 @@ class SysExtManager { component.setDirty(true); this.syncManager.sync("resolveExtensionsManager createNew"); + this.systemExtensions.push(component.uuid); + valueCallback(component); }); } diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index d719763bc..68510d557 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -17,7 +17,7 @@ .item{"ng-repeat" => "room in ctrl.rooms track by room.uuid"} .column{"ng-click" => "ctrl.selectRoom(room)"} .label {{room.name}} - %component-modal{"ng-if" => "room.showRoom", "component" => "room", "controller" => "room.directiveController"} + %component-modal{"ng-if" => "room.showRoom", "component" => "room", "on-dismiss" => "ctrl.onRoomDismiss"} .right From 2d71617f0777c46f64783681d1cc4448a2865742 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 24 Jan 2018 12:41:35 -0600 Subject: [PATCH 55/99] Style fixes --- app/assets/javascripts/app/controllers/editor.js | 3 +++ app/assets/javascripts/app/services/desktopManager.js | 1 - app/assets/stylesheets/app/_footer.scss | 4 ++++ app/assets/stylesheets/app/_lock-screen.scss | 1 + app/assets/templates/footer.html.haml | 2 +- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 514342e83..d3f9c9db1 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -407,6 +407,9 @@ angular.module('app') this.noteReady = false; $timeout(() => { this.noteReady = true; + $timeout(() => { + this.reloadFont(); + }) }, 0) } } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 3454d6e19..24a1bf152 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -41,7 +41,6 @@ class DesktopManager { if(!this.isDesktop) return; var data = components.map((component) => { - console.log("Web Sycying Component", this.convertComponentForTransmission(component)); return this.convertComponentForTransmission(component); }) this.installationSyncHandler(data); diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index 5eb9851d0..950331dea 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -37,6 +37,10 @@ a.disabled { pointer-events: none; } +#lock-item { + margin-left: 3px; +} + .icon.ion-locked { margin-left: 5px; border-left: 1px solid gray; diff --git a/app/assets/stylesheets/app/_lock-screen.scss b/app/assets/stylesheets/app/_lock-screen.scss index 7fffb7097..5b0fe4ba9 100644 --- a/app/assets/stylesheets/app/_lock-screen.scss +++ b/app/assets/stylesheets/app/_lock-screen.scss @@ -25,6 +25,7 @@ .panel { width: 315px; + flex-grow: 0; .header { justify-content: center; diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index 68510d557..d06f0a86e 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -36,6 +36,6 @@ .item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} .label Refresh - .item{"ng-if" => "ctrl.hasPasscode()"} + .item#lock-item{"ng-if" => "ctrl.hasPasscode()"} .label %i.icon.ion-locked{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"} From 8f8c22daa9ff9d87760702be1f7e94e5b1e9826f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 25 Jan 2018 10:29:26 -0600 Subject: [PATCH 56/99] Component dont overwrite appData, tags panel css --- .../javascripts/app/controllers/footer.js | 4 +++ app/assets/javascripts/app/models/api/item.js | 6 ++-- app/assets/stylesheets/app/_notes.scss | 3 -- app/assets/stylesheets/app/_tags.scss | 31 +++++++++++-------- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index fa6157410..8124e4ebe 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -107,6 +107,10 @@ angular.module('app') 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}); + + for(var room of this.rooms) { + room.hosted_url = "http://localhost:8080"; + } }); componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => { diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js index d2f77577d..b05f94adb 100644 --- a/app/assets/javascripts/app/models/api/item.js +++ b/app/assets/javascripts/app/models/api/item.js @@ -84,10 +84,10 @@ class Item { } mapContentToLocalProperties(contentObj) { - this.appData = contentObj.appData; - if(!this.appData) { - this.appData = {}; + if(contentObj.appData) { + this.appData = contentObj.appData; } + if(!this.appData) { this.appData = {}; } } createContentJSONFromProperties() { diff --git a/app/assets/stylesheets/app/_notes.scss b/app/assets/stylesheets/app/_notes.scss index 88fce804f..45e6cd0f0 100644 --- a/app/assets/stylesheets/app/_notes.scss +++ b/app/assets/stylesheets/app/_notes.scss @@ -31,11 +31,8 @@ } #notes-menu-bar { - color: default; position: relative; margin-top: 14px; - height: auto; - width: auto; } .filter-section { diff --git a/app/assets/stylesheets/app/_tags.scss b/app/assets/stylesheets/app/_tags.scss index 283a0cc8e..5285e9c91 100644 --- a/app/assets/stylesheets/app/_tags.scss +++ b/app/assets/stylesheets/app/_tags.scss @@ -5,20 +5,20 @@ -khtml-user-select: none; -webkit-user-select: none; - $tags-title-bar-height: 55px; - - #tags-title-bar { - color: black; - height: $tags-title-bar-height; - padding-top: 14px; - padding-left: 12px; - padding-right: 12px; - font-size: 12px; - color: rgba(black, 0.8); - } - &, #tags-content { background-color: #f6f6f6; + display: flex; + flex-direction: column; + } + + #tags-title-bar { + color: black; + padding-top: 14px; + padding-bottom: 16px; + padding-left: 12px; + padding-right: 12px; + font-size: 12px; + color: rgba(black, 0.8); } #tag-add-button { @@ -30,7 +30,12 @@ } .scrollable { - height: calc(100vh - (#{$tags-title-bar-height} + #{$footer-height})); + height: 100%; + } + + .infinite-scroll { + overflow-x: hidden; + height: inherit; } .tag { From 7a68f641162f23fb775664ae0ee16b783febe03c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 25 Jan 2018 13:16:22 -0600 Subject: [PATCH 57/99] Updates --- .../app/directives/views/componentModal.js | 2 +- .../app/directives/views/componentView.js | 4 +-- .../app/services/componentManager.js | 30 ++++++++----------- .../app/services/desktopManager.js | 27 +++++++++-------- app/assets/stylesheets/app/_main.scss | 8 +++-- .../directives/editor-menu.html.haml | 14 +++------ app/assets/templates/editor.html.haml | 6 ++-- app/assets/templates/footer.html.haml | 2 +- package-lock.json | 6 ++-- package.json | 2 +- 10 files changed, 47 insertions(+), 54 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentModal.js b/app/assets/javascripts/app/directives/views/componentModal.js index 006b9b23e..7cda3c897 100644 --- a/app/assets/javascripts/app/directives/views/componentModal.js +++ b/app/assets/javascripts/app/directives/views/componentModal.js @@ -21,7 +21,7 @@ class ComponentModal { $scope.dismiss = function(callback) { $scope.el.remove(); $scope.$destroy(); - $scope.onDismiss && $scope.onDismiss()($scope.component); + $scope.onDismiss && $scope.onDismiss() && $scope.onDismiss()($scope.component); callback && callback(); } } diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 73f390983..61c1a3cb3 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -17,7 +17,7 @@ class ComponentView { $scope.identifier = "component-view-" + Math.random(); - console.log("Registering handler", $scope.identifier, $scope.component.name); + // console.log("Registering handler", $scope.identifier, $scope.component.name); this.componentManager.registerHandler({identifier: $scope.identifier, areas: [$scope.component.area], activationHandler: (component) => { if(component.active) { @@ -86,7 +86,7 @@ class ComponentView { } $scope.$on("$destroy", function() { - console.log("Deregistering handler", $scope.identifier, $scope.component.name); + // console.log("Deregistering handler", $scope.identifier, $scope.component.name); componentManager.deregisterHandler($scope.identifier); if($scope.component && !$scope.manualDealloc) { componentManager.deactivateComponent($scope.component); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 5e1db02cf..4082df661 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -278,6 +278,8 @@ class ComponentManager { this.handleToggleComponentMessage(component, componentToToggle, message); } else if(message.action === "request-permissions") { this.handleRequestPermissionsMessage(component, message); + } else if(message.action === "install-local-component") { + this.handleInstallLocalComponentMessage(component, message); } // Notify observers @@ -516,6 +518,16 @@ class ComponentManager { } } + handleInstallLocalComponentMessage(sourceComponent, message) { + // Only extensions manager has this permission + if(!this.sysExtManager.isSystemExtension(sourceComponent)) { + return; + } + + let targetComponent = this.modelManager.findItem(message.data.uuid); + this.desktopManager.installComponent(targetComponent); + } + runWithPermissions(component, requiredPermissions, runFunction) { if(!component.permissions) { @@ -625,28 +637,10 @@ class ComponentManager { openModalComponent(component) { var scope = this.$rootScope.$new(true); scope.component = component; - scope.onDismiss = () => { - - } var el = this.$compile( "" )(scope); angular.element(document.body).append(el); } - installComponent(url) { - var name = getParameterByName("name", url); - var area = getParameterByName("area", url); - var component = this.modelManager.createItem({ - content_type: "SN|Component", - url: url, - name: name, - area: area - }) - - this.modelManager.addItem(component); - component.setDirty(true); - this.syncManager.sync("installComponent"); - } - activateComponent(component) { var didChange = component.active != true; diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 24a1bf152..916d3a968 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -37,7 +37,6 @@ class DesktopManager { // All `components` should be installed syncComponentsInstallation(components) { - // console.log("Web Syncing Components", components); if(!this.isDesktop) return; var data = components.map((component) => { @@ -46,18 +45,22 @@ class DesktopManager { this.installationSyncHandler(data); } - desktop_onComponentInstallationComplete(componentData) { - console.log("Web|Component Installation Complete", componentData); - var component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; - component.setDirty(true); - this.syncManager.sync("desktop_onComponentInstallationComplete"); + installComponent(component) { + this.installComponentHandler(this.convertComponentForTransmission(component)); } - desktop_updateComponentComplete(componentData) { - console.log("Web|Component Update Complete", componentData); - var component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; + desktop_onComponentInstallationComplete(componentData, error) { + console.log("Web|Component Installation/Update Complete", componentData, error); + var component = this.modelManager.findItem(componentData.uuid); + if(error) { + component = this.modelManager.findItem(componentData.uuid); + component.setAppDataItem("installError", error); + } else { + component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; + component.setAppDataItem("installError", null); + } component.setDirty(true); - this.syncManager.sync("desktop_updateComponentComplete"); + this.syncManager.sync("onComponentInstallationComplete"); } /* Used to resolve "sn://" */ @@ -69,8 +72,8 @@ class DesktopManager { this.installationSyncHandler = handler; } - desktop_setOfflineComponentInstallationHandler(handler) { - this.componentInstallationHandler = handler; + desktop_setInstallComponentHandler(handler) { + this.installComponentHandler = handler; } desktop_setInitialDataLoadHandler(handler) { diff --git a/app/assets/stylesheets/app/_main.scss b/app/assets/stylesheets/app/_main.scss index 8dc27c5a5..45228e125 100644 --- a/app/assets/stylesheets/app/_main.scss +++ b/app/assets/stylesheets/app/_main.scss @@ -160,18 +160,20 @@ $footer-height: 32px; } > .add-button { - + $button-bg: #e9e9e9; + color: lighten($main-text-color, 40%); font-size: 18px; width: 45px; height: 24px; cursor: pointer; - background-color: #e9e9e9; + background-color: $button-bg; border-radius: 4px; font-weight: normal; text-align: center; &:hover { - background-color: rgba(#e9e9e9, 0.8); + background-color: darken($button-bg, 5%); + color: lighten($main-text-color, 40%); } } } diff --git a/app/assets/templates/directives/editor-menu.html.haml b/app/assets/templates/directives/editor-menu.html.haml index f6508d3af..7d309d19e 100644 --- a/app/assets/templates/directives/editor-menu.html.haml +++ b/app/assets/templates/directives/editor-menu.html.haml @@ -9,11 +9,8 @@ "circle" => "selectedEditor === editor && 'success'", "has-button" => "selectedEditor == editor || defaultEditor == editor", "button-text" => "defaultEditor == editor ? 'Undefault' : 'Set Default'", "button-action" => "toggleDefaultForEditor(editor)", "button-class" => "defaultEditor == editor ? 'warning' : 'info'"} - .row{"ng-if" => "component.conflict_of || offlineAvailableForComponent(editor)"} - .column - %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy - .sublabel{"ng-if" => "offlineAvailableForComponent(editor)"} - Available Offline + .column{"ng-if" => "component.conflict_of"} + %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download More Editors'"} @@ -22,8 +19,5 @@ %h4.title Editor Stack %menu-row{"ng-repeat" => "component in stack", "ng-click" => "selectComponent($event, component)", "title" => "component.name", "circle" => "component.active ? 'success' : 'danger'"} - .row{"ng-if" => "component.conflict_of || offlineAvailableForComponent(component)"} - .column - %strong.red.medium{"ng-if" => "component.conflict_of"} Conflicted copy - .sublabel{"ng-if" => "offlineAvailableForComponent(component)"} - Available Offline + .column{"ng-if" => "component.conflict_of"} + %strong.red.medium{"ng-if" => "component.conflict_of"} Conflicted copy diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index d946b4e07..62c819776 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -22,13 +22,13 @@ .section .header %h4.title Note Options - %menu-row{"title" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.togglePin()"} - %menu-row{"title" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.toggleArchiveNote()"} + %menu-row{"title" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "ng-click" => "ctrl.selectedMenuItem($event, true); ctrl.togglePin()"} + %menu-row{"title" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "ng-click" => "ctrl.selectedMenuItem($event, true); ctrl.toggleArchiveNote()"} %menu-row{"title" => "'Delete'", "ng-click" => "ctrl.selectedMenuItem($event); ctrl.deleteNote()"} .section{"ng-if" => "!ctrl.selectedEditor"} .header - %h4.title Display + %h4.title Global Display %menu-row{"title" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event, true); ctrl.toggleKey('monospaceFont')"} %menu-row{"title" => "'Spellcheck'", "circle" => "ctrl.spellcheck ? 'success' : 'default'", "ng-click" => "ctrl.selectedMenuItem($event, true); ctrl.toggleKey('spellcheck')"} diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index d06f0a86e..1f5e7243a 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -9,7 +9,7 @@ %account-menu{"ng-click" => "$event.stopPropagation()", "ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess", "close-function" => "ctrl.closeAccountMenu"} .item - .label.title{"href" => "https://standardnotes.org/help", "target" => "_blank"} + %a.no-decoration.label.title{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help .item.border diff --git a/package-lock.json b/package-lock.json index bbfd700e7..69b9a3d84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5781,9 +5781,9 @@ "dev": true }, "sn-stylekit": { - "version": "1.0.1192", - "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1192.tgz", - "integrity": "sha512-qvR1rPI1FeG+Us2+P+0XZ0kndd5D9gg93Xla8aqopNbJ3xB2vh3OXvl/3XRSL4xKNrG+4Ub7u5Xg/J5NPPNRoA==", + "version": "1.0.1195", + "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1195.tgz", + "integrity": "sha512-XPutiDkA0Jbf3fGHjC6avoWbMsLJKLOXnPJDpE2VFA2/HUf87sjyTvvhg8mrRT2IiE8Cr0gJFQr/ALnoP+u1EQ==", "dev": true }, "snake-case": { diff --git a/package.json b/package.json index f2ced494e..12f5b412f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "karma-cli": "^1.0.1", "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", - "sn-stylekit": "^1.0.1192" + "sn-stylekit": "^1.0.1195" }, "license": "GPL-3.0" } From 8aa79cecf9cd3a585cdf43ff72da2039a7147ce8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 25 Jan 2018 17:06:42 -0600 Subject: [PATCH 58/99] Component error handling --- .../app/directives/views/componentView.js | 12 ++++++++++-- .../templates/directives/component-modal.html.haml | 1 - .../templates/directives/component-view.html.haml | 13 ++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 61c1a3cb3..3227cec55 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -64,9 +64,17 @@ class ComponentView { $scope.reloading = true; let previouslyValid = $scope.componentValid; - $scope.offlineRestricted = component.offlineOnly && !isDesktopApplication(); + var expired, offlineRestricted, urlError; - $scope.componentValid = !$scope.offlineRestricted && (!component.valid_until || (component.valid_until && component.valid_until > new Date())); + offlineRestricted = component.offlineOnly && !isDesktopApplication(); + urlError = !isDesktopApplication() && (!component.url || component.hosted_url); + expired = component.valid_until && component.valid_until <= new Date(); + + $scope.componentValid = !offlineRestricted && !urlError && !expired; + + if(offlineRestricted) $scope.error = 'offline-restricted'; + if(urlError) $scope.error = 'url-missing'; + if(expired) $scope.error = 'expired'; if($scope.componentValid !== previouslyValid) { if($scope.componentValid) { diff --git a/app/assets/templates/directives/component-modal.html.haml b/app/assets/templates/directives/component-modal.html.haml index 25126a337..1229327cc 100644 --- a/app/assets/templates/directives/component-modal.html.haml +++ b/app/assets/templates/directives/component-modal.html.haml @@ -6,6 +6,5 @@ .header %h1.title {{component.name}} - %span.subtle.subtitle{"ng-if" => "component.runningLocally"} | Desktop Mode %a.close-button.info{"ng-click" => "dismiss()"} Close %component-view.component-view{"component" => "component"} diff --git a/app/assets/templates/directives/component-view.html.haml b/app/assets/templates/directives/component-view.html.haml index 8489d07c2..b7aa03eb5 100644 --- a/app/assets/templates/directives/component-view.html.haml +++ b/app/assets/templates/directives/component-view.html.haml @@ -1,4 +1,4 @@ -.sn-component{"ng-if" => "!componentValid && !offlineRestricted"} +.sn-component{"ng-if" => "error == 'expired'"} .panel.static .content .panel-section.stretch @@ -32,7 +32,7 @@ %a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help page. -.sn-component{"ng-if" => "offlineRestricted"} +.sn-component{"ng-if" => "error == 'offline-restricted'"} .panel.static .content .panel-section.stretch @@ -50,8 +50,15 @@ .label Reload .spinner.info.small{"ng-if" => "reloading"} +.sn-component{"ng-if" => "error == 'url-missing'"} + .panel.static + .content + .panel-section.stretch + %h2.title This extension is not installed correctly. + %p Please uninstall {{component.name}}, then re-install it. -%iframe{"ng-if" => "component && componentValid && !offlineRestricted", + +%iframe{"ng-if" => "component && componentValid", "ng-attr-id" => "component-{{component.uuid}}", "ng-src" => "{{getUrl() | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms", From 074b86aa3542ed0fc276b6e528a29561811e2cbd Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 25 Jan 2018 17:23:41 -0600 Subject: [PATCH 59/99] Fix editor component stack reload issue --- .../javascripts/app/controllers/editor.js | 32 ++++++------------- .../app/directives/views/editorMenu.js | 8 ++++- .../directives/editor-menu.html.haml | 3 +- app/assets/templates/editor.html.haml | 2 +- 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index d3f9c9db1..70e2e6675 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -27,7 +27,6 @@ angular.module('app') this.spellcheck = true; this.componentManager = componentManager; - this.componentStack = []; $rootScope.$on("sync:taking-too-long", function(){ this.syncTakingTooLong = true; @@ -425,16 +424,7 @@ angular.module('app') if(component.area === "note-tags") { // Autocomplete Tags this.tagsComponent = component.active ? component : null; - } else if(component.area == "editor-stack") { - // Stack - if(component.active) { - if(!_.find(this.componentStack, component)) { - this.componentStack.push(component); - } - } else { - _.pull(this.componentStack, component); - } - } else { + } else if(component.area == "editor-editor") { // Editor if(component.active && this.note && (component.isExplicitlyEnabledForItem(this.note) || component.isDefaultEditor())) { this.selectedEditor = component; @@ -493,21 +483,17 @@ angular.module('app') }.bind(this)}); this.reloadComponentContext = function() { + // componentStack is used by the template to ng-repeat + this.componentStack = componentManager.componentsForArea("editor-stack"); + for(var component of this.componentStack) { + if(component.active) { + component.hidden = component.isExplicitlyDisabledForItem(this.note); + } + } + componentManager.contextItemDidChangeInArea("note-tags"); componentManager.contextItemDidChangeInArea("editor-stack"); componentManager.contextItemDidChangeInArea("editor-editor"); - - var stack = componentManager.componentsForArea("editor-stack"); - for(var component of stack) { - if(component.active) { - var disabledForItem = component.isExplicitlyDisabledForItem(this.note); - if(disabledForItem) { - component.hidden = true; - } else { - component.hidden = false; - } - } - } } this.toggleStackComponentForCurrentItem = function(component) { diff --git a/app/assets/javascripts/app/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js index 78b9bb66f..21fdb6f89 100644 --- a/app/assets/javascripts/app/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/directives/views/editorMenu.js @@ -5,7 +5,8 @@ class EditorMenu { this.templateUrl = "directives/editor-menu.html"; this.scope = { callback: "&", - selectedEditor: "=" + selectedEditor: "=", + currentItem: "=" }; } @@ -49,6 +50,7 @@ class EditorMenu { currentDefault.setAppDataItem("defaultEditor", false); currentDefault.setDirty(true); } + component.setAppDataItem("defaultEditor", true); component.setDirty(true); syncManager.sync("makeEditorDefault"); @@ -64,6 +66,10 @@ class EditorMenu { $scope.defaultEditor = null; } + $scope.stackComponentEnabled = function(component) { + return component.active && !component.isExplicitlyDisabledForItem($scope.currentItem); + } + } } diff --git a/app/assets/templates/directives/editor-menu.html.haml b/app/assets/templates/directives/editor-menu.html.haml index 7d309d19e..5325256c9 100644 --- a/app/assets/templates/directives/editor-menu.html.haml +++ b/app/assets/templates/directives/editor-menu.html.haml @@ -14,10 +14,11 @@ %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download More Editors'"} + .section{"ng-if" => "stack.length > 0"} .header %h4.title Editor Stack %menu-row{"ng-repeat" => "component in stack", "ng-click" => "selectComponent($event, component)", "title" => "component.name", - "circle" => "component.active ? 'success' : 'danger'"} + "circle" => "stackComponentEnabled(component) ? 'success' : 'danger'"} .column{"ng-if" => "component.conflict_of"} %strong.red.medium{"ng-if" => "component.conflict_of"} Conflicted copy diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 62c819776..a32c17a72 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -34,7 +34,7 @@ .item{"ng-click" => "ctrl.onEditorMenuClick()", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} .label Editor - %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.editorMenuOnSelect", "selected-editor" => "ctrl.selectedEditor"} + %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.editorMenuOnSelect", "selected-editor" => "ctrl.selectedEditor", "current-item" => "ctrl.note"} .item{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showExtensions}", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} .label Actions From 5a26b89e8633016304119a334ecc25d679a43efb Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 26 Jan 2018 11:00:00 -0600 Subject: [PATCH 60/99] Logic update --- app/assets/javascripts/app/directives/views/componentView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 3227cec55..df6686720 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -67,7 +67,7 @@ class ComponentView { var expired, offlineRestricted, urlError; offlineRestricted = component.offlineOnly && !isDesktopApplication(); - urlError = !isDesktopApplication() && (!component.url || component.hosted_url); + urlError = !isDesktopApplication() && (!component.url && !component.hosted_url); expired = component.valid_until && component.valid_until <= new Date(); $scope.componentValid = !offlineRestricted && !urlError && !expired; From 457eb6f04349be6a68d404f7f92fc42868e52d58 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 26 Jan 2018 12:35:03 -0600 Subject: [PATCH 61/99] Submodules --- .../app/directives/views/componentView.js | 2 +- .../javascripts/app/directives/views/editorMenu.js | 14 ++++++++++++++ .../templates/directives/editor-menu.html.haml | 6 ++++-- public/extensions/extensions-manager | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index df6686720..ecfa5ba14 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -89,7 +89,7 @@ class ComponentView { $scope.getUrl = function() { var url = componentManager.urlForComponent($scope.component); - $scope.component.runningLocally = url !== ($scope.component.url || $scope.component.hosted_url); + $scope.component.runningLocally = (url !== $scope.component.url) && url !== ($scope.component.hosted_url); return url; } diff --git a/app/assets/javascripts/app/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js index 21fdb6f89..16ae8f6dc 100644 --- a/app/assets/javascripts/app/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/directives/views/editorMenu.js @@ -66,6 +66,20 @@ class EditorMenu { $scope.defaultEditor = null; } + $scope.shouldDisplayRunningLocallyLabel = function(component) { + if(!component.runningLocally) { + return false; + } + + if(component == $scope.selectedEditor) { + return true; + } else if(component.area == "editor-stack") { + return $scope.stackComponentEnabled(component); + } else { + return false; + } + } + $scope.stackComponentEnabled = function(component) { return component.active && !component.isExplicitlyDisabledForItem($scope.currentItem); } diff --git a/app/assets/templates/directives/editor-menu.html.haml b/app/assets/templates/directives/editor-menu.html.haml index 5325256c9..6787ab986 100644 --- a/app/assets/templates/directives/editor-menu.html.haml +++ b/app/assets/templates/directives/editor-menu.html.haml @@ -9,8 +9,9 @@ "circle" => "selectedEditor === editor && 'success'", "has-button" => "selectedEditor == editor || defaultEditor == editor", "button-text" => "defaultEditor == editor ? 'Undefault' : 'Set Default'", "button-action" => "toggleDefaultForEditor(editor)", "button-class" => "defaultEditor == editor ? 'warning' : 'info'"} - .column{"ng-if" => "component.conflict_of"} + .column{"ng-if" => "component.conflict_of || shouldDisplayRunningLocallyLabel(editor)"} %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy + .sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(editor)"} Running Locally %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} %menu-row{"title" => "'Download More Editors'"} @@ -20,5 +21,6 @@ %h4.title Editor Stack %menu-row{"ng-repeat" => "component in stack", "ng-click" => "selectComponent($event, component)", "title" => "component.name", "circle" => "stackComponentEnabled(component) ? 'success' : 'danger'"} - .column{"ng-if" => "component.conflict_of"} + .column{"ng-if" => "component.conflict_of || shouldDisplayRunningLocallyLabel(component)"} %strong.red.medium{"ng-if" => "component.conflict_of"} Conflicted copy + .sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(component)"} Running Locally diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager index 87b260800..d0f467cf4 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 87b26080032d4df427015e49396895f506501bc9 +Subproject commit d0f467cf4424f11d4c5972284fe6af66a862e012 From 04060a3dc3752033aa7472372fbe38dc0da90563 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 26 Jan 2018 13:05:03 -0600 Subject: [PATCH 62/99] Fix tags area component active --- app/assets/javascripts/app/controllers/tags.js | 1 - app/assets/javascripts/app/services/componentManager.js | 2 +- app/assets/templates/tags.html.haml | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js index 76f11ddf2..575011f58 100644 --- a/app/assets/javascripts/app/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -62,7 +62,6 @@ angular.module('app') componentManager.registerHandler({identifier: "tags", areas: ["tags-list"], activationHandler: function(component){ this.component = component; - }.bind(this), contextRequestHandler: function(component){ return null; }.bind(this), actionHandler: function(component, action, data){ diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 4082df661..55072029e 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -376,7 +376,7 @@ class ComponentManager { for(let handler of this.handlersForArea(component.area)) { if(handler.contextRequestHandler) { var itemInContext = handler.contextRequestHandler(component); - if(itemInContext.uuid == item.uuid) { + if(itemInContext && itemInContext.uuid == item.uuid) { return true; } } diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml index cbbc9d214..ccab40f7e 100644 --- a/app/assets/templates/tags.html.haml +++ b/app/assets/templates/tags.html.haml @@ -1,5 +1,5 @@ .section.tags#tags-column - .component-view-container{"ng-if" => "ctrl.component"} + .component-view-container{"ng-if" => "ctrl.component.active"} %component-view.component-view{"component" => "ctrl.component"} #tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"} #tags-title-bar.section-title-bar From c33c45278f212ba9b6e4951eb82ddd8e60aa068e Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 26 Jan 2018 13:08:19 -0600 Subject: [PATCH 63/99] Fix tags add button --- app/assets/templates/tags.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml index ccab40f7e..ddb92a254 100644 --- a/app/assets/templates/tags.html.haml +++ b/app/assets/templates/tags.html.haml @@ -5,7 +5,7 @@ #tags-title-bar.section-title-bar .section-title-bar-header .title Tags - .add-button#tag-add-button{"ng-click" => "ctrl.createNewTag()"} + + .add-button#tag-add-button{"ng-click" => "ctrl.clickedAddNewTag()"} + .scrollable .tag{"ng-if" => "ctrl.allTag", "ng-click" => "ctrl.selectTag(ctrl.allTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.allTag}"} From 61843b14725577fb20b67b533a08131d6d9d071b Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 27 Jan 2018 09:40:48 -0600 Subject: [PATCH 64/99] Various components fixes --- .../javascripts/app/controllers/editor.js | 16 ++-- app/assets/javascripts/app/models/api/item.js | 8 ++ .../javascripts/app/models/app/component.js | 10 +++ .../app/services/componentManager.js | 77 +++++++++++-------- .../app/services/desktopManager.js | 10 ++- .../javascripts/app/services/modelManager.js | 3 +- .../javascripts/app/services/syncManager.js | 1 + app/assets/stylesheets/app/_editor.scss | 2 +- app/assets/templates/editor.html.haml | 2 +- app/assets/templates/tags.html.haml | 2 + 10 files changed, 85 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 70e2e6675..448c99d96 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -497,14 +497,18 @@ angular.module('app') } this.toggleStackComponentForCurrentItem = function(component) { - if(component.active) { + if(component.hidden) { + // Unhide, associate with current item + component.hidden = false; + if(!component.active) { + componentManager.activateComponent(component); + } + this.associateComponentWithCurrentNote(component); + componentManager.contextItemDidChangeInArea("editor-stack"); + } else { + // not hidden, hide component.hidden = true; this.disassociateComponentWithCurrentNote(component); - } else { - // Inactive - componentManager.activateComponent(component); - componentManager.contextItemDidChangeInArea("editor-stack"); - this.associateComponentWithCurrentNote(component); } } diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js index b05f94adb..7671b061e 100644 --- a/app/assets/javascripts/app/models/api/item.js +++ b/app/assets/javascripts/app/models/api/item.js @@ -50,9 +50,17 @@ class Item { if(json.content) { this.mapContentToLocalProperties(this.contentObject); + } else if(json.deleted == true) { + this.handleDeletedContent(); } } + + /* Allows the item to handle the case where the item is deleted and the content is null */ + handleDeletedContent() { + // Subclasses can override + } + setDirty(dirty) { this.dirty = dirty; diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index a94ff9748..18ca71daa 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -38,6 +38,10 @@ class Component extends Item { 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 @@ -50,6 +54,12 @@ class Component extends Item { this.associatedItemIds = content.associatedItemIds || []; } + handleDeletedContent() { + super.handleDeletedContent(); + + this.active = false; + } + structureParams() { var params = { url: this.url, diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 55072029e..d15e8b816 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -193,7 +193,8 @@ class ComponentManager { } sendMessageToComponent(component, message) { - if(component.hidden && message.action !== "component-registered") { + let permissibleActionsWhileHidden = ["component-registered", "themes"]; + if(component.hidden && !permissibleActionsWhileHidden.includes(message.action)) { if(this.loggingEnabled) { console.log("Component disabled for current item, not sending any messages.", component.name); } @@ -471,11 +472,15 @@ class ComponentManager { ]; this.runWithPermissions(component, requiredPermissions, () => { - var items = message.data.items; - var noun = items.length == 1 ? "item" : "items"; - if(confirm(`Are you sure you want to delete ${items.length} ${noun}?`)) { - for(var item of items) { - var model = this.modelManager.findItem(item.uuid); + var itemsData = message.data.items; + var noun = itemsData.length == 1 ? "item" : "items"; + if(confirm(`Are you sure you want to delete ${itemsData.length} ${noun}?`)) { + // Filter for any components and deactivate before deleting + for(var itemData of itemsData) { + var model = this.modelManager.findItem(itemData.uuid); + if(["SN|Component", "SN|Theme"].includes(model.content_type)) { + this.deactivateComponent(model, true); + } this.modelManager.setItemToBeDeleted(model); } @@ -598,10 +603,14 @@ class ComponentManager { return false; } - if(approved && pendingDialog.component == component) { + if(pendingDialog.component == component) { // remove pending dialogs that are encapsulated by already approved permissions, and run its function if(pendingDialog.permissions == permissions || permissions.containsObjectSubset(pendingDialog.permissions)) { - pendingDialog.actionBlock && pendingDialog.actionBlock(approved); + // If approved, run the action block. Otherwise, if canceled, cancel any pending ones as well, since the user was + // explicit in their intentions + if(approved) { + pendingDialog.actionBlock && pendingDialog.actionBlock(approved); + } return false; } } @@ -641,30 +650,6 @@ class ComponentManager { angular.element(document.body).append(el); } - activateComponent(component) { - var didChange = component.active != true; - - component.active = true; - for(var handler of this.handlers) { - if(handler.areas.includes(component.area) || handler.areas.includes("*")) { - handler.activationHandler(component); - } - } - - if(didChange) { - component.setDirty(true); - this.syncManager.sync("activateComponent"); - } - - if(!this.activeComponents.includes(component)) { - this.activeComponents.push(component); - } - - if(component.area == "themes") { - this.postThemeToAllComponents(); - } - } - registerHandler(handler) { this.handlers.push(handler); } @@ -699,7 +684,31 @@ class ComponentManager { this.postThemeToComponent(component); } - deactivateComponent(component) { + activateComponent(component, dontSync = false) { + var didChange = component.active != true; + + component.active = true; + for(var handler of this.handlers) { + if(handler.areas.includes(component.area) || handler.areas.includes("*")) { + handler.activationHandler(component); + } + } + + if(didChange && !dontSync) { + component.setDirty(true); + this.syncManager.sync("activateComponent"); + } + + if(!this.activeComponents.includes(component)) { + this.activeComponents.push(component); + } + + if(component.area == "themes") { + this.postThemeToAllComponents(); + } + } + + deactivateComponent(component, dontSync = false) { var didChange = component.active != false; component.active = false; component.sessionKey = null; @@ -710,7 +719,7 @@ class ComponentManager { } } - if(didChange) { + if(didChange && !dontSync) { component.setDirty(true); this.syncManager.sync("deactivateComponent"); } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 916d3a968..3d858bca4 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -51,12 +51,18 @@ class DesktopManager { desktop_onComponentInstallationComplete(componentData, error) { console.log("Web|Component Installation/Update Complete", componentData, error); + + // Desktop is only allowed to change these keys: + let permissableKeys = ["package_info", "local_url"]; var component = this.modelManager.findItem(componentData.uuid); + if(error) { - component = this.modelManager.findItem(componentData.uuid); component.setAppDataItem("installError", error); } else { - component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; + for(var key of permissableKeys) { + component[key] = componentData.content[key]; + } + this.modelManager.notifySyncObserversOfModels([component], ModelManager.MappingSourceDesktopInstalled); component.setAppDataItem("installError", null); } component.setDirty(true); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index c1982308d..c1e274e6c 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -119,8 +119,6 @@ class ModelManager { mapResponseItemsToLocalModelsOmittingFields(items, omitFields, source) { var models = [], processedObjects = [], modelsToNotifyObserversOf = []; - // console.log("mapResponseItemsToLocalModelsOmittingFields"); - // 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) { @@ -176,6 +174,7 @@ class ModelManager { return models; } + /* Note that this function is public, and can also be called manually (desktopManager uses it) */ notifySyncObserversOfModels(models, source) { for(var observer of this.itemSyncObservers) { var allRelevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"}); diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index dfb8dc12b..6f3643c44 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -291,6 +291,7 @@ class SyncManager { 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 = this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved); diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index cca66e8ee..9080ee9c4 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -117,9 +117,9 @@ $heading-height: 75px; width: 100%; .component-stack-item { - // height: 50px; width: 100%; position: relative; + &:not(:last-child) { border-bottom: 1px solid $bg-color; } diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index a32c17a72..0c4c5630d 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -53,4 +53,4 @@ %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. #editor-pane-component-stack - %component-view.component-view.component-stack-item{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} + %component-view.component-view.component-stack-item.border-color{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml index ddb92a254..f99e9bfba 100644 --- a/app/assets/templates/tags.html.haml +++ b/app/assets/templates/tags.html.haml @@ -1,6 +1,8 @@ .section.tags#tags-column + .component-view-container{"ng-if" => "ctrl.component.active"} %component-view.component-view{"component" => "ctrl.component"} + #tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"} #tags-title-bar.section-title-bar .section-title-bar-header From 5c6edfbc74d77c849362d2b7a13a295c9dd09633 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 27 Jan 2018 09:53:59 -0600 Subject: [PATCH 65/99] Editor stack reload --- app/assets/javascripts/app/controllers/editor.js | 5 +++-- app/assets/stylesheets/app/_editor.scss | 10 +--------- app/assets/templates/editor.html.haml | 3 ++- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 448c99d96..02b5143ca 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -420,7 +420,6 @@ angular.module('app') */ componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack", "editor-editor"], activationHandler: function(component){ - if(component.area === "note-tags") { // Autocomplete Tags this.tagsComponent = component.active ? component : null; @@ -431,6 +430,8 @@ angular.module('app') } else { this.selectedEditor = null; } + } else if(component.area == "editor-stack") { + this.reloadComponentContext(); } }.bind(this), contextRequestHandler: function(component){ return this.note; @@ -487,7 +488,7 @@ angular.module('app') this.componentStack = componentManager.componentsForArea("editor-stack"); for(var component of this.componentStack) { if(component.active) { - component.hidden = component.isExplicitlyDisabledForItem(this.note); + component.hidden = !this.note || component.isExplicitlyDisabledForItem(this.note); } } diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index 9080ee9c4..a1afdd7bb 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -119,15 +119,7 @@ $heading-height: 75px; .component-stack-item { width: 100%; position: relative; - - &:not(:last-child) { - border-bottom: 1px solid $bg-color; - } - - &:first-child { - border-top: 1px solid $bg-color; - } - + border-top: 1px solid $bg-color; iframe { width: 100%; } diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 0c4c5630d..e3bdd5a5d 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -53,4 +53,5 @@ %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. #editor-pane-component-stack - %component-view.component-view.component-stack-item.border-color{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} + %component-view.component-view.component-stack-item.border-color{"ng-repeat" => "component in ctrl.componentStack", + "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} From 528d9f4e64af79d0d6f09e93642d2bfadeed8550 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 27 Jan 2018 10:41:12 -0600 Subject: [PATCH 66/99] Updates --- app/assets/templates/editor.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index e3bdd5a5d..924030f23 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -53,5 +53,6 @@ %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. #editor-pane-component-stack - %component-view.component-view.component-stack-item.border-color{"ng-repeat" => "component in ctrl.componentStack", - "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} + .sn-component + %component-view.component-view.component-stack-item.border-color{"ng-repeat" => "component in ctrl.componentStack", + "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} From 1bfc44eb2dea1434fa65a0eb4321bee65684e69a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 28 Jan 2018 09:35:32 -0600 Subject: [PATCH 67/99] Reload component on update --- .../app/directives/views/componentView.js | 23 ++++++++++++++++--- .../javascripts/app/models/app/component.js | 6 ++++- .../app/services/componentManager.js | 23 +++++++++++++------ .../app/services/desktopManager.js | 20 +++++++++++++++- .../javascripts/app/services/themeManager.js | 12 +++++++++- 5 files changed, 71 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index ecfa5ba14..5c92ffac6 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -1,6 +1,6 @@ class ComponentView { - constructor(componentManager, $timeout) { + constructor(componentManager, desktopManager, $timeout) { this.restrict = "E"; this.templateUrl = "directives/component-view.html"; this.scope = { @@ -9,6 +9,7 @@ class ComponentView { }; this.componentManager = componentManager; + this.desktopManager = desktopManager; this.timeout = $timeout; } @@ -37,6 +38,12 @@ class ComponentView { } }}); + $scope.updateObserver = this.desktopManager.registerUpdateObserver((component) => { + if(component == $scope.component && component.active) { + $scope.reloadComponent(); + } + }) + $scope.$watch('component', function(component, prevComponent){ ctrl.componentValueChanging(component, prevComponent); }); @@ -53,12 +60,20 @@ class ComponentView { if(component) { componentManager.activateComponent(component); - console.log("Loading", $scope.component.name, $scope.getUrl(), component.valid_until); + console.log("Loading", $scope.component.name, $scope.getUrl(), component.valid_until, $scope.component); $scope.reloadStatus(); } } + $scope.reloadComponent = function() { + console.log("Reloading component", $scope.component); + componentManager.deactivateComponent($scope.component); + $timeout(() => { + componentManager.activateComponent($scope.component); + }) + } + $scope.reloadStatus = function() { let component = $scope.component; $scope.reloading = true; @@ -99,9 +114,11 @@ class ComponentView { if($scope.component && !$scope.manualDealloc) { componentManager.deactivateComponent($scope.component); } + + desktopManager.deregisterUpdateObserver($scope.updateObserver); }); } } -angular.module('app').directive('componentView', (componentManager, $timeout) => new ComponentView(componentManager, $timeout)); +angular.module('app').directive('componentView', (componentManager, desktopManager, $timeout) => new ComponentView(componentManager, desktopManager, $timeout)); diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index 18ca71daa..8ae195e1e 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -56,7 +56,7 @@ class Component extends Item { handleDeletedContent() { super.handleDeletedContent(); - + this.active = false; } @@ -94,6 +94,10 @@ class Component extends Item { return this.area == "editor-editor"; } + isTheme() { + return this.content_type == "SN|Theme" || this.area == "themes"; + } + isDefaultEditor() { return this.getAppDataItem("defaultEditor") == true; } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index d15e8b816..f34a916c7 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -15,6 +15,13 @@ class ComponentManager { this.contextStreamObservers = []; this.activeComponents = []; + desktopManager.registerUpdateObserver((component) => { + // Reload theme if active + if(component.active && component.isTheme()) { + this.postActiveThemeToAllComponents(); + } + }) + // this.loggingEnabled = true; this.permissionDialogs = []; @@ -107,12 +114,14 @@ class ComponentManager { }); } - postThemeToAllComponents() { + postActiveThemeToAllComponents() { for(var component of this.components) { - if(component.area == "themes" || !component.active || !component.window) { + // Skip over components that are themes themselves, + // or components that are not active, or components that don't have a window + if(component.isTheme() || !component.active || !component.window) { continue; } - this.postThemeToComponent(component); + this.postActiveThemeToComponent(component); } } @@ -120,7 +129,7 @@ class ComponentManager { return this.componentsForArea("themes").find((theme) => {return theme.active}); } - postThemeToComponent(component) { + postActiveThemeToComponent(component) { var activeTheme = this.getActiveTheme(); var data = { themes: [activeTheme ? this.urlForComponent(activeTheme) : null] @@ -681,7 +690,7 @@ class ComponentManager { environment: isDesktopApplication() ? "desktop" : "web" } }); - this.postThemeToComponent(component); + this.postActiveThemeToComponent(component); } activateComponent(component, dontSync = false) { @@ -704,7 +713,7 @@ class ComponentManager { } if(component.area == "themes") { - this.postThemeToAllComponents(); + this.postActiveThemeToAllComponents(); } } @@ -735,7 +744,7 @@ class ComponentManager { }) if(component.area == "themes") { - this.postThemeToAllComponents(); + this.postActiveThemeToAllComponents(); } } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 3d858bca4..145124fbd 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -2,12 +2,14 @@ class DesktopManager { - constructor($rootScope, modelManager, syncManager, authManager, passcodeManager) { + constructor($rootScope, $timeout, modelManager, syncManager, authManager, passcodeManager) { this.passcodeManager = passcodeManager; this.modelManager = modelManager; this.authManager = authManager; this.syncManager = syncManager; this.$rootScope = $rootScope; + this.timeout = $timeout; + this.updateObservers = []; this.isDesktop = isDesktopApplication(); @@ -49,6 +51,16 @@ class DesktopManager { this.installComponentHandler(this.convertComponentForTransmission(component)); } + registerUpdateObserver(callback) { + var observer = {id: Math.random, callback: callback}; + this.updateObservers.push(observer); + return observer; + } + + deregisterUpdateObserver(observer) { + _.pull(this.updateObservers, observer); + } + desktop_onComponentInstallationComplete(componentData, error) { console.log("Web|Component Installation/Update Complete", componentData, error); @@ -67,6 +79,12 @@ class DesktopManager { } component.setDirty(true); this.syncManager.sync("onComponentInstallationComplete"); + + this.timeout(() => { + for(var observer of this.updateObservers) { + observer.callback(component); + } + }) } /* Used to resolve "sn://" */ diff --git a/app/assets/javascripts/app/services/themeManager.js b/app/assets/javascripts/app/services/themeManager.js index 74dad17ff..deda2e089 100644 --- a/app/assets/javascripts/app/services/themeManager.js +++ b/app/assets/javascripts/app/services/themeManager.js @@ -1,8 +1,18 @@ class ThemeManager { - constructor(componentManager) { + constructor(componentManager, desktopManager) { this.componentManager = componentManager; + desktopManager.registerUpdateObserver((component) => { + // Reload theme if active + if(component.active && component.isTheme()) { + this.deactivateTheme(component); + setTimeout(() => { + this.activateTheme(component); + }, 10); + } + }) + componentManager.registerHandler({identifier: "themeManager", areas: ["themes"], activationHandler: (component) => { if(component.active) { this.activateTheme(component); From 6ca31575ac4e824d9441eadeb4e1bc3b0746355c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 28 Jan 2018 09:39:29 -0600 Subject: [PATCH 68/99] Autoupdate label --- app/assets/javascripts/app/controllers/footer.js | 2 +- app/assets/templates/footer.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 8124e4ebe..0f71564cd 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -95,7 +95,7 @@ angular.module('app') this.clickedNewUpdateAnnouncement = function() { this.newUpdateAvailable = false; - alert("A new update is ready to install. Updates address performance and security issues, as well as bug fixes and feature enhancements. Simply quit Standard Notes and re-open it for the update to be applied.") + alert("A new update is ready to install. Simply quit Standard Notes and reopen it to apply the update.") } diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index 1f5e7243a..07c4de532 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -23,7 +23,7 @@ .right .item{"ng-if" => "ctrl.newUpdateAvailable", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"} - %span.info.normal New update downloaded. Installs on app restart. + %span.info.label New update downloaded. Installs on app restart. .item.no-pointer{"ng-if" => "ctrl.lastSyncDate && !ctrl.isRefreshing"} .label.subtle From 6fdeeae63fd9b197a937b051ffcf086de5e00a2c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 29 Jan 2018 11:51:36 -0600 Subject: [PATCH 69/99] 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 d0f467cf4..1af12dc1a 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit d0f467cf4424f11d4c5972284fe6af66a862e012 +Subproject commit 1af12dc1abf77d6be1bc61681ef8e2452e436a08 From f072a1cf8b94da1d1b8f28847019873c3774af0f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 29 Jan 2018 11:53:16 -0600 Subject: [PATCH 70/99] Submodules --- config/deploy.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/deploy.rb b/config/deploy.rb index 0397e5190..3fc596623 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -46,6 +46,7 @@ namespace :deploy do # string commands dont work, have to use special *%w syntax execute *%w[ npm install ] execute *%w[ grunt ] + execute *%w[ git submodule update --init --force --remote ] end end end From b5c6f2adf3cd200d09b00a85c1de953f6d54e5f4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 29 Jan 2018 11:55:52 -0600 Subject: [PATCH 71/99] Submodules --- config/deploy.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/deploy.rb b/config/deploy.rb index 3fc596623..effd10286 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -46,6 +46,9 @@ namespace :deploy do # string commands dont work, have to use special *%w syntax execute *%w[ npm install ] execute *%w[ grunt ] + end + + within repo_path do execute *%w[ git submodule update --init --force --remote ] end end From eba3457c88ef8b4e0fb1d8dde976c6fa4bb39676 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 29 Jan 2018 11:57:55 -0600 Subject: [PATCH 72/99] Deploy script --- config/deploy.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/config/deploy.rb b/config/deploy.rb index effd10286..f844ae26f 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -42,15 +42,17 @@ namespace :deploy do task :npm_install do on roles(:app) do + with fetch(:git_environmental_variables) do + within repo_path do + execute *%w[ git submodule update --init --force --remote ] + end + end + within release_path do # string commands dont work, have to use special *%w syntax execute *%w[ npm install ] execute *%w[ grunt ] end - - within repo_path do - execute *%w[ git submodule update --init --force --remote ] - end end end From 21ebd4bbcb005ab2b86a9004fd7683b49be8a300 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 29 Jan 2018 12:04:49 -0600 Subject: [PATCH 73/99] Deploy script --- Capfile | 6 +++++- Gemfile | 2 +- Gemfile.lock | 23 +++++++++++------------ config/deploy.rb | 7 ------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Capfile b/Capfile index 08de6d646..c9fcd2238 100644 --- a/Capfile +++ b/Capfile @@ -23,9 +23,13 @@ require 'capistrano/rails/assets' # require 'capistrano/rails/migrations' require 'capistrano/passenger' # require 'capistrano/sidekiq' -require 'capistrano/git-submodule-strategy' # require "whenever/capistrano" # Update crontab on deploy +require "capistrano/scm/git" +install_plugin Capistrano::SCM::Git +require "capistrano/scm/git-with-submodules" +install_plugin Capistrano::SCM::Git::WithSubmodules + # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } diff --git a/Gemfile b/Gemfile index 753576444..906d29d4c 100644 --- a/Gemfile +++ b/Gemfile @@ -44,5 +44,5 @@ group :development, :test do gem 'capistrano-rails' gem 'capistrano-rvm' gem 'capistrano-sidekiq' - gem 'capistrano-git-submodule-strategy', '~> 0.1.22' + gem 'capistrano-git-with-submodules', '~> 2.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 7c79bb6c2..c13f0eaf6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,7 +38,7 @@ GEM i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - airbrussh (1.1.1) + airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) arel (7.1.4) binding_of_caller (0.7.2) @@ -46,18 +46,16 @@ GEM bower-rails (0.10.0) builder (3.2.2) byebug (9.0.6) - capistrano (3.6.1) + capistrano (3.10.1) airbrussh (>= 1.0.0) - capistrano-harrow i18n rake (>= 10.0.0) sshkit (>= 1.9.0) capistrano-bundler (1.2.0) capistrano (~> 3.1) sshkit (~> 1.2) - capistrano-git-submodule-strategy (0.1.22) - capistrano (~> 3.1) - capistrano-harrow (0.5.3) + capistrano-git-with-submodules (2.0.3) + capistrano (~> 3.7) capistrano-passenger (0.2.0) capistrano (~> 3.0) capistrano-rails (1.2.0) @@ -82,7 +80,8 @@ GEM activesupport (>= 4.1.0) haml (4.0.7) tilt - i18n (0.7.0) + i18n (0.9.3) + concurrent-ruby (~> 1.0) json (1.8.3) loofah (2.0.3) nokogiri (>= 1.5.9) @@ -96,7 +95,7 @@ GEM minitest (5.9.1) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (3.2.0) + net-ssh (4.2.0) nio4r (1.2.1) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) @@ -132,7 +131,7 @@ GEM method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (11.3.0) + rake (12.3.0) rdoc (4.3.0) redis (3.3.2) responders (2.3.0) @@ -157,7 +156,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.11.4) + sshkit (1.15.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) thor (0.19.4) @@ -185,7 +184,7 @@ DEPENDENCIES byebug capistrano capistrano-bundler - capistrano-git-submodule-strategy (~> 0.1.22) + capistrano-git-with-submodules (~> 2.0) capistrano-passenger (>= 0.2.0) capistrano-rails capistrano-rvm @@ -205,4 +204,4 @@ DEPENDENCIES web-console (~> 2.0) BUNDLED WITH - 1.15.1 + 1.15.3 diff --git a/config/deploy.rb b/config/deploy.rb index f844ae26f..2c50e5b8a 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -13,7 +13,6 @@ set :repo_url, CAP_CONFIG["default"]["repo_url"] # Default value for :scm is :git set :scm, :git -set :git_strategy, Capistrano::Git::SubmoduleStrategy # Default value for :format is :airbrussh. # set :format, :airbrussh @@ -42,12 +41,6 @@ namespace :deploy do task :npm_install do on roles(:app) do - with fetch(:git_environmental_variables) do - within repo_path do - execute *%w[ git submodule update --init --force --remote ] - end - end - within release_path do # string commands dont work, have to use special *%w syntax execute *%w[ npm install ] From 2445f6789e463bcb7a73a456bae890a3e507f7ee Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 29 Jan 2018 12:14:13 -0600 Subject: [PATCH 74/99] Deploy --- Capfile | 9 ++-- Gemfile.lock | 121 ++++++++++++++++++++++++++--------------------- config/deploy.rb | 5 -- 3 files changed, 70 insertions(+), 65 deletions(-) diff --git a/Capfile b/Capfile index c9fcd2238..8bc169d9e 100644 --- a/Capfile +++ b/Capfile @@ -3,6 +3,10 @@ require "capistrano/setup" # Include default deployment tasks require "capistrano/deploy" +require "capistrano/scm/git" +install_plugin Capistrano::SCM::Git +require "capistrano/scm/git-with-submodules" +install_plugin Capistrano::SCM::Git::WithSubmodules # Include tasks from other gems included in your Gemfile # @@ -25,11 +29,6 @@ require 'capistrano/passenger' # require 'capistrano/sidekiq' # require "whenever/capistrano" # Update crontab on deploy -require "capistrano/scm/git" -install_plugin Capistrano::SCM::Git -require "capistrano/scm/git-with-submodules" -install_plugin Capistrano::SCM::Git::WithSubmodules - # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } diff --git a/Gemfile.lock b/Gemfile.lock index c13f0eaf6..d70ec701e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,70 +41,72 @@ GEM airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) arel (7.1.4) - binding_of_caller (0.7.2) + binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) bower-rails (0.10.0) - builder (3.2.2) - byebug (9.0.6) + builder (3.2.3) + byebug (10.0.0) capistrano (3.10.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.2.0) + capistrano-bundler (1.3.0) capistrano (~> 3.1) sshkit (~> 1.2) capistrano-git-with-submodules (2.0.3) capistrano (~> 3.7) capistrano-passenger (0.2.0) capistrano (~> 3.0) - capistrano-rails (1.2.0) + capistrano-rails (1.3.1) capistrano (~> 3.1) capistrano-bundler (~> 1.1) capistrano-rvm (0.1.2) capistrano (~> 3.0) sshkit (~> 1.2) - capistrano-sidekiq (0.10.0) - capistrano + capistrano-sidekiq (1.0.0) + capistrano (>= 3.9.0) sidekiq (>= 3.4) - concurrent-ruby (1.0.2) + concurrent-ruby (1.0.5) connection_pool (2.2.1) - debug_inspector (0.0.2) - dotenv (2.1.1) - dotenv-rails (2.1.1) - dotenv (= 2.1.1) - railties (>= 4.0, < 5.1) + crass (1.0.3) + debug_inspector (0.0.3) + dotenv (2.1.2) + dotenv-rails (2.1.2) + dotenv (= 2.1.2) + railties (>= 3.2, < 5.1) erubis (2.7.0) execjs (2.7.0) - globalid (0.3.7) - activesupport (>= 4.1.0) - haml (4.0.7) + ffi (1.9.18) + globalid (0.4.1) + activesupport (>= 4.2.0) + haml (5.0.4) + temple (>= 0.8.0) tilt i18n (0.9.3) concurrent-ruby (~> 1.0) - json (1.8.3) - loofah (2.0.3) + json (1.8.6) + loofah (2.1.1) + crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.6.4) - mime-types (>= 1.16, < 4) - method_source (0.8.2) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_portile2 (2.1.0) - minitest (5.9.1) + mail (2.7.0) + mini_mime (>= 0.1.1) + method_source (0.9.0) + mini_mime (1.0.0) + mini_portile2 (2.3.0) + minitest (5.11.3) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (4.2.0) nio4r (1.2.1) - nokogiri (1.6.8.1) - mini_portile2 (~> 2.1.0) + nokogiri (1.8.2) + mini_portile2 (~> 2.3.0) non-stupid-digest-assets (1.0.9) sprockets (>= 2.0) - puma (3.6.2) - rack (2.0.1) - rack-cors (1.0.0) - rack-protection (1.5.3) + puma (3.11.2) + rack (2.0.3) + rack-cors (1.0.2) + rack-protection (2.0.0) rack rack-test (0.6.3) rack (>= 1.0) @@ -120,9 +122,9 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 5.0.0.1) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.1) - activesupport (>= 4.2.0, < 6.0) - nokogiri (~> 1.6.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) railties (5.0.0.1) @@ -132,49 +134,58 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (12.3.0) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) rdoc (4.3.0) - redis (3.3.2) - responders (2.3.0) - railties (>= 4.2.0, < 5.1) - sass (3.4.22) + redis (4.0.1) + responders (2.4.0) + actionpack (>= 4.2.0, < 5.3) + railties (>= 4.2.0, < 5.3) + sass (3.5.5) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) sdoc (0.4.2) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - secure_headers (3.6.7) - useragent - sidekiq (4.2.7) + secure_headers (5.0.4) + useragent (>= 0.15.0) + sidekiq (5.0.5) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (~> 3.2, >= 3.2.1) - spring (2.0.0) + redis (>= 3.3.4, < 5) + spring (2.0.2) activesupport (>= 4.2) - sprockets (3.7.0) + sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.0) + sprockets-rails (3.2.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) sshkit (1.15.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - thor (0.19.4) - thread_safe (0.3.5) - tilt (2.0.5) - tzinfo (1.2.2) + temple (0.8.0) + thor (0.20.0) + thread_safe (0.3.6) + tilt (2.0.8) + tzinfo (1.2.4) thread_safe (~> 0.1) - uglifier (3.0.3) + uglifier (4.1.5) execjs (>= 0.3.0, < 3) - useragent (0.16.8) + useragent (0.16.9) web-console (2.3.0) activemodel (>= 4.0) binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) - websocket-driver (0.6.4) + websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.2) + websocket-extensions (0.1.3) PLATFORMS ruby @@ -204,4 +215,4 @@ DEPENDENCIES web-console (~> 2.0) BUNDLED WITH - 1.15.3 + 1.16.1 diff --git a/config/deploy.rb b/config/deploy.rb index 2c50e5b8a..572e17333 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,6 +1,4 @@ CAP_CONFIG = YAML.load_file("config/cap.yml") -# config valid only for current version of Capistrano -lock '3.6.1' set :application, 'neeto' set :repo_url, CAP_CONFIG["default"]["repo_url"] @@ -11,9 +9,6 @@ set :repo_url, CAP_CONFIG["default"]["repo_url"] # Default deploy_to directory is /var/www/my_app_name # set :deploy_to, '/var/www/my_app_name' -# Default value for :scm is :git -set :scm, :git - # Default value for :format is :airbrussh. # set :format, :airbrussh From 3d97eb181602b7a9f2a0e38e6996e85a7b224ce6 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 29 Jan 2018 12:26:50 -0600 Subject: [PATCH 75/99] Remove testing code --- Capfile | 2 ++ app/assets/javascripts/app/controllers/footer.js | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Capfile b/Capfile index 8bc169d9e..532cfeef5 100644 --- a/Capfile +++ b/Capfile @@ -3,8 +3,10 @@ require "capistrano/setup" # Include default deployment tasks require "capistrano/deploy" + require "capistrano/scm/git" install_plugin Capistrano::SCM::Git + require "capistrano/scm/git-with-submodules" install_plugin Capistrano::SCM::Git::WithSubmodules diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 0f71564cd..31165dc25 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -107,10 +107,6 @@ angular.module('app') 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}); - - for(var room of this.rooms) { - room.hosted_url = "http://localhost:8080"; - } }); componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => { From 4281ed730c3280104ce13329daf16acccdfcb074 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 30 Jan 2018 10:33:28 -0600 Subject: [PATCH 76/99] Add nginject to permissions modal --- .../javascripts/app/directives/views/permissionsModal.js | 1 + app/assets/javascripts/app/models/app/component.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/permissionsModal.js b/app/assets/javascripts/app/directives/views/permissionsModal.js index 0e1764910..a04ad0d8a 100644 --- a/app/assets/javascripts/app/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/directives/views/permissionsModal.js @@ -29,6 +29,7 @@ class PermissionsModal { } controller($scope, modelManager) { + 'ngInject'; $scope.permissionsString = function() { var finalString = ""; diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index 8ae195e1e..92fae1ba6 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -19,10 +19,10 @@ class Component extends Item { mapContentToLocalProperties(content) { super.mapContentToLocalProperties(content) /* Legacy */ - this.url = content.url; + this.url = content.url || content.hosted_url; /* New */ this.local_url = content.local_url; - this.hosted_url = content.hosted_url; + this.hosted_url = content.hosted_url || content.url; this.offlineOnly = content.offlineOnly; if(content.valid_until) { From e20c1224d2f7ed3d056dd6f03e85d3ff118d29c9 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 31 Jan 2018 10:40:26 -0600 Subject: [PATCH 77/99] Focus handlers --- .../javascripts/app/controllers/editor.js | 18 +++++++++++---- .../javascripts/app/controllers/footer.js | 10 +++++++++ .../app/services/componentManager.js | 22 +++++++++++++++++++ .../app/services/desktopManager.js | 5 +++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 02b5143ca..55f54fbcb 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -110,6 +110,12 @@ angular.module('app') this.showExtensions = false; } + this.closeAllMenus = function() { + this.showEditorMenu = false; + this.showMenu = false; + this.showExtensions = false; + } + this.editorMenuOnSelect = function(component) { if(!component || component.area == "editor-editor") { // if plain editor or other editor @@ -419,7 +425,7 @@ angular.module('app') Components */ - componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack", "editor-editor"], activationHandler: function(component){ + componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack", "editor-editor"], activationHandler: (component) => { if(component.area === "note-tags") { // Autocomplete Tags this.tagsComponent = component.active ? component : null; @@ -433,9 +439,13 @@ angular.module('app') } else if(component.area == "editor-stack") { this.reloadComponentContext(); } - }.bind(this), contextRequestHandler: function(component){ + }, contextRequestHandler: (component) => { return this.note; - }.bind(this), actionHandler: function(component, action, data){ + }, focusHandler: (component, focused) => { + if(component.isEditor() && focused) { + this.closeAllMenus(); + } + }, actionHandler: (component, action, data) => { if(action === "set-size") { var setSize = function(element, size) { var widthString = typeof size.width === 'string' ? size.width : `${data.width}px`; @@ -481,7 +491,7 @@ angular.module('app') } } } - }.bind(this)}); + }}); this.reloadComponentContext = function() { // componentStack is used by the template to ng-repeat diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 31165dc25..ed15118ce 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -126,8 +126,18 @@ angular.module('app') if(action == "set-size") { component.setLastSize(data); } + }, focusHandler: (component, focused) => { + if(component.isEditor() && focused) { + this.closeAllRooms(); + this.closeAccountMenu(); + } }}); + $rootScope.$on("editorFocused", () => { + this.closeAllRooms(); + this.closeAccountMenu(); + }) + this.onRoomDismiss = function(room) { room.showRoom = false; } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index f34a916c7..effc98f5b 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -15,6 +15,20 @@ class ComponentManager { this.contextStreamObservers = []; this.activeComponents = []; + const detectFocusChange = (event) => { + for(var component of this.activeComponents) { + if(document.activeElement == this.iframeForComponent(component)) { + this.timeout(() => { + this.focusChangedForComponent(component); + }) + break; + } + } + } + + window.addEventListener ? window.addEventListener('focus', detectFocusChange, true) : window.attachEvent('onfocusout', detectFocusChange); + window.addEventListener ? window.addEventListener('blur', detectFocusChange, true) : window.attachEvent('onblur', detectFocusChange); + desktopManager.registerUpdateObserver((component) => { // Reload theme if active if(component.active && component.isTheme()) { @@ -766,6 +780,14 @@ class ComponentManager { } } + focusChangedForComponent(component) { + let focused = document.activeElement == this.iframeForComponent(component); + for(var handler of this.handlers) { + // Notify all handlers, and not just ones that match this component type + handler.focusHandler && handler.focusHandler(component, focused); + } + } + handleSetSizeEvent(component, data) { var setSize = function(element, size) { var widthString = typeof size.width === 'string' ? size.width : `${data.width}px`; diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 145124fbd..3044e6da2 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -68,6 +68,11 @@ class DesktopManager { let permissableKeys = ["package_info", "local_url"]; var component = this.modelManager.findItem(componentData.uuid); + if(!component) { + console.error("desktop_onComponentInstallationComplete component is null for uuid", componentData.uuid); + return; + } + if(error) { component.setAppDataItem("installError", error); } else { From d2ac94a2956d5e39a9755fa9ef190bb2aecb0e84 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 1 Feb 2018 10:33:51 -0600 Subject: [PATCH 78/99] sysExtManager -> nativeExtMangaer --- .../javascripts/app/directives/views/componentView.js | 2 +- .../javascripts/app/services/componentManager.js | 10 +++++----- .../services/{sysExtManager.js => nativeExtManager.js} | 4 ++-- package-lock.json | 6 +++--- package.json | 2 +- public/extensions/extensions-manager | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) rename app/assets/javascripts/app/services/{sysExtManager.js => nativeExtManager.js} (96%) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 5c92ffac6..045fad945 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -60,7 +60,7 @@ class ComponentView { if(component) { componentManager.activateComponent(component); - console.log("Loading", $scope.component.name, $scope.getUrl(), component.valid_until, $scope.component); + console.log("Loading", $scope.component.name, $scope.getUrl(), component.valid_until); $scope.reloadStatus(); } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index effc98f5b..cf4b58a4c 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -3,13 +3,13 @@ let ClientDataDomain = "org.standardnotes.sn.components"; class ComponentManager { - constructor($rootScope, modelManager, syncManager, desktopManager, sysExtManager, $timeout, $compile) { + constructor($rootScope, modelManager, syncManager, desktopManager, nativeExtManager, $timeout, $compile) { this.$compile = $compile; this.$rootScope = $rootScope; this.modelManager = modelManager; this.syncManager = syncManager; this.desktopManager = desktopManager; - this.sysExtManager = sysExtManager; + this.nativeExtManager = nativeExtManager; this.timeout = $timeout; this.streamObservers = []; this.contextStreamObservers = []; @@ -244,7 +244,7 @@ class ComponentManager { if(component.offlineOnly || (isDesktopApplication() && component.local_url)) { return component.local_url && component.local_url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); } else { - return component.url || component.hosted_url; + return component.hosted_url || component.url; } } @@ -319,7 +319,7 @@ class ComponentManager { removePrivatePropertiesFromResponseItems(responseItems, component, options = {}) { if(component) { // System extensions can bypass this step - if(this.sysExtManager.isSystemExtension(component)) { + if(this.nativeExtManager.isSystemExtension(component)) { return; } } @@ -548,7 +548,7 @@ class ComponentManager { handleInstallLocalComponentMessage(sourceComponent, message) { // Only extensions manager has this permission - if(!this.sysExtManager.isSystemExtension(sourceComponent)) { + if(!this.nativeExtManager.isSystemExtension(sourceComponent)) { return; } diff --git a/app/assets/javascripts/app/services/sysExtManager.js b/app/assets/javascripts/app/services/nativeExtManager.js similarity index 96% rename from app/assets/javascripts/app/services/sysExtManager.js rename to app/assets/javascripts/app/services/nativeExtManager.js index c822a9f2d..503e3f3b3 100644 --- a/app/assets/javascripts/app/services/sysExtManager.js +++ b/app/assets/javascripts/app/services/nativeExtManager.js @@ -1,6 +1,6 @@ /* A class for handling installation of system extensions */ -class SysExtManager { +class NativeExtManager { constructor(modelManager, syncManager, singletonManager) { this.modelManager = modelManager; @@ -89,4 +89,4 @@ class SysExtManager { } -angular.module('app').service('sysExtManager', SysExtManager); +angular.module('app').service('nativeExtManager', NativeExtManager); diff --git a/package-lock.json b/package-lock.json index 69b9a3d84..765a76d8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5781,9 +5781,9 @@ "dev": true }, "sn-stylekit": { - "version": "1.0.1195", - "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1195.tgz", - "integrity": "sha512-XPutiDkA0Jbf3fGHjC6avoWbMsLJKLOXnPJDpE2VFA2/HUf87sjyTvvhg8mrRT2IiE8Cr0gJFQr/ALnoP+u1EQ==", + "version": "1.0.1196", + "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1196.tgz", + "integrity": "sha512-/6P0m4A+TEwJ6VxstiEzCCfBheWz5c8Y/B209cItqLX+9ltfg4yEUaTHG/Q/TMKPID8or4CiiMLzj+6SwuMqGQ==", "dev": true }, "snake-case": { diff --git a/package.json b/package.json index 12f5b412f..13d5a11f4 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "karma-cli": "^1.0.1", "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", - "sn-stylekit": "^1.0.1195" + "sn-stylekit": "^1.0.1196" }, "license": "GPL-3.0" } diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager index 1af12dc1a..aed8ee9e8 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 1af12dc1abf77d6be1bc61681ef8e2452e436a08 +Subproject commit aed8ee9e89b62ac1bca9af0f429d6731f3c8f8d9 From 805fc48da482caf316cd84d07f22e586c1893fb4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Thu, 1 Feb 2018 11:53:12 -0600 Subject: [PATCH 79/99] Account menu copy --- app/assets/templates/directives/account-menu.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/templates/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml index 5b13d4017..9ddb39cfc 100644 --- a/app/assets/templates/directives/account-menu.html.haml +++ b/app/assets/templates/directives/account-menu.html.haml @@ -19,7 +19,7 @@ .panel-section{"ng-if" => "formData.showLogin || formData.showRegister"} %h3.title.panel-row - {{formData.showLogin ? "Sign In" : "Register (free)"}} + {{formData.showLogin ? "Sign In" : "Register"}} %form.panel-form{"ng-submit" => "submitAuthForm()"} %input{:placeholder => 'Email', "sn-autofocus" => 'true', "should-focus" => "true", :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email'} From 84d68e8f68bfd6f8789c9944293b1d7920735848 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 4 Feb 2018 11:24:48 -0600 Subject: [PATCH 80/99] Component view error handling, extensions-manager submodule --- .../app/directives/views/componentView.js | 12 +++++++++--- .../templates/directives/component-view.html.haml | 4 ++++ public/extensions/extensions-manager | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index 045fad945..dd67eadee 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -82,14 +82,20 @@ class ComponentView { var expired, offlineRestricted, urlError; offlineRestricted = component.offlineOnly && !isDesktopApplication(); - urlError = !isDesktopApplication() && (!component.url && !component.hosted_url); + + urlError = + (!isDesktopApplication() && (!component.url && !component.hosted_url)) + || + (isDesktopApplication() && (!component.local_url && !component.url && !component.hosted_url)) + expired = component.valid_until && component.valid_until <= new Date(); $scope.componentValid = !offlineRestricted && !urlError && !expired; if(offlineRestricted) $scope.error = 'offline-restricted'; - if(urlError) $scope.error = 'url-missing'; - if(expired) $scope.error = 'expired'; + else if(urlError) $scope.error = 'url-missing'; + else if(expired) $scope.error = 'expired'; + else $scope.error = null; if($scope.componentValid !== previouslyValid) { if($scope.componentValid) { diff --git a/app/assets/templates/directives/component-view.html.haml b/app/assets/templates/directives/component-view.html.haml index b7aa03eb5..e507ea060 100644 --- a/app/assets/templates/directives/component-view.html.haml +++ b/app/assets/templates/directives/component-view.html.haml @@ -57,6 +57,10 @@ %h2.title This extension is not installed correctly. %p Please uninstall {{component.name}}, then re-install it. + %p + This issue can occur if you access Standard Notes using an older version of the app. + Ensure you are running at least version 2.1 on all platforms. + %iframe{"ng-if" => "component && componentValid", "ng-attr-id" => "component-{{component.uuid}}", diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager index aed8ee9e8..76886d2fe 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit aed8ee9e89b62ac1bca9af0f429d6731f3c8f8d9 +Subproject commit 76886d2fec409cacbee0baaba954b676d041c39d From 3bc7541add390bbb5a2af52eb205a0f79c163c2a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 4 Feb 2018 11:34:42 -0600 Subject: [PATCH 81/99] 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 76886d2fe..134c0501d 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 76886d2fec409cacbee0baaba954b676d041c39d +Subproject commit 134c0501d0345ad20426b0a043872c20dd25a8c6 From e1a6da9176035edd490db0d870100c4d33d955d6 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 4 Feb 2018 11:45:17 -0600 Subject: [PATCH 82/99] 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 134c0501d..c5706ed93 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 134c0501d0345ad20426b0a043872c20dd25a8c6 +Subproject commit c5706ed93eae1343efea505cfd077810553e5837 From 92fb684e0ef32325bb517695c36e02e7a7856fc2 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 09:11:28 -0600 Subject: [PATCH 83/99] Extension mapping, omit action subrows and subactions --- app/assets/javascripts/app/models/app/extension.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/models/app/extension.js b/app/assets/javascripts/app/models/app/extension.js index 2a5111062..f990432a5 100644 --- a/app/assets/javascripts/app/models/app/extension.js +++ b/app/assets/javascripts/app/models/app/extension.js @@ -50,7 +50,7 @@ class Extension extends Component { structureParams() { var params = { description: this.description, - actions: this.actions, + actions: this.actions.map((a) => {return _.omit(a, ["subrows", "subactions"])}), supported_types: this.supported_types }; From 09eff0db7ee95ba7a338707d4f93526f786273c4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 09:49:47 -0600 Subject: [PATCH 84/99] Use dynamic value for DefaultNotesToDisplayValue in notes list --- app/assets/javascripts/app/controllers/notes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 1fa4d95a0..878e7486e 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -95,7 +95,8 @@ angular.module('app') } } - this.DefaultNotesToDisplayValue = 20; + let MinNoteHeight = 51.0; // This is the height of a note cell with nothing but the title, which *is* a display option + this.DefaultNotesToDisplayValue = (document.documentElement.clientHeight / MinNoteHeight) || 20; this.notesToDisplay = this.DefaultNotesToDisplayValue; this.paginate = function() { From cbd0198e5c54bc6e86c3a6f4f6fb0b604ffc36c9 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 10:13:32 -0600 Subject: [PATCH 85/99] Show tags in tag view if more than one, sort tags alphabetically in note cell, editor pane --- .../javascripts/app/controllers/editor.js | 22 +++++++++---------- .../javascripts/app/controllers/notes.js | 13 +++++++++++ app/assets/javascripts/app/models/app/tag.js | 10 +++------ app/assets/templates/editor.html.haml | 3 ++- app/assets/templates/notes.html.haml | 2 +- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 55f54fbcb..5fc7b4218 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -297,11 +297,7 @@ angular.module('app') */ this.loadTagsString = function() { - var string = ""; - for(var tag of this.note.tags) { - string += "#" + tag.title + " "; - } - this.tagsString = string; + this.tagsString = this.note.tagsString(); } this.addTag = function(tag) { @@ -326,16 +322,18 @@ angular.module('app') } this.updateTagsFromTagsString = function() { - var tags = this.tagsString.split("#"); - tags = _.filter(tags, function(tag){ - return tag.length > 0; - }) - tags = _.map(tags, function(tag){ - return tag.trim(); + if(this.tagsString == this.note.tagsString()) { + return; + } + + var strings = this.tagsString.split("#").filter((string) => { + return string.length > 0; + }).map((string) => { + return string.trim(); }) this.note.dummy = false; - this.updateTags()(this.note, tags); + this.updateTags()(this.note, strings); } diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 878e7486e..a89e9fffa 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -257,4 +257,17 @@ angular.module('app') authManager.syncUserPreferences(); } + this.shouldShowTags = function(note) { + if(this.hideTags) { + return false; + } + + if(this.tag.all) { + return true; + } + + // Inside a tag, only show tags string if note contains tags other than this.tag + return note.tags && note.tags.length > 1; + } + }); diff --git a/app/assets/javascripts/app/models/app/tag.js b/app/assets/javascripts/app/models/app/tag.js index f94ee4151..b83585234 100644 --- a/app/assets/javascripts/app/models/app/tag.js +++ b/app/assets/javascripts/app/models/app/tag.js @@ -87,13 +87,9 @@ class Tag extends Item { return this.notes; } - static arrayToDisplayString(tags, includeComma) { - return tags.map(function(tag, i){ - var text = "#" + tag.title; - if(i != tags.length - 1) { - text += includeComma ? ", " : " "; - } - return text; + 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/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 924030f23..884bd8335 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -11,7 +11,8 @@ #note-tags-component-container{"ng-if" => "ctrl.tagsComponent"} %component-view.component-view{ "component" => "ctrl.tagsComponent"} %input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", - "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} + "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)", + "spellcheck" => "false"} .sn-component{"ng-if" => "ctrl.note"} .app-bar.no-edges diff --git a/app/assets/templates/notes.html.haml b/app/assets/templates/notes.html.haml index 67ac906ac..5797b152a 100644 --- a/app/assets/templates/notes.html.haml +++ b/app/assets/templates/notes.html.haml @@ -54,7 +54,7 @@ %i.icon.ion-ios-box %strong.medium Archived - .tags-string{"ng-if" => "ctrl.tag.all && !ctrl.hideTags"} + .tags-string{"ng-if" => "ctrl.shouldShowTags(note)"} .faded {{note.tagsString()}} .name{"ng-if" => "note.title"} From 9c61850720c6de72ec57dff66e1e54724397b5a7 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 10:16:26 -0600 Subject: [PATCH 86/99] Sort actions alphabetically --- app/assets/javascripts/app/directives/views/actionsMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js index f7842fc05..1c7ace647 100644 --- a/app/assets/javascripts/app/directives/views/actionsMenu.js +++ b/app/assets/javascripts/app/directives/views/actionsMenu.js @@ -13,7 +13,7 @@ class ActionsMenu { $scope.renderData = {}; - $scope.extensions = actionsManager.extensions; + $scope.extensions = actionsManager.extensions.sort((a, b) => {return a.name.toLowerCase() > b.name.toLowerCase()}); for(let ext of $scope.extensions) { ext.loading = true; From d068952cfc4c2f8ca0ce655ff7f73c9b033f058e Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 10:17:34 -0600 Subject: [PATCH 87/99] Sort editors alphabetically --- app/assets/javascripts/app/directives/views/editorMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js index 16ae8f6dc..734a829cc 100644 --- a/app/assets/javascripts/app/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/directives/views/editorMenu.js @@ -15,8 +15,8 @@ class EditorMenu { $scope.formData = {}; - $scope.editors = componentManager.componentsForArea("editor-editor"); - $scope.stack = componentManager.componentsForArea("editor-stack"); + $scope.editors = componentManager.componentsForArea("editor-editor").sort((a, b) => {return a.name.toLowerCase() > b.name.toLowerCase()}); + $scope.stack = componentManager.componentsForArea("editor-stack").sort((a, b) => {return a.name.toLowerCase() > b.name.toLowerCase()}); $scope.isDesktop = isDesktopApplication(); From ad584c0dee17e739a87637f4e5d4ca10ec78ce85 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 10:18:16 -0600 Subject: [PATCH 88/99] Update messaging text --- app/assets/javascripts/app/controllers/footer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index ed15118ce..4da3b6ba1 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -95,7 +95,7 @@ angular.module('app') this.clickedNewUpdateAnnouncement = function() { this.newUpdateAvailable = false; - alert("A new update is ready to install. Simply quit Standard Notes and reopen it to apply the update.") + alert("A new update is ready to install. Simply quit Standard Notes and reopen it after a brief delay to apply the update.") } From 802c01f3599486a4600b26e0be6f1ea25c55bc70 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 10:22:51 -0600 Subject: [PATCH 89/99] Notes set selectedIndex value --- app/assets/javascripts/app/controllers/notes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index a89e9fffa..7f3289491 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -189,6 +189,7 @@ angular.module('app') this.selectedNote = note; note.conflict_of = null; // clear conflict this.selectionMade()(note); + this.selectedIndex = this.visibleNotes().indexOf(note); } this.createNewNote = function() { From dd21392ae1554488bee1ac54106d05c0977aa61f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 11:24:22 -0600 Subject: [PATCH 90/99] Panel resize handle window resize event --- .../app/directives/views/panelResizer.js | 63 ++++++++++++++++--- app/assets/templates/editor.html.haml | 2 +- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/panelResizer.js b/app/assets/javascripts/app/directives/views/panelResizer.js index 917ab06bf..646bc1ab4 100644 --- a/app/assets/javascripts/app/directives/views/panelResizer.js +++ b/app/assets/javascripts/app/directives/views/panelResizer.js @@ -29,27 +29,46 @@ class PanelResizer { } } - controller($scope, $element, modelManager, actionsManager) { + controller($scope, $element, modelManager, actionsManager, $timeout) { 'ngInject'; let panel = document.getElementById($scope.panelId); if(!panel) { console.log("Panel not found for", $scope.panelId); } + let resizerColumn = $element[0]; let resizerWidth = resizerColumn.offsetWidth; let minWidth = $scope.minWidth || resizerWidth; + var pressed = false; + var startWidth = panel.scrollWidth, startX = 0, lastDownX = 0, collapsed, lastWidth = startWidth, startLeft, lastLeft; + var appFrame; function getParentRect() { return panel.parentNode.getBoundingClientRect(); } - var pressed = false; - var startWidth = panel.scrollWidth, startX, lastDownX, collapsed, lastWidth = startWidth, startLeft, lastLeft; - let appFrame = document.getElementById("app").getBoundingClientRect(); + if($scope.property == "right") { + let handleReize = debounce((event) => { + reloadDefaultValues(); + handleWidthEvent(); + $timeout(() => { $scope.finishSettingWidth(); }) + }, 250); + + window.addEventListener('resize', handleReize); + + $scope.$on("$destroy", function() { + window.removeEventListener('resize', handleReize); + }); + } + + function reloadDefaultValues() { + startWidth = panel.scrollWidth; + appFrame = document.getElementById("app").getBoundingClientRect(); + } + reloadDefaultValues(); if($scope.alwaysVisible) { - console.log("Adding always visible", $scope.alwaysVisible); resizerColumn.classList.add("always-visible"); } @@ -68,6 +87,11 @@ class PanelResizer { width = parentRect.width; } + let maxWidth = appFrame.width - panel.getBoundingClientRect().x; + if(width > maxWidth) { + width = maxWidth; + } + if(width == parentRect.width) { panel.style.width = "100%"; panel.style.flexBasis = "100%"; @@ -76,7 +100,6 @@ class PanelResizer { panel.style.width = width + "px"; } - lastWidth = width; if(finish) { @@ -94,7 +117,6 @@ class PanelResizer { return; } - if(lastWidth <= minWidth) { collapsed = true; } else { @@ -137,7 +159,14 @@ class PanelResizer { var rect = panel.getBoundingClientRect(); var panelMaxX = rect.left + (startWidth || panel.style.maxWidth); - var x = event.clientX; + var x; + if(event) { + x = event.clientX; + } else { + // coming from resize event + x = 0; + lastDownX = 0; + } let deltaX = x - lastDownX; var newWidth = startWidth + deltaX; @@ -151,7 +180,7 @@ class PanelResizer { function handleLeftEvent(event) { var panelRect = panel.getBoundingClientRect(); - var x = event.clientX; + var x = event.clientX || panelRect.x; let deltaX = x - lastDownX; var newLeft = startLeft + deltaX; if(newLeft < 0) { @@ -199,3 +228,19 @@ class PanelResizer { } angular.module('app').directive('panelResizer', () => new PanelResizer); + +/* via https://davidwalsh.name/javascript-debounce-function */ +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +}; diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 884bd8335..31c4a0a6c 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -48,7 +48,7 @@ "ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}"} {{ctrl.onSystemEditorLoad()}} - %panel-resizer{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "hoverable" => "true"} + %panel-resizer{"panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.resizeControl", "min-width" => 300, "hoverable" => "true", "property" => "'right'"} %section.section{"ng-if" => "ctrl.note.errorDecrypting"} %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. From 1a1ead9786417254287f0572c7b32c8dfbb09c45 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 6 Feb 2018 23:10:04 -0600 Subject: [PATCH 91/99] Fix staging login issue --- app/assets/javascripts/app/controllers/home.js | 3 ++- app/assets/javascripts/app/directives/views/accountMenu.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index e775c9c4c..d52c3c59d 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -263,12 +263,13 @@ angular.module('app') return; } else { // sign out + authManager.signOut(); syncManager.destroyLocalData(function(){ window.location.reload(); }) } } else { - authManager.login(server, email, pw, false, function(response){ + authManager.login(server, email, pw, false, {}, function(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 7ec956322..6049eb1bd 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -173,7 +173,6 @@ class AccountMenu { $rootScope.$broadcast("major-data-change"); $scope.clearDatabaseAndRewriteAllItems(true, block); } - else { modelManager.resetLocalMemory(); storageManager.clearAllModels(function(){ From c15bdda6c90ebb5e55672c8f95f37d800bc395d8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 7 Feb 2018 09:55:28 -0600 Subject: [PATCH 92/99] Login improved error handling --- .../app/directives/views/accountMenu.js | 12 ++++++++++-- .../javascripts/app/services/authManager.js | 15 +++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 6049eb1bd..6237a96c1 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -110,19 +110,27 @@ class AccountMenu { if(!response || response.error) { $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(() => { $scope.formData.showLogin = false; $scope.formData.mfa = error; }) - } else if(!response || (response && !response.didDisplayAlert)) { + } + + // General Error + else { $timeout(() => { $scope.formData.showLogin = true; $scope.formData.mfa = null; }) alert(error.message); } - } else { + } + + // Success + else { $scope.onAuthSuccess(); } }); diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index a72423d01..4afb6ab92 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -133,24 +133,23 @@ angular.module('app') } if(!this.isProtocolVersionSupported(authParams.version)) { - alert("The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.org/help/security-update for more information."); - callback({didDisplayAlert: true}); + let message = "The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.org/help/security-update for more information."; + callback({error: {message: message}}); return; } if(!this.supportsPasswordDerivationCost(authParams.pw_cost)) { - var string = "Your account was created on a platform with higher security capabilities than this browser supports. " + + 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 login." - alert(string) - callback({didDisplayAlert: true}); + "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 = this.costMinimumForVersion(authParams.version); if(authParams.pw_cost < minimum) { - alert("Unable to login due to insecure password parameters. Please visit standardnotes.org/help/password-upgrade for more information."); - callback({didDisplayAlert: true}); + let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/password-upgrade for more information."; + callback({error: {message: message}}); return; } From 2d4be5f39968a3849c39ef6aa5141394ea4e9cd2 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 7 Feb 2018 11:22:27 -0600 Subject: [PATCH 93/99] Updates submodules --- .../app/services/componentManager.js | 2 +- app/assets/stylesheets/app/_modals.scss | 2 +- package-lock.json | 33 +++++++++---------- public/extensions/extensions-manager | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index cf4b58a4c..52cde7942 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -772,7 +772,7 @@ class ComponentManager { } iframeForComponent(component) { - for(var frame of document.getElementsByTagName("iframe")) { + for(var frame of Array.from(document.getElementsByTagName("iframe"))) { var componentId = frame.dataset.componentId; if(componentId === component.uuid) { return frame; diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index c1d472e04..69b9754e1 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -59,7 +59,7 @@ } > .content { - overflow-y: scroll; + overflow-y: auto; width: auto; padding: 0; padding-bottom: 0; diff --git a/package-lock.json b/package-lock.json index 765a76d8b..367778f61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -212,7 +212,7 @@ "dev": true, "requires": { "babel-core": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "babel-polyfill": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "babel-polyfill": "6.26.0", "babel-register": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "chokidar": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -1011,35 +1011,35 @@ } }, "babel-polyfill": { - "version": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, "requires": { - "babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", + "babel-runtime": "6.26.0", + "core-js": "2.5.3", "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz" }, "dependencies": { "babel-runtime": { - "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { - "core-js": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", - "regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz" + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" }, "dependencies": { "regenerator-runtime": { - "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", - "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=", - "dev": true + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" } } }, "core-js": { - "version": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", - "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=", - "dev": true + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" } } }, @@ -5549,8 +5549,7 @@ }, "regenerator-runtime": { "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" }, "regenerator-transform": { "version": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", diff --git a/public/extensions/extensions-manager b/public/extensions/extensions-manager index c5706ed93..50d632e26 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit c5706ed93eae1343efea505cfd077810553e5837 +Subproject commit 50d632e262d818ce86868bc5f12e7f5e936a1e2f From bd0a9304d96b54e723ee5c21b0d408d1f21d5918 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 7 Feb 2018 12:00:51 -0600 Subject: [PATCH 94/99] 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 50d632e26..3bfd3c1b4 160000 --- a/public/extensions/extensions-manager +++ b/public/extensions/extensions-manager @@ -1 +1 @@ -Subproject commit 50d632e262d818ce86868bc5f12e7f5e936a1e2f +Subproject commit 3bfd3c1b4fb1225fe6b063eacc51cc05697f47a8 From 02e4a6961c98f583dd3d45b9599129ece8feb350 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 9 Feb 2018 08:52:40 -0600 Subject: [PATCH 95/99] Fixes issue where archiving a note would save the next note --- .../javascripts/app/controllers/editor.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 5fc7b4218..42f403470 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -205,16 +205,25 @@ angular.module('app') } var saveTimeout; - this.changesMade = function() { - this.note.hasChanges = true; + this.changesMade = function(bypassDebouncer = false) { this.note.dummy = false; + /* In the case of keystrokes, saving should go through a debouncer to avoid frequent calls. + In the case of deleting or archiving a note, it should happen immediately before the note is switched out + */ + let delay = bypassDebouncer ? 0 : 275; + + // In the case of archiving a note, the note is saved immediately, then switched to another note. + // Usually note.hasChanges is set back to false after the saving delay, but in this case, because there is no delay, + // we set it to false immediately so that it is not saved twice: once now, and the other on setNote in oldNote.hasChanges. + this.note.hasChanges = bypassDebouncer ? false : true; + if(saveTimeout) $timeout.cancel(saveTimeout); if(statusTimeout) $timeout.cancel(statusTimeout); saveTimeout = $timeout(function(){ this.showSavingStatus(); this.saveNote(); - }.bind(this), 275) + }.bind(this), delay) } this.showSavingStatus = function() { @@ -277,7 +286,7 @@ angular.module('app') this.toggleArchiveNote = function() { this.note.setAppDataItem("archived", !this.note.archived); this.note.setDirty(true); - this.changesMade(); + this.changesMade(true); $rootScope.$broadcast("noteArchived"); } From f2dce42395426240d7f738f8b1862a04cc944e7e Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 9 Feb 2018 08:59:24 -0600 Subject: [PATCH 96/99] Fixes search in Archived issue, closes #179 --- app/assets/javascripts/app/controllers/notes.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 7f3289491..b04444c04 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -203,12 +203,7 @@ angular.module('app') this.noteFilter = {text : ''}; this.filterNotes = function(note) { - if(this.tag.archiveTag) { - note.visible = note.archived; - return note.visible; - } - - if((note.archived && !this.showArchived) || (note.pinned && this.hidePinned)) { + if((note.archived && !this.showArchived && !this.tag.archiveTag) || (note.pinned && this.hidePinned)) { note.visible = false; return note.visible; } @@ -222,6 +217,11 @@ angular.module('app') var matchesBody = words.every(function(word) { return note.safeText().toLowerCase().indexOf(word) >= 0; }); note.visible = matchesTitle || matchesBody; } + + if(this.tag.archiveTag) { + note.visible = note.visible && note.archived; + } + return note.visible; }.bind(this) From d0eb1c0aab1217822039fadccbbe3365aaff9683 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 9 Feb 2018 15:04:20 -0600 Subject: [PATCH 97/99] Package --- package-lock.json | 17 +++++++++++------ package.json | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 367778f61..8555f1a06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1014,6 +1014,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, "requires": { "babel-runtime": "6.26.0", "core-js": "2.5.3", @@ -1024,6 +1025,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, "requires": { "core-js": "2.5.3", "regenerator-runtime": "0.11.1" @@ -1032,14 +1034,16 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true } } }, "core-js": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=", + "dev": true } } }, @@ -5549,7 +5553,8 @@ }, "regenerator-runtime": { "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true }, "regenerator-transform": { "version": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", @@ -5780,9 +5785,9 @@ "dev": true }, "sn-stylekit": { - "version": "1.0.1196", - "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.1196.tgz", - "integrity": "sha512-/6P0m4A+TEwJ6VxstiEzCCfBheWz5c8Y/B209cItqLX+9ltfg4yEUaTHG/Q/TMKPID8or4CiiMLzj+6SwuMqGQ==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-1.0.12.tgz", + "integrity": "sha512-CkCGj6pS2FB9tF5uVbqMlmKpQ1wJFCcXAri2B4nCq8aAv2D5frUhhPFJ1oCnFgBssB9LxnbW2+C2CQojVCL9ig==", "dev": true }, "snake-case": { diff --git a/package.json b/package.json index 13d5a11f4..71547d01e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "karma-cli": "^1.0.1", "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", - "sn-stylekit": "^1.0.1196" + "sn-stylekit": "1.0.12" }, "license": "GPL-3.0" } From bc9e89d6de9f6d2e54d081043793b84815e94b9d Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 10 Feb 2018 09:56:41 -0600 Subject: [PATCH 98/99] Encryption status text --- app/assets/javascripts/app/directives/views/accountMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 6237a96c1..bd57c79a4 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -520,9 +520,9 @@ class AccountMenu { $scope.encryptionStatusString = function() { if(!authManager.offline()) { - return "End-to-end encryption is enabled. Your data is encrypted before being synced to your private account."; + return "End-to-end encryption is enabled. Your data is encrypted before syncing to your private account."; } else if(passcodeManager.hasPasscode()) { - return "Encryption is enabled. Your data is encrypted using your passcode before being stored on disk."; + return "Encryption is enabled. Your data is encrypted using your passcode before saving to your device storage."; } else { return "Encryption is not enabled. Sign in, register, or add a passcode lock to enable encryption."; } From c83432af2308804a7bc0c95fd65b6066e46337b4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 12 Feb 2018 14:13:59 -0600 Subject: [PATCH 99/99] Check item content_type in mapping as backup --- app/assets/javascripts/app/services/modelManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index c1e274e6c..0b9a31f96 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -141,7 +141,8 @@ class ModelManager { continue; } - var unknownContentType = !_.includes(this.acceptableContentTypes, json_obj["content_type"]); + let contentType = json_obj["content_type"] || (item && item.content_type); + var unknownContentType = !_.includes(this.acceptableContentTypes, contentType); if(json_obj.deleted == true || unknownContentType) { if(item && !unknownContentType) { modelsToNotifyObserversOf.push(item);