Cleaned folder heirarchy

This commit is contained in:
Mo Bitar
2018-01-19 12:42:44 -06:00
parent 5be2402f65
commit 5d43697ed8
86 changed files with 75 additions and 91 deletions

View File

@@ -0,0 +1,594 @@
angular.module('app')
.directive("editorSection", function($timeout, $sce){
return {
restrict: 'E',
scope: {
save: "&",
remove: "&",
note: "=",
updateTags: "&"
},
templateUrl: 'editor.html',
replace: true,
controller: 'EditorCtrl',
controllerAs: 'ctrl',
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.note', function(note, oldNote){
if(note) {
ctrl.noteDidChange(note, oldNote);
}
});
}
}
})
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager) {
this.componentManager = componentManager;
this.componentStack = [];
$rootScope.$on("sync:taking-too-long", function(){
this.syncTakingTooLong = true;
}.bind(this));
$rootScope.$on("sync:completed", function(){
this.syncTakingTooLong = false;
}.bind(this));
$rootScope.$on("tag-changed", function(){
this.loadTagsString();
}.bind(this));
this.noteDidChange = function(note, oldNote) {
this.setNote(note, oldNote);
this.reloadComponentContext();
}
this.setNote = function(note, oldNote) {
this.showExtensions = false;
this.showMenu = false;
this.loadTagsString();
let onReady = () => {
this.noteReady = true;
$timeout(() => {
this.loadPreferences();
})
}
let associatedEditor = this.editorForNote(note);
if(associatedEditor && associatedEditor != this.selectedEditor) {
// setting note to not ready will remove the editor from view in a flash,
// so we only want to do this if switching between external editors
this.noteReady = false;
// switch after timeout, so that note data isnt posted to current editor
$timeout(() => {
this.selectedEditor = associatedEditor;
onReady();
})
} else if(associatedEditor) {
// Same editor as currently active
onReady();
} else {
// No editor
this.selectedEditor = null;
onReady();
}
if(note.safeText().length == 0 && note.dummy) {
this.focusTitle(100);
}
if(oldNote && oldNote != note) {
if(oldNote.hasChanges) {
this.save()(oldNote, null);
} else if(oldNote.dummy) {
this.remove()(oldNote);
}
}
}
this.editorForNote = function(note) {
let editors = componentManager.componentsForArea("editor-editor");
for(var editor of editors) {
if(editor.isActiveForItem(note)) {
return editor;
}
}
// No editor found for note. Use default editor, if note does not prefer system editor
if(!note.getAppDataItem("prefersPlainEditor")) {
return editors.filter((e) => {return e.isDefaultEditor()})[0];
}
}
this.onEditorMenuClick = function() {
// App bar menu item click
this.showEditorMenu = !this.showEditorMenu;
this.showMenu = false;
this.showExtensions = false;
}
this.editorMenuOnSelect = function(component) {
if(!component || component.area == "editor-editor") {
// if plain editor or other editor
this.showEditorMenu = false;
var editor = component;
if(this.selectedEditor && editor !== this.selectedEditor) {
this.disassociateComponentWithCurrentNote(this.selectedEditor);
}
if(editor) {
if(this.note.getAppDataItem("prefersPlainEditor") == true) {
this.note.setAppDataItem("prefersPlainEditor", false);
this.note.setDirty(true);
}
this.associateComponentWithCurrentNote(editor);
} else {
// Note prefers plain editor
if(!this.note.getAppDataItem("prefersPlainEditor")) {
this.note.setAppDataItem("prefersPlainEditor", true);
this.note.setDirty(true);
}
$timeout(() => {
this.reloadFont();
})
}
this.selectedEditor = editor;
} else if(component.area == "editor-stack") {
// If component stack item
this.toggleStackComponentForCurrentItem(component);
}
// Lots of dirtying can happen above, so we'll sync
syncManager.sync();
}.bind(this)
this.hasAvailableExtensions = function() {
return actionsManager.extensionsInContextOfItem(this.note).length > 0;
}
this.focusEditor = function(delay) {
setTimeout(function(){
var element = document.getElementById("note-text-editor");
if(element) {
element.focus();
}
}, delay)
}
this.focusTitle = function(delay) {
setTimeout(function(){
document.getElementById("note-title-editor").focus();
}, delay)
}
this.clickedTextArea = function() {
this.showMenu = false;
}
var statusTimeout;
this.saveNote = function($event) {
var note = this.note;
note.dummy = false;
this.save()(note, function(success){
if(success) {
if(statusTimeout) $timeout.cancel(statusTimeout);
statusTimeout = $timeout(function(){
this.saveError = false;
this.syncTakingTooLong = false;
this.showAllChangesSavedStatus();
}.bind(this), 200)
} else {
if(statusTimeout) $timeout.cancel(statusTimeout);
statusTimeout = $timeout(function(){
this.saveError = true;
this.syncTakingTooLong = false;
this.showErrorStatus();
}.bind(this), 200)
}
}.bind(this));
}
this.saveTitle = function($event) {
$event.target.blur();
this.saveNote($event);
this.focusEditor();
}
var saveTimeout;
this.changesMade = function() {
this.note.hasChanges = true;
this.note.dummy = false;
if(saveTimeout) $timeout.cancel(saveTimeout);
if(statusTimeout) $timeout.cancel(statusTimeout);
saveTimeout = $timeout(function(){
this.showSavingStatus();
this.saveNote();
}.bind(this), 275)
}
this.showSavingStatus = function() {
this.noteStatus = $sce.trustAsHtml("Saving...");
}
this.showAllChangesSavedStatus = function() {
var status = "All changes saved";
if(authManager.offline()) {
status += " (offline)";
}
this.noteStatus = $sce.trustAsHtml(status);
}
this.showErrorStatus = function() {
this.noteStatus = $sce.trustAsHtml("Error syncing<br>(changes saved offline)")
}
this.contentChanged = function() {
this.changesMade();
}
this.nameChanged = function() {
this.changesMade();
}
this.onNameFocus = function() {
this.editingName = true;
}
this.onContentFocus = function() {
$rootScope.$broadcast("editorFocused");
}
this.onNameBlur = function() {
this.editingName = false;
}
this.toggleFullScreen = function() {
this.fullscreen = !this.fullscreen;
if(this.fullscreen) {
this.focusEditor(0);
}
}
this.selectedMenuItem = function($event) {
this.showMenu = false;
}
this.deleteNote = function() {
let title = this.note.safeTitle().length ? `'${this.note.title}'` : "this note";
if(confirm(`Are you sure you want to delete ${title}?`)) {
this.remove()(this.note);
this.showMenu = false;
}
}
this.togglePin = function() {
this.note.setAppDataItem("pinned", !this.note.pinned);
this.note.setDirty(true);
this.changesMade();
}
this.toggleArchiveNote = function() {
this.note.setAppDataItem("archived", !this.note.archived);
this.note.setDirty(true);
this.changesMade();
$rootScope.$broadcast("noteArchived");
}
this.clickedEditNote = function() {
this.focusEditor(100);
}
/*
Tags
*/
this.loadTagsString = function() {
var string = "";
for(var tag of this.note.tags) {
string += "#" + tag.title + " ";
}
this.tagsString = string;
}
this.addTag = function(tag) {
var tags = this.note.tags;
var strings = tags.map(function(_tag){
return _tag.title;
})
strings.push(tag.title);
this.updateTags()(this.note, strings);
this.loadTagsString();
}
this.removeTag = function(tag) {
var tags = this.note.tags;
var strings = tags.map(function(_tag){
return _tag.title;
}).filter(function(_tag){
return _tag !== tag.title;
})
this.updateTags()(this.note, strings);
this.loadTagsString();
}
this.updateTagsFromTagsString = function() {
var tags = this.tagsString.split("#");
tags = _.filter(tags, function(tag){
return tag.length > 0;
})
tags = _.map(tags, function(tag){
return tag.trim();
})
this.note.dummy = false;
this.updateTags()(this.note, tags);
}
/* Resizability */
this.resizeControl = {};
this.onPanelResizeFinish = function(width, left, isMaxWidth) {
if(isMaxWidth) {
authManager.setUserPrefValue("editorWidth", null);
} else {
if(width !== undefined && width !== null) {
authManager.setUserPrefValue("editorWidth", width);
}
}
if(left !== undefined && left !== null) {
authManager.setUserPrefValue("editorLeft", left);
}
authManager.syncUserPreferences();
}
$rootScope.$on("user-preferences-changed", () => {
this.loadPreferences();
});
this.loadPreferences = function() {
this.monospaceFont = authManager.getUserPrefValue("monospaceFont", "monospace");
if(!document.getElementById("editor-content")) {
// Elements have not yet loaded due to ng-if around wrapper
return;
}
this.reloadFont();
let width = authManager.getUserPrefValue("editorWidth", null);
if(width !== null) {
this.resizeControl.setWidth(width);
}
let left = authManager.getUserPrefValue("editorLeft", null);
if(left !== null) {
this.resizeControl.setLeft(left);
}
}
this.reloadFont = function() {
var editable = document.getElementById("note-text-editor");
if(!editable) {
return;
}
if(this.monospaceFont) {
if(isMacApplication()) {
editable.style.fontFamily = "Menlo, Consolas, 'DejaVu Sans Mono', monospace";
} else {
editable.style.fontFamily = "monospace";
}
} else {
editable.style.fontFamily = "inherit";
}
}
this.toggleKey = function(key) {
this[key] = !this[key];
authManager.setUserPrefValue(key, this[key], true);
this.reloadFont();
}
/*
Components
*/
componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack", "editor-editor"], activationHandler: function(component){
if(component.area === "note-tags") {
// Autocomplete Tags
this.tagsComponent = component.active ? component : null;
} else if(component.area == "editor-stack") {
// Stack
if(component.active) {
if(!_.find(this.componentStack, component)) {
this.componentStack.push(component);
}
} else {
_.pull(this.componentStack, component);
}
} else {
// Editor
if(component.active && this.note && (component.isActiveForItem(this.note) || component.isDefaultEditor())) {
this.selectedEditor = component;
} else {
this.selectedEditor = null;
}
}
}.bind(this), contextRequestHandler: function(component){
return this.note;
}.bind(this), actionHandler: function(component, action, data){
if(action === "set-size") {
var setSize = function(element, size) {
var widthString = typeof size.width === 'string' ? size.width : `${data.width}px`;
var heightString = typeof size.height === 'string' ? size.height : `${data.height}px`;
element.setAttribute("style", `width:${widthString}; height:${heightString}; `);
}
if(data.type == "container") {
if(component.area == "note-tags") {
var container = document.getElementById("note-tags-component-container");
setSize(container, data);
}
}
}
else if(action === "associate-item") {
if(data.item.content_type == "Tag") {
var tag = modelManager.findItem(data.item.uuid);
this.addTag(tag);
}
}
else if(action === "deassociate-item") {
var tag = modelManager.findItem(data.item.uuid);
this.removeTag(tag);
}
else if(action === "save-items" || action === "save-success" || action == "save-error") {
if(data.items.map((item) => {return item.uuid}).includes(this.note.uuid)) {
if(action == "save-items") {
if(this.componentSaveTimeout) $timeout.cancel(this.componentSaveTimeout);
this.componentSaveTimeout = $timeout(this.showSavingStatus.bind(this), 10);
}
else {
if(this.componentStatusTimeout) $timeout.cancel(this.componentStatusTimeout);
if(action == "save-success") {
this.componentStatusTimeout = $timeout(this.showAllChangesSavedStatus.bind(this), 400);
} else {
this.componentStatusTimeout = $timeout(this.showErrorStatus.bind(this), 400);
}
}
}
}
}.bind(this)});
this.reloadComponentContext = function() {
componentManager.contextItemDidChangeInArea("note-tags");
componentManager.contextItemDidChangeInArea("editor-stack");
componentManager.contextItemDidChangeInArea("editor-editor");
var stack = componentManager.componentsForArea("editor-stack");
for(var component of stack) {
var activeForItem = component.isActiveForItem(this.note);
if(activeForItem) {
if(!component.active) {
componentManager.activateComponent(component);
}
} else {
if(component.active) {
componentManager.deactivateComponent(component);
}
}
}
}
this.toggleStackComponentForCurrentItem = function(component) {
if(component.isActiveForItem(this.note)) {
componentManager.deactivateComponent(component);
this.disassociateComponentWithCurrentNote(component);
} else {
componentManager.activateComponent(component);
componentManager.contextItemDidChangeInArea("editor-stack");
this.associateComponentWithCurrentNote(component);
}
}
this.disassociateComponentWithCurrentNote = function(component) {
component.associatedItemIds = component.associatedItemIds.filter((id) => {return id !== this.note.uuid});
// Only disassociative components should modify the disassociatedItemIds
if(!component.isAssociative() && !component.disassociatedItemIds.includes(this.note.uuid)) {
component.disassociatedItemIds.push(this.note.uuid);
}
component.setDirty(true);
}
this.associateComponentWithCurrentNote = function(component) {
component.disassociatedItemIds = component.disassociatedItemIds.filter((id) => {return id !== this.note.uuid});
if(component.isAssociative() && !component.associatedItemIds.includes(this.note.uuid)) {
component.associatedItemIds.push(this.note.uuid);
}
component.setDirty(true);
}
/*
Editor Customization
*/
this.onSystemEditorLoad = function() {
if(this.loadedTabListener) {
return;
}
this.loadedTabListener = true;
/**
* Insert 4 spaces when a tab key is pressed,
* only used when inside of the text editor.
* If the shift key is pressed first, this event is
* not fired.
*/
var parent = this;
var handleTab = function (event) {
if (!event.shiftKey && event.which == 9) {
event.preventDefault();
// Using document.execCommand gives us undo support
if(!document.execCommand("insertText", false, "\t")) {
// document.execCommand works great on Chrome/Safari but not Firefox
var start = this.selectionStart;
var end = this.selectionEnd;
var spaces = " ";
// Insert 4 spaces
this.value = this.value.substring(0, start)
+ spaces + this.value.substring(end);
// Place cursor 4 spaces away from where
// the tab key was pressed
this.selectionStart = this.selectionEnd = start + 4;
}
parent.note.text = this.value;
parent.changesMade();
}
}
var element = document.getElementById("note-text-editor");
element.addEventListener('keydown', handleTab);
angular.element(element).on('$destroy', function(){
window.removeEventListener('keydown', handleTab);
this.loadedTabListener = false;
}.bind(this))
}
});

