diff --git a/.babelrc b/.babelrc
index 65e26d39f..2b95d25e0 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,9 +1,14 @@
{
- "presets": ["@babel/preset-env"],
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "targets": "> 0.25%, not dead"
+ }
+ ]
+ ],
"plugins": [
- "angularjs-annotate",
- ["@babel/transform-runtime", {
- "regenerator": true
- }]
+ "@babel/plugin-transform-runtime",
+ "angularjs-annotate"
]
}
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..41f74d004
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,14 @@
+{
+ "extends": ["semistandard", "prettier"],
+ "rules": {
+ "standard/no-callback-literal": 0 // Disable this as we have too many callbacks relying on literals
+ },
+ "env": {
+ "browser": true
+ },
+ "globals": {
+ "SFJS": true,
+ "__VERSION__": true,
+ "zip": true
+ }
+}
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 000000000..544138be4
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "singleQuote": true
+}
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 7b67d54d3..000000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,183 +0,0 @@
-module.exports = function(grunt) {
-
- grunt.initConfig({
-
- watch: {
- haml: {
- files: ['app/assets/templates/**/*.haml'],
- tasks: ['newer:haml', 'ngtemplates', 'concat:app', 'babel', 'browserify', 'concat:dist', 'clean'],
- options: {
- spawn: false,
- },
- },
-
- js: {
- files: ['app/assets/javascripts/**/*.js'],
- tasks: ['haml', 'ngtemplates', 'sass', 'concat:app', 'babel', 'browserify',
- 'concat:lib', 'concat:dist', 'concat:css', 'uglify', 'ngconstant:build', 'clean'],
- options: {
- spawn: false,
- },
- },
-
- css: {
- files: ['app/assets/stylesheets/**/*.scss'],
- tasks: ['sass', 'concat:css'],
- options: {
- spawn: false,
- },
- }
- },
-
- sass: {
- dist: {
- options: {
- style: 'expanded'
- },
- files: {
- 'dist/stylesheets/app.css': 'app/assets/stylesheets/main.css.scss'
- }
- }
- },
-
- haml: {
- dist: {
- expand: true,
- ext: '.html',
- extDot: 'last',
- src: ['app/assets/templates/**/*.haml'],
- dest: 'app/assets/templates/generated/',
- rename: function (dest, src) {
- return dest + src.replace(".html", "");
- }
- },
-
- },
-
- ngtemplates: {
- templates: {
- cwd: 'app/assets/templates/generated/app/assets/templates',
- src: ['**/*.html'],
- dest: 'dist/javascripts/templates.js',
- options: {
- module: 'app'
- }
- }
- },
-
- concat: {
- options: {
- separator: ';',
- },
- app: {
- src: [
- 'app/assets/javascripts/app/*.js',
- 'app/assets/javascripts/app/models/**/*.js',
- 'app/assets/javascripts/app/controllers/**/*.js',
- 'app/assets/javascripts/app/services/**/*.js',
- 'app/assets/javascripts/app/filters/**/*.js',
- 'app/assets/javascripts/app/directives/**/*.js',
- ],
- dest: 'dist/javascripts/app.js',
- },
-
- lib: {
- src: [
- 'node_modules/snjs/dist/regenerator.js',
- 'node_modules/snjs/dist/snjs.js',
- 'node_modules/sn-stylekit/dist/stylekit.js',
- 'node_modules/angular/angular.js',
- 'vendor/assets/javascripts/angular-sanitize.js',
- 'vendor/assets/javascripts/lodash/lodash.custom.min.js'
- ],
- dest: 'dist/javascripts/lib.js',
- },
-
- dist: {
- src: ['dist/javascripts/lib.js', 'dist/javascripts/transpiled.js', 'dist/javascripts/templates.js'],
- dest: 'dist/javascripts/compiled.js',
- },
-
- css: {
- options: {
- separator: '',
- },
- src: [
- 'dist/stylesheets/app.css',
- 'node_modules/sn-stylekit/dist/stylekit.css'
- ],
- dest: 'dist/stylesheets/app.css'
- }
- },
-
- babel: {
- options: {
- sourceMap: true
- },
- dist: {
- files: {
- 'dist/javascripts/transpiled.js': 'dist/javascripts/app.js'
- }
- }
- },
-
- browserify: {
- dist: {
- files: {
- 'dist/javascripts/transpiled.js': 'dist/javascripts/transpiled.js'
- },
- options: {
- }
- }
- },
-
- uglify: {
- compiled: {
- src: ['dist/javascripts/compiled.js'],
- dest: 'dist/javascripts/compiled.min.js'
- }
- },
-
- ngconstant: {
- options: {
- name: 'app',
- dest: 'app/assets/javascripts/app/constants.js',
- deps: false,
- constants: {
- appVersion: grunt.file.readJSON('package.json').version
- }
- },
- build: {
- }
- },
-
- clean: [
- 'dist/javascripts/app.js',
- 'dist/javascripts/lib.js',
- 'dist/javascripts/templates.js',
- 'dist/javascripts/transpiled.js',
- 'dist/javascripts/transpiled.js.map',
- ]
- });
-
- grunt.loadNpmTasks('grunt-newer');
- grunt.loadNpmTasks('grunt-contrib-watch');
- grunt.loadNpmTasks('grunt-haml2html');
- grunt.loadNpmTasks('grunt-angular-templates');
- grunt.loadNpmTasks('grunt-contrib-cssmin');
- grunt.loadNpmTasks('grunt-contrib-concat');
- grunt.loadNpmTasks('grunt-contrib-sass');
- grunt.loadNpmTasks('grunt-contrib-uglify');
- grunt.loadNpmTasks('grunt-babel');
- grunt.loadNpmTasks('grunt-browserify');
- grunt.loadNpmTasks('grunt-ng-constant');
- grunt.loadNpmTasks('grunt-contrib-clean');
-
- grunt.registerTask('default', ['haml', 'ngtemplates', 'sass', 'concat:app', 'babel', 'browserify',
- 'concat:lib', 'concat:dist', 'concat:css', 'uglify', 'ngconstant:build', 'clean']);
-
- grunt.registerTask('vendor', ['concat:app', 'sass', 'babel', 'browserify',
- 'concat:lib', 'concat:dist', 'concat:css', 'uglify']);
-
- grunt.registerTask('constants', ['ngconstant:build'])
-};
diff --git a/dist/assets/ionicons.eot b/app/assets/fonts/ionicons.eot
similarity index 100%
rename from dist/assets/ionicons.eot
rename to app/assets/fonts/ionicons.eot
diff --git a/dist/assets/ionicons.svg b/app/assets/fonts/ionicons.svg
old mode 100755
new mode 100644
similarity index 100%
rename from dist/assets/ionicons.svg
rename to app/assets/fonts/ionicons.svg
diff --git a/dist/assets/ionicons.ttf b/app/assets/fonts/ionicons.ttf
old mode 100755
new mode 100644
similarity index 100%
rename from dist/assets/ionicons.ttf
rename to app/assets/fonts/ionicons.ttf
diff --git a/dist/assets/ionicons.woff b/app/assets/fonts/ionicons.woff
old mode 100755
new mode 100644
similarity index 100%
rename from dist/assets/ionicons.woff
rename to app/assets/fonts/ionicons.woff
diff --git a/app/assets/javascripts/app/app.js b/app/assets/javascripts/app/app.js
index 6ae22ff8d..7a17502e6 100644
--- a/app/assets/javascripts/app/app.js
+++ b/app/assets/javascripts/app/app.js
@@ -1,104 +1,160 @@
'use strict';
-var SN = SN || {};
+import angular from 'angular';
+import { configRoutes } from './routes';
-angular.module('app', [
- 'ngSanitize'
-])
+import {
+ Home,
+ TagsPanel,
+ NotesPanel,
+ EditorPanel,
+ Footer,
+ LockScreen
+} from './controllers';
-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, " "));
-}
+import {
+ autofocus,
+ clickOutside,
+ delayHide,
+ elemReady,
+ fileChange,
+ infiniteScroll,
+ lowercase,
+ selectOnClick,
+ snEnter
+} from './directives/functional';
-function parametersFromURL(url) {
- url = url.split("?").slice(-1)[0];
- var obj = {};
- url.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
- obj[decodeURIComponent(key)] = decodeURIComponent(value);
- });
- return obj;
-}
+import {
+ AccountMenu,
+ ActionsMenu,
+ ComponentModal,
+ ComponentView,
+ ConflictResolutionModal,
+ EditorMenu,
+ InputModal,
+ MenuRow,
+ PanelResizer,
+ PasswordWizard,
+ PermissionsModal,
+ PrivilegesAuthModal,
+ PrivilegesManagementModal,
+ RevisionPreviewModal,
+ SessionHistoryMenu,
+ SyncResolutionMenu
+} from './directives/views';
-function getPlatformString() {
- try {
- var platform = navigator.platform.toLowerCase();
- var trimmed = "";
- if(platform.indexOf("mac") !== -1) {
- trimmed = "mac";
- } else if(platform.indexOf("win") !== -1) {
- trimmed = "windows";
- } if(platform.indexOf("linux") !== -1) {
- trimmed = "linux";
- }
+import { appDate, appDateTime, trusted } from './filters';
- return trimmed + (isDesktopApplication() ? "-desktop" : "-web");
- } catch (e) {
- return null;
- }
-}
+import {
+ ActionsManager,
+ ArchiveManager,
+ AuthManager,
+ ComponentManager,
+ DBManager,
+ DesktopManager,
+ HttpManager,
+ KeyboardManager,
+ MigrationManager,
+ ModelManager,
+ NativeExtManager,
+ PasscodeManager,
+ PrivilegesManager,
+ SessionHistory,
+ SingletonManager,
+ StatusManager,
+ StorageManager,
+ SyncManager,
+ ThemeManager,
+ AlertManager
+} from './services';
-function isDesktopApplication() {
- return window.isElectron;
-}
+angular.module('app', ['ngSanitize']);
-/* Use with numbers and strings, not objects */
-Array.prototype.containsPrimitiveSubset = function(array) {
- return !array.some(val => this.indexOf(val) === -1);
-}
+// Config
+angular
+ .module('app')
+ .config(configRoutes)
+ .constant('appVersion', __VERSION__);
-// https://tc39.github.io/ecma262/#sec-array.prototype.includes
-if (!Array.prototype.includes) {
- Object.defineProperty(Array.prototype, 'includes', {
- value: function(searchElement, fromIndex) {
+// Controllers
+angular
+ .module('app')
+ .directive('home', () => new Home())
+ .directive('tagsPanel', () => new TagsPanel())
+ .directive('notesPanel', () => new NotesPanel())
+ .directive('editorPanel', () => new EditorPanel())
+ .directive('footer', () => new Footer())
+ .directive('lockScreen', () => new LockScreen());
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
+// Directives - Functional
+angular
+ .module('app')
+ .directive('snAutofocus', ['$timeout', autofocus])
+ .directive('clickOutside', ['$document', clickOutside])
+ .directive('delayHide', delayHide)
+ .directive('elemReady', elemReady)
+ .directive('fileChange', fileChange)
+ .directive('infiniteScroll', [
+ '$rootScope',
+ '$window',
+ '$timeout',
+ infiniteScroll
+ ])
+ .directive('lowercase', lowercase)
+ .directive('selectOnClick', ['$window', selectOnClick])
+ .directive('snEnter', snEnter);
- // 1. Let O be ? ToObject(this value).
- var o = Object(this);
+// Directives - Views
+angular
+ .module('app')
+ .directive('accountMenu', () => new AccountMenu())
+ .directive('actionsMenu', () => new ActionsMenu())
+ .directive('componentModal', () => new ComponentModal())
+ .directive(
+ 'componentView',
+ ($rootScope, componentManager, desktopManager, $timeout) =>
+ new ComponentView($rootScope, componentManager, desktopManager, $timeout)
+ )
+ .directive('conflictResolutionModal', () => new ConflictResolutionModal())
+ .directive('editorMenu', () => new EditorMenu())
+ .directive('inputModal', () => new InputModal())
+ .directive('menuRow', () => new MenuRow())
+ .directive('panelResizer', () => new PanelResizer())
+ .directive('passwordWizard', () => new PasswordWizard())
+ .directive('permissionsModal', () => new PermissionsModal())
+ .directive('privilegesAuthModal', () => new PrivilegesAuthModal())
+ .directive('privilegesManagementModal', () => new PrivilegesManagementModal())
+ .directive('revisionPreviewModal', () => new RevisionPreviewModal())
+ .directive('sessionHistoryMenu', () => new SessionHistoryMenu())
+ .directive('syncResolutionMenu', () => new SyncResolutionMenu());
- // 2. Let len be ? ToLength(? Get(O, "length")).
- var len = o.length >>> 0;
+// Filters
+angular
+ .module('app')
+ .filter('appDate', appDate)
+ .filter('appDateTime', appDateTime)
+ .filter('trusted', ['$sce', trusted]);
- // 3. If len is 0, return false.
- if (len === 0) {
- return false;
- }
-
- // 4. Let n be ? ToInteger(fromIndex).
- // (If fromIndex is undefined, this step produces the value 0.)
- var n = fromIndex | 0;
-
- // 5. If n ≥ 0, then
- // a. Let k be n.
- // 6. Else n < 0,
- // a. Let k be len + n.
- // b. If k < 0, let k be 0.
- var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
-
- function sameValueZero(x, y) {
- return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
- }
-
- // 7. Repeat, while k < len
- while (k < len) {
- // a. Let elementK be the result of ? Get(O, ! ToString(k)).
- // b. If SameValueZero(searchElement, elementK) is true, return true.
- if (sameValueZero(o[k], searchElement)) {
- return true;
- }
- // c. Increase k by 1.
- k++;
- }
-
- // 8. Return false
- return false;
- }
- });
-}
+// Services
+angular
+ .module('app')
+ .service('actionsManager', ActionsManager)
+ .service('archiveManager', ArchiveManager)
+ .service('authManager', AuthManager)
+ .service('componentManager', ComponentManager)
+ .service('dbManager', DBManager)
+ .service('desktopManager', DesktopManager)
+ .service('httpManager', HttpManager)
+ .service('keyboardManager', KeyboardManager)
+ .service('migrationManager', MigrationManager)
+ .service('modelManager', ModelManager)
+ .service('nativeExtManager', NativeExtManager)
+ .service('passcodeManager', PasscodeManager)
+ .service('privilegesManager', PrivilegesManager)
+ .service('sessionHistory', SessionHistory)
+ .service('singletonManager', SingletonManager)
+ .service('statusManager', StatusManager)
+ .service('storageManager', StorageManager)
+ .service('syncManager', SyncManager)
+ .service('alertManager', AlertManager)
+ .service('themeManager', ThemeManager);
diff --git a/app/assets/javascripts/app/constants.js b/app/assets/javascripts/app/constants.js
deleted file mode 100644
index 99dcba485..000000000
--- a/app/assets/javascripts/app/constants.js
+++ /dev/null
@@ -1,5 +0,0 @@
-angular.module('app')
-
-.constant('appVersion', '3.0.22')
-
-;
\ No newline at end of file
diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js
index 77b980bb8..6b373d470 100644
--- a/app/assets/javascripts/app/controllers/editor.js
+++ b/app/assets/javascripts/app/controllers/editor.js
@@ -1,31 +1,51 @@
-angular.module('app')
- .directive("editorSection", function($timeout, $sce){
- return {
- restrict: 'E',
- scope: {
- remove: "&",
- note: "=",
- updateTags: "&"
- },
- templateUrl: 'editor.html',
- replace: true,
- controller: 'EditorCtrl',
- controllerAs: 'ctrl',
- bindToController: true,
- link:function(scope, elem, attrs, ctrl) {
- scope.$watch('ctrl.note', (note, oldNote) => {
- if(note) {
- ctrl.noteDidChange(note, oldNote);
- }
- });
+import angular from 'angular';
+import { SFModelManager } from 'snjs';
+import { isDesktopApplication } from '@/utils';
+import { KeyboardManager } from '@/services/keyboardManager';
+import { PrivilegesManager } from '@/services/privilegesManager';
+import template from '%/editor.pug';
+
+export class EditorPanel {
+ constructor() {
+ this.restrict = 'E';
+ this.scope = {
+ remove: '&',
+ note: '=',
+ updateTags: '&'
+ };
+
+ this.template = template;
+ this.replace = true;
+ this.controllerAs = 'ctrl';
+ this.bindToController = true;
+ }
+
+ link(scope, elem, attrs, ctrl) {
+ scope.$watch('ctrl.note', (note, oldNote) => {
+ if (note) {
+ ctrl.noteDidChange(note, oldNote);
}
- }
- })
- .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager,
- syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory,
- privilegesManager, keyboardManager, desktopManager, alertManager) {
+ });
+ }
+ /* @ngInject */
+ controller(
+ $timeout,
+ authManager,
+ $rootScope,
+ actionsManager,
+ syncManager,
+ modelManager,
+ themeManager,
+ componentManager,
+ storageManager,
+ sessionHistory,
+ privilegesManager,
+ keyboardManager,
+ desktopManager,
+ alertManager
+ ) {
this.spellcheck = true;
this.componentManager = componentManager;
this.componentStack = [];
@@ -412,7 +432,7 @@ angular.module('app')
let title = this.note.safeTitle().length ? `'${this.note.title}'` : "this note";
let text = permanently ? `Are you sure you want to permanently delete ${title}?`
- : `Are you sure you want to move ${title} to the trash?`
+ : `Are you sure you want to move ${title} to the trash?`
alertManager.confirm({text, destructive: true, onConfirm: () => {
if(permanently) {
@@ -851,10 +871,10 @@ angular.module('app')
this.loadedTabListener = true;
/**
- * Insert 4 spaces when a tab key is pressed,
- * only used when inside of the text editor.
- * If the shift key is pressed first, this event is
- * not fired.
+ * Insert 4 spaces when a tab key is pressed,
+ * only used when inside of the text editor.
+ * If the shift key is pressed first, this event is
+ * not fired.
*/
const editor = document.getElementById("note-text-editor");
@@ -880,9 +900,9 @@ angular.module('app')
var end = editor.selectionEnd;
var spaces = " ";
- // Insert 4 spaces
+ // Insert 4 spaces
editor.value = editor.value.substring(0, start)
- + spaces + editor.value.substring(end);
+ + spaces + editor.value.substring(end);
// Place cursor 4 spaces away from where
// the tab key was pressed
@@ -897,11 +917,12 @@ angular.module('app')
})
// This handles when the editor itself is destroyed, and not when our controller is destroyed.
- angular.element(editor).on('$destroy', function(){
+ angular.element(editor).on('$destroy', () => {
if(this.tabObserver) {
keyboardManager.removeKeyObserver(this.tabObserver);
this.loadedTabListener = false;
}
- }.bind(this));
- }
-});
+ });
+ };
+ }
+}
diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js
index 34eee126f..30544f7b1 100644
--- a/app/assets/javascripts/app/controllers/footer.js
+++ b/app/assets/javascripts/app/controllers/footer.js
@@ -1,313 +1,328 @@
-angular.module('app')
- .directive("footer", function(authManager){
- return {
- restrict: 'E',
- scope: {},
- templateUrl: 'footer.html',
- replace: true,
- controller: 'FooterCtrl',
- controllerAs: 'ctrl',
- bindToController: true,
+import { PrivilegesManager } from '@/services/privilegesManager';
+import template from '%/footer.pug';
- link:function(scope, elem, attrs, ctrl) {
- scope.$on("sync:completed", function(){
- ctrl.syncUpdated();
- ctrl.findErrors();
- ctrl.updateOfflineStatus();
- })
- scope.$on("sync:error", function(){
- ctrl.findErrors();
- ctrl.updateOfflineStatus();
- })
- }
+export class Footer {
+ constructor() {
+ this.restrict = 'E';
+ this.scope = {};
+ this.template = template;
+ this.replace = true;
+ this.controllerAs = 'ctrl';
+ this.bindToController = true;
+ }
+
+ link(scope, elem, attrs, ctrl) {
+ scope.$on('sync:completed', function() {
+ ctrl.syncUpdated();
+ ctrl.findErrors();
+ ctrl.updateOfflineStatus();
+ });
+ scope.$on('sync:error', function() {
+ ctrl.findErrors();
+ ctrl.updateOfflineStatus();
+ });
+ }
+
+ /* @ngInject */
+ controller(
+ $rootScope,
+ authManager,
+ modelManager,
+ $timeout,
+ dbManager,
+ syncManager,
+ storageManager,
+ passcodeManager,
+ componentManager,
+ singletonManager,
+ nativeExtManager,
+ privilegesManager,
+ statusManager,
+ alertManager
+ ) {
+ authManager.checkForSecurityUpdate().then((available) => {
+ this.securityUpdateAvailable = available;
+ })
+
+ $rootScope.$on("security-update-status-changed", () => {
+ this.securityUpdateAvailable = authManager.securityUpdateAvailable;
+ })
+
+ statusManager.addStatusObserver((string) => {
+ $timeout(() => {
+ this.arbitraryStatusMessage = string;
+ })
+ })
+
+ $rootScope.$on("did-begin-local-backup", () => {
+ $timeout(() => {
+ this.backupStatus = statusManager.addStatusFromString("Saving local backup...");
+ })
+ });
+
+ $rootScope.$on("did-finish-local-backup", (event, data) => {
+ $timeout(() => {
+ if(data.success) {
+ this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Successfully saved backup.");
+ } else {
+ this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Unable to save local backup.");
+ }
+
+ $timeout(() => {
+ this.backupStatus = statusManager.removeStatus(this.backupStatus);
+ }, 2000)
+ })
+ });
+
+ this.openSecurityUpdate = function() {
+ authManager.presentPasswordWizard("upgrade-security");
}
- })
- .controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager,
- syncManager, storageManager, passcodeManager, componentManager, singletonManager, nativeExtManager,
- privilegesManager, statusManager, alertManager) {
- authManager.checkForSecurityUpdate().then((available) => {
- this.securityUpdateAvailable = available;
- })
+ $rootScope.$on("reload-ext-data", () => {
+ this.reloadExtendedData();
+ });
- $rootScope.$on("security-update-status-changed", () => {
- this.securityUpdateAvailable = authManager.securityUpdateAvailable;
- })
+ this.reloadExtendedData = () => {
+ if(this.reloadInProgress) { return; }
+ this.reloadInProgress = true;
- statusManager.addStatusObserver((string) => {
- $timeout(() => {
- this.arbitraryStatusMessage = string;
- })
- })
-
- $rootScope.$on("did-begin-local-backup", () => {
- $timeout(() => {
- this.backupStatus = statusManager.addStatusFromString("Saving local backup...");
- })
- });
-
- $rootScope.$on("did-finish-local-backup", (event, data) => {
- $timeout(() => {
- if(data.success) {
- this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Successfully saved backup.");
- } else {
- this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Unable to save local backup.");
- }
-
- $timeout(() => {
- this.backupStatus = statusManager.removeStatus(this.backupStatus);
- }, 2000)
- })
- });
-
- this.openSecurityUpdate = function() {
- authManager.presentPasswordWizard("upgrade-security");
+ // A reload occurs when the extensions manager window is opened. We can close it after a delay
+ let extWindow = this.rooms.find((room) => {return room.package_info.identifier == nativeExtManager.extensionsManagerIdentifier});
+ if(!extWindow) {
+ this.queueExtReload = true; // try again when the ext is available
+ this.reloadInProgress = false;
+ return;
}
- $rootScope.$on("reload-ext-data", () => {
- this.reloadExtendedData();
- });
-
- this.reloadExtendedData = () => {
- if(this.reloadInProgress) { return; }
- this.reloadInProgress = true;
-
- // A reload occurs when the extensions manager window is opened. We can close it after a delay
- let extWindow = this.rooms.find((room) => {return room.package_info.identifier == nativeExtManager.extensionsManagerIdentifier});
- if(!extWindow) {
- this.queueExtReload = true; // try again when the ext is available
- this.reloadInProgress = false;
- return;
- }
+ this.selectRoom(extWindow);
+ $timeout(() => {
this.selectRoom(extWindow);
+ this.reloadInProgress = false;
+ $rootScope.$broadcast("ext-reload-complete");
+ }, 2000);
+ }
- $timeout(() => {
- this.selectRoom(extWindow);
- this.reloadInProgress = false;
- $rootScope.$broadcast("ext-reload-complete");
- }, 2000);
- }
+ this.getUser = function() {
+ return authManager.user;
+ }
- this.getUser = function() {
- return authManager.user;
- }
-
- this.updateOfflineStatus = function() {
- this.offline = authManager.offline();
- }
- this.updateOfflineStatus();
+ this.updateOfflineStatus = function() {
+ this.offline = authManager.offline();
+ }
+ this.updateOfflineStatus();
- syncManager.addEventHandler((syncEvent, data) => {
- $timeout(() => {
- if(syncEvent == "local-data-loaded") {
- // If the user has no notes and is offline, show Account menu
- if(this.offline && modelManager.noteCount() == 0) {
- this.showAccountMenu = true;
- }
- } else if(syncEvent == "enter-out-of-sync") {
- this.outOfSync = true;
- } else if(syncEvent == "exit-out-of-sync") {
- this.outOfSync = false;
+ syncManager.addEventHandler((syncEvent, data) => {
+ $timeout(() => {
+ if(syncEvent == "local-data-loaded") {
+ // If the user has no notes and is offline, show Account menu
+ if(this.offline && modelManager.noteCount() == 0) {
+ this.showAccountMenu = true;
}
- })
- });
-
- this.findErrors = function() {
- this.error = syncManager.syncStatus.error;
- }
- this.findErrors();
-
- this.onAuthSuccess = function() {
- this.showAccountMenu = false;
- }.bind(this)
-
- this.accountMenuPressed = function() {
- this.showAccountMenu = !this.showAccountMenu;
- this.closeAllRooms();
- }
-
- this.toggleSyncResolutionMenu = function() {
- this.showSyncResolution = !this.showSyncResolution;
- }.bind(this);
-
- this.closeAccountMenu = () => {
- this.showAccountMenu = false;
- }
-
- this.hasPasscode = function() {
- return passcodeManager.hasPasscode();
- }
-
- this.lockApp = function() {
- $rootScope.lockApplication();
- }
-
- this.refreshData = function() {
- this.isRefreshing = true;
- // Enable integrity checking for this force request
- syncManager.sync({force: true, performIntegrityCheck: true}).then((response) => {
- $timeout(function(){
- this.isRefreshing = false;
- }.bind(this), 200)
- if(response && response.error) {
- alertManager.alert({text: "There was an error syncing. Please try again. If all else fails, try signing out and signing back in."});
- } else {
- this.syncUpdated();
- }
- });
- }
-
- this.syncUpdated = function() {
- this.lastSyncDate = new Date();
- }
-
- $rootScope.$on("new-update-available", () => {
- $timeout(() => {
- this.onNewUpdateAvailable();
- })
+ } else if(syncEvent == "enter-out-of-sync") {
+ this.outOfSync = true;
+ } else if(syncEvent == "exit-out-of-sync") {
+ this.outOfSync = false;
+ }
})
+ });
- this.onNewUpdateAvailable = function() {
- this.newUpdateAvailable = true;
- }
+ this.findErrors = function() {
+ this.error = syncManager.syncStatus.error;
+ }
+ this.findErrors();
- this.clickedNewUpdateAnnouncement = function() {
- this.newUpdateAvailable = false;
- alertManager.alert({text: "A new update is ready to install. Please use the top-level 'Updates' menu to manage installation."})
- }
+ this.onAuthSuccess = function() {
+ this.showAccountMenu = false;
+ }.bind(this)
+ this.accountMenuPressed = function() {
+ this.showAccountMenu = !this.showAccountMenu;
+ this.closeAllRooms();
+ }
- /* Rooms */
+ this.toggleSyncResolutionMenu = function() {
+ this.showSyncResolution = !this.showSyncResolution;
+ }.bind(this);
- this.componentManager = componentManager;
- this.rooms = [];
- this.themesWithIcons = [];
+ this.closeAccountMenu = () => {
+ this.showAccountMenu = false;
+ }
- modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => {
- this.rooms = modelManager.components.filter((candidate) => {return candidate.area == "rooms" && !candidate.deleted});
- if(this.queueExtReload) {
- this.queueExtReload = false;
- this.reloadExtendedData();
+ this.hasPasscode = function() {
+ return passcodeManager.hasPasscode();
+ }
+
+ this.lockApp = function() {
+ $rootScope.lockApplication();
+ }
+
+ this.refreshData = function() {
+ this.isRefreshing = true;
+ // Enable integrity checking for this force request
+ syncManager.sync({force: true, performIntegrityCheck: true}).then((response) => {
+ $timeout(function(){
+ this.isRefreshing = false;
+ }.bind(this), 200)
+ if(response && response.error) {
+ alertManager.alert({text: "There was an error syncing. Please try again. If all else fails, try signing out and signing back in."});
+ } else {
+ this.syncUpdated();
}
});
+ }
- modelManager.addItemSyncObserver("footer-bar-themes", "SN|Theme", (allItems, validItems, deletedItems, source) => {
- let themes = modelManager.validItemsForContentType("SN|Theme").filter((candidate) => {
- return !candidate.deleted && candidate.content.package_info && candidate.content.package_info.dock_icon;
- }).sort((a, b) => {
- return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
- });
+ this.syncUpdated = function() {
+ this.lastSyncDate = new Date();
+ }
- let differ = themes.length != this.themesWithIcons.length;
+ $rootScope.$on("new-update-available", () => {
+ $timeout(() => {
+ this.onNewUpdateAvailable();
+ })
+ })
- this.themesWithIcons = themes;
+ this.onNewUpdateAvailable = function() {
+ this.newUpdateAvailable = true;
+ }
- if(differ) {
- this.reloadDockShortcuts();
- }
+ this.clickedNewUpdateAnnouncement = function() {
+ this.newUpdateAvailable = false;
+ alertManager.alert({text: "A new update is ready to install. Please use the top-level 'Updates' menu to manage installation."})
+ }
+
+
+ /* Rooms */
+
+ this.componentManager = componentManager;
+ this.rooms = [];
+ this.themesWithIcons = [];
+
+ modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => {
+ this.rooms = modelManager.components.filter((candidate) => {return candidate.area == "rooms" && !candidate.deleted});
+ if(this.queueExtReload) {
+ this.queueExtReload = false;
+ this.reloadExtendedData();
+ }
+ });
+
+ modelManager.addItemSyncObserver("footer-bar-themes", "SN|Theme", (allItems, validItems, deletedItems, source) => {
+ let themes = modelManager.validItemsForContentType("SN|Theme").filter((candidate) => {
+ return !candidate.deleted && candidate.content.package_info && candidate.content.package_info.dock_icon;
+ }).sort((a, b) => {
+ return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
- this.reloadDockShortcuts = function() {
- let shortcuts = [];
- for(var theme of this.themesWithIcons) {
- var name = theme.content.package_info.name;
- var icon = theme.content.package_info.dock_icon;
- if(!icon) {
- continue;
- }
- shortcuts.push({
- name: name,
- component: theme,
- icon: icon
- })
+ let differ = themes.length != this.themesWithIcons.length;
+
+ this.themesWithIcons = themes;
+
+ if(differ) {
+ this.reloadDockShortcuts();
+ }
+ });
+
+ this.reloadDockShortcuts = function() {
+ let shortcuts = [];
+ for(var theme of this.themesWithIcons) {
+ var name = theme.content.package_info.name;
+ var icon = theme.content.package_info.dock_icon;
+ if(!icon) {
+ continue;
}
-
- this.dockShortcuts = shortcuts.sort((a, b) => {
- // circles first, then images
-
- var aType = a.icon.type;
- var bType = b.icon.type;
-
- if(aType == bType) {
- return 0;
- } else if(aType == "circle" && bType == "svg") {
- return -1;
- } else if(bType == "circle" && aType == "svg") {
- return 1;
- }
- });
+ shortcuts.push({
+ name: name,
+ component: theme,
+ icon: icon
+ })
}
- this.initSvgForShortcut = function(shortcut) {
- var id = "dock-svg-" + shortcut.component.uuid;
- var element = document.getElementById(id);
- var parser = new DOMParser();
- var svg = shortcut.component.content.package_info.dock_icon.source;
- var doc = parser.parseFromString(svg, "image/svg+xml");
- element.appendChild(doc.documentElement);
- }
+ this.dockShortcuts = shortcuts.sort((a, b) => {
+ // circles first, then images
- this.selectShortcut = function(shortcut) {
- componentManager.toggleComponent(shortcut.component);
- }
+ var aType = a.icon.type;
+ var bType = b.icon.type;
- componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => {
- // RIP: There used to be code here that checked if component.active was true, and if so, displayed the component.
- // However, we no longer want to persist active state for footer extensions. If you open Extensions on one computer,
- // it shouldn't open on another computer. Active state should only be persisted for persistent extensions, like Folders.
- }, actionHandler: (component, action, data) => {
- if(action == "set-size") {
- component.setLastSize(data);
+ if(aType == bType) {
+ return 0;
+ } else if(aType == "circle" && bType == "svg") {
+ return -1;
+ } else if(bType == "circle" && aType == "svg") {
+ return 1;
}
- }, focusHandler: (component, focused) => {
- if(component.isEditor() && focused) {
- this.closeAllRooms();
- this.closeAccountMenu();
- }
- }});
+ });
+ }
- $rootScope.$on("editorFocused", () => {
+ this.initSvgForShortcut = function(shortcut) {
+ var id = "dock-svg-" + shortcut.component.uuid;
+ var element = document.getElementById(id);
+ var parser = new DOMParser();
+ var svg = shortcut.component.content.package_info.dock_icon.source;
+ var doc = parser.parseFromString(svg, "image/svg+xml");
+ element.appendChild(doc.documentElement);
+ }
+
+ this.selectShortcut = function(shortcut) {
+ componentManager.toggleComponent(shortcut.component);
+ }
+
+ componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => {
+ // RIP: There used to be code here that checked if component.active was true, and if so, displayed the component.
+ // However, we no longer want to persist active state for footer extensions. If you open Extensions on one computer,
+ // it shouldn't open on another computer. Active state should only be persisted for persistent extensions, like Folders.
+ }, actionHandler: (component, action, data) => {
+ if(action == "set-size") {
+ component.setLastSize(data);
+ }
+ }, focusHandler: (component, focused) => {
+ if(component.isEditor() && focused) {
this.closeAllRooms();
this.closeAccountMenu();
- })
+ }
+ }});
- this.onRoomDismiss = function(room) {
+ $rootScope.$on("editorFocused", () => {
+ this.closeAllRooms();
+ this.closeAccountMenu();
+ })
+
+ this.onRoomDismiss = function(room) {
+ room.showRoom = false;
+ }
+
+ this.closeAllRooms = function() {
+ for(var room of this.rooms) {
room.showRoom = false;
}
+ }
- this.closeAllRooms = function() {
- for(var room of this.rooms) {
- room.showRoom = false;
- }
+ this.selectRoom = async function(room) {
+ let run = () => {
+ $timeout(() => {
+ room.showRoom = !room.showRoom;
+ })
}
- this.selectRoom = async function(room) {
- let run = () => {
- $timeout(() => {
- room.showRoom = !room.showRoom;
- })
- }
-
- if(!room.showRoom) {
- // About to show, check if has privileges
- if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageExtensions)) {
- privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageExtensions, () => {
- run();
- });
- } else {
+ if(!room.showRoom) {
+ // About to show, check if has privileges
+ if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageExtensions)) {
+ privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageExtensions, () => {
run();
- }
+ });
} else {
run();
}
+ } else {
+ run();
}
+ }
- this.clickOutsideAccountMenu = function() {
- if(privilegesManager.authenticationInProgress()) {
- return;
- }
- this.showAccountMenu = false;
+ this.clickOutsideAccountMenu = function() {
+ if(privilegesManager.authenticationInProgress()) {
+ return;
}
-});
+ this.showAccountMenu = false;
+ }
+ }
+}
diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js
index 90f7e0ca2..48822ddd5 100644
--- a/app/assets/javascripts/app/controllers/home.js
+++ b/app/assets/javascripts/app/controllers/home.js
@@ -1,8 +1,31 @@
-angular.module('app')
-.controller('HomeCtrl', function ($scope, $location, $rootScope, $timeout, modelManager,
- dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager,
- privilegesManager, statusManager, alertManager) {
+import _ from 'lodash';
+import { SFAuthManager } from 'snjs';
+import { getPlatformString } from '@/utils';
+import template from '%/home.pug';
+export class Home {
+ constructor() {
+ this.template = template;
+ }
+
+ /* @ngInject */
+ controller(
+ $scope,
+ $location,
+ $rootScope,
+ $timeout,
+ modelManager,
+ dbManager,
+ syncManager,
+ authManager,
+ themeManager,
+ passcodeManager,
+ storageManager,
+ migrationManager,
+ privilegesManager,
+ statusManager,
+ alertManager
+ ) {
storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession());
$scope.platform = getPlatformString();
@@ -253,9 +276,9 @@ angular.module('app')
$rootScope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest')
- this.$eval(fn);
+ this.$eval(fn);
else
- this.$apply(fn);
+ this.$apply(fn);
};
$rootScope.notifyDelete = function() {
@@ -293,9 +316,9 @@ angular.module('app')
}
/*
- Disable dragging and dropping of files into main SN interface.
- both 'dragover' and 'drop' are required to prevent dropping of files.
- This will not prevent extensions from receiving drop events.
+ Disable dragging and dropping of files into main SN interface.
+ both 'dragover' and 'drop' are required to prevent dropping of files.
+ This will not prevent extensions from receiving drop events.
*/
window.addEventListener('dragover', (event) => {
event.preventDefault();
@@ -308,7 +331,7 @@ angular.module('app')
/*
- Handle Auto Sign In From URL
+ Handle Auto Sign In From URL
*/
function urlParam(key) {
@@ -341,4 +364,5 @@ angular.module('app')
if(urlParam("server")) {
autoSignInFromParams();
}
-});
+ }
+}
diff --git a/app/assets/javascripts/app/controllers/index.js b/app/assets/javascripts/app/controllers/index.js
new file mode 100644
index 000000000..00936ae3e
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/index.js
@@ -0,0 +1,6 @@
+export { EditorPanel } from './editor';
+export { Footer } from './footer';
+export { NotesPanel } from './notes';
+export { TagsPanel } from './tags';
+export { Home } from './home';
+export { LockScreen } from './lockScreen';
diff --git a/app/assets/javascripts/app/controllers/lockScreen.js b/app/assets/javascripts/app/controllers/lockScreen.js
index 1f168838e..60c7842c3 100644
--- a/app/assets/javascripts/app/controllers/lockScreen.js
+++ b/app/assets/javascripts/app/controllers/lockScreen.js
@@ -1,16 +1,16 @@
-class LockScreen {
+import template from '%/lock-screen.pug';
+export class LockScreen {
constructor() {
this.restrict = "E";
- this.templateUrl = "lock-screen.html";
+ this.template = template;
this.scope = {
onSuccess: "&",
};
}
+ /* @ngInject */
controller($scope, passcodeManager, authManager, syncManager, storageManager, alertManager) {
- 'ngInject';
-
$scope.formData = {};
this.visibilityObserver = passcodeManager.addVisibilityObserver((visible) => {
@@ -53,5 +53,3 @@ class LockScreen {
}
}
}
-
-angular.module('app').directive('lockScreen', () => new LockScreen);
diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js
index 3ba466548..e23622348 100644
--- a/app/assets/javascripts/app/controllers/notes.js
+++ b/app/assets/javascripts/app/controllers/notes.js
@@ -1,30 +1,45 @@
-angular.module('app')
- .directive("notesSection", function(){
- return {
- scope: {
- addNew: "&",
- selectionMade: "&",
- tag: "="
- },
+import _ from 'lodash';
+import angular from 'angular';
+import { SFAuthManager } from 'snjs';
+import { PrivilegesManager } from '@/services/privilegesManager';
+import { KeyboardManager } from '@/services/keyboardManager';
+import template from '%/notes.pug';
- templateUrl: 'notes.html',
- replace: true,
- controller: 'NotesCtrl',
- controllerAs: 'ctrl',
- bindToController: true,
+export class NotesPanel {
+ constructor() {
+ this.scope = {
+ addNew: '&',
+ selectionMade: '&',
+ tag: '='
+ };
- link:function(scope, elem, attrs, ctrl) {
- scope.$watch('ctrl.tag', (tag, oldTag) => {
- if(tag) {
- ctrl.tagDidChange(tag, oldTag);
- }
- });
+ this.template = template;
+ this.replace = true;
+
+ this.controllerAs = 'ctrl';
+ this.bindToController = true;
+ }
+
+ link(scope, elem, attrs, ctrl) {
+ scope.$watch('ctrl.tag', (tag, oldTag) => {
+ if (tag) {
+ ctrl.tagDidChange(tag, oldTag);
}
- }
- })
- .controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager,
- syncManager, storageManager, desktopManager, privilegesManager, keyboardManager) {
+ });
+ }
+ /* @ngInject */
+ controller(
+ authManager,
+ $timeout,
+ $rootScope,
+ modelManager,
+ syncManager,
+ storageManager,
+ desktopManager,
+ privilegesManager,
+ keyboardManager
+ ) {
this.panelController = {};
this.searchSubmitted = false;
@@ -671,5 +686,5 @@ angular.module('app')
if(searchBar) {searchBar.focus()};
}
})
-
- });
+ }
+}
diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js
index a6136b010..3314da196 100644
--- a/app/assets/javascripts/app/controllers/tags.js
+++ b/app/assets/javascripts/app/controllers/tags.js
@@ -1,21 +1,30 @@
-angular.module('app')
- .directive("tagsSection", function(){
- return {
- restrict: 'E',
- scope: {
- addNew: "&",
- selectionMade: "&",
- save: "&",
- removeTag: "&"
- },
- templateUrl: 'tags.html',
- replace: true,
- controller: 'TagsCtrl',
- controllerAs: 'ctrl',
- bindToController: true,
- }
- })
- .controller('TagsCtrl', function ($rootScope, modelManager, syncManager, $timeout, componentManager, authManager) {
+import { SNNote, SNSmartTag } from 'snjs';
+import template from '%/tags.pug';
+
+export class TagsPanel {
+ constructor() {
+ this.restrict = 'E';
+ this.scope = {
+ addNew: '&',
+ selectionMade: '&',
+ save: '&',
+ removeTag: '&'
+ };
+ this.template = template;
+ this.replace = true;
+ this.controllerAs = 'ctrl';
+ this.bindToController = true;
+ }
+
+ /* @ngInject */
+ controller(
+ $rootScope,
+ modelManager,
+ syncManager,
+ $timeout,
+ componentManager,
+ authManager
+ ) {
// Wrap in timeout so that selectTag is defined
$timeout(() => {
this.smartTags = modelManager.getSmartTags();
@@ -166,4 +175,5 @@ angular.module('app')
this.removeTag()(tag);
this.selectTag(this.smartTags[0]);
}
- });
+ }
+}
diff --git a/app/assets/javascripts/app/directives/functional/autofocus.js b/app/assets/javascripts/app/directives/functional/autofocus.js
index 8bcf6c1d1..46fc64ec1 100644
--- a/app/assets/javascripts/app/directives/functional/autofocus.js
+++ b/app/assets/javascripts/app/directives/functional/autofocus.js
@@ -1,17 +1,16 @@
-angular
- .module('app')
- .directive('snAutofocus', ['$timeout', function($timeout) {
- return {
- restrict: 'A',
- scope: {
- shouldFocus: "="
- },
- link : function($scope, $element) {
- $timeout(function() {
- if($scope.shouldFocus) {
- $element[0].focus();
- }
- });
- }
+/* @ngInject */
+export function autofocus($timeout) {
+ return {
+ restrict: 'A',
+ scope: {
+ shouldFocus: '='
+ },
+ link: function($scope, $element) {
+ $timeout(function() {
+ if ($scope.shouldFocus) {
+ $element[0].focus();
+ }
+ });
}
- }]);
+ };
+}
diff --git a/app/assets/javascripts/app/directives/functional/click-outside.js b/app/assets/javascripts/app/directives/functional/click-outside.js
index 24daf7eed..cbfad8dbe 100644
--- a/app/assets/javascripts/app/directives/functional/click-outside.js
+++ b/app/assets/javascripts/app/directives/functional/click-outside.js
@@ -1,28 +1,29 @@
-angular.module('app').directive('clickOutside', ['$document', function($document) {
+/* @ngInject */
+export function clickOutside($document) {
return {
restrict: 'A',
replace: false,
link: function($scope, $element, attrs) {
-
- let didApplyClickOutside = false;
+ var didApplyClickOutside = false;
$element.bind('click', function(e) {
didApplyClickOutside = false;
- if(attrs.isOpen) {
+ if (attrs.isOpen) {
e.stopPropagation();
}
});
- $document.bind('click', function(event) {
+ $document.bind('click', function() {
// Ignore click if on SKAlert
- if(event.target.closest(".sk-modal")) {
+ if (event.target.closest(".sk-modal")) {
return;
}
- if(!didApplyClickOutside) {
+
+ if (!didApplyClickOutside) {
$scope.$apply(attrs.clickOutside);
didApplyClickOutside = true;
}
- })
+ });
}
- }
-}]);
+ };
+}
diff --git a/app/assets/javascripts/app/directives/functional/delay-hide.js b/app/assets/javascripts/app/directives/functional/delay-hide.js
index fd52d70fb..e03b54580 100644
--- a/app/assets/javascripts/app/directives/functional/delay-hide.js
+++ b/app/assets/javascripts/app/directives/functional/delay-hide.js
@@ -1,46 +1,44 @@
-angular
- .module('app')
- .directive('delayHide', function($timeout) {
- return {
- restrict: 'A',
- scope: {
- show: '=',
- delay: '@'
- },
- link: function(scope, elem, attrs) {
- var showTimer;
+import angular from 'angular';
- showElement(false);
+/* @ngInject */
+export function delayHide($timeout) {
+ return {
+ restrict: 'A',
+ scope: {
+ show: '=',
+ delay: '@'
+ },
+ link: function(scope, elem, attrs) {
+ showElement(false);
- //This is where all the magic happens!
- // Whenever the scope variable updates we simply
- // show if it evaluates to 'true' and hide if 'false'
- scope.$watch('show', function(newVal){
- newVal ? showSpinner() : hideSpinner();
- });
+ // This is where all the magic happens!
+ // Whenever the scope variable updates we simply
+ // show if it evaluates to 'true' and hide if 'false'
+ scope.$watch('show', function(newVal) {
+ newVal ? showSpinner() : hideSpinner();
+ });
- function showSpinner() {
- if(scope.hidePromise) {
- $timeout.cancel(scope.hidePromise);
- scope.hidePromise = null;
- }
- showElement(true);
- }
+ function showSpinner() {
+ if (scope.hidePromise) {
+ $timeout.cancel(scope.hidePromise);
+ scope.hidePromise = null;
+ }
+ showElement(true);
+ }
- function hideSpinner() {
- scope.hidePromise = $timeout(showElement.bind(this, false), getDelay());
- }
+ function hideSpinner() {
+ scope.hidePromise = $timeout(showElement.bind(this, false), getDelay());
+ }
- function showElement(show) {
- show ? elem.css({display:''}) : elem.css({display:'none'});
- }
+ function showElement(show) {
+ show ? elem.css({ display: '' }) : elem.css({ display: 'none' });
+ }
- function getDelay() {
- var delay = parseInt(scope.delay);
+ function getDelay() {
+ var delay = parseInt(scope.delay);
- return angular.isNumber(delay) ? delay : 200;
- }
- }
-
- };
-});
+ return angular.isNumber(delay) ? delay : 200;
+ }
+ }
+ };
+}
diff --git a/app/assets/javascripts/app/directives/functional/elemReady.js b/app/assets/javascripts/app/directives/functional/elemReady.js
index 7eef56fa7..1b7809406 100644
--- a/app/assets/javascripts/app/directives/functional/elemReady.js
+++ b/app/assets/javascripts/app/directives/functional/elemReady.js
@@ -1,15 +1,14 @@
-angular
-.module('app')
-.directive( 'elemReady', function( $parse ) {
- return {
- restrict: 'A',
- link: function( $scope, elem, attrs ) {
- elem.ready(function(){
- $scope.$apply(function(){
- var func = $parse(attrs.elemReady);
- func($scope);
- })
- })
- }
+/* @ngInject */
+export function elemReady($parse) {
+ return {
+ restrict: 'A',
+ link: function($scope, elem, attrs) {
+ elem.ready(function() {
+ $scope.$apply(function() {
+ var func = $parse(attrs.elemReady);
+ func($scope);
+ });
+ });
}
-})
+ };
+}
diff --git a/app/assets/javascripts/app/directives/functional/file-change.js b/app/assets/javascripts/app/directives/functional/file-change.js
index c133c0852..1cd82d284 100644
--- a/app/assets/javascripts/app/directives/functional/file-change.js
+++ b/app/assets/javascripts/app/directives/functional/file-change.js
@@ -1,17 +1,16 @@
-angular
- .module('app')
- .directive('fileChange', function() {
- return {
- restrict: 'A',
- scope: {
- handler: '&'
- },
- link: function (scope, element) {
- element.on('change', function (event) {
- scope.$apply(function(){
- scope.handler({files: event.target.files});
+/* @ngInject */
+export function fileChange() {
+ return {
+ restrict: 'A',
+ scope: {
+ handler: '&'
+ },
+ link: function(scope, element) {
+ element.on('change', function(event) {
+ scope.$apply(function() {
+ scope.handler({ files: event.target.files });
});
});
- }
- };
-});
+ }
+ };
+}
diff --git a/app/assets/javascripts/app/directives/functional/index.js b/app/assets/javascripts/app/directives/functional/index.js
new file mode 100644
index 000000000..e9f8b7ca5
--- /dev/null
+++ b/app/assets/javascripts/app/directives/functional/index.js
@@ -0,0 +1,9 @@
+export { autofocus } from './autofocus';
+export { clickOutside } from './click-outside';
+export { delayHide } from './delay-hide';
+export { elemReady } from './elemReady';
+export { fileChange } from './file-change';
+export { infiniteScroll } from './infiniteScroll';
+export { lowercase } from './lowercase';
+export { selectOnClick } from './selectOnClick';
+export { snEnter } from './snEnter';
diff --git a/app/assets/javascripts/app/directives/functional/infiniteScroll.js b/app/assets/javascripts/app/directives/functional/infiniteScroll.js
index 21bacd325..4a8e172a4 100644
--- a/app/assets/javascripts/app/directives/functional/infiniteScroll.js
+++ b/app/assets/javascripts/app/directives/functional/infiniteScroll.js
@@ -1,16 +1,18 @@
-angular.module('app').directive('infiniteScroll', [
-'$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
+/* @ngInject */
+export function infiniteScroll($rootScope, $window, $timeout) {
return {
link: function(scope, elem, attrs) {
var offset = parseInt(attrs.threshold) || 0;
- var e = elem[0]
+ var e = elem[0];
- elem.on('scroll', function(){
- if(scope.$eval(attrs.canLoad) && e.scrollTop + e.offsetHeight >= e.scrollHeight - offset) {
+ elem.on('scroll', function() {
+ if (
+ scope.$eval(attrs.canLoad) &&
+ e.scrollTop + e.offsetHeight >= e.scrollHeight - offset
+ ) {
scope.$apply(attrs.infiniteScroll);
}
});
}
};
}
-]);
diff --git a/app/assets/javascripts/app/directives/functional/lowercase.js b/app/assets/javascripts/app/directives/functional/lowercase.js
index 40ce7421c..e4f7f9781 100644
--- a/app/assets/javascripts/app/directives/functional/lowercase.js
+++ b/app/assets/javascripts/app/directives/functional/lowercase.js
@@ -1,20 +1,19 @@
-angular
- .module('app')
- .directive('lowercase', function() {
- return {
- require: 'ngModel',
- link: function(scope, element, attrs, modelCtrl) {
- var lowercase = function(inputValue) {
- if (inputValue == undefined) inputValue = '';
- var lowercased = inputValue.toLowerCase();
- if (lowercased !== inputValue) {
- modelCtrl.$setViewValue(lowercased);
- modelCtrl.$render();
- }
- return lowercased;
+/* @ngInject */
+export function lowercase() {
+ return {
+ require: 'ngModel',
+ link: function(scope, element, attrs, modelCtrl) {
+ var lowercase = function(inputValue) {
+ if (inputValue === undefined) inputValue = '';
+ var lowercased = inputValue.toLowerCase();
+ if (lowercased !== inputValue) {
+ modelCtrl.$setViewValue(lowercased);
+ modelCtrl.$render();
}
- modelCtrl.$parsers.push(lowercase);
- lowercase(scope[attrs.ngModel]);
- }
- };
- });
+ return lowercased;
+ };
+ modelCtrl.$parsers.push(lowercase);
+ lowercase(scope[attrs.ngModel]);
+ }
+ };
+}
diff --git a/app/assets/javascripts/app/directives/functional/selectOnClick.js b/app/assets/javascripts/app/directives/functional/selectOnClick.js
index 598e45086..e7bccb79b 100644
--- a/app/assets/javascripts/app/directives/functional/selectOnClick.js
+++ b/app/assets/javascripts/app/directives/functional/selectOnClick.js
@@ -1,15 +1,14 @@
-angular
- .module('app')
- .directive('selectOnClick', ['$window', function ($window) {
- return {
- restrict: 'A',
- link: function (scope, element, attrs) {
- element.on('focus', function () {
- if (!$window.getSelection().toString()) {
- // Required for mobile Safari
- this.setSelectionRange(0, this.value.length)
- }
- });
+/* @ngInject */
+export function selectOnClick($window) {
+ return {
+ restrict: 'A',
+ link: function(scope, element, attrs) {
+ element.on('focus', function() {
+ if (!$window.getSelection().toString()) {
+ // Required for mobile Safari
+ this.setSelectionRange(0, this.value.length);
}
- };
-}]);
+ });
+ }
+ };
+}
diff --git a/app/assets/javascripts/app/directives/functional/snEnter.js b/app/assets/javascripts/app/directives/functional/snEnter.js
index fe12e0983..de91a89b1 100644
--- a/app/assets/javascripts/app/directives/functional/snEnter.js
+++ b/app/assets/javascripts/app/directives/functional/snEnter.js
@@ -1,15 +1,14 @@
-angular
-.module('app')
-.directive('snEnter', function() {
+/* @ngInject */
+export function snEnter() {
return function(scope, element, attrs) {
- element.bind("keydown keypress", function(event) {
- if(event.which === 13) {
- scope.$apply(function(){
- scope.$eval(attrs.snEnter, {'event': event});
+ element.bind('keydown keypress', function(event) {
+ if (event.which === 13) {
+ scope.$apply(function() {
+ scope.$eval(attrs.snEnter, { event: event });
});
event.preventDefault();
}
});
};
-});
+}
diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js
index 486f4f31a..a180bbc99 100644
--- a/app/assets/javascripts/app/directives/views/accountMenu.js
+++ b/app/assets/javascripts/app/directives/views/accountMenu.js
@@ -1,16 +1,34 @@
-class AccountMenu {
+import { isDesktopApplication } from '@/utils';
+import { PrivilegesManager } from '@/services/privilegesManager';
+import template from '%/directives/account-menu.pug';
+import { SNJS } from 'snjs';
+export class AccountMenu {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/account-menu.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- "onSuccessfulAuth" : "&",
- "closeFunction" : "&"
+ onSuccessfulAuth: '&',
+ closeFunction: '&'
};
}
- controller($scope, $rootScope, authManager, modelManager, syncManager, storageManager, dbManager, passcodeManager,
- $timeout, $compile, archiveManager, privilegesManager, appVersion, alertManager) {
+ /* @ngInject */
+ controller(
+ $scope,
+ $rootScope,
+ authManager,
+ modelManager,
+ syncManager,
+ storageManager,
+ dbManager,
+ passcodeManager,
+ $timeout,
+ $compile,
+ archiveManager,
+ privilegesManager,
+ appVersion,
+ alertManager) {
'ngInject';
$scope.appVersion = "v" + (window.electronAppVersion || appVersion);
@@ -506,8 +524,5 @@ class AccountMenu {
$scope.isDesktopApplication = function() {
return isDesktopApplication();
}
-
}
}
-
-angular.module('app').directive('accountMenu', () => new AccountMenu);
diff --git a/app/assets/javascripts/app/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js
index f79fb1a15..88abd4305 100644
--- a/app/assets/javascripts/app/directives/views/actionsMenu.js
+++ b/app/assets/javascripts/app/directives/views/actionsMenu.js
@@ -1,16 +1,16 @@
-class ActionsMenu {
+import template from '%/directives/actions-menu.pug';
+export class ActionsMenu {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/actions-menu.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- item: "="
+ item: '='
};
}
+ /* @ngInject */
controller($scope, modelManager, actionsManager) {
- 'ngInject';
-
$scope.extensions = actionsManager.extensions.sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
@@ -81,7 +81,4 @@ class ActionsMenu {
})
}
}
-
}
-
-angular.module('app').directive('actionsMenu', () => new ActionsMenu);
diff --git a/app/assets/javascripts/app/directives/views/componentModal.js b/app/assets/javascripts/app/directives/views/componentModal.js
index 7cda3c897..a465fc282 100644
--- a/app/assets/javascripts/app/directives/views/componentModal.js
+++ b/app/assets/javascripts/app/directives/views/componentModal.js
@@ -1,13 +1,14 @@
-class ComponentModal {
+import template from '%/directives/component-modal.pug';
+export class ComponentModal {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/component-modal.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- show: "=",
- component: "=",
- callback: "=",
- onDismiss: "&"
+ show: '=',
+ component: '=',
+ callback: '=',
+ onDismiss: '&'
};
}
@@ -15,9 +16,8 @@ class ComponentModal {
$scope.el = el;
}
+ /* @ngInject */
controller($scope, $timeout, componentManager) {
- 'ngInject';
-
$scope.dismiss = function(callback) {
$scope.el.remove();
$scope.$destroy();
@@ -25,7 +25,4 @@ class ComponentModal {
callback && callback();
}
}
-
}
-
-angular.module('app').directive('componentModal', () => new ComponentModal);
diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js
index 6157be972..d05764f00 100644
--- a/app/assets/javascripts/app/directives/views/componentView.js
+++ b/app/assets/javascripts/app/directives/views/componentView.js
@@ -1,12 +1,21 @@
-class ComponentView {
+import template from '%/directives/component-view.pug';
- constructor($rootScope, componentManager, desktopManager, $timeout, themeManager) {
- this.restrict = "E";
- this.templateUrl = "directives/component-view.html";
+import { isDesktopApplication } from '../../utils';
+
+export class ComponentView {
+ constructor(
+ $rootScope,
+ componentManager,
+ desktopManager,
+ $timeout,
+ themeManager
+ ) {
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- component: "=",
- onLoad: "=?",
- manualDealloc: "=?"
+ component: '=',
+ onLoad: '=?',
+ manualDealloc: '=?'
};
this.desktopManager = desktopManager;
@@ -28,9 +37,15 @@ class ComponentView {
});
}
- controller($scope, $rootScope, $timeout, componentManager, desktopManager, themeManager) {
- 'ngInject';
-
+ /* @ngInject */
+ controller(
+ $scope,
+ $rootScope,
+ $timeout,
+ componentManager,
+ desktopManager,
+ themeManager
+ ) {
$scope.onVisibilityChange = function() {
if(document.visibilityState == "hidden") {
return;
@@ -261,7 +276,4 @@ class ComponentView {
$scope.destroy();
});
}
-
}
-
-angular.module('app').directive('componentView', ($rootScope, componentManager, desktopManager, $timeout) => new ComponentView($rootScope, componentManager, desktopManager, $timeout));
diff --git a/app/assets/javascripts/app/directives/views/conflictResolutionModal.js b/app/assets/javascripts/app/directives/views/conflictResolutionModal.js
index 358913032..cf027ee74 100644
--- a/app/assets/javascripts/app/directives/views/conflictResolutionModal.js
+++ b/app/assets/javascripts/app/directives/views/conflictResolutionModal.js
@@ -3,15 +3,16 @@
and allow the user to choose which to keep (or to keep both.)
*/
-class ConflictResolutionModal {
+import template from '%/directives/conflict-resolution-modal.pug';
+export class ConflictResolutionModal {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/conflict-resolution-modal.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- item1: "=",
- item2: "=",
- callback: "="
+ item1: '=',
+ item2: '=',
+ callback: '='
};
}
@@ -22,9 +23,8 @@ class ConflictResolutionModal {
}
}
+ /* @ngInject */
controller($scope, modelManager, syncManager, archiveManager, alertManager) {
- 'ngInject';
-
$scope.createContentString = function(item) {
return JSON.stringify(
Object.assign({created_at: item.created_at, updated_at: item.updated_at}, item.content), null, 2
@@ -70,8 +70,5 @@ class ConflictResolutionModal {
$scope.applyCallback = function() {
$scope.callback && $scope.callback();
}
-
}
}
-
-angular.module('app').directive('conflictResolutionModal', () => new ConflictResolutionModal);
diff --git a/app/assets/javascripts/app/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js
index 5bcc100f4..6dba895ba 100644
--- a/app/assets/javascripts/app/directives/views/editorMenu.js
+++ b/app/assets/javascripts/app/directives/views/editorMenu.js
@@ -1,18 +1,19 @@
-class EditorMenu {
+import { isDesktopApplication } from '@/utils';
+import template from '%/directives/editor-menu.pug';
+export class EditorMenu {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/editor-menu.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- callback: "&",
- selectedEditor: "=",
- currentItem: "="
+ callback: '&',
+ selectedEditor: '=',
+ currentItem: '='
};
}
+ /* @ngInject */
controller($scope, componentManager, syncManager, modelManager, $timeout) {
- 'ngInject';
-
$scope.formData = {};
$scope.editors = componentManager.componentsForArea("editor-editor").sort((a, b) => {
@@ -82,7 +83,4 @@ class EditorMenu {
}
}
}
-
}
-
-angular.module('app').directive('editorMenu', () => new EditorMenu);
diff --git a/app/assets/javascripts/app/directives/views/index.js b/app/assets/javascripts/app/directives/views/index.js
new file mode 100644
index 000000000..5a9f76471
--- /dev/null
+++ b/app/assets/javascripts/app/directives/views/index.js
@@ -0,0 +1,16 @@
+export { AccountMenu } from './accountMenu';
+export { ActionsMenu } from './actionsMenu';
+export { ComponentModal } from './componentModal';
+export { ComponentView } from './componentView';
+export { ConflictResolutionModal } from './conflictResolutionModal';
+export { EditorMenu } from './editorMenu';
+export { InputModal } from './inputModal';
+export { MenuRow } from './menuRow';
+export { PanelResizer } from './panelResizer';
+export { PasswordWizard } from './passwordWizard';
+export { PermissionsModal } from './permissionsModal';
+export { PrivilegesAuthModal } from './privilegesAuthModal';
+export { PrivilegesManagementModal } from './privilegesManagementModal';
+export { RevisionPreviewModal } from './revisionPreviewModal';
+export { SessionHistoryMenu } from './sessionHistoryMenu';
+export { SyncResolutionMenu } from './syncResolutionMenu';
diff --git a/app/assets/javascripts/app/directives/views/inputModal.js b/app/assets/javascripts/app/directives/views/inputModal.js
index 59002ae01..ded5b8e93 100644
--- a/app/assets/javascripts/app/directives/views/inputModal.js
+++ b/app/assets/javascripts/app/directives/views/inputModal.js
@@ -1,14 +1,15 @@
-class InputModal {
+import template from '%/directives/input-modal.pug';
+export class InputModal {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/input-modal.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- type: "=",
- title: "=",
- message: "=",
- placeholder: "=",
- callback: "&"
+ type: '=',
+ title: '=',
+ message: '=',
+ placeholder: '=',
+ callback: '&'
};
}
@@ -16,9 +17,8 @@ class InputModal {
$scope.el = el;
}
+ /* @ngInject */
controller($scope, modelManager, archiveManager, authManager, syncManager, $timeout) {
- 'ngInject';
-
$scope.formData = {};
$scope.dismiss = function() {
@@ -30,9 +30,5 @@ class InputModal {
$scope.callback()($scope.formData.input);
$scope.dismiss();
}
-
-
}
}
-
-angular.module('app').directive('inputModal', () => new InputModal);
diff --git a/app/assets/javascripts/app/directives/views/menuRow.js b/app/assets/javascripts/app/directives/views/menuRow.js
index 99a61fab1..9eecf7daa 100644
--- a/app/assets/javascripts/app/directives/views/menuRow.js
+++ b/app/assets/javascripts/app/directives/views/menuRow.js
@@ -1,31 +1,31 @@
-class MenuRow {
+import template from '%/directives/menu-row.pug';
+export class MenuRow {
constructor() {
- this.restrict = "E";
+ this.restrict = 'E';
this.transclude = true;
- this.templateUrl = "directives/menu-row.html";
+ this.template = template;
this.scope = {
- action: "&",
- circle: "=",
- circleAlign: "=",
- label: "=",
- subtitle: "=",
- hasButton: "=",
- buttonText: "=",
- buttonClass: "=",
- buttonAction: "&",
- spinnerClass: "=",
- subRows: "=",
- faded: "=",
- desc: "=",
- disabled: "=",
- stylekitClass: "="
+ action: '&',
+ circle: '=',
+ circleAlign: '=',
+ label: '=',
+ subtitle: '=',
+ hasButton: '=',
+ buttonText: '=',
+ buttonClass: '=',
+ buttonAction: '&',
+ spinnerClass: '=',
+ subRows: '=',
+ faded: '=',
+ desc: '=',
+ disabled: '=',
+ stylekitClass: '='
};
}
+ /* @ngInject */
controller($scope, componentManager) {
- 'ngInject';
-
$scope.onClick = function($event) {
if($scope.disabled) {
return;
@@ -42,8 +42,5 @@ class MenuRow {
$event.stopPropagation();
$scope.buttonAction();
}
-
}
}
-
-angular.module('app').directive('menuRow', () => new MenuRow);
diff --git a/app/assets/javascripts/app/directives/views/panelResizer.js b/app/assets/javascripts/app/directives/views/panelResizer.js
index 606e0d170..06b291925 100644
--- a/app/assets/javascripts/app/directives/views/panelResizer.js
+++ b/app/assets/javascripts/app/directives/views/panelResizer.js
@@ -1,20 +1,22 @@
-class PanelResizer {
+import angular from 'angular';
+import template from '%/directives/panel-resizer.pug';
+export class PanelResizer {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/panel-resizer.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- index: "=",
- panelId: "=",
- onResize: "&",
- defaultWidth: "=",
- onResizeFinish: "&",
- control: "=",
- alwaysVisible: "=",
- minWidth: "=",
- property: "=",
- hoverable: "=",
- collapsable: "="
+ index: '=',
+ panelId: '=',
+ onResize: '&',
+ defaultWidth: '=',
+ onResizeFinish: '&',
+ control: '=',
+ alwaysVisible: '=',
+ minWidth: '=',
+ property: '=',
+ hoverable: '=',
+ collapsable: '='
};
}
@@ -38,9 +40,8 @@ class PanelResizer {
}
}
+ /* @ngInject */
controller($scope, $element, modelManager, actionsManager, $timeout, $compile) {
- 'ngInject';
-
let panel = document.getElementById($scope.panelId);
if(!panel) {
console.log("Panel not found for", $scope.panelId);
@@ -288,8 +289,6 @@ class PanelResizer {
}
}
-angular.module('app').directive('panelResizer', () => new PanelResizer);
-
/* via https://davidwalsh.name/javascript-debounce-function */
function debounce(func, wait, immediate) {
var timeout;
diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js
index 56aaa0d42..03ddf26d8 100644
--- a/app/assets/javascripts/app/directives/views/passwordWizard.js
+++ b/app/assets/javascripts/app/directives/views/passwordWizard.js
@@ -1,10 +1,12 @@
-class PasswordWizard {
+import { SNJS } from 'snjs';
+import template from '%/directives/password-wizard.pug';
+export class PasswordWizard {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/password-wizard.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- type: "="
+ type: '='
};
}
@@ -12,8 +14,8 @@ class PasswordWizard {
$scope.el = el;
}
+ /* @ngInject */
controller($scope, modelManager, archiveManager, authManager, syncManager, $timeout, alertManager) {
- 'ngInject';
window.onbeforeunload = (e) => {
// Confirms with user to close tab before closing
@@ -255,7 +257,4 @@ class PasswordWizard {
})
}
}
-
}
-
-angular.module('app').directive('passwordWizard', () => new PasswordWizard);
diff --git a/app/assets/javascripts/app/directives/views/permissionsModal.js b/app/assets/javascripts/app/directives/views/permissionsModal.js
index 54dba21ec..0b15b4e27 100644
--- a/app/assets/javascripts/app/directives/views/permissionsModal.js
+++ b/app/assets/javascripts/app/directives/views/permissionsModal.js
@@ -1,18 +1,18 @@
-class PermissionsModal {
+import template from '%/directives/permissions-modal.pug';
+export class PermissionsModal {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/permissions-modal.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- show: "=",
- component: "=",
- permissionsString: "=",
- callback: "="
+ show: '=',
+ component: '=',
+ permissionsString: '=',
+ callback: '='
};
}
link($scope, el, attrs) {
-
$scope.dismiss = function() {
el.remove();
}
@@ -28,11 +28,8 @@ class PermissionsModal {
}
}
+ /* @ngInject */
controller($scope, modelManager) {
- 'ngInject';
}
-
}
-
-angular.module('app').directive('permissionsModal', () => new PermissionsModal);
diff --git a/app/assets/javascripts/app/directives/views/privilegesAuthModal.js b/app/assets/javascripts/app/directives/views/privilegesAuthModal.js
index 8440fd849..1de610591 100644
--- a/app/assets/javascripts/app/directives/views/privilegesAuthModal.js
+++ b/app/assets/javascripts/app/directives/views/privilegesAuthModal.js
@@ -1,12 +1,14 @@
-class PrivilegesAuthModal {
+import template from '%/directives/privileges-auth-modal.pug';
+/* @ngInject */
+export class PrivilegesAuthModal {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/privileges-auth-modal.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- action: "=",
- onSuccess: "=",
- onCancel: "=",
+ action: '=',
+ onSuccess: '=',
+ onCancel: '='
};
}
@@ -85,8 +87,5 @@ class PrivilegesAuthModal {
})
})
}
-
}
}
-
-angular.module('app').directive('privilegesAuthModal', () => new PrivilegesAuthModal);
diff --git a/app/assets/javascripts/app/directives/views/privilegesManagementModal.js b/app/assets/javascripts/app/directives/views/privilegesManagementModal.js
index bec8691d4..630f498b4 100644
--- a/app/assets/javascripts/app/directives/views/privilegesManagementModal.js
+++ b/app/assets/javascripts/app/directives/views/privilegesManagementModal.js
@@ -1,11 +1,11 @@
-class PrivilegesManagementModal {
+import { PrivilegesManager } from '@/services/privilegesManager';
+import template from '%/directives/privileges-management-modal.pug';
+export class PrivilegesManagementModal {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/privileges-management-modal.html";
- this.scope = {
-
- };
+ this.restrict = 'E';
+ this.template = template;
+ this.scope = {};
}
link($scope, el, attrs) {
@@ -14,9 +14,8 @@ class PrivilegesManagementModal {
}
}
+ /* @ngInject */
controller($scope, privilegesManager, passcodeManager, authManager, $timeout) {
- 'ngInject';
-
$scope.dummy = {};
$scope.hasPasscode = passcodeManager.hasPasscode();
@@ -84,5 +83,3 @@ class PrivilegesManagementModal {
}
}
}
-
-angular.module('app').directive('privilegesManagementModal', () => new PrivilegesManagementModal);
diff --git a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js
index fcf458181..22f3532ba 100644
--- a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js
+++ b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js
@@ -1,11 +1,13 @@
-class RevisionPreviewModal {
+import { SNJS, SNComponent, SFItem, SFModelManager } from 'snjs';
+import template from '%/directives/revision-preview-modal.pug';
+export class RevisionPreviewModal {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/revision-preview-modal.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- uuid: "=",
- content: "="
+ uuid: '=',
+ content: '='
};
}
@@ -13,9 +15,8 @@ class RevisionPreviewModal {
$scope.el = el;
}
+ /* @ngInject */
controller($scope, modelManager, syncManager, componentManager, $timeout, alertManager) {
- 'ngInject';
-
$scope.dismiss = function() {
$scope.el.remove();
$scope.$destroy();
@@ -90,5 +91,3 @@ class RevisionPreviewModal {
}
}
}
-
-angular.module('app').directive('revisionPreviewModal', () => new RevisionPreviewModal);
diff --git a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js
index 220f96abb..974a6d789 100644
--- a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js
+++ b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js
@@ -1,16 +1,16 @@
-class SessionHistoryMenu {
+import template from '%/directives/session-history-menu.pug';
+export class SessionHistoryMenu {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/session-history-menu.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- item: "="
+ item: '='
};
}
+ /* @ngInject */
controller($scope, modelManager, sessionHistory, actionsManager, $timeout, alertManager) {
- 'ngInject';
-
$scope.diskEnabled = sessionHistory.diskEnabled;
$scope.autoOptimize = sessionHistory.autoOptimize;
@@ -85,9 +85,5 @@ class SessionHistoryMenu {
})
});
}
-
}
-
}
-
-angular.module('app').directive('sessionHistoryMenu', () => new SessionHistoryMenu);
diff --git a/app/assets/javascripts/app/directives/views/syncResolutionMenu.js b/app/assets/javascripts/app/directives/views/syncResolutionMenu.js
index e9de1ec01..ff210d2db 100644
--- a/app/assets/javascripts/app/directives/views/syncResolutionMenu.js
+++ b/app/assets/javascripts/app/directives/views/syncResolutionMenu.js
@@ -1,16 +1,16 @@
-class SyncResolutionMenu {
+import template from '%/directives/sync-resolution-menu.pug';
+export class SyncResolutionMenu {
constructor() {
- this.restrict = "E";
- this.templateUrl = "directives/sync-resolution-menu.html";
+ this.restrict = 'E';
+ this.template = template;
this.scope = {
- "closeFunction" : "&"
+ closeFunction: '&'
};
}
+ /* @ngInject */
controller($scope, modelManager, syncManager, archiveManager, $timeout) {
- 'ngInject';
-
$scope.status = {};
$scope.close = function() {
@@ -42,5 +42,3 @@ class SyncResolutionMenu {
}
}
}
-
-angular.module('app').directive('syncResolutionMenu', () => new SyncResolutionMenu);
diff --git a/app/assets/javascripts/app/filters/appDate.js b/app/assets/javascripts/app/filters/appDate.js
index f41db3381..acc7c0fc5 100644
--- a/app/assets/javascripts/app/filters/appDate.js
+++ b/app/assets/javascripts/app/filters/appDate.js
@@ -1,28 +1,33 @@
// reuse
var locale, formatter;
-angular.module('app')
- .filter('appDate', function ($filter) {
- return function (input) {
- return input ? $filter('date')(new Date(input), 'MM/dd/yyyy', 'UTC') : '';
- };
- })
- .filter('appDateTime', function ($filter) {
- return function (input) {
- if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
- if (!formatter) {
- locale = (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language;
- formatter = new Intl.DateTimeFormat(locale, {
- year: 'numeric',
- month: 'numeric',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- });
- }
- return formatter.format(input);
- } else {
- return input ? $filter('date')(new Date(input), 'MM/dd/yyyy h:mm a') : '';
- }
+/* @ngInject */
+export function appDate($filter) {
+ return function(input) {
+ return input ? $filter('date')(new Date(input), 'MM/dd/yyyy', 'UTC') : '';
+ };
+}
+
+/* @ngInject */
+export function appDateTime($filter) {
+ return function(input) {
+ if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
+ if (!formatter) {
+ locale =
+ navigator.languages && navigator.languages.length
+ ? navigator.languages[0]
+ : navigator.language;
+ formatter = new Intl.DateTimeFormat(locale, {
+ year: 'numeric',
+ month: 'numeric',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
}
- });
+ return formatter.format(input);
+ } else {
+ return input ? $filter('date')(new Date(input), 'MM/dd/yyyy h:mm a') : '';
+ }
+ };
+}
diff --git a/app/assets/javascripts/app/filters/index.js b/app/assets/javascripts/app/filters/index.js
new file mode 100644
index 000000000..f7a37d7d9
--- /dev/null
+++ b/app/assets/javascripts/app/filters/index.js
@@ -0,0 +1,2 @@
+export { appDate, appDateTime } from './appDate';
+export { trusted } from './trusted';
diff --git a/app/assets/javascripts/app/filters/trusted.js b/app/assets/javascripts/app/filters/trusted.js
index f2a41a3c0..3ebbd7c96 100644
--- a/app/assets/javascripts/app/filters/trusted.js
+++ b/app/assets/javascripts/app/filters/trusted.js
@@ -1,5 +1,6 @@
-angular.module('app').filter('trusted', ['$sce', function ($sce) {
- return function(url) {
- return $sce.trustAsResourceUrl(url);
- };
-}]);
+/* @ngInject */
+export function trusted($sce) {
+ return function(url) {
+ return $sce.trustAsResourceUrl(url);
+ };
+}
diff --git a/app/assets/javascripts/app/models/noteHistoryEntry.js b/app/assets/javascripts/app/models/noteHistoryEntry.js
index 51831cc40..a6be3e15f 100644
--- a/app/assets/javascripts/app/models/noteHistoryEntry.js
+++ b/app/assets/javascripts/app/models/noteHistoryEntry.js
@@ -1,4 +1,6 @@
-class NoteHistoryEntry extends SFItemHistoryEntry {
+import { SFItemHistoryEntry } from 'snjs';
+
+export class NoteHistoryEntry extends SFItemHistoryEntry {
previewTitle() {
return this.item.updated_at.toLocaleString();
diff --git a/app/assets/javascripts/app/routes.js b/app/assets/javascripts/app/routes.js
index fe6dac476..d689f85c8 100644
--- a/app/assets/javascripts/app/routes.js
+++ b/app/assets/javascripts/app/routes.js
@@ -1,14 +1,15 @@
-angular.module('app')
- .config(function ($locationProvider) {
+import { isDesktopApplication } from './utils';
- if(!isDesktopApplication()) {
- if (window.history && window.history.pushState) {
- $locationProvider.html5Mode({
- enabled: true,
- requireBase: false
- });
- }
- } else {
- $locationProvider.html5Mode(false);
+/* @ngInject */
+export function configRoutes($locationProvider) {
+ if (!isDesktopApplication()) {
+ if (window.history && window.history.pushState) {
+ $locationProvider.html5Mode({
+ enabled: true,
+ requireBase: false
+ });
}
- });
+ } else {
+ $locationProvider.html5Mode(false);
+ }
+}
diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js
index c25f9d928..070c481ba 100644
--- a/app/assets/javascripts/app/services/actionsManager.js
+++ b/app/assets/javascripts/app/services/actionsManager.js
@@ -1,6 +1,20 @@
-class ActionsManager {
+import _ from 'lodash';
+import angular from 'angular';
+import { Action, SFModelManager, SFItemParams, SNJS } from 'snjs';
- constructor(httpManager, modelManager, authManager, syncManager, $rootScope, $compile, $timeout, alertManager) {
+export class ActionsManager {
+
+ /* @ngInject */
+ constructor(
+ httpManager,
+ modelManager,
+ authManager,
+ syncManager,
+ $rootScope,
+ $compile,
+ $timeout,
+ alertManager
+ ) {
this.httpManager = httpManager;
this.modelManager = modelManager;
this.authManager = authManager;
@@ -226,7 +240,4 @@ class ActionsManager {
var el = this.$compile( "" )(scope);
angular.element(document.body).append(el);
}
-
}
-
-angular.module('app').service('actionsManager', ActionsManager);
diff --git a/app/assets/javascripts/app/services/alertManager.js b/app/assets/javascripts/app/services/alertManager.js
index 300c2ed03..b2e6fc4ee 100644
--- a/app/assets/javascripts/app/services/alertManager.js
+++ b/app/assets/javascripts/app/services/alertManager.js
@@ -1,5 +1,8 @@
-class AlertManager extends SFAlertManager {
+import { SFAlertManager } from 'snjs';
+import { SKAlert } from 'sn-stylekit';
+export class AlertManager extends SFAlertManager {
+ /* @ngInject */
constructor($timeout) {
super();
this.$timeout = $timeout;
@@ -15,7 +18,7 @@ class AlertManager extends SFAlertManager {
resolve(true);
}}
]
- let alert = new Stylekit.SKAlert({title, text, buttons});
+ let alert = new SKAlert({title, text, buttons});
alert.present();
})
}
@@ -37,11 +40,8 @@ class AlertManager extends SFAlertManager {
}},
];
- let alert = new Stylekit.SKAlert({title, text, buttons});
+ let alert = new SKAlert({title, text, buttons});
alert.present();
})
}
-
}
-
-angular.module('app').service('alertManager', AlertManager);
diff --git a/app/assets/javascripts/app/services/archiveManager.js b/app/assets/javascripts/app/services/archiveManager.js
index 797821c3d..c9774ad78 100644
--- a/app/assets/javascripts/app/services/archiveManager.js
+++ b/app/assets/javascripts/app/services/archiveManager.js
@@ -1,5 +1,7 @@
-class ArchiveManager {
+import { PrivilegesManager } from '@/services/privilegesManager';
+export class ArchiveManager {
+ /* @ngInject */
constructor(passcodeManager, authManager, modelManager, privilegesManager) {
this.passcodeManager = passcodeManager;
this.authManager = authManager;
@@ -156,8 +158,4 @@ class ArchiveManager {
link.click();
link.remove();
}
-
-
}
-
-angular.module('app').service('archiveManager', ArchiveManager);
diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js
index 6afed24ef..9c116e938 100644
--- a/app/assets/javascripts/app/services/authManager.js
+++ b/app/assets/javascripts/app/services/authManager.js
@@ -1,6 +1,19 @@
-class AuthManager extends SFAuthManager {
+import angular from 'angular';
+import { StorageManager } from './storageManager';
+import { SNJS, SFItem, SFPredicate, SFAuthManager } from 'snjs';
- constructor(modelManager, singletonManager, storageManager, dbManager, httpManager, $rootScope, $timeout, $compile) {
+export class AuthManager extends SFAuthManager {
+ /* @ngInject */
+ constructor(
+ modelManager,
+ singletonManager,
+ storageManager,
+ dbManager,
+ httpManager,
+ $rootScope,
+ $timeout,
+ $compile
+ ) {
super(storageManager, httpManager, null, $timeout);
this.$rootScope = $rootScope;
this.$compile = $compile;
@@ -176,5 +189,3 @@ class AuthManager extends SFAuthManager {
}
}
}
-
-angular.module('app').service('authManager', AuthManager);
diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js
index 84de54717..85bf2b6ce 100644
--- a/app/assets/javascripts/app/services/componentManager.js
+++ b/app/assets/javascripts/app/services/componentManager.js
@@ -1,5 +1,18 @@
-class ComponentManager extends SNComponentManager {
- constructor(modelManager, syncManager, desktopManager, nativeExtManager, $rootScope, $timeout, $compile) {
+import angular from 'angular';
+import { SNComponentManager, SFAlertManager } from 'snjs';
+import { isDesktopApplication, getPlatformString } from '@/utils';
+
+export class ComponentManager extends SNComponentManager {
+ /* @ngInject */
+ constructor(
+ modelManager,
+ syncManager,
+ desktopManager,
+ nativeExtManager,
+ $rootScope,
+ $timeout,
+ $compile
+ ) {
super({
modelManager,
syncManager,
@@ -35,5 +48,3 @@ class ComponentManager extends SNComponentManager {
angular.element(document.body).append(el);
}
}
-
-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 d68f52962..cd2c162cd 100644
--- a/app/assets/javascripts/app/services/dbManager.js
+++ b/app/assets/javascripts/app/services/dbManager.js
@@ -1,8 +1,8 @@
-class DBManager {
-
+export class DBManager {
+ /* @ngInject */
constructor(alertManager) {
this.locked = true;
- this.alertManager;
+ this.alertManager = alertManager;
}
displayOfflineAlert() {
@@ -179,5 +179,3 @@ class 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 130d0d21c..e22ecbb73 100644
--- a/app/assets/javascripts/app/services/desktopManager.js
+++ b/app/assets/javascripts/app/services/desktopManager.js
@@ -1,8 +1,18 @@
// An interface used by the Desktop app to interact with SN
+import _ from 'lodash';
+import { isDesktopApplication } from '@/utils';
+import { SFItemParams, SFModelManager } from 'snjs';
-class DesktopManager {
-
- constructor($rootScope, $timeout, modelManager, syncManager, authManager, passcodeManager) {
+export class DesktopManager {
+ /* @ngInject */
+ constructor(
+ $rootScope,
+ $timeout,
+ modelManager,
+ syncManager,
+ authManager,
+ passcodeManager
+ ) {
this.passcodeManager = passcodeManager;
this.modelManager = modelManager;
this.authManager = authManager;
@@ -203,5 +213,3 @@ class DesktopManager {
this.$rootScope.$broadcast("did-finish-local-backup", {success: success});
}
}
-
-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 a3415b4b5..8d47d0141 100644
--- a/app/assets/javascripts/app/services/httpManager.js
+++ b/app/assets/javascripts/app/services/httpManager.js
@@ -1,13 +1,13 @@
-class HttpManager extends SFHttpManager {
+import { SFHttpManager } from 'snjs';
+export class HttpManager extends SFHttpManager {
+ /* @ngInject */
constructor(storageManager, $timeout) {
// calling callbacks in a $timeout allows UI to update
super($timeout);
this.setJWTRequestHandler(async () => {
- return storageManager.getItem("jwt");
- })
+ return storageManager.getItem('jwt');
+ });
}
}
-
-angular.module('app').service('httpManager', HttpManager);
diff --git a/app/assets/javascripts/app/services/index.js b/app/assets/javascripts/app/services/index.js
new file mode 100644
index 000000000..f6ff34078
--- /dev/null
+++ b/app/assets/javascripts/app/services/index.js
@@ -0,0 +1,20 @@
+export { ActionsManager } from './actionsManager';
+export { ArchiveManager } from './archiveManager';
+export { AuthManager } from './authManager';
+export { ComponentManager } from './componentManager';
+export { DBManager } from './dbManager';
+export { DesktopManager } from './desktopManager';
+export { HttpManager } from './httpManager';
+export { KeyboardManager } from './keyboardManager';
+export { MigrationManager } from './migrationManager';
+export { ModelManager } from './modelManager';
+export { NativeExtManager } from './nativeExtManager';
+export { PasscodeManager } from './passcodeManager';
+export { PrivilegesManager } from './privilegesManager';
+export { SessionHistory } from './sessionHistory';
+export { SingletonManager } from './singletonManager';
+export { StatusManager } from './statusManager';
+export { StorageManager } from './storageManager';
+export { SyncManager } from './syncManager';
+export { ThemeManager } from './themeManager';
+export { AlertManager } from './alertManager';
diff --git a/app/assets/javascripts/app/services/keyboardManager.js b/app/assets/javascripts/app/services/keyboardManager.js
index 9f333cc9c..75d17e3cb 100644
--- a/app/assets/javascripts/app/services/keyboardManager.js
+++ b/app/assets/javascripts/app/services/keyboardManager.js
@@ -1,4 +1,4 @@
-class KeyboardManager {
+export class KeyboardManager {
constructor() {
this.observers = [];
@@ -113,5 +113,3 @@ class KeyboardManager {
this.observers.splice(this.observers.indexOf(observer), 1);
}
}
-
-angular.module('app').service('keyboardManager', KeyboardManager);
diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js
index 5bc659ade..aecbc9fb5 100644
--- a/app/assets/javascripts/app/services/migrationManager.js
+++ b/app/assets/javascripts/app/services/migrationManager.js
@@ -1,6 +1,19 @@
-class MigrationManager extends SFMigrationManager {
+import { isDesktopApplication } from '@/utils';
+import { SFMigrationManager } from 'snjs';
+import { ComponentManager } from '@/services/componentManager';
- constructor($rootScope, modelManager, syncManager, componentManager, storageManager, statusManager, authManager, desktopManager) {
+export class MigrationManager extends SFMigrationManager {
+
+ /* @ngInject */
+ constructor(
+ modelManager,
+ syncManager,
+ componentManager,
+ storageManager,
+ statusManager,
+ authManager,
+ desktopManager
+ ) {
super(modelManager, syncManager, storageManager, authManager);
this.componentManager = componentManager;
this.statusManager = statusManager;
@@ -157,5 +170,3 @@ class MigrationManager extends SFMigrationManager {
}
}
}
-
-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 218704a3f..de520ac4b 100644
--- a/app/assets/javascripts/app/services/modelManager.js
+++ b/app/assets/javascripts/app/services/modelManager.js
@@ -1,3 +1,20 @@
+import _ from 'lodash';
+import {
+ SFItem,
+ SFModelManager,
+ SFPrivileges,
+ SFPredicate,
+ SNNote,
+ SNTag,
+ SNSmartTag,
+ SNExtension,
+ SNEditor,
+ SNTheme,
+ SNComponent,
+ SNServerExtension,
+ SNMfa
+} from 'snjs';
+
SFModelManager.ContentTypeClassMapping = {
"Note" : SNNote,
"Tag" : SNTag,
@@ -11,10 +28,8 @@ SFModelManager.ContentTypeClassMapping = {
"SN|Privileges" : SFPrivileges
};
-SFItem.AppDomain = "org.standardnotes.sn";
-
-class ModelManager extends SFModelManager {
-
+export class ModelManager extends SFModelManager {
+ /* @ngInject */
constructor(storageManager, $timeout) {
super($timeout);
this.notes = [];
@@ -177,7 +192,4 @@ class ModelManager extends SFModelManager {
"SN|FileSafe|Integration": "FileSafe integration"
}[contentType];
}
-
}
-
-angular.module('app').service('modelManager', ModelManager);
diff --git a/app/assets/javascripts/app/services/nativeExtManager.js b/app/assets/javascripts/app/services/nativeExtManager.js
index 489936c82..08eb5f84b 100644
--- a/app/assets/javascripts/app/services/nativeExtManager.js
+++ b/app/assets/javascripts/app/services/nativeExtManager.js
@@ -1,7 +1,10 @@
/* A class for handling installation of system extensions */
-class NativeExtManager {
+import { isDesktopApplication } from '@/utils';
+import { SFPredicate } from 'snjs';
+export class NativeExtManager {
+ /* @ngInject */
constructor(modelManager, syncManager, singletonManager) {
this.modelManager = modelManager;
this.syncManager = syncManager;
@@ -180,5 +183,3 @@ class NativeExtManager {
});
}
}
-
-angular.module('app').service('nativeExtManager', NativeExtManager);
diff --git a/app/assets/javascripts/app/services/passcodeManager.js b/app/assets/javascripts/app/services/passcodeManager.js
index 26ede6afe..6d7961d80 100644
--- a/app/assets/javascripts/app/services/passcodeManager.js
+++ b/app/assets/javascripts/app/services/passcodeManager.js
@@ -1,282 +1,285 @@
+import _ from 'lodash';
+import { isDesktopApplication } from '@/utils';
+import { StorageManager } from './storageManager';
+import { SNJS } from 'snjs';
+
const MillisecondsPerSecond = 1000;
-class PasscodeManager {
+export class PasscodeManager {
+ /* @ngInject */
+ constructor($rootScope, authManager, storageManager, syncManager) {
+ this.authManager = authManager;
+ this.storageManager = storageManager;
+ this.syncManager = syncManager;
+ this.$rootScope = $rootScope;
- constructor($rootScope, authManager, storageManager, syncManager) {
- this.authManager = authManager;
- this.storageManager = storageManager;
- this.syncManager = syncManager;
- this.$rootScope = $rootScope;
+ this._hasPasscode = this.storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
+ this._locked = this._hasPasscode;
- this._hasPasscode = this.storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
- this._locked = this._hasPasscode;
+ this.visibilityObservers = [];
+ this.passcodeChangeObservers = [];
- this.visibilityObservers = [];
- this.passcodeChangeObservers = [];
+ this.configureAutoLock();
+ }
- this.configureAutoLock();
+ addPasscodeChangeObserver(callback) {
+ this.passcodeChangeObservers.push(callback);
+ }
+
+ lockApplication() {
+ window.location.reload();
+ this.cancelAutoLockTimer();
+ }
+
+ isLocked() {
+ return this._locked;
+ }
+
+ hasPasscode() {
+ return this._hasPasscode;
+ }
+
+ keys() {
+ return this._keys;
+ }
+
+ addVisibilityObserver(callback) {
+ this.visibilityObservers.push(callback);
+ return callback;
+ }
+
+ removeVisibilityObserver(callback) {
+ _.pull(this.visibilityObservers, callback);
+ }
+
+ notifiyVisibilityObservers(visible) {
+ for(let callback of this.visibilityObservers) {
+ callback(visible);
}
+ }
- addPasscodeChangeObserver(callback) {
- this.passcodeChangeObservers.push(callback);
+ async setAutoLockInterval(interval) {
+ return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.FixedEncrypted);
+ }
+
+ async getAutoLockInterval() {
+ let interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.FixedEncrypted);
+ if(interval) {
+ return JSON.parse(interval);
+ } else {
+ return PasscodeManager.AutoLockIntervalNone;
}
+ }
- lockApplication() {
- window.location.reload();
- this.cancelAutoLockTimer();
- }
-
- isLocked() {
- return this._locked;
- }
-
- hasPasscode() {
- return this._hasPasscode;
- }
-
- keys() {
- return this._keys;
- }
-
- addVisibilityObserver(callback) {
- this.visibilityObservers.push(callback);
- return callback;
- }
-
- removeVisibilityObserver(callback) {
- _.pull(this.visibilityObservers, callback);
- }
-
- notifiyVisibilityObservers(visible) {
- for(let callback of this.visibilityObservers) {
- callback(visible);
- }
- }
-
- async setAutoLockInterval(interval) {
- return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.FixedEncrypted);
- }
-
- async getAutoLockInterval() {
- let interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.FixedEncrypted);
- if(interval) {
- return JSON.parse(interval);
+ passcodeAuthParams() {
+ var authParams = JSON.parse(this.storageManager.getItemSync("offlineParams", StorageManager.Fixed));
+ if(authParams && !authParams.version) {
+ var keys = this.keys();
+ if(keys && keys.ak) {
+ // If there's no version stored, and there's an ak, it has to be 002. Newer versions would have their version stored in authParams.
+ authParams.version = "002";
} else {
- return PasscodeManager.AutoLockIntervalNone;
+ authParams.version = "001";
}
}
+ return authParams;
+ }
- passcodeAuthParams() {
- var authParams = JSON.parse(this.storageManager.getItemSync("offlineParams", StorageManager.Fixed));
- if(authParams && !authParams.version) {
- var keys = this.keys();
- if(keys && keys.ak) {
- // If there's no version stored, and there's an ak, it has to be 002. Newer versions would have their version stored in authParams.
- authParams.version = "002";
- } else {
- authParams.version = "001";
- }
- }
- return authParams;
- }
-
- async verifyPasscode(passcode) {
- return new Promise(async (resolve, reject) => {
- var params = this.passcodeAuthParams();
- let keys = await SNJS.crypto.computeEncryptionKeysForUser(passcode, params);
- if(keys.pw !== params.hash) {
- resolve(false);
- } else {
- resolve(true);
- }
- })
- }
-
- unlock(passcode, callback) {
+ async verifyPasscode(passcode) {
+ return new Promise(async (resolve, reject) => {
var params = this.passcodeAuthParams();
- SNJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
- if(keys.pw !== params.hash) {
- callback(false);
- return;
- }
-
- this._keys = keys;
- this._authParams = params;
- this.decryptLocalStorage(keys, params).then(() => {
- this._locked = false;
- callback(true);
- })
- });
- }
-
- setPasscode(passcode, callback) {
- var uuid = SNJS.crypto.generateUUIDSync();
-
- SNJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
- let keys = results.keys;
- let authParams = results.authParams;
-
- authParams.hash = keys.pw;
- this._keys = keys;
- this._hasPasscode = true;
- this._authParams = authParams;
-
- // Encrypting will initially clear localStorage
- this.encryptLocalStorage(keys, authParams);
-
- // After it's cleared, it's safe to write to it
- this.storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
- callback(true);
-
- this.notifyObserversOfPasscodeChange();
- });
- }
-
- changePasscode(newPasscode, callback) {
- this.setPasscode(newPasscode, callback);
- }
-
- clearPasscode() {
- this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
- this.storageManager.removeItem("offlineParams", StorageManager.Fixed);
- this._keys = null;
- this._hasPasscode = false;
-
- this.notifyObserversOfPasscodeChange();
- }
-
- notifyObserversOfPasscodeChange() {
- for(var observer of this.passcodeChangeObservers) {
- observer();
- }
- }
-
- encryptLocalStorage(keys, authParams) {
- this.storageManager.setKeys(keys, authParams);
- // Switch to Ephemeral storage, wiping Fixed storage
- // Last argument is `force`, which we set to true because in the case of changing passcode
- this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
- }
-
- async decryptLocalStorage(keys, authParams) {
- this.storageManager.setKeys(keys, authParams);
- return this.storageManager.decryptStorage();
- }
-
- configureAutoLock() {
- PasscodeManager.AutoLockPollFocusInterval = 1 * MillisecondsPerSecond;
-
- PasscodeManager.AutoLockIntervalNone = 0;
- PasscodeManager.AutoLockIntervalImmediate = 1;
- PasscodeManager.AutoLockIntervalOneMinute = 60 * MillisecondsPerSecond;
- PasscodeManager.AutoLockIntervalFiveMinutes = 300 * MillisecondsPerSecond;
- PasscodeManager.AutoLockIntervalOneHour = 3600 * MillisecondsPerSecond;
-
- PasscodeManager.AutoLockIntervalKey = "AutoLockIntervalKey";
-
- if(isDesktopApplication()) {
- // desktop only
- this.$rootScope.$on("window-lost-focus", () => {
- this.documentVisibilityChanged(false);
- })
- this.$rootScope.$on("window-gained-focus", () => {
- this.documentVisibilityChanged(true);
- })
+ let keys = await SNJS.crypto.computeEncryptionKeysForUser(passcode, params);
+ if(keys.pw !== params.hash) {
+ resolve(false);
} else {
- // tab visibility listener, web only
- document.addEventListener('visibilitychange', (e) => {
- let visible = document.visibilityState == "visible";
- this.documentVisibilityChanged(visible);
- });
-
- // verify document is in focus every so often as visibilitychange event is not triggered
- // on a typical window blur event but rather on tab changes
- this.pollFocusTimeout = setInterval(() => {
- let hasFocus = document.hasFocus();
-
- if(hasFocus && this.lastFocusState == "hidden") {
- this.documentVisibilityChanged(true);
- } else if(!hasFocus && this.lastFocusState == "visible") {
- this.documentVisibilityChanged(false);
- }
-
- // save this to compare against next time around
- this.lastFocusState = hasFocus ? "visible" : "hidden";
- }, PasscodeManager.AutoLockPollFocusInterval);
+ resolve(true);
}
- }
+ })
+ }
- getAutoLockIntervalOptions() {
- return [
- {
- value: PasscodeManager.AutoLockIntervalNone,
- label: "Off"
- },
- {
- value: PasscodeManager.AutoLockIntervalImmediate,
- label: "Immediately"
- },
- {
- value: PasscodeManager.AutoLockIntervalOneMinute,
- label: "1m"
- },
- {
- value: PasscodeManager.AutoLockIntervalFiveMinutes,
- label: "5m"
- },
- {
- value: PasscodeManager.AutoLockIntervalOneHour,
- label: "1h"
- }
- ]
- }
-
- documentVisibilityChanged(visible) {
- if(visible) {
- // check to see if lockAfterDate is not null, and if the application isn't locked.
- // if that's the case, it needs to be locked immediately.
- if(this.lockAfterDate && new Date() > this.lockAfterDate && !this.isLocked()) {
- this.lockApplication();
- } else {
- if(!this.isLocked()) {
- this.syncManager.sync();
- }
- }
- this.cancelAutoLockTimer();
- } else {
- this.beginAutoLockTimer();
- }
-
- this.notifiyVisibilityObservers(visible);
- }
-
- async beginAutoLockTimer() {
- var interval = await this.getAutoLockInterval();
- if(interval == PasscodeManager.AutoLockIntervalNone) {
+ unlock(passcode, callback) {
+ var params = this.passcodeAuthParams();
+ SNJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
+ if(keys.pw !== params.hash) {
+ callback(false);
return;
}
- // Use a timeout if possible, but if the computer is put to sleep, timeouts won't work.
- // Need to set a date as backup. this.lockAfterDate does not need to be persisted, as
- // living in memory seems sufficient. If memory is cleared, then the application will lock anyway.
- let addToNow = (seconds) => {
- let date = new Date();
- date.setSeconds(date.getSeconds() + seconds);
- return date;
+ this._keys = keys;
+ this._authParams = params;
+ this.decryptLocalStorage(keys, params).then(() => {
+ this._locked = false;
+ callback(true);
+ })
+ });
+ }
+
+ setPasscode(passcode, callback) {
+ var uuid = SNJS.crypto.generateUUIDSync();
+
+ SNJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
+ let keys = results.keys;
+ let authParams = results.authParams;
+
+ authParams.hash = keys.pw;
+ this._keys = keys;
+ this._hasPasscode = true;
+ this._authParams = authParams;
+
+ // Encrypting will initially clear localStorage
+ this.encryptLocalStorage(keys, authParams);
+
+ // After it's cleared, it's safe to write to it
+ this.storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
+ callback(true);
+
+ this.notifyObserversOfPasscodeChange();
+ });
+ }
+
+ changePasscode(newPasscode, callback) {
+ this.setPasscode(newPasscode, callback);
+ }
+
+ clearPasscode() {
+ this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
+ this.storageManager.removeItem("offlineParams", StorageManager.Fixed);
+ this._keys = null;
+ this._hasPasscode = false;
+
+ this.notifyObserversOfPasscodeChange();
+ }
+
+ notifyObserversOfPasscodeChange() {
+ for(var observer of this.passcodeChangeObservers) {
+ observer();
+ }
+ }
+
+ encryptLocalStorage(keys, authParams) {
+ this.storageManager.setKeys(keys, authParams);
+ // Switch to Ephemeral storage, wiping Fixed storage
+ // Last argument is `force`, which we set to true because in the case of changing passcode
+ this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
+ }
+
+ async decryptLocalStorage(keys, authParams) {
+ this.storageManager.setKeys(keys, authParams);
+ return this.storageManager.decryptStorage();
+ }
+
+ configureAutoLock() {
+ PasscodeManager.AutoLockPollFocusInterval = 1 * MillisecondsPerSecond;
+
+ PasscodeManager.AutoLockIntervalNone = 0;
+ PasscodeManager.AutoLockIntervalImmediate = 1;
+ PasscodeManager.AutoLockIntervalOneMinute = 60 * MillisecondsPerSecond;
+ PasscodeManager.AutoLockIntervalFiveMinutes = 300 * MillisecondsPerSecond;
+ PasscodeManager.AutoLockIntervalOneHour = 3600 * MillisecondsPerSecond;
+
+ PasscodeManager.AutoLockIntervalKey = "AutoLockIntervalKey";
+
+ if(isDesktopApplication()) {
+ // desktop only
+ this.$rootScope.$on("window-lost-focus", () => {
+ this.documentVisibilityChanged(false);
+ })
+ this.$rootScope.$on("window-gained-focus", () => {
+ this.documentVisibilityChanged(true);
+ })
+ } else {
+ // tab visibility listener, web only
+ document.addEventListener('visibilitychange', (e) => {
+ let visible = document.visibilityState == "visible";
+ this.documentVisibilityChanged(visible);
+ });
+
+ // verify document is in focus every so often as visibilitychange event is not triggered
+ // on a typical window blur event but rather on tab changes
+ this.pollFocusTimeout = setInterval(() => {
+ let hasFocus = document.hasFocus();
+
+ if(hasFocus && this.lastFocusState == "hidden") {
+ this.documentVisibilityChanged(true);
+ } else if(!hasFocus && this.lastFocusState == "visible") {
+ this.documentVisibilityChanged(false);
+ }
+
+ // save this to compare against next time around
+ this.lastFocusState = hasFocus ? "visible" : "hidden";
+ }, PasscodeManager.AutoLockPollFocusInterval);
+ }
+ }
+
+ getAutoLockIntervalOptions() {
+ return [
+ {
+ value: PasscodeManager.AutoLockIntervalNone,
+ label: "Off"
+ },
+ {
+ value: PasscodeManager.AutoLockIntervalImmediate,
+ label: "Immediately"
+ },
+ {
+ value: PasscodeManager.AutoLockIntervalOneMinute,
+ label: "1m"
+ },
+ {
+ value: PasscodeManager.AutoLockIntervalFiveMinutes,
+ label: "5m"
+ },
+ {
+ value: PasscodeManager.AutoLockIntervalOneHour,
+ label: "1h"
}
+ ]
+ }
- this.lockAfterDate = addToNow(interval / MillisecondsPerSecond);
- this.lockTimeout = setTimeout(() => {
+ documentVisibilityChanged(visible) {
+ if(visible) {
+ // check to see if lockAfterDate is not null, and if the application isn't locked.
+ // if that's the case, it needs to be locked immediately.
+ if(this.lockAfterDate && new Date() > this.lockAfterDate && !this.isLocked()) {
this.lockApplication();
- // We don't need to look at this anymore since we've succeeded with timeout lock
- this.lockAfterDate = null;
- }, interval);
+ } else {
+ if(!this.isLocked()) {
+ this.syncManager.sync();
+ }
+ }
+ this.cancelAutoLockTimer();
+ } else {
+ this.beginAutoLockTimer();
}
- cancelAutoLockTimer() {
- clearTimeout(this.lockTimeout);
+ this.notifiyVisibilityObservers(visible);
+ }
+
+ async beginAutoLockTimer() {
+ var interval = await this.getAutoLockInterval();
+ if(interval == PasscodeManager.AutoLockIntervalNone) {
+ return;
+ }
+
+ // Use a timeout if possible, but if the computer is put to sleep, timeouts won't work.
+ // Need to set a date as backup. this.lockAfterDate does not need to be persisted, as
+ // living in memory seems sufficient. If memory is cleared, then the application will lock anyway.
+ let addToNow = (seconds) => {
+ let date = new Date();
+ date.setSeconds(date.getSeconds() + seconds);
+ return date;
+ }
+
+ this.lockAfterDate = addToNow(interval / MillisecondsPerSecond);
+ this.lockTimeout = setTimeout(() => {
+ this.lockApplication();
+ // We don't need to look at this anymore since we've succeeded with timeout lock
this.lockAfterDate = null;
- }
-}
+ }, interval);
+ }
-angular.module('app').service('passcodeManager', PasscodeManager);
+ cancelAutoLockTimer() {
+ clearTimeout(this.lockTimeout);
+ this.lockAfterDate = null;
+ }
+}
diff --git a/app/assets/javascripts/app/services/privilegesManager.js b/app/assets/javascripts/app/services/privilegesManager.js
index 3d30fd280..044e63653 100644
--- a/app/assets/javascripts/app/services/privilegesManager.js
+++ b/app/assets/javascripts/app/services/privilegesManager.js
@@ -1,6 +1,19 @@
-class PrivilegesManager extends SFPrivilegesManager {
+import angular from 'angular';
+import { SFPrivilegesManager } from 'snjs';
- constructor(passcodeManager, authManager, syncManager, singletonManager, modelManager, storageManager, $rootScope, $compile) {
+export class PrivilegesManager extends SFPrivilegesManager {
+
+ /* @ngInject */
+ constructor(
+ passcodeManager,
+ authManager,
+ syncManager,
+ singletonManager,
+ modelManager,
+ storageManager,
+ $rootScope,
+ $compile
+ ) {
super(modelManager, syncManager, singletonManager);
this.$rootScope = $rootScope;
@@ -63,7 +76,4 @@ class PrivilegesManager extends SFPrivilegesManager {
authenticationInProgress() {
return this.currentAuthenticationElement != null;
}
-
}
-
-angular.module('app').service('privilegesManager', PrivilegesManager);
diff --git a/app/assets/javascripts/app/services/sessionHistory.js b/app/assets/javascripts/app/services/sessionHistory.js
index 7b5ac9f04..e8a9e2f48 100644
--- a/app/assets/javascripts/app/services/sessionHistory.js
+++ b/app/assets/javascripts/app/services/sessionHistory.js
@@ -1,7 +1,15 @@
-class SessionHistory extends SFSessionHistoryManager {
-
- constructor(modelManager, storageManager, authManager, passcodeManager, $timeout) {
+import { NoteHistoryEntry } from '@/models/noteHistoryEntry';
+import { SFSessionHistoryManager , SFItemHistory } from 'snjs';
+export class SessionHistory extends SFSessionHistoryManager {
+ /* @ngInject */
+ constructor(
+ modelManager,
+ storageManager,
+ authManager,
+ passcodeManager,
+ $timeout
+ ) {
SFItemHistory.HistoryEntryClassMapping = {
"Note" : NoteHistoryEntry
}
@@ -25,8 +33,12 @@ class SessionHistory extends SFSessionHistoryManager {
}
var contentTypes = ["Note"];
- super(modelManager, storageManager, keyRequestHandler, contentTypes, $timeout);
+ super(
+ modelManager,
+ storageManager,
+ keyRequestHandler,
+ contentTypes,
+ $timeout
+ );
}
}
-
-angular.module('app').service('sessionHistory', SessionHistory);
diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js
index 067e007d1..9c7c05390 100644
--- a/app/assets/javascripts/app/services/singletonManager.js
+++ b/app/assets/javascripts/app/services/singletonManager.js
@@ -1,8 +1,10 @@
-class SingletonManager extends SFSingletonManager {
+import { SFSingletonManager } from 'snjs';
+export class SingletonManager extends SFSingletonManager {
+ // constructor needed for angularjs injection to work
+ // eslint-disable-next-line no-useless-constructor
+ /* @ngInject */
constructor(modelManager, syncManager) {
super(modelManager, syncManager);
}
}
-
-angular.module('app').service('singletonManager', SingletonManager);
diff --git a/app/assets/javascripts/app/services/statusManager.js b/app/assets/javascripts/app/services/statusManager.js
index 30f407da6..d85a3bda6 100644
--- a/app/assets/javascripts/app/services/statusManager.js
+++ b/app/assets/javascripts/app/services/statusManager.js
@@ -1,5 +1,6 @@
-class StatusManager {
+import _ from 'lodash';
+export class StatusManager {
constructor() {
this.statuses = [];
this.observers = [];
@@ -60,8 +61,4 @@ class StatusManager {
removeStatusObserver(callback) {
_.pull(this.statuses, callback);
}
-
-
}
-
-angular.module('app').service('statusManager', StatusManager);
diff --git a/app/assets/javascripts/app/services/storageManager.js b/app/assets/javascripts/app/services/storageManager.js
index 7b1ddbdc3..108dd403d 100644
--- a/app/assets/javascripts/app/services/storageManager.js
+++ b/app/assets/javascripts/app/services/storageManager.js
@@ -1,4 +1,6 @@
-class MemoryStorage {
+import { SNJS, SNEncryptedStorage, SFStorageManager , SFItemParams } from 'snjs';
+
+export class MemoryStorage {
constructor() {
this.memory = {};
}
@@ -36,8 +38,9 @@ class MemoryStorage {
}
}
-class StorageManager extends SFStorageManager {
+export class StorageManager extends SFStorageManager {
+ /* @ngInject */
constructor(dbManager, alertManager) {
super();
this.dbManager = dbManager;
@@ -251,5 +254,3 @@ class StorageManager extends SFStorageManager {
StorageManager.FixedEncrypted = "FixedEncrypted"; // encrypted memoryStorage + localStorage persistence
StorageManager.Ephemeral = "Ephemeral"; // memoryStorage
StorageManager.Fixed = "Fixed"; // localStorage
-
-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 41466a4cd..58ae877fc 100644
--- a/app/assets/javascripts/app/services/syncManager.js
+++ b/app/assets/javascripts/app/services/syncManager.js
@@ -1,6 +1,17 @@
-class SyncManager extends SFSyncManager {
+import angular from 'angular';
+import { SFSyncManager } from 'snjs';
- constructor(modelManager, storageManager, httpManager, $timeout, $interval, $compile, $rootScope) {
+export class SyncManager extends SFSyncManager {
+ /* @ngInject */
+ constructor(
+ modelManager,
+ storageManager,
+ httpManager,
+ $timeout,
+ $interval,
+ $compile,
+ $rootScope
+ ) {
super(modelManager, storageManager, httpManager, $timeout, $interval);
this.$rootScope = $rootScope;
this.$compile = $compile;
@@ -21,7 +32,4 @@ class SyncManager extends SFSyncManager {
var el = this.$compile( "" )(scope);
angular.element(document.body).append(el);
}
-
}
-
-angular.module('app').service('syncManager', SyncManager);
diff --git a/app/assets/javascripts/app/services/themeManager.js b/app/assets/javascripts/app/services/themeManager.js
index 415885918..4c66cfdc8 100644
--- a/app/assets/javascripts/app/services/themeManager.js
+++ b/app/assets/javascripts/app/services/themeManager.js
@@ -1,5 +1,10 @@
-class ThemeManager {
+import _ from 'lodash';
+import angular from 'angular';
+import { SNTheme, SFItemParams } from 'snjs';
+import { StorageManager } from './storageManager';
+export class ThemeManager {
+ /* @ngInject */
constructor(componentManager, desktopManager, storageManager, passcodeManager, $rootScope) {
this.componentManager = componentManager;
this.storageManager = storageManager;
@@ -128,5 +133,3 @@ class ThemeManager {
}
}
}
-
-angular.module('app').service('themeManager', ThemeManager);
diff --git a/app/assets/javascripts/app/utils.js b/app/assets/javascripts/app/utils.js
new file mode 100644
index 000000000..d448bab22
--- /dev/null
+++ b/app/assets/javascripts/app/utils.js
@@ -0,0 +1,104 @@
+export function getParameterByName(name, url) {
+ name = name.replace(/[[\]]/g, '\\$&');
+ var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)');
+ var results = regex.exec(url);
+ if (!results) return null;
+ if (!results[2]) return '';
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
+}
+
+export function parametersFromURL(url) {
+ url = url.split('?').slice(-1)[0];
+ var obj = {};
+ url.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
+ obj[decodeURIComponent(key)] = decodeURIComponent(value);
+ });
+ return obj;
+}
+
+export function getPlatformString() {
+ try {
+ var platform = navigator.platform.toLowerCase();
+ var trimmed = '';
+ if (platform.indexOf('mac') !== -1) {
+ trimmed = 'mac';
+ } else if (platform.indexOf('win') !== -1) {
+ trimmed = 'windows';
+ }
+ if (platform.indexOf('linux') !== -1) {
+ trimmed = 'linux';
+ }
+
+ return trimmed + (isDesktopApplication() ? '-desktop' : '-web');
+ } catch (e) {
+ return null;
+ }
+}
+
+export function isDesktopApplication() {
+ return window.isElectron;
+}
+
+/* Use with numbers and strings, not objects */
+// eslint-disable-next-line no-extend-native
+Array.prototype.containsPrimitiveSubset = function(array) {
+ return !array.some(val => this.indexOf(val) === -1);
+};
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.includes
+if (!Array.prototype.includes) {
+ // eslint-disable-next-line no-extend-native
+ Object.defineProperty(Array.prototype, 'includes', {
+ value: function(searchElement, fromIndex) {
+ if (this == null) {
+ throw new TypeError('"this" is null or not defined');
+ }
+
+ // 1. Let O be ? ToObject(this value).
+ var o = Object(this);
+
+ // 2. Let len be ? ToLength(? Get(O, "length")).
+ var len = o.length >>> 0;
+
+ // 3. If len is 0, return false.
+ if (len === 0) {
+ return false;
+ }
+
+ // 4. Let n be ? ToInteger(fromIndex).
+ // (If fromIndex is undefined, this step produces the value 0.)
+ var n = fromIndex | 0;
+
+ // 5. If n ≥ 0, then
+ // a. Let k be n.
+ // 6. Else n < 0,
+ // a. Let k be len + n.
+ // b. If k < 0, let k be 0.
+ var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
+
+ function sameValueZero(x, y) {
+ return (
+ x === y ||
+ (typeof x === 'number' &&
+ typeof y === 'number' &&
+ isNaN(x) &&
+ isNaN(y))
+ );
+ }
+
+ // 7. Repeat, while k < len
+ while (k < len) {
+ // a. Let elementK be the result of ? Get(O, ! ToString(k)).
+ // b. If SameValueZero(searchElement, elementK) is true, return true.
+ if (sameValueZero(o[k], searchElement)) {
+ return true;
+ }
+ // c. Increase k by 1.
+ k++;
+ }
+
+ // 8. Return false
+ return false;
+ }
+ });
+}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index d7ba63e74..8d4df584f 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1 +1,22 @@
//= require_tree ./app
+
+// css
+import 'sn-stylekit/dist/stylekit.css';
+import '../stylesheets/main.css.scss';
+
+// Vendor
+import 'angular';
+import '../../../vendor/assets/javascripts/angular-sanitize';
+import '../../../vendor/assets/javascripts/zip/deflate';
+import '../../../vendor/assets/javascripts/zip/inflate';
+import '../../../vendor/assets/javascripts/zip/zip';
+import '../../../vendor/assets/javascripts/zip/z-worker';
+
+import { SFItem } from 'snjs';
+
+// Set the app domain before starting the app
+SFItem.AppDomain = 'org.standardnotes.sn';
+
+// entry point
+// eslint-disable-next-line import/first
+import './app/app';
diff --git a/app/assets/stylesheets/app/_ionicons.scss b/app/assets/stylesheets/app/_ionicons.scss
index ae07f0906..bda8c265c 100644
--- a/app/assets/stylesheets/app/_ionicons.scss
+++ b/app/assets/stylesheets/app/_ionicons.scss
@@ -1,4 +1,5 @@
@charset "UTF-8";
+
/*!
Ionicons, v2.0.1
Created by Ben Sperry for the Ionic Framework, http://ionicons.com/
@@ -10,17 +11,38 @@
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.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-locked:before,
-.ion-plus: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("../fonts/ionicons.eot?v=2.0.0");
+ src: url("../fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("../fonts/ionicons.ttf?v=2.0.1") format("truetype"), url("../fonts/ionicons.woff?v=2.0.1") format("woff"), url("../fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg");
+ font-weight: normal;
+ font-style: normal;
}
-.ion-locked:before { content: "\f200"; }
+.ion,
+.ionicons,
+.ion-locked:before,
+.ion-plus: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-plus:before { content: "\f218"; }
+.ion-locked:before {
+ content: "\f200";
+}
+
+.ion-plus:before {
+ content: "\f218";
+}
/*# sourceMappingURL=ionicons.css.map */
diff --git a/app/assets/templates/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml
deleted file mode 100644
index 85e6dec53..000000000
--- a/app/assets/templates/directives/account-menu.html.haml
+++ /dev/null
@@ -1,204 +0,0 @@
-.sn-component
- .sk-panel#account-panel
- .sk-panel-header
- .sk-panel-header-title Account
- %a.sk-a.info.close-button{"ng-click" => "close()"} Close
- .sk-panel-content
- .sk-panel-section.sk-panel-hero{"ng-if" => "!user && !formData.showLogin && !formData.showRegister && !formData.mfa"}
- .sk-panel-row
- .sk-h1 Sign in or register to enable sync and end-to-end encryption.
- .sk-panel-row
- .sk-button-group.stretch
- .sk-button.info.featured{"ng-click" => "formData.showLogin = true"}
- .sk-label Sign In
- .sk-button.info.featured{"ng-click" => "formData.showRegister = true"}
- .sk-label Register
- .sk-panel-row.sk-p
- Standard Notes is free on every platform, and comes standard with sync and encryption.
-
- .sk-panel-section{"ng-if" => "formData.showLogin || formData.showRegister"}
- .sk-panel-section-title
- {{formData.showLogin ? "Sign In" : "Register"}}
-
- %form.sk-panel-form{"ng-submit" => "submitAuthForm()"}
- .sk-panel-section
- %input.sk-input.contrast{:placeholder => 'Email', "sn-autofocus" => 'true',
- "should-focus" => "true", :name => 'email', :required => true, :type => 'email',
- 'ng-model' => 'formData.email', 'spellcheck' => 'false', "ng-model-options"=> "{allowInvalid: true}"}
- %input.sk-input.contrast{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password', 'sn-enter' => 'submitAuthForm()'}
- %input.sk-input.contrast{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf', 'sn-enter' => 'submitAuthForm()'}
- .sk-panel-row
- %a.sk-panel-row.sk-bold{"ng-click" => "formData.showAdvanced = !formData.showAdvanced"}
- Advanced Options
-
- .sk-notification.unpadded.contrast.advanced-options.sk-panel-row{"ng-if" => "formData.showAdvanced"}
- .sk-panel-column.stretch
- .sk-notification-title.sk-panel-row.padded-row Advanced Options
- %div.bordered-row.padded-row
- %label.sk-label Sync Server Domain
- %input.sk-input.mt-5.sk-base{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'}
- %label.sk-label.padded-row{"ng-if" => "formData.showLogin"}
- %input.sk-input{"type" => "checkbox", "ng-model" => "formData.strictSignin"}
- Use strict sign in
- %span
- %a.info{"href" => "https://standardnotes.org/help/security", "target" => "_blank", "rel" => "noopener"} (Learn more)
-
- .sk-panel-section.form-submit{"ng-if" => "!formData.authenticating"}
- .sk-button-group.stretch
- .sk-button.info.featured{'ng-click' => 'submitAuthForm()', "ng-disabled" => "formData.authenticating"}
- .sk-label {{formData.showLogin ? "Sign In" : "Register"}}
-
- .sk-notification.neutral{"ng-if" => "formData.showRegister"}
- .sk-notification-title No Password Reset.
- .sk-notification-text Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.
-
- .sk-panel-section.no-bottom-pad{"ng-if" => "formData.status"}
- .sk-horizontal-group
- .sk-spinner.small.neutral
- .sk-label {{formData.status}}
-
- .sk-panel-section.no-bottom-pad{"ng-if" => "!formData.authenticating"}
- %label.sk-panel-row.justify-left
- .sk-horizontal-group
- %input{"type" => "checkbox", "ng-model" => "formData.ephemeral", "ng-true-value" => "false", "ng-false-value" => "true"}
- Stay signed in
- %label.sk-panel-row.justify-left{"ng-if" => "notesAndTagsCount() > 0"}
- .sk-panel-row
- %input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"}
- Merge local data ({{notesAndTagsCount()}} notes and tags)
-
- .sk-panel-section{"ng-if" => "formData.mfa"}
- %form.sk-panel-form{"ng-submit" => "submitMfaForm()"}
- .sk-p.sk-panel-row {{formData.mfa.message}}
- .sk-panel-row
- %input.sk-input.contrast{:placeholder => "Enter Code", "sn-autofocus" => "true", "should-focus" => "true", :autofocus => "true", :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'}
- .sk-button-group.stretch.sk-panel-row.form-submit{"ng-if" => "!formData.status"}
- %button.sk-button.info.featured{"type" => "submit"}
- .sk-label Sign In
- .sk-panel-section.no-bottom-pad{"ng-if" => "formData.status"}
- .sk-panel-row
- .sk-panel-row
- .sk-horizontal-group
- .sk-spinner.small.neutral
- .sk-label {{formData.status}}
-
- %div{"ng-if" => "!formData.showLogin && !formData.showRegister && !formData.mfa"}
- .sk-panel-section{"ng-if" => "user"}
- .sk-notification.danger{"ng-if" => "syncStatus.error"}
- .sk-notification-title Sync Unreachable
- .sk-notification-text Hmm...we can't seem to sync your account. The reason: {{syncStatus.error.message}}
- %a.sk-a.info-contrast.sk-bold.sk-panel-row{"href" => "https://standardnotes.org/help", "target" => "_blank", "rel" => "noopener"} Need help?
-
- .sk-panel-row
- .sk-panel-column
- .sk-h1.sk-bold.wrap {{user.email}}
- .sk-subtitle.subtle.normal {{server}}
- .sk-horizontal-group{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
- .sk-spinner.small.info
- .sk-sublabel
- {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}}
- %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}}
-
-
- .sk-panel-row
-
- %a.sk-a.info.sk-panel-row.condensed{"ng-click" => "openPasswordWizard('change-pw')"}
- Change Password
- %a.sk-a.info.sk-panel-row.condensed{"ng-show" => "user", "ng-click" => "openPrivilegesModal('')"}
- Manage Privileges
- %a.sk-panel-row.justify-left.condensed.success{"ng-if" => "securityUpdateAvailable", "ng-click" => "openPasswordWizard('upgrade-security')"}
- .inline.sk-circle.small.success.mr-8
- .inline Security Update Available
-
- .sk-panel-section
- .sk-panel-section-title Encryption
- .sk-panel-section-subtitle.info{"ng-if" => "encryptionEnabled()"}
- {{encryptionStatusForNotes()}}
- %p.sk-p
- {{encryptionStatusString()}}
-
- .sk-panel-section
- .sk-panel-section-title Passcode Lock
- %div{"ng-if" => "!hasPasscode()"}
- %div{"ng-if" => "canAddPasscode"}
- .sk-panel-row{"ng-if" => "!formData.showPasscodeForm"}
- .sk-button.info{"ng-click" => "addPasscodeClicked(); $event.stopPropagation();"}
- .sk-label Add Passcode
-
- %p.sk-p Add a passcode to lock the application and encrypt on-device key storage.
-
- %div{"ng-if" => "!canAddPasscode"}
- %p.sk-p Adding a passcode is not supported in temporary sessions. Please sign out, then sign back in with the "Stay signed in" option checked.
-
- %form.sk-panel-form{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"}
- .sk-panel-row
- %input.sk-input.contrast{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "sn-autofocus" => "true", "should-focus" => "true"}
- %input.sk-input.contrast{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"}
- .sk-button-group.stretch.sk-panel-row.form-submit
- %button.sk-button.info{"type" => "submit"}
- .sk-label Set Passcode
- %a.neutral.sk-a.sk-panel-row{"ng-click" => "formData.showPasscodeForm = false"} Cancel
-
- %div{"ng-if" => "hasPasscode() && !formData.showPasscodeForm"}
- .sk-p
- Passcode lock is enabled.
-
- .sk-notification.contrast
- .sk-notification-title Options
- .sk-notification-text
- .sk-panel-row
- .sk-horizontal-group
- .sk-h4.sk-bold Autolock
- %a.sk-a.info{"ng-repeat" => "option in passcodeAutoLockOptions", "ng-click" => "selectAutoLockInterval(option.value)",
- "ng-class" => "{'boxed' : option.value == selectedAutoLockInterval}"}
- {{option.label}}
- .sk-p The autolock timer begins when the window or tab loses focus.
-
- .sk-panel-row
- %a.sk-a.info.sk-panel-row.condensed{"ng-show" => "!user", "ng-click" => "openPrivilegesModal('')"} Manage Privileges
- %a.sk-a.info.sk-panel-row.condensed{"ng-click" => "changePasscodePressed()"} Change Passcode
- %a.sk-a.danger.sk-panel-row.condensed{"ng-click" => "removePasscodePressed()"} Remove Passcode
-
- .sk-panel-section{"ng-if" => "!importData.loading"}
- .sk-panel-section-title Data Backups
- .sk-p
- Download a backup of all your data.
- .sk-panel-row
- %form.sk-panel-form.sk-panel-row{"ng-if" => "encryptedBackupsAvailable()"}
- .sk-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
-
- .sk-button-group.sk-panel-row.justify-left
- .sk-button.info{"ng-click" => "downloadDataArchive()"}
- .sk-label Download Backup
-
- %label.sk-button.info
- %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"}
- .sk-label Import Backup
-
- %span{"ng-if" => "isDesktopApplication()"} Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
-
- #import-password-request{"ng-if" => "importData.requestPassword"}
- %form.sk-panel-form.stretch{"ng-submit" => "submitImportPassword()"}
- %p Enter the account password associated with the import file.
- %input.sk-input.contrast.mt-5{:type => 'password', "placeholder" => "Enter File Account Password", "ng-model" => "importData.password", "autofocus" => "true"}
- .sk-button-group.stretch.sk-panel-row.form-submit
- %button.sk-button.info{"type" => "submit"}
- .sk-label Decrypt & Import
- %p
- Importing from backup will not overwrite existing data, but instead create a duplicate of any differing data.
- %p If you'd like to import only a selection of items instead of the whole file, please use the Batch Manager extension.
- .sk-panel-row
- .sk-spinner.small.info{"ng-if" => "importData.loading"}
- .sk-panel-footer
- .sk-panel-row
- .sk-p.left.neutral.faded {{appVersion}}
- %a.sk-a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"}
- Cancel
- %a.sk-a.right.danger{"ng-if" => "!formData.showLogin && !formData.showRegister", "ng-click" => "destroyLocalData()"}
- {{ user ? "Sign out and clear local data" : "Clear all local data" }}
diff --git a/app/assets/templates/directives/account-menu.pug b/app/assets/templates/directives/account-menu.pug
new file mode 100644
index 000000000..1d87544fe
--- /dev/null
+++ b/app/assets/templates/directives/account-menu.pug
@@ -0,0 +1,180 @@
+.sn-component
+ #account-panel.sk-panel
+ .sk-panel-header
+ .sk-panel-header-title Account
+ a.sk-a.info.close-button(ng-click='close()') Close
+ .sk-panel-content
+ .sk-panel-section.sk-panel-hero(ng-if='!user && !formData.showLogin && !formData.showRegister && !formData.mfa')
+ .sk-panel-row
+ .sk-h1 Sign in or register to enable sync and end-to-end encryption.
+ .sk-panel-row
+ .sk-button-group.stretch
+ .sk-button.info.featured(ng-click='formData.showLogin = true')
+ .sk-label Sign In
+ .sk-button.info.featured(ng-click='formData.showRegister = true')
+ .sk-label Register
+ .sk-panel-row.sk-p
+ | Standard Notes is free on every platform, and comes standard with sync and encryption.
+ .sk-panel-section(ng-if='formData.showLogin || formData.showRegister')
+ .sk-panel-section-title
+ | {{formData.showLogin ? "Sign In" : "Register"}}
+ form.sk-panel-form(ng-submit='submitAuthForm()')
+ .sk-panel-section
+ input.sk-input.contrast(name='email', ng-model='formData.email', ng-model-options='{allowInvalid: true}', placeholder='Email', required='', should-focus='true', sn-autofocus='true', spellcheck='false', type='email')
+ input.sk-input.contrast(name='password', ng-model='formData.user_password', placeholder='Password', required='', sn-enter='submitAuthForm()', type='password')
+ input.sk-input.contrast(name='password', ng-if='formData.showRegister', ng-model='formData.password_conf', placeholder='Confirm Password', required='', sn-enter='submitAuthForm()', type='password')
+ .sk-panel-row
+ a.sk-panel-row.sk-bold(ng-click='formData.showAdvanced = !formData.showAdvanced')
+ | Advanced Options
+ .sk-notification.unpadded.contrast.advanced-options.sk-panel-row(ng-if='formData.showAdvanced')
+ .sk-panel-column.stretch
+ .sk-notification-title.sk-panel-row.padded-row Advanced Options
+ .bordered-row.padded-row
+ label.sk-label Sync Server Domain
+ input.sk-input.mt-5.sk-base(name='server', ng-model='formData.url', placeholder='Server URL', required='', type='text')
+ label.sk-label.padded-row(ng-if='formData.showLogin')
+ input.sk-input(ng-model='formData.strictSignin', type='checkbox')
+ | Use strict sign in
+ span
+ a.info(href='https://standardnotes.org/help/security', rel='noopener', target='_blank') (Learn more)
+ .sk-panel-section.form-submit(ng-if='!formData.authenticating')
+ .sk-button-group.stretch
+ .sk-button.info.featured(ng-click='submitAuthForm()', ng-disabled='formData.authenticating')
+ .sk-label {{formData.showLogin ? "Sign In" : "Register"}}
+ .sk-notification.neutral(ng-if='formData.showRegister')
+ .sk-notification-title No Password Reset.
+ .sk-notification-text
+ | Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.
+ .sk-panel-section.no-bottom-pad(ng-if='formData.status')
+ .sk-horizontal-group
+ .sk-spinner.small.neutral
+ .sk-label {{formData.status}}
+ .sk-panel-section.no-bottom-pad(ng-if='!formData.authenticating')
+ label.sk-panel-row.justify-left
+ .sk-horizontal-group
+ input(ng-false-value='true', ng-model='formData.ephemeral', ng-true-value='false', type='checkbox')
+ | Stay signed in
+ label.sk-panel-row.justify-left(ng-if='notesAndTagsCount() > 0')
+ .sk-panel-row
+ input(ng-bind='true', ng-change='mergeLocalChanged()', ng-model='formData.mergeLocal', type='checkbox')
+ | Merge local data ({{notesAndTagsCount()}} notes and tags)
+ .sk-panel-section(ng-if='formData.mfa')
+ form.sk-panel-form(ng-submit='submitMfaForm()')
+ .sk-p.sk-panel-row {{formData.mfa.message}}
+ .sk-panel-row
+ input.sk-input.contrast(autofocus='true', name='mfa', ng-model='formData.userMfaCode', placeholder='Enter Code', required='', should-focus='true', sn-autofocus='true')
+ .sk-button-group.stretch.sk-panel-row.form-submit(ng-if='!formData.status')
+ button.sk-button.info.featured(type='submit')
+ .sk-label Sign In
+ .sk-panel-section.no-bottom-pad(ng-if='formData.status')
+ .sk-panel-row
+ .sk-panel-row
+ .sk-horizontal-group
+ .sk-spinner.small.neutral
+ .sk-label {{formData.status}}
+ div(ng-if='!formData.showLogin && !formData.showRegister && !formData.mfa')
+ .sk-panel-section(ng-if='user')
+ .sk-notification.danger(ng-if='syncStatus.error')
+ .sk-notification-title Sync Unreachable
+ .sk-notification-text
+ | Hmm...we can't seem to sync your account. The reason: {{syncStatus.error.message}}
+ a.sk-a.info-contrast.sk-bold.sk-panel-row(href='https://standardnotes.org/help', rel='noopener', target='_blank') Need help?
+ .sk-panel-row
+ .sk-panel-column
+ .sk-h1.sk-bold.wrap {{user.email}}
+ .sk-subtitle.subtle.normal {{server}}
+ .sk-horizontal-group(delay='1000', delay-hide='true', show='syncStatus.syncOpInProgress || syncStatus.needsMoreSync')
+ .sk-spinner.small.info
+ .sk-sublabel
+ | {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}}
+ span(ng-if='syncStatus.total > 0') {{syncStatus.current}}/{{syncStatus.total}}
+ .sk-panel-row
+ a.sk-a.info.sk-panel-row.condensed(ng-click="openPasswordWizard('change-pw')")
+ | Change Password
+ a.sk-a.info.sk-panel-row.condensed(ng-click="openPrivilegesModal('')", ng-show='user')
+ | Manage Privileges
+ a.sk-panel-row.justify-left.condensed.success(ng-click="openPasswordWizard('upgrade-security')", ng-if='securityUpdateAvailable')
+ .inline.sk-circle.small.success.mr-8
+ .inline Security Update Available
+ .sk-panel-section
+ .sk-panel-section-title Encryption
+ .sk-panel-section-subtitle.info(ng-if='encryptionEnabled()')
+ | {{encryptionStatusForNotes()}}
+ p.sk-p
+ | {{encryptionStatusString()}}
+ .sk-panel-section
+ .sk-panel-section-title Passcode Lock
+ div(ng-if='!hasPasscode()')
+ div(ng-if='canAddPasscode')
+ .sk-panel-row(ng-if='!formData.showPasscodeForm')
+ .sk-button.info(ng-click='addPasscodeClicked(); $event.stopPropagation();')
+ .sk-label Add Passcode
+ p.sk-p Add a passcode to lock the application and encrypt on-device key storage.
+ div(ng-if='!canAddPasscode')
+ p.sk-p
+ | Adding a passcode is not supported in temporary sessions. Please sign out, then sign back in with the "Stay signed in" option checked.
+ form.sk-panel-form(ng-if='formData.showPasscodeForm', ng-submit='submitPasscodeForm()')
+ .sk-panel-row
+ input.sk-input.contrast(ng-model='formData.passcode', placeholder='Passcode', should-focus='true', sn-autofocus='true', type='password')
+ input.sk-input.contrast(ng-model='formData.confirmPasscode', placeholder='Confirm Passcode', type='password')
+ .sk-button-group.stretch.sk-panel-row.form-submit
+ button.sk-button.info(type='submit')
+ .sk-label Set Passcode
+ a.neutral.sk-a.sk-panel-row(ng-click='formData.showPasscodeForm = false') Cancel
+ div(ng-if='hasPasscode() && !formData.showPasscodeForm')
+ .sk-p
+ | Passcode lock is enabled.
+ .sk-notification.contrast
+ .sk-notification-title Options
+ .sk-notification-text
+ .sk-panel-row
+ .sk-horizontal-group
+ .sk-h4.sk-bold Autolock
+ a.sk-a.info(ng-class="{'boxed' : option.value == selectedAutoLockInterval}", ng-click='selectAutoLockInterval(option.value)', ng-repeat='option in passcodeAutoLockOptions')
+ | {{option.label}}
+ .sk-p The autolock timer begins when the window or tab loses focus.
+ .sk-panel-row
+ a.sk-a.info.sk-panel-row.condensed(ng-click="openPrivilegesModal('')", ng-show='!user') Manage Privileges
+ a.sk-a.info.sk-panel-row.condensed(ng-click='changePasscodePressed()') Change Passcode
+ a.sk-a.danger.sk-panel-row.condensed(ng-click='removePasscodePressed()') Remove Passcode
+ .sk-panel-section(ng-if='!importData.loading')
+ .sk-panel-section-title Data Backups
+ .sk-p
+ | Download a backup of all your data.
+ .sk-panel-row
+ form.sk-panel-form.sk-panel-row(ng-if='encryptedBackupsAvailable()')
+ .sk-input-group
+ label
+ input(ng-change='archiveFormData.encrypted = true', ng-model='archiveFormData.encrypted', ng-value='true', type='radio')
+ | Encrypted
+ label
+ input(ng-change='archiveFormData.encrypted = false', ng-model='archiveFormData.encrypted', ng-value='false', type='radio')
+ | Decrypted
+ .sk-button-group.sk-panel-row.justify-left
+ .sk-button.info(ng-click='downloadDataArchive()')
+ .sk-label Download Backup
+ label.sk-button.info
+ input(file-change='->', handler='importFileSelected(files)', style='display: none;', type='file')
+ .sk-label Import Backup
+ span(ng-if='isDesktopApplication()')
+ | Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
+ #import-password-request(ng-if='importData.requestPassword')
+ form.sk-panel-form.stretch(ng-submit='submitImportPassword()')
+ p Enter the account password associated with the import file.
+ input.sk-input.contrast.mt-5(autofocus='true', ng-model='importData.password', placeholder='Enter File Account Password', type='password')
+ .sk-button-group.stretch.sk-panel-row.form-submit
+ button.sk-button.info(type='submit')
+ .sk-label Decrypt & Import
+ p
+ | Importing from backup will not overwrite existing data, but instead create a duplicate of any differing data.
+ p
+ | If you'd like to import only a selection of items instead of the whole file, please use the Batch Manager extension.
+ .sk-panel-row
+ .sk-spinner.small.info(ng-if='importData.loading')
+ .sk-panel-footer
+ .sk-panel-row
+ .sk-p.left.neutral.faded {{appVersion}}
+ a.sk-a.right(ng-click='formData.showLogin = false; formData.showRegister = false;', ng-if='formData.showLogin || formData.showRegister')
+ | Cancel
+ a.sk-a.right.danger(ng-click='destroyLocalData()', ng-if='!formData.showLogin && !formData.showRegister')
+ | {{ user ? "Sign out and clear local data" : "Clear all local data" }}
diff --git a/app/assets/templates/directives/actions-menu.html.haml b/app/assets/templates/directives/actions-menu.html.haml
deleted file mode 100644
index d7dcb4606..000000000
--- a/app/assets/templates/directives/actions-menu.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-.sn-component
- .sk-menu-panel.dropdown-menu
-
- %a.no-decoration{"ng-if" => "extensions.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank", "rel" => "noopener"}
- %menu-row{"label" => "'Download Actions'"}
-
- %div{"ng-repeat" => "extension in extensions"}
- .sk-menu-panel-header{"ng-click" => "extension.hide = !extension.hide; $event.stopPropagation();"}
- .sk-menu-panel-column
- .sk-menu-panel-header-title {{extension.name}}
- .sk-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)",
- "action" => "executeAction(action, extension);", "label" => "action.label", "subtitle" => "action.desc",
- "spinner-class" => "action.running ? 'info' : null", "sub-rows" => "action.subrows"}
- .sk-sublabel{"ng-if" => "action.access_type"}
- Uses
- %strong {{action.access_type}}
- access to this note.
-
- %menu-row{"ng-if" => "extension.actionsWithContextForItem(item).length == 0", "label" => "'No Actions Available'", "faded" => "true"}
diff --git a/app/assets/templates/directives/actions-menu.pug b/app/assets/templates/directives/actions-menu.pug
new file mode 100644
index 000000000..864562eaf
--- /dev/null
+++ b/app/assets/templates/directives/actions-menu.pug
@@ -0,0 +1,16 @@
+.sn-component
+ .sk-menu-panel.dropdown-menu
+ a.no-decoration(href='https://standardnotes.org/extensions', ng-if='extensions.length == 0', rel='noopener', target='blank')
+ menu-row(label="'Download Actions'")
+ div(ng-repeat='extension in extensions')
+ .sk-menu-panel-header(ng-click='extension.hide = !extension.hide; $event.stopPropagation();')
+ .sk-menu-panel-column
+ .sk-menu-panel-header-title {{extension.name}}
+ .sk-spinner.small.loading(ng-if='extension.loading')
+ div(ng-if='extension.hide') …
+ menu-row(action='executeAction(action, extension);', label='action.label', ng-if='!extension.hide', ng-repeat='action in extension.actionsWithContextForItem(item)', spinner-class="action.running ? 'info' : null", sub-rows='action.subrows', subtitle='action.desc')
+ .sk-sublabel(ng-if="action.access_type")
+ | Uses
+ strong {{action.access_type}}
+ | access to this note.
+ menu-row(faded='true', label="'No Actions Available'", ng-if='extension.actionsWithContextForItem(item).length == 0')
diff --git a/app/assets/templates/directives/component-modal.html.haml b/app/assets/templates/directives/component-modal.html.haml
deleted file mode 100644
index 4501b11aa..000000000
--- a/app/assets/templates/directives/component-modal.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-.sk-modal-background{"ng-click" => "dismiss()"}
-
-.sk-modal-content{"ng-attr-id" => "component-content-outer-{{component.uuid}}"}
- .sn-component
- .sk-panel{"ng-attr-id" => "component-content-inner-{{component.uuid}}"}
- .sk-panel-header
- .sk-panel-header-title
- {{component.name}}
- %a.sk-a.info.close-button{"ng-click" => "dismiss()"} Close
- %component-view.component-view{"component" => "component"}
diff --git a/app/assets/templates/directives/component-modal.pug b/app/assets/templates/directives/component-modal.pug
new file mode 100644
index 000000000..910e7a085
--- /dev/null
+++ b/app/assets/templates/directives/component-modal.pug
@@ -0,0 +1,9 @@
+.sk-modal-background(ng-click="dismiss()")
+.sk-modal-content(ng-attr-id="component-content-outer-{{component.uuid}}")
+ .sn-component
+ .sk-panel(ng-attr-id="component-content-inner-{{component.uuid}}")
+ .sk-panel-header
+ .sk-panel-header-title
+ | {{component.name}}
+ a.sk-a.info.close-button(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
deleted file mode 100644
index ef24cddd9..000000000
--- a/app/assets/templates/directives/component-view.html.haml
+++ /dev/null
@@ -1,81 +0,0 @@
-.sn-component{"ng-if" => "issueLoading"}
- .sk-app-bar.no-edges.no-top-edge.dynamic-height
- .left
- .sk-app-bar-item
- .sk-label.warning There was an issue loading {{component.name}}.
- .right
- .sk-app-bar-item{"ng-click" => "reloadComponent()"}
- .sk-button.info
- .sk-label Reload
-
-.sn-component{"ng-if" => "showNoThemesMessage"}
- .sk-app-bar.no-edges.no-top-edge.dynamic-height
- .left
- .sk-app-bar-item
- .sk-label.warning This extension does not support themes.
- .right
- .sk-app-bar-item{"ng-click" => "noThemesMessageDismiss()"}
- .sk-label Dismiss
- .sk-app-bar-item{"ng-click" => "disableActiveTheme()"}
- .sk-label Disable Active Theme
-
-.sn-component{"ng-if" => "expired"}
- .sk-app-bar.no-edges.no-top-edge.dynamic-height
- .left
- .sk-app-bar-item
- .sk-app-bar-item-column
- .sk-circle.danger.small
- .sk-app-bar-item-column
- %div
- %a.sk-label.sk-base{"href" => "https://dashboard.standardnotes.org", "target" => "_blank", "rel" => "noopener"}
- Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}.
- .sk-p
- Extensions are in a read-only state.
- .right
- .sk-app-bar-item{"ng-click" => "reloadComponent()"}
- .sk-button.info
- .sk-label Reload
- .sk-app-bar-item
- .sk-app-bar-item-column
- .sk-button.warning
- %a.sk-label{"href" => "https://standardnotes.org/help/41/expired", "target" => "_blank", "rel" => "noopener"} Help
-
-.sn-component{"ng-if" => "error == 'offline-restricted'"}
- .sk-panel.static
- .sk-panel-content
- .sk-panel-section.stretch
- .sk-panel-column
- .sk-h1.sk-bold You have restricted this extension to be used offline only.
- .sk-subtitle Offline extensions are not available in the Web app.
- .sk-panel-row
- .sk-panel-row
- .sk-panel-column
- .sk-p You can either:
- %ul
- %li.sk-p 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.sk-p Use the Desktop application.
- .sk-panel-row
- .sk-button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"}
- .sk-label Reload
- .sk-spinner.info.small{"ng-if" => "reloading"}
-
-.sn-component{"ng-if" => "error == 'url-missing'"}
- .sk-panel.static
- .sk-panel-content
- .sk-panel-section.stretch
- .sk-panel-section-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-iframe-{{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
-
-.loading-overlay{"ng-if" => "loading"}
diff --git a/app/assets/templates/directives/component-view.pug b/app/assets/templates/directives/component-view.pug
new file mode 100644
index 000000000..56c15096f
--- /dev/null
+++ b/app/assets/templates/directives/component-view.pug
@@ -0,0 +1,72 @@
+.sn-component(ng-if='issueLoading')
+ .sk-app-bar.no-edges.no-top-edge.dynamic-height
+ .left
+ .sk-app-bar-item
+ .sk-label.warning There was an issue loading {{component.name}}.
+ .right
+ .sk-app-bar-item(ng-click='reloadComponent()')
+ .sk-button.info
+ .sk-label Reload
+.sn-component(ng-if='showNoThemesMessage')
+ .sk-app-bar.no-edges.no-top-edge.dynamic-height
+ .left
+ .sk-app-bar-item
+ .sk-label.warning This extension does not support themes.
+ .right
+ .sk-app-bar-item(ng-click='noThemesMessageDismiss()')
+ .sk-label Dismiss
+ .sk-app-bar-item(ng-click='disableActiveTheme()')
+ .sk-label Disable Active Theme
+.sn-component(ng-if='expired')
+ .sk-app-bar.no-edges.no-top-edge.dynamic-height
+ .left
+ .sk-app-bar-item
+ .sk-app-bar-item-column
+ .sk-circle.danger.small
+ .sk-app-bar-item-column
+ div
+ a.sk-label.sk-base(href='https://dashboard.standardnotes.org', rel='noopener', target='_blank')
+ | Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}.
+ .sk-p
+ | Extensions are in a read-only state.
+ .right
+ .sk-app-bar-item(ng-click='reloadComponent()')
+ .sk-button.info
+ .sk-label Reload
+ .sk-app-bar-item
+ .sk-app-bar-item-column
+ .sk-button.warning
+ a.sk-label(href='https://standardnotes.org/help/41/expired', rel='noopener', target='_blank') Help
+.sn-component(ng-if="error == 'offline-restricted'")
+ .sk-panel.static
+ .sk-panel-content
+ .sk-panel-section.stretch
+ .sk-panel-column
+ .sk-h1.sk-bold You have restricted this extension to be used offline only.
+ .sk-subtitle Offline extensions are not available in the Web app.
+ .sk-panel-row
+ .sk-panel-row
+ .sk-panel-column
+ .sk-p You can either:
+ ul
+ li.sk-p
+ strong 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.sk-p
+ strong Use the Desktop application.
+ .sk-panel-row
+ .sk-button.info(ng-click='reloadStatus()', ng-if='!reloading')
+ .sk-label Reload
+ .sk-spinner.info.small(ng-if='reloading')
+.sn-component(ng-if="error == 'url-missing'")
+ .sk-panel.static
+ .sk-panel-content
+ .sk-panel-section.stretch
+ .sk-panel-section-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(data-component-id='{{component.uuid}}', frameborder='0', ng-attr-id='component-iframe-{{component.uuid}}', ng-if='component && componentValid', ng-src='{{getUrl() | trusted}}', sandbox='allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms')
+ | Loading
+.loading-overlay(ng-if='loading')
diff --git a/app/assets/templates/directives/conflict-resolution-modal.html.haml b/app/assets/templates/directives/conflict-resolution-modal.html.haml
deleted file mode 100644
index c59b7ea03..000000000
--- a/app/assets/templates/directives/conflict-resolution-modal.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
-.sn-component
- .sk-modal.large#conflict-resolution-modal
- .sk-modal-background
- .sk-modal-content
- .sk-panel
- .sk-panel-header
- %h1.sk-panel-header-title Conflicted items — choose which version to keep
- .sk-horizontal-group
- %a.sk-a.info.close-button{"ng-click" => "keepItem1()"} Keep left
- %a.sk-a.info.close-button{"ng-click" => "keepItem2()"} Keep right
- %a.sk-a.info.close-button{"ng-click" => "keepBoth()"} Keep both
- %a.sk-a.info.close-button{"ng-click" => "export()"} Export
- %a.sk-a.info.close-button{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
- .sk-panel-content.selectable
- .sk-panel-section
- %h3
- %strong Content type:
- {{contentType}}
- %p You may wish to look at the "created_at" and "updated_at" fields of the items to gain better context in deciding which to keep.
- #items
- .sk-panel.static#item1.item
- %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{item1Content}}
-
- .border
-
- .sk-panel.static#item2.item
- %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{item2Content}}
diff --git a/app/assets/templates/directives/conflict-resolution-modal.pug b/app/assets/templates/directives/conflict-resolution-modal.pug
new file mode 100644
index 000000000..93b32ee9c
--- /dev/null
+++ b/app/assets/templates/directives/conflict-resolution-modal.pug
@@ -0,0 +1,26 @@
+.sn-component
+ #conflict-resolution-modal.sk-modal.large
+ .sk-modal-background
+ .sk-modal-content
+ .sk-panel
+ .sk-panel-header
+ h1.sk-panel-header-title Conflicted items — choose which version to keep
+ .sk-horizontal-group
+ a.sk-a.info.close-button(ng-click="keepItem1()") Keep left
+ a.sk-a.info.close-button(ng-click="keepItem2()") Keep right
+ a.sk-a.info.close-button(ng-click="keepBoth()") Keep both
+ a.sk-a.info.close-button(ng-click="export()") Export
+ a.sk-a.info.close-button(ng-click="dismiss(); $event.stopPropagation()") Close
+ .sk-panel-content.selectable
+ .sk-panel-section
+ h3
+ strong Content type:
+ | {{contentType}}
+ p
+ | You may wish to look at the "created_at" and "updated_at" fields of the items to gain better context in deciding which to keep.
+ #items
+ #item1.sk-panel.static.item
+ p.normal(style="white-space: pre-wrap; font-size: 16px;") {{item1Content}}
+ .border
+ #item2.sk-panel.static.item
+ p.normal(style="white-space: pre-wrap; font-size: 16px;") {{item2Content}}
diff --git a/app/assets/templates/directives/editor-menu.html.haml b/app/assets/templates/directives/editor-menu.html.haml
deleted file mode 100644
index 29fdca679..000000000
--- a/app/assets/templates/directives/editor-menu.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-.sn-component
- .sk-menu-panel.dropdown-menu
- .sk-menu-panel-section
- .sk-menu-panel-header
- .sk-menu-panel-header-title Note Editor
- %menu-row{"label" => "'Plain Editor'", "circle" => "selectedEditor == null && 'success'", "action" => "selectComponent(null)"}
-
- %menu-row{"ng-repeat" => "editor in editors", "action" => "selectComponent(editor)", "label" => "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'"}
- .sk-menu-panel-column{"ng-if" => "component.content.conflict_of || shouldDisplayRunningLocallyLabel(editor)"}
- %strong.danger.medium-text{"ng-if" => "editor.content.conflict_of"} Conflicted copy
- .sk-sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(editor)"} Running Locally
-
- %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank", "rel" => "noopener"}
- %menu-row{"label" => "'Download More Editors'"}
diff --git a/app/assets/templates/directives/editor-menu.pug b/app/assets/templates/directives/editor-menu.pug
new file mode 100644
index 000000000..51309ea34
--- /dev/null
+++ b/app/assets/templates/directives/editor-menu.pug
@@ -0,0 +1,12 @@
+.sn-component
+ .sk-menu-panel.dropdown-menu
+ .sk-menu-panel-section
+ .sk-menu-panel-header
+ .sk-menu-panel-header-title Note Editor
+ menu-row(action='selectComponent(null)', circle="selectedEditor == null && 'success'", label="'Plain Editor'")
+ menu-row(action='selectComponent(editor)', button-action='toggleDefaultForEditor(editor)', button-class="defaultEditor == editor ? 'warning' : 'info'", button-text="defaultEditor == editor ? 'Undefault' : 'Set Default'", circle="selectedEditor === editor && 'success'", has-button='selectedEditor == editor || defaultEditor == editor', label='editor.name', ng-repeat='editor in editors')
+ .sk-menu-panel-column(ng-if='component.content.conflict_of || shouldDisplayRunningLocallyLabel(editor)')
+ strong.danger.medium-text(ng-if='editor.content.conflict_of') Conflicted copy
+ .sk-sublabel(ng-if='shouldDisplayRunningLocallyLabel(editor)') Running Locally
+ a.no-decoration(href='https://standardnotes.org/extensions', ng-if='editors.length == 0', rel='noopener', target='blank')
+ menu-row(label="'Download More Editors'")
diff --git a/app/assets/templates/directives/input-modal.html.haml b/app/assets/templates/directives/input-modal.pug
similarity index 51%
rename from app/assets/templates/directives/input-modal.html.haml
rename to app/assets/templates/directives/input-modal.pug
index 66d36f10e..e1852835d 100644
--- a/app/assets/templates/directives/input-modal.html.haml
+++ b/app/assets/templates/directives/input-modal.pug
@@ -6,15 +6,14 @@
.sk-panel
.sk-panel-header
.sk-h1.sk-panel-header-title {{title}}
- %a.sk-a.info.close-button{"ng-click" => "dismiss()"} Close
+ a.sk-a.info.close-button(ng-click="dismiss()") Close
.sk-panel-content
.sk-panel-section
.sk-p.sk-panel-row {{message}}
.sk-panel-row
.sk-panel-column.stretch
- %form{"ng-submit" => "submit()"}
- %input.sk-input.contrast{:type => '{{type}}', "ng-model" => "formData.input", "placeholder" => "{{placeholder}}", "sn-autofocus" => "true", "should-focus" => "true"}
-
+ form(ng-submit="submit()")
+ input.sk-input.contrast(ng-model="formData.input" placeholder="{{placeholder}}" should-focus="true" sn-autofocus="true" type="{{type}}")
.sk-panel-footer
- %a.sk-a.info.right{"ng-click" => "submit()"}
- Submit
+ a.sk-a.info.right(ng-click="submit()")
+ | Submit
diff --git a/app/assets/templates/directives/menu-row.html.haml b/app/assets/templates/directives/menu-row.html.haml
deleted file mode 100644
index d1091301c..000000000
--- a/app/assets/templates/directives/menu-row.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-.sk-menu-panel-row.row{"ng-attr-title" => "{{desc}}", "ng-click" => "onClick($event)"}
- .sk-menu-panel-column
- .left
- .sk-menu-panel-column{"ng-if" => "circle && (!circleAlign || circleAlign == 'left')"}
- .sk-circle.small{"ng-class" => "circle"}
- .sk-menu-panel-column{"ng-class" => "{'faded' : faded || disabled}"}
- .sk-label{"ng-class" => "stylekitClass"}
- {{label}}
- .sk-sublabel{"ng-if" => "subtitle"}
- {{subtitle}}
- %ng-transclude
- .sk-menu-panel-subrows{"ng-if" => "subRows && subRows.length > 0"}
- %menu-row{"ng-repeat" => "row in subRows", "action" => "row.onClick()",
- "label" => "row.label", "subtitle" => "row.subtitle", "spinner-class" => "row.spinnerClass"}
-
- .sk-menu-panel-column{"ng-if" => "circle && circleAlign == 'right'"}
- .sk-circle.small{"ng-class" => "circle"}
-
- .sk-menu-panel-column{"ng-if" => "hasButton"}
- .sk-button{"ng-click" => "clickButton($event)", "ng-class" => "buttonClass"}
- .sk-label {{buttonText}}
-
- .sk-menu-panel-column{"ng-if" => "spinnerClass"}
- .sk-spinner.small{"ng-class" => "spinnerClass"}
diff --git a/app/assets/templates/directives/menu-row.pug b/app/assets/templates/directives/menu-row.pug
new file mode 100644
index 000000000..caa286553
--- /dev/null
+++ b/app/assets/templates/directives/menu-row.pug
@@ -0,0 +1,20 @@
+.sk-menu-panel-row.row(ng-attr-title='{{desc}}', ng-click='onClick($event)')
+ .sk-menu-panel-column
+ .left
+ .sk-menu-panel-column(ng-if="circle && (!circleAlign || circleAlign == 'left')")
+ .sk-circle.small(ng-class='circle')
+ .sk-menu-panel-column(ng-class="{'faded' : faded || disabled}")
+ .sk-label(ng-class='stylekitClass')
+ | {{label}}
+ .sk-sublabel(ng-if='subtitle')
+ | {{subtitle}}
+ ng-transclude
+ .sk-menu-panel-subrows(ng-if='subRows && subRows.length > 0')
+ menu-row(action='row.onClick()', label='row.label', ng-repeat='row in subRows', spinner-class='row.spinnerClass', subtitle='row.subtitle')
+ .sk-menu-panel-column(ng-if="circle && circleAlign == 'right'")
+ .sk-circle.small(ng-class='circle')
+ .sk-menu-panel-column(ng-if='hasButton')
+ .sk-button(ng-class='buttonClass', ng-click='clickButton($event)')
+ .sk-label {{buttonText}}
+ .sk-menu-panel-column(ng-if='spinnerClass')
+ .sk-spinner.small(ng-class='spinnerClass')
diff --git a/app/assets/templates/directives/panel-resizer.html.haml b/app/assets/templates/directives/panel-resizer.pug
similarity index 100%
rename from app/assets/templates/directives/panel-resizer.html.haml
rename to app/assets/templates/directives/panel-resizer.pug
diff --git a/app/assets/templates/directives/password-wizard.html.haml b/app/assets/templates/directives/password-wizard.html.haml
deleted file mode 100644
index 77398c42d..000000000
--- a/app/assets/templates/directives/password-wizard.html.haml
+++ /dev/null
@@ -1,105 +0,0 @@
-.sn-component
- #password-wizard.sk-modal.small.auto-height
- .sk-modal-background
- .sk-modal-content
- .sn-component
- .sk-panel
- .sk-panel-header
- .sk-panel-header-title {{title}}
- %a.sk-a.info.close-button{"ng-click" => "dismiss()"} Close
- .sk-panel-content
-
- %div{"ng-if" => "step == 0"}
- %div{"ng-if" => "changePassword"}
- %p.sk-p.sk-panel-row
- Changing your password involves changing your encryption key, which requires your data to be re-encrypted and synced.
- If you have many items, syncing your data can take several minutes.
- %p.sk-p.sk-panel-row You must keep the application window open during this process.
- %div{"ng-if" => "securityUpdate"}
- %p.sk-p.sk-panel-row
- A new update is available for your account. Updates address improvements and enhancements to our security specification.
- This process will guide you through the update, and perform the steps necessary with your supervision.
- .sk-panel-row
- .sk-panel-column
- %p.sk-p For more information about security updates, please visit
- %a.sk-a.info{"href" => "https://standardnotes.org/help/security", "target" => "_blank", "rel" => "noopener"} standardnotes.org/help/security.
-
- %p.sk-panel-row.sk-p
- .info Press Continue to proceed.
-
- .sk-panel-section{"ng-if" => "step > 0"}
-
- .sk-panel-section-title Step {{step}} — {{titleForStep(step)}}
-
- %div{"ng-if" => "step == 1"}
- %p.sk-panel-row.sk-p
- As a result of this process, the entirety of your data will be re-encrypted and synced to your account. This is a generally safe process,
- but unforeseen factors like poor network connectivity or a sudden shutdown of your computer may cause this process to fail.
- It's best to be on the safe side before large operations such as this one.
- .sk-panel-row
- .sk-panel-row
- .sk-button-group
- .sk-button.info{"ng-click" => "downloadBackup(true)"}
- .sk-label Download Encrypted Backup
- .sk-button.info{"ng-click" => "downloadBackup(false)"}
- .sk-label Download Decrypted Backup
-
- %div{"ng-if" => "step == 2"}
- %p.sk-p.sk-panel-row
- As a result of this process, your encryption keys will change.
- Any device on which you use Standard Notes will need to end its session. After this process completes, you will be asked to sign back in.
-
- %p.sk-p.bold.sk-panel-row.info-i Please sign out of all applications (excluding this one), including:
- %ul
- %li.sk-p Desktop
- %li.sk-p Web (Chrome, Firefox, Safari)
- %li.sk-p Mobile (iOS and Android)
- %p.sk-p.sk-panel-row
- If you do not currently have access to a device you're signed in on, you may proceed,
- but must make signing out and back in the first step upon gaining access to that device.
- %p.sk-p.sk-panel-row Press Continue only when you have completed signing out of all your devices.
-
-
- %div{"ng-if" => "step == 3"}
- %div{"ng-if" => "changePassword"}
- %div{"ng-if" => "securityUpdate"}
- %p.sk-panel-row Enter your current password. We'll run this through our encryption scheme to generate strong new encryption keys.
- .sk-panel-row
- .sk-panel-row
- .sk-panel-column.stretch
- %form.sk-panel-form
- %input.sk-input.contrast{:type => 'password', "ng-model" => "formData.currentPassword", "placeholder" => "Current Password", "sn-autofocus" => "true", "should-focus" => "true"}
- %input.sk-input.contrast{"ng-if" => "changePassword", :type => 'password', "ng-model" => "formData.newPassword", "placeholder" => "New Password"}
- %input.sk-input.contrast{"ng-if" => "changePassword", :type => 'password', "ng-model" => "formData.newPasswordConfirmation", "placeholder" => "Confirm New Password"}
-
- %div{"ng-if" => "step == 4"}
- %p.sk-panel-row
- Your data is being re-encrypted with your new keys and synced to your account.
- %p.sk-panel-row.danger{"ng-if" => "lockContinue"}
- Do not close this window until this process completes.
-
- .sk-panel-row
- .sk-panel-column
- .sk-spinner.small.inline.info.mr-5{"ng-if" => "formData.processing"}
- .inline.bold{"ng-class" => "{'info' : !formData.statusError, 'error' : formData.statusError}"}
- {{formData.status}}
- .sk-panel-column{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
- %p.info
- Syncing {{syncStatus.current}}/{{syncStatus.total}}
-
- %div{"ng-if" => "step == 5"}
- %div{"ng-if" => "changePassword"}
- %p.sk-p.sk-panel-row.info-i Your password has been successfully changed.
- %div{"ng-if" => "securityUpdate"}
- %p.sk-p.sk-panel-row.info-i
- The security update has been successfully applied to your account.
- %p.sk-p.sk-panel-row
- Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.
-
- %p.sk-p.sk-panel-row You may now sign back in on all your devices and close this window.
-
- .sk-panel-footer
- .empty
- %a.sk-a.info.right{"ng-click" => "continue()", "ng-disabled" => "lockContinue", "ng-class" => "{'disabled' : lockContinue}"}
- .sk-spinner.small.inline.info.mr-5{"ng-if" => "showSpinner"}
- {{continueTitle}}
diff --git a/app/assets/templates/directives/password-wizard.pug b/app/assets/templates/directives/password-wizard.pug
new file mode 100644
index 000000000..588b9d858
--- /dev/null
+++ b/app/assets/templates/directives/password-wizard.pug
@@ -0,0 +1,93 @@
+.sn-component
+ #password-wizard.sk-modal.small.auto-height
+ .sk-modal-background
+ .sk-modal-content
+ .sn-component
+ .sk-panel
+ .sk-panel-header
+ .sk-panel-header-title {{title}}
+ a.sk-a.info.close-button(ng-click='dismiss()') Close
+ .sk-panel-content
+ div(ng-if='step == 0')
+ div(ng-if='changePassword')
+ p.sk-p.sk-panel-row
+ | Changing your password involves changing your encryption key, which requires your data to be re-encrypted and synced.
+ | If you have many items, syncing your data can take several minutes.
+ p.sk-p.sk-panel-row You must keep the application window open during this process.
+ div(ng-if='securityUpdate')
+ p.sk-p.sk-panel-row
+ | A new update is available for your account. Updates address improvements and enhancements to our security specification.
+ | This process will guide you through the update, and perform the steps necessary with your supervision.
+ .sk-panel-row
+ .sk-panel-column
+ p.sk-p For more information about security updates, please visit
+ a.sk-a.info(href='https://standardnotes.org/help/security', rel='noopener', target='_blank') standardnotes.org/help/security.
+ p.sk-panel-row.sk-p
+ .info Press Continue to proceed.
+ p
+ .sk-panel-section(ng-if='step > 0')
+ .sk-panel-section-title Step {{step}} — {{titleForStep(step)}}
+ div(ng-if='step == 1')
+ p.sk-panel-row.sk-p
+ | As a result of this process, the entirety of your data will be re-encrypted and synced to your account. This is a generally safe process,
+ | but unforeseen factors like poor network connectivity or a sudden shutdown of your computer may cause this process to fail.
+ | It's best to be on the safe side before large operations such as this one.
+ .sk-panel-row
+ .sk-panel-row
+ .sk-button-group
+ .sk-button.info(ng-click='downloadBackup(true)')
+ .sk-label Download Encrypted Backup
+ .sk-button.info(ng-click='downloadBackup(false)')
+ .sk-label Download Decrypted Backup
+ div(ng-if='step == 2')
+ p.sk-p.sk-panel-row
+ | As a result of this process, your encryption keys will change.
+ | Any device on which you use Standard Notes will need to end its session. After this process completes, you will be asked to sign back in.
+ p.sk-p.bold.sk-panel-row.info-i Please sign out of all applications (excluding this one), including:
+ ul
+ li.sk-p Desktop
+ li.sk-p Web (Chrome, Firefox, Safari)
+ li.sk-p Mobile (iOS and Android)
+ p.sk-p.sk-panel-row
+ | If you do not currently have access to a device you're signed in on, you may proceed,
+ | but must make signing out and back in the first step upon gaining access to that device.
+ p.sk-p.sk-panel-row Press Continue only when you have completed signing out of all your devices.
+ div(ng-if='step == 3')
+ div(ng-if='changePassword')
+ div(ng-if='securityUpdate')
+ p.sk-panel-row
+ | Enter your current password. We'll run this through our encryption scheme to generate strong new encryption keys.
+ .sk-panel-row
+ .sk-panel-row
+ .sk-panel-column.stretch
+ form.sk-panel-form
+ input.sk-input.contrast(ng-model='formData.currentPassword', placeholder='Current Password', should-focus='true', sn-autofocus='true', type='password')
+ input.sk-input.contrast(ng-if='changePassword', ng-model='formData.newPassword', placeholder='New Password', type='password')
+ input.sk-input.contrast(ng-if='changePassword', ng-model='formData.newPasswordConfirmation', placeholder='Confirm New Password', type='password')
+ div(ng-if='step == 4')
+ p.sk-panel-row
+ | Your data is being re-encrypted with your new keys and synced to your account.
+ p.sk-panel-row.danger(ng-if='lockContinue')
+ | Do not close this window until this process completes.
+ .sk-panel-row
+ .sk-panel-column
+ .sk-spinner.small.inline.info.mr-5(ng-if='formData.processing')
+ .inline.bold(ng-class="{'info' : !formData.statusError, 'error' : formData.statusError}")
+ | {{formData.status}}
+ .sk-panel-column(delay='1000', delay-hide='true', show='syncStatus.syncOpInProgress || syncStatus.needsMoreSync')
+ p.info
+ | Syncing {{syncStatus.current}}/{{syncStatus.total}}
+ div(ng-if='step == 5')
+ div(ng-if='changePassword')
+ p.sk-p.sk-panel-row.info-i Your password has been successfully changed.
+ div(ng-if='securityUpdate')
+ p.sk-p.sk-panel-row.info-i
+ | The security update has been successfully applied to your account.
+ p.sk-p.sk-panel-row
+ | Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.
+ p.sk-p.sk-panel-row You may now sign back in on all your devices and close this window.
+ .sk-panel-footer
+ .empty
+ a.sk-a.info.right(ng-class="{'disabled' : lockContinue}", ng-click='continue()', ng-disabled='lockContinue')
+ .sk-spinner.small.inline.info.mr-5(ng-if='showSpinner')
+ | {{continueTitle}}
diff --git a/app/assets/templates/directives/permissions-modal.html.haml b/app/assets/templates/directives/permissions-modal.html.haml
deleted file mode 100644
index 4c935571d..000000000
--- a/app/assets/templates/directives/permissions-modal.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-.sk-modal-background{"ng-click" => "deny()"}
-
-.sk-modal-content#permissions-modal
- .sn-component
- .sk-panel
- .sk-panel-header
- .sk-panel-header-title Activate Extension
- %a.sk-a.info.close-button{"ng-click" => "deny()"} Cancel
- .sk-panel-content
- .sk-panel-section
- .sk-panel-row
- .sk-h2
- %strong {{component.name}}
- would like to interact with your
- {{permissionsString}}
-
- .sk-panel-row
- %p.sk-p
- Extensions use an offline messaging system to communicate. Learn more at
- %a.sk-a.info{"href" => "https://standardnotes.org/permissions", "target" => "_blank", "rel" => "noopener"} https://standardnotes.org/permissions.
- .sk-panel-footer
- .sk-button.info.big.block.bold{"ng-click" => "accept()"}
- .sk-label Continue
diff --git a/app/assets/templates/directives/permissions-modal.pug b/app/assets/templates/directives/permissions-modal.pug
new file mode 100644
index 000000000..34741022e
--- /dev/null
+++ b/app/assets/templates/directives/permissions-modal.pug
@@ -0,0 +1,21 @@
+.sk-modal-background(ng-click='deny()')
+#permissions-modal.sk-modal-content
+ .sn-component
+ .sk-panel
+ .sk-panel-header
+ .sk-panel-header-title Activate Extension
+ a.sk-a.info.close-button(ng-click='deny()') Cancel
+ .sk-panel-content
+ .sk-panel-section
+ .sk-panel-row
+ .sk-h2
+ strong {{component.name}}
+ | would like to interact with your
+ | {{permissionsString}}
+ .sk-panel-row
+ p.sk-p
+ | Extensions use an offline messaging system to communicate. Learn more at
+ a.sk-a.info(href='https://standardnotes.org/permissions', rel='noopener', target='_blank') https://standardnotes.org/permissions.
+ .sk-panel-footer
+ .sk-button.info.big.block.bold(ng-click='accept()')
+ .sk-label Continue
diff --git a/app/assets/templates/directives/privileges-auth-modal.html.haml b/app/assets/templates/directives/privileges-auth-modal.html.haml
deleted file mode 100644
index 95d41c9a5..000000000
--- a/app/assets/templates/directives/privileges-auth-modal.html.haml
+++ /dev/null
@@ -1,29 +0,0 @@
-.sk-modal-background{"ng-click" => "cancel()"}
-
-.sk-modal-content#privileges-modal
- .sn-component
- .sk-panel
- .sk-panel-header
- .sk-panel-header-title Authentication Required
- %a.close-button.info{"ng-click" => "cancel()"} Cancel
- .sk-panel-content
- .sk-panel-section
- %div{"ng-repeat" => "credential in requiredCredentials"}
- .sk-p.sk-bold.sk-panel-row
- %strong {{promptForCredential(credential)}}
- .sk-panel-row
- %input.sk-input.contrast{"type" => "password", "ng-model" => "authenticationParameters[credential]",
- "sn-autofocus" => "true", "should-focus" => "$index == 0", "sn-enter" => "submit()"}
- .sk-panel-row
- %label.sk-label.danger{"ng-if" => "isCredentialInFailureState(credential)"} Invalid authentication. Please try again.
- .sk-panel-row
- .sk-panel-row
- .sk-horizontal-group
- .sk-p.sk-bold Remember For
- %a.sk-a.info{"ng-repeat" => "option in sessionLengthOptions", "ng-click" => "selectSessionLength(option.value)",
- "ng-class" => "{'boxed' : option.value == selectedSessionLength}"}
- {{option.label}}
-
- .sk-panel-footer.extra-padding
- .sk-button.info.big.block.bold{"ng-click" => "submit()"}
- .sk-label Submit
diff --git a/app/assets/templates/directives/privileges-auth-modal.pug b/app/assets/templates/directives/privileges-auth-modal.pug
new file mode 100644
index 000000000..bae3343f0
--- /dev/null
+++ b/app/assets/templates/directives/privileges-auth-modal.pug
@@ -0,0 +1,25 @@
+.sk-modal-background(ng-click="cancel()")
+#privileges-modal.sk-modal-content
+ .sn-component
+ .sk-panel
+ .sk-panel-header
+ .sk-panel-header-title Authentication Required
+ a.close-button.info(ng-click="cancel()") Cancel
+ .sk-panel-content
+ .sk-panel-section
+ div(ng-repeat="credential in requiredCredentials")
+ .sk-p.sk-bold.sk-panel-row
+ strong {{promptForCredential(credential)}}
+ .sk-panel-row
+ input.sk-input.contrast(ng-model="authenticationParameters[credential]" should-focus="$index == 0" sn-autofocus="true" sn-enter="submit()" type="password")
+ .sk-panel-row
+ label.sk-label.danger(ng-if="isCredentialInFailureState(credential)") Invalid authentication. Please try again.
+ .sk-panel-row
+ .sk-panel-row
+ .sk-horizontal-group
+ .sk-p.sk-bold Remember For
+ a.sk-a.info(ng-class="{'boxed' : option.value == selectedSessionLength}" ng-click="selectSessionLength(option.value)" ng-repeat="option in sessionLengthOptions")
+ | {{option.label}}
+ .sk-panel-footer.extra-padding
+ .sk-button.info.big.block.bold(ng-click="submit()")
+ .sk-label Submit
diff --git a/app/assets/templates/directives/privileges-management-modal.html.haml b/app/assets/templates/directives/privileges-management-modal.html.haml
deleted file mode 100644
index ba57cb537..000000000
--- a/app/assets/templates/directives/privileges-management-modal.html.haml
+++ /dev/null
@@ -1,41 +0,0 @@
-.sk-modal-background{"ng-click" => "cancel()"}
-
-.sk-modal-content#privileges-modal
- .sn-component
- .sk-panel
- .sk-panel-header
- .sk-panel-header-title Manage Privileges
- %a.sk-a.close-button.info{"ng-click" => "cancel()"} Done
- .sk-panel-content
- .sk-panel-section
- %table.sk-table
- %thead
- %tr
- %th
- %th{"ng-repeat" => "cred in availableCredentials"}
- .priv-header
- %strong {{credentialDisplayInfo[cred].label}}
- .sk-p.font-small{"style" => "margin-top: 2px", "ng-show" => "!credentialDisplayInfo[cred].availability"} Not Configured
- %tbody
- %tr{"ng-repeat" => "action in availableActions"}
- %td
- .sk-p {{displayInfoForAction(action)}}
- %th{"ng-repeat" => "credential in availableCredentials"}
- %input{"type" => "checkbox", "ng-disabled" => "!credentialDisplayInfo[credential].availability", "ng-checked" => "isCredentialRequiredForAction(action, credential)", "ng-click" => "checkboxValueChanged(action, credential)"}
-
- .sk-panel-section{"ng-if" => "sessionExpirey && !sessionExpired"}
- .sk-p.sk-panel-row You will not be asked to authenticate until {{sessionExpirey}}.
- %a.sk-a.sk-panel-row.info{"ng-click" => "clearSession()"} Clear Session
- .sk-panel-footer
- .sk-h2.sk-bold About Privileges
- .sk-panel-section.no-bottom-pad
- .sk-panel-row
- .text-content
- .sk-p
- Privileges represent interface level authentication for accessing certain items and features.
- Note that when your application is unlocked, your data exists in temporary memory in an unencrypted state.
- Privileges are meant to protect against unwanted access in the event of an unlocked application, but do not affect data encryption state.
- %p.sk-p
- Privileges sync across your other devices; however, note that if you require
- a "Local Passcode" privilege, and another device does not have a local passcode set up, the local passcode
- requirement will be ignored on that device.
diff --git a/app/assets/templates/directives/privileges-management-modal.pug b/app/assets/templates/directives/privileges-management-modal.pug
new file mode 100644
index 000000000..ca60bef65
--- /dev/null
+++ b/app/assets/templates/directives/privileges-management-modal.pug
@@ -0,0 +1,39 @@
+.sk-modal-background(ng-click='cancel()')
+#privileges-modal.sk-modal-content
+ .sn-component
+ .sk-panel
+ .sk-panel-header
+ .sk-panel-header-title Manage Privileges
+ a.sk-a.close-button.info(ng-click='cancel()') Done
+ .sk-panel-content
+ .sk-panel-section
+ table.sk-table
+ thead
+ tr
+ th
+ th(ng-repeat='cred in availableCredentials')
+ .priv-header
+ strong {{credentialDisplayInfo[cred].label}}
+ .sk-p.font-small(ng-show='!credentialDisplayInfo[cred].availability', style='margin-top: 2px') Not Configured
+ tbody
+ tr(ng-repeat='action in availableActions')
+ td
+ .sk-p {{displayInfoForAction(action)}}
+ th(ng-repeat='credential in availableCredentials')
+ input(ng-checked='isCredentialRequiredForAction(action, credential)', ng-click='checkboxValueChanged(action, credential)', ng-disabled='!credentialDisplayInfo[credential].availability', type='checkbox')
+ .sk-panel-section(ng-if='sessionExpirey && !sessionExpired')
+ .sk-p.sk-panel-row You will not be asked to authenticate until {{sessionExpirey}}.
+ a.sk-a.sk-panel-row.info(ng-click='clearSession()') Clear Session
+ .sk-panel-footer
+ .sk-h2.sk-bold About Privileges
+ .sk-panel-section.no-bottom-pad
+ .sk-panel-row
+ .text-content
+ .sk-p
+ | Privileges represent interface level authentication for accessing certain items and features.
+ | Note that when your application is unlocked, your data exists in temporary memory in an unencrypted state.
+ | Privileges are meant to protect against unwanted access in the event of an unlocked application, but do not affect data encryption state.
+ p.sk-p
+ | Privileges sync across your other devices; however, note that if you require
+ | a "Local Passcode" privilege, and another device does not have a local passcode set up, the local passcode
+ | requirement will be ignored on that device.
diff --git a/app/assets/templates/directives/revision-preview-modal.html.haml b/app/assets/templates/directives/revision-preview-modal.html.haml
deleted file mode 100644
index bd0a5158a..000000000
--- a/app/assets/templates/directives/revision-preview-modal.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-.sn-component
- .sk-modal.medium#item-preview-modal
- .sk-modal-background
- .sk-modal-content
- .sn-component
- .sk-panel
- .sk-panel-header
- .sk-panel-header-title Preview
- .sk-horizontal-group
- %a.sk-a.info.close-button{"ng-click" => "restore(false)"} Restore
- %a.sk-a.info.close-button{"ng-click" => "restore(true)"} Restore as copy
- %a.sk-a.info.close-button{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
- .sk-panel-content.selectable{"ng-if" => "!editor"}
- .sk-h2 {{content.title}}
- %p.normal.sk-p{"style" => "white-space: pre-wrap; font-size: 16px;"} {{content.text}}
-
- %component-view.component-view{"ng-if" => "editor", "component" => "editor"}
diff --git a/app/assets/templates/directives/revision-preview-modal.pug b/app/assets/templates/directives/revision-preview-modal.pug
new file mode 100644
index 000000000..37eb5f38c
--- /dev/null
+++ b/app/assets/templates/directives/revision-preview-modal.pug
@@ -0,0 +1,16 @@
+.sn-component
+ #item-preview-modal.sk-modal.medium
+ .sk-modal-background
+ .sk-modal-content
+ .sn-component
+ .sk-panel
+ .sk-panel-header
+ .sk-panel-header-title Preview
+ .sk-horizontal-group
+ a.sk-a.info.close-button(ng-click="restore(false)") Restore
+ a.sk-a.info.close-button(ng-click="restore(true)") Restore as copy
+ a.sk-a.info.close-button(ng-click="dismiss(); $event.stopPropagation()") Close
+ .sk-panel-content.selectable(ng-if="!editor")
+ .sk-h2 {{content.title}}
+ p.normal.sk-p(style="white-space: pre-wrap; font-size: 16px;") {{content.text}}
+ component-view.component-view(component="editor" ng-if="editor")
diff --git a/app/assets/templates/directives/session-history-menu.html.haml b/app/assets/templates/directives/session-history-menu.html.haml
deleted file mode 100644
index b060d299e..000000000
--- a/app/assets/templates/directives/session-history-menu.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-.sn-component#session-history-menu
- .sk-menu-panel.dropdown-menu
- .sk-menu-panel-header
- .sk-menu-panel-header-title {{history.entries.length || 'No'}} revisions
- %a.sk-a.info.sk-h5{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"} Options
-
- %div{"ng-if" => "showOptions"}
- %menu-row{"label" => "'Clear note local history'", "action" => "clearItemHistory()"}
- %menu-row{"label" => "'Clear all local history'", "action" => "clearAllHistory()"}
- %menu-row{"label" => "(autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'", "action" => "toggleAutoOptimize()"}
- .sk-sublabel
- Automatically cleans up small revisions to conserve space.
- %menu-row{"label" => "(diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'", "action" => "toggleDiskSaving()"}
- .sk-sublabel
- Saving to disk is not recommended. Decreases performance and increases app loading time and memory footprint.
-
- %menu-row{"ng-repeat" => "revision in entries",
- "action" => "openRevision(revision);",
- "label" => "revision.previewTitle()"}
- .sk-sublabel.opaque{"ng-class" => "classForRevision(revision)"}
- {{revision.previewSubTitle()}}
diff --git a/app/assets/templates/directives/session-history-menu.pug b/app/assets/templates/directives/session-history-menu.pug
new file mode 100644
index 000000000..961b8e115
--- /dev/null
+++ b/app/assets/templates/directives/session-history-menu.pug
@@ -0,0 +1,17 @@
+#session-history-menu.sn-component
+ .sk-menu-panel.dropdown-menu
+ .sk-menu-panel-header
+ .sk-menu-panel-header-title {{history.entries.length || 'No'}} revisions
+ a.sk-a.info.sk-h5(ng-click='showOptions = !showOptions; $event.stopPropagation();') Options
+ div(ng-if='showOptions')
+ menu-row(action='clearItemHistory()', label="'Clear note local history'")
+ menu-row(action='clearAllHistory()', label="'Clear all local history'")
+ menu-row(action='toggleAutoOptimize()', label="(autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'")
+ .sk-sublabel
+ | Automatically cleans up small revisions to conserve space.
+ menu-row(action='toggleDiskSaving()', label="(diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'")
+ .sk-sublabel
+ | Saving to disk is not recommended. Decreases performance and increases app loading time and memory footprint.
+ menu-row(action='openRevision(revision);', label='revision.previewTitle()', ng-repeat='revision in entries')
+ .sk-sublabel.opaque(ng-class='classForRevision(revision)')
+ | {{revision.previewSubTitle()}}
diff --git a/app/assets/templates/directives/sync-resolution-menu.html.haml b/app/assets/templates/directives/sync-resolution-menu.html.haml
deleted file mode 100644
index eaeb4469f..000000000
--- a/app/assets/templates/directives/sync-resolution-menu.html.haml
+++ /dev/null
@@ -1,60 +0,0 @@
-.sn-component
- .sk-panel.sk-panel-right#sync-resolution-menu
- .sk-panel-header
- .sk-panel-header-title Out of Sync
- %a.sk-a.info.close-button{"ng-click" => "close()"} Close
- .sk-panel-content
-
- .sk-panel-section
- .sk-panel-row.sk-p
- We've detected that the data on the server may not match the data in the current application session.
- .sk-p.sk-panel-row
- .sk-panel-column
- %strong.sk-panel-row Option 1 — Restart App:
- .sk-p Quit the application and re-open it. Sometimes, this may resolve the issue.
- .sk-p.sk-panel-row
- .sk-panel-column
- %strong.sk-panel-row Option 2 (recommended) — Sign Out:
- .sk-p Sign out of your account, then sign back in. This will ensure your data is consistent with the server.
- Be sure to download a backup of your data before doing so.
- .sk-p.sk-panel-row
- .sk-panel-column
- %strong.sk-panel-row Option 3 — Sync Resolution:
- .sk-p
- We can attempt to reconcile changes by downloading all data from the server.
- No existing data will be overwritten. If the local contents of an item differ from what the server has,
- a conflicted copy will be created.
-
- %div{"ng-if" => "!status.backupFinished"}
- .sk-p.sk-panel-row
- Please download a backup before we attempt to perform a full account sync resolution.
- .sk-panel-row
- .sk-button-group
- .sk-button.info{"ng-click" => "downloadBackup(true)"}
- .sk-label Encrypted
- .sk-button.info{"ng-click" => "downloadBackup(false)"}
- .sk-label Decrypted
- .sk-button.danger{"ng-click" => "skipBackup()"}
- .sk-label Skip
-
- %div{"ng-if" => "status.backupFinished"}
- .sk-panel-row{"ng-if" => "!status.resolving && !status.attemptedResolution"}
- .sk-button.info{"ng-click" => "performSyncResolution()"}
- .sk-label Perform Sync Resolution
-
- .sk-panel-row.justify-left{"ng-if" => "status.resolving"}
- .sk-horizontal-group
- .sk-spinner.small.info
- .sk-label Attempting sync resolution...
-
- .sk-panel-column{"ng-if" => "status.fail"}
- .sk-panel-row.sk-label.danger Sync Resolution Failed
- .sk-p.sk-panel-row
- We attempted to reconcile local content and server content, but were unable to do so.
- At this point, we recommend signing out of your account and signing back in. You may
- wish to download a data backup before doing so.
-
- .sk-panel-column{"ng-if" => "status.success"}
- .sk-panel-row.sk-label.success Sync Resolution Success
- .sk-p.sk-panel-row
- Your local data is now in sync with the server. You may close this window.
diff --git a/app/assets/templates/directives/sync-resolution-menu.pug b/app/assets/templates/directives/sync-resolution-menu.pug
new file mode 100644
index 000000000..9b84bb116
--- /dev/null
+++ b/app/assets/templates/directives/sync-resolution-menu.pug
@@ -0,0 +1,55 @@
+.sn-component
+ #sync-resolution-menu.sk-panel.sk-panel-right
+ .sk-panel-header
+ .sk-panel-header-title Out of Sync
+ a.sk-a.info.close-button(ng-click='close()') Close
+ .sk-panel-content
+ .sk-panel-section
+ .sk-panel-row.sk-p
+ | We've detected that the data on the server may not match the data in the current application session.
+ .sk-p.sk-panel-row
+ .sk-panel-column
+ strong.sk-panel-row Option 1 — Restart App:
+ .sk-p Quit the application and re-open it. Sometimes, this may resolve the issue.
+ .sk-p.sk-panel-row
+ .sk-panel-column
+ strong.sk-panel-row Option 2 (recommended) — Sign Out:
+ .sk-p
+ | Sign out of your account, then sign back in. This will ensure your data is consistent with the server.
+ | Be sure to download a backup of your data before doing so.
+ .sk-p.sk-panel-row
+ .sk-panel-column
+ strong.sk-panel-row Option 3 — Sync Resolution:
+ .sk-p
+ | We can attempt to reconcile changes by downloading all data from the server.
+ | No existing data will be overwritten. If the local contents of an item differ from what the server has,
+ | a conflicted copy will be created.
+ div(ng-if='!status.backupFinished')
+ .sk-p.sk-panel-row
+ | Please download a backup before we attempt to perform a full account sync resolution.
+ .sk-panel-row
+ .sk-button-group
+ .sk-button.info(ng-click='downloadBackup(true)')
+ .sk-label Encrypted
+ .sk-button.info(ng-click='downloadBackup(false)')
+ .sk-label Decrypted
+ .sk-button.danger(ng-click='skipBackup()')
+ .sk-label Skip
+ div(ng-if='status.backupFinished')
+ .sk-panel-row(ng-if='!status.resolving && !status.attemptedResolution')
+ .sk-button.info(ng-click='performSyncResolution()')
+ .sk-label Perform Sync Resolution
+ .sk-panel-row.justify-left(ng-if='status.resolving')
+ .sk-horizontal-group
+ .sk-spinner.small.info
+ .sk-label Attempting sync resolution...
+ .sk-panel-column(ng-if='status.fail')
+ .sk-panel-row.sk-label.danger Sync Resolution Failed
+ .sk-p.sk-panel-row
+ | We attempted to reconcile local content and server content, but were unable to do so.
+ | At this point, we recommend signing out of your account and signing back in. You may
+ | wish to download a data backup before doing so.
+ .sk-panel-column(ng-if='status.success')
+ .sk-panel-row.sk-label.success Sync Resolution Success
+ .sk-p.sk-panel-row
+ | Your local data is now in sync with the server. You may close this window.
diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml
deleted file mode 100644
index 084927059..000000000
--- a/app/assets/templates/editor.html.haml
+++ /dev/null
@@ -1,102 +0,0 @@
-.section.editor#editor-column.sn-component{"aria-label" => "Note"}
- .sn-component
- .sk-app-bar.no-edges{"ng-if" => "ctrl.note.locked", "ng-init" => "ctrl.lockText = 'Note Locked'", "ng-mouseover" => "ctrl.lockText = 'Unlock'", "ng-mouseleave" => "ctrl.lockText = 'Note Locked'"}
- .left
- .sk-app-bar-item{"ng-click" => "ctrl.toggleLockNote()"}
- .sk-label.warning
- %i.icon.ion-locked
- {{ctrl.lockText}}
-
- #editor-title-bar.section-title-bar{"ng-show" => "ctrl.note && !ctrl.note.errorDecrypting", "ng-class" => "{'locked' : ctrl.note.locked }"}
- .title
- %input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.onTitleEnter($event)",
- "ng-change" => "ctrl.onTitleChange()", "ng-focus" => "ctrl.onNameFocus()", "ng-blur" => "ctrl.onNameBlur()",
- "select-on-click" => "true", "ng-disabled" => "ctrl.note.locked", "spellcheck" => "false"}
-
- #save-status
- .message{"ng-class" => "{'warning sk-bold': ctrl.syncTakingTooLong, 'danger sk-bold': ctrl.saveError}"} {{ctrl.noteStatus.message}}
- .desc{"ng-show" => "ctrl.noteStatus.desc"} {{ctrl.noteStatus.desc}}
-
- .editor-tags
- #note-tags-component-container{"ng-if" => "ctrl.tagsComponent"}
- %component-view.component-view{ "component" => "ctrl.tagsComponent", "ng-style" => "ctrl.note.locked && {'pointer-events' : 'none'}", "ng-class" => "{'locked' : ctrl.note.locked }"}
- %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)",
- "spellcheck" => "false", "ng-disabled" => "ctrl.note.locked"}
-
- .sn-component{"ng-if" => "ctrl.note"}
- .sk-app-bar.no-edges#editor-menu-bar
- .left
- .sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showMenu')", "ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"}
- .sk-label Options
- .sk-menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"}
- .sk-menu-panel-section
- .sk-menu-panel-header
- .sk-menu-panel-header-title Note Options
- %menu-row{"label" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "action" => "ctrl.selectedMenuItem(true); ctrl.togglePin()", "desc" => "'Pin or unpin a note from the top of your list'"}
- %menu-row{"label" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleArchiveNote()", "desc" => "'Archive or unarchive a note from your Archived system tag'"}
- %menu-row{"label" => "ctrl.note.locked ? 'Unlock' : 'Lock'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleLockNote()", "desc" => "'Locking notes prevents unintentional editing'"}
- %menu-row{"label" => "ctrl.note.content.protected ? 'Unprotect' : 'Protect'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleProtectNote()", "desc" => "'Protecting a note will require credentials to view it (Manage Privileges via Account menu)'"}
- %menu-row{"label" => "'Preview'", "circle" => "ctrl.note.content.hidePreview ? 'danger' : 'success'", "circle-align" => "'right'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleNotePreview()", "desc" => "'Hide or unhide the note preview from the list of notes'"}
- %menu-row{"ng-show" => "!ctrl.altKeyDown && !ctrl.note.content.trashed && !ctrl.note.errorDecrypting", "label" => "'Move to Trash'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNote()", "stylekit-class" => "'warning'", "desc" => "'Send this note to the trash'"}
- %menu-row{"ng-show" => "!ctrl.note.content.trashed && ctrl.note.errorDecrypting", "label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
-
- %div{"ng-if" => "ctrl.note.content.trashed || ctrl.altKeyDown"}
- %menu-row{"label" => "'Restore'", "ng-show" => "ctrl.note.content.trashed", "action" => "ctrl.selectedMenuItem(true); ctrl.restoreTrashedNote()", "stylekit-class" => "'info'", "desc" => "'Undelete this note and restore it back into your notes'"}
- %menu-row{"label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(true); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
- %menu-row{"label" => "'Empty Trash'", "ng-show" => "ctrl.note.content.trashed || !ctrl.altKeyDown", "subtitle" => "ctrl.getTrashCount() + ' notes in trash'", "action" => "ctrl.selectedMenuItem(true); ctrl.emptyTrash()", "stylekit-class" => "'danger'", "desc" => "'Permanently delete all notes in the trash'"}
-
- .sk-menu-panel-section
- .sk-menu-panel-header
- .sk-menu-panel-header-title Global Display
-
- %menu-row{"label" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'neutral'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('monospaceFont')",
- "desc" => "'Toggles the font style for the default editor'", "subtitle" => "ctrl.selectedEditor ? 'Not available with editor extensions' : null", "disabled" => "ctrl.selectedEditor"}
-
- %menu-row{"label" => "'Spellcheck'", "circle" => "ctrl.spellcheck ? 'success' : 'neutral'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('spellcheck')",
- "desc" => "'Toggles spellcheck for the default editor'", "subtitle" => "ctrl.selectedEditor ? 'Not available with editor extensions' : (ctrl.isDesktop ? 'May degrade editor performance' : null)", "disabled" => "ctrl.selectedEditor"}
-
- %menu-row{"label" => "'Margin Resizers'", "circle" => "ctrl.marginResizersEnabled ? 'success' : 'neutral'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('marginResizersEnabled')",
- "desc" => "'Allows for editor left and right margins to be resized'", "faded" => "!ctrl.marginResizersEnabled"}
-
- .sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showEditorMenu')", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}
- .sk-label Editor
- %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.editorMenuOnSelect", "selected-editor" => "ctrl.selectedEditor", "current-item" => "ctrl.note"}
-
- .sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showExtensions')", "ng-class" => "{'selected' : ctrl.showExtensions}", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"}
- .sk-label Actions
- %actions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
-
- .sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showSessionHistory')", "click-outside" => "ctrl.showSessionHistory = false;", "is-open" => "ctrl.showSessionHistory"}
- .sk-label Session History
- %session-history-menu{"ng-if" => "ctrl.showSessionHistory", "item" => "ctrl.note"}
-
- .editor-content#editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting"}
-
- %panel-resizer.left{"ng-if" => "ctrl.marginResizersEnabled", "panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.leftResizeControl", "min-width" => 300, "property" => "'left'", "hoverable" => "true"}
-
- %component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor", "on-load" => "ctrl.onEditorLoad"}
-
- %textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", "ng-readonly" => "ctrl.note.locked",
- "ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()",
- "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}",
- "ng-model-options"=>"{ debounce: ctrl.EditorNgDebounce }"}
- {{ctrl.onSystemEditorLoad()}}
-
- %panel-resizer{"ng-if" => "ctrl.marginResizersEnabled", "panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish", "control" => "ctrl.rightResizeControl", "min-width" => 300, "hoverable" => "true", "property" => "'right'"}
-
- .section{"ng-show" => "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.
-
- #editor-pane-component-stack{"ng-show" => "ctrl.note"}
- #component-stack-menu-bar.sk-app-bar.no-edges{"ng-if" => "ctrl.componentStack.length"}
- .left
- .sk-app-bar-item{"ng-repeat" => "component in ctrl.componentStack", "ng-click" => "ctrl.toggleStackComponentForCurrentItem(component)"}
- .sk-app-bar-item-column
- .sk-circle.small{"ng-class" => "{'info' : !component.hidden && component.active, 'neutral' : component.hidden || !component.active}"}
- .sk-app-bar-item-column
- .sk-label {{component.name}}
-
- .sn-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/editor.pug b/app/assets/templates/editor.pug
new file mode 100644
index 000000000..c4f38f819
--- /dev/null
+++ b/app/assets/templates/editor.pug
@@ -0,0 +1,72 @@
+#editor-column.section.editor.sn-component(aria-label='Note')
+ .sn-component
+ .sk-app-bar.no-edges(ng-if='ctrl.note.locked', ng-init="ctrl.lockText = 'Note Locked'", ng-mouseleave="ctrl.lockText = 'Note Locked'", ng-mouseover="ctrl.lockText = 'Unlock'")
+ .left
+ .sk-app-bar-item(ng-click='ctrl.toggleLockNote()')
+ .sk-label.warning
+ i.icon.ion-locked
+ | {{ctrl.lockText}}
+ #editor-title-bar.section-title-bar(ng-class="{'locked' : ctrl.note.locked}", ng-show='ctrl.note && !ctrl.note.errorDecrypting')
+ .title
+ input#note-title-editor.input(ng-blur='ctrl.onNameBlur()', ng-change='ctrl.onTitleChange()', ng-disabled='ctrl.note.locked', ng-focus='ctrl.onNameFocus()', ng-keyup='$event.keyCode == 13 && ctrl.onTitleEnter($event)', ng-model='ctrl.note.title', select-on-click='true', spellcheck='false')
+ #save-status
+ .message(ng-class="{'warning sk-bold': ctrl.syncTakingTooLong, 'danger sk-bold': ctrl.saveError}") {{ctrl.noteStatus.message}}
+ .desc(ng-show='ctrl.noteStatus.desc') {{ctrl.noteStatus.desc}}
+ .editor-tags
+ #note-tags-component-container(ng-if='ctrl.tagsComponent')
+ component-view.component-view(component='ctrl.tagsComponent', ng-class="{'locked' : ctrl.note.locked}", ng-style="ctrl.note.locked && {'pointer-events' : 'none'}")
+ input.tags-input(ng-blur='ctrl.updateTagsFromTagsString($event, ctrl.tagsString)', ng-disabled='ctrl.note.locked', ng-if='!(ctrl.tagsComponent && ctrl.tagsComponent.active)', ng-keyup='$event.keyCode == 13 && $event.target.blur();', ng-model='ctrl.tagsString', placeholder='#tags', spellcheck='false', type='text')
+ .sn-component(ng-if='ctrl.note')
+ #editor-menu-bar.sk-app-bar.no-edges
+ .left
+ .sk-app-bar-item(click-outside='ctrl.showMenu = false;', is-open='ctrl.showMenu', ng-class="{'selected' : ctrl.showMenu}", ng-click="ctrl.toggleMenu('showMenu')")
+ .sk-label Options
+ .sk-menu-panel.dropdown-menu(ng-if='ctrl.showMenu')
+ .sk-menu-panel-section
+ .sk-menu-panel-header
+ .sk-menu-panel-header-title Note Options
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.togglePin()', desc="'Pin or unpin a note from the top of your list'", label="ctrl.note.pinned ? 'Unpin' : 'Pin'")
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.toggleArchiveNote()', desc="'Archive or unarchive a note from your Archived system tag'", label="ctrl.note.archived ? 'Unarchive' : 'Archive'")
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.toggleLockNote()', desc="'Locking notes prevents unintentional editing'", label="ctrl.note.locked ? 'Unlock' : 'Lock'")
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.toggleProtectNote()', desc="'Protecting a note will require credentials to view it (Manage Privileges via Account menu)'", label="ctrl.note.content.protected ? 'Unprotect' : 'Protect'")
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.toggleNotePreview()', circle="ctrl.note.content.hidePreview ? 'danger' : 'success'", circle-align="'right'", desc="'Hide or unhide the note preview from the list of notes'", label="'Preview'")
+ menu-row(action='ctrl.selectedMenuItem(); ctrl.deleteNote()', desc="'Send this note to the trash'", label="'Move to Trash'", ng-show='!ctrl.altKeyDown && !ctrl.note.content.trashed && !ctrl.note.errorDecrypting', stylekit-class="'warning'")
+ menu-row(action='ctrl.selectedMenuItem(); ctrl.deleteNotePermanantely()', desc="'Delete this note permanently from all your devices'", label="'Delete Permanently'", ng-show='!ctrl.note.content.trashed && ctrl.note.errorDecrypting', stylekit-class="'danger'")
+ div(ng-if='ctrl.note.content.trashed || ctrl.altKeyDown')
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.restoreTrashedNote()', desc="'Undelete this note and restore it back into your notes'", label="'Restore'", ng-show='ctrl.note.content.trashed', stylekit-class="'info'")
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.deleteNotePermanantely()', desc="'Delete this note permanently from all your devices'", label="'Delete Permanently'", stylekit-class="'danger'")
+ menu-row(action='ctrl.selectedMenuItem(true); ctrl.emptyTrash()', desc="'Permanently delete all notes in the trash'", label="'Empty Trash'", ng-show='ctrl.note.content.trashed || !ctrl.altKeyDown', stylekit-class="'danger'", subtitle="ctrl.getTrashCount() + ' notes in trash'")
+ .sk-menu-panel-section
+ .sk-menu-panel-header
+ .sk-menu-panel-header-title Global Display
+ menu-row(action="ctrl.selectedMenuItem(true); ctrl.toggleKey('monospaceFont')", circle="ctrl.monospaceFont ? 'success' : 'neutral'", desc="'Toggles the font style for the default editor'", disabled='ctrl.selectedEditor', label="'Monospace Font'", subtitle="ctrl.selectedEditor ? 'Not available with editor extensions' : null")
+ menu-row(action="ctrl.selectedMenuItem(true); ctrl.toggleKey('spellcheck')", circle="ctrl.spellcheck ? 'success' : 'neutral'", desc="'Toggles spellcheck for the default editor'", disabled='ctrl.selectedEditor', label="'Spellcheck'", subtitle="ctrl.selectedEditor ? 'Not available with editor extensions' : (ctrl.isDesktop ? 'May degrade editor performance' : null)")
+ menu-row(action="ctrl.selectedMenuItem(true); ctrl.toggleKey('marginResizersEnabled')", circle="ctrl.marginResizersEnabled ? 'success' : 'neutral'", desc="'Allows for editor left and right margins to be resized'", faded='!ctrl.marginResizersEnabled', label="'Margin Resizers'")
+ .sk-app-bar-item(click-outside='ctrl.showEditorMenu = false;', is-open='ctrl.showEditorMenu', ng-class="{'selected' : ctrl.showEditorMenu}", ng-click="ctrl.toggleMenu('showEditorMenu')")
+ .sk-label Editor
+ editor-menu(callback='ctrl.editorMenuOnSelect', current-item='ctrl.note', ng-if='ctrl.showEditorMenu', selected-editor='ctrl.selectedEditor')
+ .sk-app-bar-item(click-outside='ctrl.showExtensions = false;', is-open='ctrl.showExtensions', ng-class="{'selected' : ctrl.showExtensions}", ng-click="ctrl.toggleMenu('showExtensions')")
+ .sk-label Actions
+ actions-menu(item='ctrl.note', ng-if='ctrl.showExtensions')
+ .sk-app-bar-item(click-outside='ctrl.showSessionHistory = false;', is-open='ctrl.showSessionHistory', ng-click="ctrl.toggleMenu('showSessionHistory')")
+ .sk-label Session History
+ session-history-menu(item='ctrl.note', ng-if='ctrl.showSessionHistory')
+ #editor-content.editor-content(ng-if='ctrl.noteReady && !ctrl.note.errorDecrypting')
+ panel-resizer.left(control='ctrl.leftResizeControl', hoverable='true', min-width='300', ng-if='ctrl.marginResizersEnabled', on-resize-finish='ctrl.onPanelResizeFinish', panel-id="'editor-content'", property="'left'")
+ component-view.component-view(component='ctrl.selectedEditor', ng-if='ctrl.selectedEditor', on-load='ctrl.onEditorLoad')
+ textarea#note-text-editor.editable(dir='auto', ng-attr-spellcheck='{{ctrl.spellcheck}}', ng-change='ctrl.contentChanged()', ng-click='ctrl.clickedTextArea()', ng-focus='ctrl.onContentFocus()', ng-if='!ctrl.selectedEditor', ng-model='ctrl.note.text', ng-model-options='{ debounce: ctrl.EditorNgDebounce}', ng-readonly='ctrl.note.locked', ng-trim='false')
+ | {{ctrl.onSystemEditorLoad()}}
+ panel-resizer(control='ctrl.rightResizeControl', hoverable='true', min-width='300', ng-if='ctrl.marginResizersEnabled', on-resize-finish='ctrl.onPanelResizeFinish', panel-id="'editor-content'", property="'right'")
+ .section(ng-show='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.
+ #editor-pane-component-stack(ng-show='ctrl.note')
+ #component-stack-menu-bar.sk-app-bar.no-edges(ng-if='ctrl.componentStack.length')
+ .left
+ .sk-app-bar-item(ng-click='ctrl.toggleStackComponentForCurrentItem(component)', ng-repeat='component in ctrl.componentStack')
+ .sk-app-bar-item-column
+ .sk-circle.small(ng-class="{'info' : !component.hidden && component.active, 'neutral' : component.hidden || !component.active}")
+ .sk-app-bar-item-column
+ .sk-label {{component.name}}
+ .sn-component
+ component-view.component-view.component-stack-item(component='component', manual-dealloc='true', ng-if='component.active', ng-repeat='component in ctrl.componentStack', ng-show='!component.hidden')
diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml
deleted file mode 100644
index 4ffb6fbca..000000000
--- a/app/assets/templates/footer.html.haml
+++ /dev/null
@@ -1,64 +0,0 @@
-.sn-component
- #footer-bar.sk-app-bar.no-edges.no-bottom-edge
- .left
-
- .sk-app-bar-item{"ng-click" => "ctrl.accountMenuPressed()", "click-outside" => "ctrl.clickOutsideAccountMenu()", "is-open" => "ctrl.showAccountMenu"}
- .sk-app-bar-item-column
- .sk-circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'neutral')"}
- .sk-app-bar-item-column
- .sk-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"}
-
- .sk-app-bar-item
- %a.no-decoration.sk-label.title{"href" => "https://standardnotes.org/help", "target" => "_blank", "rel" => "noopener"}
- Help
-
- .sk-app-bar-item.border
-
- .sk-app-bar-item{"ng-repeat" => "room in ctrl.rooms track by room.uuid"}
- .sk-app-bar-item-column{"ng-click" => "ctrl.selectRoom(room)"}
- .sk-label {{room.name}}
- %component-modal{"ng-if" => "room.showRoom", "component" => "room", "on-dismiss" => "ctrl.onRoomDismiss"}
-
-
- .center
- .sk-app-bar-item{"ng-show" => "ctrl.arbitraryStatusMessage"}
- .sk-app-bar-item-column
- %span.neutral.sk-label {{ctrl.arbitraryStatusMessage}}
-
- .right
- .sk-app-bar-item{"ng-show" => "ctrl.securityUpdateAvailable", "ng-click" => "ctrl.openSecurityUpdate()"}
- %span.success.sk-label Security update available.
-
- .sk-app-bar-item{"ng-show" => "ctrl.newUpdateAvailable == true", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"}
- %span.info.sk-label New update available.
-
- .sk-app-bar-item.no-pointer{"ng-if" => "ctrl.lastSyncDate && !ctrl.isRefreshing"}
- .sk-label.subtle
- Last refreshed {{ctrl.lastSyncDate | appDateTime}}
-
- .sk-app-bar-item{"ng-if" => "(ctrl.outOfSync && !ctrl.isRefreshing) || ctrl.showSyncResolution", "ng-click" => "ctrl.toggleSyncResolutionMenu()"}
- .sk-label.warning{"ng-if" => "ctrl.outOfSync"} Potentially Out of Sync
- %sync-resolution-menu{"ng-if" => "ctrl.showSyncResolution", "ng-click" => "$event.stopPropagation();", "close-function" => "ctrl.toggleSyncResolutionMenu"}
-
- .sk-app-bar-item{"ng-if" => "ctrl.lastSyncDate && ctrl.isRefreshing"}
- .sk-spinner.small
-
- .sk-app-bar-item{"ng-if" => "ctrl.offline"}
- .sk-label Offline
- .sk-app-bar-item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"}
- .sk-label Refresh
-
- .sk-app-bar-item.border{"ng-if" => "ctrl.dockShortcuts.length > 0"}
-
- .sk-app-bar-item.dock-shortcut{"ng-repeat" => "shortcut in ctrl.dockShortcuts"}
- .sk-app-bar-item-column{"ng-click" => "ctrl.selectShortcut(shortcut)", "ng-class" => "{'underline': shortcut.component.active}"}
- .div{"ng-if" => "shortcut.icon.type == 'circle'", "title" => "{{shortcut.name}}"}
- .sk-circle.small{"ng-style" => "{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}"}
- .div{"ng-if" => "shortcut.icon.type == 'svg'", "title" => "{{shortcut.name}}"}
- .svg-item{"ng-attr-id" => "dock-svg-{{shortcut.component.uuid}}", "elem-ready" => "ctrl.initSvgForShortcut(shortcut)"}
-
- .sk-app-bar-item.border{"ng-if" => "ctrl.hasPasscode()"}
- .sk-app-bar-item#lock-item{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()", "title" => "Locks application and wipes unencrypted data from memory."}
- .sk-label
- %i.icon.ion-locked#footer-lock-icon
diff --git a/app/assets/templates/footer.pug b/app/assets/templates/footer.pug
new file mode 100644
index 000000000..023292371
--- /dev/null
+++ b/app/assets/templates/footer.pug
@@ -0,0 +1,49 @@
+.sn-component
+ #footer-bar.sk-app-bar.no-edges.no-bottom-edge
+ .left
+ .sk-app-bar-item(click-outside='ctrl.clickOutsideAccountMenu()', is-open='ctrl.showAccountMenu', ng-click='ctrl.accountMenuPressed()')
+ .sk-app-bar-item-column
+ .sk-circle.small(ng-class="ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'neutral')")
+ .sk-app-bar-item-column
+ .sk-label.title(ng-class='{red: ctrl.error}') Account
+ account-menu(close-function='ctrl.closeAccountMenu', ng-click='$event.stopPropagation()', ng-if='ctrl.showAccountMenu', on-successful-auth='ctrl.onAuthSuccess')
+ .sk-app-bar-item
+ a.no-decoration.sk-label.title(href='https://standardnotes.org/help', rel='noopener', target='_blank')
+ | Help
+ .sk-app-bar-item.border
+ .sk-app-bar-item(ng-repeat='room in ctrl.rooms track by room.uuid')
+ .sk-app-bar-item-column(ng-click='ctrl.selectRoom(room)')
+ .sk-label {{room.name}}
+ component-modal(component='room', ng-if='room.showRoom', on-dismiss='ctrl.onRoomDismiss')
+ .center
+ .sk-app-bar-item(ng-show='ctrl.arbitraryStatusMessage')
+ .sk-app-bar-item-column
+ span.neutral.sk-label {{ctrl.arbitraryStatusMessage}}
+ .right
+ .sk-app-bar-item(ng-click='ctrl.openSecurityUpdate()', ng-show='ctrl.securityUpdateAvailable')
+ span.success.sk-label Security update available.
+ .sk-app-bar-item(ng-click='ctrl.clickedNewUpdateAnnouncement()', ng-show='ctrl.newUpdateAvailable == true')
+ span.info.sk-label New update available.
+ .sk-app-bar-item.no-pointer(ng-if='ctrl.lastSyncDate && !ctrl.isRefreshing')
+ .sk-label.subtle
+ | Last refreshed {{ctrl.lastSyncDate | appDateTime}}
+ .sk-app-bar-item(ng-click='ctrl.toggleSyncResolutionMenu()', ng-if='(ctrl.outOfSync && !ctrl.isRefreshing) || ctrl.showSyncResolution')
+ .sk-label.warning(ng-if='ctrl.outOfSync') Potentially Out of Sync
+ sync-resolution-menu(close-function='ctrl.toggleSyncResolutionMenu', ng-click='$event.stopPropagation();', ng-if='ctrl.showSyncResolution')
+ .sk-app-bar-item(ng-if='ctrl.lastSyncDate && ctrl.isRefreshing')
+ .sk-spinner.small
+ .sk-app-bar-item(ng-if='ctrl.offline')
+ .sk-label Offline
+ .sk-app-bar-item(ng-click='ctrl.refreshData()', ng-if='!ctrl.offline')
+ .sk-label Refresh
+ .sk-app-bar-item.border(ng-if='ctrl.dockShortcuts.length > 0')
+ .sk-app-bar-item.dock-shortcut(ng-repeat='shortcut in ctrl.dockShortcuts')
+ .sk-app-bar-item-column(ng-class="{'underline': shortcut.component.active}", ng-click='ctrl.selectShortcut(shortcut)')
+ .div(ng-if="shortcut.icon.type == 'circle'", title='{{shortcut.name}}')
+ .sk-circle.small(ng-style="{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}")
+ .div(ng-if="shortcut.icon.type == 'svg'", title='{{shortcut.name}}')
+ .svg-item(elem-ready='ctrl.initSvgForShortcut(shortcut)', ng-attr-id='dock-svg-{{shortcut.component.uuid}}')
+ .sk-app-bar-item.border(ng-if='ctrl.hasPasscode()')
+ #lock-item.sk-app-bar-item(ng-click='ctrl.lockApp()', ng-if='ctrl.hasPasscode()', title='Locks application and wipes unencrypted data from memory.')
+ .sk-label
+ i#footer-lock-icon.icon.ion-locked
diff --git a/app/assets/templates/home.html.haml b/app/assets/templates/home.html.haml
deleted file mode 100644
index e0ba05513..000000000
--- a/app/assets/templates/home.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-.main-ui-view{"ng-class" => "platform"}
-
- %lock-screen{"ng-if" => "needsUnlock", "on-success" => "onSuccessfulUnlock"}
-
- .app#app{"ng-if" => "!needsUnlock", "ng-class" => "appClass"}
- %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "selection-made" => "tagsSelectionMade", "remove-tag" => "removeTag"}
- %notes-section{"add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", "tag" => "selectedTag"}
- %editor-section{"note" => "selectedNote", "remove" => "deleteNote", "update-tags" => "updateTagsForNote"}
-
- %footer{"ng-if" => "!needsUnlock"}
diff --git a/app/assets/templates/home.pug b/app/assets/templates/home.pug
new file mode 100644
index 000000000..6a1eb149e
--- /dev/null
+++ b/app/assets/templates/home.pug
@@ -0,0 +1,7 @@
+.main-ui-view(ng-class='platform')
+ lock-screen(ng-if='needsUnlock', on-success='onSuccessfulUnlock')
+ #app.app(ng-class='appClass', ng-if='!needsUnlock')
+ tags-panel(add-new='tagsAddNew', remove-tag='removeTag', save='tagsSave', selection-made='tagsSelectionMade')
+ notes-panel(add-new='notesAddNew', selection-made='notesSelectionMade', tag='selectedTag')
+ editor-panel(note='selectedNote', remove='deleteNote', update-tags='updateTagsForNote')
+ footer(ng-if='!needsUnlock')
diff --git a/app/assets/templates/lock-screen.html.haml b/app/assets/templates/lock-screen.html.haml
deleted file mode 100644
index e455959a1..000000000
--- a/app/assets/templates/lock-screen.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-#lock-screen.sn-component
- .sk-panel
- .sk-panel-header
- .sk-panel-header-title Passcode Required
- .sk-panel-content
- .sk-panel-section
- %form.sk-panel-form.sk-panel-row{"ng-submit" => "submitPasscodeForm()"}
- .sk-panel-column.stretch
- %input#passcode-input.center-text.sk-input.contrast{:type => 'password',
- "ng-model" => "formData.passcode", "autofocus" => "true", "sn-autofocus" => "true", "should-focus" => "true",
- "placeholder" => "Enter Passcode", "autocomplete" => "new-password"}
- .sk-button-group.stretch.sk-panel-row.form-submit
- %button.sk-button.info{"type" => "submit"}
- .sk-label Unlock
-
- .sk-panel-footer
- #passcode-reset
- %a.sk-a.neutral{"ng-if" => "!formData.showRecovery", "ng-click" => "forgotPasscode()"} Forgot?
-
- %div{"ng-if" => "formData.showRecovery"}
- .sk-p
- If you forgot your local passcode, your only option is to clear your local data from this device
- and sign back in to your account.
- .sk-panel-row
- %a.sk-a.danger.center-text{"ng-click" => "beginDeleteData()"} Delete Local Data
diff --git a/app/assets/templates/lock-screen.pug b/app/assets/templates/lock-screen.pug
new file mode 100644
index 000000000..46cd227ff
--- /dev/null
+++ b/app/assets/templates/lock-screen.pug
@@ -0,0 +1,21 @@
+#lock-screen.sn-component
+ .sk-panel
+ .sk-panel-header
+ .sk-panel-header-title Passcode Required
+ .sk-panel-content
+ .sk-panel-section
+ form.sk-panel-form.sk-panel-row(ng-submit='submitPasscodeForm()')
+ .sk-panel-column.stretch
+ input#passcode-input.center-text.sk-input.contrast(autocomplete='new-password', autofocus='true', ng-model='formData.passcode', placeholder='Enter Passcode', should-focus='true', sn-autofocus='true', type='password')
+ .sk-button-group.stretch.sk-panel-row.form-submit
+ button.sk-button.info(type='submit')
+ .sk-label Unlock
+ .sk-panel-footer
+ #passcode-reset
+ a.sk-a.neutral(ng-click='forgotPasscode()', ng-if='!formData.showRecovery') Forgot?
+ div(ng-if='formData.showRecovery')
+ .sk-p
+ | If you forgot your local passcode, your only option is to clear your local data from this device
+ | and sign back in to your account.
+ .sk-panel-row
+ a.sk-a.danger.center-text(ng-click='beginDeleteData()') Delete Local Data
diff --git a/app/assets/templates/notes.html.haml b/app/assets/templates/notes.html.haml
deleted file mode 100644
index 07886c1a0..000000000
--- a/app/assets/templates/notes.html.haml
+++ /dev/null
@@ -1,71 +0,0 @@
-.sn-component.section.notes#notes-column{"aria-label" => "Notes"}
- .content
- .section-title-bar#notes-title-bar
- .padded
- .section-title-bar-header
- .title {{ctrl.panelTitle}}
- .sk-button.contrast.wide{"ng-click" => "ctrl.createNewNote()", "title" => "Create a new note in the selected tag"}
- .sk-label
- %i.icon.ion-plus.add-button
- .filter-section{"role" => "search"}
- %input.filter-bar#search-bar{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Search",
- "ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true", "ng-blur" => "ctrl.onFilterEnter()", "ng-keyup" => "$event.keyCode == 13 && ctrl.onFilterEnter();",
- "title" => "Searches notes in the currently selected tag"}
- #search-clear-button{"ng-show" => "ctrl.noteFilter.text", "ng-click" => "ctrl.clearFilterText();"} ✕
- .sn-component#notes-menu-bar
- .sk-app-bar.no-edges
- .left
- .sk-app-bar-item{"ng-click" => "ctrl.showMenu = !ctrl.showMenu", "ng-class" => "{'selected' : ctrl.showMenu}"}
- .sk-app-bar-item-column
- .sk-label
- Options
- .sk-app-bar-item-column
- .sk-sublabel {{ctrl.optionsSubtitle()}}
-
- .sk-menu-panel.dropdown-menu#notes-options-menu{"ng-show" => "ctrl.showMenu"}
- .sk-menu-panel-header
- .sk-menu-panel-header-title Sort By
- %a.info.sk-h5{"ng-click" => "ctrl.toggleReverseSort()"}
- {{ctrl.sortReverse === true ? 'Disable Reverse Sort' : 'Enable Reverse Sort'}}
-
- %menu-row{"label" => "'Date Added'", "circle" => "ctrl.sortBy == 'created_at' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()", "desc" => "'Sort notes by newest first'"}
- %menu-row{"label" => "'Date Modified'", "circle" => "ctrl.sortBy == 'client_updated_at' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByUpdated()", "desc" => "'Sort notes with the most recently updated first'"}
- %menu-row{"label" => "'Title'", "circle" => "ctrl.sortBy == 'title' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByTitle()", "desc" => "'Sort notes alphabetically by their title'"}
-
- .sk-menu-panel-section
- .sk-menu-panel-header
- .sk-menu-panel-header-title Display
-
- %menu-row{"label" => "'Archived Notes'", "circle" => "ctrl.showArchived ? 'success' : 'danger'", "faded" => "!ctrl.showArchived", "action" => "ctrl.selectedMenuItem(); ctrl.togglePrefKey('showArchived')", "desc" => "'Archived notes are usually hidden. You can explicitly show them with this option.'"}
- %menu-row{"label" => "'Pinned Notes'", "circle" => "ctrl.hidePinned ? 'danger' : 'success'", "faded" => "ctrl.hidePinned", "action" => "ctrl.selectedMenuItem(); ctrl.togglePrefKey('hidePinned')", "desc" => "'Pinned notes always appear on top. You can hide them temporarily with this option so you can focus on other notes in the list.'"}
-
- %menu-row{"label" => "'Note Preview'", "circle" => "ctrl.hideNotePreview ? 'danger' : 'success'", "faded" => "ctrl.hideNotePreview", "action" => "ctrl.selectedMenuItem(); ctrl.togglePrefKey('hideNotePreview')", "desc" => "'Hide the note preview for a more condensed list of notes'"}
- %menu-row{"label" => "'Date'", "circle" => "ctrl.hideDate ? 'danger' : 'success'","faded" => "ctrl.hideDate", "action" => "ctrl.selectedMenuItem(); ctrl.togglePrefKey('hideDate')", "desc" => "'Hide the date displayed in each row'"}
- %menu-row{"label" => "'Tags'", "circle" => "ctrl.hideTags ? 'danger' : 'success'","faded" => "ctrl.hideTags", "action" => "ctrl.selectedMenuItem(); ctrl.togglePrefKey('hideTags')", "desc" => "'Hide the list of tags associated with each note'"}
-
- .scrollable
- .infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
- .note{"ng-repeat" => "note in (ctrl.renderedNotes = (ctrl.notes | limitTo:ctrl.notesToDisplay)) track by note.uuid",
- "ng-click" => "ctrl.selectNote(note, true)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
-
- .note-flags{"ng-show" => "note.flags.length > 0"}
- .flag{"ng-repeat" => "flag in note.flags", "ng-class" => "flag.class"}
- .label {{flag.text}}
-
- .name{"ng-show" => "note.title"}
- {{note.title}}
-
- -# Use ng-if and not ng-show here. We don't want previews in the dom at all if protected.
- .note-preview{"ng-if" => "!ctrl.hideNotePreview && !note.content.hidePreview && !note.content.protected"}
- .html-preview{"ng-show" => "note.content.preview_html", "ng-bind-html" => "note.content.preview_html"}
- .plain-preview{"ng-show" => "!note.content.preview_html && note.content.preview_plain"} {{note.content.preview_plain}}
- .default-preview{"ng-show" => "!note.content.preview_html && !note.content.preview_plain"} {{note.text}}
-
- .date.faded{"ng-show" => "!ctrl.hideDate"}
- %span{"ng-show" => "ctrl.sortBy == 'client_updated_at'"} Modified {{note.cachedUpdatedAtString || 'Now'}}
- %span{"ng-show" => "ctrl.sortBy != 'client_updated_at'"} {{note.cachedCreatedAtString || 'Now'}}
-
- .tags-string{"ng-show" => "note.shouldShowTags"}
- .faded {{note.savedTagsString || note.tagsString()}}
-
- %panel-resizer{"panel-id" => "'notes-column'", "default-width" => 300, "on-resize-finish" => "ctrl.onPanelResize", "control" => "ctrl.panelController", "hoverable" => "true", "collapsable" => "true"}
diff --git a/app/assets/templates/notes.pug b/app/assets/templates/notes.pug
new file mode 100644
index 000000000..461008c36
--- /dev/null
+++ b/app/assets/templates/notes.pug
@@ -0,0 +1,55 @@
+#notes-column.sn-component.section.notes(aria-label='Notes')
+ .content
+ #notes-title-bar.section-title-bar
+ .padded
+ .section-title-bar-header
+ .title {{ctrl.panelTitle}}
+ .sk-button.contrast.wide(ng-click='ctrl.createNewNote()', title='Create a new note in the selected tag')
+ .sk-label
+ i.icon.ion-plus.add-button
+ .filter-section(role='search')
+ input#search-bar.filter-bar(lowercase='true', ng-blur='ctrl.onFilterEnter()', ng-change='ctrl.filterTextChanged()', ng-keyup='$event.keyCode == 13 && ctrl.onFilterEnter();', ng-model='ctrl.noteFilter.text', placeholder='Search', select-on-click='true', title='Searches notes in the currently selected tag')
+ #search-clear-button(ng-click='ctrl.clearFilterText();', ng-show='ctrl.noteFilter.text') ✕
+ #notes-menu-bar.sn-component
+ .sk-app-bar.no-edges
+ .left
+ .sk-app-bar-item(ng-class="{'selected' : ctrl.showMenu}", ng-click='ctrl.showMenu = !ctrl.showMenu')
+ .sk-app-bar-item-column
+ .sk-label
+ | Options
+ .sk-app-bar-item-column
+ .sk-sublabel {{ctrl.optionsSubtitle()}}
+ #notes-options-menu.sk-menu-panel.dropdown-menu(ng-show='ctrl.showMenu')
+ .sk-menu-panel-header
+ .sk-menu-panel-header-title Sort By
+ a.info.sk-h5(ng-click='ctrl.toggleReverseSort()')
+ | {{ctrl.sortReverse === true ? 'Disable Reverse Sort' : 'Enable Reverse Sort'}}
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()" circle="ctrl.sortBy == 'created_at' && 'success'" desc="'Sort notes by newest first'" label="'Date Added'")
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.selectedSortByUpdated()" circle="ctrl.sortBy == 'client_updated_at' && 'success'" desc="'Sort notes with the most recently updated first'" label="'Date Modified'")
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.selectedSortByTitle()" circle="ctrl.sortBy == 'title' && 'success'" desc="'Sort notes alphabetically by their title'" label="'Title'")
+ .sk-menu-panel-section
+ .sk-menu-panel-header
+ .sk-menu-panel-header-title Display
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.togglePrefKey('showArchived')" circle="ctrl.showArchived ? 'success' : 'danger'" desc="'Archived notes are usually hidden. You can explicitly show them with this option.'" faded="!ctrl.showArchived" label="'Archived Notes'")
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.togglePrefKey('hidePinned')" circle="ctrl.hidePinned ? 'danger' : 'success'" desc="'Pinned notes always appear on top. You can hide them temporarily with this option so you can focus on other notes in the list.'" faded="ctrl.hidePinned" label="'Pinned Notes'")
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.togglePrefKey('hideNotePreview')" circle="ctrl.hideNotePreview ? 'danger' : 'success'" desc="'Hide the note preview for a more condensed list of notes'" faded="ctrl.hideNotePreview" label="'Note Preview'")
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.togglePrefKey('hideDate')" circle="ctrl.hideDate ? 'danger' : 'success'" desc="'Hide the date displayed in each row'" faded="ctrl.hideDate" label="'Date'")
+ menu-row(action="ctrl.selectedMenuItem(); ctrl.togglePrefKey('hideTags')" circle="ctrl.hideTags ? 'danger' : 'success'" desc="'Hide the list of tags associated with each note'" faded="ctrl.hideTags" label="'Tags'")
+ .scrollable
+ #notes-scrollable.infinite-scroll(can-load='true', infinite-scroll='ctrl.paginate()', threshold='200')
+ .note(ng-class="{'selected' : ctrl.selectedNote == note}", ng-click='ctrl.selectNote(note, true)', ng-repeat='note in (ctrl.renderedNotes = (ctrl.notes | limitTo:ctrl.notesToDisplay)) track by note.uuid')
+ .note-flags(ng-show='note.flags.length > 0')
+ .flag(ng-class='flag.class', ng-repeat='flag in note.flags')
+ .label {{flag.text}}
+ .name(ng-show='note.title')
+ | {{note.title}}
+ .note-preview(ng-if='!ctrl.hideNotePreview && !note.content.hidePreview && !note.content.protected')
+ .html-preview(ng-bind-html='note.content.preview_html', ng-show='note.content.preview_html')
+ .plain-preview(ng-show='!note.content.preview_html && note.content.preview_plain') {{note.content.preview_plain}}
+ .default-preview(ng-show='!note.content.preview_html && !note.content.preview_plain') {{note.text}}
+ .date.faded(ng-show='!ctrl.hideDate')
+ span(ng-show="ctrl.sortBy == 'client_updated_at'") Modified {{note.cachedUpdatedAtString || 'Now'}}
+ span(ng-show="ctrl.sortBy != 'client_updated_at'") {{note.cachedCreatedAtString || 'Now'}}
+ .tags-string(ng-show='note.shouldShowTags')
+ .faded {{note.savedTagsString || note.tagsString()}}
+ panel-resizer(collapsable="true" control="ctrl.panelController" default-width="300" hoverable="true" on-resize-finish="ctrl.onPanelResize" panel-id="'notes-column'")
diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml
deleted file mode 100644
index e020fc0ea..000000000
--- a/app/assets/templates/tags.html.haml
+++ /dev/null
@@ -1,46 +0,0 @@
-.sn-component.section.tags#tags-column{"aria-label" => "Tags"}
-
- .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-section.section-title-bar
- .section-title-bar-header
- .sk-h3.title
- %span.sk-bold Views
- .sk-button.sk-secondary-contrast.wide{"ng-click" => "ctrl.clickedAddNewTag()", "title" => "Create a new tag"}
- .sk-label
- %i.icon.ion-plus.add-button
-
- .scrollable
- .infinite-scroll
- .tag{"ng-repeat" => "tag in ctrl.smartTags", "ng-click" => "ctrl.selectTag(tag)",
- "ng-class" => "{'selected' : ctrl.selectedTag == tag, 'faded' : !tag.content.isAllTag}"}
- .tag-info
- %input.title{"ng-disabled" => "true", "ng-model" => "tag.title"}
- .count{"ng-show" => "tag.content.isAllTag"} {{tag.cachedNoteCount}}
-
- .tags-title-section.section-title-bar
- .section-title-bar-header
- .sk-h3.title
- %span.sk-bold Tags
- .tag{"ng-repeat" => "tag in ctrl.tags track by tag.uuid", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}"}
- .tag-info
- .tag-icon #
- %input.title{"ng-class" => "{'editing' : ctrl.editingTag == tag}", "ng-attr-id" => "tag-{{tag.uuid}}", "ng-click" => "ctrl.selectTag(tag)", "ng-model" => "tag.title",
- "ng-keyup" => "$event.keyCode == 13 && $event.target.blur()", "sn-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag",
- "ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"}
- .count {{tag.cachedNoteCount}}
-
- .danger.small-text.bold{"ng-show" => "tag.content.conflict_of"} Conflicted Copy
- .danger.small-text.bold{"ng-show" => "tag.errorDecrypting"} Missing Keys
-
- .menu{"ng-show" => "ctrl.selectedTag == tag"}
- %a.item{"ng-click" => "ctrl.selectedRenameTag($event, tag)", "ng-show" => "!ctrl.editingTag"} Rename
- %a.item{"ng-click" => "ctrl.saveTag($event, tag)", "ng-show" => "ctrl.editingTag"} Save
- %a.item{"ng-click" => "ctrl.selectedDeleteTag(tag)"} Delete
-
- .no-tags-placeholder{"ng-show" => "ctrl.tags.length == 0"}
- No tags. Create one using the add button above.
-
- %panel-resizer{"panel-id" => "'tags-column'", "default-width" => 150, "on-resize-finish" => "ctrl.onPanelResize", "control" => "ctrl.panelController", "hoverable" => "true", "collapsable" => "true"}
diff --git a/app/assets/templates/tags.pug b/app/assets/templates/tags.pug
new file mode 100644
index 000000000..cabdcced3
--- /dev/null
+++ b/app/assets/templates/tags.pug
@@ -0,0 +1,35 @@
+#tags-column.sn-component.section.tags(aria-label='Tags')
+ .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-section.section-title-bar
+ .section-title-bar-header
+ .sk-h3.title
+ span.sk-bold Views
+ .sk-button.sk-secondary-contrast.wide(ng-click='ctrl.clickedAddNewTag()', title='Create a new tag')
+ .sk-label
+ i.icon.ion-plus.add-button
+ .scrollable
+ .infinite-scroll
+ .tag(ng-class="{'selected' : ctrl.selectedTag == tag, 'faded' : !tag.content.isAllTag}", ng-click='ctrl.selectTag(tag)', ng-repeat='tag in ctrl.smartTags')
+ .tag-info
+ input.title(ng-disabled='true', ng-model='tag.title')
+ .count(ng-show='tag.content.isAllTag') {{tag.cachedNoteCount}}
+ .tags-title-section.section-title-bar
+ .section-title-bar-header
+ .sk-h3.title
+ span.sk-bold Tags
+ .tag(ng-class="{'selected' : ctrl.selectedTag == tag}", ng-click='ctrl.selectTag(tag)', ng-repeat='tag in ctrl.tags track by tag.uuid')
+ .tag-info
+ .tag-icon #
+ input.title(ng-attr-id='tag-{{tag.uuid}}', ng-blur='ctrl.saveTag($event, tag)', ng-change='ctrl.tagTitleDidChange(tag)', ng-class="{'editing' : ctrl.editingTag == tag}", ng-click='ctrl.selectTag(tag)', ng-keyup='$event.keyCode == 13 && $event.target.blur()', ng-model='tag.title', should-focus='ctrl.newTag || ctrl.editingTag == tag', sn-autofocus='true', spellcheck='false')
+ .count {{tag.cachedNoteCount}}
+ .danger.small-text.bold(ng-show='tag.content.conflict_of') Conflicted Copy
+ .danger.small-text.bold(ng-show='tag.errorDecrypting') Missing Keys
+ .menu(ng-show='ctrl.selectedTag == tag')
+ a.item(ng-click='ctrl.selectedRenameTag($event, tag)', ng-show='!ctrl.editingTag') Rename
+ a.item(ng-click='ctrl.saveTag($event, tag)', ng-show='ctrl.editingTag') Save
+ a.item(ng-click='ctrl.selectedDeleteTag(tag)') Delete
+ .no-tags-placeholder(ng-show='ctrl.tags.length == 0')
+ | No tags. Create one using the add button above.
+ panel-resizer(collapsable='true', control='ctrl.panelController', default-width='150', hoverable='true', on-resize-finish='ctrl.onPanelResize', panel-id="'tags-column'")
diff --git a/app/views/application/app.html.erb b/app/views/application/app.html.erb
index 41fedd8a7..bdc56278c 100644
--- a/app/views/application/app.html.erb
+++ b/app/views/application/app.html.erb
@@ -35,16 +35,16 @@
<% if Rails.env.development? %>
- <%= javascript_include_tag "compiled.js", debug: true %>
+ <%= javascript_include_tag "javascripts/app.js", debug: true %>
<% else %>
- <%= javascript_include_tag "compiled.min.js", debug: false %>
+ <%= javascript_include_tag "javascripts/app.js", debug: false %>
<% end %>
- <%= stylesheet_link_tag "app", media: "all", debug: false %>
+ <%= stylesheet_link_tag "stylesheets/app.css", media: "all", debug: false %>
-
+