This commit is contained in:
Mo Bitar
2020-02-08 12:11:04 -06:00
parent 13dd41b9ab
commit f6ef4a39e2
26 changed files with 10885 additions and 2044 deletions

View File

@@ -4,7 +4,7 @@
"rules": {
"standard/no-callback-literal": 0, // Disable this as we have too many callbacks relying on literals
"no-throw-literal": 0,
"no-console": "error",
// "no-console": "error",
"semi": 1
},
"env": {

View File

@@ -54,24 +54,13 @@ import {
import { trusted } from './filters';
import {
ActionsManager,
ArchiveManager,
AuthManager,
ComponentManager,
DatabaseManager,
DesktopManager,
HttpManager,
KeyboardManager,
MigrationManager,
ModelManager,
NativeExtManager,
LockManager,
PrivilegesManager,
SessionHistory,
SingletonManager,
StatusManager,
StorageManager,
SyncManager,
ThemeManager,
AlertManager,
PreferencesManager
@@ -148,23 +137,13 @@ angular
.service('application', Application)
.service('appState', AppState)
.service('preferencesManager', PreferencesManager)
.service('actionsManager', ActionsManager)
.service('archiveManager', ArchiveManager)
.service('authManager', AuthManager)
.service('componentManager', ComponentManager)
.service('databaseManager', DatabaseManager)
.service('desktopManager', DesktopManager)
.service('httpManager', HttpManager)
.service('keyboardManager', KeyboardManager)
.service('migrationManager', MigrationManager)
.service('modelManager', ModelManager)
.service('nativeExtManager', NativeExtManager)
.service('lockManager', LockManager)
.service('privilegesManager', PrivilegesManager)
.service('sessionHistory', SessionHistory)
.service('singletonManager', SingletonManager)
.service('statusManager', StatusManager)
.service('storageManager', StorageManager)
.service('syncManager', SyncManager)
.service('alertManager', AlertManager)
.service('themeManager', ThemeManager);

View File

@@ -1,11 +1,10 @@
import {
SNApplication,
SNAlertManager
Platforms,
Environments
SNAlertManager,
Platforms
} from 'snjs';
import angular from 'angular';
import { AlertManager } from '@/services/alertManager'
import { AlertManager } from '@/services/alertManager';
import { WebDeviceInterface } from '@/web_device_interface';

View File

@@ -2,37 +2,24 @@ import angular from 'angular';
import {
ApplicationEvents,
isPayloadSourceRetrieved,
CONTENT_TYPE_NOTE,
CONTENT_TYPE_TAG,
CONTENT_TYPE_COMPONENT,
ContentTypes,
ProtectedActions
} from 'snjs';
import find from 'lodash/find';
import { isDesktopApplication } from '@/utils';
import { KeyboardManager } from '@/services/keyboardManager';
import template from '%/editor.pug';
import { PureCtrl } from '@Controllers';
import {
APP_STATE_EVENT_NOTE_CHANGED,
APP_STATE_EVENT_PREFERENCES_CHANGED,
EVENT_SOURCE_SCRIPT
} from '@/state';
import { AppStateEvents, EventSources } from '@/state';
import {
STRING_DELETED_NOTE,
STRING_INVALID_NOTE,
STRING_ELLIPSES,
STRING_GENERIC_SAVE_ERROR,
STRING_DELETE_PLACEHOLDER_ATTEMPT,
STRING_DELETE_LOCKED_ATTEMPT,
StringDeleteNote,
StringEmptyTrash
} from '@/strings';
import {
PREF_EDITOR_WIDTH,
PREF_EDITOR_LEFT,
PREF_EDITOR_MONOSPACE_ENABLED,
PREF_EDITOR_SPELLCHECK,
PREF_EDITOR_RESIZERS_ENABLED
} from '@/services/preferencesManager';
import { PrefKeys } from '@/services/preferencesManager';
const NOTE_PREVIEW_CHAR_LIMIT = 80;
const MINIMUM_STATUS_DURATION = 400;
@@ -40,19 +27,23 @@ const SAVE_TIMEOUT_DEBOUNCE = 350;
const SAVE_TIMEOUT_NO_DEBOUNCE = 100;
const EDITOR_DEBOUNCE = 200;
const APP_DATA_KEY_PINNED = 'pinned';
const APP_DATA_KEY_LOCKED = 'locked';
const APP_DATA_KEY_ARCHIVED = 'archived';
const APP_DATA_KEY_PREFERS_PLAIN_EDITOR = 'prefersPlainEditor';
const ELEMENT_ID_NOTE_TEXT_EDITOR = 'note-text-editor';
const ELEMENT_ID_NOTE_TITLE_EDITOR = 'note-title-editor';
const ELEMENT_ID_EDITOR_CONTENT = 'editor-content';
const ELEMENT_ID_NOTE_TAGS_COMPONENT_CONTAINER = 'note-tags-component-container';
const DESKTOP_MONOSPACE_FAMILY = `Menlo,Consolas,'DejaVu Sans Mono',monospace`;
const WEB_MONOSPACE_FAMILY = `monospace`;
const SANS_SERIF_FAMILY = `inherit`;
const AppDataKeys = {
Pinned: 'pinned',
Locked: 'locked',
Archived: 'archived',
PrefersPlainEditor: 'prefersPlainEditor'
};
const ElementIds = {
NoteTextEditor: 'note-text-editor',
NoteTitleEditor: 'note-title-editor',
EditorContent: 'editor-content',
NoteTagsComponentContainer: 'note-tags-component-container'
};
const Fonts = {
DesktopMonospaceFamily: `Menlo,Consolas,'DejaVu Sans Mono',monospace`,
WebMonospaceFamily: `monospace`,
SansSerifFamily: `inherit`
};
class EditorCtrl extends PureCtrl {
/* @ngInject */
@@ -61,17 +52,14 @@ class EditorCtrl extends PureCtrl {
$rootScope,
appState,
application,
actionsManager,
desktopManager,
keyboardManager,
preferencesManager,
sessionHistory /** Unused below, required to load globally */
) {
super($timeout);
this.$rootScope = $rootScope;
this.application = application;
this.appState = appState;
this.actionsManager = actionsManager;
this.desktopManager = desktopManager;
this.keyboardManager = keyboardManager;
this.preferencesManager = preferencesManager;
@@ -97,19 +85,19 @@ class EditorCtrl extends PureCtrl {
this.registerKeyboardShortcuts();
/** Used by .pug template */
this.prefKeyMonospace = PREF_EDITOR_MONOSPACE_ENABLED;
this.prefKeySpellcheck = PREF_EDITOR_SPELLCHECK;
this.prefKeyMarginResizers = PREF_EDITOR_RESIZERS_ENABLED;
this.prefKeyMonospace = PrefKeys.EditorMonospaceEnabled;
this.prefKeySpellcheck = PrefKeys.EditorSpellcheck;
this.prefKeyMarginResizers = PrefKeys.EditorResizersEnabled;
}
addAppStateObserver() {
this.appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_NOTE_CHANGED) {
if (eventName === AppStateEvents.NoteChanged) {
this.handleNoteSelectionChange(
this.appState.getSelectedNote(),
data.previousNote
);
} else if (eventName === APP_STATE_EVENT_PREFERENCES_CHANGED) {
} else if (eventName === AppStateEvents.PreferencesChanged) {
this.loadPreferences();
}
});
@@ -117,7 +105,7 @@ class EditorCtrl extends PureCtrl {
streamItems() {
this.application.streamItems({
contentType: CONTENT_TYPE_NOTE,
contentType: ContentTypes.Note,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
@@ -139,7 +127,7 @@ class EditorCtrl extends PureCtrl {
});
this.application.streamItems({
contentType: CONTENT_TYPE_TAG,
contentType: ContentTypes.Tag,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
@@ -158,7 +146,7 @@ class EditorCtrl extends PureCtrl {
});
this.application.streamItems({
contentType: CONTENT_TYPE_COMPONENT,
contentType: ContentTypes.Component,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
@@ -267,7 +255,7 @@ class EditorCtrl extends PureCtrl {
addSyncStatusObserver() {
/** @todo */
// this.syncStatusObserver = this.syncManager.
// this.syncStatusObserver = syncManager.
// registerSyncStatusObserver((status) => {
// if (status.localError) {
// this.$timeout(() => {
@@ -321,11 +309,11 @@ class EditorCtrl extends PureCtrl {
}
if (editor) {
const prefersPlain = this.state.note.getAppDataItem(
APP_DATA_KEY_PREFERS_PLAIN_EDITOR
AppDataKeys.PrefersPlainEditor
) === true;
if (prefersPlain) {
this.state.note.setAppDataItem(
APP_DATA_KEY_PREFERS_PLAIN_EDITOR,
AppDataKeys.PrefersPlainEditor,
false
);
this.application.setItemNeedsSync({ item: this.state.note });
@@ -333,9 +321,9 @@ class EditorCtrl extends PureCtrl {
this.associateComponentWithCurrentNote(editor);
} else {
/** Note prefers plain editor */
if (!this.state.note.getAppDataItem(APP_DATA_KEY_PREFERS_PLAIN_EDITOR)) {
if (!this.state.note.getAppDataItem(AppDataKeys.PrefersPlainEditor)) {
this.state.note.setAppDataItem(
APP_DATA_KEY_PREFERS_PLAIN_EDITOR,
AppDataKeys.PrefersPlainEditor,
true
);
this.application.setItemNeedsSync({ item: this.state.note });
@@ -356,7 +344,7 @@ class EditorCtrl extends PureCtrl {
}
hasAvailableExtensions() {
return this.actionsManager.extensionsInContextOfItem(this.state.note).length > 0;
return this.application.actionsManager.extensionsInContextOfItem(this.state.note).length > 0;
}
performFirefoxPinnedTabFix() {
@@ -494,15 +482,15 @@ class EditorCtrl extends PureCtrl {
}
focusEditor() {
const element = document.getElementById(ELEMENT_ID_NOTE_TEXT_EDITOR);
const element = document.getElementById(ElementIds.NoteTextEditor);
if (element) {
this.lastEditorFocusEventSource = EVENT_SOURCE_SCRIPT;
this.lastEditorFocusEventSource = EventSources.Script;
element.focus();
}
}
focusTitle() {
document.getElementById(ELEMENT_ID_NOTE_TITLE_EDITOR).focus();
document.getElementById(ElementIds.NoteTitleEditor).focus();
}
clickedTextArea() {
@@ -627,7 +615,7 @@ class EditorCtrl extends PureCtrl {
togglePin() {
this.state.note.setAppDataItem(
APP_DATA_KEY_PINNED,
AppDataKeys.Pinned,
!this.state.note.pinned
);
this.saveNote({
@@ -638,7 +626,7 @@ class EditorCtrl extends PureCtrl {
toggleLockNote() {
this.state.note.setAppDataItem(
APP_DATA_KEY_LOCKED,
AppDataKeys.Locked,
!this.state.note.locked
);
this.saveNote({
@@ -674,7 +662,7 @@ class EditorCtrl extends PureCtrl {
toggleArchiveNote() {
this.state.note.setAppDataItem(
APP_DATA_KEY_ARCHIVED,
AppDataKeys.Archived,
!this.state.note.archived
);
this.saveNote({
@@ -734,7 +722,7 @@ class EditorCtrl extends PureCtrl {
this.application.setItemsNeedsSync({ items: toRemove });
const tags = [];
for (const tagString of strings) {
const existingRelationship = _.find(
const existingRelationship = find(
this.state.note.tags,
{ title: tagString }
);
@@ -754,13 +742,13 @@ class EditorCtrl extends PureCtrl {
onPanelResizeFinish = (width, left, isMaxWidth) => {
if (isMaxWidth) {
this.preferencesManager.setUserPrefValue(
PREF_EDITOR_WIDTH,
PrefKeys.EditorWidth,
null
);
} else {
if (width !== undefined && width !== null) {
this.preferencesManager.setUserPrefValue(
PREF_EDITOR_WIDTH,
PrefKeys.EditorWidth,
width
);
this.leftResizeControl.setWidth(width);
@@ -768,7 +756,7 @@ class EditorCtrl extends PureCtrl {
}
if (left !== undefined && left !== null) {
this.preferencesManager.setUserPrefValue(
PREF_EDITOR_LEFT,
PrefKeys.EditorLeft,
left
);
this.rightResizeControl.setLeft(left);
@@ -778,15 +766,15 @@ class EditorCtrl extends PureCtrl {
loadPreferences() {
const monospaceEnabled = this.preferencesManager.getValue(
PREF_EDITOR_MONOSPACE_ENABLED,
PrefKeys.EditorMonospaceEnabled,
true
);
const spellcheck = this.preferencesManager.getValue(
PREF_EDITOR_SPELLCHECK,
PrefKeys.EditorSpellcheck,
true
);
const marginResizersEnabled = this.preferencesManager.getValue(
PREF_EDITOR_RESIZERS_ENABLED,
PrefKeys.EditorResizersEnabled,
true
);
this.setState({
@@ -795,7 +783,7 @@ class EditorCtrl extends PureCtrl {
marginResizersEnabled
});
if (!document.getElementById(ELEMENT_ID_EDITOR_CONTENT)) {
if (!document.getElementById(ElementIds.EditorContent)) {
/** Elements have not yet loaded due to ng-if around wrapper */
return;
}
@@ -804,7 +792,7 @@ class EditorCtrl extends PureCtrl {
if (this.state.marginResizersEnabled) {
const width = this.preferencesManager.getValue(
PREF_EDITOR_WIDTH,
PrefKeys.EditorWidth,
null
);
if (width != null) {
@@ -812,7 +800,7 @@ class EditorCtrl extends PureCtrl {
this.rightResizeControl.setWidth(width);
}
const left = this.preferencesManager.getValue(
PREF_EDITOR_LEFT,
PrefKeys.EditorLeft,
null
);
if (left != null) {
@@ -824,19 +812,19 @@ class EditorCtrl extends PureCtrl {
reloadFont() {
const editor = document.getElementById(
ELEMENT_ID_NOTE_TEXT_EDITOR
ElementIds.NoteTextEditor
);
if (!editor) {
return;
}
if (this.state.monospaceEnabled) {
if (this.state.isDesktop) {
editor.style.fontFamily = DESKTOP_MONOSPACE_FAMILY;
editor.style.fontFamily = Fonts.DesktopMonospaceFamily;
} else {
editor.style.fontFamily = WEB_MONOSPACE_FAMILY;
editor.style.fontFamily = Fonts.WebMonospaceFamily;
}
} else {
editor.style.fontFamily = SANS_SERIF_FAMILY;
editor.style.fontFamily = Fonts.SansSerifFamily;
}
}
@@ -849,7 +837,7 @@ class EditorCtrl extends PureCtrl {
);
this.reloadFont();
if (key === PREF_EDITOR_SPELLCHECK) {
if (key === PrefKeys.EditorSpellcheck) {
/** Allows textarea to reload */
await this.setState({
noteReady: false
@@ -858,7 +846,7 @@ class EditorCtrl extends PureCtrl {
noteReady: true
});
this.reloadFont();
} else if (key === PREF_EDITOR_RESIZERS_ENABLED && this[key] === true) {
} else if (key === PrefKeys.EditorResizersEnabled && this[key] === true) {
this.$timeout(() => {
this.leftResizeControl.flash();
this.rightResizeControl.flash();
@@ -956,7 +944,7 @@ class EditorCtrl extends PureCtrl {
if (data.type === 'container') {
if (component.area === 'note-tags') {
const container = document.getElementById(
ELEMENT_ID_NOTE_TAGS_COMPONENT_CONTAINER
ElementIds.NoteTagsComponentContainer
);
setSize(container, data);
}
@@ -1055,7 +1043,7 @@ class EditorCtrl extends PureCtrl {
registerKeyboardShortcuts() {
this.altKeyObserver = this.keyboardManager.addKeyObserver({
modifiers: [
KeyboardManager.KeyModifierAlt
KeyboardModifiers.Alt
],
onKeyDown: () => {
this.setState({
@@ -1070,23 +1058,23 @@ class EditorCtrl extends PureCtrl {
});
this.trashKeyObserver = this.keyboardManager.addKeyObserver({
key: KeyboardManager.KeyBackspace,
key: KeyboardKeys.Backspace,
notElementIds: [
ELEMENT_ID_NOTE_TEXT_EDITOR,
ELEMENT_ID_NOTE_TITLE_EDITOR
ElementIds.NoteTextEditor,
ElementIds.NoteTitleEditor
],
modifiers: [KeyboardManager.KeyModifierMeta],
modifiers: [KeyboardModifiers.Meta],
onKeyDown: () => {
this.deleteNote();
},
});
this.deleteKeyObserver = this.keyboardManager.addKeyObserver({
key: KeyboardManager.KeyBackspace,
key: KeyboardKeys.Backspace,
modifiers: [
KeyboardManager.KeyModifierMeta,
KeyboardManager.KeyModifierShift,
KeyboardManager.KeyModifierAlt
KeyboardModifiers.Meta,
KeyboardModifiers.Shift,
KeyboardModifiers.Alt
],
onKeyDown: (event) => {
event.preventDefault();
@@ -1107,11 +1095,11 @@ class EditorCtrl extends PureCtrl {
* not fired.
*/
const editor = document.getElementById(
ELEMENT_ID_NOTE_TEXT_EDITOR
ElementIds.NoteTextEditor
);
this.tabObserver = this.keyboardManager.addKeyObserver({
element: editor,
key: KeyboardManager.KeyTab,
key: KeyboardKeys.Tab,
onKeyDown: (event) => {
if (this.state.note.locked || event.shiftKey) {
return;

View File

@@ -2,15 +2,11 @@ import { dateToLocalizedString } from '@/utils';
import {
ApplicationEvents,
TIMING_STRATEGY_FORCE_SPAWN_NEW,
ProtectedActions
ProtectedActions,
ContentTypes
} from 'snjs';
import template from '%/footer.pug';
import {
APP_STATE_EVENT_EDITOR_FOCUSED,
APP_STATE_EVENT_BEGAN_BACKUP_DOWNLOAD,
APP_STATE_EVENT_ENDED_BACKUP_DOWNLOAD,
EVENT_SOURCE_USER_INTERACTION
} from '@/state';
import { AppStateEvents, EventSources } from '@/state';
import {
STRING_GENERIC_SYNC_ERROR,
STRING_NEW_UPDATE_READY
@@ -74,16 +70,16 @@ class FooterCtrl {
addAppStateObserver() {
this.appState.addObserver((eventName, data) => {
if(eventName === APP_STATE_EVENT_EDITOR_FOCUSED) {
if (data.eventSource === EVENT_SOURCE_USER_INTERACTION) {
if(eventName === AppStateEvents.EditorFocused) {
if (data.eventSource === EventSources.UserInteraction) {
this.closeAllRooms();
this.closeAccountMenu();
}
} else if(eventName === APP_STATE_EVENT_BEGAN_BACKUP_DOWNLOAD) {
} else if(eventName === AppStateEvents.BeganBackupDownload) {
this.backupStatus = this.statusManager.addStatusFromString(
"Saving local backup..."
);
} else if(eventName === APP_STATE_EVENT_ENDED_BACKUP_DOWNLOAD) {
} else if(eventName === AppStateEvents.EndedBackupDownload) {
if(data.success) {
this.backupStatus = this.statusManager.replaceStatusWithString(
this.backupStatus,
@@ -125,10 +121,10 @@ class FooterCtrl {
streamItems() {
this.application.streamItems({
contentType: CONTENT_TYPE_COMPONENT,
contentType: ContentTypes.Component,
stream: async () => {
this.rooms = this.application.getItems({
contentType: CONTENT_TYPE_COMPONENT
contentType: ContentTypes.Component
}).filter((candidate) => {
return candidate.area === 'rooms' && !candidate.deleted;
});
@@ -143,7 +139,7 @@ class FooterCtrl {
contentType: 'SN|Theme',
stream: async () => {
const themes = this.application.getDisplayableItems({
contentType: CONTENT_TYPE_THEME
contentType: ContentTypes.Theme
}).filter((candidate) => {
return (
!candidate.deleted &&
@@ -159,7 +155,7 @@ class FooterCtrl {
this.reloadDockShortcuts();
}
}
);
});
}
registerComponentHandler() {

View File

@@ -1,21 +1,16 @@
import template from '%/lock-screen.pug';
import {
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '@/state';
import { AppStateEvents } from '@/state';
const ELEMENT_ID_PASSCODE_INPUT = 'passcode-input';
class LockScreenCtrl {
/* @ngInject */
constructor(
$scope,
alertManager,
application,
appState
) {
this.$scope = $scope;
this.alertManager = alertManager;
this.application = application;
this.appState = appState;
this.formData = {};
@@ -37,7 +32,7 @@ class LockScreenCtrl {
addVisibilityObserver() {
this.unregisterObserver = this.appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
if (eventName === AppStateEvents.WindowDidFocus) {
const input = this.passcodeInput;
if(input) {
input.focus();
@@ -56,7 +51,7 @@ class LockScreenCtrl {
this.passcodeInput.blur();
const success = await this.onValue()(this.formData.passcode);
if(!success) {
this.alertManager.alert({
this.application.alertManager.alert({
text: "Invalid passcode. Please try again.",
onClose: () => {
this.passcodeInput.focus();
@@ -70,7 +65,7 @@ class LockScreenCtrl {
}
beginDeleteData() {
this.alertManager.confirm({
this.application.alertManager.confirm({
text: "Are you sure you want to clear all local data?",
destructive: true,
onConfirm: async () => {

View File

@@ -1,24 +1,11 @@
import _ from 'lodash';
import angular from 'angular';
import template from '%/notes.pug';
import { ApplicationEvents, CONTENT_TYPE_NOTE, CONTENT_TYPE_TAG } from 'snjs';
import { KeyboardManager } from '@/services/keyboardManager';
import { ApplicationEvents, ContentTypes } from 'snjs';
import { PureCtrl } from '@Controllers';
import { AppStateEvents } from '@/state';
import {
APP_STATE_EVENT_NOTE_CHANGED,
APP_STATE_EVENT_TAG_CHANGED,
APP_STATE_EVENT_PREFERENCES_CHANGED,
APP_STATE_EVENT_EDITOR_FOCUSED
} from '@/state';
import {
PREF_NOTES_PANEL_WIDTH,
PREF_SORT_NOTES_BY,
PREF_SORT_NOTES_REVERSE,
PREF_NOTES_SHOW_ARCHIVED,
PREF_NOTES_HIDE_PINNED,
PREF_NOTES_HIDE_NOTE_PREVIEW,
PREF_NOTES_HIDE_DATE,
PREF_NOTES_HIDE_TAGS
PrefKeys
} from '@/services/preferencesManager';
import {
PANEL_NAME_NOTES
@@ -37,8 +24,6 @@ import {
*/
const MIN_NOTE_CELL_HEIGHT = 51.0;
const DEFAULT_LIST_NUM_NOTES = 20;
const ELEMENT_ID_SEARCH_BAR = 'search-bar';
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
@@ -96,14 +81,14 @@ class NotesCtrl extends PureCtrl {
addAppStateObserver() {
this.appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_TAG_CHANGED) {
if (eventName === AppStateEvents.TagChanged) {
this.handleTagChange(this.appState.getSelectedTag(), data.previousTag);
} else if (eventName === APP_STATE_EVENT_NOTE_CHANGED) {
} else if (eventName === AppStateEvents.NoteChanged) {
this.handleNoteSelection(this.appState.getSelectedNote());
} else if (eventName === APP_STATE_EVENT_PREFERENCES_CHANGED) {
} else if (eventName === AppStateEvents.PreferencesChanged) {
this.reloadPreferences();
this.reloadNotes();
} else if (eventName === APP_STATE_EVENT_EDITOR_FOCUSED) {
} else if (eventName === AppStateEvents.EditorFocused) {
this.setShowMenuFalse();
}
});
@@ -129,7 +114,7 @@ class NotesCtrl extends PureCtrl {
if (this.state.notes.length === 0) {
this.createNewNote();
}
} else if(eventName === ApplicationEvents.CompletedSync) {
} else if (eventName === ApplicationEvents.CompletedSync) {
if (this.createDummyOnSynCompletionIfNoNotes && this.state.notes.length === 0) {
this.createDummyOnSynCompletionIfNoNotes = false;
this.createNewNote();
@@ -140,7 +125,7 @@ class NotesCtrl extends PureCtrl {
streamNotesAndTags() {
this.application.streamItems({
contentType: [CONTENT_TYPE_NOTE, CONTENT_TYPE_TAG],
contentType: [ContentTypes.Note, ContentTypes.Tag],
stream: async ({ items }) => {
await this.reloadNotes();
const selectedNote = this.state.selectedNote;
@@ -155,7 +140,7 @@ class NotesCtrl extends PureCtrl {
}
/** Note has changed values, reset its flags */
const notes = items.filter((item) => item.content_type === CONTENT_TYPE_NOTE);
const notes = items.filter((item) => item.content_type === ContentTypes.Note);
for (const note of notes) {
this.loadFlagsForNote(note);
note.cachedCreatedAtString = note.createdAtString();
@@ -167,7 +152,7 @@ class NotesCtrl extends PureCtrl {
async handleTagChange(tag, previousTag) {
if (this.state.selectedNote && this.state.selectedNote.dummy) {
this.application.removeItemLocally({item: this.state.selectedNote});
this.application.removeItemLocally({ item: this.state.selectedNote });
if (previousTag) {
_.remove(previousTag.notes, this.state.selectedNote);
}
@@ -266,7 +251,7 @@ class NotesCtrl extends PureCtrl {
}
const previousNote = this.state.selectedNote;
if (previousNote && previousNote.dummy) {
this.application.removeItemLocally({previousNote});
this.application.removeItemLocally({ previousNote });
this.removeNoteFromList(previousNote);
}
await this.setState({
@@ -279,7 +264,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.application.saveItem({item: note});
this.application.saveItem({ item: note });
}
if (this.isFiltering()) {
this.desktopManager.searchText(this.state.noteFilter.text);
@@ -290,7 +275,7 @@ class NotesCtrl extends PureCtrl {
const viewOptions = {};
const prevSortValue = this.state.sortBy;
let sortBy = this.preferencesManager.getValue(
PREF_SORT_NOTES_BY,
PrefKeys.SortNotesBy,
SORT_KEY_CREATED_AT
);
if (sortBy === SORT_KEY_UPDATED_AT) {
@@ -299,27 +284,27 @@ class NotesCtrl extends PureCtrl {
}
viewOptions.sortBy = sortBy;
viewOptions.sortReverse = this.preferencesManager.getValue(
PREF_SORT_NOTES_REVERSE,
PrefKeys.SortNotesReverse,
false
);
viewOptions.showArchived = this.preferencesManager.getValue(
PREF_NOTES_SHOW_ARCHIVED,
PrefKeys.NotesShowArchived,
false
);
viewOptions.hidePinned = this.preferencesManager.getValue(
PREF_NOTES_HIDE_PINNED,
PrefKeys.NotesHidePinned,
false
);
viewOptions.hideNotePreview = this.preferencesManager.getValue(
PREF_NOTES_HIDE_NOTE_PREVIEW,
PrefKeys.NotesHideNotePreview,
false
);
viewOptions.hideDate = this.preferencesManager.getValue(
PREF_NOTES_HIDE_DATE,
PrefKeys.NotesHideDate,
false
);
viewOptions.hideTags = this.preferencesManager.getValue(
PREF_NOTES_HIDE_TAGS,
PrefKeys.NotesHideTags,
false
);
this.setState({
@@ -329,7 +314,7 @@ class NotesCtrl extends PureCtrl {
this.selectFirstNote();
}
const width = this.preferencesManager.getValue(
PREF_NOTES_PANEL_WIDTH
PrefKeys.NotesPanelWidth
);
if (width) {
this.panelController.setWidth(width);
@@ -344,7 +329,7 @@ class NotesCtrl extends PureCtrl {
onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => {
this.preferencesManager.setUserPrefValue(
PREF_NOTES_PANEL_WIDTH,
PrefKeys.NotesPanelWidth,
newWidth
);
this.preferencesManager.syncUserPreferences();
@@ -523,7 +508,7 @@ class NotesCtrl extends PureCtrl {
}
const title = "Note" + (this.state.notes ? (" " + (this.state.notes.length + 1)) : "");
const newNote = this.application.createItem({
contentType: CONTENT_TYPE_NOTE,
contentType: ContentTypes.Note,
content: {
text: '',
title: title
@@ -531,7 +516,7 @@ class NotesCtrl extends PureCtrl {
});
newNote.client_updated_at = new Date();
newNote.dummy = true;
this.application.setItemNeedsSync({item: newNote});
this.application.setItemNeedsSync({ item: newNote });
const selectedTag = this.appState.getSelectedTag();
if (!selectedTag.isSmartTag()) {
selectedTag.addItemAsRelationship(newNote);
@@ -605,7 +590,7 @@ class NotesCtrl extends PureCtrl {
toggleReverseSort() {
this.selectedMenuItem();
this.preferencesManager.setUserPrefValue(
PREF_SORT_NOTES_REVERSE,
PrefKeys.SortNotesReverse,
!this.state.sortReverse
);
this.preferencesManager.syncUserPreferences();
@@ -613,7 +598,7 @@ class NotesCtrl extends PureCtrl {
setSortBy(type) {
this.preferencesManager.setUserPrefValue(
PREF_SORT_NOTES_BY,
PrefKeys.SortNotesBy,
type
);
this.preferencesManager.syncUserPreferences();
@@ -649,8 +634,8 @@ class NotesCtrl extends PureCtrl {
this.newNoteKeyObserver = this.keyboardManager.addKeyObserver({
key: 'n',
modifiers: [
KeyboardManager.KeyModifierMeta,
KeyboardManager.KeyModifierCtrl
KeyboardModifiers.Meta,
KeyboardModifiers.Ctrl
],
onKeyDown: (event) => {
event.preventDefault();
@@ -659,7 +644,7 @@ class NotesCtrl extends PureCtrl {
});
this.nextNoteKeyObserver = this.keyboardManager.addKeyObserver({
key: KeyboardManager.KeyDown,
key: KeyboardKeys.Down,
elements: [
document.body,
this.getSearchBar()
@@ -674,7 +659,7 @@ class NotesCtrl extends PureCtrl {
});
this.nextNoteKeyObserver = this.keyboardManager.addKeyObserver({
key: KeyboardManager.KeyUp,
key: KeyboardKeys.Up,
element: document.body,
onKeyDown: (event) => {
this.selectPreviousNote();
@@ -684,8 +669,8 @@ class NotesCtrl extends PureCtrl {
this.searchKeyObserver = this.keyboardManager.addKeyObserver({
key: "f",
modifiers: [
KeyboardManager.KeyModifierMeta,
KeyboardManager.KeyModifierShift
KeyboardModifiers.Meta,
KeyboardModifiers.Shift
],
onKeyDown: (event) => {
const searchBar = this.getSearchBar();

View File

@@ -2,10 +2,7 @@ import _ from 'lodash';
import { Challenges } from 'snjs';
import { getPlatformString } from '@/utils';
import template from '%/root.pug';
import {
APP_STATE_EVENT_PANEL_RESIZED,
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '@/state';
import { AppStateEvents } from '@/state';
import {
PANEL_NAME_NOTES,
PANEL_NAME_TAGS
@@ -15,6 +12,7 @@ import {
STRING_DEFAULT_FILE_ERROR,
StringSyncException
} from '@/strings';
import { PureCtrl } from './abstract/pure_ctrl';
class RootCtrl extends PureCtrl {
/* @ngInject */
@@ -45,7 +43,6 @@ class RootCtrl extends PureCtrl {
needsUnlock: false,
appClass: ''
};
this.loadApplication();
this.handleAutoSignInFromParams();
this.addAppStateObserver();
@@ -53,7 +50,7 @@ class RootCtrl extends PureCtrl {
}
async loadApplication() {
this.application.prepareForLaunch({
await this.application.prepareForLaunch({
callbacks: {
authChallengeResponses: async (challenges) => {
if (challenges.includes(Challenges.LocalPasscode)) {
@@ -65,7 +62,7 @@ class RootCtrl extends PureCtrl {
await this.application.launch();
this.setState({ needsUnlock: false });
await this.openDatabase();
this.preferencesManager.load();
await this.preferencesManager.initialize();
this.addSyncStatusObserver();
this.addSyncEventHandler();
}
@@ -76,7 +73,7 @@ class RootCtrl extends PureCtrl {
addAppStateObserver() {
this.appState.addObserver(async (eventName, data) => {
if (eventName === APP_STATE_EVENT_PANEL_RESIZED) {
if (eventName === AppStateEvents.PanelResized) {
if (data.panel === PANEL_NAME_NOTES) {
this.notesCollapsed = data.collapsed;
}
@@ -87,7 +84,7 @@ class RootCtrl extends PureCtrl {
if (this.notesCollapsed) { appClass += "collapsed-notes"; }
if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.setState({ appClass });
} else if (eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
} else if (eventName === AppStateEvents.WindowDidFocus) {
if (!(await this.application.isPasscodeLocked())) {
this.application.sync();
}
@@ -110,7 +107,7 @@ class RootCtrl extends PureCtrl {
}
// addSyncStatusObserver() {
// this.syncStatusObserver = this.syncManager.registerSyncStatusObserver((status) => {
// this.syncStatusObserver = syncManager.registerSyncStatusObserver((status) => {
// if (status.retrievedCount > 20) {
// const text = `Downloading ${status.retrievedCount} items. Keep app open.`;
// this.syncStatus = this.statusManager.replaceStatusWithString(
@@ -143,7 +140,7 @@ class RootCtrl extends PureCtrl {
// addSyncEventHandler() {
// let lastShownDate;
// this.syncManager.addEventHandler((syncEvent, data) => {
// syncManager.addEventHandler((syncEvent, data) => {
// this.$rootScope.$broadcast(
// syncEvent,
// data || {}
@@ -183,14 +180,14 @@ class RootCtrl extends PureCtrl {
// status
// );
// };
// this.syncManager.loadLocalItems({ incrementalCallback }).then(() => {
// syncManager.loadLocalItems({ incrementalCallback }).then(() => {
// this.$timeout(() => {
// this.$rootScope.$broadcast("initial-data-loaded");
// this.syncStatus = this.statusManager.replaceStatusWithString(
// this.syncStatus,
// "Syncing..."
// );
// this.syncManager.sync({
// syncManager.sync({
// checkIntegrity: true
// }).then(() => {
// this.syncStatus = this.statusManager.removeStatus(this.syncStatus);

View File

@@ -1,11 +1,8 @@
import { SNNote, SNSmartTag, CONTENT_TYPE_TAG, CONTENT_TYPE_SMART_TAG } from 'snjs';
import { SNNote, SNSmartTag, ContentTypes } from 'snjs';
import template from '%/tags.pug';
import {
APP_STATE_EVENT_PREFERENCES_CHANGED,
APP_STATE_EVENT_TAG_CHANGED
} from '@/state';
import { AppStateEvents } from '@/state';
import { PANEL_NAME_TAGS } from '@/controllers/constants';
import { PREF_TAGS_PANEL_WIDTH } from '@/services/preferencesManager';
import { PrefKeys } from '@/services/preferencesManager';
import { STRING_DELETE_TAG } from '@/strings';
import { PureCtrl } from '@Controllers';
@@ -40,11 +37,11 @@ class TagsPanelCtrl extends PureCtrl {
beginStreamingItems() {
this.application.streamItems({
contentType: CONTENT_TYPE_TAG,
stream: async ({items}) => {
contentType: ContentTypes.Tag,
stream: async ({ items }) => {
await this.setState({
tags: this.application.getItems({contentType: CONTENT_TYPE_TAG}),
smartTags: this.application.getItems({ contentType: CONTENT_TYPE_SMART_TAG }),
tags: this.application.getItems({ contentType: ContentTypes.Tag }),
smartTags: this.application.getItems({ contentType: ContentTypes.SmartTag }),
});
this.reloadNoteCounts();
if (this.state.selectedTag) {
@@ -62,9 +59,9 @@ class TagsPanelCtrl extends PureCtrl {
addAppStateObserver() {
this.appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_PREFERENCES_CHANGED) {
if (eventName === AppStateEvents.PreferencesChanged) {
this.loadPreferences();
} else if (eventName === APP_STATE_EVENT_TAG_CHANGED) {
} else if (eventName === AppStateEvents.TagChanged) {
this.setState({
selectedTag: this.appState.getSelectedTag()
});
@@ -93,7 +90,7 @@ class TagsPanelCtrl extends PureCtrl {
}
loadPreferences() {
const width = this.preferencesManager.getValue(PREF_TAGS_PANEL_WIDTH);
const width = this.preferencesManager.getValue(PrefKeys.TagsPanelWidth);
if (width) {
this.panelController.setWidth(width);
if (this.panelController.isCollapsed()) {
@@ -107,7 +104,7 @@ class TagsPanelCtrl extends PureCtrl {
onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => {
this.preferencesManager.setUserPrefValue(
PREF_TAGS_PANEL_WIDTH,
PrefKeys.TagsPanelWidth,
newWidth,
true
);
@@ -130,7 +127,7 @@ class TagsPanelCtrl extends PureCtrl {
actionHandler: (component, action, data) => {
if (action === 'select-item') {
if (data.item.content_type === 'Tag') {
const tag = this.application.findItem({uuid: data.item.uuid});
const tag = this.application.findItem({ uuid: data.item.uuid });
if (tag) {
this.selectTag(tag);
}
@@ -157,7 +154,7 @@ class TagsPanelCtrl extends PureCtrl {
}
if (tag.content.conflict_of) {
tag.content.conflict_of = null;
this.application.saveItem({item: tag});
this.application.saveItem({ item: tag });
}
this.appState.setSelectedTag(tag);
}
@@ -167,7 +164,7 @@ class TagsPanelCtrl extends PureCtrl {
return;
}
const newTag = this.application.createItem({
contentType: CONTENT_TYPE_TAG
contentType: ContentTypes.Tag
});
this.setState({
previousTag: this.state.selectedTag,
@@ -177,7 +174,7 @@ class TagsPanelCtrl extends PureCtrl {
});
/** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.insertItems({items: [newTag]});
this.application.modelManager.insertItems({ items: [newTag] });
}
tagTitleDidChange(tag) {
@@ -188,14 +185,14 @@ class TagsPanelCtrl extends PureCtrl {
async saveTag($event, tag) {
$event.target.blur();
await this.setState({
editingTag: null
await this.setState({
editingTag: null
});
if (!tag.title || tag.title.length === 0) {
if (this.state.editingTag) {
tag.title = this.editingOriginalName;
this.editingOriginalName = null;
} else if(this.state.newTag) {
} else if (this.state.newTag) {
/** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.removeItemLocally(tag);
@@ -206,10 +203,10 @@ class TagsPanelCtrl extends PureCtrl {
this.setState({ newTag: null });
return;
}
this.editingOriginalName = null;
const matchingTag = this.application.findTag({title: tag.title});
const matchingTag = this.application.findTag({ title: tag.title });
const alreadyExists = matchingTag && matchingTag !== tag;
if (this.state.newTag === tag && alreadyExists) {
this.application.alertManager.alert({
@@ -222,7 +219,7 @@ class TagsPanelCtrl extends PureCtrl {
return;
}
this.application.saveItem({item: tag});
this.application.saveItem({ item: tag });
this.selectTag(tag);
this.setState({
newTag: null
@@ -247,7 +244,7 @@ class TagsPanelCtrl extends PureCtrl {
text: STRING_DELETE_TAG,
destructive: true,
onConfirm: () => {
this.application.deleteItem({item: tag});
this.application.deleteItem({ item: tag });
}
});
}

View File

@@ -4,7 +4,6 @@ import { PureCtrl } from '@Controllers';
class ActionsMenuCtrl extends PureCtrl {
/* @ngInject */
constructor(
$scope,
$timeout,
actionsManager,
godService

View File

@@ -2,22 +2,25 @@ import angular from 'angular';
import template from '%/directives/panel-resizer.pug';
import { debounce } from '@/utils';
const PANEL_SIDE_RIGHT = 'right';
const PANEL_SIDE_LEFT = 'left';
const MOUSE_EVENT_MOVE = 'mousemove';
const MOUSE_EVENT_DOWN = 'mousedown';
const MOUSE_EVENT_UP = 'mouseup';
const PanelSides = {
Right: 'right',
Left: 'left'
};
const MouseEvents = {
Move: 'mousemove',
Down: 'mousedown',
Up: 'mouseup'
};
const CssClasses = {
Hoverable: 'hoverable',
AlwaysVisible: 'always-visible',
Dragging: 'dragging',
NoSelection: 'no-selection',
Collapsed: 'collapsed',
AnimateOpacity: 'animate-opacity',
};
const WINDOW_EVENT_RESIZE = 'resize';
const PANEL_CSS_CLASS_HOVERABLE = 'hoverable';
const PANEL_CSS_CLASS_ALWAYS_VISIBLE = 'always-visible';
const PANEL_CSS_CLASS_DRAGGING = 'dragging';
const PANEL_CSS_CLASS_NO_SELECTION = 'no-selection';
const PANEL_CSS_CLASS_COLLAPSED = 'collapsed';
const PANEL_CSS_CLASS_ANIMATE_OPACITY = 'animate-opacity';
class PanelResizerCtrl {
/* @ngInject */
constructor(
@@ -78,14 +81,14 @@ class PanelResizerCtrl {
this.appFrame = null;
this.widthBeforeLastDblClick = 0;
if (this.property === PANEL_SIDE_RIGHT) {
if (this.property === PanelSides.Right) {
this.configureRightPanel();
}
if (this.alwaysVisible) {
this.resizerColumn.classList.add(PANEL_CSS_CLASS_ALWAYS_VISIBLE);
this.resizerColumn.classList.add(CssClasses.AlwaysVisible);
}
if (this.hoverable) {
this.resizerColumn.classList.add(PANEL_CSS_CLASS_HOVERABLE);
this.resizerColumn.classList.add(CssClasses.Hoverable);
}
}
@@ -140,26 +143,26 @@ class PanelResizerCtrl {
}
addMouseDownListener() {
this.resizerColumn.addEventListener(MOUSE_EVENT_DOWN, (event) => {
this.resizerColumn.addEventListener(MouseEvents.Down, (event) => {
this.addInvisibleOverlay();
this.pressed = true;
this.lastDownX = event.clientX;
this.startWidth = this.panel.scrollWidth;
this.startLeft = this.panel.offsetLeft;
this.panel.classList.add(PANEL_CSS_CLASS_NO_SELECTION);
this.panel.classList.add(CssClasses.NoSelection);
if (this.hoverable) {
this.resizerColumn.classList.add(PANEL_CSS_CLASS_DRAGGING);
this.resizerColumn.classList.add(CssClasses.Dragging);
}
});
}
addMouseMoveListener() {
document.addEventListener(MOUSE_EVENT_MOVE, (event) => {
document.addEventListener(MouseEvents.Move, (event) => {
if (!this.pressed) {
return;
}
event.preventDefault();
if (this.property && this.property === PANEL_SIDE_LEFT) {
if (this.property && this.property === PanelSides.Left) {
this.handleLeftEvent(event);
} else {
this.handleWidthEvent(event);
@@ -210,12 +213,12 @@ class PanelResizerCtrl {
}
addMouseUpListener() {
document.addEventListener(MOUSE_EVENT_UP, event => {
document.addEventListener(MouseEvents.Up, event => {
this.removeInvisibleOverlay();
if (this.pressed) {
this.pressed = false;
this.resizerColumn.classList.remove(PANEL_CSS_CLASS_DRAGGING);
this.panel.classList.remove(PANEL_CSS_CLASS_NO_SELECTION);
this.resizerColumn.classList.remove(CssClasses.Dragging);
this.panel.classList.remove(CssClasses.NoSelection);
const isMaxWidth = this.isAtMaxWidth();
if (this.onResizeFinish) {
this.onResizeFinish()(
@@ -232,7 +235,7 @@ class PanelResizerCtrl {
isAtMaxWidth() {
return (
Math.round(this.lastWidth + this.lastLeft) ===
Math.round(this.lastWidth + this.lastLeft) ===
Math.round(this.getParentRect().width)
);
}
@@ -279,9 +282,9 @@ class PanelResizerCtrl {
this.collapsed = this.isCollapsed();
if (this.collapsed) {
this.resizerColumn.classList.add(PANEL_CSS_CLASS_COLLAPSED);
this.resizerColumn.classList.add(CssClasses.Collapsed);
} else {
this.resizerColumn.classList.remove(PANEL_CSS_CLASS_COLLAPSED);
this.resizerColumn.classList.remove(CssClasses.Collapsed);
}
}
@@ -308,9 +311,9 @@ class PanelResizerCtrl {
flash() {
const FLASH_DURATION = 3000;
this.resizerColumn.classList.add(PANEL_CSS_CLASS_ANIMATE_OPACITY);
this.resizerColumn.classList.add(CssClasses.AnimateOpacity);
this.$timeout(() => {
this.resizerColumn.classList.remove(PANEL_CSS_CLASS_ANIMATE_OPACITY);
this.resizerColumn.classList.remove(CssClasses.AnimateOpacity);
}, FLASH_DURATION);
}
}

View File

@@ -1,5 +1,5 @@
import { PrivilegesManager } from '@/services/privilegesManager';
import template from '%/directives/privileges-management-modal.pug';
import { PrivilegeCredentials } from 'snjs';
class PrivilegesManagementModalCtrl {
/* @ngInject */
@@ -18,9 +18,9 @@ class PrivilegesManagementModalCtrl {
displayInfoForCredential(credential) {
const info = this.application.privilegesManager.displayInfoForCredential(credential);
if (credential === PrivilegesManager.CredentialLocalPasscode) {
if (credential === PrivilegeCredentials.LocalPasscode) {
info.availability = this.hasPasscode;
} else if (credential === PrivilegesManager.CredentialAccountPassword) {
} else if (credential === PrivilegeCredentials.AccountPassword) {
info.availability = this.hasAccount;
} else {
info.availability = true;

View File

@@ -1,7 +1,6 @@
import {
PAYLOAD_SOURCE_REMOTE_ACTION_RETRIEVED,
CONTENT_TYPE_NOTE,
CONTENT_TYPE_COMPONENT
ContentTypes
} from 'snjs';
import template from '%/directives/revision-preview-modal.pug';
@@ -25,7 +24,7 @@ class RevisionPreviewModalCtrl {
async configure() {
this.note = await this.application.createItem({
contentType: CONTENT_TYPE_NOTE,
contentType: ContentTypes.Note,
content: this.content
});
@@ -43,7 +42,7 @@ class RevisionPreviewModalCtrl {
* editor object has non-copyable properties like .window, which cannot be transfered
*/
const editorCopy = await this.application.createItem({
contentType: CONTENT_TYPE_COMPONENT,
contentType: ContentTypes.Component,
content: editorForNote.content
});
editorCopy.readonly = true;

View File

@@ -1,48 +1,46 @@
import { PrivilegesManager } from '@/services/privilegesManager';
import { EncryptionIntents, ProtectedActions } from 'snjs';
export class ArchiveManager {
/* @ngInject */
constructor(lockManager, authManager, modelManager, privilegesManager) {
constructor(lockManager, application, authManager, modelManager, privilegesManager) {
this.lockManager = lockManager;
this.authManager = authManager;
this.modelManager = modelManager;
modelManager = modelManager;
this.privilegesManager = privilegesManager;
this.application = application;
}
/*
Public
*/
/** @public */
async downloadBackup(encrypted) {
return this.downloadBackupOfItems(this.modelManager.allItems, encrypted);
return this.downloadBackupOfItems(modelManager.allItems, encrypted);
}
/** @public */
async downloadBackupOfItems(items, encrypted) {
const run = async () => {
// download in Standard Notes format
let keys, authParams;
if(encrypted) {
if(this.authManager.offline() && this.lockManager.hasPasscode()) {
keys = this.lockManager.keys();
authParams = this.lockManager.passcodeAuthParams();
} else {
keys = await this.authManager.keys();
authParams = await this.authManager.getAuthParams();
}
}
this.__itemsData(items, keys, authParams).then((data) => {
const intent = encrypted
? EncryptionIntents.FileEncrypted
: EncryptionIntents.FileDecrypted;
this.itemsData(items, intent).then((data) => {
const modifier = encrypted ? "Encrypted" : "Decrypted";
this.__downloadData(data, `Standard Notes ${modifier} Backup - ${this.__formattedDate()}.txt`);
this.downloadData(
data,
`Standard Notes ${modifier} Backup - ${this.formattedDate()}.txt`
);
// download as zipped plain text files
if(!keys) {
this.__downloadZippedItems(items);
if (!encrypted) {
this.downloadZippedItems(items);
}
});
};
if(await this.privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageBackups)) {
this.godService.presentPrivilegesModal(PrivilegesManager.ActionManageBackups, () => {
if (await this.privilegesManager.actionRequiresPrivilege(ProtectedActions.ManageBackups)) {
this.godService.presentPrivilegesModal(ProtectedActions.ManageBackups, () => {
run();
});
} else {
@@ -50,29 +48,30 @@ export class ArchiveManager {
}
}
/*
Private
*/
__formattedDate() {
/** @private */
formattedDate() {
var string = `${new Date()}`;
// Match up to the first parenthesis, i.e do not include '(Central Standard Time)'
var matches = string.match(/^(.*?) \(/);
if(matches.length >= 2) {
if (matches.length >= 2) {
return matches[1];
}
return string;
}
async __itemsData(items, keys, authParams) {
const data = await this.modelManager.getJSONDataForItems(items, keys, authParams);
const blobData = new Blob([data], {type: 'text/json'});
/** @private */
async itemsData(items, intent) {
const data = await this.application.protocolService.createBackupFile({
subItems: items,
intent: intent
});
const blobData = new Blob([data], { type: 'text/json' });
return blobData;
}
__loadZip(callback) {
if(window.zip) {
callback();
/** @private */
async loadZip() {
if (window.zip) {
return;
}
@@ -81,75 +80,77 @@ export class ArchiveManager {
scriptTag.async = false;
var headTag = document.getElementsByTagName('head')[0];
headTag.appendChild(scriptTag);
scriptTag.onload = function() {
zip.workerScriptsPath = "assets/zip/";
callback();
};
}
__downloadZippedItems(items) {
this.__loadZip(() => {
zip.createWriter(new zip.BlobWriter("application/zip"), (zipWriter) => {
var index = 0;
const nextFile = () => {
var item = items[index];
var name, contents;
if(item.content_type === "Note") {
name = item.content.title;
contents = item.content.text;
} else {
name = item.content_type;
contents = JSON.stringify(item.content, null, 2);
}
if(!name) {
name = "";
}
const blob = new Blob([contents], {type: 'text/plain'});
let filePrefix = name.replace(/\//g, "").replace(/\\+/g, "");
const fileSuffix = `-${item.uuid.split("-")[0]}.txt`;
// Standard max filename length is 255. Slice the note name down to allow filenameEnd
filePrefix = filePrefix.slice(0, (255 - fileSuffix.length));
const fileName = `${item.content_type}/${filePrefix}${fileSuffix}`;
zipWriter.add(fileName, new zip.BlobReader(blob), () => {
index++;
if(index < items.length) {
nextFile();
} else {
zipWriter.close((blob) => {
this.__downloadData(blob, `Standard Notes Backup - ${this.__formattedDate()}.zip`);
zipWriter = null;
});
}
});
};
nextFile();
}, onerror);
return new Promise((resolve, reject) => {
scriptTag.onload = function () {
zip.workerScriptsPath = "assets/zip/";
resolve();
};
});
}
/** @private */
async downloadZippedItems(items) {
await this.loadZip();
zip.createWriter(new zip.BlobWriter("application/zip"), (zipWriter) => {
var index = 0;
__hrefForData(data) {
const nextFile = () => {
var item = items[index];
var name, contents;
if (item.content_type === "Note") {
name = item.content.title;
contents = item.content.text;
} else {
name = item.content_type;
contents = JSON.stringify(item.content, null, 2);
}
if (!name) {
name = "";
}
const blob = new Blob([contents], { type: 'text/plain' });
let filePrefix = name.replace(/\//g, "").replace(/\\+/g, "");
const fileSuffix = `-${item.uuid.split("-")[0]}.txt`;
// Standard max filename length is 255. Slice the note name down to allow filenameEnd
filePrefix = filePrefix.slice(0, (255 - fileSuffix.length));
const fileName = `${item.content_type}/${filePrefix}${fileSuffix}`;
zipWriter.add(fileName, new zip.BlobReader(blob), () => {
index++;
if (index < items.length) {
nextFile();
} else {
zipWriter.close((blob) => {
this.downloadData(blob, `Standard Notes Backup - ${this.formattedDate()}.zip`);
zipWriter = null;
});
}
});
};
nextFile();
}, onerror);
}
/** @private */
hrefForData(data) {
// If we are replacing a previously generated file we need to
// manually revoke the object URL to avoid memory leaks.
if (this.textFile !== null) {
window.URL.revokeObjectURL(this.textFile);
}
this.textFile = window.URL.createObjectURL(data);
// returns a URL you can use as a href
return this.textFile;
}
__downloadData(data, fileName) {
/** @private */
downloadData(data, fileName) {
var link = document.createElement('a');
link.setAttribute('download', fileName);
link.href = this.__hrefForData(data);
link.href = this.hrefForData(data);
document.body.appendChild(link);
link.click();
link.remove();

View File

@@ -1,7 +1,8 @@
/* eslint-disable camelcase */
// An interface used by the Desktop app to interact with SN
import _ from 'lodash';
import pull from 'lodash/pull';
import { isDesktopApplication } from '@/utils';
import { SFItemParams, SFModelManager } from 'snjs';
import { EncryptionIntents } from 'snjs';
const COMPONENT_DATA_KEY_INSTALL_ERROR = 'installError';
const COMPONENT_CONTENT_KEY_PACKAGE_INFO = 'package_info';
@@ -12,33 +13,26 @@ export class DesktopManager {
constructor(
$rootScope,
$timeout,
modelManager,
syncManager,
authManager,
lockManager,
appState
application,
appState,
) {
this.lockManager = lockManager;
this.modelManager = modelManager;
this.authManager = authManager;
this.syncManager = syncManager;
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.appState = appState;
this.timeout = $timeout;
this.updateObservers = [];
this.application = application;
this.componentActivationObservers = [];
this.updateObservers = [];
this.isDesktop = isDesktopApplication();
$rootScope.$on("initial-data-loaded", () => {
$rootScope.$on('initial-data-loaded', () => {
this.dataLoaded = true;
if(this.dataLoadHandler) {
if (this.dataLoadHandler) {
this.dataLoadHandler();
}
});
$rootScope.$on("major-data-change", () => {
if(this.majorDataChangeHandler) {
$rootScope.$on('major-data-change', () => {
if (this.majorDataChangeHandler) {
this.majorDataChangeHandler();
}
});
@@ -56,17 +50,20 @@ export class DesktopManager {
return this.extServerHost;
}
/*
Sending a component in its raw state is really slow for the desktop app
Keys are not passed into ItemParams, so the result is not encrypted
/**
* Sending a component in its raw state is really slow for the desktop app
* Keys are not passed into ItemParams, so the result is not encrypted
*/
async convertComponentForTransmission(component) {
return new SFItemParams(component).paramsForExportFile(true);
return this.application.protocolService.payloadByEncryptingPayload({
payload: component.payloadRepresentation(),
intent: EncryptionIntents.FileDecrypted
});
}
// All `components` should be installed
syncComponentsInstallation(components) {
if(!this.isDesktop) {
if (!this.isDesktop) {
return;
}
Promise.all(components.map((component) => {
@@ -91,21 +88,21 @@ export class DesktopManager {
}
searchText(text) {
if(!this.isDesktop) {
if (!this.isDesktop) {
return;
}
this.lastSearchedText = text;
this.searchHandler && this.searchHandler(text);
}
redoSearch() {
if(this.lastSearchedText) {
redoSearch() {
if (this.lastSearchedText) {
this.searchText(this.lastSearchedText);
}
}
deregisterUpdateObserver(observer) {
_.pull(this.updateObservers, observer);
pull(this.updateObservers, observer);
}
// Pass null to cancel search
@@ -114,19 +111,19 @@ export class DesktopManager {
}
desktop_windowGainedFocus() {
this.$rootScope.$broadcast("window-gained-focus");
this.$rootScope.$broadcast('window-gained-focus');
}
desktop_windowLostFocus() {
this.$rootScope.$broadcast("window-lost-focus");
this.$rootScope.$broadcast('window-lost-focus');
}
desktop_onComponentInstallationComplete(componentData, error) {
const component = this.modelManager.findItem(componentData.uuid);
if(!component) {
async desktop_onComponentInstallationComplete(componentData, error) {
const component = await this.application.findItem({ uuid: componentData.uuid });
if (!component) {
return;
}
if(error) {
if (error) {
component.setAppDataItem(
COMPONENT_DATA_KEY_INSTALL_ERROR,
error
@@ -136,35 +133,30 @@ export class DesktopManager {
COMPONENT_CONTENT_KEY_PACKAGE_INFO,
COMPONENT_CONTENT_KEY_LOCAL_URL
];
for(const key of permissableKeys) {
for (const key of permissableKeys) {
component[key] = componentData.content[key];
}
this.modelManager.notifySyncObserversOfModels(
[component],
SFModelManager.MappingSourceDesktopInstalled
);
component.setAppDataItem(
COMPONENT_DATA_KEY_INSTALL_ERROR,
null
);
}
this.modelManager.setItemDirty(component);
this.syncManager.sync();
this.timeout(() => {
for(const observer of this.updateObservers) {
this.application.saveItem({ item: component });
this.$timeout(() => {
for (const observer of this.updateObservers) {
observer.callback(component);
}
});
}
desktop_registerComponentActivationObserver(callback) {
const observer = {id: Math.random, callback: callback};
const observer = { id: Math.random, callback: callback };
this.componentActivationObservers.push(observer);
return observer;
}
desktop_deregisterComponentActivationObserver(observer) {
_.pull(this.componentActivationObservers, observer);
pull(this.componentActivationObservers, observer);
}
/* Notify observers that a component has been registered/activated */
@@ -172,14 +164,14 @@ export class DesktopManager {
const serializedComponent = await this.convertComponentForTransmission(
component
);
this.timeout(() => {
for(const observer of this.componentActivationObservers) {
this.$timeout(() => {
for (const observer of this.componentActivationObservers) {
observer.callback(serializedComponent);
}
});
}
/* Used to resolve "sn://" */
/* Used to resolve 'sn://' */
desktop_setExtServerHost(host) {
this.extServerHost = host;
this.appState.desktopExtensionsReady();
@@ -195,28 +187,16 @@ export class DesktopManager {
desktop_setInitialDataLoadHandler(handler) {
this.dataLoadHandler = handler;
if(this.dataLoaded) {
if (this.dataLoaded) {
this.dataLoadHandler();
}
}
async desktop_requestBackupFile(callback) {
let keys, authParams;
if(this.authManager.offline() && this.lockManager.hasPasscode()) {
keys = this.lockManager.keys();
authParams = this.lockManager.passcodeAuthParams();
} else {
keys = await this.authManager.keys();
authParams = await this.authManager.getAuthParams();
}
const nullOnEmpty = true;
this.modelManager.getAllItemsJSONData(
keys,
authParams,
nullOnEmpty
).then((data) => {
callback(data);
const data = await this.application.protocolService.createBackupFile({
returnIfEmpty: true
});
callback(data);
}
desktop_setMajorDataChangeHandler(handler) {

View File

@@ -1,21 +1,10 @@
export { ActionsManager } from './actionsManager';
export { ArchiveManager } from './archiveManager';
export { AuthManager } from './authManager';
export { ComponentManager } from './componentManager';
export { DatabaseManager } from './databaseManager';
export { DesktopManager } from './desktopManager';
export { HttpManager } from './httpManager';
export { KeyboardManager } from './keyboardManager';
export { MigrationManager } from './migrationManager';
export { ModelManager } from './modelManager';
export { NativeExtManager } from './nativeExtManager';
export { LockManager } from './lockManager';
export { PrivilegesManager } from './privilegesManager';
export { SessionHistory } from './sessionHistory';
export { SingletonManager } from './singletonManager';
export { StatusManager } from './statusManager';
export { StorageManager } from './storageManager';
export { SyncManager } from './syncManager';
export { ThemeManager } from './themeManager';
export { AlertManager } from './alertManager';
export { PreferencesManager } from './preferencesManager';

View File

@@ -1,42 +1,41 @@
export class KeyboardManager {
/** @public */
export const KeyboardKeys = {
Tab: "Tab",
Backspace: "Backspace",
Up: "ArrowUp",
Down: "ArrowDown",
};
/** @public */
export const KeyboardModifiers = {
Shift: "Shift",
Ctrl: "Control",
/** ⌘ key on Mac, ⊞ key on Windows */
Meta: "Meta",
Alt: "Alt",
};
/** @private */
const KeyboardKeyEvents = {
Down: "KeyEventDown",
Up: "KeyEventUp"
};
export class KeyboardManager {
constructor() {
this.observers = [];
KeyboardManager.KeyTab = "Tab";
KeyboardManager.KeyBackspace = "Backspace";
KeyboardManager.KeyUp = "ArrowUp";
KeyboardManager.KeyDown = "ArrowDown";
KeyboardManager.KeyModifierShift = "Shift";
KeyboardManager.KeyModifierCtrl = "Control";
// ⌘ key on Mac, ⊞ key on Windows
KeyboardManager.KeyModifierMeta = "Meta";
KeyboardManager.KeyModifierAlt = "Alt";
KeyboardManager.KeyEventDown = "KeyEventDown";
KeyboardManager.KeyEventUp = "KeyEventUp";
KeyboardManager.AllModifiers = [
KeyboardManager.KeyModifierShift,
KeyboardManager.KeyModifierCtrl,
KeyboardManager.KeyModifierMeta,
KeyboardManager.KeyModifierAlt
];
window.addEventListener('keydown', this.handleKeyDown.bind(this));
window.addEventListener('keyup', this.handleKeyUp.bind(this));
}
modifiersForEvent(event) {
const eventModifiers = KeyboardManager.AllModifiers.filter((modifier) => {
const allModifiers = Object.keys(KeyboardModifiers).map((key) => KeyboardModifiers[key]);
const eventModifiers = allModifiers.filter((modifier) => {
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
const matches = (
((event.ctrlKey || event.key == KeyboardManager.KeyModifierCtrl) && modifier === KeyboardManager.KeyModifierCtrl) ||
((event.metaKey || event.key == KeyboardManager.KeyModifierMeta) && modifier === KeyboardManager.KeyModifierMeta) ||
((event.altKey || event.key == KeyboardManager.KeyModifierAlt) && modifier === KeyboardManager.KeyModifierAlt) ||
((event.shiftKey || event.key == KeyboardManager.KeyModifierShift) && modifier === KeyboardManager.KeyModifierShift)
((event.ctrlKey || event.key === KeyboardModifiers.Ctrl) && modifier === KeyboardModifiers.Ctrl) ||
((event.metaKey || event.key === KeyboardModifiers.Meta) && modifier === KeyboardModifiers.Meta) ||
((event.altKey || event.key === KeyboardModifiers.Alt) && modifier === KeyboardModifiers.Alt) ||
((event.shiftKey || event.key === KeyboardModifiers.Shift) && modifier === KeyboardModifiers.Shift)
);
return matches;
@@ -45,50 +44,50 @@ export class KeyboardManager {
return eventModifiers;
}
eventMatchesKeyAndModifiers(event, key, modifiers = []) {
eventMatchesKeyAndModifiers(event, key, modifiers = []) {
const eventModifiers = this.modifiersForEvent(event);
if(eventModifiers.length != modifiers.length) {
if (eventModifiers.length !== modifiers.length) {
return false;
}
for(const modifier of modifiers) {
if(!eventModifiers.includes(modifier)) {
for (const modifier of modifiers) {
if (!eventModifiers.includes(modifier)) {
return false;
}
}
// Modifers match, check key
if(!key) {
if (!key) {
return true;
}
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
// In our case we don't differentiate between the two.
return key.toLowerCase() == event.key.toLowerCase();
return key.toLowerCase() === event.key.toLowerCase();
}
notifyObserver(event, keyEventType) {
for(const observer of this.observers) {
if(observer.element && event.target != observer.element) {
for (const observer of this.observers) {
if (observer.element && event.target !== observer.element) {
continue;
}
if(observer.elements && !observer.elements.includes(event.target)) {
if (observer.elements && !observer.elements.includes(event.target)) {
continue;
}
if(observer.notElement && observer.notElement == event.target) {
if (observer.notElement && observer.notElement === event.target) {
continue;
}
if(observer.notElementIds && observer.notElementIds.includes(event.target.id)) {
if (observer.notElementIds && observer.notElementIds.includes(event.target.id)) {
continue;
}
if(this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) {
const callback = keyEventType == KeyboardManager.KeyEventDown ? observer.onKeyDown : observer.onKeyUp;
if(callback) {
if (this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) {
const callback = keyEventType === KeyboardKeyEvents.Down ? observer.onKeyDown : observer.onKeyUp;
if (callback) {
callback(event);
}
}
@@ -96,15 +95,15 @@ export class KeyboardManager {
}
handleKeyDown(event) {
this.notifyObserver(event, KeyboardManager.KeyEventDown);
this.notifyObserver(event, KeyboardKeyEvents.Down);
}
handleKeyUp(event) {
this.notifyObserver(event, KeyboardManager.KeyEventUp);
this.notifyObserver(event, KeyboardKeyEvents.Up);
}
addKeyObserver({key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds}) {
const observer = {key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds};
addKeyObserver({ key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds }) {
const observer = { key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds };
this.observers.push(observer);
return observer;
}

View File

@@ -1,9 +1,5 @@
import _ from 'lodash';
import { isDesktopApplication } from '@/utils';
import {
APP_STATE_EVENT_WINDOW_DID_BLUR,
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '../state';
import { AppStateEvents } from '../state';
const MILLISECONDS_PER_SECOND = 1000;
const FOCUS_POLL_INTERVAL = 1 * MILLISECONDS_PER_SECOND;
@@ -26,9 +22,9 @@ export class LockManager {
observeVisibility() {
this.appState.addObserver((eventName, data) => {
if(eventName === APP_STATE_EVENT_WINDOW_DID_BLUR) {
if(eventName === AppStateEvents.WindowDidBlur) {
this.documentVisibilityChanged(false);
} else if(eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
} else if(eventName === AppStateEvents.WindowDidFocus) {
this.documentVisibilityChanged(true);
}
});

View File

@@ -1,183 +1,177 @@
/* A class for handling installation of system extensions */
import { isDesktopApplication, dictToArray } from '@/utils';
import { SFPredicate, ContentTypes, CreateMaxPayloadFromAnyObject } from 'snjs';
import { AppStateEvents } from '@/state';
import { isDesktopApplication } from '@/utils';
import { SFPredicate } from 'snjs';
const STREAM_ITEMS_PERMISSION = 'stream-items';
/** A class for handling installation of system extensions */
export class NativeExtManager {
/* @ngInject */
constructor(modelManager, syncManager, singletonManager) {
this.modelManager = modelManager;
this.syncManager = syncManager;
this.singletonManager = singletonManager;
this.extManagerId = "org.standardnotes.extensions-manager";
this.batchManagerId = "org.standardnotes.batch-manager";
constructor(application, appState) {
this.application = application;
this.extManagerId = 'org.standardnotes.extensions-manager';
this.batchManagerId = 'org.standardnotes.batch-manager';
this.systemExtensions = [];
this.resolveExtensionsManager();
this.resolveBatchManager();
appState.addObserver(async (eventName) => {
if (eventName === AppStateEvents.ApplicationReady) {
await this.initialize();
}
});
}
isSystemExtension(extension) {
return this.systemExtensions.includes(extension.uuid);
}
resolveExtensionsManager() {
async initialize() {
this.resolveExtensionsManager();
this.resolveBatchManager();
}
const contentTypePredicate = new SFPredicate("content_type", "=", "SN|Component");
const packagePredicate = new SFPredicate("package_info.identifier", "=", this.extManagerId);
this.singletonManager.registerSingleton([contentTypePredicate, packagePredicate], (resolvedSingleton) => {
// Resolved Singleton
this.systemExtensions.push(resolvedSingleton.uuid);
var needsSync = false;
if(isDesktopApplication()) {
if(!resolvedSingleton.local_url) {
resolvedSingleton.local_url = window._extensions_manager_location;
needsSync = true;
}
} else {
if(!resolvedSingleton.hosted_url) {
resolvedSingleton.hosted_url = window._extensions_manager_location;
needsSync = true;
extensionsManagerTemplatePayload() {
const url = window._extensions_manager_location;
if (!url) {
console.error('window._extensions_manager_location must be set.');
return;
}
const packageInfo = {
name: 'Extensions',
identifier: this.extManagerId
};
const content = {
name: packageInfo.name,
area: 'rooms',
package_info: packageInfo,
permissions: [
{
name: STREAM_ITEMS_PERMISSION,
content_types: [
ContentTypes.Component,
ContentTypes.Theme,
ContentTypes.ServerExtension,
ContentTypes.ActionsExtension,
ContentTypes.Mfa,
ContentTypes.Editor,
ContentTypes.ExtensionRepo
]
}
]
};
if (isDesktopApplication()) {
content.local_url = window._extensions_manager_location;
} else {
content.hosted_url = window._extensions_manager_location;
}
const payload = CreateMaxPayloadFromAnyObject({
object: {
content_type: ContentTypes.Component,
content: content
}
});
return payload;
}
// Handle addition of SN|ExtensionRepo permission
const permission = resolvedSingleton.content.permissions.find((p) => p.name == "stream-items");
if(!permission.content_types.includes("SN|ExtensionRepo")) {
permission.content_types.push("SN|ExtensionRepo");
async resolveExtensionsManager() {
const contentTypePredicate = new SFPredicate('content_type', '=', ContentTypes.Component);
const packagePredicate = new SFPredicate('package_info.identifier', '=', this.extManagerId);
const predicate = SFPredicate.CompoundPredicate([contentTypePredicate, packagePredicate]);
const extensionsManager = await this.application.singletonManager.findOrCreateSingleton({
predicate: predicate,
createPayload: this.extensionsManagerTemplatePayload()
});
this.systemExtensions.push(extensionsManager.uuid);
let needsSync = false;
if (isDesktopApplication()) {
if (!extensionsManager.local_url) {
extensionsManager.local_url = window._extensions_manager_location;
needsSync = true;
}
if(needsSync) {
this.modelManager.setItemDirty(resolvedSingleton, true);
this.syncManager.sync();
} else {
if (!extensionsManager.hosted_url) {
extensionsManager.hosted_url = window._extensions_manager_location;
needsSync = true;
}
}, (valueCallback) => {
// Safe to create. Create and return object.
const url = window._extensions_manager_location;
if(!url) {
console.error("window._extensions_manager_location must be set.");
return;
}
const packageInfo = {
name: "Extensions",
identifier: this.extManagerId
};
var item = {
content_type: "SN|Component",
content: {
name: packageInfo.name,
area: "rooms",
package_info: packageInfo,
permissions: [
{
name: "stream-items",
content_types: [
"SN|Component", "SN|Theme", "SF|Extension",
"Extension", "SF|MFA", "SN|Editor", "SN|ExtensionRepo"
]
}
]
}
};
if(isDesktopApplication()) {
item.content.local_url = window._extensions_manager_location;
} else {
item.content.hosted_url = window._extensions_manager_location;
}
var component = this.modelManager.createItem(item);
this.modelManager.addItem(component);
this.modelManager.setItemDirty(component, true);
this.syncManager.sync();
this.systemExtensions.push(component.uuid);
valueCallback(component);
});
}
// Handle addition of SN|ExtensionRepo permission
const permission = extensionsManager.content.permissions.find((p) => p.name === STREAM_ITEMS_PERMISSION);
if (!permission.content_types.includes(ContentTypes.ExtensionRepo)) {
permission.content_types.push(ContentTypes.ExtensionRepo);
needsSync = true;
}
if (needsSync) {
this.application.saveItem({ item: extensionsManager });
}
}
resolveBatchManager() {
const contentTypePredicate = new SFPredicate("content_type", "=", "SN|Component");
const packagePredicate = new SFPredicate("package_info.identifier", "=", this.batchManagerId);
this.singletonManager.registerSingleton([contentTypePredicate, packagePredicate], (resolvedSingleton) => {
// Resolved Singleton
this.systemExtensions.push(resolvedSingleton.uuid);
var needsSync = false;
if(isDesktopApplication()) {
if(!resolvedSingleton.local_url) {
resolvedSingleton.local_url = window._batch_manager_location;
needsSync = true;
}
} else {
if(!resolvedSingleton.hosted_url) {
resolvedSingleton.hosted_url = window._batch_manager_location;
needsSync = true;
batchManagerTemplatePayload() {
const url = window._batch_manager_location;
if (!url) {
console.error('window._batch_manager_location must be set.');
return;
}
const packageInfo = {
name: 'Batch Manager',
identifier: this.batchManagerId
};
const allContentTypes = dictToArray(ContentTypes);
const content = {
name: packageInfo.name,
area: 'modal',
package_info: packageInfo,
permissions: [
{
name: STREAM_ITEMS_PERMISSION,
content_types: allContentTypes
}
]
};
if (isDesktopApplication()) {
content.local_url = window._batch_manager_location;
} else {
content.hosted_url = window._batch_manager_location;
}
const payload = CreateMaxPayloadFromAnyObject({
object: {
content_type: ContentTypes.Component,
content: content
}
if(needsSync) {
this.modelManager.setItemDirty(resolvedSingleton, true);
this.syncManager.sync();
}
}, (valueCallback) => {
// Safe to create. Create and return object.
const url = window._batch_manager_location;
if(!url) {
console.error("window._batch_manager_location must be set.");
return;
}
const packageInfo = {
name: "Batch Manager",
identifier: this.batchManagerId
};
var item = {
content_type: "SN|Component",
content: {
name: packageInfo.name,
area: "modal",
package_info: packageInfo,
permissions: [
{
name: "stream-items",
content_types: [
"Note", "Tag", "SN|SmartTag",
"SN|Component", "SN|Theme", "SN|UserPreferences",
"SF|Extension", "Extension", "SF|MFA", "SN|Editor",
"SN|FileSafe|Credentials", "SN|FileSafe|FileMetadata", "SN|FileSafe|Integration"
]
}
]
}
};
if(isDesktopApplication()) {
item.content.local_url = window._batch_manager_location;
} else {
item.content.hosted_url = window._batch_manager_location;
}
var component = this.modelManager.createItem(item);
this.modelManager.addItem(component);
this.modelManager.setItemDirty(component, true);
this.syncManager.sync();
this.systemExtensions.push(component.uuid);
valueCallback(component);
});
return payload;
}
async resolveBatchManager() {
const contentTypePredicate = new SFPredicate('content_type', '=', ContentTypes.Component);
const packagePredicate = new SFPredicate('package_info.identifier', '=', this.batchManagerId);
const predicate = SFPredicate.CompoundPredicate([contentTypePredicate, packagePredicate]);
const batchManager = await this.application.singletonManager.findOrCreateSingleton({
predicate: predicate,
createPayload: this.batchManagerTemplatePayload()
});
this.systemExtensions.push(batchManager.uuid);
let needsSync = false;
if (isDesktopApplication()) {
if (!batchManager.local_url) {
batchManager.local_url = window._batch_manager_location;
needsSync = true;
}
} else {
if (!batchManager.hosted_url) {
batchManager.hosted_url = window._batch_manager_location;
needsSync = true;
}
}
// Handle addition of SN|ExtensionRepo permission
const permission = batchManager.content.permissions.find((p) => p.name === STREAM_ITEMS_PERMISSION);
if (!permission.content_types.includes(ContentTypes.ExtensionRepo)) {
permission.content_types.push(ContentTypes.ExtensionRepo);
needsSync = true;
}
if (needsSync) {
this.application.saveItem({ item: batchManager });
}
}
}

View File

@@ -1,63 +1,63 @@
import { SFPredicate, SFItem } from 'snjs';
import { SFPredicate, CreateMaxPayloadFromAnyObject } from 'snjs';
import { AppStateEvents } from '../state';
export const PREF_TAGS_PANEL_WIDTH = 'tagsPanelWidth';
export const PREF_NOTES_PANEL_WIDTH = 'notesPanelWidth';
export const PREF_EDITOR_WIDTH = 'editorWidth';
export const PREF_EDITOR_LEFT = 'editorLeft';
export const PREF_EDITOR_MONOSPACE_ENABLED = 'monospaceFont';
export const PREF_EDITOR_SPELLCHECK = 'spellcheck';
export const PREF_EDITOR_RESIZERS_ENABLED = 'marginResizersEnabled';
export const PREF_SORT_NOTES_BY = 'sortBy';
export const PREF_SORT_NOTES_REVERSE = 'sortReverse';
export const PREF_NOTES_SHOW_ARCHIVED = 'showArchived';
export const PREF_NOTES_HIDE_PINNED = 'hidePinned';
export const PREF_NOTES_HIDE_NOTE_PREVIEW = 'hideNotePreview';
export const PREF_NOTES_HIDE_DATE = 'hideDate';
export const PREF_NOTES_HIDE_TAGS = 'hideTags';
export const PrefKeys = {
TagsPanelWidth: 'tagsPanelWidth',
NotesPanelWidth: 'notesPanelWidth',
EditorWidth: 'editorWidth',
EditorLeft: 'editorLeft',
EditorMonospaceEnabled: 'monospaceFont',
EditorSpellcheck: 'spellcheck',
EditorResizersEnabled: 'marginResizersEnabled',
SortNotesBy: 'sortBy',
SortNotesReverse: 'sortReverse',
NotesShowArchived: 'showArchived',
NotesHidePinned: 'hidePinned',
NotesHideNotePreview: 'hideNotePreview',
NotesHideDate: 'hideDate',
NotesHideTags: 'hideTags'
};
export class PreferencesManager {
/* @ngInject */
constructor(
modelManager,
singletonManager,
appState,
syncManager
application
) {
this.singletonManager = singletonManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
this.application = application;
this.appState = appState;
this.modelManager.addItemSyncObserver(
'user-prefs',
'SN|UserPreferences',
(allItems, validItems, deletedItems, source, sourceKey) => {
this.preferencesDidChange();
appState.addObserver(async (eventName) => {
if (eventName === AppStateEvents.ApplicationReady) {
await this.initialize();
}
);
});
}
load() {
const prefsContentType = 'SN|UserPreferences';
const contentTypePredicate = new SFPredicate(
'content_type',
'=',
prefsContentType
);
this.singletonManager.registerSingleton(
[contentTypePredicate],
(resolvedSingleton) => {
this.userPreferences = resolvedSingleton;
},
(valueCallback) => {
// Safe to create. Create and return object.
const prefs = new SFItem({content_type: prefsContentType});
this.modelManager.addItem(prefs);
this.modelManager.setItemDirty(prefs);
this.syncManager.sync();
valueCallback(prefs);
async initialize() {
this.streamPreferences();
await this.loadSingleton();
}
streamPreferences() {
this.application.streamItems({
contentType: 'SN|UserPreferences',
stream: () => {
this.preferencesDidChange();
}
);
});
}
async loadSingleton() {
const contentType = 'SN|UserPreferences';
const predicate = new SFPredicate('content_type', '=', contentType);
this.userPreferences = await this.application.singletonManager.findOrCreateSingleton({
predicate: predicate,
createPayload: CreateMaxPayloadFromAnyObject({
object: {
content_type: contentType
}
})
});
}
preferencesDidChange() {
@@ -65,21 +65,20 @@ export class PreferencesManager {
}
syncUserPreferences() {
if(this.userPreferences) {
this.modelManager.setItemDirty(this.userPreferences);
this.syncManager.sync();
if (this.userPreferences) {
this.application.saveItem({item: this.userPreferences});
}
}
getValue(key, defaultValue) {
if(!this.userPreferences) { return defaultValue; }
if (!this.userPreferences) { return defaultValue; }
const value = this.userPreferences.getAppDataItem(key);
return (value !== undefined && value != null) ? value : defaultValue;
}
setUserPrefValue(key, value, sync) {
this.userPreferences.setAppDataItem(key, value);
if(sync) {
if (sync) {
this.syncUserPreferences();
}
}

View File

@@ -1,42 +1,28 @@
import _ from 'lodash';
import angular from 'angular';
import { SNTheme, SFItemParams } from 'snjs';
import { StorageManager } from './storageManager';
import {
APP_STATE_EVENT_DESKTOP_EXTS_READY
} from '@/state';
import { SNTheme, StorageValueModes, EncryptionIntents } from 'snjs';
import { AppStateEvents } from '@/state';
const CACHED_THEMES_KEY = 'cachedThemes';
export class ThemeManager {
/* @ngInject */
constructor(
componentManager,
application,
appState,
desktopManager,
storageManager,
lockManager,
appState
) {
this.componentManager = componentManager;
this.storageManager = storageManager;
this.application = application;
this.appState = appState;
this.desktopManager = desktopManager;
this.activeThemes = [];
ThemeManager.CachedThemesKey = "cachedThemes";
this.registerObservers();
if (desktopManager.isDesktop) {
appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_DESKTOP_EXTS_READY) {
this.activateCachedThemes();
}
});
} else {
if (!desktopManager.isDesktop) {
this.activateCachedThemes();
}
}
activateCachedThemes() {
const cachedThemes = this.getCachedThemes();
async activateCachedThemes() {
const cachedThemes = await this.getCachedThemes();
const writeToCache = false;
for (const theme of cachedThemes) {
this.activateTheme(theme, writeToCache);
@@ -44,6 +30,11 @@ export class ThemeManager {
}
registerObservers() {
this.appState.addObserver((eventName, data) => {
if (eventName === AppStateEvents.DesktopExtsReady) {
this.activateCachedThemes();
}
});
this.desktopManager.registerUpdateObserver((component) => {
// Reload theme if active
if (component.active && component.isTheme()) {
@@ -54,9 +45,9 @@ export class ThemeManager {
}
});
this.componentManager.registerHandler({
identifier: "themeManager",
areas: ["themes"],
this.application.componentManager.registerHandler({
identifier: 'themeManager',
areas: ['themes'],
activationHandler: (component) => {
if (component.active) {
this.activateTheme(component);
@@ -68,14 +59,14 @@ export class ThemeManager {
}
hasActiveTheme() {
return this.componentManager.getActiveThemes().length > 0;
return this.application.componentManager.getActiveThemes().length > 0;
}
deactivateAllThemes() {
var activeThemes = this.componentManager.getActiveThemes();
var activeThemes = this.application.componentManager.getActiveThemes();
for (var theme of activeThemes) {
if (theme) {
this.componentManager.deactivateComponent(theme);
this.application.componentManager.deactivateComponent(theme);
}
}
@@ -86,25 +77,22 @@ export class ThemeManager {
if (_.find(this.activeThemes, { uuid: theme.uuid })) {
return;
}
this.activeThemes.push(theme);
var url = this.componentManager.urlForComponent(theme);
var link = document.createElement("link");
const url = this.application.componentManager.urlForComponent(theme);
const link = document.createElement('link');
link.href = url;
link.type = "text/css";
link.rel = "stylesheet";
link.media = "screen,print";
link.type = 'text/css';
link.rel = 'stylesheet';
link.media = 'screen,print';
link.id = theme.uuid;
document.getElementsByTagName("head")[0].appendChild(link);
document.getElementsByTagName('head')[0].appendChild(link);
if (writeToCache) {
this.cacheThemes();
}
}
deactivateTheme(theme) {
var element = document.getElementById(theme.uuid);
const element = document.getElementById(theme.uuid);
if (element) {
element.disabled = true;
element.parentNode.removeChild(element);
@@ -117,20 +105,33 @@ export class ThemeManager {
async cacheThemes() {
const mapped = await Promise.all(this.activeThemes.map(async (theme) => {
const transformer = new SFItemParams(theme);
const params = await transformer.paramsForLocalStorage();
return params;
const payload = theme.payloadRepresentation();
const processedPayload = await this.application.protocolService.payloadByEncryptingPayload({
payload: payload,
intent: EncryptionIntents.LocalStorageDecrypted
});
return processedPayload;
}));
const data = JSON.stringify(mapped);
return this.storageManager.setItem(ThemeManager.CachedThemesKey, data, StorageManager.Fixed);
return this.application.setValue(
CACHED_THEMES_KEY,
data,
StorageValueModes.Nonwrapped
);
}
async decacheThemes() {
return this.storageManager.removeItem(ThemeManager.CachedThemesKey, StorageManager.Fixed);
return this.application.removeValue(
CACHED_THEMES_KEY,
StorageValueModes.Nonwrapped
);
}
getCachedThemes() {
const cachedThemes = this.storageManager.getItemSync(ThemeManager.CachedThemesKey, StorageManager.Fixed);
async getCachedThemes() {
const cachedThemes = await this.application.getValue(
CACHED_THEMES_KEY,
StorageValueModes.Nonwrapped
);
if (cachedThemes) {
const parsed = JSON.parse(cachedThemes);
return parsed.map((theme) => {

View File

@@ -1,32 +1,39 @@
import { PrivilegesManager } from '@/services/privilegesManager';
import { isDesktopApplication } from '@/utils';
import pull from 'lodash/pull';
import { ProtectedActions } from 'snjs';
export const APP_STATE_EVENT_TAG_CHANGED = 1;
export const APP_STATE_EVENT_NOTE_CHANGED = 2;
export const APP_STATE_EVENT_PREFERENCES_CHANGED = 3;
export const APP_STATE_EVENT_PANEL_RESIZED = 4;
export const APP_STATE_EVENT_EDITOR_FOCUSED = 5;
export const APP_STATE_EVENT_BEGAN_BACKUP_DOWNLOAD = 6;
export const APP_STATE_EVENT_ENDED_BACKUP_DOWNLOAD = 7;
export const APP_STATE_EVENT_DESKTOP_EXTS_READY = 8;
export const APP_STATE_EVENT_WINDOW_DID_FOCUS = 9;
export const APP_STATE_EVENT_WINDOW_DID_BLUR = 10;
export const AppStateEvents = {
TagChanged: 1,
NoteChanged: 2,
PreferencesChanged: 3,
PanelResized: 4,
EditorFocused: 5,
BeganBackupDownload: 6,
EndedBackupDownload: 7,
DesktopExtsReady: 8,
WindowDidFocus: 9,
WindowDidBlur: 10,
/** Register observers and streamers on this event */
ApplicationReady: 11
};
export const EVENT_SOURCE_USER_INTERACTION = 1;
export const EVENT_SOURCE_SCRIPT = 2;
export const EventSources = {
UserInteraction: 1,
Script: 2
};
export class AppState {
/* @ngInject */
constructor(
$timeout,
$timeout,
$rootScope,
privilegesManager
application,
) {
this.$timeout = $timeout;
this.$rootScope = $rootScope;
this.privilegesManager = privilegesManager;
this.application = application;
this.observers = [];
this.registerVisibilityObservers();
}
@@ -34,18 +41,18 @@ export class AppState {
registerVisibilityObservers() {
if (isDesktopApplication()) {
this.$rootScope.$on('window-lost-focus', () => {
this.notifyEvent(APP_STATE_EVENT_WINDOW_DID_BLUR);
this.notifyEvent(AppStateEvents.WindowDidBlur);
});
this.$rootScope.$on('window-gained-focus', () => {
this.notifyEvent(APP_STATE_EVENT_WINDOW_DID_FOCUS);
this.notifyEvent(AppStateEvents.WindowDidFocus);
});
} else {
/* Tab visibility listener, web only */
document.addEventListener('visibilitychange', (e) => {
const visible = document.visibilityState === "visible";
const event = visible
? APP_STATE_EVENT_WINDOW_DID_FOCUS
: APP_STATE_EVENT_WINDOW_DID_BLUR;
? AppStateEvents.WindowDidFocus
: AppStateEvents.WindowDidBlur;
this.notifyEvent(event);
});
}
@@ -66,7 +73,7 @@ export class AppState {
*/
return new Promise((resolve) => {
this.$timeout(async () => {
for(const callback of this.observers) {
for (const callback of this.observers) {
await callback(eventName, data);
}
resolve();
@@ -75,14 +82,14 @@ export class AppState {
}
setSelectedTag(tag) {
if(this.selectedTag === tag) {
if (this.selectedTag === tag) {
return;
}
const previousTag = this.selectedTag;
this.selectedTag = tag;
this.notifyEvent(
APP_STATE_EVENT_TAG_CHANGED,
{previousTag: previousTag}
AppStateEvents.TagChanged,
{ previousTag: previousTag }
);
}
@@ -91,16 +98,16 @@ export class AppState {
const previousNote = this.selectedNote;
this.selectedNote = note;
await this.notifyEvent(
APP_STATE_EVENT_NOTE_CHANGED,
AppStateEvents.NoteChanged,
{ previousNote: previousNote }
);
};
if (note && note.content.protected &&
await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionViewProtectedNotes
await this.application.privilegesManager.actionRequiresPrivilege(
ProtectedActions.ViewProtectedNotes
)) {
this.godService.presentPrivilegesModal(
PrivilegesManager.ActionViewProtectedNotes,
ProtectedActions.ViewProtectedNotes,
run
);
} else {
@@ -119,13 +126,13 @@ export class AppState {
setUserPreferences(preferences) {
this.userPreferences = preferences;
this.notifyEvent(
APP_STATE_EVENT_PREFERENCES_CHANGED
AppStateEvents.PreferencesChanged
);
}
panelDidResize({name, collapsed}) {
panelDidResize({ name, collapsed }) {
this.notifyEvent(
APP_STATE_EVENT_PANEL_RESIZED,
AppStateEvents.PanelResized,
{
panel: name,
collapsed: collapsed
@@ -135,21 +142,21 @@ export class AppState {
editorDidFocus(eventSource) {
this.notifyEvent(
APP_STATE_EVENT_EDITOR_FOCUSED,
{eventSource: eventSource}
AppStateEvents.EditorFocused,
{ eventSource: eventSource }
);
}
beganBackupDownload() {
this.notifyEvent(
APP_STATE_EVENT_BEGAN_BACKUP_DOWNLOAD
AppStateEvents.BeganBackupDownload
);
}
endedBackupDownload({success}) {
endedBackupDownload({ success }) {
this.notifyEvent(
APP_STATE_EVENT_ENDED_BACKUP_DOWNLOAD,
{success: success}
AppStateEvents.EndedBackupDownload,
{ success: success }
);
}
@@ -158,7 +165,7 @@ export class AppState {
*/
desktopExtensionsReady() {
this.notifyEvent(
APP_STATE_EVENT_DESKTOP_EXTS_READY
AppStateEvents.DesktopExtsReady
);
}

View File

@@ -1,49 +1,49 @@
/** @generic */
export const STRING_SESSION_EXPIRED = "Your session has expired. New changes will not be pulled in. Please sign out and sign back in to refresh your session.";
export const STRING_DEFAULT_FILE_ERROR = "Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe.";
export const STRING_GENERIC_SYNC_ERROR = "There was an error syncing. Please try again. If all else fails, try signing out and signing back in.";
export const STRING_SESSION_EXPIRED = "Your session has expired. New changes will not be pulled in. Please sign out and sign back in to refresh your session.";
export const STRING_DEFAULT_FILE_ERROR = "Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe.";
export const STRING_GENERIC_SYNC_ERROR = "There was an error syncing. Please try again. If all else fails, try signing out and signing back in.";
export function StringSyncException(data) {
return `There was an error while trying to save your items. Please contact support and share this message: ${data}.`;
}
/** @footer */
export const STRING_NEW_UPDATE_READY = "A new update is ready to install. Please use the top-level 'Updates' menu to manage installation.";
export const STRING_NEW_UPDATE_READY = "A new update is ready to install. Please use the top-level 'Updates' menu to manage installation.";
/** @tags */
export const STRING_DELETE_TAG = "Are you sure you want to delete this tag? Note: deleting a tag will not delete its notes.";
export const STRING_DELETE_TAG = "Are you sure you want to delete this tag? Note: deleting a tag will not delete its notes.";
/** @editor */
export const STRING_DELETED_NOTE = "The note you are attempting to edit has been deleted, and is awaiting sync. Changes you make will be disregarded.";
export const STRING_INVALID_NOTE = "The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.";
export const STRING_ELLIPSES = "...";
export const STRING_GENERIC_SAVE_ERROR = "There was an error saving your note. Please try again.";
export const STRING_DELETED_NOTE = "The note you are attempting to edit has been deleted, and is awaiting sync. Changes you make will be disregarded.";
export const STRING_INVALID_NOTE = "The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.";
export const STRING_ELLIPSES = "...";
export const STRING_GENERIC_SAVE_ERROR = "There was an error saving your note. Please try again.";
export const STRING_DELETE_PLACEHOLDER_ATTEMPT = "This note is a placeholder and cannot be deleted. To remove from your list, simply navigate to a different note.";
export const STRING_DELETE_LOCKED_ATTEMPT = "This note is locked. If you'd like to delete it, unlock it, and try again.";
export function StringDeleteNote({title, permanently}) {
export const STRING_DELETE_LOCKED_ATTEMPT = "This note is locked. If you'd like to delete it, unlock it, and try again.";
export function StringDeleteNote({ title, permanently }) {
return permanently
? `Are you sure you want to permanently delete ${title}?`
: `Are you sure you want to move ${title} to the trash?`;
}
export function StringEmptyTrash({count}) {
export function StringEmptyTrash({ count }) {
return `Are you sure you want to permanently delete ${count} note(s)?`;
}
/** @account */
export const STRING_ACCOUNT_MENU_UNCHECK_MERGE = "Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?";
export const STRING_SIGN_OUT_CONFIRMATION = "Are you sure you want to end your session? This will delete all local items and extensions.";
export const STRING_ERROR_DECRYPTING_IMPORT = "There was an error decrypting your items. Make sure the password you entered is correct and try again.";
export const STRING_E2E_ENABLED = "End-to-end encryption is enabled. Your data is encrypted on your device first, then synced to your private cloud.";
export const STRING_LOCAL_ENC_ENABLED = "Encryption is enabled. Your data is encrypted using your passcode before it is saved to your device storage.";
export const STRING_ENC_NOT_ENABLED = "Encryption is not enabled. Sign in, register, or add a passcode lock to enable encryption.";
export const STRING_IMPORT_SUCCESS = "Your data has been successfully imported.";
export const STRING_REMOVE_PASSCODE_CONFIRMATION = "Are you sure you want to remove your local passcode?";
export const STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM = " This will remove encryption from your local data.";
export const STRING_NON_MATCHING_PASSCODES = "The two passcodes you entered do not match. Please try again.";
export const STRING_NON_MATCHING_PASSWORDS = "The two passwords you entered do not match. Please try again.";
export const STRING_GENERATING_LOGIN_KEYS = "Generating Login Keys...";
export const STRING_GENERATING_REGISTER_KEYS = "Generating Account Keys...";
export const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a proper JSON file and try again.";
export function StringImportError({errorCount}) {
export const STRING_ACCOUNT_MENU_UNCHECK_MERGE = "Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?";
export const STRING_SIGN_OUT_CONFIRMATION = "Are you sure you want to end your session? This will delete all local items and extensions.";
export const STRING_ERROR_DECRYPTING_IMPORT = "There was an error decrypting your items. Make sure the password you entered is correct and try again.";
export const STRING_E2E_ENABLED = "End-to-end encryption is enabled. Your data is encrypted on your device first, then synced to your private cloud.";
export const STRING_LOCAL_ENC_ENABLED = "Encryption is enabled. Your data is encrypted using your passcode before it is saved to your device storage.";
export const STRING_ENC_NOT_ENABLED = "Encryption is not enabled. Sign in, register, or add a passcode lock to enable encryption.";
export const STRING_IMPORT_SUCCESS = "Your data has been successfully imported.";
export const STRING_REMOVE_PASSCODE_CONFIRMATION = "Are you sure you want to remove your local passcode?";
export const STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM = " This will remove encryption from your local data.";
export const STRING_NON_MATCHING_PASSCODES = "The two passcodes you entered do not match. Please try again.";
export const STRING_NON_MATCHING_PASSWORDS = "The two passwords you entered do not match. Please try again.";
export const STRING_GENERATING_LOGIN_KEYS = "Generating Login Keys...";
export const STRING_GENERATING_REGISTER_KEYS = "Generating Account Keys...";
export const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a proper JSON file and try again.";
export function StringImportError({ errorCount }) {
return `Import complete. ${errorCount} items were not imported because there was an error decrypting them. Make sure the password is correct and try again.`;
}

View File

@@ -20,6 +20,10 @@ export function isNullOrUndefined(value) {
return value === null || value === undefined;
}
export function dictToArray(dict) {
return Object.keys(dict).map((key) => dict[key]);
}
export function getPlatformString() {
try {
const platform = navigator.platform.toLowerCase();

11440
dist/javascripts/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long