View File

@@ -0,0 +1,154 @@
angular.module('app')
.directive("footer", function(authManager){
return {
restrict: 'E',
scope: {},
templateUrl: 'footer.html',
replace: true,
controller: 'FooterCtrl',
controllerAs: 'ctrl',
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
scope.$on("sync:updated_token", function(){
ctrl.syncUpdated();
ctrl.findErrors();
ctrl.updateOfflineStatus();
})
scope.$on("sync:error", function(){
ctrl.findErrors();
ctrl.updateOfflineStatus();
})
}
}
})
.controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager,
syncManager, storageManager, passcodeManager, componentManager, singletonManager, packageManager) {
this.getUser = function() {
return authManager.user;
}
this.updateOfflineStatus = function() {
this.offline = authManager.offline();
}
this.updateOfflineStatus();
if(this.offline && !passcodeManager.hasPasscode()) {
this.showAccountMenu = true;
}
this.findErrors = function() {
this.error = syncManager.syncStatus.error;
}
this.findErrors();
this.onAuthSuccess = function() {
this.showAccountMenu = false;
}.bind(this)
this.closeAccountMenu = () => {
this.showAccountMenu = false;
}
this.accountMenuPressed = function() {
this.showAccountMenu = !this.showAccountMenu;
}
this.toggleExtensions = function() {
this.showExtensionsMenu = !this.showExtensionsMenu;
}
this.hasPasscode = function() {
return passcodeManager.hasPasscode();
}
this.lockApp = function() {
$rootScope.lockApplication();
}
this.refreshData = function() {
this.isRefreshing = true;
syncManager.sync(function(response){
$timeout(function(){
this.isRefreshing = false;
}.bind(this), 200)
if(response && response.error) {
alert("There was an error syncing. Please try again. If all else fails, log out and log back in.");
} else {
this.syncUpdated();
}
}.bind(this));
}
this.syncUpdated = function() {
this.lastSyncDate = new Date();
}
$rootScope.$on("new-update-available", function(version){
$timeout(function(){
// timeout calls apply() which is needed
this.onNewUpdateAvailable();
}.bind(this))
}.bind(this))
this.onNewUpdateAvailable = function() {
this.newUpdateAvailable = true;
}
this.clickedNewUpdateAnnouncement = function() {
this.newUpdateAvailable = false;
alert("A new update is ready to install. Updates address performance and security issues, as well as bug fixes and feature enhancements. Simply quit Standard Notes and re-open it for the update to be applied.")
}
/* Rooms */
this.componentManager = componentManager;
this.rooms = [];
modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => {
var incomingRooms = allItems.filter((candidate) => {return candidate.area == "rooms"});
this.rooms = _.uniq(this.rooms.concat(incomingRooms)).filter((candidate) => {return !candidate.deleted});
});
componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => {
if(component.active) {
// Show room, if it was not activated manually (in the event of event from componentManager)
if(component.area == "rooms" && !component.showRoom) {
this.selectRoom(component);
}
$timeout(() => {
var lastSize = component.getLastSize();
if(lastSize) {
componentManager.handleSetSizeEvent(component, lastSize);
}
});
}
}, actionHandler: (component, action, data) => {
if(action == "set-size") {
component.setLastSize(data);
}
}});
this.selectRoom = function(room) {
// Allows us to send messages to component modal directive
if(!room.directiveController) {
room.directiveController = {onDismiss: () => {
room.showRoom = false;
}};
}
// Make sure to call dismiss() before setting new showRoom value
// This way the directive stays alive long enough to deactivate the associated component
// (The directive's life is at the mercy of "ng-if" => "room.showRoom")
if(room.showRoom) {
room.directiveController.dismiss(() => {
});
} else {
room.showRoom = true;
}
}
});

