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';
function getParameterByName(name, url) {
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
import {
autofocus,
clickOutside,
delayHide,
elemReady,
fileChange,
infiniteScroll,
lowercase,
selectOnClick,
snEnter
} from './directives/functional';
function parametersFromURL(url) {
url = url.split("?").slice(-1)[0];
var obj = {};
url.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
obj[decodeURIComponent(key)] = decodeURIComponent(value);
});
return obj;
}
import {
AccountMenu,
ActionsMenu,
ComponentModal,
ComponentView,
ConflictResolutionModal,
EditorMenu,
InputModal,
MenuRow,
PanelResizer,
PasswordWizard,
PermissionsModal,
PrivilegesAuthModal,
PrivilegesManagementModal,
RevisionPreviewModal,
SessionHistoryMenu,
SyncResolutionMenu
} from './directives/views';
function getPlatformString() {
try {
var platform = navigator.platform.toLowerCase();
var trimmed = "";
if(platform.indexOf("mac") !== -1) {
trimmed = "mac";
} else if(platform.indexOf("win") !== -1) {
trimmed = "windows";
} if(platform.indexOf("linux") !== -1) {
trimmed = "linux";
}
import { appDate, appDateTime, trusted } from './filters';
return trimmed + (isDesktopApplication() ? "-desktop" : "-web");
} catch (e) {
return null;
}
}
import {
ActionsManager,
ArchiveManager,
AuthManager,
ComponentManager,
DBManager,
DesktopManager,
HttpManager,
KeyboardManager,
MigrationManager,
ModelManager,
NativeExtManager,
PasscodeManager,
PrivilegesManager,
SessionHistory,
SingletonManager,
StatusManager,
StorageManager,
SyncManager,
ThemeManager,
AlertManager
} from './services';
function isDesktopApplication() {
return window.isElectron;
}
angular.module('app', ['ngSanitize']);
/* Use with numbers and strings, not objects */
Array.prototype.containsPrimitiveSubset = function(array) {
return !array.some(val => this.indexOf(val) === -1);
}
// Config
angular
.module('app')
.config(configRoutes)
.constant('appVersion', __VERSION__);
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) {
// Controllers
angular
.module('app')
.directive('home', () => new Home())
.directive('tagsPanel', () => new TagsPanel())
.directive('notesPanel', () => new NotesPanel())
.directive('editorPanel', () => new EditorPanel())
.directive('footer', () => new Footer())
.directive('lockScreen', () => new LockScreen());
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
// Directives - Functional
angular
.module('app')
.directive('snAutofocus', ['$timeout', autofocus])
.directive('clickOutside', ['$document', clickOutside])
.directive('delayHide', delayHide)
.directive('elemReady', elemReady)
.directive('fileChange', fileChange)
.directive('infiniteScroll', [
'$rootScope',
'$window',
'$timeout',
infiniteScroll
])
.directive('lowercase', lowercase)
.directive('selectOnClick', ['$window', selectOnClick])
.directive('snEnter', snEnter);
// 1. Let O be ? ToObject(this value).
var o = Object(this);
// Directives - Views
angular
.module('app')
.directive('accountMenu', () => new AccountMenu())
.directive('actionsMenu', () => new ActionsMenu())
.directive('componentModal', () => new ComponentModal())
.directive(
'componentView',
($rootScope, componentManager, desktopManager, $timeout) =>
new ComponentView($rootScope, componentManager, desktopManager, $timeout)
)
.directive('conflictResolutionModal', () => new ConflictResolutionModal())
.directive('editorMenu', () => new EditorMenu())
.directive('inputModal', () => new InputModal())
.directive('menuRow', () => new MenuRow())
.directive('panelResizer', () => new PanelResizer())
.directive('passwordWizard', () => new PasswordWizard())
.directive('permissionsModal', () => new PermissionsModal())
.directive('privilegesAuthModal', () => new PrivilegesAuthModal())
.directive('privilegesManagementModal', () => new PrivilegesManagementModal())
.directive('revisionPreviewModal', () => new RevisionPreviewModal())
.directive('sessionHistoryMenu', () => new SessionHistoryMenu())
.directive('syncResolutionMenu', () => new SyncResolutionMenu());
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// Filters
angular
.module('app')
.filter('appDate', appDate)
.filter('appDateTime', appDateTime)
.filter('trusted', ['$sce', trusted]);
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0;
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
if (sameValueZero(o[k], searchElement)) {
return true;
}
// c. Increase k by 1.
k++;
}
// 8. Return false
return false;
}
});
}
// Services
angular
.module('app')
.service('actionsManager', ActionsManager)
.service('archiveManager', ArchiveManager)
.service('authManager', AuthManager)
.service('componentManager', ComponentManager)
.service('dbManager', DBManager)
.service('desktopManager', DesktopManager)
.service('httpManager', HttpManager)
.service('keyboardManager', KeyboardManager)
.service('migrationManager', MigrationManager)
.service('modelManager', ModelManager)
.service('nativeExtManager', NativeExtManager)
.service('passcodeManager', PasscodeManager)
.service('privilegesManager', PrivilegesManager)
.service('sessionHistory', SessionHistory)
.service('singletonManager', SingletonManager)
.service('statusManager', StatusManager)
.service('storageManager', StorageManager)
.service('syncManager', SyncManager)
.service('alertManager', AlertManager)
.service('themeManager', ThemeManager);

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) {
scope.$watch('ctrl.note', (note, oldNote) => {
if(note) {
ctrl.noteDidChange(note, oldNote);
}
});
import angular from 'angular';
import { SFModelManager } from 'snjs';
import { isDesktopApplication } from '@/utils';
import { KeyboardManager } from '@/services/keyboardManager';
import { PrivilegesManager } from '@/services/privilegesManager';
import template from '%/editor.pug';
export class EditorPanel {
constructor() {
this.restrict = 'E';
this.scope = {
remove: '&',
note: '=',
updateTags: '&'
};
this.template = template;
this.replace = true;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
link(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.note', (note, oldNote) => {
if (note) {
ctrl.noteDidChange(note, oldNote);
}
}
})
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager,
syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory,
privilegesManager, keyboardManager, desktopManager, alertManager) {
});
}
/* @ngInject */
controller(
$timeout,
authManager,
$rootScope,
actionsManager,
syncManager,
modelManager,
themeManager,
componentManager,
storageManager,
sessionHistory,
privilegesManager,
keyboardManager,
desktopManager,
alertManager
) {
this.spellcheck = true;
this.componentManager = componentManager;
this.componentStack = [];
@ -412,7 +432,7 @@ angular.module('app')
let title = this.note.safeTitle().length ? `'${this.note.title}'` : "this note";
let text = permanently ? `Are you sure you want to permanently delete ${title}?`
: `Are you sure you want to move ${title} to the trash?`
: `Are you sure you want to move ${title} to the trash?`
alertManager.confirm({text, destructive: true, onConfirm: () => {
if(permanently) {
@ -851,10 +871,10 @@ angular.module('app')
this.loadedTabListener = true;
/**
* Insert 4 spaces when a tab key is pressed,
* only used when inside of the text editor.
* If the shift key is pressed first, this event is
* not fired.
* Insert 4 spaces when a tab key is pressed,
* only used when inside of the text editor.
* If the shift key is pressed first, this event is
* not fired.
*/
const editor = document.getElementById("note-text-editor");
@ -880,9 +900,9 @@ angular.module('app')
var end = editor.selectionEnd;
var spaces = " ";
// Insert 4 spaces
// Insert 4 spaces
editor.value = editor.value.substring(0, start)
+ spaces + editor.value.substring(end);
+ spaces + editor.value.substring(end);
// Place cursor 4 spaces away from where
// the tab key was pressed
@ -897,11 +917,12 @@ angular.module('app')
})
// This handles when the editor itself is destroyed, and not when our controller is destroyed.
angular.element(editor).on('$destroy', function(){
angular.element(editor).on('$destroy', () => {
if(this.tabObserver) {
keyboardManager.removeKeyObserver(this.tabObserver);
this.loadedTabListener = false;
}
}.bind(this));
}
});
});
};
}
}

