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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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