View File

@@ -0,0 +1,281 @@
angular.module('app')
.controller('HomeCtrl', function ($scope, $location, $rootScope, $timeout, modelManager,
dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager) {
storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession());
$scope.onUpdateAvailable = function(version) {
$rootScope.$broadcast('new-update-available', version);
}
/* Used to avoid circular dependencies where syncManager cannot be imported but rootScope can */
$rootScope.sync = function() {
syncManager.sync();
}
$rootScope.lockApplication = function() {
// Reloading wipes current objects from memory
window.location.reload();
}
function load() {
// pass keys to storageManager to decrypt storage
storageManager.setKeys(passcodeManager.keys());
openDatabase();
// Retrieve local data and begin sycing timer
initiateSync();
// Configure "All" psuedo-tag
loadAllTag();
// Configure "Archived" psuedo-tag
loadArchivedTag();
}
if(passcodeManager.isLocked()) {
$scope.needsUnlock = true;
} else {
load();
}
$scope.onSuccessfulUnlock = function() {
$timeout(() => {
$scope.needsUnlock = false;
load();
})
}
function openDatabase() {
dbManager.setLocked(false);
dbManager.openDatabase(null, function() {
// new database, delete syncToken so that items can be refetched entirely from server
syncManager.clearSyncToken();
syncManager.sync();
})
}
function initiateSync() {
authManager.loadInitialData();
syncManager.loadLocalItems(function(items) {
$scope.allTag.didLoad = true;
themeManager.activateInitialTheme();
$scope.$apply();
$rootScope.$broadcast("initial-data-loaded");
syncManager.sync(null);
// refresh every 30s
setInterval(function () {
syncManager.sync(null);
}, 30000);
});
}
function loadAllTag() {
var allTag = new Tag({all: true, title: "All"});
allTag.needsLoad = true;
$scope.allTag = allTag;
$scope.tags = modelManager.tags;
$scope.allTag.notes = modelManager.notes;
}
function loadArchivedTag() {
var archiveTag = new Tag({archiveTag: true, title: "Archived"});
$scope.archiveTag = archiveTag;
$scope.archiveTag.notes = modelManager.notes;
}
/*
Editor Callbacks
*/
$scope.updateTagsForNote = function(note, stringTags) {
var toRemove = [];
for(var tag of note.tags) {
if(stringTags.indexOf(tag.title) === -1) {
// remove this tag
toRemove.push(tag);
}
}
for(var tagToRemove of toRemove) {
note.removeItemAsRelationship(tagToRemove);
tagToRemove.removeItemAsRelationship(note);
tagToRemove.setDirty(true);
}
var tags = [];
for(var tagString of stringTags) {
var existingRelationship = _.find(note.tags, {title: tagString});
if(!existingRelationship) {
tags.push(modelManager.findOrCreateTagByTitle(tagString));
}
}
for(var tag of tags) {
modelManager.createRelationshipBetweenItems(note, tag);
}
note.setDirty(true);
syncManager.sync();
}
/*
Tags Ctrl Callbacks
*/
$scope.tagsWillMakeSelection = function(tag) {
}
$scope.tagsSelectionMade = function(tag) {
$scope.selectedTag = tag;
if($scope.selectedNote && $scope.selectedNote.dummy) {
modelManager.removeItemLocally($scope.selectedNote);
}
}
$scope.tagsAddNew = function(tag) {
modelManager.addItem(tag);
}
$scope.tagsSave = function(tag, callback) {
if(!tag.title || tag.title.length == 0) {
$scope.removeTag(tag);
return;
}
tag.setDirty(true);
syncManager.sync(callback);
$rootScope.$broadcast("tag-changed");
modelManager.resortTag(tag);
}
/*
Notes Ctrl Callbacks
*/
$scope.removeTag = function(tag) {
if(confirm("Are you sure you want to delete this tag? Note: deleting a tag will not delete its notes.")) {
modelManager.setItemToBeDeleted(tag);
// if no more notes, delete tag
syncManager.sync(function(){
// force scope tags to update on sub directives
$scope.safeApply();
});
}
}
$scope.notesSelectionMade = function(note) {
$scope.selectedNote = note;
}
$scope.notesAddNew = function(note) {
modelManager.addItem(note);
if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag) {
modelManager.createRelationshipBetweenItems($scope.selectedTag, note);
}
}
/*
Shared Callbacks
*/
$scope.saveNote = function(note, callback) {
note.setDirty(true);
syncManager.sync(function(response){
if(response && response.error) {
if(!$scope.didShowErrorAlert) {
$scope.didShowErrorAlert = true;
alert("There was an error saving your note. Please try again.");
}
if(callback) {
callback(false);
}
} else {
note.hasChanges = false;
if(callback) {
callback(true);
}
}
})
}
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest')
this.$eval(fn);
else
this.$apply(fn);
};
$scope.notifyDelete = function() {
$timeout(function() {
$rootScope.$broadcast("noteDeleted");
}.bind(this), 0);
}
$scope.deleteNote = function(note) {
modelManager.setItemToBeDeleted(note);
if(note == $scope.selectedNote) {
$scope.selectedNote = null;
}
if(note.dummy) {
modelManager.removeItemLocally(note);
$scope.notifyDelete();
return;
}
syncManager.sync(function(){
if(authManager.offline()) {
// when deleting items while ofline, we need to explictly tell angular to refresh UI
setTimeout(function () {
$scope.notifyDelete();
$scope.safeApply();
}, 50);
} else {
$scope.notifyDelete();
}
});
}
// Handle Auto Sign In From URL
function urlParam(key) {
return $location.search()[key];
}
function autoSignInFromParams() {
var server = urlParam("server");
var email = urlParam("email");
var pw = urlParam("pw");
if(!authManager.offline()) {
// check if current account
if(syncManager.serverURL === server && authManager.user.email === email) {
// already signed in, return
return;
} else {
// sign out
syncManager.destroyLocalData(function(){
window.location.reload();
})
}
} else {
authManager.login(server, email, pw, false, function(response){
window.location.reload();
})
}
}
if(urlParam("server")) {
autoSignInFromParams();
}
});

