Grunt -> Webpack, Haml -> Pug

This commit is contained in:
Mo Bitar
2019-12-16 16:26:51 -06:00
parent 68fbd745d5
commit c4c38616b0
134 changed files with 6780 additions and 76580 deletions

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