mirror of
https://github.com/standardnotes/web.git
synced 2024-08-17 18:50:27 +03:00
Grunt -> Webpack, Haml -> Pug
This commit is contained in:
parent
68fbd745d5
commit
c4c38616b0
15
.babelrc
15
.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"
|
||||
]
|
||||
}
|
||||
|
14
.eslintrc
Normal file
14
.eslintrc
Normal file
@ -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
|
||||
}
|
||||
}
|
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
183
Gruntfile.js
183
Gruntfile.js
@ -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'])
|
||||
};
|
0
dist/assets/ionicons.svg → app/assets/fonts/ionicons.svg
Executable file → Normal file
0
dist/assets/ionicons.svg → app/assets/fonts/ionicons.svg
Executable file → Normal file
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
0
dist/assets/ionicons.ttf → app/assets/fonts/ionicons.ttf
Executable file → Normal file
0
dist/assets/ionicons.ttf → app/assets/fonts/ionicons.ttf
Executable file → Normal file
0
dist/assets/ionicons.woff → app/assets/fonts/ionicons.woff
Executable file → Normal file
0
dist/assets/ionicons.woff → app/assets/fonts/ionicons.woff
Executable file → Normal file
@ -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);
|
||||
|
@ -1,5 +0,0 @@
|
||||
angular.module('app')
|
||||
|
||||
.constant('appVersion', '3.0.22')
|
||||
|
||||
;
|
@ -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));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
6
app/assets/javascripts/app/controllers/index.js
Normal file
6
app/assets/javascripts/app/controllers/index.js
Normal file
@ -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';
|
@ -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);
|
||||
|
@ -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()};
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}]);
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}]);
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
@ -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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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';
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
@ -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]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}]);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
16
app/assets/javascripts/app/directives/views/index.js
Normal file
16
app/assets/javascripts/app/directives/views/index.js
Normal file
@ -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';
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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') : '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
2
app/assets/javascripts/app/filters/index.js
Normal file
2
app/assets/javascripts/app/filters/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { appDate, appDateTime } from './appDate';
|
||||
export { trusted } from './trusted';
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
class NoteHistoryEntry extends SFItemHistoryEntry {
|
||||
import { SFItemHistoryEntry } from 'snjs';
|
||||
|
||||
export class NoteHistoryEntry extends SFItemHistoryEntry {
|
||||
|
||||
previewTitle() {
|
||||
return this.item.updated_at.toLocaleString();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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( "<input-modal type='type' message='message' title='title' callback='callback'></input-modal>" )(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module('app').service('actionsManager', ActionsManager);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
20
app/assets/javascripts/app/services/index.js
Normal file
20
app/assets/javascripts/app/services/index.js
Normal file
@ -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';
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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( "<conflict-resolution-modal item1='item1' item2='item2' callback='callback' class='sk-modal'></conflict-resolution-modal>" )(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module('app').service('syncManager', SyncManager);
|
||||
|
@ -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);
|
||||
|
104
app/assets/javascripts/app/utils.js
Normal file
104
app/assets/javascripts/app/utils.js
Normal file
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
@ -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';
|
||||
|
@ -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 */
|
||||
|
@ -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" }}
|
180
app/assets/templates/directives/account-menu.pug
Normal file
180
app/assets/templates/directives/account-menu.pug
Normal file
@ -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" }}
|
@ -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"}
|
16
app/assets/templates/directives/actions-menu.pug
Normal file
16
app/assets/templates/directives/actions-menu.pug
Normal file
@ -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')
|
@ -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"}
|
9
app/assets/templates/directives/component-modal.pug
Normal file
9
app/assets/templates/directives/component-modal.pug
Normal file
@ -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")
|
@ -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 <strong>Enable the Hosted option</strong> 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.</strong>
|
||||
.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"}
|
72
app/assets/templates/directives/component-view.pug
Normal file
72
app/assets/templates/directives/component-view.pug
Normal file
@ -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')
|
@ -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}}
|
@ -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}}
|
@ -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'"}
|
12
app/assets/templates/directives/editor-menu.pug
Normal file
12
app/assets/templates/directives/editor-menu.pug
Normal file
@ -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'")
|
@ -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
|
@ -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"}
|
20
app/assets/templates/directives/menu-row.pug
Normal file
20
app/assets/templates/directives/menu-row.pug
Normal file
@ -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')
|
@ -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}}
|
93
app/assets/templates/directives/password-wizard.pug
Normal file
93
app/assets/templates/directives/password-wizard.pug
Normal file
@ -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}}
|
@ -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
|
21
app/assets/templates/directives/permissions-modal.pug
Normal file
21
app/assets/templates/directives/permissions-modal.pug
Normal file
@ -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
|
@ -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
|
25
app/assets/templates/directives/privileges-auth-modal.pug
Normal file
25
app/assets/templates/directives/privileges-auth-modal.pug
Normal file
@ -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
|
@ -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.
|
@ -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.
|
@ -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"}
|
16
app/assets/templates/directives/revision-preview-modal.pug
Normal file
16
app/assets/templates/directives/revision-preview-modal.pug
Normal file
@ -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")
|
@ -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()}}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user