View File

@@ -0,0 +1,30 @@
class LockScreen {
constructor() {
this.restrict = "E";
this.templateUrl = "lock-screen.html";
this.scope = {
onSuccess: "&",
};
}
controller($scope, passcodeManager) {
'ngInject';
$scope.formData = {};
$scope.submitPasscodeForm = function() {
passcodeManager.unlock($scope.formData.passcode, (success) => {
if(!success) {
alert("Invalid passcode. Please try again.");
return;
}
$scope.onSuccess()();
})
}
}
}
angular.module('app').directive('lockScreen', () => new LockScreen);

View File

@@ -0,0 +1,253 @@
angular.module('app')
.directive("notesSection", function(){
return {
scope: {
addNew: "&",
selectionMade: "&",
tag: "="
},
templateUrl: 'notes.html',
replace: true,
controller: 'NotesCtrl',
controllerAs: 'ctrl',
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.tag', function(tag, oldTag){
if(tag) {
if(tag.needsLoad) {
scope.$watch('ctrl.tag.didLoad', function(didLoad){
if(didLoad) {
tag.needsLoad = false;
ctrl.tagDidChange(tag, oldTag);
}
});
} else {
ctrl.tagDidChange(tag, oldTag);
}
}
});
}
}
})
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager, storageManager) {
this.panelController = {};
$rootScope.$on("user-preferences-changed", () => {
this.loadPreferences();
});
this.loadPreferences = function() {
this.sortBy = authManager.getUserPrefValue("sortBy", "created_at");
this.sortDescending = this.sortBy != "title";
this.showArchived = authManager.getUserPrefValue("showArchived", false);
this.hidePinned = authManager.getUserPrefValue("hidePinned", false);
this.hideNotePreview = authManager.getUserPrefValue("hideNotePreview", false);
this.hideDate = authManager.getUserPrefValue("hideDate", false);
this.hideTags = authManager.getUserPrefValue("hideTags", false);
let width = authManager.getUserPrefValue("notesPanelWidth");
if(width) {
this.panelController.setWidth(width);
}
}
this.loadPreferences();
this.onPanelResize = function(newWidth) {
authManager.setUserPrefValue("notesPanelWidth", newWidth);
authManager.syncUserPreferences();
}
angular.element(document).ready(() => {
this.loadPreferences();
});
$rootScope.$on("editorFocused", function(){
this.showMenu = false;
}.bind(this))
$rootScope.$on("noteDeleted", function() {
$timeout(this.onNoteRemoval.bind(this));
}.bind(this))
$rootScope.$on("noteArchived", function() {
$timeout(this.onNoteRemoval.bind(this));
}.bind(this));
// When a note is removed from the list
this.onNoteRemoval = function() {
let visibleNotes = this.visibleNotes();
if(this.selectedIndex < visibleNotes.length) {
this.selectNote(visibleNotes[this.selectedIndex]);
} else {
this.selectNote(visibleNotes[visibleNotes.length - 1]);
}
}
this.DefaultNotesToDisplayValue = 20;
this.notesToDisplay = this.DefaultNotesToDisplayValue;
this.paginate = function() {
this.notesToDisplay += this.DefaultNotesToDisplayValue
}
this.panelTitle = function() {
if(this.noteFilter.text.length) {
return `${this.tag.notes.filter((i) => {return i.visible;}).length} search results`;
} else if(this.tag) {
return `${this.tag.title} notes`;
}
}
this.optionsSubtitle = function() {
var base = "";
if(this.sortBy == "created_at") {
base += " Date Added";
} else if(this.sortBy == "updated_at") {
base += " Date Modifed";
} else if(this.sortBy == "title") {
base += " Title";
}
if(this.showArchived && (!this.tag || !this.tag.archiveTag)) {
base += " | + Archived"
}
if(this.hidePinned) {
base += " | Pinned"
}
return base;
}
this.toggleKey = function(key) {
this[key] = !this[key];
authManager.setUserPrefValue(key, this[key]);
authManager.syncUserPreferences();
}
this.tagDidChange = function(tag, oldTag) {
var scrollable = document.getElementById("notes-scrollable");
if(scrollable) {
scrollable.scrollTop = 0;
scrollable.scrollLeft = 0;
}
this.notesToDisplay = this.DefaultNotesToDisplayValue;
this.showMenu = false;
if(this.selectedNote && this.selectedNote.dummy) {
if(oldTag) {
_.remove(oldTag.notes, this.selectedNote);
}
}
this.noteFilter.text = "";
this.setNotes(tag.notes);
}
this.setNotes = function(notes) {
notes.forEach(function(note){
note.visible = true;
})
var createNew = notes.length == 0;
this.selectFirstNote(createNew);
}
this.visibleNotes = function() {
return this.sortedNotes.filter(function(note){
return note.visible;
});
}
this.selectFirstNote = function(createNew) {
var visibleNotes = this.visibleNotes();
if(visibleNotes.length > 0) {
this.selectNote(visibleNotes[0]);
} else if(createNew) {
this.createNewNote();
}
}
this.selectNote = function(note) {
if(!note) { return; }
this.selectedNote = note;
note.conflict_of = null; // clear conflict
this.selectionMade()(note);
}
this.createNewNote = function() {
var title = "New Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : "");
this.newNote = modelManager.createItem({content_type: "Note", dummy: true, text: ""});
this.newNote.title = title;
this.selectNote(this.newNote);
this.addNew()(this.newNote);
}
this.noteFilter = {text : ''};
this.filterNotes = function(note) {
if(this.tag.archiveTag) {
note.visible = note.archived;
return note.visible;
}
if((note.archived && !this.showArchived) || (note.pinned && this.hidePinned)) {
note.visible = false;
return note.visible;
}
var filterText = this.noteFilter.text.toLowerCase();
if(filterText.length == 0) {
note.visible = true;
} else {
var words = filterText.split(" ");
var matchesTitle = words.every(function(word) { return note.safeTitle().toLowerCase().indexOf(word) >= 0; });
var matchesBody = words.every(function(word) { return note.safeText().toLowerCase().indexOf(word) >= 0; });
note.visible = matchesTitle || matchesBody;
}
return note.visible;
}.bind(this)
this.filterTextChanged = function() {
$timeout(function(){
if(!this.selectedNote.visible) {
this.selectFirstNote(false);
}
}.bind(this), 100)
}
this.selectedMenuItem = function($event) {
this.showMenu = false;
}
this.selectedSortByCreated = function() {
this.setSortBy("created_at");
this.sortDescending = true;
}
this.selectedSortByUpdated = function() {
this.setSortBy("updated_at");
this.sortDescending = true;
}
this.selectedSortByTitle = function() {
this.setSortBy("title");
this.sortDescending = false;
}
this.setSortBy = function(type) {
this.sortBy = type;
authManager.setUserPrefValue("sortBy", this.sortBy);
authManager.syncUserPreferences();
}
});