View File

@ -1,313 +1,328 @@
angular.module('app')
.directive("footer", function(authManager){
return {
restrict: 'E',
scope: {},
templateUrl: 'footer.html',
replace: true,
controller: 'FooterCtrl',
controllerAs: 'ctrl',
bindToController: true,
import { PrivilegesManager } from '@/services/privilegesManager';
import template from '%/footer.pug';
link:function(scope, elem, attrs, ctrl) {
scope.$on("sync:completed", function(){
ctrl.syncUpdated();
ctrl.findErrors();
ctrl.updateOfflineStatus();
})
scope.$on("sync:error", function(){
ctrl.findErrors();
ctrl.updateOfflineStatus();
})
}
export class Footer {
constructor() {
this.restrict = 'E';
this.scope = {};
this.template = template;
this.replace = true;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
link(scope, elem, attrs, ctrl) {
scope.$on('sync:completed', function() {
ctrl.syncUpdated();
ctrl.findErrors();
ctrl.updateOfflineStatus();
});
scope.$on('sync:error', function() {
ctrl.findErrors();
ctrl.updateOfflineStatus();
});
}
/* @ngInject */
controller(
$rootScope,
authManager,
modelManager,
$timeout,
dbManager,
syncManager,
storageManager,
passcodeManager,
componentManager,
singletonManager,
nativeExtManager,
privilegesManager,
statusManager,
alertManager
) {
authManager.checkForSecurityUpdate().then((available) => {
this.securityUpdateAvailable = available;
})
$rootScope.$on("security-update-status-changed", () => {
this.securityUpdateAvailable = authManager.securityUpdateAvailable;
})
statusManager.addStatusObserver((string) => {
$timeout(() => {
this.arbitraryStatusMessage = string;
})
})
$rootScope.$on("did-begin-local-backup", () => {
$timeout(() => {
this.backupStatus = statusManager.addStatusFromString("Saving local backup...");
})
});
$rootScope.$on("did-finish-local-backup", (event, data) => {
$timeout(() => {
if(data.success) {
this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Successfully saved backup.");
} else {
this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Unable to save local backup.");
}
$timeout(() => {
this.backupStatus = statusManager.removeStatus(this.backupStatus);
}, 2000)
})
});
this.openSecurityUpdate = function() {
authManager.presentPasswordWizard("upgrade-security");
}
})
.controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager,
syncManager, storageManager, passcodeManager, componentManager, singletonManager, nativeExtManager,
privilegesManager, statusManager, alertManager) {
authManager.checkForSecurityUpdate().then((available) => {
this.securityUpdateAvailable = available;
})
$rootScope.$on("reload-ext-data", () => {
this.reloadExtendedData();
});
$rootScope.$on("security-update-status-changed", () => {
this.securityUpdateAvailable = authManager.securityUpdateAvailable;
})
this.reloadExtendedData = () => {
if(this.reloadInProgress) { return; }
this.reloadInProgress = true;
statusManager.addStatusObserver((string) => {
$timeout(() => {
this.arbitraryStatusMessage = string;
})
})
$rootScope.$on("did-begin-local-backup", () => {
$timeout(() => {
this.backupStatus = statusManager.addStatusFromString("Saving local backup...");
})
});
$rootScope.$on("did-finish-local-backup", (event, data) => {
$timeout(() => {
if(data.success) {
this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Successfully saved backup.");
} else {
this.backupStatus = statusManager.replaceStatusWithString(this.backupStatus, "Unable to save local backup.");
}
$timeout(() => {
this.backupStatus = statusManager.removeStatus(this.backupStatus);
}, 2000)
})
});
this.openSecurityUpdate = function() {
authManager.presentPasswordWizard("upgrade-security");
// A reload occurs when the extensions manager window is opened. We can close it after a delay
let extWindow = this.rooms.find((room) => {return room.package_info.identifier == nativeExtManager.extensionsManagerIdentifier});
if(!extWindow) {
this.queueExtReload = true; // try again when the ext is available
this.reloadInProgress = false;
return;
}
$rootScope.$on("reload-ext-data", () => {
this.reloadExtendedData();
});
this.reloadExtendedData = () => {
if(this.reloadInProgress) { return; }
this.reloadInProgress = true;
// A reload occurs when the extensions manager window is opened. We can close it after a delay
let extWindow = this.rooms.find((room) => {return room.package_info.identifier == nativeExtManager.extensionsManagerIdentifier});
if(!extWindow) {
this.queueExtReload = true; // try again when the ext is available
this.reloadInProgress = false;
return;
}
this.selectRoom(extWindow);
$timeout(() => {
this.selectRoom(extWindow);
this.reloadInProgress = false;
$rootScope.$broadcast("ext-reload-complete");
}, 2000);
}
$timeout(() => {
this.selectRoom(extWindow);
this.reloadInProgress = false;
$rootScope.$broadcast("ext-reload-complete");
}, 2000);
}
this.getUser = function() {
return authManager.user;
}
this.getUser = function() {
return authManager.user;
}
this.updateOfflineStatus = function() {
this.offline = authManager.offline();
}
this.updateOfflineStatus();
this.updateOfflineStatus = function() {
this.offline = authManager.offline();
}
this.updateOfflineStatus();
syncManager.addEventHandler((syncEvent, data) => {
$timeout(() => {
if(syncEvent == "local-data-loaded") {
// If the user has no notes and is offline, show Account menu
if(this.offline && modelManager.noteCount() == 0) {
this.showAccountMenu = true;
}
} else if(syncEvent == "enter-out-of-sync") {
this.outOfSync = true;
} else if(syncEvent == "exit-out-of-sync") {
this.outOfSync = false;
syncManager.addEventHandler((syncEvent, data) => {
$timeout(() => {
if(syncEvent == "local-data-loaded") {
// If the user has no notes and is offline, show Account menu
if(this.offline && modelManager.noteCount() == 0) {
this.showAccountMenu = true;
}
})
});
this.findErrors = function() {
this.error = syncManager.syncStatus.error;
}
this.findErrors();
this.onAuthSuccess = function() {
this.showAccountMenu = false;
}.bind(this)
this.accountMenuPressed = function() {
this.showAccountMenu = !this.showAccountMenu;
this.closeAllRooms();
}
this.toggleSyncResolutionMenu = function() {
this.showSyncResolution = !this.showSyncResolution;
}.bind(this);
this.closeAccountMenu = () => {
this.showAccountMenu = false;
}
this.hasPasscode = function() {
return passcodeManager.hasPasscode();
}
this.lockApp = function() {
$rootScope.lockApplication();
}
this.refreshData = function() {
this.isRefreshing = true;
// Enable integrity checking for this force request
syncManager.sync({force: true, performIntegrityCheck: true}).then((response) => {
$timeout(function(){
this.isRefreshing = false;
}.bind(this), 200)
if(response && response.error) {
alertManager.alert({text: "There was an error syncing. Please try again. If all else fails, try signing out and signing back in."});
} else {
this.syncUpdated();
}
});
}
this.syncUpdated = function() {
this.lastSyncDate = new Date();
}
$rootScope.$on("new-update-available", () => {
$timeout(() => {
this.onNewUpdateAvailable();
})
} else if(syncEvent == "enter-out-of-sync") {
this.outOfSync = true;
} else if(syncEvent == "exit-out-of-sync") {
this.outOfSync = false;
}
})
});
this.onNewUpdateAvailable = function() {
this.newUpdateAvailable = true;
}
this.findErrors = function() {
this.error = syncManager.syncStatus.error;
}
this.findErrors();
this.clickedNewUpdateAnnouncement = function() {
this.newUpdateAvailable = false;
alertManager.alert({text: "A new update is ready to install. Please use the top-level 'Updates' menu to manage installation."})
}
this.onAuthSuccess = function() {
this.showAccountMenu = false;
}.bind(this)
this.accountMenuPressed = function() {
this.showAccountMenu = !this.showAccountMenu;
this.closeAllRooms();
}
/* Rooms */
this.toggleSyncResolutionMenu = function() {
this.showSyncResolution = !this.showSyncResolution;
}.bind(this);
this.componentManager = componentManager;
this.rooms = [];
this.themesWithIcons = [];
this.closeAccountMenu = () => {
this.showAccountMenu = false;
}
modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => {
this.rooms = modelManager.components.filter((candidate) => {return candidate.area == "rooms" && !candidate.deleted});
if(this.queueExtReload) {
this.queueExtReload = false;
this.reloadExtendedData();
this.hasPasscode = function() {
return passcodeManager.hasPasscode();
}
this.lockApp = function() {
$rootScope.lockApplication();
}
this.refreshData = function() {
this.isRefreshing = true;
// Enable integrity checking for this force request
syncManager.sync({force: true, performIntegrityCheck: true}).then((response) => {
$timeout(function(){
this.isRefreshing = false;
}.bind(this), 200)
if(response && response.error) {
alertManager.alert({text: "There was an error syncing. Please try again. If all else fails, try signing out and signing back in."});
} else {
this.syncUpdated();
}
});
}
modelManager.addItemSyncObserver("footer-bar-themes", "SN|Theme", (allItems, validItems, deletedItems, source) => {
let themes = modelManager.validItemsForContentType("SN|Theme").filter((candidate) => {
return !candidate.deleted && candidate.content.package_info && candidate.content.package_info.dock_icon;
}).sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
this.syncUpdated = function() {
this.lastSyncDate = new Date();
}
let differ = themes.length != this.themesWithIcons.length;
$rootScope.$on("new-update-available", () => {
$timeout(() => {
this.onNewUpdateAvailable();
})
})
this.themesWithIcons = themes;
this.onNewUpdateAvailable = function() {
this.newUpdateAvailable = true;
}
if(differ) {
this.reloadDockShortcuts();
}
this.clickedNewUpdateAnnouncement = function() {
this.newUpdateAvailable = false;
alertManager.alert({text: "A new update is ready to install. Please use the top-level 'Updates' menu to manage installation."})
}
/* Rooms */
this.componentManager = componentManager;
this.rooms = [];
this.themesWithIcons = [];
modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => {
this.rooms = modelManager.components.filter((candidate) => {return candidate.area == "rooms" && !candidate.deleted});
if(this.queueExtReload) {
this.queueExtReload = false;
this.reloadExtendedData();
}
});
modelManager.addItemSyncObserver("footer-bar-themes", "SN|Theme", (allItems, validItems, deletedItems, source) => {
let themes = modelManager.validItemsForContentType("SN|Theme").filter((candidate) => {
return !candidate.deleted && candidate.content.package_info && candidate.content.package_info.dock_icon;
}).sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
this.reloadDockShortcuts = function() {
let shortcuts = [];
for(var theme of this.themesWithIcons) {
var name = theme.content.package_info.name;
var icon = theme.content.package_info.dock_icon;
if(!icon) {
continue;
}
shortcuts.push({
name: name,
component: theme,
icon: icon
})
let differ = themes.length != this.themesWithIcons.length;
this.themesWithIcons = themes;
if(differ) {
this.reloadDockShortcuts();
}
});
this.reloadDockShortcuts = function() {
let shortcuts = [];
for(var theme of this.themesWithIcons) {
var name = theme.content.package_info.name;
var icon = theme.content.package_info.dock_icon;
if(!icon) {
continue;
}
this.dockShortcuts = shortcuts.sort((a, b) => {
// circles first, then images
var aType = a.icon.type;
var bType = b.icon.type;
if(aType == bType) {
return 0;
} else if(aType == "circle" && bType == "svg") {
return -1;
} else if(bType == "circle" && aType == "svg") {
return 1;
}
});
shortcuts.push({
name: name,
component: theme,
icon: icon
})
}
this.initSvgForShortcut = function(shortcut) {
var id = "dock-svg-" + shortcut.component.uuid;
var element = document.getElementById(id);
var parser = new DOMParser();
var svg = shortcut.component.content.package_info.dock_icon.source;
var doc = parser.parseFromString(svg, "image/svg+xml");
element.appendChild(doc.documentElement);
}
this.dockShortcuts = shortcuts.sort((a, b) => {
// circles first, then images
this.selectShortcut = function(shortcut) {
componentManager.toggleComponent(shortcut.component);
}
var aType = a.icon.type;
var bType = b.icon.type;
componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => {
// RIP: There used to be code here that checked if component.active was true, and if so, displayed the component.
// However, we no longer want to persist active state for footer extensions. If you open Extensions on one computer,
// it shouldn't open on another computer. Active state should only be persisted for persistent extensions, like Folders.
}, actionHandler: (component, action, data) => {
if(action == "set-size") {
component.setLastSize(data);
if(aType == bType) {
return 0;
} else if(aType == "circle" && bType == "svg") {
return -1;
} else if(bType == "circle" && aType == "svg") {
return 1;
}
}, focusHandler: (component, focused) => {
if(component.isEditor() && focused) {
this.closeAllRooms();
this.closeAccountMenu();
}
}});
});
}
$rootScope.$on("editorFocused", () => {
this.initSvgForShortcut = function(shortcut) {
var id = "dock-svg-" + shortcut.component.uuid;
var element = document.getElementById(id);
var parser = new DOMParser();
var svg = shortcut.component.content.package_info.dock_icon.source;
var doc = parser.parseFromString(svg, "image/svg+xml");
element.appendChild(doc.documentElement);
}
this.selectShortcut = function(shortcut) {
componentManager.toggleComponent(shortcut.component);
}
componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => {
// RIP: There used to be code here that checked if component.active was true, and if so, displayed the component.
// However, we no longer want to persist active state for footer extensions. If you open Extensions on one computer,
// it shouldn't open on another computer. Active state should only be persisted for persistent extensions, like Folders.
}, actionHandler: (component, action, data) => {
if(action == "set-size") {
component.setLastSize(data);
}
}, focusHandler: (component, focused) => {
if(component.isEditor() && focused) {
this.closeAllRooms();
this.closeAccountMenu();
})
}
}});
this.onRoomDismiss = function(room) {
$rootScope.$on("editorFocused", () => {
this.closeAllRooms();
this.closeAccountMenu();
})
this.onRoomDismiss = function(room) {
room.showRoom = false;
}
this.closeAllRooms = function() {
for(var room of this.rooms) {
room.showRoom = false;
}
}
this.closeAllRooms = function() {
for(var room of this.rooms) {
room.showRoom = false;
}
this.selectRoom = async function(room) {
let run = () => {
$timeout(() => {
room.showRoom = !room.showRoom;
})
}
this.selectRoom = async function(room) {
let run = () => {
$timeout(() => {
room.showRoom = !room.showRoom;
})
}
if(!room.showRoom) {
// About to show, check if has privileges
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageExtensions)) {
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageExtensions, () => {
run();
});
} else {
if(!room.showRoom) {
// About to show, check if has privileges
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageExtensions)) {
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageExtensions, () => {
run();
}
});
} else {
run();
}
} else {
run();
}
}
this.clickOutsideAccountMenu = function() {
if(privilegesManager.authenticationInProgress()) {
return;
}
this.showAccountMenu = false;
this.clickOutsideAccountMenu = function() {
if(privilegesManager.authenticationInProgress()) {
return;
}
});
this.showAccountMenu = false;
}
}
}

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();
@ -253,9 +276,9 @@ angular.module('app')
$rootScope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest')
this.$eval(fn);
this.$eval(fn);
else
this.$apply(fn);
this.$apply(fn);
};
$rootScope.notifyDelete = function() {
@ -293,9 +316,9 @@ angular.module('app')
}
/*
Disable dragging and dropping of files into main SN interface.
both 'dragover' and 'drop' are required to prevent dropping of files.
This will not prevent extensions from receiving drop events.
Disable dragging and dropping of files into main SN interface.
both 'dragover' and 'drop' are required to prevent dropping of files.
This will not prevent extensions from receiving drop events.
*/
window.addEventListener('dragover', (event) => {
event.preventDefault();
@ -308,7 +331,7 @@ angular.module('app')
/*
Handle Auto Sign In From URL
Handle Auto Sign In From URL
*/
function urlParam(key) {
@ -341,4 +364,5 @@ angular.module('app')
if(urlParam("server")) {
autoSignInFromParams();
}
});
}
}

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) {
scope.$watch('ctrl.tag', (tag, oldTag) => {
if(tag) {
ctrl.tagDidChange(tag, oldTag);
}
});
this.template = template;
this.replace = true;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
link(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.tag', (tag, oldTag) => {
if (tag) {
ctrl.tagDidChange(tag, oldTag);
}
}
})
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager,
syncManager, storageManager, desktopManager, privilegesManager, keyboardManager) {
});
}
/* @ngInject */
controller(
authManager,
$timeout,
$rootScope,
modelManager,
syncManager,
storageManager,
desktopManager,
privilegesManager,
keyboardManager
) {
this.panelController = {};
this.searchSubmitted = false;
@ -671,5 +686,5 @@ angular.module('app')
if(searchBar) {searchBar.focus()};
}
})
});
}
}

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,
}
})
.controller('TagsCtrl', function ($rootScope, modelManager, syncManager, $timeout, componentManager, authManager) {
import { SNNote, SNSmartTag } from 'snjs';
import template from '%/tags.pug';
export class TagsPanel {
constructor() {
this.restrict = 'E';
this.scope = {
addNew: '&',
selectionMade: '&',
save: '&',
removeTag: '&'
};
this.template = template;
this.replace = true;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
/* @ngInject */
controller(
$rootScope,
modelManager,
syncManager,
$timeout,
componentManager,
authManager
) {
// Wrap in timeout so that selectTag is defined
$timeout(() => {
this.smartTags = modelManager.getSmartTags();
@ -166,4 +175,5 @@ angular.module('app')
this.removeTag()(tag);
this.selectTag(this.smartTags[0]);
}
});
}
}

View File

@ -1,17 +1,16 @@
angular
.module('app')
.directive('snAutofocus', ['$timeout', function($timeout) {
return {
restrict: 'A',
scope: {
shouldFocus: "="
},
link : function($scope, $element) {
$timeout(function() {
if($scope.shouldFocus) {
$element[0].focus();
}
});
}
/* @ngInject */
export function autofocus($timeout) {
return {
restrict: 'A',
scope: {
shouldFocus: '='
},
link: function($scope, $element) {
$timeout(function() {
if ($scope.shouldFocus) {
$element[0].focus();
}
});
}
}]);
};
}

View File

@ -1,28 +1,29 @@
angular.module('app').directive('clickOutside', ['$document', function($document) {
/* @ngInject */
export function clickOutside($document) {
return {
restrict: 'A',
replace: false,
link: function($scope, $element, attrs) {
let didApplyClickOutside = false;
var didApplyClickOutside = false;
$element.bind('click', function(e) {
didApplyClickOutside = false;
if(attrs.isOpen) {
if (attrs.isOpen) {
e.stopPropagation();
}
});
$document.bind('click', function(event) {
$document.bind('click', function() {
// Ignore click if on SKAlert
if(event.target.closest(".sk-modal")) {
if (event.target.closest(".sk-modal")) {
return;
}
if(!didApplyClickOutside) {
if (!didApplyClickOutside) {
$scope.$apply(attrs.clickOutside);
didApplyClickOutside = true;
}
})
});
}
}
}]);
};
}

View File

@ -1,46 +1,44 @@
angular
.module('app')
.directive('delayHide', function($timeout) {
return {
restrict: 'A',
scope: {
show: '=',
delay: '@'
},
link: function(scope, elem, attrs) {
var showTimer;
import angular from 'angular';
showElement(false);
/* @ngInject */
export function delayHide($timeout) {
return {
restrict: 'A',
scope: {
show: '=',
delay: '@'
},
link: function(scope, elem, attrs) {
showElement(false);
//This is where all the magic happens!
// Whenever the scope variable updates we simply
// show if it evaluates to 'true' and hide if 'false'
scope.$watch('show', function(newVal){
newVal ? showSpinner() : hideSpinner();
});
// This is where all the magic happens!
// Whenever the scope variable updates we simply
// show if it evaluates to 'true' and hide if 'false'
scope.$watch('show', function(newVal) {
newVal ? showSpinner() : hideSpinner();
});
function showSpinner() {
if(scope.hidePromise) {
$timeout.cancel(scope.hidePromise);
scope.hidePromise = null;
}
showElement(true);
}
function showSpinner() {
if (scope.hidePromise) {
$timeout.cancel(scope.hidePromise);
scope.hidePromise = null;
}
showElement(true);
}
function hideSpinner() {
scope.hidePromise = $timeout(showElement.bind(this, false), getDelay());
}
function hideSpinner() {
scope.hidePromise = $timeout(showElement.bind(this, false), getDelay());
}
function showElement(show) {
show ? elem.css({display:''}) : elem.css({display:'none'});
}
function showElement(show) {
show ? elem.css({ display: '' }) : elem.css({ display: 'none' });
}
function getDelay() {
var delay = parseInt(scope.delay);
function getDelay() {
var delay = parseInt(scope.delay);
return angular.isNumber(delay) ? delay : 200;
}
}
};
});
return angular.isNumber(delay) ? delay : 200;
}
}
};
}

View File

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

View File

@ -1,17 +1,16 @@
angular
.module('app')
.directive('fileChange', function() {
return {
restrict: 'A',
scope: {
handler: '&'
},
link: function (scope, element) {
element.on('change', function (event) {
scope.$apply(function(){
scope.handler({files: event.target.files});
/* @ngInject */
export function fileChange() {
return {
restrict: 'A',
scope: {
handler: '&'
},
link: function(scope, element) {
element.on('change', function(event) {
scope.$apply(function() {
scope.handler({ files: event.target.files });
});
});
}
};
});
}
};
}

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) {
elem.on('scroll', function() {
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() {
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var lowercase = function(inputValue) {
if (inputValue == undefined) inputValue = '';
var lowercased = inputValue.toLowerCase();
if (lowercased !== inputValue) {
modelCtrl.$setViewValue(lowercased);
modelCtrl.$render();
}
return lowercased;
/* @ngInject */
export function lowercase() {
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var lowercase = function(inputValue) {
if (inputValue === undefined) inputValue = '';
var lowercased = inputValue.toLowerCase();
if (lowercased !== inputValue) {
modelCtrl.$setViewValue(lowercased);
modelCtrl.$render();
}
modelCtrl.$parsers.push(lowercase);
lowercase(scope[attrs.ngModel]);
}
};
});
return lowercased;
};
modelCtrl.$parsers.push(lowercase);
lowercase(scope[attrs.ngModel]);
}
};
}

View File

@ -1,15 +1,14 @@
angular
.module('app')
.directive('selectOnClick', ['$window', function ($window) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('focus', function () {
if (!$window.getSelection().toString()) {
// Required for mobile Safari
this.setSelectionRange(0, this.value.length)
}
});
/* @ngInject */
export function selectOnClick($window) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.on('focus', function() {
if (!$window.getSelection().toString()) {
// Required for mobile Safari
this.setSelectionRange(0, this.value.length);
}
};
}]);
});
}
};
}

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

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) {
return function (input) {
return input ? $filter('date')(new Date(input), 'MM/dd/yyyy', 'UTC') : '';
};
})
.filter('appDateTime', function ($filter) {
return function (input) {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
if (!formatter) {
locale = (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language;
formatter = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'numeric',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
return formatter.format(input);
} else {
return input ? $filter('date')(new Date(input), 'MM/dd/yyyy h:mm a') : '';
}
/* @ngInject */
export function appDate($filter) {
return function(input) {
return input ? $filter('date')(new Date(input), 'MM/dd/yyyy', 'UTC') : '';
};
}
/* @ngInject */
export function appDateTime($filter) {
return function(input) {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
if (!formatter) {
locale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
formatter = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'numeric',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
});
return formatter.format(input);
} else {
return input ? $filter('date')(new Date(input), 'MM/dd/yyyy h:mm a') : '';
}
};
}

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) {
return function(url) {
return $sce.trustAsResourceUrl(url);
};
}]);
/* @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,14 +1,15 @@
angular.module('app')
.config(function ($locationProvider) {
import { isDesktopApplication } from './utils';
if(!isDesktopApplication()) {
if (window.history && window.history.pushState) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
} else {
$locationProvider.html5Mode(false);
/* @ngInject */
export function configRoutes($locationProvider) {
if (!isDesktopApplication()) {
if (window.history && window.history.pushState) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
});
} else {
$locationProvider.html5Mode(false);
}
}

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,282 +1,285 @@
import _ from 'lodash';
import { isDesktopApplication } from '@/utils';
import { StorageManager } from './storageManager';
import { SNJS } from 'snjs';
const MillisecondsPerSecond = 1000;
class PasscodeManager {
export class PasscodeManager {
/* @ngInject */
constructor($rootScope, authManager, storageManager, syncManager) {
this.authManager = authManager;
this.storageManager = storageManager;
this.syncManager = syncManager;
this.$rootScope = $rootScope;
constructor($rootScope, authManager, storageManager, syncManager) {
this.authManager = authManager;
this.storageManager = storageManager;
this.syncManager = syncManager;
this.$rootScope = $rootScope;
this._hasPasscode = this.storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
this._locked = this._hasPasscode;
this._hasPasscode = this.storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
this._locked = this._hasPasscode;
this.visibilityObservers = [];
this.passcodeChangeObservers = [];
this.visibilityObservers = [];
this.passcodeChangeObservers = [];
this.configureAutoLock();
}
this.configureAutoLock();
addPasscodeChangeObserver(callback) {
this.passcodeChangeObservers.push(callback);
}
lockApplication() {
window.location.reload();
this.cancelAutoLockTimer();
}
isLocked() {
return this._locked;
}
hasPasscode() {
return this._hasPasscode;
}
keys() {
return this._keys;
}
addVisibilityObserver(callback) {
this.visibilityObservers.push(callback);
return callback;
}
removeVisibilityObserver(callback) {
_.pull(this.visibilityObservers, callback);
}
notifiyVisibilityObservers(visible) {
for(let callback of this.visibilityObservers) {
callback(visible);
}
}
addPasscodeChangeObserver(callback) {
this.passcodeChangeObservers.push(callback);
async setAutoLockInterval(interval) {
return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.FixedEncrypted);
}
async getAutoLockInterval() {
let interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.FixedEncrypted);
if(interval) {
return JSON.parse(interval);
} else {
return PasscodeManager.AutoLockIntervalNone;
}
}
lockApplication() {
window.location.reload();
this.cancelAutoLockTimer();
}
isLocked() {
return this._locked;
}
hasPasscode() {
return this._hasPasscode;
}
keys() {
return this._keys;
}
addVisibilityObserver(callback) {
this.visibilityObservers.push(callback);
return callback;
}
removeVisibilityObserver(callback) {
_.pull(this.visibilityObservers, callback);
}
notifiyVisibilityObservers(visible) {
for(let callback of this.visibilityObservers) {
callback(visible);
}
}
async setAutoLockInterval(interval) {
return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.FixedEncrypted);
}
async getAutoLockInterval() {
let interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.FixedEncrypted);
if(interval) {
return JSON.parse(interval);
passcodeAuthParams() {
var authParams = JSON.parse(this.storageManager.getItemSync("offlineParams", StorageManager.Fixed));
if(authParams && !authParams.version) {
var keys = this.keys();
if(keys && keys.ak) {
// If there's no version stored, and there's an ak, it has to be 002. Newer versions would have their version stored in authParams.
authParams.version = "002";
} else {
return PasscodeManager.AutoLockIntervalNone;
authParams.version = "001";
}
}
return authParams;
}
passcodeAuthParams() {
var authParams = JSON.parse(this.storageManager.getItemSync("offlineParams", StorageManager.Fixed));
if(authParams && !authParams.version) {
var keys = this.keys();
if(keys && keys.ak) {
// If there's no version stored, and there's an ak, it has to be 002. Newer versions would have their version stored in authParams.
authParams.version = "002";
} else {
authParams.version = "001";
}
}
return authParams;
}
async verifyPasscode(passcode) {
return new Promise(async (resolve, reject) => {
var params = this.passcodeAuthParams();
let keys = await SNJS.crypto.computeEncryptionKeysForUser(passcode, params);
if(keys.pw !== params.hash) {
resolve(false);
} else {
resolve(true);
}
})
}
unlock(passcode, callback) {
async verifyPasscode(passcode) {
return new Promise(async (resolve, reject) => {
var params = this.passcodeAuthParams();
SNJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
if(keys.pw !== params.hash) {
callback(false);
return;
}
this._keys = keys;
this._authParams = params;
this.decryptLocalStorage(keys, params).then(() => {
this._locked = false;
callback(true);
})
});
}
setPasscode(passcode, callback) {
var uuid = SNJS.crypto.generateUUIDSync();
SNJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
let keys = results.keys;
let authParams = results.authParams;
authParams.hash = keys.pw;
this._keys = keys;
this._hasPasscode = true;
this._authParams = authParams;
// Encrypting will initially clear localStorage
this.encryptLocalStorage(keys, authParams);
// After it's cleared, it's safe to write to it
this.storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
callback(true);
this.notifyObserversOfPasscodeChange();
});
}
changePasscode(newPasscode, callback) {
this.setPasscode(newPasscode, callback);
}
clearPasscode() {
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
this.storageManager.removeItem("offlineParams", StorageManager.Fixed);
this._keys = null;
this._hasPasscode = false;
this.notifyObserversOfPasscodeChange();
}
notifyObserversOfPasscodeChange() {
for(var observer of this.passcodeChangeObservers) {
observer();
}
}
encryptLocalStorage(keys, authParams) {
this.storageManager.setKeys(keys, authParams);
// Switch to Ephemeral storage, wiping Fixed storage
// Last argument is `force`, which we set to true because in the case of changing passcode
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
}
async decryptLocalStorage(keys, authParams) {
this.storageManager.setKeys(keys, authParams);
return this.storageManager.decryptStorage();
}
configureAutoLock() {
PasscodeManager.AutoLockPollFocusInterval = 1 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalNone = 0;
PasscodeManager.AutoLockIntervalImmediate = 1;
PasscodeManager.AutoLockIntervalOneMinute = 60 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalFiveMinutes = 300 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalOneHour = 3600 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalKey = "AutoLockIntervalKey";
if(isDesktopApplication()) {
// desktop only
this.$rootScope.$on("window-lost-focus", () => {
this.documentVisibilityChanged(false);
})
this.$rootScope.$on("window-gained-focus", () => {
this.documentVisibilityChanged(true);
})
let keys = await SNJS.crypto.computeEncryptionKeysForUser(passcode, params);
if(keys.pw !== params.hash) {
resolve(false);
} else {
// tab visibility listener, web only
document.addEventListener('visibilitychange', (e) => {
let visible = document.visibilityState == "visible";
this.documentVisibilityChanged(visible);
});
// verify document is in focus every so often as visibilitychange event is not triggered
// on a typical window blur event but rather on tab changes
this.pollFocusTimeout = setInterval(() => {
let hasFocus = document.hasFocus();
if(hasFocus && this.lastFocusState == "hidden") {
this.documentVisibilityChanged(true);
} else if(!hasFocus && this.lastFocusState == "visible") {
this.documentVisibilityChanged(false);
}
// save this to compare against next time around
this.lastFocusState = hasFocus ? "visible" : "hidden";
}, PasscodeManager.AutoLockPollFocusInterval);
resolve(true);
}
}
})
}
getAutoLockIntervalOptions() {
return [
{
value: PasscodeManager.AutoLockIntervalNone,
label: "Off"
},
{
value: PasscodeManager.AutoLockIntervalImmediate,
label: "Immediately"
},
{
value: PasscodeManager.AutoLockIntervalOneMinute,
label: "1m"
},
{
value: PasscodeManager.AutoLockIntervalFiveMinutes,
label: "5m"
},
{
value: PasscodeManager.AutoLockIntervalOneHour,
label: "1h"
}
]
}
documentVisibilityChanged(visible) {
if(visible) {
// check to see if lockAfterDate is not null, and if the application isn't locked.
// if that's the case, it needs to be locked immediately.
if(this.lockAfterDate && new Date() > this.lockAfterDate && !this.isLocked()) {
this.lockApplication();
} else {
if(!this.isLocked()) {
this.syncManager.sync();
}
}
this.cancelAutoLockTimer();
} else {
this.beginAutoLockTimer();
}
this.notifiyVisibilityObservers(visible);
}
async beginAutoLockTimer() {
var interval = await this.getAutoLockInterval();
if(interval == PasscodeManager.AutoLockIntervalNone) {
unlock(passcode, callback) {
var params = this.passcodeAuthParams();
SNJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
if(keys.pw !== params.hash) {
callback(false);
return;
}
// Use a timeout if possible, but if the computer is put to sleep, timeouts won't work.
// Need to set a date as backup. this.lockAfterDate does not need to be persisted, as
// living in memory seems sufficient. If memory is cleared, then the application will lock anyway.
let addToNow = (seconds) => {
let date = new Date();
date.setSeconds(date.getSeconds() + seconds);
return date;
this._keys = keys;
this._authParams = params;
this.decryptLocalStorage(keys, params).then(() => {
this._locked = false;
callback(true);
})
});
}
setPasscode(passcode, callback) {
var uuid = SNJS.crypto.generateUUIDSync();
SNJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
let keys = results.keys;
let authParams = results.authParams;
authParams.hash = keys.pw;
this._keys = keys;
this._hasPasscode = true;
this._authParams = authParams;
// Encrypting will initially clear localStorage
this.encryptLocalStorage(keys, authParams);
// After it's cleared, it's safe to write to it
this.storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
callback(true);
this.notifyObserversOfPasscodeChange();
});
}
changePasscode(newPasscode, callback) {
this.setPasscode(newPasscode, callback);
}
clearPasscode() {
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
this.storageManager.removeItem("offlineParams", StorageManager.Fixed);
this._keys = null;
this._hasPasscode = false;
this.notifyObserversOfPasscodeChange();
}
notifyObserversOfPasscodeChange() {
for(var observer of this.passcodeChangeObservers) {
observer();
}
}
encryptLocalStorage(keys, authParams) {
this.storageManager.setKeys(keys, authParams);
// Switch to Ephemeral storage, wiping Fixed storage
// Last argument is `force`, which we set to true because in the case of changing passcode
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
}
async decryptLocalStorage(keys, authParams) {
this.storageManager.setKeys(keys, authParams);
return this.storageManager.decryptStorage();
}
configureAutoLock() {
PasscodeManager.AutoLockPollFocusInterval = 1 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalNone = 0;
PasscodeManager.AutoLockIntervalImmediate = 1;
PasscodeManager.AutoLockIntervalOneMinute = 60 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalFiveMinutes = 300 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalOneHour = 3600 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalKey = "AutoLockIntervalKey";
if(isDesktopApplication()) {
// desktop only
this.$rootScope.$on("window-lost-focus", () => {
this.documentVisibilityChanged(false);
})
this.$rootScope.$on("window-gained-focus", () => {
this.documentVisibilityChanged(true);
})
} else {
// tab visibility listener, web only
document.addEventListener('visibilitychange', (e) => {
let visible = document.visibilityState == "visible";
this.documentVisibilityChanged(visible);
});
// verify document is in focus every so often as visibilitychange event is not triggered
// on a typical window blur event but rather on tab changes
this.pollFocusTimeout = setInterval(() => {
let hasFocus = document.hasFocus();
if(hasFocus && this.lastFocusState == "hidden") {
this.documentVisibilityChanged(true);
} else if(!hasFocus && this.lastFocusState == "visible") {
this.documentVisibilityChanged(false);
}
// save this to compare against next time around
this.lastFocusState = hasFocus ? "visible" : "hidden";
}, PasscodeManager.AutoLockPollFocusInterval);
}
}
getAutoLockIntervalOptions() {
return [
{
value: PasscodeManager.AutoLockIntervalNone,
label: "Off"
},
{
value: PasscodeManager.AutoLockIntervalImmediate,
label: "Immediately"
},
{
value: PasscodeManager.AutoLockIntervalOneMinute,
label: "1m"
},
{
value: PasscodeManager.AutoLockIntervalFiveMinutes,
label: "5m"
},
{
value: PasscodeManager.AutoLockIntervalOneHour,
label: "1h"
}
]
}
this.lockAfterDate = addToNow(interval / MillisecondsPerSecond);
this.lockTimeout = setTimeout(() => {
documentVisibilityChanged(visible) {
if(visible) {
// check to see if lockAfterDate is not null, and if the application isn't locked.
// if that's the case, it needs to be locked immediately.
if(this.lockAfterDate && new Date() > this.lockAfterDate && !this.isLocked()) {
this.lockApplication();
// We don't need to look at this anymore since we've succeeded with timeout lock
this.lockAfterDate = null;
}, interval);
} else {
if(!this.isLocked()) {
this.syncManager.sync();
}
}
this.cancelAutoLockTimer();
} else {
this.beginAutoLockTimer();
}
cancelAutoLockTimer() {
clearTimeout(this.lockTimeout);
this.notifiyVisibilityObservers(visible);
}
async beginAutoLockTimer() {
var interval = await this.getAutoLockInterval();
if(interval == PasscodeManager.AutoLockIntervalNone) {
return;
}
// Use a timeout if possible, but if the computer is put to sleep, timeouts won't work.
// Need to set a date as backup. this.lockAfterDate does not need to be persisted, as
// living in memory seems sufficient. If memory is cleared, then the application will lock anyway.
let addToNow = (seconds) => {
let date = new Date();
date.setSeconds(date.getSeconds() + seconds);
return date;
}
this.lockAfterDate = addToNow(interval / MillisecondsPerSecond);
this.lockTimeout = setTimeout(() => {
this.lockApplication();
// We don't need to look at this anymore since we've succeeded with timeout lock
this.lockAfterDate = null;
}
}
}, interval);
}
angular.module('app').service('passcodeManager', PasscodeManager);
cancelAutoLockTimer() {
clearTimeout(this.lockTimeout);
this.lockAfterDate = null;
}
}

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; }
.ion, .ionicons,
.ion-locked:before,
.ion-plus:before,
{
display: inline-block; font-family: "Ionicons"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; text-rendering: auto; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
@font-face {
font-family: "Ionicons";
src: url("../fonts/ionicons.eot?v=2.0.0");
src: url("../fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("../fonts/ionicons.ttf?v=2.0.1") format("truetype"), url("../fonts/ionicons.woff?v=2.0.1") format("woff"), url("../fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg");
font-weight: normal;
font-style: normal;
}
.ion-locked:before { content: "\f200"; }
.ion,
.ionicons,
.ion-locked:before,
.ion-plus:before,
{
display: inline-block;
font-family: "Ionicons";
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
text-rendering: auto;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ion-plus:before { content: "\f218"; }
.ion-locked:before {
content: "\f200";
}
.ion-plus:before {
content: "\f218";
}
/*# sourceMappingURL=ionicons.css.map */

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