This commit is contained in:
Mo Bitar
2020-02-05 13:17:20 -06:00
parent 8c898b51be
commit 1ae3790ea3
48 changed files with 99518 additions and 2230 deletions

View File

@@ -3,6 +3,10 @@
import angular from 'angular';
import { configRoutes } from './routes';
import {
Application
} from './application';
import {
AppState
} from './state';
@@ -54,14 +58,14 @@ import {
ArchiveManager,
AuthManager,
ComponentManager,
DBManager,
DatabaseManager,
DesktopManager,
HttpManager,
KeyboardManager,
MigrationManager,
ModelManager,
NativeExtManager,
PasscodeManager,
LockManager,
PrivilegesManager,
SessionHistory,
SingletonManager,
@@ -141,20 +145,21 @@ angular
// Services
angular
.module('app')
.service('application', Application)
.service('appState', AppState)
.service('preferencesManager', PreferencesManager)
.service('actionsManager', ActionsManager)
.service('archiveManager', ArchiveManager)
.service('authManager', AuthManager)
.service('componentManager', ComponentManager)
.service('dbManager', DBManager)
.service('databaseManager', DatabaseManager)
.service('desktopManager', DesktopManager)
.service('httpManager', HttpManager)
.service('keyboardManager', KeyboardManager)
.service('migrationManager', MigrationManager)
.service('modelManager', ModelManager)
.service('nativeExtManager', NativeExtManager)
.service('passcodeManager', PasscodeManager)
.service('lockManager', LockManager)
.service('privilegesManager', PrivilegesManager)
.service('sessionHistory', SessionHistory)
.service('singletonManager', SingletonManager)

View File

@@ -0,0 +1,55 @@
import {
SNApplication,
SNAlertManager
Platforms,
Environments
} from 'snjs';
import angular from 'angular';
import { AlertManager } from '@/services/alertManager'
import { WebDeviceInterface } from '@/web_device_interface';
export class Application extends SNApplication {
constructor(
desktopManager
) {
const deviceInterface = new WebDeviceInterface();
super({
platform: Platforms.Web,
namespace: '',
host: window._default_sync_server,
deviceInterface: deviceInterface,
swapClasses: [
{
swap: SNAlertManager,
with: AlertManager
}
]
});
this.desktopManager = desktopManager;
this.overrideComponentManagerFunctions();
}
overrideComponentManagerFunctions() {
function openModalComponent(component) {
var scope = this.$rootScope.$new(true);
scope.component = component;
var el = this.$compile("<component-modal component='component' class='sk-modal'></component-modal>")(scope);
angular.element(document.body).append(el);
}
function presentPermissionsDialog(dialog) {
const scope = this.$rootScope.$new(true);
scope.permissionsString = dialog.permissionsString;
scope.component = dialog.component;
scope.callback = dialog.callback;
var el = this.$compile("<permissions-modal component='component' permissions-string='permissionsString' callback='callback' class='sk-modal'></permissions-modal>")(scope);
angular.element(document.body).append(el);
}
this.componentManager.openModalComponent = openModalComponent;
this.componentManager.presentPermissionsDialog = presentPermissionsDialog;
this.componentManager.setDesktopManager(this.desktopManager);
}
}

View File

@@ -1,8 +1,14 @@
import angular from 'angular';
import { SFModelManager } from 'snjs';
import {
ApplicationEvents,
isPayloadSourceRetrieved,
CONTENT_TYPE_NOTE,
CONTENT_TYPE_TAG,
CONTENT_TYPE_COMPONENT,
ProtectedActions
} from 'snjs';
import { isDesktopApplication } from '@/utils';
import { KeyboardManager } from '@/services/keyboardManager';
import { PrivilegesManager } from '@/services/privilegesManager';
import template from '%/editor.pug';
import { PureCtrl } from '@Controllers';
import {
@@ -53,32 +59,22 @@ class EditorCtrl extends PureCtrl {
constructor(
$timeout,
$rootScope,
alertManager,
appState,
authManager,
application,
actionsManager,
componentManager,
desktopManager,
keyboardManager,
modelManager,
preferencesManager,
privilegesManager,
sessionHistory /** Unused below, required to load globally */,
syncManager,
sessionHistory /** Unused below, required to load globally */
) {
super($timeout);
this.$rootScope = $rootScope;
this.alertManager = alertManager;
this.application = application;
this.appState = appState;
this.actionsManager = actionsManager;
this.authManager = authManager;
this.componentManager = componentManager;
this.desktopManager = desktopManager;
this.keyboardManager = keyboardManager;
this.modelManager = modelManager;
this.preferencesManager = preferencesManager;
this.privilegesManager = privilegesManager;
this.syncManager = syncManager;
this.state = {
componentStack: [],
@@ -94,9 +90,9 @@ class EditorCtrl extends PureCtrl {
this.rightResizeControl = {};
this.addAppStateObserver();
this.addSyncEventHandler();
this.addAppEventObserver();
this.addSyncStatusObserver();
this.addMappingObservers();
this.streamItems();
this.registerComponentHandler();
this.registerKeyboardShortcuts();
@@ -119,6 +115,111 @@ class EditorCtrl extends PureCtrl {
});
}
streamItems() {
this.application.streamItems({
contentType: CONTENT_TYPE_NOTE,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
}
if (this.state.note.deleted || this.state.note.content.trashed) {
return;
}
if (!isPayloadSourceRetrieved(source)) {
return;
}
const matchingNote = items.find((item) => {
return item.uuid === this.state.note.uuid;
});
if (!matchingNote) {
return;
}
this.reloadTagsString();
}
});
this.application.streamItems({
contentType: CONTENT_TYPE_TAG,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
}
for (const tag of items) {
if (
!this.state.note.savedTagsString ||
tag.deleted ||
tag.hasRelationshipWithItem(this.state.note)
) {
this.reloadTagsString();
break;
}
}
}
});
this.application.streamItems({
contentType: CONTENT_TYPE_COMPONENT,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
}
/** Reload componentStack in case new ones were added or removed */
this.reloadComponentStackArray();
/** Observe editor changes to see if the current note should update its editor */
const editors = items.filter(function (item) {
return item.isEditor();
});
if (editors.length === 0) {
return;
}
/** Find the most recent editor for note */
const editor = this.editorForNote(this.state.note);
this.setState({
selectedEditor: editor
});
if (!editor) {
this.reloadFont();
}
}
});
}
addAppEventObserver() {
this.application.addEventObserver((eventName) => {
if (!this.state.note) {
return;
}
if (eventName === ApplicationEvents.HighLatencySync) {
this.setState({
syncTakingTooLong: true
});
} else if (eventName === ApplicationEvents.CompletedSync) {
this.setState({
syncTakingTooLong: false
});
if (this.state.note.dirty) {
/** if we're still dirty, don't change status, a sync is likely upcoming. */
} else {
const saved = this.state.note.updated_at > this.state.note.lastSyncBegan;
const isInErrorState = this.state.saveError;
if (isInErrorState || saved) {
this.showAllChangesSavedStatus();
}
}
} else if (eventName === ApplicationEvents.FailedSync) {
/**
* Only show error status in editor if the note is dirty.
* Otherwise, it means the originating sync came from somewhere else
* and we don't want to display an error here.
*/
if (this.state.note.dirty) {
this.showErrorStatus();
}
}
});
}
async handleNoteSelectionChange(note, previousNote) {
this.setState({
note: this.appState.getSelectedNote(),
@@ -164,124 +265,19 @@ class EditorCtrl extends PureCtrl {
this.reloadComponentContext();
}
addMappingObservers() {
this.modelManager.addItemSyncObserver(
'editor-note-observer',
'Note',
(allItems, validItems, deletedItems, source) => {
if (!this.state.note) {
return;
}
if (this.state.note.deleted || this.state.note.content.trashed) {
return;
}
if (!SFModelManager.isMappingSourceRetrieved(source)) {
return;
}
const matchingNote = allItems.find((item) => {
return item.uuid === this.state.note.uuid;
});
if (!matchingNote) {
return;
}
this.reloadTagsString();
});
this.modelManager.addItemSyncObserver(
'editor-tag-observer',
'Tag',
(allItems, validItems, deletedItems, source) => {
if (!this.state.note) {
return;
}
for (const tag of allItems) {
if (
!this.state.note.savedTagsString ||
tag.deleted ||
tag.hasRelationshipWithItem(this.state.note)
) {
this.reloadTagsString();
break;
}
}
});
this.modelManager.addItemSyncObserver(
'editor-component-observer',
'SN|Component',
(allItems, validItems, deletedItems, source) => {
if (!this.state.note) {
return;
}
/** Reload componentStack in case new ones were added or removed */
this.reloadComponentStackArray();
/** Observe editor changes to see if the current note should update its editor */
const editors = allItems.filter(function (item) {
return item.isEditor();
});
if (editors.length === 0) {
return;
}
/** Find the most recent editor for note */
const editor = this.editorForNote(this.state.note);
this.setState({
selectedEditor: editor
});
if (!editor) {
this.reloadFont();
}
});
}
addSyncEventHandler() {
this.syncManager.addEventHandler((eventName, data) => {
if (!this.state.note) {
return;
}
if (eventName === 'sync:taking-too-long') {
this.setState({
syncTakingTooLong: true
});
} else if (eventName === 'sync:completed') {
this.setState({
syncTakingTooLong: false
});
if (this.state.note.dirty) {
/** if we're still dirty, don't change status, a sync is likely upcoming. */
} else {
const savedItem = data.savedItems.find((item) => {
return item.uuid === this.state.note.uuid;
});
const isInErrorState = this.state.saveError;
if (isInErrorState || savedItem) {
this.showAllChangesSavedStatus();
}
}
} else if (eventName === 'sync:error') {
/**
* Only show error status in editor if the note is dirty.
* Otherwise, it means the originating sync came from somewhere else
* and we don't want to display an error here.
*/
if (this.state.note.dirty) {
this.showErrorStatus();
}
}
});
}
addSyncStatusObserver() {
this.syncStatusObserver = this.syncManager.
registerSyncStatusObserver((status) => {
if (status.localError) {
this.$timeout(() => {
this.showErrorStatus({
message: "Offline Saving Issue",
desc: "Changes not saved"
});
}, 500);
}
});
/** @todo */
// this.syncStatusObserver = this.syncManager.
// registerSyncStatusObserver((status) => {
// if (status.localError) {
// this.$timeout(() => {
// this.showErrorStatus({
// message: "Offline Saving Issue",
// desc: "Changes not saved"
// });
// }, 500);
// }
// });
}
editorForNote(note) {
@@ -332,7 +328,7 @@ class EditorCtrl extends PureCtrl {
APP_DATA_KEY_PREFERS_PLAIN_EDITOR,
false
);
this.modelManager.setItemDirty(this.state.note);
this.application.setItemNeedsSync({ item: this.state.note });
}
this.associateComponentWithCurrentNote(editor);
} else {
@@ -342,7 +338,7 @@ class EditorCtrl extends PureCtrl {
APP_DATA_KEY_PREFERS_PLAIN_EDITOR,
true
);
this.modelManager.setItemDirty(this.state.note);
this.application.setItemNeedsSync({ item: this.state.note });
}
this.reloadFont();
@@ -356,7 +352,7 @@ class EditorCtrl extends PureCtrl {
}
/** Dirtying can happen above */
this.syncManager.sync();
this.application.sync();
}
hasAvailableExtensions() {
@@ -383,13 +379,13 @@ class EditorCtrl extends PureCtrl {
const note = this.state.note;
note.dummy = false;
if (note.deleted) {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_DELETED_NOTE
});
return;
}
if (!this.modelManager.findItem(note.uuid)) {
this.alertManager.alert({
if (!this.application.findItem({ uuid: note.uuid })) {
this.application.alertManager.alert({
text: STRING_INVALID_NOTE
});
return;
@@ -405,28 +401,20 @@ class EditorCtrl extends PureCtrl {
note.content.preview_plain = previewPlain;
note.content.preview_html = null;
}
this.modelManager.setItemDirty(
note,
true,
updateClientModified
);
this.application.setItemNeedsSync({
item: note,
updateUserModifiedDate: updateClientModified
});
if (this.saveTimeout) {
this.$timeout.cancel(this.saveTimeout);
}
const noDebounce = bypassDebouncer || this.authManager.offline();
const noDebounce = bypassDebouncer || this.application.noAccount();
const syncDebouceMs = noDebounce
? SAVE_TIMEOUT_NO_DEBOUNCE
: SAVE_TIMEOUT_DEBOUNCE;
this.saveTimeout = this.$timeout(() => {
this.syncManager.sync().then((response) => {
if (response && response.error && !this.didShowErrorAlert) {
this.didShowErrorAlert = true;
this.alertManager.alert({
text: STRING_GENERIC_SAVE_ERROR
});
}
});
this.application.sync();
}, syncDebouceMs);
}
@@ -443,7 +431,7 @@ class EditorCtrl extends PureCtrl {
syncTakingTooLong: false
});
let status = "All changes saved";
if (this.authManager.offline()) {
if (this.application.noAccount()) {
status += " (offline)";
}
this.setStatus(
@@ -542,14 +530,14 @@ class EditorCtrl extends PureCtrl {
async deleteNote(permanently) {
if (this.state.note.dummy) {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_DELETE_PLACEHOLDER_ATTEMPT
});
return;
}
const run = () => {
if (this.state.note.locked) {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_DELETE_LOCKED_ATTEMPT
});
return;
@@ -561,7 +549,7 @@ class EditorCtrl extends PureCtrl {
title: title,
permanently: permanently
});
this.alertManager.confirm({
this.application.alertManager.confirm({
text: text,
destructive: true,
onConfirm: () => {
@@ -579,12 +567,12 @@ class EditorCtrl extends PureCtrl {
}
});
};
const requiresPrivilege = await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionDeleteNote
const requiresPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
ProtectedActions.DeleteNote
);
if (requiresPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionDeleteNote,
this.godService.presentPrivilegesModal(
ProtectedActions.DeleteNote,
() => {
run();
}
@@ -595,17 +583,17 @@ class EditorCtrl extends PureCtrl {
}
performNoteDeletion(note) {
this.modelManager.setItemToBeDeleted(note);
this.application.deleteItem({ item: note });
if (note === this.state.note) {
this.setState({
note: null
});
}
if (note.dummy) {
this.modelManager.removeItemLocally(note);
this.application.deleteItemLocally({ item: note });
return;
}
this.syncManager.sync();
this.application.sync();
}
restoreTrashedNote() {
@@ -622,17 +610,17 @@ class EditorCtrl extends PureCtrl {
}
getTrashCount() {
return this.modelManager.trashedItems().length;
return this.application.getTrashedItems().length;
}
emptyTrash() {
const count = this.getTrashCount();
this.alertManager.confirm({
this.application.alertManager.confirm({
text: StringEmptyTrash({ count }),
destructive: true,
onConfirm: () => {
this.modelManager.emptyTrash();
this.syncManager.sync();
this.application.emptyTrash();
this.application.sync();
}
});
}
@@ -666,12 +654,12 @@ class EditorCtrl extends PureCtrl {
dontUpdatePreviews: true
});
/** Show privilegesManager if protection is not yet set up */
this.privilegesManager.actionHasPrivilegesConfigured(
PrivilegesManager.ActionViewProtectedNotes
/** Show privileges manager if protection is not yet set up */
this.application.privilegesManager.actionHasPrivilegesConfigured(
ProtectedActions.ViewProtectedNotes
).then((configured) => {
if (!configured) {
this.privilegesManager.presentPrivilegesManagementModal();
this.godService.presentPrivilegesManagementModal();
}
});
}
@@ -709,7 +697,7 @@ class EditorCtrl extends PureCtrl {
return currentTag.title;
});
strings.push(tag.title);
this.saveTags({ strings: strings});
this.saveTags({ strings: strings });
}
removeTag(tag) {
@@ -721,7 +709,7 @@ class EditorCtrl extends PureCtrl {
this.saveTags({ strings: strings });
}
saveTags({strings} = {}) {
saveTags({ strings } = {}) {
if (!strings && this.state.mutable.tagsString === this.state.note.tagsString()) {
return;
}
@@ -743,7 +731,7 @@ class EditorCtrl extends PureCtrl {
for (const tagToRemove of toRemove) {
tagToRemove.removeItemAsRelationship(this.state.note);
}
this.modelManager.setItemsDirty(toRemove);
this.application.setItemsNeedsSync({ items: toRemove });
const tags = [];
for (const tagString of strings) {
const existingRelationship = _.find(
@@ -752,15 +740,14 @@ class EditorCtrl extends PureCtrl {
);
if (!existingRelationship) {
tags.push(
this.modelManager.findOrCreateTagByTitle(tagString)
this.application.findOrCreateTag({ title: tagString })
);
}
}
for (const tag of tags) {
tag.addItemAsRelationship(this.state.note);
}
this.modelManager.setItemsDirty(tags);
this.syncManager.sync();
this.application.saveItems({ items: tags });
this.reloadTagsString();
}
@@ -977,12 +964,12 @@ class EditorCtrl extends PureCtrl {
}
else if (action === 'associate-item') {
if (data.item.content_type === 'Tag') {
const tag = this.modelManager.findItem(data.item.uuid);
const tag = this.application.findItem({ uuid: data.item.uuid });
this.addTag(tag);
}
}
else if (action === 'deassociate-item') {
const tag = this.modelManager.findItem(data.item.uuid);
const tag = this.application.findItem({ uuid: data.item.uuid });
this.removeTag(tag);
}
else if (action === 'save-items') {
@@ -1049,8 +1036,7 @@ class EditorCtrl extends PureCtrl {
component.disassociatedItemIds.push(this.state.note.uuid);
}
this.modelManager.setItemDirty(component);
this.syncManager.sync();
this.application.saveItem({ item: component });
}
associateComponentWithCurrentNote(component) {
@@ -1063,8 +1049,7 @@ class EditorCtrl extends PureCtrl {
component.associatedItemIds.push(this.state.note.uuid);
}
this.modelManager.setItemDirty(component);
this.syncManager.sync();
this.application.saveItem({ item: component });
}
registerKeyboardShortcuts() {

View File

@@ -1,5 +1,9 @@
import { PrivilegesManager } from '@/services/privilegesManager';
import { dateToLocalizedString } from '@/utils';
import {
ApplicationEvents,
TIMING_STRATEGY_FORCE_SPAWN_NEW,
ProtectedActions
} from 'snjs';
import template from '%/footer.pug';
import {
APP_STATE_EVENT_EDITOR_FOCUSED,
@@ -18,29 +22,19 @@ class FooterCtrl {
constructor(
$rootScope,
$timeout,
alertManager,
appState,
authManager,
componentManager,
modelManager,
application,
nativeExtManager,
passcodeManager,
privilegesManager,
statusManager,
syncManager,
godService
) {
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.alertManager = alertManager;
this.application = application;
this.appState = appState;
this.authManager = authManager;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.nativeExtManager = nativeExtManager;
this.passcodeManager = passcodeManager;
this.privilegesManager = privilegesManager;
this.statusManager = statusManager;
this.syncManager = syncManager;
this.godService = godService;
this.rooms = [];
this.themesWithIcons = [];
@@ -48,13 +42,13 @@ class FooterCtrl {
this.addAppStateObserver();
this.updateOfflineStatus();
this.addSyncEventHandler();
this.addAppEventObserver();
this.findErrors();
this.registerMappingObservers();
this.streamItems();
this.registerComponentHandler();
this.addRootScopeListeners();
this.authManager.checkForSecurityUpdate().then((available) => {
this.godService.checkForSecurityUpdate().then((available) => {
this.securityUpdateAvailable = available;
});
this.statusManager.addStatusObserver((string) => {
@@ -66,7 +60,7 @@ class FooterCtrl {
addRootScopeListeners() {
this.$rootScope.$on("security-update-status-changed", () => {
this.securityUpdateAvailable = this.authManager.securityUpdateAvailable;
this.securityUpdateAvailable = this.godService.securityUpdateAvailable;
});
this.$rootScope.$on("reload-ext-data", () => {
this.reloadExtendedData();
@@ -108,35 +102,34 @@ class FooterCtrl {
});
}
addSyncEventHandler() {
this.syncManager.addEventHandler((syncEvent, data) => {
this.$timeout(() => {
if(syncEvent === "local-data-loaded") {
if(this.offline && this.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;
} else if(syncEvent === 'sync:completed') {
this.syncUpdated();
this.findErrors();
this.updateOfflineStatus();
} else if(syncEvent === 'sync:error') {
this.findErrors();
this.updateOfflineStatus();
addAppEventObserver() {
this.application.addEventHandler((eventName) => {
if (eventName === ApplicationEvents.LoadedLocalData) {
if(this.offline && this.application.getNoteCount() === 0) {
this.showAccountMenu = true;
}
});
} else if (eventName === ApplicationEvents.EnteredOutOfSync) {
this.outOfSync = true;
} else if (eventName === ApplicationEvents.ExitedOutOfSync) {
this.outOfSync = false;
} else if (eventName === ApplicationEvents.CompletedSync) {
this.syncUpdated();
this.findErrors();
this.updateOfflineStatus();
} else if (eventName === ApplicationEvents.FailedSync) {
this.findErrors();
this.updateOfflineStatus();
}
});
}
registerMappingObservers() {
this.modelManager.addItemSyncObserver(
'room-bar',
'SN|Component',
(allItems, validItems, deletedItems, source) => {
this.rooms = this.modelManager.components.filter((candidate) => {
streamItems() {
this.application.streamItems({
contentType: CONTENT_TYPE_COMPONENT,
stream: async () => {
this.rooms = this.application.getItems({
contentType: CONTENT_TYPE_COMPONENT
}).filter((candidate) => {
return candidate.area === 'rooms' && !candidate.deleted;
});
if(this.queueExtReload) {
@@ -144,14 +137,14 @@ class FooterCtrl {
this.reloadExtendedData();
}
}
);
});
this.modelManager.addItemSyncObserver(
'footer-bar-themes',
'SN|Theme',
(allItems, validItems, deletedItems, source) => {
const themes = this.modelManager.validItemsForContentType('SN|Theme')
.filter((candidate) => {
this.application.streamItems({
contentType: 'SN|Theme',
stream: async () => {
const themes = this.application.getDisplayableItems({
contentType: CONTENT_TYPE_THEME
}).filter((candidate) => {
return (
!candidate.deleted &&
candidate.content.package_info &&
@@ -170,7 +163,7 @@ class FooterCtrl {
}
registerComponentHandler() {
this.componentManager.registerHandler({
this.application.componentManager.registerHandler({
identifier: "roomBar",
areas: ["rooms", "modal"],
activationHandler: (component) => {},
@@ -215,19 +208,19 @@ class FooterCtrl {
}
getUser() {
return this.authManager.user;
return this.application.getUser();
}
updateOfflineStatus() {
this.offline = this.authManager.offline();
this.offline = this.application.noUser();
}
openSecurityUpdate() {
this.authManager.presentPasswordWizard('upgrade-security');
this.godService.presentPasswordWizard('upgrade-security');
}
findErrors() {
this.error = this.syncManager.syncStatus.error;
this.error = this.application.getSyncStatus().error;
}
accountMenuPressed() {
@@ -244,7 +237,7 @@ class FooterCtrl {
}
hasPasscode() {
return this.passcodeManager.hasPasscode();
return this.application.hasPasscode();
}
lockApp() {
@@ -253,15 +246,15 @@ class FooterCtrl {
refreshData() {
this.isRefreshing = true;
this.syncManager.sync({
force: true,
performIntegrityCheck: true
this.application.sync({
timingStrategy: TIMING_STRATEGY_FORCE_SPAWN_NEW,
checkIntegrity: true
}).then((response) => {
this.$timeout(() => {
this.isRefreshing = false;
}, 200);
if(response && response.error) {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_GENERIC_SYNC_ERROR
});
} else {
@@ -280,7 +273,7 @@ class FooterCtrl {
clickedNewUpdateAnnouncement() {
this.newUpdateAvailable = false;
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_NEW_UPDATE_READY
});
}
@@ -324,7 +317,7 @@ class FooterCtrl {
}
selectShortcut(shortcut) {
this.componentManager.toggleComponent(shortcut.component);
this.application.componentManager.toggleComponent(shortcut.component);
}
onRoomDismiss(room) {
@@ -345,12 +338,12 @@ class FooterCtrl {
};
if(!room.showRoom) {
const requiresPrivilege = await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManageExtensions
const requiresPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
ProtectedActions.ManageExtensions
);
if(requiresPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionManageExtensions,
this.godService.presentPrivilegesModal(
ProtectedActions.ManageExtensions,
run
);
} else {
@@ -362,7 +355,7 @@ class FooterCtrl {
}
clickOutsideAccountMenu() {
if(this.privilegesManager.authenticationInProgress()) {
if(this.application.privilegesManager.authenticationInProgress()) {
return;
}
this.showAccountMenu = false;

View File

@@ -1,4 +1,7 @@
import template from '%/lock-screen.pug';
import {
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '@/state';
const ELEMENT_ID_PASSCODE_INPUT = 'passcode-input';
@@ -8,15 +11,14 @@ class LockScreenCtrl {
constructor(
$scope,
alertManager,
authManager,
passcodeManager,
application,
appState
) {
this.$scope = $scope;
this.alertManager = alertManager;
this.authManager = authManager;
this.passcodeManager = passcodeManager;
this.application = application;
this.appState = appState;
this.formData = {};
this.addVisibilityObserver();
this.addDestroyHandler();
}
@@ -29,16 +31,13 @@ class LockScreenCtrl {
addDestroyHandler() {
this.$scope.$on('$destroy', () => {
this.passcodeManager.removeVisibilityObserver(
this.visibilityObserver
);
this.unregisterObserver();
});
}
addVisibilityObserver() {
this.visibilityObserver = this.passcodeManager
.addVisibilityObserver((visible) => {
if(visible) {
this.unregisterObserver = this.appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
const input = this.passcodeInput;
if(input) {
input.focus();
@@ -47,7 +46,7 @@ class LockScreenCtrl {
});
}
submitPasscodeForm($event) {
async submitPasscodeForm($event) {
if(
!this.formData.passcode ||
this.formData.passcode.length === 0
@@ -55,21 +54,15 @@ class LockScreenCtrl {
return;
}
this.passcodeInput.blur();
this.passcodeManager.unlock(
this.formData.passcode,
(success) => {
if(!success) {
this.alertManager.alert({
text: "Invalid passcode. Please try again.",
onClose: () => {
this.passcodeInput.focus();
}
});
} else {
this.onSuccess()();
const success = await this.onValue()(this.formData.passcode);
if(!success) {
this.alertManager.alert({
text: "Invalid passcode. Please try again.",
onClose: () => {
this.passcodeInput.focus();
}
}
);
});
}
}
forgotPasscode() {
@@ -80,10 +73,9 @@ class LockScreenCtrl {
this.alertManager.confirm({
text: "Are you sure you want to clear all local data?",
destructive: true,
onConfirm: () => {
this.authManager.signout(true).then(() => {
window.location.reload();
});
onConfirm: async () => {
await this.application.signOut();
await this.application.restart();
}
});
}
@@ -97,7 +89,7 @@ export class LockScreen {
this.controllerAs = 'ctrl';
this.bindToController = true;
this.scope = {
onSuccess: '&',
onValue: '&',
};
}
}

View File

@@ -1,7 +1,7 @@
import _ from 'lodash';
import angular from 'angular';
import template from '%/notes.pug';
import { SFAuthManager } from 'snjs';
import { ApplicationEvents, CONTENT_TYPE_NOTE, CONTENT_TYPE_TAG } from 'snjs';
import { KeyboardManager } from '@/services/keyboardManager';
import { PureCtrl } from '@Controllers';
import {
@@ -48,25 +48,19 @@ class NotesCtrl extends PureCtrl {
constructor(
$timeout,
$rootScope,
application,
appState,
authManager,
desktopManager,
keyboardManager,
modelManager,
preferencesManager,
privilegesManager,
syncManager,
) {
super($timeout);
this.$rootScope = $rootScope;
this.application = application;
this.appState = appState;
this.authManager = authManager;
this.desktopManager = desktopManager;
this.keyboardManager = keyboardManager;
this.modelManager = modelManager;
this.preferencesManager = preferencesManager;
this.privilegesManager = privilegesManager;
this.syncManager = syncManager;
this.state = {
notes: [],
@@ -90,9 +84,8 @@ class NotesCtrl extends PureCtrl {
};
this.addAppStateObserver();
this.addSignInObserver();
this.addSyncEventHandler();
this.addMappingObserver();
this.addAppEventObserver();
this.streamNotesAndTags();
this.reloadPreferences();
this.resetPagination();
this.registerKeyboardShortcuts();
@@ -116,12 +109,12 @@ class NotesCtrl extends PureCtrl {
});
}
addSignInObserver() {
this.authManager.addEventHandler((event) => {
if (event === SFAuthManager.DidSignInEvent) {
addAppEventObserver() {
this.application.addEventObserver((eventName) => {
if (eventName === ApplicationEvents.SignedIn) {
/** Delete dummy note if applicable */
if (this.state.selectedNote && this.state.selectedNote.dummy) {
this.modelManager.removeItemLocally(this.state.selectedNote);
this.application.removeItemLocally({ item: this.state.selectedNote });
this.selectNote(null).then(() => {
this.reloadNotes();
});
@@ -132,17 +125,11 @@ class NotesCtrl extends PureCtrl {
*/
this.createDummyOnSynCompletionIfNoNotes = true;
}
}
});
}
addSyncEventHandler() {
this.syncManager.addEventHandler((syncEvent, data) => {
if (syncEvent === 'local-data-loaded') {
} else if (eventName === ApplicationEvents.LoadedLocalData) {
if (this.state.notes.length === 0) {
this.createNewNote();
}
} else if (syncEvent === 'sync:completed') {
} else if(eventName === ApplicationEvents.CompletedSync) {
if (this.createDummyOnSynCompletionIfNoNotes && this.state.notes.length === 0) {
this.createDummyOnSynCompletionIfNoNotes = false;
this.createNewNote();
@@ -151,11 +138,10 @@ class NotesCtrl extends PureCtrl {
});
}
addMappingObserver() {
this.modelManager.addItemSyncObserver(
'note-list',
'*',
async (allItems, validItems, deletedItems, source, sourceKey) => {
streamNotesAndTags() {
this.application.streamItems({
contentType: [CONTENT_TYPE_NOTE, CONTENT_TYPE_TAG],
stream: async ({ items }) => {
await this.reloadNotes();
const selectedNote = this.state.selectedNote;
if (selectedNote) {
@@ -169,18 +155,19 @@ class NotesCtrl extends PureCtrl {
}
/** Note has changed values, reset its flags */
const notes = allItems.filter((item) => item.content_type === 'Note');
const notes = items.filter((item) => item.content_type === CONTENT_TYPE_NOTE);
for (const note of notes) {
this.loadFlagsForNote(note);
note.cachedCreatedAtString = note.createdAtString();
note.cachedUpdatedAtString = note.updatedAtString();
}
});
}
});
}
async handleTagChange(tag, previousTag) {
if (this.state.selectedNote && this.state.selectedNote.dummy) {
this.modelManager.removeItemLocally(this.state.selectedNote);
this.application.removeItemLocally({item: this.state.selectedNote});
if (previousTag) {
_.remove(previousTag.notes, this.state.selectedNote);
}
@@ -201,7 +188,7 @@ class NotesCtrl extends PureCtrl {
if (this.state.notes.length > 0) {
this.selectFirstNote();
} else if (this.syncManager.initialDataLoaded()) {
} else if (this.application.isDatabaseLoaded()) {
if (!tag.isSmartTag() || tag.content.isAllTag) {
this.createNewNote();
} else if (
@@ -279,7 +266,7 @@ class NotesCtrl extends PureCtrl {
}
const previousNote = this.state.selectedNote;
if (previousNote && previousNote.dummy) {
this.modelManager.removeItemLocally(previousNote);
this.application.removeItemLocally({previousNote});
this.removeNoteFromList(previousNote);
}
await this.setState({
@@ -292,8 +279,7 @@ class NotesCtrl extends PureCtrl {
this.selectedIndex = Math.max(0, this.displayableNotes().indexOf(note));
if (note.content.conflict_of) {
note.content.conflict_of = null;
this.modelManager.setItemDirty(note);
this.syncManager.sync();
this.application.saveItem({item: note});
}
if (this.isFiltering()) {
this.desktopManager.searchText(this.state.noteFilter.text);
@@ -536,8 +522,8 @@ class NotesCtrl extends PureCtrl {
return;
}
const title = "Note" + (this.state.notes ? (" " + (this.state.notes.length + 1)) : "");
const newNote = this.modelManager.createItem({
content_type: 'Note',
const newNote = this.application.createItem({
contentType: CONTENT_TYPE_NOTE,
content: {
text: '',
title: title
@@ -545,19 +531,18 @@ class NotesCtrl extends PureCtrl {
});
newNote.client_updated_at = new Date();
newNote.dummy = true;
this.modelManager.addItem(newNote);
this.modelManager.setItemDirty(newNote);
this.application.setItemNeedsSync({item: newNote});
const selectedTag = this.appState.getSelectedTag();
if (!selectedTag.isSmartTag()) {
selectedTag.addItemAsRelationship(newNote);
this.modelManager.setItemDirty(selectedTag);
this.application.setItemNeedsSync({ item: selectedTag });
}
this.selectNote(newNote);
}
isFiltering() {
return this.state.noteFilter.text &&
this.state.noteFilter.text.length > 0;
return this.state.noteFilter.text &&
this.state.noteFilter.text.length > 0;
}
async setNoteFilterText(text) {

View File

@@ -1,9 +1,10 @@
import _ from 'lodash';
import { SFAuthManager } from 'snjs';
import { Challenges } from 'snjs';
import { getPlatformString } from '@/utils';
import template from '%/root.pug';
import {
APP_STATE_EVENT_PANEL_RESIZED
APP_STATE_EVENT_PANEL_RESIZED,
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '@/state';
import {
PANEL_NAME_NOTES,
@@ -15,257 +16,188 @@ import {
StringSyncException
} from '@/strings';
/** How often to automatically sync, in milliseconds */
const AUTO_SYNC_INTERVAL = 30000;
class RootCtrl {
class RootCtrl extends PureCtrl {
/* @ngInject */
constructor(
$location,
$rootScope,
$scope,
$timeout,
alertManager,
application,
appState,
authManager,
dbManager,
modelManager,
passcodeManager,
databaseManager,
lockManager,
preferencesManager,
themeManager /** Unused below, required to load globally */,
statusManager,
storageManager,
syncManager,
) {
this.$rootScope = $rootScope;
this.$scope = $scope;
super($timeout);
this.$location = $location;
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.dbManager = dbManager;
this.syncManager = syncManager;
this.statusManager = statusManager;
this.storageManager = storageManager;
this.application = application;
this.appState = appState;
this.authManager = authManager;
this.modelManager = modelManager;
this.alertManager = alertManager;
this.databaseManager = databaseManager;
this.lockManager = lockManager;
this.preferencesManager = preferencesManager;
this.passcodeManager = passcodeManager;
this.statusManager = statusManager;
this.platformString = getPlatformString();
this.state = {
needsUnlock: false,
appClass: ''
};
this.defineRootScopeFunctions();
this.loadApplication();
this.handleAutoSignInFromParams();
this.initializeStorageManager();
this.addAppStateObserver();
this.addDragDropHandlers();
this.defaultLoad();
}
defineRootScopeFunctions() {
this.$rootScope.lockApplication = () => {
/** Reloading wipes current objects from memory */
window.location.reload();
};
this.$rootScope.safeApply = (fn) => {
const phase = this.$scope.$root.$$phase;
if(phase === '$apply' || phase === '$digest') {
this.$scope.$eval(fn);
} else {
this.$scope.$apply(fn);
async loadApplication() {
this.application.prepareForLaunch({
callbacks: {
authChallengeResponses: async (challenges) => {
if (challenges.includes(Challenges.LocalPasscode)) {
this.setState({ needsUnlock: true });
}
}
}
};
});
await this.application.launch();
this.setState({ needsUnlock: false });
await this.openDatabase();
this.preferencesManager.load();
this.addSyncStatusObserver();
this.addSyncEventHandler();
}
defaultLoad() {
this.$scope.platform = getPlatformString();
if(this.passcodeManager.isLocked()) {
this.$scope.needsUnlock = true;
} else {
this.loadAfterUnlock();
}
this.$scope.onSuccessfulUnlock = () => {
this.$timeout(() => {
this.$scope.needsUnlock = false;
this.loadAfterUnlock();
});
};
this.$scope.onUpdateAvailable = () => {
this.$rootScope.$broadcast('new-update-available');
};
}
initializeStorageManager() {
this.storageManager.initialize(
this.passcodeManager.hasPasscode(),
this.authManager.isEphemeralSession()
);
}
onUpdateAvailable() {
this.$rootScope.$broadcast('new-update-available');
};
addAppStateObserver() {
this.appState.addObserver((eventName, data) => {
if(eventName === APP_STATE_EVENT_PANEL_RESIZED) {
if(data.panel === PANEL_NAME_NOTES) {
this.appState.addObserver(async (eventName, data) => {
if (eventName === APP_STATE_EVENT_PANEL_RESIZED) {
if (data.panel === PANEL_NAME_NOTES) {
this.notesCollapsed = data.collapsed;
}
if(data.panel === PANEL_NAME_TAGS) {
if (data.panel === PANEL_NAME_TAGS) {
this.tagsCollapsed = data.collapsed;
}
let appClass = "";
if(this.notesCollapsed) { appClass += "collapsed-notes"; }
if(this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.$scope.appClass = appClass;
if (this.notesCollapsed) { appClass += "collapsed-notes"; }
if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.setState({ appClass });
} else if (eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
if (!(await this.application.isPasscodeLocked())) {
this.application.sync();
}
}
});
}
loadAfterUnlock() {
this.openDatabase();
this.authManager.loadInitialData();
this.preferencesManager.load();
this.addSyncStatusObserver();
this.configureKeyRequestHandler();
this.addSyncEventHandler();
this.addSignOutObserver();
this.loadLocalData();
}
openDatabase() {
this.dbManager.setLocked(false);
this.dbManager.openDatabase({
async openDatabase() {
this.databaseManager.setLocked(false);
this.databaseManager.openDatabase({
onUpgradeNeeded: () => {
/**
* New database, delete syncToken so that items
* New database/database wiped, delete syncToken so that items
* can be refetched entirely from server
*/
this.syncManager.clearSyncToken();
this.syncManager.sync();
this.application.syncManager.clearSyncPositionTokens();
this.application.sync();
}
});
}
addSyncStatusObserver() {
this.syncStatusObserver = this.syncManager.registerSyncStatusObserver((status) => {
if(status.retrievedCount > 20) {
const text = `Downloading ${status.retrievedCount} items. Keep app open.`;
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
text
);
this.showingDownloadStatus = true;
} else if(this.showingDownloadStatus) {
this.showingDownloadStatus = false;
const text = "Download Complete.";
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
text
);
setTimeout(() => {
this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
}, 2000);
} else if(status.total > 20) {
this.uploadSyncStatus = this.statusManager.replaceStatusWithString(
this.uploadSyncStatus,
`Syncing ${status.current}/${status.total} items...`
);
} else if(this.uploadSyncStatus) {
this.uploadSyncStatus = this.statusManager.removeStatus(
this.uploadSyncStatus
);
}
});
}
// addSyncStatusObserver() {
// this.syncStatusObserver = this.syncManager.registerSyncStatusObserver((status) => {
// if (status.retrievedCount > 20) {
// const text = `Downloading ${status.retrievedCount} items. Keep app open.`;
// this.syncStatus = this.statusManager.replaceStatusWithString(
// this.syncStatus,
// text
// );
// this.showingDownloadStatus = true;
// } else if (this.showingDownloadStatus) {
// this.showingDownloadStatus = false;
// const text = "Download Complete.";
// this.syncStatus = this.statusManager.replaceStatusWithString(
// this.syncStatus,
// text
// );
// setTimeout(() => {
// this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
// }, 2000);
// } else if (status.total > 20) {
// this.uploadSyncStatus = this.statusManager.replaceStatusWithString(
// this.uploadSyncStatus,
// `Syncing ${status.current}/${status.total} items...`
// );
// } else if (this.uploadSyncStatus) {
// this.uploadSyncStatus = this.statusManager.removeStatus(
// this.uploadSyncStatus
// );
// }
// });
// }
configureKeyRequestHandler() {
this.syncManager.setKeyRequestHandler(async () => {
const offline = this.authManager.offline();
const authParams = (
offline
? this.passcodeManager.passcodeAuthParams()
: await this.authManager.getAuthParams()
);
const keys = offline
? this.passcodeManager.keys()
: await this.authManager.keys();
return {
keys: keys,
offline: offline,
auth_params: authParams
};
});
}
// addSyncEventHandler() {
// let lastShownDate;
// this.syncManager.addEventHandler((syncEvent, data) => {
// this.$rootScope.$broadcast(
// syncEvent,
// data || {}
// );
// if (syncEvent === 'sync-session-invalid') {
// /** Don't show repeatedly; at most 30 seconds in between */
// const SHOW_INTERVAL = 30;
// const lastShownSeconds = (new Date() - lastShownDate) / 1000;
// if (!lastShownDate || lastShownSeconds > SHOW_INTERVAL) {
// lastShownDate = new Date();
// setTimeout(() => {
// this.alertManager.alert({
// text: STRING_SESSION_EXPIRED
// });
// }, 500);
// }
// } else if (syncEvent === 'sync-exception') {
// this.alertManager.alert({
// text: StringSyncException(data)
// });
// }
// });
// }
addSyncEventHandler() {
let lastShownDate;
this.syncManager.addEventHandler((syncEvent, data) => {
this.$rootScope.$broadcast(
syncEvent,
data || {}
);
if(syncEvent === 'sync-session-invalid') {
/** Don't show repeatedly; at most 30 seconds in between */
const SHOW_INTERVAL = 30;
const lastShownSeconds = (new Date() - lastShownDate) / 1000;
if(!lastShownDate || lastShownSeconds > SHOW_INTERVAL) {
lastShownDate = new Date();
setTimeout(() => {
this.alertManager.alert({
text: STRING_SESSION_EXPIRED
});
}, 500);
}
} else if(syncEvent === 'sync-exception') {
this.alertManager.alert({
text: StringSyncException(data)
});
}
});
}
loadLocalData() {
const encryptionEnabled = this.authManager.user || this.passcodeManager.hasPasscode();
this.syncStatus = this.statusManager.addStatusFromString(
encryptionEnabled ? "Decrypting items..." : "Loading items..."
);
const incrementalCallback = (current, total) => {
const notesString = `${current}/${total} items...`;
const status = encryptionEnabled
? `Decrypting ${notesString}`
: `Loading ${notesString}`;
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
status
);
};
this.syncManager.loadLocalItems({incrementalCallback}).then(() => {
this.$timeout(() => {
this.$rootScope.$broadcast("initial-data-loaded");
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
"Syncing..."
);
this.syncManager.sync({
performIntegrityCheck: true
}).then(() => {
this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
});
setInterval(() => {
this.syncManager.sync();
}, AUTO_SYNC_INTERVAL);
});
});
}
addSignOutObserver() {
this.authManager.addEventHandler((event) => {
if(event === SFAuthManager.DidSignOutEvent) {
this.modelManager.handleSignout();
this.syncManager.handleSignout();
}
});
}
// loadLocalData() {
// const encryptionEnabled = this.application.getUser || this.application.hasPasscode();
// this.syncStatus = this.statusManager.addStatusFromString(
// encryptionEnabled ? "Decrypting items..." : "Loading items..."
// );
// const incrementalCallback = (current, total) => {
// const notesString = `${current}/${total} items...`;
// const status = encryptionEnabled
// ? `Decrypting ${notesString}`
// : `Loading ${notesString}`;
// this.syncStatus = this.statusManager.replaceStatusWithString(
// this.syncStatus,
// status
// );
// };
// this.syncManager.loadLocalItems({ incrementalCallback }).then(() => {
// this.$timeout(() => {
// this.$rootScope.$broadcast("initial-data-loaded");
// this.syncStatus = this.statusManager.replaceStatusWithString(
// this.syncStatus,
// "Syncing..."
// );
// this.syncManager.sync({
// checkIntegrity: true
// }).then(() => {
// this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
// });
// });
// });
// }
addDragDropHandlers() {
/**
@@ -280,9 +212,9 @@ class RootCtrl {
}, false);
window.addEventListener('drop', (event) => {
if(event.dataTransfer.files.length > 0) {
if (event.dataTransfer.files.length > 0) {
event.preventDefault();
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_DEFAULT_FILE_ERROR
});
}
@@ -294,39 +226,33 @@ class RootCtrl {
return this.$location.search()[key];
};
const autoSignInFromParams = async () => {
const autoSignInFromParams = async () => {
const server = urlParam('server');
const email = urlParam('email');
const pw = urlParam('pw');
if(!this.authManager.offline()) {
if(
await this.syncManager.getServerURL() === server
&& this.authManager.user.email === email
if (!this.application.getUser()) {
if (
await this.application.getHost() === server
&& this.application.getUser().email === email
) {
/** Already signed in, return */
// eslint-disable-next-line no-useless-return
return;
} else {
/** Sign out */
this.authManager.signout(true).then(() => {
window.location.reload();
});
await this.application.signOut();
await this.application.restart();
}
} else {
this.authManager.login(
server,
email,
pw,
false,
false,
{}
).then((response) => {
window.location.reload();
await this.application.setHost(server);
this.application.signIn({
email: email,
password: pw,
});
}
};
if(urlParam('server')) {
if (urlParam('server')) {
autoSignInFromParams();
}
}
@@ -336,5 +262,8 @@ export class Root {
constructor() {
this.template = template;
this.controller = RootCtrl;
this.replace = true;
this.controllerAs = 'self';
this.bindToController = true;
}
}

View File

@@ -1,4 +1,4 @@
import { SNNote, SNSmartTag } from 'snjs';
import { SNNote, SNSmartTag, CONTENT_TYPE_TAG, CONTENT_TYPE_SMART_TAG } from 'snjs';
import template from '%/tags.pug';
import {
APP_STATE_EVENT_PREFERENCES_CHANGED,
@@ -14,29 +14,22 @@ class TagsPanelCtrl extends PureCtrl {
constructor(
$rootScope,
$timeout,
alertManager,
application,
appState,
componentManager,
modelManager,
preferencesManager,
syncManager,
preferencesManager
) {
super($timeout);
this.$rootScope = $rootScope;
this.alertManager = alertManager;
this.application = application;
this.appState = appState;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.preferencesManager = preferencesManager;
this.syncManager = syncManager;
this.panelController = {};
this.addSyncEventHandler();
this.beginStreamingItems();
this.addAppStateObserver();
this.addMappingObserver();
this.loadPreferences();
this.registerComponentHandler();
this.state = {
smartTags: this.modelManager.getSmartTags(),
smartTags: this.application.getSmartTags(),
noteCounts: {}
};
}
@@ -45,18 +38,24 @@ class TagsPanelCtrl extends PureCtrl {
this.selectTag(this.state.smartTags[0]);
}
addSyncEventHandler() {
this.syncManager.addEventHandler(async (syncEvent, data) => {
if (
syncEvent === 'local-data-loaded' ||
syncEvent === 'sync:completed' ||
syncEvent === 'local-data-incremental-load'
) {
beginStreamingItems() {
this.application.streamItems({
contentType: CONTENT_TYPE_TAG,
stream: async ({items}) => {
await this.setState({
tags: this.modelManager.tags,
smartTags: this.modelManager.getSmartTags()
tags: this.application.getItems({contentType: CONTENT_TYPE_TAG}),
smartTags: this.application.getItems({ contentType: CONTENT_TYPE_SMART_TAG }),
});
this.reloadNoteCounts();
if (this.state.selectedTag) {
/** If the selected tag has been deleted, revert to All view. */
const selectedTag = items.find((tag) => {
return tag.uuid === this.state.selectedTag.uuid;
});
if (selectedTag && selectedTag.deleted) {
this.selectTag(this.state.smartTags[0]);
}
}
}
});
}
@@ -73,27 +72,6 @@ class TagsPanelCtrl extends PureCtrl {
});
}
addMappingObserver() {
this.modelManager.addItemSyncObserver(
'tags-list-tags',
'Tag',
(allItems, validItems, deletedItems, source, sourceKey) => {
this.reloadNoteCounts();
if (!this.state.selectedTag) {
return;
}
/** If the selected tag has been deleted, revert to All view. */
const selectedTag = allItems.find((tag) => {
return tag.uuid === this.state.selectedTag.uuid;
});
if (selectedTag && selectedTag.deleted) {
this.selectTag(this.state.smartTags[0]);
}
}
);
}
reloadNoteCounts() {
let allTags = [];
if (this.state.tags) {
@@ -140,7 +118,7 @@ class TagsPanelCtrl extends PureCtrl {
}
registerComponentHandler() {
this.componentManager.registerHandler({
this.application.componentManager.registerHandler({
identifier: 'tags',
areas: ['tags-list'],
activationHandler: (component) => {
@@ -152,7 +130,7 @@ class TagsPanelCtrl extends PureCtrl {
actionHandler: (component, action, data) => {
if (action === 'select-item') {
if (data.item.content_type === 'Tag') {
const tag = this.modelManager.findItem(data.item.uuid);
const tag = this.application.findItem({uuid: data.item.uuid});
if (tag) {
this.selectTag(tag);
}
@@ -171,14 +149,15 @@ class TagsPanelCtrl extends PureCtrl {
if (tag.isSmartTag()) {
Object.defineProperty(tag, 'notes', {
get: () => {
return this.modelManager.notesMatchingSmartTag(tag);
return this.application.getNotesMatchingSmartTag({
smartTag: tag
});
}
});
}
if (tag.content.conflict_of) {
tag.content.conflict_of = null;
this.modelManager.setItemDirty(tag);
this.syncManager.sync();
this.application.saveItem({item: tag});
}
this.appState.setSelectedTag(tag);
}
@@ -187,8 +166,8 @@ class TagsPanelCtrl extends PureCtrl {
if (this.state.editingTag) {
return;
}
const newTag = this.modelManager.createItem({
content_type: 'Tag'
const newTag = this.application.createItem({
contentType: CONTENT_TYPE_TAG
});
this.setState({
previousTag: this.state.selectedTag,
@@ -196,7 +175,9 @@ class TagsPanelCtrl extends PureCtrl {
editingTag: newTag,
newTag: newTag
});
this.modelManager.addItem(newTag);
/** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.insertItems({items: [newTag]});
}
tagTitleDidChange(tag) {
@@ -215,7 +196,9 @@ class TagsPanelCtrl extends PureCtrl {
tag.title = this.editingOriginalName;
this.editingOriginalName = null;
} else if(this.state.newTag) {
this.modelManager.removeItemLocally(tag);
/** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.removeItemLocally(tag);
this.setState({
selectedTag: this.state.previousTag
});
@@ -226,20 +209,20 @@ class TagsPanelCtrl extends PureCtrl {
this.editingOriginalName = null;
const matchingTag = this.modelManager.findTag(tag.title);
const matchingTag = this.application.findTag({title: tag.title});
const alreadyExists = matchingTag && matchingTag !== tag;
if (this.state.newTag === tag && alreadyExists) {
this.alertManager.alert({
this.application.alertManager.alert({
text: "A tag with this name already exists."
});
this.modelManager.removeItemLocally(tag);
/** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.removeItemLocally(tag);
this.setState({ newTag: null });
return;
}
this.modelManager.setItemDirty(tag);
this.syncManager.sync();
this.modelManager.resortTag(tag);
this.application.saveItem({item: tag});
this.selectTag(tag);
this.setState({
newTag: null
@@ -260,12 +243,11 @@ class TagsPanelCtrl extends PureCtrl {
}
removeTag(tag) {
this.alertManager.confirm({
this.application.alertManager.confirm({
text: STRING_DELETE_TAG,
destructive: true,
onConfirm: () => {
this.modelManager.setItemToBeDeleted(tag);
this.syncManager.sync();
this.application.deleteItem({item: tag});
}
});
}

View File

@@ -1,7 +1,6 @@
import { isDesktopApplication, isNullOrUndefined } from '@/utils';
import { PrivilegesManager } from '@/services/privilegesManager';
import template from '%/directives/account-menu.pug';
import { protocolManager } from 'snjs';
import { ProtectedActions } from 'snjs';
import { PureCtrl } from '@Controllers';
import {
STRING_ACCOUNT_MENU_UNCHECK_MERGE,
@@ -33,56 +32,38 @@ class AccountMenuCtrl extends PureCtrl {
$scope,
$rootScope,
$timeout,
alertManager,
archiveManager,
appVersion,
authManager,
modelManager,
passcodeManager,
privilegesManager,
storageManager,
syncManager,
godService,
lockManager,
application
) {
super($timeout);
this.$scope = $scope;
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.alertManager = alertManager;
this.archiveManager = archiveManager;
this.authManager = authManager;
this.modelManager = modelManager;
this.passcodeManager = passcodeManager;
this.privilegesManager = privilegesManager;
this.storageManager = storageManager;
this.syncManager = syncManager;
this.godService = godService;
this.lockManager = lockManager;
this.application = application;
this.state = {
appVersion: 'v' + (window.electronAppVersion || appVersion),
user: this.authManager.user,
canAddPasscode: !this.authManager.isEphemeralSession(),
passcodeAutoLockOptions: this.passcodeManager.getAutoLockIntervalOptions(),
user: this.application.getUser(),
canAddPasscode: !this.application.isEphemeralSession(),
passcodeAutoLockOptions: this.lockManager.getAutoLockIntervalOptions(),
formData: {
mergeLocal: true,
ephemeral: false
},
mutable: {
backupEncrypted: this.encryptedBackupsAvailable()
}
mutable: {}
};
this.syncStatus = this.syncManager.syncStatus;
this.syncManager.getServerURL().then((url) => {
this.setState({
server: url,
formData: { ...this.state.formData, url: url }
});
});
this.authManager.checkForSecurityUpdate().then((available) => {
this.setState({
securityUpdateAvailable: available
});
});
this.syncStatus = this.application.getSyncStatus();
this.loadHost();
this.checkForSecurityUpdate();
this.reloadAutoLockInterval();
this.loadBackupsAvailability();
}
$onInit() {
@@ -97,15 +78,51 @@ class AccountMenuCtrl extends PureCtrl {
});
}
encryptedBackupsAvailable() {
return !isNullOrUndefined(this.authManager.user) || this.passcodeManager.hasPasscode();
async loadHost() {
const host = await this.application.getHost();
this.setState({
server: host,
formData: {
...this.state.formData,
url: host
}
});
}
async checkForSecurityUpdate() {
const available = await this.godService.checkForSecurityUpdate();
this.setState({
securityUpdateAvailable: available
});
}
async loadBackupsAvailability() {
const hasUser = !isNullOrUndefined(await this.application.getUser());
const hasPasscode = await this.application.hasPasscode();
const encryptedAvailable = hasUser || hasPasscode;
function encryptionStatusString() {
if (hasUser) {
return STRING_E2E_ENABLED;
} else if (hasPasscode) {
return STRING_LOCAL_ENC_ENABLED;
} else {
return STRING_ENC_NOT_ENABLED;
}
}
this.setState({
encryptionStatusString: encryptionStatusString(),
encryptionEnabled: encryptedAvailable,
mutable: {
...this.state.mutable,
backupEncrypted: encryptedAvailable
}
});
}
submitMfaForm() {
const params = {
[this.state.formData.mfa.payload.mfa_key]: this.state.formData.userMfaCode
};
this.login(params);
this.login();
}
blurAuthFields() {
@@ -114,9 +131,9 @@ class AccountMenuCtrl extends PureCtrl {
ELEMENT_NAME_AUTH_PASSWORD,
ELEMENT_NAME_AUTH_PASSWORD_CONF
];
for(const name of names) {
for (const name of names) {
const element = document.getElementsByName(name)[0];
if(element) {
if (element) {
element.blur();
}
}
@@ -143,29 +160,25 @@ class AccountMenuCtrl extends PureCtrl {
});
}
async login(extraParams) {
/** Prevent a timed sync from occuring while signing in. */
this.syncManager.lockSyncing();
async login() {
await this.setFormDataState({
status: STRING_GENERATING_LOGIN_KEYS,
authenticating: true
});
const response = await this.authManager.login(
this.state.formData.url,
this.state.formData.email,
this.state.formData.user_password,
this.state.formData.ephemeral,
this.state.formData.strictSignin,
extraParams
);
const response = await this.application.signIn({
email: this.state.formData.email,
password: this.state.formData.user_password,
strict: this.state.formData.strictSignin,
ephemeral: this.state.formData.ephemeral,
mfaKeyPath: this.state.formData.mfa.payload.mfa_key,
mfaCode: this.state.formData.userMfaCode,
mergeLocal: this.state.formData.mergeLocal
});
const hasError = !response || response.error;
if (!hasError) {
await this.onAuthSuccess();
this.syncManager.unlockSyncing();
this.syncManager.sync({ performIntegrityCheck: true });
return;
}
this.syncManager.unlockSyncing();
await this.setFormDataState({
status: null,
});
@@ -174,7 +187,7 @@ class AccountMenuCtrl extends PureCtrl {
: { message: "An unknown error occured." };
if (error.tag === 'mfa-required' || error.tag === 'mfa-invalid') {
await this.setFormDataState({
await this.setFormDataState({
showLogin: false,
mfa: error
});
@@ -184,7 +197,7 @@ class AccountMenuCtrl extends PureCtrl {
mfa: null
});
if (error.message) {
this.alertManager.alert({
this.application.alertManager.alert({
text: error.message
});
}
@@ -197,22 +210,22 @@ class AccountMenuCtrl extends PureCtrl {
async register() {
const confirmation = this.state.formData.password_conf;
if (confirmation !== this.state.formData.user_password) {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_NON_MATCHING_PASSWORDS
});
return;
}
}
await this.setFormDataState({
confirmPassword: false,
status: STRING_GENERATING_REGISTER_KEYS,
authenticating: true
});
const response = await this.authManager.register(
this.state.formData.url,
this.state.formData.email,
this.state.formData.user_password,
this.state.formData.ephemeral
);
const response = await this.application.register({
email: this.state.formData.email,
password: this.state.formData.user_password,
ephemeral: this.state.formData.ephemeral,
mergeLocal: this.state.formData.mergeLocal
});
if (!response || response.error) {
await this.setFormDataState({
status: null
@@ -223,18 +236,18 @@ class AccountMenuCtrl extends PureCtrl {
await this.setFormDataState({
authenticating: false
});
this.alertManager.alert({
this.application.alertManager.alert({
text: error.message
});
} else {
await this.onAuthSuccess();
this.syncManager.sync();
this.application.sync();
}
}
mergeLocalChanged() {
if (!this.state.formData.mergeLocal) {
this.alertManager.confirm({
this.application.alertManager.confirm({
text: STRING_ACCOUNT_MENU_UNCHECK_MERGE,
destructive: true,
onCancel: () => {
@@ -249,34 +262,30 @@ class AccountMenuCtrl extends PureCtrl {
async onAuthSuccess() {
if (this.state.formData.mergeLocal) {
this.$rootScope.$broadcast('major-data-change');
await this.clearDatabaseAndRewriteAllItems({ alternateUuids: true });
} else {
this.modelManager.removeAllItemsFromMemory();
await this.storageManager.clearAllModels();
await this.rewriteDatabase({ alternateUuids: true });
}
await this.setFormDataState({
authenticating: false
});
this.syncManager.refreshErroredItems();
this.close();
}
openPasswordWizard(type) {
this.close();
this.authManager.presentPasswordWizard(type);
this.godService.presentPasswordWizard(type);
}
async openPrivilegesModal() {
this.close();
const run = () => {
this.privilegesManager.presentPrivilegesManagementModal();
this.godService.presentPrivilegesManagementModal();
};
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManagePrivileges
const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
ProtectedActions.ManagePrivileges
);
if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionManagePrivileges,
this.godService.presentPrivilegesModal(
ProtectedActions.ManagePrivileges,
() => {
run();
}
@@ -288,21 +297,21 @@ class AccountMenuCtrl extends PureCtrl {
/**
* Allows IndexedDB unencrypted logs to be deleted
* `clearAllModels` will remove data from backing store,
* `clearAllPayloads` will remove data from backing store,
* but not from working memory See:
* https://github.com/standardnotes/desktop/issues/131
*/
async clearDatabaseAndRewriteAllItems({ alternateUuids } = {}) {
await this.storageManager.clearAllModels();
await this.syncManager.markAllItemsDirtyAndSaveOffline(alternateUuids);
async rewriteDatabase({ alternateUuids } = {}) {
await this.application.destroyDatabase();
await this.application.markAllItemsAsNeedingSync({ alternateUuids });
}
destroyLocalData() {
this.alertManager.confirm({
this.application.alertManager.confirm({
text: STRING_SIGN_OUT_CONFIRMATION,
destructive: true,
onConfirm: async () => {
await this.authManager.signout(true);
await this.application.signOut();
window.location.reload();
}
});
@@ -323,7 +332,7 @@ class AccountMenuCtrl extends PureCtrl {
const data = JSON.parse(e.target.result);
resolve(data);
} catch (e) {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_INVALID_IMPORT_FILE
});
}
@@ -360,12 +369,12 @@ class AccountMenuCtrl extends PureCtrl {
await this.performImport(data, null);
}
};
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManageBackups
const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
ProtectedActions.ManageBackups
);
if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionManageBackups,
this.godService.presentPrivilegesModal(
ProtectedActions.ManageBackups,
run
);
} else {
@@ -386,47 +395,22 @@ class AccountMenuCtrl extends PureCtrl {
});
if (errorCount > 0) {
const message = StringImportError({ errorCount: errorCount });
this.alertManager.alert({
this.application.alertManager.alert({
text: message
});
} else {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_IMPORT_SUCCESS
});
}
}
async importJSONData(data, password) {
let errorCount = 0;
if (data.auth_params) {
const keys = await protocolManager.computeEncryptionKeysForUser(
password,
data.auth_params
);
try {
const throws = false;
await protocolManager.decryptMultipleItems(data.items, keys, throws);
const items = [];
for (const item of data.items) {
item.enc_item_key = null;
item.auth_hash = null;
if (item.errorDecrypting) {
errorCount++;
} else {
items.push(item);
}
}
data.items = items;
} catch (e) {
this.alertManager.alert({
text: STRING_ERROR_DECRYPTING_IMPORT
});
return;
}
}
const items = await this.modelManager.importItems(data.items);
for (const item of items) {
const { affectedItems, errorCount } = await this.application.importData({
data: data.items,
password: password
});
for (const item of affectedItems) {
/**
* Don't want to activate any components during import process in
* case of exceptions breaking up the import proccess
@@ -435,8 +419,6 @@ class AccountMenuCtrl extends PureCtrl {
item.active = false;
}
}
this.syncManager.sync();
return errorCount;
}
@@ -445,10 +427,12 @@ class AccountMenuCtrl extends PureCtrl {
}
notesAndTagsCount() {
return this.modelManager.allItemsMatchingTypes([
'Note',
'Tag'
]).length;
return this.application.getItems({
contentType: [
'Note',
'Tag'
]
}).length;
}
encryptionStatusForNotes() {
@@ -456,32 +440,8 @@ class AccountMenuCtrl extends PureCtrl {
return length + "/" + length + " notes and tags encrypted";
}
encryptionEnabled() {
return this.passcodeManager.hasPasscode() || !this.authManager.offline();
}
encryptionSource() {
if (!this.authManager.offline()) {
return "Account keys";
} else if (this.passcodeManager.hasPasscode()) {
return "Local Passcode";
} else {
return null;
}
}
encryptionStatusString() {
if (!this.authManager.offline()) {
return STRING_E2E_ENABLED;
} else if (this.passcodeManager.hasPasscode()) {
return STRING_LOCAL_ENC_ENABLED;
} else {
return STRING_ENC_NOT_ENABLED;
}
}
async reloadAutoLockInterval() {
const interval = await this.passcodeManager.getAutoLockInterval();
const interval = await this.lockManager.getAutoLockInterval();
this.setState({
selectedAutoLockInterval: interval
});
@@ -489,15 +449,15 @@ class AccountMenuCtrl extends PureCtrl {
async selectAutoLockInterval(interval) {
const run = async () => {
await this.passcodeManager.setAutoLockInterval(interval);
await this.lockManager.setAutoLockInterval(interval);
this.reloadAutoLockInterval();
};
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManagePasscode
const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
ProtectedActions.ManagePasscode
);
if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionManagePasscode,
this.godService.presentPrivilegesModal(
ProtectedActions.ManagePasscode,
() => {
run();
}
@@ -508,7 +468,7 @@ class AccountMenuCtrl extends PureCtrl {
}
hasPasscode() {
return this.passcodeManager.hasPasscode();
return this.application.hasPasscode();
}
addPasscodeClicked() {
@@ -520,23 +480,23 @@ class AccountMenuCtrl extends PureCtrl {
submitPasscodeForm() {
const passcode = this.state.formData.passcode;
if (passcode !== this.state.formData.confirmPasscode) {
this.alertManager.alert({
this.application.alertManager.alert({
text: STRING_NON_MATCHING_PASSCODES
});
return;
}
const func = this.state.formData.changingPasscode
? this.passcodeManager.changePasscode.bind(this.passcodeManager)
: this.passcodeManager.setPasscode.bind(this.passcodeManager);
? this.application.changePasscode.bind(this.application)
: this.application.setPasscode.bind(this.application);
func(passcode, async () => {
await this.setFormDataState({
passcode: null,
confirmPasscode: null,
showPasscodeForm: false
});
if (await this.authManager.offline()) {
if (isNullOrUndefined(await this.application.getUser())) {
this.$rootScope.$broadcast('major-data-change');
this.clearDatabaseAndRewriteAllItems();
this.rewriteDatabase();
}
});
}
@@ -546,12 +506,12 @@ class AccountMenuCtrl extends PureCtrl {
this.state.formData.changingPasscode = true;
this.addPasscodeClicked();
};
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManagePasscode
const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
ProtectedActions.ManagePasscode
);
if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionManagePasscode,
this.godService.presentPrivilegesModal(
ProtectedActions.ManagePasscode,
run
);
} else {
@@ -560,34 +520,24 @@ class AccountMenuCtrl extends PureCtrl {
}
async removePasscodePressed() {
const run = () => {
const signedIn = !this.authManager.offline();
const run = async () => {
const signedIn = !isNullOrUndefined(await this.application.getUser());
let message = STRING_REMOVE_PASSCODE_CONFIRMATION;
if (!signedIn) {
message += STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM;
}
this.alertManager.confirm({
this.application.alertManager.confirm({
text: message,
destructive: true,
onConfirm: () => {
this.passcodeManager.clearPasscode();
if (this.authManager.offline()) {
this.syncManager.markAllItemsDirtyAndSaveOffline();
}
this.application.removePasscode();
}
});
};
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManagePasscode
this.godService.presentPrivilegesModal(
ProtectedActions.ManagePasscode,
run
);
if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionManagePasscode,
run
);
} else {
run();
}
}
isDesktopApplication() {

View File

@@ -14,15 +14,15 @@ class ComponentViewCtrl {
$scope,
$rootScope,
$timeout,
componentManager,
application,
desktopManager,
themeManager
) {
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.application = application;
this.themeManager = themeManager;
this.desktopManager = desktopManager;
this.componentManager = componentManager;
this.componentValid = true;
$scope.$watch('ctrl.component', (component, prevComponent) => {
@@ -52,7 +52,7 @@ class ComponentViewCtrl {
registerComponentHandlers() {
this.themeHandlerIdentifier = 'component-view-' + Math.random();
this.componentManager.registerHandler({
this.application.componentManager.registerHandler({
identifier: this.themeHandlerIdentifier,
areas: ['themes'],
activationHandler: (component) => {
@@ -61,7 +61,7 @@ class ComponentViewCtrl {
});
this.identifier = 'component-view-' + Math.random();
this.componentManager.registerHandler({
this.application.componentManager.registerHandler({
identifier: this.identifier,
areas: [this.component.area],
activationHandler: (component) => {
@@ -74,7 +74,7 @@ class ComponentViewCtrl {
},
actionHandler: (component, action, data) => {
if(action === 'set-size') {
this.componentManager.handleSetSizeEvent(component, data);
this.application.componentManager.handleSetSizeEvent(component, data);
}
}
});
@@ -91,7 +91,7 @@ class ComponentViewCtrl {
async reloadComponent() {
this.componentValid = false;
await this.componentManager.reloadComponent(this.component);
await this.application.componentManager.reloadComponent(this.component);
this.reloadStatus();
}
@@ -124,7 +124,7 @@ class ComponentViewCtrl {
}
if(this.componentValid !== previouslyValid) {
if(this.componentValid) {
this.componentManager.reloadComponent(component, true);
this.application.componentManager.reloadComponent(component, true);
}
}
if(this.expired && doManualReload) {
@@ -140,7 +140,7 @@ class ComponentViewCtrl {
if(!this.component.active) {
return;
}
const iframe = this.componentManager.iframeForComponent(
const iframe = this.application.componentManager.iframeForComponent(
this.component
);
if(!iframe) {
@@ -186,7 +186,7 @@ class ComponentViewCtrl {
} catch (e) {}
}
this.$timeout.cancel(this.loadTimeout);
await this.componentManager.registerComponentWindow(
await this.application.componentManager.registerComponentWindow(
this.component,
iframe.contentWindow
);
@@ -201,13 +201,13 @@ class ComponentViewCtrl {
componentValueDidSet(component, prevComponent) {
const dontSync = true;
if(prevComponent && component !== prevComponent) {
this.componentManager.deactivateComponent(
this.application.componentManager.deactivateComponent(
prevComponent,
dontSync
);
}
if(component) {
this.componentManager.activateComponent(
this.application.componentManager.activateComponent(
component,
dontSync
);
@@ -239,17 +239,17 @@ class ComponentViewCtrl {
}
getUrl() {
const url = this.componentManager.urlForComponent(this.component);
const url = this.application.componentManager.urlForComponent(this.component);
this.component.runningLocally = (url === this.component.local_url);
return url;
}
destroy() {
this.componentManager.deregisterHandler(this.themeHandlerIdentifier);
this.componentManager.deregisterHandler(this.identifier);
this.application.componentManager.deregisterHandler(this.themeHandlerIdentifier);
this.application.componentManager.deregisterHandler(this.identifier);
if(this.component && !this.manualDealloc) {
const dontSync = true;
this.componentManager.deactivateComponent(this.component, dontSync);
this.application.componentManager.deactivateComponent(this.component, dontSync);
}
this.desktopManager.deregisterUpdateObserver(this.updateObserver);

View File

@@ -4,16 +4,12 @@ class ConflictResolutionCtrl {
/* @ngInject */
constructor(
$element,
alertManager,
archiveManager,
modelManager,
syncManager
application
) {
this.$element = $element;
this.alertManager = alertManager;
this.application = application;
this.archiveManager = archiveManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
}
$onInit() {
@@ -31,35 +27,31 @@ class ConflictResolutionCtrl {
}
keepItem1() {
this.alertManager.confirm({
this.application.alertManager.confirm({
text: `Are you sure you want to delete the item on the right?`,
destructive: true,
onConfirm: () => {
this.modelManager.setItemToBeDeleted(this.item2);
this.syncManager.sync().then(() => {
this.applyCallback();
});
this.application.deleteItem({item: this.item2});
this.triggerCallback();
this.dismiss();
}
});
}
keepItem2() {
this.alertManager.confirm({
this.application.alertManager.confirm({
text: `Are you sure you want to delete the item on the left?`,
destructive: true,
onConfirm: () => {
this.modelManager.setItemToBeDeleted(this.item1);
this.syncManager.sync().then(() => {
this.applyCallback();
});
this.application.deleteItem({item: this.item1});
this.triggerCallback();
this.dismiss();
}
});
}
keepBoth() {
this.applyCallback();
this.triggerCallback();
this.dismiss();
}
@@ -70,7 +62,7 @@ class ConflictResolutionCtrl {
);
}
applyCallback() {
triggerCallback() {
this.callback && this.callback();
}

View File

@@ -6,22 +6,17 @@ class EditorMenuCtrl extends PureCtrl {
/* @ngInject */
constructor(
$timeout,
componentManager,
modelManager,
syncManager,
application
) {
super($timeout);
this.$timeout = $timeout;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
this.application = application;
this.state = {
isDesktop: isDesktopApplication()
};
}
$onInit() {
const editors = this.componentManager.componentsForArea('editor-editor')
const editors = this.application.componentManager.componentsForArea('editor-editor')
.sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
@@ -36,8 +31,7 @@ class EditorMenuCtrl extends PureCtrl {
if(component) {
if(component.content.conflict_of) {
component.content.conflict_of = null;
this.modelManager.setItemDirty(component, true);
this.syncManager.sync();
this.application.saveItem({item: component});
}
}
this.$timeout(() => {
@@ -58,16 +52,15 @@ class EditorMenuCtrl extends PureCtrl {
}
makeEditorDefault(component) {
const currentDefault = this.componentManager
const currentDefault = this.application.componentManager
.componentsForArea('editor-editor')
.filter((e) => e.isDefaultEditor())[0];
if(currentDefault) {
currentDefault.setAppDataItem('defaultEditor', false);
this.modelManager.setItemDirty(currentDefault);
this.application.setItemsNeedsSync({item: currentDefault});
}
component.setAppDataItem('defaultEditor', true);
this.modelManager.setItemDirty(component);
this.syncManager.sync();
this.application.saveItem({ item: component });
this.setState({
defaultEditor: component
});
@@ -75,8 +68,7 @@ class EditorMenuCtrl extends PureCtrl {
removeEditorDefault(component) {
component.setAppDataItem('defaultEditor', false);
this.modelManager.setItemDirty(component);
this.syncManager.sync();
this.application.saveItem({ item: component });
this.setState({
defaultEditor: null
});

View File

@@ -1,6 +1,6 @@
import { protocolManager } from 'snjs';
import template from '%/directives/password-wizard.pug';
import { STRING_FAILED_PASSWORD_CHANGE } from '@/strings';
import { isNullOrUndefined } from '../../utils';
const DEFAULT_CONTINUE_TITLE = "Continue";
const Steps = {
@@ -18,25 +18,17 @@ class PasswordWizardCtrl {
$element,
$scope,
$timeout,
alertManager,
archiveManager,
authManager,
modelManager,
syncManager,
archiveManager
) {
this.$element = $element;
this.$timeout = $timeout;
this.$scope = $scope;
this.alertManager = alertManager;
this.archiveManager = archiveManager;
this.authManager = authManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
this.registerWindowUnloadStopper();
}
$onInit() {
this.syncStatus = this.syncManager.syncStatus;
this.syncStatus = this.application.getSyncStatus();
this.formData = {};
this.configureDefaults();
}
@@ -139,20 +131,11 @@ class PasswordWizardCtrl {
this.formData.status = "Unable to process your password. Please try again.";
return;
}
this.formData.status = "Encrypting and syncing data with new keys...";
const syncSuccess = await this.resyncData();
this.formData.statusError = !syncSuccess;
this.formData.processing = !syncSuccess;
if (syncSuccess) {
this.lockContinue = false;
if (this.changePassword) {
this.formData.status = "Successfully changed password and synced all items.";
} else if (this.securityUpdate) {
this.formData.status = "Successfully performed security update and synced all items.";
}
} else {
this.formData.status = STRING_FAILED_PASSWORD_CHANGE;
this.lockContinue = false;
if (this.changePassword) {
this.formData.status = "Successfully changed password.";
} else if (this.securityUpdate) {
this.formData.status = "Successfully performed security update.";
}
}
@@ -160,28 +143,28 @@ class PasswordWizardCtrl {
const currentPassword = this.formData.currentPassword;
const newPass = this.securityUpdate ? currentPassword : this.formData.newPassword;
if (!currentPassword || currentPassword.length === 0) {
this.alertManager.alert({
this.application.alertManager.alert({
text: "Please enter your current password."
});
return false;
}
if (this.changePassword) {
if (!newPass || newPass.length === 0) {
this.alertManager.alert({
this.application.alertManager.alert({
text: "Please enter a new password."
});
return false;
}
if (newPass !== this.formData.newPasswordConfirmation) {
this.alertManager.alert({
this.application.alertManager.alert({
text: "Your new password does not match its confirmation."
});
this.formData.status = null;
return false;
}
}
if (!this.authManager.user.email) {
this.alertManager.alert({
if (!this.application.getUser().email) {
this.application.alertManager.alert({
text: "We don't have your email stored. Please log out then log back in to fix this issue."
});
this.formData.status = null;
@@ -189,61 +172,31 @@ class PasswordWizardCtrl {
}
/** Validate current password */
const authParams = await this.authManager.getAuthParams();
const password = this.formData.currentPassword;
const keys = await protocolManager.computeEncryptionKeysForUser(
password,
authParams
);
const success = keys.mk === (await this.authManager.keys()).mk;
if (success) {
this.currentServerPw = keys.pw;
const key = await this.application.validateAccountPassword({
password: this.formData.currentPassword
});
if (key) {
this.currentServerPassword = key.serverPassword;
} else {
this.alertManager.alert({
this.application.alertManager.alert({
text: "The current password you entered is not correct. Please try again."
});
}
return success;
}
async resyncData() {
await this.modelManager.setAllItemsDirty();
const response = await this.syncManager.sync();
if (!response || response.error) {
this.alertManager.alert({
text: STRING_FAILED_PASSWORD_CHANGE
});
return false;
} else {
return true;
}
return !isNullOrUndefined(key);
}
async processPasswordChange() {
const newUserPassword = this.securityUpdate
const newPassword = this.securityUpdate
? this.formData.currentPassword
: this.formData.newPassword;
const currentServerPw = this.currentServerPw;
const results = await protocolManager.generateInitialKeysAndAuthParamsForUser(
this.authManager.user.email,
newUserPassword
);
const newKeys = results.keys;
const newAuthParams = results.authParams;
/**
* Perform a sync beforehand to pull in any last minutes changes before we change
* the encryption key (and thus cant decrypt new changes).
*/
await this.syncManager.sync();
const response = await this.authManager.changePassword(
await this.syncManager.getServerURL(),
this.authManager.user.email,
currentServerPw,
newKeys,
newAuthParams
);
const response = await this.application.changePassword({
email: this.application.getUser().email,
currentPassword: this.formData.currentPassword,
newPassword: newPassword
});
if (response.error) {
this.alertManager.alert({
this.application.alertManager.alert({
text: response.error.message
? response.error.message
: "There was an error changing your password. Please try again."
@@ -260,7 +213,7 @@ class PasswordWizardCtrl {
dismiss() {
if (this.lockContinue) {
this.alertManager.alert({
this.application.alertManager.alert({
text: "Cannot close window until pending tasks are complete."
});
} else {

View File

@@ -5,22 +5,24 @@ class PrivilegesAuthModalCtrl {
constructor(
$element,
$timeout,
privilegesManager,
application
) {
this.$element = $element;
this.$timeout = $timeout;
this.privilegesManager = privilegesManager;
this.application = application;
}
$onInit() {
this.authParameters = {};
this.sessionLengthOptions = this.privilegesManager.getSessionLengthOptions();
this.privilegesManager.getSelectedSessionLength().then((length) => {
this.sessionLengthOptions = this.application.privilegesManager.getSessionLengthOptions();
this.application.privilegesManager.getSelectedSessionLength()
.then((length) => {
this.$timeout(() => {
this.selectedSessionLength = length;
});
});
this.privilegesManager.netCredentialsForAction(this.action).then((credentials) => {
this.application.privilegesManager.netCredentialsForAction(this.action)
.then((credentials) => {
this.$timeout(() => {
this.requiredCredentials = credentials.sort();
});
@@ -32,7 +34,7 @@ class PrivilegesAuthModalCtrl {
}
promptForCredential(credential) {
return this.privilegesManager.displayInfoForCredential(credential).prompt;
return this.application.privilegesManager.displayInfoForCredential(credential).prompt;
}
cancel() {
@@ -65,13 +67,13 @@ class PrivilegesAuthModalCtrl {
if (!this.validate()) {
return;
}
const result = await this.privilegesManager.authenticateAction(
const result = await this.application.privilegesManager.authenticateAction(
this.action,
this.authParameters
);
this.$timeout(() => {
if (result.success) {
this.privilegesManager.setSessionLength(this.selectedSessionLength);
this.application.privilegesManager.setSessionLength(this.selectedSessionLength);
this.onSuccess();
this.dismiss();
} else {

View File

@@ -6,20 +6,18 @@ class PrivilegesManagementModalCtrl {
constructor(
$timeout,
$element,
privilegesManager,
authManager,
passcodeManager,
application
) {
this.$element = $element;
this.$timeout = $timeout;
this.privilegesManager = privilegesManager;
this.hasPasscode = passcodeManager.hasPasscode();
this.hasAccount = !authManager.offline();
this.application = application;
this.hasPasscode = application.hasPasscode();
this.hasAccount = !application.noUser();
this.reloadPrivileges();
}
displayInfoForCredential(credential) {
const info = this.privilegesManager.displayInfoForCredential(credential);
const info = this.application.privilegesManager.displayInfoForCredential(credential);
if (credential === PrivilegesManager.CredentialLocalPasscode) {
info.availability = this.hasPasscode;
} else if (credential === PrivilegesManager.CredentialAccountPassword) {
@@ -31,7 +29,7 @@ class PrivilegesManagementModalCtrl {
}
displayInfoForAction(action) {
return this.privilegesManager.displayInfoForAction(action).label;
return this.application.privilegesManager.displayInfoForAction(action).label;
}
isCredentialRequiredForAction(action, credential) {
@@ -42,21 +40,21 @@ class PrivilegesManagementModalCtrl {
}
async clearSession() {
await this.privilegesManager.clearSession();
await this.application.privilegesManager.clearSession();
this.reloadPrivileges();
}
async reloadPrivileges() {
this.availableActions = this.privilegesManager.getAvailableActions();
this.availableCredentials = this.privilegesManager.getAvailableCredentials();
const sessionEndDate = await this.privilegesManager.getSessionExpirey();
this.availableActions = this.application.privilegesManager.getAvailableActions();
this.availableCredentials = this.application.privilegesManager.getAvailableCredentials();
const sessionEndDate = await this.application.privilegesManager.getSessionExpirey();
this.sessionExpirey = sessionEndDate.toLocaleString();
this.sessionExpired = new Date() >= sessionEndDate;
this.credentialDisplayInfo = {};
for (const cred of this.availableCredentials) {
this.credentialDisplayInfo[cred] = this.displayInfoForCredential(cred);
}
const privs = await this.privilegesManager.getPrivileges();
const privs = await this.application.privilegesManager.getPrivileges();
this.$timeout(() => {
this.privileges = privs;
});
@@ -64,7 +62,7 @@ class PrivilegesManagementModalCtrl {
checkboxValueChanged(action, credential) {
this.privileges.toggleCredentialForAction(action, credential);
this.privilegesManager.savePrivileges();
this.application.privilegesManager.savePrivileges();
}
cancel() {

View File

@@ -7,23 +7,17 @@ class RevisionPreviewModalCtrl {
$element,
$scope,
$timeout,
alertManager,
componentManager,
modelManager,
syncManager,
) {
this.$element = $element;
this.$scope = $scope;
this.$timeout = $timeout;
this.alertManager = alertManager;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
this.createNote();
this.configureEditor();
$scope.$on('$destroy', () => {
if (this.identifier) {
this.componentManager.deregisterHandler(this.identifier);
this.application.componentManager.deregisterHandler(this.identifier);
}
});
}
@@ -41,7 +35,7 @@ class RevisionPreviewModalCtrl {
* for note as not to save changes to original, if editor makes changes.
*/
this.note.uuid = this.uuid;
const editorForNote = this.componentManager.editorForNote(this.note);
const editorForNote = this.application.componentManager.editorForNote(this.note);
this.note.uuid = protocolManager.crypto.generateUUIDSync();
if (editorForNote) {
/**
@@ -55,7 +49,7 @@ class RevisionPreviewModalCtrl {
editorCopy.readonly = true;
editorCopy.lockReadonly = true;
this.identifier = editorCopy.uuid;
this.componentManager.registerHandler({
this.application.componentManager.registerHandler({
identifier: this.identifier,
areas: ['editor-editor'],
contextRequestHandler: (component) => {
@@ -75,21 +69,21 @@ class RevisionPreviewModalCtrl {
}
restore(asCopy) {
const run = () => {
const run = async () => {
let item;
if (asCopy) {
const contentCopy = Object.assign({}, this.content);
if (contentCopy.title) {
contentCopy.title += " (copy)";
}
item = this.modelManager.createItem({
content_type: 'Note',
content: contentCopy
item = await this.application.createItem({
contentType: 'Note',
content: contentCopy,
needsSync: true
});
this.modelManager.addItem(item);
} else {
const uuid = this.uuid;
item = this.modelManager.findItem(uuid);
item = this.application.findItem({uuid: uuid});
item.content = Object.assign({}, this.content);
this.modelManager.mapResponseItemsToLocalModels(
[item],
@@ -102,7 +96,7 @@ class RevisionPreviewModalCtrl {
};
if (!asCopy) {
this.alertManager.confirm({
this.application.alertManager.confirm({
text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
destructive: true,
onConfirm: run

View File

@@ -12,11 +12,6 @@ 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';

View File

@@ -1,7 +1,7 @@
import { SFAlertManager } from 'snjs';
import { SNAlertManager } from 'snjs';
import { SKAlert } from 'sn-stylekit';
export class AlertManager extends SFAlertManager {
export class AlertManager extends SNAlertManager {
/* @ngInject */
constructor($timeout) {
super();

View File

@@ -2,8 +2,8 @@ import { PrivilegesManager } from '@/services/privilegesManager';
export class ArchiveManager {
/* @ngInject */
constructor(passcodeManager, authManager, modelManager, privilegesManager) {
this.passcodeManager = passcodeManager;
constructor(lockManager, authManager, modelManager, privilegesManager) {
this.lockManager = lockManager;
this.authManager = authManager;
this.modelManager = modelManager;
this.privilegesManager = privilegesManager;
@@ -22,9 +22,9 @@ export class ArchiveManager {
// download in Standard Notes format
let keys, authParams;
if(encrypted) {
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
keys = this.passcodeManager.keys();
authParams = this.passcodeManager.passcodeAuthParams();
if(this.authManager.offline() && this.lockManager.hasPasscode()) {
keys = this.lockManager.keys();
authParams = this.lockManager.passcodeAuthParams();
} else {
keys = await this.authManager.keys();
authParams = await this.authManager.getAuthParams();
@@ -42,7 +42,7 @@ export class ArchiveManager {
};
if(await this.privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageBackups)) {
this.privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageBackups, () => {
this.godService.presentPrivilegesModal(PrivilegesManager.ActionManageBackups, () => {
run();
});
} else {

View File

@@ -1,140 +0,0 @@
import angular from 'angular';
import { StorageManager } from './storageManager';
import { protocolManager, SFAuthManager } from 'snjs';
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;
this.modelManager = modelManager;
this.singletonManager = singletonManager;
this.storageManager = storageManager;
this.dbManager = dbManager;
}
loadInitialData() {
const userData = this.storageManager.getItemSync("user");
if(userData) {
this.user = JSON.parse(userData);
} else {
// legacy, check for uuid
const idData = this.storageManager.getItemSync("uuid");
if(idData) {
this.user = {uuid: idData};
}
}
this.checkForSecurityUpdate();
}
offline() {
return !this.user;
}
isEphemeralSession() {
if(this.ephemeral == null || this.ephemeral == undefined) {
this.ephemeral = JSON.parse(this.storageManager.getItemSync("ephemeral", StorageManager.Fixed));
}
return this.ephemeral;
}
setEphemeral(ephemeral) {
this.ephemeral = ephemeral;
if(ephemeral) {
this.storageManager.setModelStorageMode(StorageManager.Ephemeral);
this.storageManager.setItemsMode(StorageManager.Ephemeral);
} else {
this.storageManager.setModelStorageMode(StorageManager.Fixed);
this.storageManager.setItemsMode(this.storageManager.bestStorageMode());
this.storageManager.setItem("ephemeral", JSON.stringify(false), StorageManager.Fixed);
}
}
async getAuthParamsForEmail(url, email, extraParams) {
return super.getAuthParamsForEmail(url, email, extraParams);
}
async login(url, email, password, ephemeral, strictSignin, extraParams) {
return super.login(url, email, password, strictSignin, extraParams).then((response) => {
if(!response.error) {
this.setEphemeral(ephemeral);
this.checkForSecurityUpdate();
}
return response;
});
}
async register(url, email, password, ephemeral) {
return super.register(url, email, password).then((response) => {
if(!response.error) {
this.setEphemeral(ephemeral);
}
return response;
});
}
async changePassword(url, email, current_server_pw, newKeys, newAuthParams) {
return super.changePassword(url, email, current_server_pw, newKeys, newAuthParams).then((response) => {
if(!response.error) {
this.checkForSecurityUpdate();
}
return response;
});
}
async handleAuthResponse(response, email, url, authParams, keys) {
try {
await super.handleAuthResponse(response, email, url, authParams, keys);
this.user = response.user;
this.storageManager.setItem("user", JSON.stringify(response.user));
} catch (e) {
this.dbManager.displayOfflineAlert();
}
}
async verifyAccountPassword(password) {
const authParams = await this.getAuthParams();
const keys = await protocolManager.computeEncryptionKeysForUser(password, authParams);
const success = keys.mk === (await this.keys()).mk;
return success;
}
async checkForSecurityUpdate() {
if(this.offline()) {
return false;
}
const latest = protocolManager.version();
const updateAvailable = await this.protocolVersion() !== latest;
if(updateAvailable !== this.securityUpdateAvailable) {
this.securityUpdateAvailable = updateAvailable;
this.$rootScope.$broadcast("security-update-status-changed");
}
return this.securityUpdateAvailable;
}
presentPasswordWizard(type) {
var scope = this.$rootScope.$new(true);
scope.type = type;
var el = this.$compile( "<password-wizard type='type'></password-wizard>" )(scope);
angular.element(document.body).append(el);
}
signOut() {
super.signout();
this.user = null;
this._authParams = null;
}
}

View File

@@ -1,50 +0,0 @@
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,
desktopManager,
nativeExtManager,
alertManager: new SFAlertManager(),
$uiRunner: $rootScope.safeApply,
$timeout: $timeout,
environment: isDesktopApplication() ? "desktop" : "web",
platform: getPlatformString()
});
// this.loggingEnabled = true;
this.$compile = $compile;
this.$rootScope = $rootScope;
}
openModalComponent(component) {
var scope = this.$rootScope.$new(true);
scope.component = component;
var el = this.$compile( "<component-modal component='component' class='sk-modal'></component-modal>" )(scope);
angular.element(document.body).append(el);
}
presentPermissionsDialog(dialog) {
const scope = this.$rootScope.$new(true);
scope.permissionsString = dialog.permissionsString;
scope.component = dialog.component;
scope.callback = dialog.callback;
var el = this.$compile( "<permissions-modal component='component' permissions-string='permissionsString' callback='callback' class='sk-modal'></permissions-modal>" )(scope);
angular.element(document.body).append(el);
}
}

View File

@@ -1,4 +1,4 @@
export class DBManager {
export class DatabaseManager {
/* @ngInject */
constructor(alertManager) {
this.locked = true;

View File

@@ -15,10 +15,10 @@ export class DesktopManager {
modelManager,
syncManager,
authManager,
passcodeManager,
lockManager,
appState
) {
this.passcodeManager = passcodeManager;
this.lockManager = lockManager;
this.modelManager = modelManager;
this.authManager = authManager;
this.syncManager = syncManager;
@@ -202,9 +202,9 @@ export class DesktopManager {
async desktop_requestBackupFile(callback) {
let keys, authParams;
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
keys = this.passcodeManager.keys();
authParams = this.passcodeManager.passcodeAuthParams();
if(this.authManager.offline() && this.lockManager.hasPasscode()) {
keys = this.lockManager.keys();
authParams = this.lockManager.passcodeAuthParams();
} else {
keys = await this.authManager.keys();
authParams = await this.authManager.getAuthParams();

View File

@@ -1,44 +1,36 @@
import angular from 'angular';
import { SFPrivilegesManager } from 'snjs';
export class PrivilegesManager extends SFPrivilegesManager {
export class GodService {
/* @ngInject */
constructor(
passcodeManager,
authManager,
syncManager,
singletonManager,
modelManager,
storageManager,
$rootScope,
$compile
) {
super(modelManager, syncManager, singletonManager);
this.$rootScope = $rootScope;
this.$compile = $compile;
}
this.setDelegate({
isOffline: async () => {
return authManager.offline();
},
hasLocalPasscode: async () => {
return passcodeManager.hasPasscode();
},
saveToStorage: async (key, value) => {
return storageManager.setItem(key, value, storageManager.bestStorageMode());
},
getFromStorage: async (key) => {
return storageManager.getItem(key, storageManager.bestStorageMode());
},
verifyAccountPassword: async (password) => {
return authManager.verifyAccountPassword(password);
},
verifyLocalPasscode: async (passcode) => {
return passcodeManager.verifyPasscode(passcode);
},
});
async checkForSecurityUpdate() {
if (this.offline()) {
return false;
}
const latest = protocolManager.version();
const updateAvailable = await this.protocolVersion() !== latest;
if (updateAvailable !== this.securityUpdateAvailable) {
this.securityUpdateAvailable = updateAvailable;
this.$rootScope.$broadcast("security-update-status-changed");
}
return this.securityUpdateAvailable;
}
presentPasswordWizard(type) {
var scope = this.$rootScope.$new(true);
scope.type = type;
var el = this.$compile("<password-wizard type='type'></password-wizard>")(scope);
angular.element(document.body).append(el);
}
async presentPrivilegesModal(action, onSuccess, onCancel) {

View File

@@ -1,13 +0,0 @@
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');
});
}
}

View File

@@ -2,14 +2,14 @@ export { ActionsManager } from './actionsManager';
export { ArchiveManager } from './archiveManager';
export { AuthManager } from './authManager';
export { ComponentManager } from './componentManager';
export { DBManager } from './dbManager';
export { DatabaseManager } from './databaseManager';
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 { LockManager } from './lockManager';
export { PrivilegesManager } from './privilegesManager';
export { SessionHistory } from './sessionHistory';
export { SingletonManager } from './singletonManager';

View File

@@ -0,0 +1,143 @@
import _ from 'lodash';
import { isDesktopApplication } from '@/utils';
import {
APP_STATE_EVENT_WINDOW_DID_BLUR,
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '../state';
const MILLISECONDS_PER_SECOND = 1000;
const FOCUS_POLL_INTERVAL = 1 * MILLISECONDS_PER_SECOND;
const LOCK_INTERVAL_NONE = 0;
const LOCK_INTERVAL_IMMEDIATE = 1;
const LOCK_INTERVAL_ONE_MINUTE = 60 * MILLISECONDS_PER_SECOND;
const LOCK_INTERVAL_FIVE_MINUTES = 300 * MILLISECONDS_PER_SECOND;
const LOCK_INTERVAL_ONE_HOUR= 3600 * MILLISECONDS_PER_SECOND;
const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey";
export class LockManager {
/* @ngInject */
constructor($rootScope, application, appState) {
this.$rootScope = $rootScope;
this.application = application;
this.appState = appState;
this.observeVisibility();
}
observeVisibility() {
this.appState.addObserver((eventName, data) => {
if(eventName === APP_STATE_EVENT_WINDOW_DID_BLUR) {
this.documentVisibilityChanged(false);
} else if(eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
this.documentVisibilityChanged(true);
}
});
if (!isDesktopApplication()) {
this.beginWebFocusPolling();
}
}
async setAutoLockInterval(interval) {
return this.application.setValue(
STORAGE_KEY_AUTOLOCK_INTERVAL,
JSON.stringify(interval),
);
}
async getAutoLockInterval() {
const interval = await this.application.getValue(
STORAGE_KEY_AUTOLOCK_INTERVAL,
);
if(interval) {
return JSON.parse(interval);
} else {
return LOCK_INTERVAL_NONE;
}
}
/**
* 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.
*/
beginWebFocusPolling() {
this.pollFocusTimeout = setInterval(() => {
const 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';
}, FOCUS_POLL_INTERVAL);
}
getAutoLockIntervalOptions() {
return [
{
value: LOCK_INTERVAL_NONE,
label: "Off"
},
{
value: LOCK_INTERVAL_IMMEDIATE,
label: "Immediately"
},
{
value: LOCK_INTERVAL_ONE_MINUTE,
label: "1m"
},
{
value: LOCK_INTERVAL_FIVE_MINUTES,
label: "5m"
},
{
value: LOCK_INTERVAL_ONE_HOUR,
label: "1h"
}
];
}
documentVisibilityChanged(visible) {
if(visible) {
if(
!this.isLocked() &&
this.lockAfterDate &&
new Date() > this.lockAfterDate
) {
this.application.passcodeLock();
}
this.cancelAutoLockTimer();
} else {
this.beginAutoLockTimer();
}
}
async beginAutoLockTimer() {
var interval = await this.getAutoLockInterval();
if(interval === LOCK_INTERVAL_NONE) {
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 is sufficient. If memory is cleared, then the
* application will lock anyway.
*/
const addToNow = (seconds) => {
const date = new Date();
date.setSeconds(date.getSeconds() + seconds);
return date;
};
this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND);
this.lockTimeout = setTimeout(() => {
this.cancelAutoLockTimer();
this.application.passcodeLock();
this.lockAfterDate = null;
}, interval);
}
cancelAutoLockTimer() {
clearTimeout(this.lockTimeout);
this.lockAfterDate = null;
}
}

View File

@@ -1,172 +0,0 @@
import { isDesktopApplication } from '@/utils';
import { SFMigrationManager } from 'snjs';
import { ComponentManager } from '@/services/componentManager';
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;
this.desktopManager = desktopManager;
}
registeredMigrations() {
return [
this.editorToComponentMigration(),
this.componentUrlToHostedUrl(),
this.removeTagReferencesFromNotes()
];
}
/*
Migrate SN|Editor to SN|Component. Editors are deprecated as of November 2017. Editors using old APIs must
convert to using the new component API.
*/
editorToComponentMigration() {
return {
name: "editor-to-component",
content_type: "SN|Editor",
handler: async (editors) => {
// Convert editors to components
for(var editor of editors) {
// If there's already a component for this url, then skip this editor
if(editor.url && !this.componentManager.componentForUrl(editor.url)) {
var component = this.modelManager.createItem({
content_type: "SN|Component",
content: {
url: editor.url,
name: editor.name,
area: "editor-editor"
}
});
component.setAppDataItem("data", editor.data);
this.modelManager.addItem(component);
this.modelManager.setItemDirty(component, true);
}
}
for(const editor of editors) {
this.modelManager.setItemToBeDeleted(editor);
}
this.syncManager.sync();
}
};
}
/*
Migrate component.url fields to component.hosted_url. This involves rewriting any note data that relied on the
component.url value to store clientData, such as the CodeEditor, which stores the programming language for the note
in the note's clientData[component.url]. We want to rewrite any matching items to transfer that clientData into
clientData[component.uuid].
April 3, 2019 note: it seems this migration is mis-named. The first part of the description doesn't match what the code is actually doing.
It has nothing to do with url/hosted_url relationship and more to do with just mapping client data from the note's hosted_url to its uuid
Created: July 6, 2018
*/
componentUrlToHostedUrl() {
return {
name: "component-url-to-hosted-url",
content_type: "SN|Component",
handler: async (components) => {
let hasChanges = false;
const notes = this.modelManager.validItemsForContentType("Note");
for(const note of notes) {
for(const component of components) {
const clientData = note.getDomainDataItem(component.hosted_url, ComponentManager.ClientDataDomain);
if(clientData) {
note.setDomainDataItem(component.uuid, clientData, ComponentManager.ClientDataDomain);
note.setDomainDataItem(component.hosted_url, null, ComponentManager.ClientDataDomain);
this.modelManager.setItemDirty(note, true);
hasChanges = true;
}
}
}
if(hasChanges) {
this.syncManager.sync();
}
}
};
}
/*
Migrate notes which have relationships on tags to migrate those relationships to the tags themselves.
That is, notes.content.references should not include any mention of tags.
This will apply to notes created before the schema change. Now, only tags reference notes.
Created: April 3, 2019
*/
removeTagReferencesFromNotes() {
return {
name: "remove-tag-references-from-notes",
content_type: "Note",
handler: async (notes) => {
const needsSync = false;
let status = this.statusManager.addStatusFromString("Optimizing data...");
let dirtyCount = 0;
for(const note of notes) {
if(!note.content) {
continue;
}
const references = note.content.references;
// Remove any tag references, and transfer them to the tag if neccessary.
const newReferences = [];
for(const reference of references) {
if(reference.content_type != "Tag") {
newReferences.push(reference);
continue;
}
// is Tag content_type, we will not be adding this to newReferences
const tag = this.modelManager.findItem(reference.uuid);
if(tag && !tag.hasRelationshipWithItem(note)) {
tag.addItemAsRelationship(note);
this.modelManager.setItemDirty(tag, true);
dirtyCount++;
}
}
if(newReferences.length != references.length) {
note.content.references = newReferences;
this.modelManager.setItemDirty(note, true);
dirtyCount++;
}
}
if(dirtyCount > 0) {
if(isDesktopApplication()) {
this.desktopManager.saveBackup();
}
status = this.statusManager.replaceStatusWithString(status, `${dirtyCount} items optimized.`);
await this.syncManager.sync();
status = this.statusManager.replaceStatusWithString(status, `Optimization complete.`);
setTimeout(() => {
this.statusManager.removeStatus(status);
}, 2000);
} else {
this.statusManager.removeStatus(status);
}
}
};
}
}

View File

@@ -1,170 +0,0 @@
import _ from 'lodash';
import { SFModelManager, SNSmartTag, SFPredicate } from 'snjs';
export class ModelManager extends SFModelManager {
/* @ngInject */
constructor(storageManager, $timeout) {
super($timeout);
this.notes = [];
this.tags = [];
this.components = [];
this.storageManager = storageManager;
this.buildSystemSmartTags();
}
handleSignout() {
super.handleSignout();
this.notes.length = 0;
this.tags.length = 0;
this.components.length = 0;
}
noteCount() {
return this.notes.filter((n) => !n.dummy).length;
}
removeAllItemsFromMemory() {
for(var item of this.items) {
item.deleted = true;
}
this.notifySyncObserversOfModels(this.items);
this.handleSignout();
}
findTag(title) {
return _.find(this.tags, { title: title });
}
findOrCreateTagByTitle(title) {
let tag = this.findTag(title);
if(!tag) {
tag = this.createItem({content_type: "Tag", content: {title: title}});
this.addItem(tag);
this.setItemDirty(tag, true);
}
return tag;
}
addItems(items, globalOnly = false) {
super.addItems(items, globalOnly);
items.forEach((item) => {
// In some cases, you just want to add the item to this.items, and not to the individual arrays
// This applies when you want to keep an item syncable, but not display it via the individual arrays
if(!globalOnly) {
if(item.content_type == "Tag") {
if(!_.find(this.tags, {uuid: item.uuid})) {
this.tags.splice(_.sortedIndexBy(this.tags, item, function(item){
if (item.title) return item.title.toLowerCase();
else return '';
}), 0, item);
}
} else if(item.content_type == "Note") {
if(!_.find(this.notes, {uuid: item.uuid})) {
this.notes.unshift(item);
}
} else if(item.content_type == "SN|Component") {
if(!_.find(this.components, {uuid: item.uuid})) {
this.components.unshift(item);
}
}
}
});
}
resortTag(tag) {
_.pull(this.tags, tag);
this.tags.splice(_.sortedIndexBy(this.tags, tag, function(tag){
if (tag.title) return tag.title.toLowerCase();
else return '';
}), 0, tag);
}
setItemToBeDeleted(item) {
super.setItemToBeDeleted(item);
// remove from relevant array, but don't remove from all items.
// This way, it's removed from the display, but still synced via get dirty items
this.removeItemFromRespectiveArray(item);
}
removeItemLocally(item, callback) {
super.removeItemLocally(item, callback);
this.removeItemFromRespectiveArray(item);
this.storageManager.deleteModel(item).then(callback);
}
removeItemFromRespectiveArray(item) {
if(item.content_type == "Tag") {
_.remove(this.tags, {uuid: item.uuid});
} else if(item.content_type == "Note") {
_.remove(this.notes, {uuid: item.uuid});
} else if(item.content_type == "SN|Component") {
_.remove(this.components, {uuid: item.uuid});
}
}
notesMatchingSmartTag(tag) {
const contentTypePredicate = new SFPredicate("content_type", "=", "Note");
const predicates = [contentTypePredicate, tag.content.predicate];
if(!tag.content.isTrashTag) {
const notTrashedPredicate = new SFPredicate("content.trashed", "=", false);
predicates.push(notTrashedPredicate);
}
const results = this.itemsMatchingPredicates(predicates);
return results;
}
trashSmartTag() {
return this.systemSmartTags.find((tag) => tag.content.isTrashTag);
}
trashedItems() {
return this.notesMatchingSmartTag(this.trashSmartTag());
}
emptyTrash() {
const notes = this.trashedItems();
for(const note of notes) {
this.setItemToBeDeleted(note);
}
}
buildSystemSmartTags() {
this.systemSmartTags = SNSmartTag.systemSmartTags();
}
getSmartTagWithId(id) {
return this.getSmartTags().find((candidate) => candidate.uuid == id);
}
getSmartTags() {
const userTags = this.validItemsForContentType("SN|SmartTag").sort((a, b) => {
return a.content.title < b.content.title ? -1 : 1;
});
return this.systemSmartTags.concat(userTags);
}
/*
Misc
*/
humanReadableDisplayForContentType(contentType) {
return {
"Note" : "note",
"Tag" : "tag",
"SN|SmartTag": "smart tag",
"Extension" : "action-based extension",
"SN|Component" : "component",
"SN|Editor" : "editor",
"SN|Theme" : "theme",
"SF|Extension" : "server extension",
"SF|MFA" : "two-factor authentication setting",
"SN|FileSafe|Credentials": "FileSafe credential",
"SN|FileSafe|FileMetadata": "FileSafe file",
"SN|FileSafe|Integration": "FileSafe integration"
}[contentType];
}
}

View File

@@ -1,285 +0,0 @@
import _ from 'lodash';
import { isDesktopApplication } from '@/utils';
import { StorageManager } from './storageManager';
import { protocolManager } from 'snjs';
const MillisecondsPerSecond = 1000;
export class PasscodeManager {
/* @ngInject */
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.visibilityObservers = [];
this.passcodeChangeObservers = [];
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(const callback of this.visibilityObservers) {
callback(visible);
}
}
async setAutoLockInterval(interval) {
return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.FixedEncrypted);
}
async getAutoLockInterval() {
const interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.FixedEncrypted);
if(interval) {
return JSON.parse(interval);
} else {
return PasscodeManager.AutoLockIntervalNone;
}
}
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();
const keys = await protocolManager.computeEncryptionKeysForUser(passcode, params);
if(keys.pw !== params.hash) {
resolve(false);
} else {
resolve(true);
}
});
}
unlock(passcode, callback) {
var params = this.passcodeAuthParams();
protocolManager.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 = protocolManager.crypto.generateUUIDSync();
protocolManager.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
const keys = results.keys;
const 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) => {
const 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(() => {
const 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"
}
];
}
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) {
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.
const addToNow = (seconds) => {
const 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);
}
cancelAutoLockTimer() {
clearTimeout(this.lockTimeout);
this.lockAfterDate = null;
}
}

View File

@@ -1,44 +0,0 @@
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
};
// Session History can be encrypted with passcode keys. If it changes, we need to resave session
// history with the new keys.
passcodeManager.addPasscodeChangeObserver(() => {
this.saveToDisk();
});
var keyRequestHandler = async () => {
const offline = authManager.offline();
const auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams();
const keys = offline ? passcodeManager.keys() : await authManager.keys();
return {
keys: keys,
offline: offline,
auth_params: auth_params
};
};
var contentTypes = ["Note"];
super(
modelManager,
storageManager,
keyRequestHandler,
contentTypes,
$timeout
);
}
}

View File

@@ -1,10 +0,0 @@
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);
}
}

View File

@@ -1,241 +0,0 @@
import { protocolManager, SNEncryptedStorage, SFStorageManager , SFItemParams } from 'snjs';
export class MemoryStorage {
constructor() {
this.memory = {};
}
getItem(key) {
return this.memory[key] || null;
}
getItemSync(key) {
return this.getItem(key);
}
get length() {
return Object.keys(this.memory).length;
}
setItem(key, value) {
this.memory[key] = value;
}
removeItem(key) {
delete this.memory[key];
}
clear() {
this.memory = {};
}
keys() {
return Object.keys(this.memory);
}
key(index) {
return Object.keys(this.memory)[index];
}
}
export class StorageManager extends SFStorageManager {
/* @ngInject */
constructor(dbManager, alertManager) {
super();
this.dbManager = dbManager;
this.alertManager = alertManager;
}
initialize(hasPasscode, ephemeral) {
if(hasPasscode) {
// We don't want to save anything in fixed storage except for actual item data (in IndexedDB)
this.storage = this.memoryStorage;
this.itemsStorageMode = StorageManager.FixedEncrypted;
} else if(ephemeral) {
// We don't want to save anything in fixed storage as well as IndexedDB
this.storage = this.memoryStorage;
this.itemsStorageMode = StorageManager.Ephemeral;
} else {
this.storage = localStorage;
this.itemsStorageMode = StorageManager.Fixed;
}
this.modelStorageMode = ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed;
}
get memoryStorage() {
if(!this._memoryStorage) {
this._memoryStorage = new MemoryStorage();
}
return this._memoryStorage;
}
setItemsMode(mode, force) {
var newStorage = this.getVault(mode);
if(newStorage !== this.storage || mode !== this.itemsStorageMode || force) {
// transfer storages
var length = this.storage.length;
for(var i = 0; i < length; i++) {
var key = this.storage.key(i);
newStorage.setItem(key, this.storage.getItem(key));
}
this.itemsStorageMode = mode;
if(newStorage !== this.storage) {
// Only clear if this.storage isn't the same reference as newStorage
this.storage.clear();
}
this.storage = newStorage;
if(mode == StorageManager.FixedEncrypted) {
this.writeEncryptedStorageToDisk();
} else if(mode == StorageManager.Fixed) {
// Remove encrypted storage
this.removeItem("encryptedStorage", StorageManager.Fixed);
}
}
}
getVault(vaultKey) {
if(vaultKey) {
if(vaultKey == StorageManager.Ephemeral || vaultKey == StorageManager.FixedEncrypted) {
return this.memoryStorage;
} else {
return localStorage;
}
} else {
return this.storage;
}
}
async setItem(key, value, vaultKey) {
var storage = this.getVault(vaultKey);
try {
storage.setItem(key, value);
} catch (e) {
console.error("Exception while trying to setItem in StorageManager:", e);
this.alertManager.alert({text: "The application's local storage is out of space. If you have Session History save-to-disk enabled, please disable it, and try again."});
}
if(vaultKey === StorageManager.FixedEncrypted || (!vaultKey && this.itemsStorageMode === StorageManager.FixedEncrypted)) {
return this.writeEncryptedStorageToDisk();
}
}
async getItem(key, vault) {
return this.getItemSync(key, vault);
}
getItemSync(key, vault) {
var storage = this.getVault(vault);
return storage.getItem(key);
}
async removeItem(key, vault) {
var storage = this.getVault(vault);
return storage.removeItem(key);
}
async clear() {
this.memoryStorage.clear();
localStorage.clear();
}
storageAsHash() {
var hash = {};
var length = this.storage.length;
for(var i = 0; i < length; i++) {
var key = this.storage.key(i);
hash[key] = this.storage.getItem(key);
}
return hash;
}
setKeys(keys, authParams) {
this.encryptedStorageKeys = keys;
this.encryptedStorageAuthParams = authParams;
}
async writeEncryptedStorageToDisk() {
var encryptedStorage = new SNEncryptedStorage();
// Copy over totality of current storage
encryptedStorage.content.storage = this.storageAsHash();
// Save new encrypted storage in Fixed storage
var params = new SFItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams);
const syncParams = await params.paramsForSync();
this.setItem("encryptedStorage", JSON.stringify(syncParams), StorageManager.Fixed);
}
async decryptStorage() {
var stored = JSON.parse(this.getItemSync("encryptedStorage", StorageManager.Fixed));
await protocolManager.decryptItem(stored, this.encryptedStorageKeys);
var encryptedStorage = new SNEncryptedStorage(stored);
for(var key of Object.keys(encryptedStorage.content.storage)) {
this.setItem(key, encryptedStorage.storage[key]);
}
}
hasPasscode() {
return this.getItemSync("encryptedStorage", StorageManager.Fixed) !== null;
}
bestStorageMode() {
return this.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Fixed;
}
/*
Model Storage
If using ephemeral storage, we don't need to write it to anything as references will be held already by controllers
and the global modelManager service.
*/
setModelStorageMode(mode) {
if(mode == this.modelStorageMode) {
return;
}
if(mode == StorageManager.Ephemeral) {
// Clear IndexedDB
this.dbManager.clearAllModels(null);
} else {
// Fixed
}
this.modelStorageMode = mode;
}
async getAllModels() {
if(this.modelStorageMode == StorageManager.Fixed) {
return this.dbManager.getAllModels();
}
}
async saveModel(item) {
return this.saveModels([item]);
}
async saveModels(items, onsuccess, onerror) {
if(this.modelStorageMode == StorageManager.Fixed) {
return this.dbManager.saveModels(items);
}
}
async deleteModel(item) {
if(this.modelStorageMode == StorageManager.Fixed) {
return this.dbManager.deleteModel(item);
}
}
async clearAllModels() {
return this.dbManager.clearAllModels();
}
}
StorageManager.FixedEncrypted = "FixedEncrypted"; // encrypted memoryStorage + localStorage persistence
StorageManager.Ephemeral = "Ephemeral"; // memoryStorage
StorageManager.Fixed = "Fixed"; // localStorage

View File

@@ -1,30 +0,0 @@
import angular from 'angular';
import { SFSyncManager } from 'snjs';
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;
// this.loggingEnabled = true;
}
presentConflictResolutionModal(items, callback) {
var scope = this.$rootScope.$new(true);
scope.item1 = items[0];
scope.item2 = items[1];
scope.callback = callback;
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);
}
}

View File

@@ -12,7 +12,7 @@ export class ThemeManager {
componentManager,
desktopManager,
storageManager,
passcodeManager,
lockManager,
appState
) {
this.componentManager = componentManager;
@@ -24,13 +24,6 @@ export class ThemeManager {
this.registerObservers();
// When a passcode is added, all local storage will be encrypted (it doesn't know what was
// originally saved as Fixed or FixedEncrypted). We want to rewrite cached themes here to Fixed
// so that it's readable without authentication.
passcodeManager.addPasscodeChangeObserver(() => {
this.cacheThemes();
});
if (desktopManager.isDesktop) {
appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_DESKTOP_EXTS_READY) {

View File

@@ -1,4 +1,6 @@
import { PrivilegesManager } from '@/services/privilegesManager';
import { isDesktopApplication } from '@/utils';
import pull from 'lodash/pull';
export const APP_STATE_EVENT_TAG_CHANGED = 1;
export const APP_STATE_EVENT_NOTE_CHANGED = 2;
@@ -8,6 +10,8 @@ export const APP_STATE_EVENT_EDITOR_FOCUSED = 5;
export const APP_STATE_EVENT_BEGAN_BACKUP_DOWNLOAD = 6;
export const APP_STATE_EVENT_ENDED_BACKUP_DOWNLOAD = 7;
export const APP_STATE_EVENT_DESKTOP_EXTS_READY = 8;
export const APP_STATE_EVENT_WINDOW_DID_FOCUS = 9;
export const APP_STATE_EVENT_WINDOW_DID_BLUR = 10;
export const EVENT_SOURCE_USER_INTERACTION = 1;
export const EVENT_SOURCE_SCRIPT = 2;
@@ -15,15 +19,44 @@ export const EVENT_SOURCE_SCRIPT = 2;
export class AppState {
/* @ngInject */
constructor($timeout, privilegesManager) {
constructor(
$timeout,
$rootScope,
privilegesManager
) {
this.$timeout = $timeout;
this.$rootScope = $rootScope;
this.privilegesManager = privilegesManager;
this.observers = [];
this.registerVisibilityObservers();
}
registerVisibilityObservers() {
if (isDesktopApplication()) {
this.$rootScope.$on('window-lost-focus', () => {
this.notifyEvent(APP_STATE_EVENT_WINDOW_DID_BLUR);
});
this.$rootScope.$on('window-gained-focus', () => {
this.notifyEvent(APP_STATE_EVENT_WINDOW_DID_FOCUS);
});
} else {
/* Tab visibility listener, web only */
document.addEventListener('visibilitychange', (e) => {
const visible = document.visibilityState === "visible";
const event = visible
? APP_STATE_EVENT_WINDOW_DID_FOCUS
: APP_STATE_EVENT_WINDOW_DID_BLUR;
this.notifyEvent(event);
});
}
}
/** @returns A function that unregisters this observer */
addObserver(callback) {
this.observers.push(callback);
return callback;
return () => {
pull(this.observers, callback);
};
}
async notifyEvent(eventName, data) {
@@ -66,7 +99,7 @@ export class AppState {
await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionViewProtectedNotes
)) {
this.privilegesManager.presentPrivilegesModal(
this.godService.presentPrivilegesModal(
PrivilegesManager.ActionViewProtectedNotes,
run
);

View File

@@ -22,8 +22,8 @@ export function isNullOrUndefined(value) {
export function getPlatformString() {
try {
var platform = navigator.platform.toLowerCase();
var trimmed = '';
const platform = navigator.platform.toLowerCase();
let trimmed = '';
if (platform.indexOf('mac') !== -1) {
trimmed = 'mac';
} else if (platform.indexOf('win') !== -1) {

View File

@@ -0,0 +1,114 @@
import { DeviceInterface } from 'snjs';
export class WebDeviceInterface extends DeviceInterface {
async getRawStorageValue(key) {
return localStorage.getItem(key);
}
async getAllRawStorageKeyValues() {
const results = [];
for(const key of Object.keys(localStorage)) {
results.push({
key: key,
value: localStorage[key]
});
}
return results;
}
async setRawStorageValue(key, value) {
localStorage.setItem(key, value);
}
async removeRawStorageValue(key) {
localStorage.removeItem(key);
}
async removeAllRawStorageValues() {
localStorage.clear();
}
/** @database */
_getDatabaseKeyPrefix() {
if(this.namespace) {
return `${this.namespace}-item-`;
} else {
return `item-`;
}
}
_keyForPayloadId(id) {
return `${this._getDatabaseKeyPrefix()}${id}`;
}
async getRawDatabasePayloadWithId(id) {
return localStorage.getItem(this._keyForPayloadId(id))
}
async getAllRawDatabasePayloads() {
const models = [];
for(const key in localStorage) {
if(key.startsWith(this._getDatabaseKeyPrefix())) {
models.push(JSON.parse(localStorage[key]))
}
}
return models;
}
async saveRawDatabasePayload(payload) {
localStorage.setItem(
this._keyForPayloadId(payload.uuid),
JSON.stringify(payload)
);
}
async saveRawDatabasePayloads(payloads) {
for(const payload of payloads) {
await this.saveRawDatabasePayload(payload);
}
}
async removeRawDatabasePayloadWithId(id) {
localStorage.removeItem(this._keyForPayloadId(id));
}
async removeAllRawDatabasePayloads() {
for(const key in localStorage) {
if(key.startsWith(this._getDatabaseKeyPrefix())) {
delete localStorage[key];
}
}
}
/** @keychian */
async getRawKeychainValue() {
if(this.keychainValue) {
return this.keychainValue;
} else {
const authParams = localStorage.getItem('auth_params');
if(!authParams) {
return null;
}
const version = JSON.parse(authParams).version;
return {
mk: localStorage.getItem('mk'),
pw: localStorage.getItem('pw'),
ak: localStorage.getItem('ak'),
version: version
}
}
}
async setKeychainValue(value) {
this.keychainValue = value;
}
async clearKeychainValue() {
this.keychainValue = null;
}
}

View File

@@ -204,10 +204,10 @@
.inline Security Update Available
.sk-panel-section
.sk-panel-section-title Encryption
.sk-panel-section-subtitle.info(ng-if='self.encryptionEnabled()')
.sk-panel-section-subtitle.info(ng-if='self.state.encryptionEnabled')
| {{self.encryptionStatusForNotes()}}
p.sk-p
| {{self.encryptionStatusString()}}
| {{self.state.encryptionStatusString}}
.sk-panel-section
.sk-panel-section-title Passcode Lock
div(ng-if='!self.hasPasscode()')

View File

@@ -1,18 +1,17 @@
.main-ui-view(
ng-class='platform'
ng-class='self.platformString'
)
lock-screen(
ng-if='needsUnlock',
on-success='onSuccessfulUnlock'
ng-if='self.state.needsUnlock'
)
#app.app(
ng-class='appClass',
ng-if='!needsUnlock'
ng-class='self.state.appClass',
ng-if='!self.state.needsUnlock'
)
tags-panel
notes-panel
editor-panel
footer(
ng-if='!needsUnlock'
ng-if='!self.state.needsUnlock'
)

View File

@@ -29,7 +29,7 @@
<meta name="og:description" content="Standard Notes is a basic notes app that delivers only the essentials in note taking. Because of its simplicity and resistance to growth, users can count on durability and convenience."/>
<script>
window._default_sf_server = "<%= ENV['SF_DEFAULT_SERVER'] %>";
window._default_sync_server = "<%= ENV['SF_DEFAULT_SERVER'] %>";
window._extensions_manager_location = "<%= ENV['EXTENSIONS_MANAGER_LOCATION'] %>";
window._batch_manager_location = "<%= ENV['BATCH_MANAGER_LOCATION'] %>";
</script>

80773
dist/javascripts/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2652
dist/stylesheets/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -25,7 +25,7 @@
<title>Notes · Standard Notes</title>
<script>
window._default_sf_server = "https://sync.standardnotes.org";
window._default_sync_server = "https://sync.standardnotes.org";
window._extensions_manager_location = "public/extensions/extensions-manager/dist/index.html";
window._batch_manager_location = "public/extensions/batch-manager/dist/index.min.html";
</script>

14998
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@
"sass-loader": "^8.0.2",
"serve-static": "^1.14.1",
"sn-stylekit": "2.0.20",
"snjs": "1.0.6",
"snjs": "file:~/Desktop/sn/dev/snjs",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
},