View File

@@ -0,0 +1,163 @@
angular.module('app')
.directive("tagsSection", function(){
return {
restrict: 'E',
scope: {
addNew: "&",
selectionMade: "&",
willSelect: "&",
save: "&",
tags: "=",
allTag: "=",
archiveTag: "=",
updateNoteTag: "&",
removeTag: "&"
},
templateUrl: 'tags.html',
replace: true,
controller: 'TagsCtrl',
controllerAs: 'ctrl',
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.tags', function(newTags){
if(newTags) {
ctrl.setTags(newTags);
}
});
scope.$watch('ctrl.allTag', function(allTag){
if(allTag) {
ctrl.setAllTag(allTag);
}
});
}
}
})
.controller('TagsCtrl', function ($rootScope, modelManager, $timeout, componentManager, authManager) {
var initialLoad = true;
this.panelController = {};
$rootScope.$on("user-preferences-changed", () => {
this.loadPreferences();
});
this.loadPreferences = function() {
let width = authManager.getUserPrefValue("tagsPanelWidth");
if(width) {
this.panelController.setWidth(width);
}
}
this.loadPreferences();
this.onPanelResize = function(newWidth) {
authManager.setUserPrefValue("tagsPanelWidth", newWidth);
authManager.syncUserPreferences();
}
this.componentManager = componentManager;
componentManager.registerHandler({identifier: "tags", areas: ["tags-list"], activationHandler: function(component){
this.component = component;
}.bind(this), contextRequestHandler: function(component){
return null;
}.bind(this), actionHandler: function(component, action, data){
if(action === "select-item") {
var tag = modelManager.findItem(data.item.uuid);
if(tag) {
this.selectTag(tag);
}
}
else if(action === "clear-selection") {
this.selectTag(this.allTag);
}
}.bind(this)});
this.setAllTag = function(allTag) {
this.selectTag(this.allTag);
}
this.setTags = function(tags) {
if(initialLoad) {
initialLoad = false;
this.selectTag(this.allTag);
} else {
if(tags && tags.length > 0) {
this.selectTag(tags[0]);
}
}
}
this.selectTag = function(tag) {
this.willSelect()(tag);
this.selectedTag = tag;
tag.conflict_of = null; // clear conflict
this.selectionMade()(tag);
}
this.clickedAddNewTag = function() {
if(this.editingTag) {
return;
}
this.newTag = modelManager.createItem({content_type: "Tag"});
this.selectedTag = this.newTag;
this.editingTag = this.newTag;
this.addNew()(this.newTag);
}
this.tagTitleDidChange = function(tag) {
this.editingTag = tag;
}
this.saveTag = function($event, tag) {
this.editingTag = null;
$event.target.blur();
if(!tag.title || tag.title.length == 0) {
if(originalTagName) {
tag.title = originalTagName;
originalTagName = null;
} else {
// newly created tag without content
modelManager.removeItemLocally(tag);
}
return;
}
this.save()(tag, function(savedTag){
this.selectTag(tag);
this.newTag = null;
}.bind(this));
}
function inputElementForTag(tag) {
return document.getElementById("tag-" + tag.uuid);
}
var originalTagName = "";
this.selectedRenameTag = function($event, tag) {
originalTagName = tag.title;
this.editingTag = tag;
$timeout(function(){
inputElementForTag(tag).focus();
})
}
this.selectedDeleteTag = function(tag) {
this.removeTag()(tag);
}
this.noteCount = function(tag) {
var validNotes = Note.filterDummyNotes(tag.notes).filter(function(note){
return !note.archived;
});
return validNotes.length;
}
});