WIP
This commit is contained in:
@@ -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});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user