Grunt -> Webpack, Haml -> Pug

This commit is contained in:
Mo Bitar 2019-12-16 16:26:51 -06:00
parent 68fbd745d5
commit c4c38616b0
No known key found for this signature in database
GPG Key ID: C9EAD0759817F96F
134 changed files with 6780 additions and 76580 deletions

View File

@ -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
View 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
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@ -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'])
};

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

View File

View 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';
import {
autofocus,
clickOutside,
delayHide,
elemReady,
fileChange,
infiniteScroll,
lowercase,
selectOnClick,
snEnter
} from './directives/functional';
import {
AccountMenu,
ActionsMenu,
ComponentModal,
ComponentView,
ConflictResolutionModal,
EditorMenu,
InputModal,
MenuRow,
PanelResizer,
PasswordWizard,
PermissionsModal,
PrivilegesAuthModal,
PrivilegesManagementModal,
RevisionPreviewModal,
SessionHistoryMenu,
SyncResolutionMenu
} from './directives/views';
import { appDate, appDateTime, trusted } from './filters';
import {
ActionsManager,
ArchiveManager,
AuthManager,
ComponentManager,
DBManager,
DesktopManager,
HttpManager,
KeyboardManager,
MigrationManager,
ModelManager,
NativeExtManager,
PasscodeManager,
PrivilegesManager,
SessionHistory,
SingletonManager,
StatusManager,
StorageManager,
SyncManager,
ThemeManager,
AlertManager
} from './services';
angular.module('app', ['ngSanitize']);
// Config
angular
.module('app')
.config(configRoutes)
.constant('appVersion', __VERSION__);
// 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());
// 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);
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, " "));
}
// 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());
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;
}
// Filters
angular
.module('app')
.filter('appDate', appDate)
.filter('appDateTime', appDateTime)
.filter('trusted', ['$sce', trusted]);
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;
}
}
function isDesktopApplication() {
return window.isElectron;
}
/* Use with numbers and strings, not objects */
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) {
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;
}
});
}
// 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);

View File

@ -1,5 +0,0 @@
angular.module('app')
.constant('appVersion', '3.0.22')
;

View File

@ -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) {
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 = [];
@ -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));
}
});
};
}
}

View File

@ -1,31 +1,45 @@
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(){
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(){
});
scope.$on('sync:error', function() {
ctrl.findErrors();
ctrl.updateOfflineStatus();
})
});
}
}
})
.controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager,
syncManager, storageManager, passcodeManager, componentManager, singletonManager, nativeExtManager,
privilegesManager, statusManager, alertManager) {
/* @ngInject */
controller(
$rootScope,
authManager,
modelManager,
$timeout,
dbManager,
syncManager,
storageManager,
passcodeManager,
componentManager,
singletonManager,
nativeExtManager,
privilegesManager,
statusManager,
alertManager
) {
authManager.checkForSecurityUpdate().then((available) => {
this.securityUpdateAvailable = available;
})
@ -310,4 +324,5 @@ angular.module('app')
}
this.showAccountMenu = false;
}
});
}
}

View File

@ -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();
@ -341,4 +364,5 @@ angular.module('app')
if(urlParam("server")) {
autoSignInFromParams();
}
});
}
}

View 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';

View File

@ -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);

View File

@ -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) {
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()};
}
})
});
}
}

View File

@ -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,
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;
}
})
.controller('TagsCtrl', function ($rootScope, modelManager, syncManager, $timeout, componentManager, authManager) {
/* @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]);
}
});
}
}

View File

@ -1,10 +1,9 @@
angular
.module('app')
.directive('snAutofocus', ['$timeout', function($timeout) {
/* @ngInject */
export function autofocus($timeout) {
return {
restrict: 'A',
scope: {
shouldFocus: "="
shouldFocus: '='
},
link: function($scope, $element) {
$timeout(function() {
@ -13,5 +12,5 @@ angular
}
});
}
};
}
}]);

View File

@ -1,10 +1,10 @@
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;
@ -13,16 +13,17 @@ angular.module('app').directive('clickOutside', ['$document', function($document
}
});
$document.bind('click', function(event) {
$document.bind('click', function() {
// Ignore click if on SKAlert
if (event.target.closest(".sk-modal")) {
return;
}
if (!didApplyClickOutside) {
$scope.$apply(attrs.clickOutside);
didApplyClickOutside = true;
}
})
});
}
};
}
}]);

View File

@ -1,6 +1,7 @@
angular
.module('app')
.directive('delayHide', function($timeout) {
import angular from 'angular';
/* @ngInject */
export function delayHide($timeout) {
return {
restrict: 'A',
scope: {
@ -8,8 +9,6 @@ angular
delay: '@'
},
link: function(scope, elem, attrs) {
var showTimer;
showElement(false);
// This is where all the magic happens!
@ -41,6 +40,5 @@ angular
return angular.isNumber(delay) ? delay : 200;
}
}
};
});
}

