Functioning UI

This commit is contained in:
Mo Bitar
2020-02-08 18:57:02 -06:00
parent f6ef4a39e2
commit 8822580e7a
23 changed files with 2088 additions and 1729 deletions

View File

@@ -55,15 +55,14 @@ import { trusted } from './filters';
import {
ArchiveManager,
DatabaseManager,
DesktopManager,
KeyboardManager,
NativeExtManager,
GodService,
LockManager,
NativeExtManager,
PreferencesManager,
StatusManager,
ThemeManager,
AlertManager,
PreferencesManager
} from './services';
angular.module('app', ['ngSanitize']);
@@ -108,11 +107,12 @@ angular
.directive('accountMenu', () => new AccountMenu())
.directive('actionsMenu', () => new ActionsMenu())
.directive('componentModal', () => new ComponentModal())
.directive(
'componentView',
($rootScope, componentManager, desktopManager, $timeout) =>
new ComponentView($rootScope, componentManager, desktopManager, $timeout)
)
.directive('componentView', () => new ComponentView())
// .directive(
// 'componentView',
// ($rootScope, componentManager, desktopManager, $timeout) =>
// new ComponentView($rootScope, componentManager, desktopManager, $timeout)
// )
.directive('conflictResolutionModal', () => new ConflictResolutionModal())
.directive('editorMenu', () => new EditorMenu())
.directive('inputModal', () => new InputModal())
@@ -134,16 +134,15 @@ angular
// Services
angular
.module('app')
.service('application', Application)
.service('appState', AppState)
.service('preferencesManager', PreferencesManager)
.service('application', Application)
.service('archiveManager', ArchiveManager)
.service('databaseManager', DatabaseManager)
.service('desktopManager', DesktopManager)
.service('godService', GodService)
.service('keyboardManager', KeyboardManager)
.service('nativeExtManager', NativeExtManager)
.service('lockManager', LockManager)
.service('nativeExtManager', NativeExtManager)
.service('preferencesManager', PreferencesManager)
.service('statusManager', StatusManager)
.service('storageManager', StorageManager)
.service('alertManager', AlertManager)
.service('themeManager', ThemeManager);

View File

