From 5ec4797d85033e40637e3eb0b894f4a734d8e77d Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Tue, 16 May 2017 11:55:52 -0500 Subject: [PATCH] no extensions box --- .../app/frontend/controllers/_base.js | 10 + .../app/frontend/controllers/header.js | 5 + .../views/contextualExtensionsMenu.js | 10 +- .../services/directives/views/editorMenu.js | 19 -- .../directives/views/globalExtensionsMenu.js | 107 +++++++---- .../javascripts/app/services/editorManager.js | 12 +- .../javascripts/app/services/themeManager.js | 4 + app/assets/stylesheets/app/_extensions.scss | 88 +++++++++ app/assets/stylesheets/app/_menus.scss | 11 ++ app/assets/stylesheets/app/_standard.scss | 10 +- .../directives/contextual-menu.html.haml | 2 +- .../frontend/directives/editor-menu.html.haml | 19 +- .../global-extensions-menu.html.haml | 174 ++++++++++-------- 13 files changed, 315 insertions(+), 156 deletions(-) diff --git a/app/assets/javascripts/app/frontend/controllers/_base.js b/app/assets/javascripts/app/frontend/controllers/_base.js index cd7cc16d9..4641b0ed0 100644 --- a/app/assets/javascripts/app/frontend/controllers/_base.js +++ b/app/assets/javascripts/app/frontend/controllers/_base.js @@ -8,4 +8,14 @@ class BaseCtrl { } } + +function getParameterByName(name, url) { + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); +} + angular.module('app.frontend').controller('BaseCtrl', BaseCtrl); diff --git a/app/assets/javascripts/app/frontend/controllers/header.js b/app/assets/javascripts/app/frontend/controllers/header.js index 19261f27a..21b4efa07 100644 --- a/app/assets/javascripts/app/frontend/controllers/header.js +++ b/app/assets/javascripts/app/frontend/controllers/header.js @@ -55,6 +55,11 @@ angular.module('app.frontend') this.showExtensionsMenu = !this.showExtensionsMenu; } + $timeout(function(){ + // testing + this.toggleExtensions(); + }.bind(this)) + this.toggleIO = function() { this.showIOMenu = !this.showIOMenu; this.showExtensionsMenu = false; diff --git a/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js index b71c7fc8a..a41644af5 100644 --- a/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js @@ -26,7 +26,7 @@ class ContextualExtensionsMenu { }) } - $scope.executeAction = function(action, extension) { + $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; @@ -41,7 +41,13 @@ class ContextualExtensionsMenu { $scope.handleActionResponse(action, response); // reload extension actions - extensionManager.loadExtensionInContextOfItem(extension, $scope.item, null); + extensionManager.loadExtensionInContextOfItem(extension, $scope.item, function(ext){ + // keep nested state + if(parentAction) { + var matchingAction = _.find(ext.actions, {label: parentAction.label}); + matchingAction.showNestedActions = true; + } + }); }) } diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index c9c97ac16..c10ff2c20 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -20,25 +20,6 @@ class EditorMenu { $scope.callback()(editor); } - $scope.deleteEditor = function(editor) { - if(confirm("Are you sure you want to delete this editor?")) { - editorManager.deleteEditor(editor); - } - } - - $scope.setDefaultEditor = function(editor) { - editorManager.setDefaultEditor(editor); - } - - $scope.removeDefaultEditor = function(editor) { - editorManager.removeDefaultEditor(editor); - } - - $scope.submitNewEditorRequest = function() { - editorManager.addNewEditorFromURL($scope.formData.url); - $scope.formData = {}; - } - } } diff --git a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js index 1932bcd50..af6f2d3ae 100644 --- a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js @@ -7,37 +7,14 @@ class GlobalExtensionsMenu { }; } - controller($scope, extensionManager, syncManager, modelManager, themeManager) { + controller($scope, extensionManager, syncManager, modelManager, themeManager, editorManager) { 'ngInject'; + $scope.formData = {}; + $scope.extensionManager = extensionManager; $scope.themeManager = themeManager; - $scope.state = {showDataExts: true, showThemes: true}; - - $scope.toggleExtensionForm = function() { - $scope.newExtensionData = {}; - $scope.showNewExtensionForm = !$scope.showNewExtensionForm; - } - - $scope.submitNewExtensionForm = function() { - if($scope.newExtensionData.url) { - extensionManager.addExtension($scope.newExtensionData.url, function(response){ - if(!response) { - if($scope.newExtensionData.url.indexOf("type=sf") != -1) { - alert("Unable to register this extension. You are attempting to register a Standard File extension in Standard Notes. You should instead open your Standard File Dashboard and register this extension there."); - } else if($scope.newExtensionData.url.indexOf("name=") != -1) { - // user is mistakenly trying to register editor extension, most likely - alert("Unable to register this extension. It looks like you may be trying to install an editor extension. To do that, click 'Editor' under the current note's title."); - } else { - alert("Unable to register this extension. Make sure the link is valid and try again."); - } - } else { - $scope.newExtensionData.url = ""; - $scope.showNewExtensionForm = false; - } - }) - } - } + $scope.editorManager = editorManager; $scope.selectedAction = function(action, extension) { extensionManager.executeAction(action, extension, null, function(response){ @@ -55,7 +32,7 @@ class GlobalExtensionsMenu { extensionManager.changeExtensionEncryptionFormat(encrypted, extension); } - $scope.deleteExtension = function(extension) { + $scope.deleteActionExtension = function(extension) { if(confirm("Are you sure you want to delete this extension?")) { extensionManager.deleteExtension(extension); } @@ -67,11 +44,6 @@ class GlobalExtensionsMenu { } } - $scope.submitTheme = function() { - themeManager.submitTheme($scope.state.themeUrl); - $scope.state.themeUrl = ""; - } - $scope.deleteTheme = function(theme) { if(confirm("Are you sure you want to delete this theme?")) { themeManager.deactivateTheme(theme); @@ -80,6 +52,75 @@ class GlobalExtensionsMenu { } } + + // Editors + + $scope.deleteEditor = function(editor) { + if(confirm("Are you sure you want to delete this editor?")) { + editorManager.deleteEditor(editor); + } + } + + $scope.setDefaultEditor = function(editor) { + editorManager.setDefaultEditor(editor); + } + + $scope.removeDefaultEditor = function(editor) { + editorManager.removeDefaultEditor(editor); + } + + // Installation + + $scope.submitInstallLink = function() { + + var link = $scope.formData.installLink; + if(!link) { + return; + } + + var completion = function() { + $scope.formData.installLink = ""; + } + + var type = getParameterByName("type", link); + + if(type == "sf" || type == "sync") { + $scope.handleSyncAdapterLink(link, completion); + } else if(type == "editor") { + $scope.handleEditorLink(link, completion); + } else if(link.indexOf(".css") != -1 || type == "theme") { + $scope.handleThemeLink(link, completion); + } else { + $scope.handleActionLink(link, completion); + } + } + + $scope.handleSyncAdapterLink = function(link, completion) { + completion(); + } + + $scope.handleThemeLink = function(link, completion) { + themeManager.submitTheme(link); + completion(); + } + + $scope.handleActionLink = function(link, completion) { + if(link) { + extensionManager.addExtension(link, function(response){ + if(!response) { + alert("Unable to register this extension. Make sure the link is valid and try again."); + } else { + completion(); + } + }) + } + } + + $scope.handleEditorLink = function(link, completion) { + editorManager.addNewEditorFromURL(link); + completion(); + } + } } diff --git a/app/assets/javascripts/app/services/editorManager.js b/app/assets/javascripts/app/services/editorManager.js index 5ce52b430..1b9ee579b 100644 --- a/app/assets/javascripts/app/services/editorManager.js +++ b/app/assets/javascripts/app/services/editorManager.js @@ -1,6 +1,5 @@ class EditorManager { - constructor($rootScope, modelManager, syncManager) { this.syncManager = syncManager; this.modelManager = modelManager; @@ -79,7 +78,7 @@ class EditorManager { } addNewEditorFromURL(url) { - var name = this.getParameterByName("name", url); + var name = getParameterByName("name", url); var editor = this.modelManager.createItem({ content_type: this.editorType, url: url, @@ -96,15 +95,6 @@ class EditorManager { this.syncManager.sync(); } - getParameterByName(name, url) { - name = name.replace(/[\[\]]/g, "\\$&"); - var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), - results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, " ")); - } - } angular.module('app.frontend').service('editorManager', EditorManager); diff --git a/app/assets/javascripts/app/services/themeManager.js b/app/assets/javascripts/app/services/themeManager.js index a4698ea6a..0bf779033 100644 --- a/app/assets/javascripts/app/services/themeManager.js +++ b/app/assets/javascripts/app/services/themeManager.js @@ -84,6 +84,10 @@ class ThemeManager { } 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); diff --git a/app/assets/stylesheets/app/_extensions.scss b/app/assets/stylesheets/app/_extensions.scss index a9519d627..e58a41cf7 100644 --- a/app/assets/stylesheets/app/_extensions.scss +++ b/app/assets/stylesheets/app/_extensions.scss @@ -10,6 +10,7 @@ width: 100vw; height: 100vh; background-color: rgba(gray, 0.3); + color: black; .content { box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); @@ -23,3 +24,90 @@ 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; + } + } + + .blue-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; + } + } + } + + .dashboard-link { + padding-top: 12px; + font-weight: normal; + } + + .section-margin { + margin-top: 20px; + } + + input { + border: 1px solid $blue-color; + border-radius: 2px; + } + + ul { + border-top: 1px solid $light-bg-color; + border-bottom: 1px solid $light-bg-color; + margin: 0; + padding: 0; + margin-top: 12px; + + 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/_menus.scss b/app/assets/stylesheets/app/_menus.scss index d659fd4ab..127371e8e 100644 --- a/app/assets/stylesheets/app/_menus.scss +++ b/app/assets/stylesheets/app/_menus.scss @@ -142,9 +142,20 @@ ul.section-menu-bar { &:hover { background-color: $blue-color; + .blue { color: white; } + + &.nested-hover { + color: black; + background-color: $light-bg-color; + } + } + + &.nested-hover { + color: black; + background-color: white; } .menu-item-title { diff --git a/app/assets/stylesheets/app/_standard.scss b/app/assets/stylesheets/app/_standard.scss index 436b4a833..e9a6a2cb4 100644 --- a/app/assets/stylesheets/app/_standard.scss +++ b/app/assets/stylesheets/app/_standard.scss @@ -1,3 +1,11 @@ +.selectable { + user-select: all; +} + +.clear { + clear: both; +} + .pull-left { float: left !important; } @@ -166,7 +174,7 @@ } .small { - font-size: 10px !important; + font-size: 10px; } .medium { diff --git a/app/assets/templates/frontend/directives/contextual-menu.html.haml b/app/assets/templates/frontend/directives/contextual-menu.html.haml index 113596128..3a0582515 100644 --- a/app/assets/templates/frontend/directives/contextual-menu.html.haml +++ b/app/assets/templates/frontend/directives/contextual-menu.html.haml @@ -18,7 +18,7 @@ %div{"ng-if" => "action.showNestedActions"} %ul.mt-10 - %li.menu-item.white-bg{"ng-repeat" => "subaction in action.subactions", "ng-click" => "executeAction(subaction, extension);", "style" => "margin-top: -1px;"} + %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;"} .menu-item-title {{subaction.label}} .menu-item-subtitle {{subaction.desc}} %span{"ng-if" => "subaction.running"} diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index 4238fd52b..5f2e3fd6b 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -12,18 +12,7 @@ .subtitle Can access your current note decrypted. %ul %li.menu-item{"ng-repeat" => "editor in editorManager.externalEditors", "ng-click" => "selectEditor($event, editor)"} - - .left-side - %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy - .menu-item-title {{editor.name}} - %a.faded{"ng-if" => "!editor.default", "ng-click" => "setDefaultEditor(editor); $event.stopPropagation();"} Set Default - %a.blue{"ng-if" => "editor.default", "ng-click" => "removeDefaultEditor(editor); $event.stopPropagation();"} Remove Default - %a.faded{"ng-click" => "editor.showUrl = !editor.showUrl; $event.stopPropagation();"} Show URL - .menu-item-subtitle.wrap.mt-5{"ng-if" => "editor.showUrl"} {{editor.url}} - - %span.inline.ml-10{"ng-if" => "selectedEditor === editor"} ✓ - .right-side - %button.white.medium{"style" => "width: 50px; height: 40px;", "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} ☓ - .footer.mt-10 - %input.form-control{"ng-model" => "formData.url", "placeholder" => "Add new editor via URL", "ng-keyup" => "$event.keyCode == 13 && submitNewEditorRequest()"} - %a.block.blue{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} Available Editors + %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy + .menu-item-title + {{editor.name}} + %span.inline.blue{"style" => "margin-left: 8px;", "ng-if" => "selectedEditor === editor"} ✓ 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 840c08124..a834ec9d3 100644 --- a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml +++ b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml @@ -1,82 +1,108 @@ -.panel.panel-default.account-panel.panel-right +.panel.panel-default.account-panel.panel-right#global-ext-menu .panel-body - .white-bg.medium-padding.ext-section{"ng-class" => "{'opened': state.showDataExts}"} - %h1 - %a{"ng-click" => "state.showDataExts = !state.showDataExts"} Action Extensions - %p{"style" => "margin-top: 3px;"} These extensions perform actions on a specific note. Choose "Actions" in the note editor to use installed actions. + .container + .float-group.h20 + %h1.blue.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 && !editorManager.externalEditors.length"} + %p Customize your experience with editors, themes, and actions. + .blue-box.mt-10 + %h3 Available as part of the Extended subscription. + %p.mt-5 Note history + %p.mt-5 Automated backups + %p.mt-5 All editors, themes, and actions + %a{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} + %button.mt-10 + %h3 Learn More - %div.mt-10{"ng-if" => "state.showDataExts"} - %div{"style" => "font-size: 15px;", "ng-if" => "!extensionManager.extensions.length"} No extensions installed - %div{"ng-if" => "extensionManager.extensions.length"} - %section.gray-bg.inline-h.mb-10.medium-padding{"ng-repeat" => "extension in extensionManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}"} + %div{"ng-if" => "themeManager.themes.length > 0"} + .container.no-bottom.section-margin + %h2 Themes + %ul + %li{"ng-repeat" => "theme in themeManager.themes", "ng-click" => "theme.showDetails = !theme.showDetails"} + .container + %h3 {{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 + %div{"ng-if" => "theme.showDetails"} + .link-group + %a.red{"ng-click" => "deleteTheme(theme); $event.stopPropagation();"} Delete + %a{"ng-click" => "theme.showLink = !theme.showLink; $event.stopPropagation();"} Show Link + %p.small.selectable.wrap{"ng-if" => "theme.showLink"} + {{theme.url}} + + %div{"ng-if" => "editorManager.externalEditors.length > 0"} + .container.no-bottom.section-margin + %h2 Editors + %p{"style" => "margin-top: 3px;"} Choose "Editor" in the note menu to use an editor for a specific note. + %ul + %li{"ng-repeat" => "editor in editorManager.externalEditors", "ng-click" => "editor.showDetails = !editor.showDetails"} + .container + %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy + %h3 {{editor.name}} + %div.mt-5{"ng-if" => "editor.showDetails"} + .link-group + %a{"ng-if" => "!editor.default", "ng-click" => "setDefaultEditor(editor); $event.stopPropagation();"} Make Default + %a.blue{"ng-if" => "editor.default", "ng-click" => "removeDefaultEditor(editor); $event.stopPropagation();"} Remove as Default + %a{"ng-click" => "editor.showUrl = !editor.showUrl; $event.stopPropagation();"} Show Link + %a.red{ "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} Delete + .wrap.mt-5.selectable{"ng-if" => "editor.showUrl"} {{editor.url}} + + %div{"ng-if" => "extensionManager.extensions.length"} + .container.no-bottom.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" => "extension.showDetails = !extension.showDetails"} + .container %h3 {{extension.name}} - %p{"ng-if" => "extension.description"} {{extension.description}} - .mt-10 - %label.block Access Type - %label.normal.block - %input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "true", "ng-change" => "changeExtensionEncryptionFormat(true, extension)"} - Encrypted - %label.normal.block - %input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "false", "ng-change" => "changeExtensionEncryptionFormat(false, extension)"} - Decrypted + %p.small{"ng-if" => "extension.description"} {{extension.description}} + %div{"ng-if" => "extension.showDetails"} + .mt-10 + %label.block Access Type + %label.normal.block + %input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "true", "ng-change" => "changeExtensionEncryptionFormat(true, extension)"} + Encrypted + %label.normal.block + %input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "false", "ng-change" => "changeExtensionEncryptionFormat(false, extension)"} + Decrypted - .small-v-space + .small-v-space - %section.inline-h.white-bg.medium-padding.mb-10.pb-4{"ng-repeat" => "action in extension.actionsInGlobalContext()"} - %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()}} + %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{"ng-if" => "extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.disableRepeatAction(action, extension)"} Disable - %button.light{"ng-if" => "!extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.enableRepeatAction(action, extension)"} Enable - %button.light.mt-10{"ng-if" => "!action.running && !action.repeat_mode", "ng-click" => "selectedAction(action, extension)"} - 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. + %div + .mt-5{"ng-if" => "action.repeat_mode"} + %button.light{"ng-if" => "extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.disableRepeatAction(action, extension); $event.stopPropagation();"} Disable + %button.light{"ng-if" => "!extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.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" => "extension.showURL = !extension.showURL"} Show URL - %p.wrap{"ng-if" => "extension.showURL"} {{extension.url}} - %a.block.mt-5{"ng-click" => "deleteExtension(extension)"} Remove extension + %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();"} Remove extension - .large-v-space - - %a.block{"ng-click" => "toggleExtensionForm()"} Add New Extension - - %form.mt-10.mb-10{"ng-if" => "showNewExtensionForm"} - %input.form-control{:autofocus => 'autofocus', :name => 'url', :placeholder => 'Extension URL', :required => true, :type => 'url', 'ng-model' => 'newExtensionData.url'} - %button.ui-button.block{"ng-click" => "submitNewExtensionForm()", :type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"} - Add Extension - - %a.block.mt-5{"ng-click" => "reloadExtensionsPressed()", "ng-if" => "extensionManager.extensions.length > 0"} Reload all extensions - %a.block.mt-5{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} Available Extensions - - - .white-bg.medium-padding.mt-10.ext-section{"ng-class" => "{'opened': state.showThemes}"} - %h1 - %a{"ng-click" => "state.showThemes = !state.showThemes"} Themes - %div{"ng-if" => "state.showThemes"} - %section{"ng-repeat" => "theme in themeManager.themes"} - %label {{theme.name}} - %p {{theme.url}} - %a{"ng-if" => "!themeManager.isThemeActive(theme)", "ng-click" => "themeManager.activateTheme(theme)"} Activate - %a{"ng-if" => "themeManager.isThemeActive(theme)", "ng-click" => "themeManager.deactivateTheme(theme)"} Deactivate - %a.red.ml-2{"ng-click" => "deleteTheme(theme)"} Delete - - %section - %p.bold.mb-5 Install New Theme - %input.form-control{:autofocus => 'autofocus', :name => 'url', :placeholder => 'New Theme URL', :required => true, - :type => 'url', 'ng-model' => 'state.themeUrl', "ng-keyup" => "$event.keyCode == 13 && submitTheme();"} - %a.block.mt-5{"href" => "https://standardnotes.org/extensions/themes", "target" => "_blank"} Available Themes + .container.section-margin + %h2.blue Install + %p.faded Enter an install link + %form.mt-10.mb-10 + %input.form-control{:autofocus => 'autofocus', :name => 'url', :required => true, + :type => 'url', 'ng-model' => 'formData.installLink', "ng-keyup" => "$event.keyCode == 13 && submitInstallLink();"}