View File

@ -1,6 +1,5 @@
angular
.module('app')
.directive( 'elemReady', function( $parse ) {
/* @ngInject */
export function elemReady($parse) {
return {
restrict: 'A',
link: function($scope, elem, attrs) {
@ -8,8 +7,8 @@ angular
$scope.$apply(function() {
var func = $parse(attrs.elemReady);
func($scope);
})
})
});
});
}
};
}
})

View File

@ -1,6 +1,5 @@
angular
.module('app')
.directive('fileChange', function() {
/* @ngInject */
export function fileChange() {
return {
restrict: 'A',
scope: {
@ -14,4 +13,4 @@ angular
});
}
};
});
}

View File

@ -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';

View File

@ -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) {
if (
scope.$eval(attrs.canLoad) &&
e.scrollTop + e.offsetHeight >= e.scrollHeight - offset
) {
scope.$apply(attrs.infiniteScroll);
}
});
}
};
}
]);

View File

@ -1,20 +1,19 @@
angular
.module('app')
.directive('lowercase', function() {
/* @ngInject */
export function lowercase() {
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var lowercase = function(inputValue) {
if (inputValue == undefined) inputValue = '';
if (inputValue === undefined) inputValue = '';
var lowercased = inputValue.toLowerCase();
if (lowercased !== inputValue) {
modelCtrl.$setViewValue(lowercased);
modelCtrl.$render();
}
return lowercased;
}
};
modelCtrl.$parsers.push(lowercase);
lowercase(scope[attrs.ngModel]);
}
};
});
}

View File

@ -1,15 +1,14 @@
angular
.module('app')
.directive('selectOnClick', ['$window', function ($window) {
/* @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)
this.setSelectionRange(0, this.value.length);
}
});
}
};
}]);
}

View File

@ -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) {
element.bind('keydown keypress', function(event) {
if (event.which === 13) {
scope.$apply(function() {
scope.$eval(attrs.snEnter, {'event': event});
scope.$eval(attrs.snEnter, { event: event });
});
event.preventDefault();
}
});
};
});
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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));

View File

@ -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);

View File

@ -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);

View 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';

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -1,28 +1,33 @@
// reuse
var locale, formatter;
angular.module('app')
.filter('appDate', function ($filter) {
/* @ngInject */
export function appDate($filter) {
return function(input) {
return input ? $filter('date')(new Date(input), 'MM/dd/yyyy', 'UTC') : '';
};
})
.filter('appDateTime', function ($filter) {
}
/* @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;
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',
minute: '2-digit'
});
}
return formatter.format(input);
} else {
return input ? $filter('date')(new Date(input), 'MM/dd/yyyy h:mm a') : '';
}
};
}
});

View File

@ -0,0 +1,2 @@
export { appDate, appDateTime } from './appDate';
export { trusted } from './trusted';

View File

@ -1,5 +1,6 @@
angular.module('app').filter('trusted', ['$sce', function ($sce) {
/* @ngInject */
export function trusted($sce) {
return function(url) {
return $sce.trustAsResourceUrl(url);
};
}]);
}

View File

@ -1,4 +1,6 @@
class NoteHistoryEntry extends SFItemHistoryEntry {
import { SFItemHistoryEntry } from 'snjs';
export class NoteHistoryEntry extends SFItemHistoryEntry {
previewTitle() {
return this.item.updated_at.toLocaleString();

View File

@ -1,6 +1,7 @@
angular.module('app')
.config(function ($locationProvider) {
import { isDesktopApplication } from './utils';
/* @ngInject */
export function configRoutes($locationProvider) {
if (!isDesktopApplication()) {
if (window.history && window.history.pushState) {
$locationProvider.html5Mode({
@ -11,4 +12,4 @@ angular.module('app')
} else {
$locationProvider.html5Mode(false);
}
});
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View 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';

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -1,7 +1,12 @@
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;
@ -278,5 +283,3 @@ class PasscodeManager {
this.lockAfterDate = null;
}
}
angular.module('app').service('passcodeManager', PasscodeManager);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View 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;
}
});
}

View File

@ -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';

View File

@ -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 ionicons 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; }
@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, .ionicons,
.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;
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-locked:before { content: "\f200"; }
.ion-locked:before {
content: "\f200";
}
.ion-plus:before { content: "\f218"; }
.ion-plus:before {
content: "\f218";
}
/*# sourceMappingURL=ionicons.css.map */

View File

@ -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" }}

View 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" }}

View File

@ -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"}

View 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')

View File

@ -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"}

View 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")

View File

@ -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"}

View 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')

View File

@ -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}}

View File

@ -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}}

View File

@ -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'"}

View 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'")

View File

@ -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

View File

@ -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"}

View 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')

View File

@ -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}}

View 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}}

View File

@ -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

View 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

View File

@ -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

View 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

View File

@ -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.

View File

@ -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.

View File

@ -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"}

View 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")

View File

@ -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