@@ -1,20 +1,21 @@
import {
import {
SNApplication,
SNAlertManager,
Platforms
} from 'snjs';
import angular from 'angular';
import { AlertManager } from '@/services/alertManager';
Environments,
platformFromString
} from 'snjs';
import angular from 'angular';
import { getPlatformString } from '@/utils';
import { AlertManager } from '@/services/alertManager';
import { WebDeviceInterface } from '@/web_device_interface';
export class Application extends SNApplication {
constructor(
desktopManager
) {
constructor() {
const deviceInterface = new WebDeviceInterface();
super({
platform: Platforms.Web,
environment: Environments.Web,
platform: platformFromString(getPlatformString()),
namespace: '',
host: window._default_sync_server,
deviceInterface: deviceInterface,
@@ -25,30 +26,26 @@ export class Application extends SNApplication {
}
]
});
this.desktopManager = desktopManager;
deviceInterface.setApplication(this);
this.overrideComponentManagerFunctions();
}
overrideComponentManagerFunctions() {
function openModalComponent(component) {
var scope = this.$rootScope.$new(true);
const scope = this.$rootScope.$new(true);
scope.component = component;
var el = this.$compile("<component-modal component='component' class='sk-modal'></component-modal>")(scope);
const 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);
const 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

@@ -7,6 +7,7 @@ import {
} from 'snjs';
import find from 'lodash/find';
import { isDesktopApplication } from '@/utils';
import { KeyboardModifiers, KeyboardKeys } from '@/services/keyboardManager';
import template from '%/editor.pug';
import { PureCtrl } from '@Controllers';
import { AppStateEvents, EventSources } from '@/state';
@@ -80,9 +81,12 @@ class EditorCtrl extends PureCtrl {
this.addAppStateObserver();
this.addAppEventObserver();
this.addSyncStatusObserver();
this.streamItems();
this.registerComponentHandler();
this.registerKeyboardShortcuts();
application.onReady(() => {
this.streamItems();
this.registerComponentHandler();
});
/** Used by .pug template */
this.prefKeyMonospace = PrefKeys.EditorMonospaceEnabled;
@@ -269,7 +273,7 @@ class EditorCtrl extends PureCtrl {
}
editorForNote(note) {
return this.componentManager.editorForNote(note);
return this.application.componentManager.editorForNote(note);
}
setMenuState(menu, state) {
@@ -861,7 +865,7 @@ class EditorCtrl extends PureCtrl {
}
registerComponentHandler() {
this.componentManager.registerHandler({
this.application.componentManager.registerHandler({
identifier: 'editor',
areas: [
'note-tags',
@@ -973,7 +977,7 @@ class EditorCtrl extends PureCtrl {
}
reloadComponentStackArray() {
const components = this.componentManager.componentsForArea('editor-stack')
const components = this.application.componentManager.componentsForArea('editor-stack')
.sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
@@ -988,7 +992,7 @@ class EditorCtrl extends PureCtrl {
if (this.state.note) {
for (const component of this.state.componentStack) {
if (component.active) {
this.componentManager.setComponentHidden(
this.application.componentManager.setComponentHidden(
component,
!component.isExplicitlyEnabledForItem(this.state.note)
);
@@ -996,21 +1000,21 @@ class EditorCtrl extends PureCtrl {
}
}
this.componentManager.contextItemDidChangeInArea('note-tags');
this.componentManager.contextItemDidChangeInArea('editor-stack');
this.componentManager.contextItemDidChangeInArea('editor-editor');
this.application.componentManager.contextItemDidChangeInArea('note-tags');
this.application.componentManager.contextItemDidChangeInArea('editor-stack');
this.application.componentManager.contextItemDidChangeInArea('editor-editor');
}
toggleStackComponentForCurrentItem(component) {
if (component.hidden || !component.active) {
this.componentManager.setComponentHidden(component, false);
this.application.componentManager.setComponentHidden(component, false);
this.associateComponentWithCurrentNote(component);
if (!component.active) {
this.componentManager.activateComponent(component);
this.application.componentManager.activateComponent(component);
}
this.componentManager.contextItemDidChangeInArea('editor-stack');
this.application.componentManager.contextItemDidChangeInArea('editor-stack');
} else {
this.componentManager.setComponentHidden(component, true);
this.application.componentManager.setComponentHidden(component, true);
this.disassociateComponentWithCurrentNote(component);
}
}

View File

@@ -37,21 +37,29 @@ class FooterCtrl {
this.showSyncResolution = false;
this.addAppStateObserver();
this.updateOfflineStatus();
this.addAppEventObserver();
this.findErrors();
this.streamItems();
this.registerComponentHandler();
this.addRootScopeListeners();
this.godService.checkForSecurityUpdate().then((available) => {
this.securityUpdateAvailable = available;
});
this.statusManager.addStatusObserver((string) => {
this.$timeout(() => {
this.arbitraryStatusMessage = string;
});
});
application.onReady(() => {
this.application.hasPasscode().then((value) => {
this.hasPasscode = value;
});
this.godService.checkForSecurityUpdate().then((available) => {
this.securityUpdateAvailable = available;
});
this.user = this.application.getUser();
this.updateOfflineStatus();
this.addAppEventObserver();
this.findErrors();
this.streamItems();
this.registerComponentHandler();
});
}
addRootScopeListeners() {
@@ -99,7 +107,7 @@ class FooterCtrl {
}
addAppEventObserver() {
this.application.addEventHandler((eventName) => {
this.application.addEventObserver((eventName) => {
if (eventName === ApplicationEvents.LoadedLocalData) {
if(this.offline && this.application.getNoteCount() === 0) {
this.showAccountMenu = true;
@@ -203,12 +211,8 @@ class FooterCtrl {
}, 2000);
}
getUser() {
return this.application.getUser();
}
updateOfflineStatus() {
this.offline = this.application.noUser();
this.offline = this.application.noAccount();
}
openSecurityUpdate() {
@@ -232,10 +236,6 @@ class FooterCtrl {
this.showAccountMenu = false;
}
hasPasscode() {
return this.application.hasPasscode();
}
lockApp() {
this.$rootScope.lockApplication();
}
@@ -351,7 +351,7 @@ class FooterCtrl {
}
clickOutsideAccountMenu() {
if(this.application.privilegesManager.authenticationInProgress()) {
if(this.godService.authenticationInProgress()) {
return;
}
this.showAccountMenu = false;

View File

@@ -4,6 +4,7 @@ import template from '%/notes.pug';
import { ApplicationEvents, ContentTypes } from 'snjs';
import { PureCtrl } from '@Controllers';
import { AppStateEvents } from '@/state';
import { KeyboardModifiers, KeyboardKeys } from '@/services/keyboardManager';
import {
PrefKeys
} from '@/services/preferencesManager';
@@ -70,13 +71,15 @@ class NotesCtrl extends PureCtrl {
this.addAppStateObserver();
this.addAppEventObserver();
this.streamNotesAndTags();
this.reloadPreferences();
this.resetPagination();
this.registerKeyboardShortcuts();
angular.element(document).ready(() => {
this.reloadPreferences();
});
application.onReady(() => {
this.streamNotesAndTags();
this.reloadPreferences();
});
}
addAppStateObserver() {
@@ -99,7 +102,7 @@ class NotesCtrl extends PureCtrl {
if (eventName === ApplicationEvents.SignedIn) {
/** Delete dummy note if applicable */
if (this.state.selectedNote && this.state.selectedNote.dummy) {
this.application.removeItemLocally({ item: this.state.selectedNote });
this.application.deleteItemLocally({ item: this.state.selectedNote });
this.selectNote(null).then(() => {
this.reloadNotes();
});
@@ -110,10 +113,6 @@ class NotesCtrl extends PureCtrl {
*/
this.createDummyOnSynCompletionIfNoNotes = true;
}
} else if (eventName === ApplicationEvents.LoadedLocalData) {
if (this.state.notes.length === 0) {
this.createNewNote();
}
} else if (eventName === ApplicationEvents.CompletedSync) {
if (this.createDummyOnSynCompletionIfNoNotes && this.state.notes.length === 0) {
this.createDummyOnSynCompletionIfNoNotes = false;
@@ -152,7 +151,7 @@ class NotesCtrl extends PureCtrl {
async handleTagChange(tag, previousTag) {
if (this.state.selectedNote && this.state.selectedNote.dummy) {
this.application.removeItemLocally({ item: this.state.selectedNote });
this.application.deleteItemLocally({ item: this.state.selectedNote });
if (previousTag) {
_.remove(previousTag.notes, this.state.selectedNote);
}
@@ -251,7 +250,7 @@ class NotesCtrl extends PureCtrl {
}
const previousNote = this.state.selectedNote;
if (previousNote && previousNote.dummy) {
this.application.removeItemLocally({ previousNote });
this.application.deleteItemLocally({ item: previousNote });
this.removeNoteFromList(previousNote);
}
await this.setState({
@@ -502,12 +501,17 @@ class NotesCtrl extends PureCtrl {
}
}
createNewNote() {
async createNewNote() {
const selectedTag = this.appState.getSelectedTag();
if (!selectedTag) {
debugger;
throw 'Attempting to create note with no selected tag';
}
if (this.state.selectedNote && this.state.selectedNote.dummy) {
return;
}
const title = "Note" + (this.state.notes ? (" " + (this.state.notes.length + 1)) : "");
const newNote = this.application.createItem({
const newNote = await this.application.createItem({
contentType: ContentTypes.Note,
content: {
text: '',
@@ -517,7 +521,6 @@ class NotesCtrl extends PureCtrl {
newNote.client_updated_at = new Date();
newNote.dummy = true;
this.application.setItemNeedsSync({ item: newNote });
const selectedTag = this.appState.getSelectedTag();
if (!selectedTag.isSmartTag()) {
selectedTag.addItemAsRelationship(newNote);
this.application.setItemNeedsSync({ item: selectedTag });

View File

@@ -22,7 +22,7 @@ class RootCtrl extends PureCtrl {
$timeout,
application,
appState,
databaseManager,
desktopManager,
lockManager,
preferencesManager,
themeManager /** Unused below, required to load globally */,
@@ -34,7 +34,7 @@ class RootCtrl extends PureCtrl {
this.$timeout = $timeout;
this.application = application;
this.appState = appState;
this.databaseManager = databaseManager;
this.desktopManager = desktopManager;
this.lockManager = lockManager;
this.preferencesManager = preferencesManager;
this.statusManager = statusManager;
@@ -44,27 +44,34 @@ class RootCtrl extends PureCtrl {
appClass: ''
};
this.loadApplication();
this.handleAutoSignInFromParams();
this.addAppStateObserver();
this.addDragDropHandlers();
application.onReady(() => {
this.handleAutoSignInFromParams();
});
}
async loadApplication() {
await this.application.prepareForLaunch({
callbacks: {
authChallengeResponses: async (challenges) => {
console.log("Needs challenge repsonses", challenges);
if (challenges.includes(Challenges.LocalPasscode)) {
this.setState({ needsUnlock: true });
}
},
onReady: async () => {
await this.appState.setApplicationReady();
}
}
});
await this.application.launch();
this.setState({ needsUnlock: false });
await this.openDatabase();
await this.preferencesManager.initialize();
this.addSyncStatusObserver();
this.addSyncEventHandler();
this.application.componentManager.setDesktopManager(this.desktopManager);
this.preferencesManager.initialize();
// this.addSyncStatusObserver();
// this.addSyncEventHandler();
}
onUpdateAvailable() {
@@ -92,20 +99,6 @@ class RootCtrl extends PureCtrl {
});
}
async openDatabase() {
this.databaseManager.setLocked(false);
this.databaseManager.openDatabase({
onUpgradeNeeded: () => {
/**
* New database/database wiped, delete syncToken so that items
* can be refetched entirely from server
*/
this.application.syncManager.clearSyncPositionTokens();
this.application.sync();
}
});
}
// addSyncStatusObserver() {
// this.syncStatusObserver = syncManager.registerSyncStatusObserver((status) => {
// if (status.retrievedCount > 20) {

View File

@@ -21,18 +21,21 @@ class TagsPanelCtrl extends PureCtrl {
this.appState = appState;
this.preferencesManager = preferencesManager;
this.panelController = {};
this.beginStreamingItems();
this.addAppStateObserver();
this.loadPreferences();
this.registerComponentHandler();
this.state = {
smartTags: this.application.getSmartTags(),
smartTags: [],
noteCounts: {}
};
}
$onInit() {
this.selectTag(this.state.smartTags[0]);
application.onReady(() => {
this.beginStreamingItems();
const smartTags = this.application.getSmartTags();
this.setState({
smartTags: smartTags,
});
this.selectTag(smartTags[0]);
});
}
beginStreamingItems() {
@@ -41,7 +44,7 @@ class TagsPanelCtrl extends PureCtrl {
stream: async ({ items }) => {
await this.setState({
tags: this.application.getItems({ contentType: ContentTypes.Tag }),
smartTags: this.application.getItems({ contentType: ContentTypes.SmartTag }),
smartTags: this.application.getSmartTags(),
});
this.reloadNoteCounts();
if (this.state.selectedTag) {
@@ -159,11 +162,11 @@ class TagsPanelCtrl extends PureCtrl {
this.appState.setSelectedTag(tag);
}
clickedAddNewTag() {
async clickedAddNewTag() {
if (this.state.editingTag) {
return;
}
const newTag = this.application.createItem({
const newTag = await this.application.createItem({
contentType: ContentTypes.Tag
});
this.setState({

View File

@@ -0,0 +1,185 @@
export class Database {
constructor() {
this.locked = true;
}
setApplication(application) {
this.alertManager = application.alertManager;
}
displayOfflineAlert() {
var message = "There was an issue loading your offline database. This could happen for two reasons:";
message += "\n\n1. You're in a private window in your browser. We can't save your data without access to the local database. Please use a non-private window.";
message += "\n\n2. You have two windows of the app open at the same time. Please close any other app instances and reload the page.";
this.alertManager.alert({ text: message });
}
setLocked(locked) {
this.locked = locked;
}
async openDatabase({ onUpgradeNeeded } = {}) {
if (this.locked) {
throw 'Attempting to open locked database';
}
const request = window.indexedDB.open('standardnotes', 1);
return new Promise((resolve, reject) => {
request.onerror = (event) => {
if (event.target.errorCode) {
this.alertManager.alert({ text: 'Offline database issue: ' + event.target.errorCode });
} else {
this.displayOfflineAlert();
}
console.error('Offline database issue:', event);
resolve(null);
};
request.onsuccess = (event) => {
const db = event.target.result;
db.onversionchange = function (event) {
db.close();
};
db.onerror = function (errorEvent) {
console.error('Database error: ' + errorEvent.target.errorCode);
};
resolve(db);
};
request.onblocked = (event) => {
console.error('Request blocked error:', event.target.errorCode);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.onversionchange = function (event) {
db.close();
};
// Create an objectStore for this database
const objectStore = db.createObjectStore('items', { keyPath: 'uuid' });
objectStore.createIndex('uuid', 'uuid', { unique: true });
objectStore.transaction.oncomplete = function (event) {
// Ready to store values in the newly created objectStore.
if (db.version === 1 && onUpgradeNeeded) {
onUpgradeNeeded();
}
};
};
});
}
async getAllPayloads() {
const db = await this.openDatabase();
const objectStore = db.transaction('items').objectStore('items');
const payloads = [];
return new Promise((resolve) => {
objectStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
payloads.push(cursor.value);
cursor.continue();
} else {
resolve(payloads);
}
};
});
}
async savePayload(payload) {
this.savePayloads([payload]);
}
async savePayloads(payloads) {
const showGenericError = (error) => {
this.alertManager.alert({
text: `Unable to save changes locally due to an unknown system issue. Issue Code: ${error.code} Issue Name: ${error.name}.`
});
};
if (payloads.length === 0) {
return;
}
const db = await this.openDatabase();
const transaction = db.transaction('items', 'readwrite');
return new Promise(async (resolve, reject) => {
transaction.oncomplete = (event) => { };
transaction.onerror = function (event) {
console.error('Transaction error:', event.target.errorCode);
showGenericError(event.target.error);
};
transaction.onblocked = function (event) {
console.error('Transaction blocked error:', event.target.errorCode);
showGenericError(event.target.error);
};
transaction.onabort = function (event) {
console.error('Offline saving aborted:', event);
const error = event.target.error;
if (error.name === 'QuotaExceededError') {
this.alertManager.alert({ text:
'Unable to save changes locally because your device is out of space. Please free up some disk space and try again, otherwise, your data may end up in an inconsistent state.'
});
} else {
showGenericError(error);
}
reject(error);
};
const payloadObjectStore = transaction.objectStore('items');
const putPayload = async (payload) => {
return new Promise((resolve, reject) => {
const request = payloadObjectStore.put(payload);
request.onerror = (event) => {
console.error('DB put error:', event.target.error);
resolve();
};
request.onsuccess = resolve;
});
};
for (const payload of payloads) {
await putPayload(payload);
}
resolve();
});
}
async deletePayload(uuid) {
const db = await this.openDatabase();
const request = db.transaction('items', 'readwrite').objectStore('items').delete(uuid);
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
resolve();
};
request.onerror = (event) => {
// eslint-disable-next-line prefer-promise-reject-errors
reject();
};
});
}
async clearAllPayloads() {
const deleteRequest = window.indexedDB.deleteDatabase('standardnotes');
return new Promise((resolve, reject) => {
deleteRequest.onerror = function (event) {
console.error('Error deleting database.');
resolve();
};
deleteRequest.onsuccess = function (event) {
resolve();
};
deleteRequest.onblocked = function (event) {
console.error('Delete request blocked');
this.alertManager.alert({ text: 'Your browser is blocking Standard Notes from deleting the local database. Make sure there are no other open windows of this app and try again. If the issue persists, please manually delete app data to sign out.' });
resolve();
};
});
}
}

View File

@@ -2,6 +2,7 @@ import { isDesktopApplication, isNullOrUndefined } from '@/utils';
import template from '%/directives/account-menu.pug';
import { ProtectedActions } from 'snjs';
import { PureCtrl } from '@Controllers';
import { AppStateEvents } from '@/state';
import {
STRING_ACCOUNT_MENU_UNCHECK_MERGE,
STRING_SIGN_OUT_CONFIRMATION,
@@ -32,25 +33,25 @@ class AccountMenuCtrl extends PureCtrl {
$scope,
$rootScope,
$timeout,
archiveManager,
appVersion,
application,
appState,
archiveManager,
godService,
lockManager,
application
) {
super($timeout);
this.$scope = $scope;
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.appState = appState;
this.application = application;
this.archiveManager = archiveManager;
this.godService = godService;
this.lockManager = lockManager;
this.application = application;
this.state = {
appVersion: 'v' + (window.electronAppVersion || appVersion),
user: this.application.getUser(),
canAddPasscode: !this.application.isEphemeralSession(),
passcodeAutoLockOptions: this.lockManager.getAutoLockIntervalOptions(),
formData: {
mergeLocal: true,
@@ -58,12 +59,17 @@ class AccountMenuCtrl extends PureCtrl {
},
mutable: {}
};
application.onReady(() => {
this.setState({
user: this.application.getUser(),
canAddPasscode: !this.application.isEphemeralSession(),
});
this.loadHost();
this.checkForSecurityUpdate();
this.reloadAutoLockInterval();
this.loadBackupsAvailability();
});
this.syncStatus = this.application.getSyncStatus();
this.loadHost();
this.checkForSecurityUpdate();
this.reloadAutoLockInterval();
this.loadBackupsAvailability();
}
$onInit() {

View File

@@ -11,9 +11,11 @@ class PrivilegesManagementModalCtrl {
this.$element = $element;
this.$timeout = $timeout;
this.application = application;
this.hasPasscode = application.hasPasscode();
this.hasAccount = !application.noUser();
this.reloadPrivileges();
application.onReady(() => {
this.hasPasscode = application.hasPasscode();
this.hasAccount = !application.noAccount();
this.reloadPrivileges();
});
}
displayInfoForCredential(credential) {

View File

@@ -3,11 +3,8 @@ import { EncryptionIntents, ProtectedActions } from 'snjs';
export class ArchiveManager {
/* @ngInject */
constructor(lockManager, application, authManager, modelManager, privilegesManager) {
constructor(lockManager, application) {
this.lockManager = lockManager;
this.authManager = authManager;
modelManager = modelManager;
this.privilegesManager = privilegesManager;
this.application = application;
}
@@ -16,7 +13,7 @@ export class ArchiveManager {
*/
/** @public */
async downloadBackup(encrypted) {
return this.downloadBackupOfItems(modelManager.allItems, encrypted);
return this.downloadBackupOfItems(this.application.modelManager.allItems, encrypted);
}
/** @public */
@@ -39,7 +36,7 @@ export class ArchiveManager {
});
};
if (await this.privilegesManager.actionRequiresPrivilege(ProtectedActions.ManageBackups)) {
if (await this.application.privilegesManager.actionRequiresPrivilege(ProtectedActions.ManageBackups)) {
this.godService.presentPrivilegesModal(ProtectedActions.ManageBackups, () => {
run();
});

View File

@@ -1,179 +0,0 @@
export class DatabaseManager {
/* @ngInject */
constructor(alertManager) {
this.locked = true;
this.alertManager = alertManager;
}
displayOfflineAlert() {
var message = "There was an issue loading your offline database. This could happen for two reasons:";
message += "\n\n1. You're in a private window in your browser. We can't save your data without access to the local database. Please use a non-private window.";
message += "\n\n2. You have two windows of the app open at the same time. Please close any other app instances and reload the page.";
this.alertManager.alert({text: message});
}
setLocked(locked) {
this.locked = locked;
}
async openDatabase({onUpgradeNeeded} = {}) {
if(this.locked) {
return;
}
const request = window.indexedDB.open("standardnotes", 1);
return new Promise((resolve, reject) => {
request.onerror = (event) => {
if(event.target.errorCode) {
this.alertManager.alert({text: "Offline database issue: " + event.target.errorCode});
} else {
this.displayOfflineAlert();
}
console.error("Offline database issue:", event);
resolve(null);
};
request.onsuccess = (event) => {
const db = event.target.result;
db.onversionchange = function(event) {
db.close();
};
db.onerror = function(errorEvent) {
console.error("Database error: " + errorEvent.target.errorCode);
};
resolve(db);
};
request.onblocked = (event) => {
console.error("Request blocked error:", event.target.errorCode);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.onversionchange = function(event) {
db.close();
};
// Create an objectStore for this database
const objectStore = db.createObjectStore("items", { keyPath: "uuid" });
objectStore.createIndex("uuid", "uuid", { unique: true });
objectStore.transaction.oncomplete = function(event) {
// Ready to store values in the newly created objectStore.
if(db.version === 1 && onUpgradeNeeded) {
onUpgradeNeeded();
}
};
};
});
}
async getAllModels() {
const db = await this.openDatabase();
const objectStore = db.transaction("items").objectStore("items");
const items = [];
return new Promise(async (resolve, reject) => {
objectStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
items.push(cursor.value);
cursor.continue();
} else {
resolve(items);
}
};
});
}
async saveModel(item) {
this.saveModels([item]);
}
async saveModels(items) {
const showGenericError = (error) => {
this.alertManager.alert({text: `Unable to save changes locally due to an unknown system issue. Issue Code: ${error.code} Issue Name: ${error.name}.`});
};
return new Promise(async (resolve, reject) => {
if(items.length === 0) {
resolve();
return;
}
const db = await this.openDatabase();
const transaction = db.transaction("items", "readwrite");
transaction.oncomplete = (event) => {};
transaction.onerror = function(event) {
console.error("Transaction error:", event.target.errorCode);
showGenericError(event.target.error);
};
transaction.onblocked = function(event) {
console.error("Transaction blocked error:", event.target.errorCode);
showGenericError(event.target.error);
};
transaction.onabort = function(event) {
console.error("Offline saving aborted:", event);
const error = event.target.error;
if(error.name == "QuotaExceededError") {
this.alertManager.alert({text: "Unable to save changes locally because your device is out of space. Please free up some disk space and try again, otherwise, your data may end up in an inconsistent state."});
} else {
showGenericError(error);
}
reject(error);
};
const itemObjectStore = transaction.objectStore("items");
const putItem = async (item) => {
return new Promise((resolve, reject) => {
const request = itemObjectStore.put(item);
request.onerror = (event) => {
console.error("DB put error:", event.target.error);
resolve();
};
request.onsuccess = resolve;
});
};
for(const item of items) {
await putItem(item);
}
resolve();
});
}
async deleteModel(item) {
return new Promise(async (resolve, reject) => {
const db = await this.openDatabase();
const request = db.transaction("items", "readwrite").objectStore("items").delete(item.uuid);
request.onsuccess = (event) => {
resolve();
};
request.onerror = (event) => {
reject();
};
});
}
async clearAllModels() {
const deleteRequest = window.indexedDB.deleteDatabase("standardnotes");
return new Promise((resolve, reject) => {
deleteRequest.onerror = function(event) {
console.error("Error deleting database.");
resolve();
};
deleteRequest.onsuccess = function(event) {
resolve();
};
deleteRequest.onblocked = function(event) {
console.error("Delete request blocked");
this.alertManager.alert({text: "Your browser is blocking Standard Notes from deleting the local database. Make sure there are no other open windows of this app and try again. If the issue persists, please manually delete app data to sign out."});
resolve();
};
});
}
}

View File

@@ -1,7 +1,6 @@
import angular from 'angular';
export class GodService {
/* @ngInject */
constructor(
$rootScope,
@@ -14,7 +13,7 @@ export class GodService {
}
async checkForSecurityUpdate() {
if (this.offline()) {
if (this.application.noAccount()) {
return false;
}

View File

@@ -1,10 +1,10 @@
export { AlertManager } from './alertManager';
export { ArchiveManager } from './archiveManager';
export { DatabaseManager } from './databaseManager';
export { DesktopManager } from './desktopManager';
export { GodService } from './godService';
export { KeyboardManager } from './keyboardManager';
export { NativeExtManager } from './nativeExtManager';
export { LockManager } from './lockManager';
export { NativeExtManager } from './nativeExtManager';
export { PreferencesManager } from './preferencesManager';
export { StatusManager } from './statusManager';
export { ThemeManager } from './themeManager';
export { AlertManager } from './alertManager';
export { PreferencesManager } from './preferencesManager';

View File

@@ -34,10 +34,7 @@ export class LockManager {
}
async setAutoLockInterval(interval) {
return this.application.setValue(
STORAGE_KEY_AUTOLOCK_INTERVAL,
JSON.stringify(interval),
);
return this.application.setValue(STORAGE_KEY_AUTOLOCK_INTERVAL, interval);
}
async getAutoLockInterval() {
@@ -45,7 +42,7 @@ export class LockManager {
STORAGE_KEY_AUTOLOCK_INTERVAL,
);
if(interval) {
return JSON.parse(interval);
return interval;
} else {
return LOCK_INTERVAL_NONE;
}
@@ -93,10 +90,11 @@ export class LockManager {
];
}
documentVisibilityChanged(visible) {
async documentVisibilityChanged(visible) {
if(visible) {
const locked = await this.application.isPasscodeLocked();
if(
!this.isLocked() &&
!locked &&
this.lockAfterDate &&
new Date() > this.lockAfterDate
) {

View File

@@ -15,10 +15,8 @@ export class NativeExtManager {
this.resolveExtensionsManager();
this.resolveBatchManager();
appState.addObserver(async (eventName) => {
if (eventName === AppStateEvents.ApplicationReady) {
await this.initialize();
}
application.onReady(() => {
this.initialize();
});
}

View File

@@ -26,10 +26,8 @@ export class PreferencesManager {
) {
this.application = application;
this.appState = appState;
appState.addObserver(async (eventName) => {
if (eventName === AppStateEvents.ApplicationReady) {
await this.initialize();
}
application.onReady(() => {
this.initialize();
});
}

View File

@@ -1,5 +1,5 @@
import _ from 'lodash';
import { SNTheme, StorageValueModes, EncryptionIntents } from 'snjs';
import { ContentTypes, StorageValueModes, EncryptionIntents } from 'snjs';
import { AppStateEvents } from '@/state';
const CACHED_THEMES_KEY = 'cachedThemes';
@@ -16,9 +16,16 @@ export class ThemeManager {
this.desktopManager = desktopManager;
this.activeThemes = [];
this.registerObservers();
if (!desktopManager.isDesktop) {
this.activateCachedThemes();
}
application.onReady(() => {
if (!desktopManager.isDesktop) {
this.activateCachedThemes();
}
});
appState.addObserver((eventName, data) => {
if (eventName === AppStateEvents.DesktopExtsReady) {
this.activateCachedThemes();
}
});
}
async activateCachedThemes() {
@@ -30,11 +37,6 @@ export class ThemeManager {
}
registerObservers() {
this.appState.addObserver((eventName, data) => {
if (eventName === AppStateEvents.DesktopExtsReady) {
this.activateCachedThemes();
}
});
this.desktopManager.registerUpdateObserver((component) => {
// Reload theme if active
if (component.active && component.isTheme()) {
@@ -112,10 +114,9 @@ export class ThemeManager {
});
return processedPayload;
}));
const data = JSON.stringify(mapped);
return this.application.setValue(
CACHED_THEMES_KEY,
data,
mapped,
StorageValueModes.Nonwrapped
);
}
@@ -133,10 +134,15 @@ export class ThemeManager {
StorageValueModes.Nonwrapped
);
if (cachedThemes) {
const parsed = JSON.parse(cachedThemes);
return parsed.map((theme) => {
return new SNTheme(theme);
});
const themes = [];
for(const cachedTheme of cachedThemes) {
const theme = await this.application.createItem({
contentType: ContentTypes.Theme,
content: cachedTheme.content
});
themes.push(theme);
}
return themes;
} else {
return [];
}

View File

@@ -23,7 +23,6 @@ export const EventSources = {
};
export class AppState {
/* @ngInject */
constructor(
$timeout,
@@ -81,6 +80,11 @@ export class AppState {
});
}
async setApplicationReady() {
this.applicationReady = true;
await this.notifyEvent(AppStateEvents.ApplicationReady);
}
setSelectedTag(tag) {
if (this.selectedTag === tag) {
return;

View File

@@ -1,14 +1,40 @@
import { DeviceInterface } from 'snjs';
import { DeviceInterface, getGlobalScope } from 'snjs';
import { Database } from '@/database';
const KEYCHAIN_STORAGE_KEY = 'keychain';
export class WebDeviceInterface extends DeviceInterface {
constructor({
namespace,
} = {}) {
super({
namespace,
timeout: setTimeout.bind(getGlobalScope()),
interval: setInterval.bind(getGlobalScope())
});
this.createDatabase();
}
createDatabase() {
this.database = new Database();
}
setApplication(application) {
this.database.setApplication(application);
}
/**
* @value storage
*/
async getRawStorageValue(key) {
return localStorage.getItem(key);
}
async getAllRawStorageKeyValues() {
const results = [];
for(const key of Object.keys(localStorage)) {
for (const key of Object.keys(localStorage)) {
results.push({
key: key,
value: localStorage[key]
@@ -29,6 +55,78 @@ export class WebDeviceInterface extends DeviceInterface {
localStorage.clear();
}
/**
* @database
*/
async openDatabase() {
this.database.setLocked(false);
this.database.openDatabase({
onUpgradeNeeded: () => {
/**
* New database/database wiped, delete syncToken so that items
* can be refetched entirely from server
*/
/** @todo notify parent */
// this.syncManager.clearSyncPositionTokens();
// this.sync();
}
});
}
/** @private */
getDatabaseKeyPrefix() {
if (this.namespace) {
return `${this.namespace}-item-`;
} else {
return `item-`;
}
}
/** @private */
keyForPayloadId(id) {
return `${this.getDatabaseKeyPrefix()}${id}`;
}
async getAllRawDatabasePayloads() {
return this.database.getAllPayloads();
}
async saveRawDatabasePayload(payload) {
return this.database.savePayload(payload);
}
async saveRawDatabasePayloads(payloads) {
return this.database.savePayloads(payloads);
}
async removeRawDatabasePayloadWithId(id) {
return this.database.deletePayload(id);
}
async removeAllRawDatabasePayloads() {
return this.database.clearAllPayloads();
}
/** @keychian */
async getRawKeychainValue() {
const value = localStorage.getItem(KEYCHAIN_STORAGE_KEY);
if(value) {
return JSON.parse(value);
}
}
async setKeychainValue(value) {
localStorage.setItem(KEYCHAIN_STORAGE_KEY, JSON.stringify(value));
}
async clearKeychainValue() {
localStorage.removeItem(KEYCHAIN_STORAGE_KEY);
}
/**
* @actions
*/
openUrl(url) {
const win = window.open(url, '_blank');
if (win) {
@@ -36,85 +134,4 @@ export class WebDeviceInterface extends DeviceInterface {
}
}
/** @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

@@ -8,7 +8,7 @@
)
.sk-app-bar-item-column
.sk-circle.small(
ng-class="ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'neutral')"
ng-class="ctrl.error ? 'danger' : (ctrl.user ? 'info' : 'neutral')"
)
.sk-app-bar-item-column
.sk-label.title(ng-class='{red: ctrl.error}') Account
@@ -84,10 +84,10 @@
elem-ready='ctrl.initSvgForShortcut(shortcut)',
ng-attr-id='dock-svg-{{shortcut.component.uuid}}'
)
.sk-app-bar-item.border(ng-if='ctrl.hasPasscode()')
.sk-app-bar-item.border(ng-if='ctrl.hasPasscode')
#lock-item.sk-app-bar-item(
ng-click='ctrl.lockApp()',
ng-if='ctrl.hasPasscode()',
ng-if='ctrl.hasPasscode',
title='Locks application and wipes unencrypted data from memory.'
)
.sk-label

2933
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