WIP
This commit is contained in:
@@ -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)
|
||||
|
||||
55
app/assets/javascripts/application.js
Normal file
55
app/assets/javascripts/application.js
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: '&',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export class DBManager {
|
||||
export class DatabaseManager {
|
||||
/* @ngInject */
|
||||
constructor(alertManager) {
|
||||
this.locked = true;
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
143
app/assets/javascripts/services/lockManager.js
Normal file
143
app/assets/javascripts/services/lockManager.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
114
app/assets/javascripts/web_device_interface.js
Normal file
114
app/assets/javascripts/web_device_interface.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()')
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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
80773
dist/javascripts/app.js
vendored
File diff suppressed because one or more lines are too long
2
dist/javascripts/app.js.map
vendored
2
dist/javascripts/app.js.map
vendored
File diff suppressed because one or more lines are too long
2652
dist/stylesheets/app.css
vendored
2652
dist/stylesheets/app.css
vendored
File diff suppressed because one or more lines are too long
2
dist/stylesheets/app.css.map
vendored
2
dist/stylesheets/app.css.map
vendored
File diff suppressed because one or more lines are too long
@@ -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
14998
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user