Files
standardnotes-app-web/vendor/assets/javascripts/transpiled.js
Mo Bitar 6a2e3e9ec1 es6
2016-12-15 12:52:34 -06:00

2309 lines
67 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
var _set = function set(object, property, value, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent !== null) { set(parent, property, value, receiver); } } else if ("value" in desc && desc.writable) { desc.value = value; } else { var setter = desc.set; if (setter !== undefined) { setter.call(receiver, value); } } return value; };
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Neeto = Neeto || {};
angular.module('app.frontend', ['ui.router', 'ng-token-auth', 'restangular', 'ipCookie', 'oc.lazyLoad', 'angularLazyImg', 'ngDialog'])
// Configure path to API
.config(function (RestangularProvider, apiControllerProvider) {
var url = apiControllerProvider.defaultServerURL();
RestangularProvider.setBaseUrl(url);
console.log(url);
RestangularProvider.setFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
var token = localStorage.getItem("jwt");
if (token) {
headers = _.extend(headers, { Authorization: "Bearer " + localStorage.getItem("jwt") });
}
return {
element: element,
params: params,
headers: headers,
httpConfig: httpConfig
};
});
});
// Shared function for configure auth service. Can be overwritten.
function configureAuth($authProvider, apiControllerProvider) {
var url = apiControllerProvider.defaultServerURL();
$authProvider.configure([{
default: {
apiUrl: url,
passwordResetSuccessUrl: window.location.protocol + '//' + window.location.host + '/auth/reset'
}
}]);
}
;angular.module('app.frontend').config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
$stateProvider.state('base', {
abstract: true
})
// Homepage
.state('home', {
url: '/',
parent: 'base',
views: {
'content@': {
templateUrl: 'frontend/home.html',
controller: 'HomeCtrl'
}
}
}).state('presentation', {
url: '/:root_path',
parent: 'base',
views: {
'content@': {
templateUrl: 'frontend/presentation.html',
controller: "PresentationCtrl"
}
},
resolve: {
presentation: getPresentation
}
}).state('group', {
url: '/:root_path/:secondary_path',
parent: 'base',
views: {
'content@': {
templateUrl: 'frontend/presentation.html',
controller: "PresentationCtrl"
}
},
resolve: {
presentation: getPresentation
}
})
// Auth routes
.state('auth', {
abstract: true,
url: '/auth',
parent: 'base',
views: {
'content@': {
templateUrl: 'frontend/auth/wrapper.html'
}
}
}).state('auth.login', {
url: '/login',
templateUrl: 'frontend/auth/login.html'
}).state('auth.forgot', {
url: '/forgot',
templateUrl: 'frontend/auth/forgot.html'
}).state('auth.reset', {
url: '/reset?reset_password_token&email',
templateUrl: 'frontend/auth/reset.html',
controller: function controller($rootScope, $stateParams) {
$rootScope.resetData = { reset_password_token: $stateParams.reset_password_token, email: $stateParams.email };
// Clear reset_password_token on change state
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams, options) {
$rootScope.reset_password_token = null;
});
}
})
// 404 Error
.state('404', {
parent: 'base',
views: {
'content@': {
templateUrl: 'frontend/errors/404.html'
}
}
});
function getPresentation($q, $state, $stateParams, Restangular) {
var deferred = $q.defer();
var restangularQuery = Restangular.one('presentations', 'show_by_path');
restangularQuery.get({ root_path: $stateParams.root_path, secondary_path: $stateParams.secondary_path }).then(function (response) {
deferred.resolve(response);
}).catch(function (response) {
$state.go('404');
});
return deferred.promise;
}
// Default fall back route
$urlRouterProvider.otherwise(function ($injector, $location) {
var state = $injector.get('$state');
state.go('404');
return $location.path();
});
// enable HTML5 Mode for SEO
$locationProvider.html5Mode(true);
});
;
var BaseCtrl = function BaseCtrl($rootScope, modelManager) {
// $rootScope.resetPasswordSubmit = function() {
// var new_keys = Neeto.crypto.generateEncryptionKeysForUser($rootScope.resetData.password, $rootScope.resetData.email);
// var data = _.clone($rootScope.resetData);
// data.password = new_keys.pw;
// data.password_confirmation = new_keys.pw;
// $auth.updatePassword(data);
// apiController.setGk(new_keys.gk);
// }
// var note = new Note();
// note.content = {title: "hello", text: "world"};
// console.log("note content", note.content);
// console.log("note title", note.title);
// console.log("note json", JSON.stringify(note));
//
// console.log("Copy", _.cloneDeep(note));
_classCallCheck(this, BaseCtrl);
};
angular.module('app.frontend').controller('BaseCtrl', BaseCtrl);
;angular.module('app.frontend').directive("editorSection", function ($timeout) {
return {
restrict: 'E',
scope: {
save: "&",
remove: "&",
note: "=",
user: "="
},
templateUrl: 'frontend/editor.html',
replace: true,
controller: 'EditorCtrl',
controllerAs: 'ctrl',
bindToController: true,
link: function link(scope, elem, attrs, ctrl) {
var handler = function handler(event) {
if (event.ctrlKey || event.metaKey) {
switch (String.fromCharCode(event.which).toLowerCase()) {
case 's':
event.preventDefault();
$timeout(function () {
ctrl.saveNote(event);
});
break;
case 'e':
event.preventDefault();
$timeout(function () {
ctrl.clickedEditNote();
});
break;
case 'm':
event.preventDefault();
$timeout(function () {
ctrl.toggleMarkdown();
});
break;
case 'o':
event.preventDefault();
$timeout(function () {
ctrl.toggleFullScreen();
});
break;
}
}
};
window.addEventListener('keydown', handler);
scope.$on('$destroy', function () {
window.removeEventListener('keydown', handler);
});
scope.$watch('ctrl.note', function (note, oldNote) {
if (note) {
ctrl.setNote(note, oldNote);
} else {
ctrl.note = {};
}
});
}
};
}).controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope) {
this.demoNotes = [{ title: "Live print a file with tail", content: "tail -f log/production.log" }, { title: "Create SSH tunnel", content: "ssh -i .ssh/key.pem -N -L 3306:example.com:3306 ec2-user@example.com" }, { title: "List of processes running on port", content: "lsof -i:8080" }, { title: "Set ENV from file", content: "export $(cat .envfile | xargs)" }, { title: "Find process by name", content: "ps -ax | grep <application name>" }, { title: "NPM install without sudo", content: "sudo chown -R $(whoami) ~/.npm" }, { title: "Email validation regex", content: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" }, { title: "Ruby generate 256 bit key", content: "Digest::SHA256.hexdigest(SecureRandom.random_bytes(32))" }, { title: "Mac add user to user group", content: "sudo dseditgroup -o edit -a USERNAME -t user GROUPNAME" }, { title: "Kill Mac OS System Apache", content: "sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist" }, { title: "Docker run with mount binding and port", content: "docker run -v /home/vagrant/www/app:/var/www/app -p 8080:80 -d kpi/s3" }, { title: "MySQL grant privileges", content: "GRANT [type of permission] ON [database name].[table name] TO [username]@'%;" }, { title: "MySQL list users", content: "SELECT User FROM mysql.user;" }];
this.showSampler = !this.user.id && this.user.filteredNotes().length == 0;
this.demoNoteNames = _.map(this.demoNotes, function (note) {
return note.title;
});
this.currentDemoContent = { text: null };
this.prebeginFn = function () {
this.currentDemoContent.text = null;
}.bind(this);
this.callback = function (index) {
this.currentDemoContent.text = this.demoNotes[index].text;
}.bind(this);
this.contentCallback = function (index) {};
this.setNote = function (note, oldNote) {
this.editorMode = 'edit';
if (note.content.text.length == 0) {
this.focusTitle(100);
}
if (oldNote && oldNote != note) {
if (oldNote.hasChanges) {
this.save()(oldNote, null);
} else if (oldNote.dummy) {
this.remove()(oldNote);
}
}
};
this.onPreviewDoubleClick = function () {
this.editorMode = 'edit';
this.focusEditor(100);
};
this.focusEditor = function (delay) {
setTimeout(function () {
var element = document.getElementById("note-text-editor");
element.focus();
}, delay);
};
this.focusTitle = function (delay) {
setTimeout(function () {
document.getElementById("note-title-editor").focus();
}, delay);
};
this.clickedTextArea = function () {
this.showMenu = false;
};
this.renderedContent = function () {
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.content.text));
};
var statusTimeout;
this.saveNote = function ($event) {
var note = this.note;
note.dummy = false;
this.save()(note, function (success) {
if (success) {
apiController.clearDraft();
if (statusTimeout) $timeout.cancel(statusTimeout);
statusTimeout = $timeout(function () {
this.noteStatus = "All changes saved";
}.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;
apiController.saveDraftToDisk(this.note);
if (saveTimeout) $timeout.cancel(saveTimeout);
if (statusTimeout) $timeout.cancel(statusTimeout);
saveTimeout = $timeout(function () {
this.noteStatus = "Saving...";
this.saveNote();
}.bind(this), 150);
};
this.contentChanged = function () {
this.changesMade();
};
this.nameChanged = function () {
this.changesMade();
};
this.onNameFocus = function () {
this.editingName = true;
};
this.onContentFocus = function () {
this.showSampler = false;
$rootScope.$broadcast("editorFocused");
this.editingUrl = false;
};
this.onNameBlur = function () {
this.editingName = false;
};
this.toggleFullScreen = function () {
this.fullscreen = !this.fullscreen;
if (this.fullscreen) {
if (this.editorMode == 'edit') {
// refocus
this.focusEditor(0);
}
} else {}
};
this.selectedMenuItem = function () {
this.showMenu = false;
};
this.toggleMarkdown = function () {
if (this.editorMode == 'preview') {
this.editorMode = 'edit';
} else {
this.editorMode = 'preview';
}
};
this.editUrlPressed = function () {
this.showMenu = false;
var url = this.publicUrlForNote(this.note);
url = url.replace(this.note.presentation.root_path, "");
this.url = { base: url, token: this.note.presentation.root_path };
this.editingUrl = true;
};
this.saveUrl = function ($event) {
$event.target.blur();
var original = this.note.presentation.relative_path;
this.note.presentation.relative_path = this.url.token;
apiController.updatePresentation(this.note, this.note.presentation, function (response) {
if (!response) {
this.note.presentation.relative_path = original;
this.url.token = original;
alert("This URL is not available.");
} else {
this.editingUrl = false;
}
}.bind(this));
};
this.shareNote = function () {
function openInNewTab(url) {
var a = document.createElement("a");
a.target = "_blank";
a.href = url;
a.click();
}
apiController.shareItem(this.user, this.note, function (note) {
openInNewTab(this.publicUrlForNote(note));
}.bind(this));
this.showMenu = false;
};
this.unshareNote = function () {
apiController.unshareItem(this.user, this.note, function (note) {});
this.showMenu = false;
};
this.publicUrlForNote = function () {
return this.note.presentationURL();
};
this.clickedMenu = function () {
if (this.note.locked) {
alert("This note has been shared without an account, and can therefore not be changed.");
} else {
this.showMenu = !this.showMenu;
}
};
this.deleteNote = function () {
apiController.clearDraft();
this.remove()(this.note);
this.showMenu = false;
};
this.clickedEditNote = function () {
this.editorMode = 'edit';
this.focusEditor(100);
};
});
;angular.module('app.frontend').directive("groupsSection", function () {
return {
restrict: 'E',
scope: {
addNew: "&",
selectionMade: "&",
willSelect: "&",
save: "&",
groups: "=",
allGroup: "=",
user: "=",
updateNoteGroup: "&"
},
templateUrl: 'frontend/groups.html',
replace: true,
controller: 'GroupsCtrl',
controllerAs: 'ctrl',
bindToController: true,
link: function link(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.groups', function (newGroups) {
if (newGroups) {
ctrl.setGroups(newGroups);
}
});
}
};
}).controller('GroupsCtrl', function () {
var initialLoad = true;
this.setGroups = function (groups) {
if (initialLoad) {
initialLoad = false;
this.selectGroup(this.allGroup);
} else {
if (groups && groups.length > 0) {
this.selectGroup(groups[0]);
}
}
};
this.selectGroup = function (group) {
this.willSelect()(group);
this.selectedGroup = group;
this.selectionMade()(group);
};
this.clickedAddNewGroup = function () {
if (this.editingGroup) {
return;
}
this.newGroup = new Group({ notes: [] });
if (!this.user.uuid) {
this.newGroup.uuid = Neeto.crypto.generateRandomKey();
}
this.selectedGroup = this.newGroup;
this.editingGroup = this.newGroup;
this.addNew()(this.newGroup);
};
var originalGroupName = "";
this.onGroupTitleFocus = function (group) {
originalGroupName = group.name;
};
this.groupTitleDidChange = function (group) {
this.editingGroup = group;
};
this.saveGroup = function ($event, group) {
this.editingGroup = null;
if (group.name.length == 0) {
group.name = originalGroupName;
originalGroupName = "";
return;
}
$event.target.blur();
if (!group.name || group.name.length == 0) {
return;
}
this.save()(group, function (savedGroup) {
_.merge(group, savedGroup);
this.selectGroup(group);
this.newGroup = null;
}.bind(this));
};
this.noteCount = function (group) {
var validNotes = Note.filterDummyNotes(group.notes);
return validNotes.length;
};
this.handleDrop = function (e, newGroup, note) {
this.updateNoteGroup()(note, newGroup, this.selectedGroup);
}.bind(this);
});
;angular.module('app.frontend').directive("header", function () {
return {
restrict: 'E',
scope: {
user: "=",
logout: "&"
},
templateUrl: 'frontend/header.html',
replace: true,
controller: 'HeaderCtrl',
controllerAs: 'ctrl',
bindToController: true,
link: function link(scope, elem, attrs, ctrl) {}
};
}).controller('HeaderCtrl', function ($auth, $state, apiController, serverSideValidation, $timeout) {
this.changePasswordPressed = function () {
this.showNewPasswordForm = !this.showNewPasswordForm;
};
this.accountMenuPressed = function () {
this.serverData = { url: apiController.getServer() };
this.showAccountMenu = !this.showAccountMenu;
this.showFaq = false;
this.showNewPasswordForm = false;
};
this.changeServer = function () {
apiController.setServer(this.serverData.url, true);
};
this.signOutPressed = function () {
this.showAccountMenu = false;
this.logout()();
apiController.signout();
window.location.reload();
};
this.submitPasswordChange = function () {
this.passwordChangeData.status = "Generating New Keys...";
$timeout(function () {
if (data.password != data.password_confirmation) {
alert("Your new password does not match its confirmation.");
return;
}
apiController.changePassword(this.user, this.passwordChangeData.current_password, this.passwordChangeData.new_password, function (response) {});
}.bind(this));
};
this.hasLocalData = function () {
return this.user.filteredNotes().length > 0;
};
this.mergeLocalChanged = function () {
if (!this.user.shouldMerge) {
if (!confirm("Unchecking this option means any locally stored groups and notes you have now will be deleted. Are you sure you want to continue?")) {
this.user.shouldMerge = true;
}
}
};
this.loginSubmitPressed = function () {
this.loginData.status = "Generating Login Keys...";
$timeout(function () {
apiController.login(this.loginData.email, this.loginData.user_password, function (response) {
if (response.errors) {
this.loginData.status = response.errors[0];
} else {
this.onAuthSuccess(response.user);
}
}.bind(this));
}.bind(this));
};
this.submitRegistrationForm = function () {
this.loginData.status = "Generating Account Keys...";
$timeout(function () {
apiController.register(this.loginData.email, this.loginData.user_password, function (response) {
if (response.errors) {
this.loginData.status = response.errors[0];
} else {
this.onAuthSuccess(response.user);
}
}.bind(this));
}.bind(this));
};
this.forgotPasswordSubmit = function () {
$auth.requestPasswordReset(this.resetData).then(function (resp) {
this.resetData.response = "Success";
// handle success response
}.bind(this)).catch(function (resp) {
// handle error response
this.resetData.response = "Error";
}.bind(this));
};
this.encryptionStatusForNotes = function () {
var allNotes = this.user.filteredNotes();
var countEncrypted = 0;
allNotes.forEach(function (note) {
if (note.encryptionEnabled()) {
countEncrypted++;
}
}.bind(this));
return countEncrypted + "/" + allNotes.length + " notes encrypted";
};
this.downloadDataArchive = function () {
var link = document.createElement('a');
link.setAttribute('download', 'neeto.json');
link.href = apiController.notesDataFile(this.user);
link.click();
};
this.importFileSelected = function (files) {
var file = files[0];
var reader = new FileReader();
reader.onload = function (e) {
apiController.importJSONData(e.target.result, function (success, response) {
console.log("import response", success, response);
if (success) {
// window.location.reload();
} else {
alert("There was an error importing your data. Please try again.");
}
});
};
reader.readAsText(file);
};
this.onAuthSuccess = function (user) {
this.user.id = user.id;
if (this.user.shouldMerge && this.hasLocalData()) {
apiController.mergeLocalDataRemotely(this.user, function () {
window.location.reload();
});
} else {
window.location.reload();
}
this.showLogin = false;
this.showRegistration = false;
};
});
;angular.module('app.frontend').controller('HomeCtrl', function ($scope, $rootScope, $timeout, apiController, modelManager) {
$rootScope.bodyClass = "app-body-class";
$rootScope.title = "Notes — Neeto, a secure code box for developers";
$rootScope.description = "A secure code box for developers to store common commands and useful notes.";
var onUserSet = function onUserSet() {
$scope.allGroup = new Group({ name: "All", all: true });
$scope.groups = modelManager.groups;
apiController.verifyEncryptionStatusOfAllItems($scope.defaultUser, function (success) {});
};
apiController.getCurrentUser(function (response) {
if (response && !response.errors) {
$scope.defaultUser = new User(response);
modelManager.items = response.items;
$rootScope.title = "Notes — Neeto";
onUserSet();
} else {
$scope.defaultUser = new User(apiController.localUser());
onUserSet();
}
});
/*
Groups Ctrl Callbacks
*/
$scope.updateAllGroup = function () {
$scope.allGroup.notes = modelManager.filteredNotes;
};
$scope.groupsWillMakeSelection = function (group) {
if (group.all) {
$scope.updateAllGroup();
}
};
$scope.groupsSelectionMade = function (group) {
if (!group.notes) {
group.notes = [];
}
$scope.selectedGroup = group;
};
$scope.groupsAddNew = function (group) {
modelManager.addTag(group);
};
$scope.groupsSave = function (group, callback) {
apiController.saveItems([group], callback);
};
/*
Called to update the group of a note after drag and drop change
The note object is a copy of the original
*/
$scope.groupsUpdateNoteGroup = function (noteCopy, newGroup, oldGroup) {
var originalNote = _.find($scope.defaultUser.notes, { uuid: noteCopy.uuid });
modelManager.removeTagFromNote(oldGroup, originalNote);
if (!newGroup.all) {
modelManager.addTagToNote(newGroup, originalNote);
}
apiController.saveDirtyItems(function () {});
};
/*
Notes Ctrl Callbacks
*/
$scope.notesRemoveGroup = function (group) {
var validNotes = Note.filterDummyNotes(group.notes);
if (validNotes == 0) {
// if no more notes, delete group
apiController.deleteItem($scope.defaultUser, group, function () {
// force scope groups to update on sub directives
$scope.groups = [];
$timeout(function () {
$scope.groups = modelManager.groups;
});
});
} else {
alert("To delete this group, remove all its notes first.");
}
};
$scope.notesSelectionMade = function (note) {
$scope.selectedNote = note;
};
$scope.notesAddNew = function (note) {
if (!$scope.defaultUser.id) {
// generate local id for note
note.id = Neeto.crypto.generateRandomKey();
}
modelManager.addNote(note);
if (!$scope.selectedGroup.all) {
modelManager.addTagToNote($scope.selectedGroup, note);
}
};
/*
Shared Callbacks
*/
$scope.saveNote = function (note, callback) {
apiController.saveItems([note], function () {
modelManager.addNote(note);
note.hasChanges = false;
if (callback) {
callback(true);
}
});
};
$scope.deleteNote = function (note) {
modelManager.deleteNote(note);
if (note == $scope.selectedNote) {
$scope.selectedNote = null;
}
if (note.dummy) {
return;
}
apiController.deleteItem($scope.defaultUser, note, function (success) {});
apiController.saveDirtyItems(function () {});
};
/*
Header Ctrl Callbacks
*/
$scope.headerLogout = function () {
$scope.defaultUser = apiController.localUser();
$scope.groups = $scope.defaultUser.groups;
};
});
;angular.module('app.frontend').directive("notesSection", function () {
return {
scope: {
addNew: "&",
selectionMade: "&",
remove: "&",
group: "=",
user: "=",
removeGroup: "&"
},
templateUrl: 'frontend/notes.html',
replace: true,
controller: 'NotesCtrl',
controllerAs: 'ctrl',
bindToController: true,
link: function link(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.group', function (group, oldGroup) {
if (group) {
ctrl.groupDidChange(group, oldGroup);
}
});
}
};
}).controller('NotesCtrl', function (apiController, $timeout, ngDialog, $rootScope) {
$rootScope.$on("editorFocused", function () {
this.showMenu = false;
}.bind(this));
var isFirstLoad = true;
this.groupDidChange = function (group, oldGroup) {
this.showMenu = false;
if (this.selectedNote && this.selectedNote.dummy) {
_.remove(oldGroup.notes, this.selectedNote);
}
this.noteFilter.text = "";
this.setNotes(group.notes, false);
if (isFirstLoad) {
$timeout(function () {
var draft = apiController.getDraft();
if (draft) {
var note = draft;
this.selectNote(note);
} else {
this.createNewNote();
isFirstLoad = false;
}
}.bind(this));
} else if (group.notes.length == 0) {
this.createNewNote();
}
};
this.selectedGroupDelete = function () {
this.showMenu = false;
this.removeGroup()(this.group);
};
this.selectedGroupShare = function () {
this.showMenu = false;
if (!this.user.id) {
alert("You must be signed in to share a group.");
return;
}
if (this.group.all) {
alert("You cannot share the 'All' group.");
return;
}
var _callback = function (username) {
apiController.shareItem(this.user, this.group, function (response) {});
}.bind(this);
if (!this.user.username) {
ngDialog.open({
template: 'frontend/modals/username.html',
controller: 'UsernameModalCtrl',
resolve: {
user: function () {
return this.user;
}.bind(this),
callback: function callback() {
return _callback;
}
},
className: 'ngdialog-theme-default',
disableAnimation: true
});
} else {
_callback(this.user.username);
}
};
this.selectedGroupUnshare = function () {
this.showMenu = false;
apiController.unshareItem(this.user, this.group, function (response) {});
};
this.publicUrlForGroup = function () {
return this.group.presentation.url;
};
this.setNotes = function (notes, createNew) {
this.notes = notes;
notes.forEach(function (note) {
note.visible = true;
});
apiController.decryptNotesWithLocalKey(notes);
this.selectFirstNote(createNew);
};
this.selectFirstNote = function (createNew) {
var visibleNotes = this.notes.filter(function (note) {
return note.visible;
});
if (visibleNotes.length > 0) {
this.selectNote(visibleNotes[0]);
} else if (createNew) {
this.createNewNote();
}
};
this.selectNote = function (note) {
this.selectedNote = note;
this.selectionMade()(note);
};
this.createNewNote = function () {
var title = "New Note" + (this.notes ? " " + (this.notes.length + 1) : "");
this.newNote = new Note({ dummy: true });
this.newNote.content.title = title;
modelManager.addTagToNote(this.group, this.newNote);
this.selectNote(this.newNote);
this.addNew()(this.newNote);
};
this.noteFilter = { text: '' };
this.filterNotes = function (note) {
if (this.noteFilter.text.length == 0) {
note.visible = true;
} else {
note.visible = note.title.toLowerCase().includes(this.noteFilter.text) || note.text.toLowerCase().includes(this.noteFilter.text);
}
return note.visible;
}.bind(this);
this.filterTextChanged = function () {
$timeout(function () {
if (!this.selectedNote.visible) {
this.selectFirstNote(false);
}
}.bind(this), 100);
};
});
;angular.module('app.frontend').controller('UsernameModalCtrl', function ($scope, apiController, Restangular, user, callback, $timeout) {
$scope.formData = {};
$scope.saveUsername = function () {
apiController.setUsername(user, $scope.formData.username, function (response) {
var username = response.root_path;
user.presentation = response;
callback(username);
$scope.closeThisDialog();
});
};
});
;
var Item = function () {
function Item(json_obj) {
_classCallCheck(this, Item);
var content;
Object.defineProperty(this, "content", {
get: function get() {
return content;
},
set: function set(value) {
var finalValue = value;
if (typeof value === 'string') {
try {
decodedValue = JSON.parse(value);
finalValue = decodedValue;
} catch (e) {
finalValue = value;
}
}
content = finalValue;
},
enumerable: true
});
_.merge(this, json_obj);
this.setContentRaw = function (rawContent) {
content = rawContent;
};
}
_createClass(Item, [{
key: 'referencesMatchingContentType',
value: function referencesMatchingContentType(contentType) {
return this.references.filter(function (reference) {
return reference.content_type == content_type;
});
}
}, {
key: 'updateReferencesLocalMapping',
value: function updateReferencesLocalMapping() {}
// should be overriden to manage local properties
/* Returns true if note is shared individually or via group */
}, {
key: 'isPublic',
value: function isPublic() {
return this.presentation;
}
}, {
key: 'isEncrypted',
value: function isEncrypted() {
return this.encryptionEnabled() && typeof this.content === 'string' ? true : false;
}
}, {
key: 'encryptionEnabled',
value: function encryptionEnabled() {
return this.loc_eek;
}
}, {
key: 'presentationURL',
value: function presentationURL() {
return this.presentation.url;
}
}]);
return Item;
}();
;
var Note = function (_Item) {
_inherits(Note, _Item);
function Note(json_obj) {
_classCallCheck(this, Note);
var _this = _possibleConstructorReturn(this, (Note.__proto__ || Object.getPrototypeOf(Note)).call(this, json_obj));
if (!_this.content) {
_this.content = { title: "", text: "" };
}
return _this;
}
_createClass(Note, [{
key: 'filterDummyNotes',
value: function filterDummyNotes(notes) {
var filtered = notes.filter(function (note) {
return note.dummy == false || note.dummy == null;
});
return filtered;
}
}, {
key: 'isPublic',
value: function isPublic() {
return _get(Note.prototype.__proto__ || Object.getPrototypeOf(Note.prototype), 'isPublic', this).call(this) || this.hasOnePublicGroup;
}
}, {
key: 'hasOnePublicGroup',
get: function get() {
var hasPublicGroup = false;
this.groups.forEach(function (group) {
if (group.isPublic()) {
hasPublicGroup = true;
return;
}
});
return hasPublicGroup;
}
}, {
key: 'content_type',
get: function get() {
return "Note";
}
}]);
return Note;
}(Item);
;
var Tag = function (_Item2) {
_inherits(Tag, _Item2);
function Tag(json_obj) {
_classCallCheck(this, Tag);
return _possibleConstructorReturn(this, (Tag.__proto__ || Object.getPrototypeOf(Tag)).call(this, json_obj));
}
_createClass(Tag, [{
key: 'content_type',
get: function get() {
return "Tag";
}
}]);
return Tag;
}(Item);
;
var User = function User(json_obj) {
_classCallCheck(this, User);
_.merge(this, json_obj);
};
;angular.module('app.frontend').provider('apiController', function () {
function domainName() {
var domain_comps = location.hostname.split(".");
var domain = domain_comps[domain_comps.length - 2] + "." + domain_comps[domain_comps.length - 1];
return domain;
}
var url;
this.defaultServerURL = function () {
if (!url) {
url = localStorage.getItem("server");
if (!url) {
url = location.protocol + "//" + domainName() + (location.port ? ':' + location.port : '');
}
}
return url;
};
this.$get = function (Restangular) {
return new ApiController(Restangular);
};
function ApiController(Restangular) {
/*
Config
*/
this.getServer = function () {
if (!url) {
url = localStorage.getItem("server");
if (!url) {
url = location.protocol + "//" + domainName() + (location.port ? ':' + location.port : '');
this.setServer(url);
}
}
return url;
};
this.setServer = function (url, refresh) {
localStorage.setItem("server", url);
if (refresh) {
window.location.reload();
}
};
/*
Auth
*/
this.getCurrentUser = function (callback) {
if (!localStorage.getItem("jwt")) {
callback(null);
return;
}
Restangular.one("users/current").get().then(function (response) {
callback(response.plain());
});
};
this.login = function (email, password, callback) {
var keys = Neeto.crypto.generateEncryptionKeysForUser(password, email);
this.setGk(keys.gk);
var request = Restangular.one("auth/sign_in.json");
request.user = { password: keys.pw, email: email };
request.post().then(function (response) {
localStorage.setItem("jwt", response.token);
callback(response);
});
};
this.register = function (email, password, callback) {
var keys = Neeto.crypto.generateEncryptionKeysForUser(password, email);
this.setGk(keys.gk);
var request = Restangular.one("auth.json");
request.user = { password: keys.pw, email: email };
request.post().then(function (response) {
localStorage.setItem("jwt", response.token);
callback(response);
});
};
this.changePassword = function (user, current_password, new_password) {
var current_keys = Neeto.crypto.generateEncryptionKeysForUser(current_password, user.email);
var new_keys = Neeto.crypto.generateEncryptionKeysForUser(new_password, user.email);
var data = {};
data.current_password = current_keys.pw;
data.password = new_keys.pw;
data.password_confirmation = new_keys.pw;
var user = this.user;
this._performPasswordChange(current_keys, new_keys, function (response) {
if (response && !response.errors) {
// this.showNewPasswordForm = false;
// reencrypt data with new gk
this.reencryptAllNotesAndSave(user, new_keys.gk, current_keys.gk, function (success) {
if (success) {
this.setGk(new_keys.gk);
alert("Your password has been changed and your data re-encrypted.");
} else {
// rollback password
this._performPasswordChange(new_keys, current_keys, function (response) {
alert("There was an error changing your password. Your password has been rolled back.");
window.location.reload();
});
}
}.bind(this));
} else {
// this.showNewPasswordForm = false;
alert("There was an error changing your password. Please try again.");
}
});
};
this._performPasswordChange = function (email, current_keys, new_keys, callback) {
var request = Restangular.one("auth");
request.user = { password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email };
request.patch().then(function (response) {
callback(response);
});
};
/*
User
*/
this.setUsername = function (user, username, callback) {
var request = Restangular.one("users", user.id).one("set_username");
request.username = username;
request.post().then(function (response) {
callback(response.plain());
});
};
/*
Ensures that if encryption is disabled, all local notes are uncrypted,
and that if it's enabled, that all local notes are encrypted
*/
this.verifyEncryptionStatusOfAllItems = function (user, callback) {
var allNotes = user.filteredNotes();
var notesNeedingUpdate = [];
allNotes.forEach(function (note) {
if (!note.isPublic()) {
if (note.encryptionEnabled() && !note.isEncrypted()) {
notesNeedingUpdate.push(note);
}
} else {
if (note.isEncrypted()) {
notesNeedingUpdate.push(note);
}
}
}.bind(this));
if (notesNeedingUpdate.length > 0) {
console.log("verifying encryption, notes need updating", notesNeedingUpdate);
this.saveBatchNotes(user, notesNeedingUpdate, callback);
}
};
/*
Items
*/
this.saveDirtyItems = function (callback) {
var dirtyItems = modelManager.dirtyItems;
this.saveItems(dirtyItems, function (response) {
modelManager.clearDirtyItems();
});
};
this.saveItems = function (items, callback) {
var request = Restangular.one("users", user.uuid).one("items");
request.items = _.map(items, function (item) {
return this.createRequestParamsFromItem(item, user);
}.bind(this));
request.post().then(function (response) {
var savedItems = response.items;
items.forEach(function (item) {
_.merge(item, _.find(savedItems, { uuid: item.uuid }));
});
callback(response);
});
};
this.createRequestParamsForItem = function (item, user) {
var params = { uuid: item.uuid };
if (!item.isPublic()) {
// encrypted
var itemCopy = _.cloneDeep(item);
this.encryptSingleNote(itemCopy, this.retrieveGk());
params.content = itemCopy.content;
params.loc_eek = itemCopy.loc_eek;
} else {
// decrypted
params.content = JSON.stringify(item.content);
params.loc_eek = null;
}
return params;
};
this.deleteItem = function (user, item, callback) {
if (!user.id) {
this.writeUserToLocalStorage(user);
callback(true);
} else {
Restangular.one("users", user.uuid).one("items", item.uuid).remove().then(function (response) {
callback(true);
});
}
};
this.shareItem = function (user, item, callback) {
if (!user.id) {
alert("You must be signed in to share.");
} else {
Restangular.one("users", user.uuid).one("items", item.uuid).one("presentations").post().then(function (response) {
var presentation = response.plain();
_.merge(item, { presentation: presentation });
callback(item);
// decrypt references
if (item.references.length > 0) {
this.saveBatchItems(user, item.references, function (success) {});
}
});
}
};
this.unshareItem = function (user, item, callback) {
var request = Restangular.one("users", user.uuid).one("notes", item.uuid).one("presentations", item.presentation.uuid);
request.remove().then(function (response) {
item.presentation = null;
callback(null);
// encrypt references
if (item.references.length > 0) {
this.saveBatchItems(user, item.references, function (success) {});
}
});
};
/*
Presentations
*/
this.updatePresentation = function (resource, presentation, callback) {
var request = Restangular.one("users", user.id).one("items", resource.id).one("presentations", resource.presentation.id);
_.merge(request, presentation);
request.patch().then(function (response) {
callback(response.plain());
}).catch(function (error) {
callback(nil);
});
};
/*
Import
*/
this.importJSONData = function (jsonString, callback) {
var data = JSON.parse(jsonString);
var user = new User(data);
console.log("importing data", JSON.parse(jsonString));
user.notes.forEach(function (note) {
if (note.isPublic()) {
note.setContentRaw(JSON.stringify(note.content));
} else {
this.encryptSingleNoteWithLocalKey(note);
}
// prevent circular links
note.group = null;
}.bind(this));
user.groups.forEach(function (group) {
// prevent circular links
group.notes = null;
});
var request = Restangular.one("import");
request.data = { notes: user.notes, groups: user.groups };
request.post().then(function (response) {
callback(true, response);
}).catch(function (error) {
callback(false, error);
});
};
/*
Export
*/
this.notesDataFile = function (user) {
var textFile = null;
var makeTextFile = function (text) {
var data = new Blob([text], { type: 'text/json' });
// If we are replacing a previously generated file we need to
// manually revoke the object URL to avoid memory leaks.
if (textFile !== null) {
window.URL.revokeObjectURL(textFile);
}
textFile = window.URL.createObjectURL(data);
// returns a URL you can use as a href
return textFile;
}.bind(this);
var presentationParams = function presentationParams(presentation) {
if (!presentation) {
return null;
}
return {
id: presentation.id,
uuid: presentation.uuid,
root_path: presentation.root_path,
relative_path: presentation.relative_path,
presentable_type: presentation.presentable_type,
presentable_id: presentation.presentable_id,
created_at: presentation.created_at,
modified_at: presentation.modified_at
};
};
var notes = _.map(user.filteredNotes(), function (note) {
return {
id: note.id,
uuid: note.uuid,
content: note.content,
group_id: note.group_id,
created_at: note.created_at,
modified_at: note.modified_at,
presentation: presentationParams(note.presentation)
};
});
var groups = _.map(user.groups, function (group) {
return {
id: group.id,
uuid: group.uuid,
name: group.name,
created_at: group.created_at,
modified_at: group.modified_at,
presentation: presentationParams(group.presentation)
};
});
var data = {
notes: notes,
groups: groups
};
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
};
/*
Merging
*/
this.mergeLocalDataRemotely = function (user, callback) {
var request = Restangular.one("users", user.id).one("merge");
var groups = user.groups;
request.notes = user.notes;
request.notes.forEach(function (note) {
if (note.group_id) {
var group = groups.filter(function (group) {
return group.id == note.group_id;
})[0];
note.group_name = group.name;
}
});
request.post().then(function (response) {
callback();
localStorage.removeItem('user');
});
};
this.staticifyObject = function (object) {
return JSON.parse(JSON.stringify(object));
};
this.writeUserToLocalStorage = function (user) {
var saveUser = _.cloneDeep(user);
saveUser.notes = Note.filterDummyNotes(saveUser.notes);
saveUser.groups.forEach(function (group) {
group.notes = null;
}.bind(this));
this.writeToLocalStorage('user', saveUser);
};
this.writeToLocalStorage = function (key, value) {
localStorage.setItem(key, angular.toJson(value));
};
this.localUser = function () {
var user = JSON.parse(localStorage.getItem('user'));
if (!user) {
user = { notes: [], groups: [] };
}
user.shouldMerge = true;
return user;
};
/*
Drafts
*/
this.saveDraftToDisk = function (draft) {
localStorage.setItem("draft", JSON.stringify(draft));
};
this.clearDraft = function () {
localStorage.removeItem("draft");
};
this.getDraft = function () {
var draftString = localStorage.getItem("draft");
if (!draftString || draftString == 'undefined') {
return null;
}
return new Note(JSON.parse(draftString));
};
/*
Encrpytion
*/
this.retrieveGk = function () {
if (!this.gk) {
this.gk = localStorage.getItem("gk");
}
return this.gk;
};
this.setGk = function (gk) {
localStorage.setItem('gk', gk);
};
this.signout = function () {
localStorage.removeItem("jwt");
localStorage.removeItem("gk");
};
this.encryptSingleNote = function (note, key) {
var ek = null;
if (note.loc_eek) {
ek = Neeto.crypto.decryptText(note.loc_eek, key);
} else {
ek = Neeto.crypto.generateRandomEncryptionKey();
note.loc_eek = Neeto.crypto.encryptText(ek, key);
}
note.content = Neeto.crypto.encryptText(JSON.stringify(note.content), ek);
note.local_encryption_scheme = "1.0";
};
this.encryptNotes = function (notes, key) {
notes.forEach(function (note) {
this.encryptSingleNote(note, key);
}.bind(this));
};
this.encryptSingleNoteWithLocalKey = function (note) {
this.encryptSingleNote(note, this.retrieveGk());
};
this.encryptNotesWithLocalKey = function (notes) {
this.encryptNotes(notes, this.retrieveGk());
};
this.encryptNonPublicNotesWithLocalKey = function (notes) {
var nonpublic = notes.filter(function (note) {
return !note.isPublic() && !note.pending_share;
});
this.encryptNotes(nonpublic, this.retrieveGk());
};
this.decryptSingleNoteWithLocalKey = function (note) {
this.decryptSingleNote(note, this.retrieveGk());
};
this.decryptSingleNote = function (note, key) {
var ek = Neeto.crypto.decryptText(note.loc_eek || note.local_eek, key);
var content = Neeto.crypto.decryptText(note.content, ek);
// console.log("decrypted contnet", content);
note.content = content;
};
this.decryptNotes = function (notes, key) {
notes.forEach(function (note) {
// console.log("is encrypted?", note);
if (note.isEncrypted()) {
this.decryptSingleNote(note, key);
}
}.bind(this));
};
this.decryptNotesWithLocalKey = function (notes) {
this.decryptNotes(notes, this.retrieveGk());
};
this.reencryptAllNotesAndSave = function (user, newKey, oldKey, callback) {
var notes = user.filteredNotes();
notes.forEach(function (note) {
if (note.isEncrypted()) {
// first decrypt eek with old key
var ek = Neeto.crypto.decryptText(note.loc_eek, oldKey);
// now encrypt ek with new key
note.loc_eek = Neeto.crypto.encryptText(ek, newKey);
}
});
this.saveBatchNotes(user, notes, function (success) {
callback(success);
}.bind(this));
};
}
});
;
var ItemManager = function () {
function ItemManager() {
_classCallCheck(this, ItemManager);
}
_createClass(ItemManager, [{
key: 'referencesForItemId',
value: function referencesForItemId(itemId) {
return _.find(this.items, { uuid: itemId });
}
}, {
key: 'resolveReferences',
value: function resolveReferences() {
this.items.forEach(function (item) {
// build out references
_.map(item.references, function (reference) {
return referencesForItemId(reference.uuid);
});
});
}
}, {
key: 'itemsForContentType',
value: function itemsForContentType(contentType) {
this.items.filter(function (item) {
return item.content_type == contentType;
});
}
// returns dirty item references that need saving
}, {
key: 'deleteItem',
value: function deleteItem(item) {
_.remove(this.items, item);
item.references.forEach(function (referencedItem) {
removeReferencesBetweenItems(referencedItem, item);
});
return item.references;
}
}, {
key: 'removeReferencesBetweenItems',
value: function removeReferencesBetweenItems(itemOne, itemTwo) {
_.remove(itemOne.references, _.find(itemOne.references, { uuid: itemTwo.uuid }));
_.remove(itemTwo.references, _.find(itemTwo.references, { uuid: itemOne.uuid }));
return [itemOne, itemTwo];
}
}, {
key: 'createReferencesBetweenItems',
value: function createReferencesBetweenItems(itemOne, itemTwo) {
itemOne.references.push(itemTwo);
itemTwo.references.push(itemOne);
return [itemOne, itemTwo];
}
}, {
key: 'items',
set: function set(items) {
this.items = items;
resolveReferences();
}
}]);
return ItemManager;
}();
angular.module('app.frontend').service('itemManager', ItemManager);
;angular.module('app.frontend').service('markdownRenderer', function ($sce) {
marked.setOptions({
breaks: true,
sanitize: true
});
this.renderedContentForText = function (text) {
if (!text || text.length == 0) {
return "";
}
return marked(text);
};
this.renderHtml = function (html_code) {
return $sce.trustAsHtml(html_code);
};
});
;
var ModelManager = function (_ItemManager) {
_inherits(ModelManager, _ItemManager);
function ModelManager() {
_classCallCheck(this, ModelManager);
return _possibleConstructorReturn(this, (ModelManager.__proto__ || Object.getPrototypeOf(ModelManager)).apply(this, arguments));
}
_createClass(ModelManager, [{
key: 'addDirtyItems',
value: function addDirtyItems(items) {
if (this.dirtyItems) {
this.dirtyItems = [];
}
this.dirtyItems.concat(items);
}
}, {
key: 'clearDirtyItems',
value: function clearDirtyItems() {
this.dirtyItems = [];
}
}, {
key: 'addNote',
value: function addNote(note) {
if (!_.find(this.notes, { uuid: note.uuid })) {
this.notes.unshift(note);
}
}
}, {
key: 'addTag',
value: function addTag(tag) {
this.tags.unshift(tag);
}
}, {
key: 'addTagToNote',
value: function addTagToNote(tag, note) {
var dirty = this.createReferencesBetweenItems(tag, note);
this.refreshRelationshipsForTag(tag);
this.refreshRelationshipsForNote(note);
this.addDirtyItems(dirty);
}
}, {
key: 'refreshRelationshipsForTag',
value: function refreshRelationshipsForTag(tag) {
tag.notes = tag.referencesMatchingContentType("Note");
tag.notes.sort(function (a, b) {
return new Date(b.created_at) - new Date(a.created_at);
});
}
}, {
key: 'refreshRelationshipsForNote',
value: function refreshRelationshipsForNote(note) {
note.groups = note.referencesMatchingContentType("Group");
}
}, {
key: 'removeTagFromNote',
value: function removeTagFromNote(tag, note) {
var dirty = this.removeReferencesBetweenItems(tag, note);
this.addDirtyItems(dirty);
}
}, {
key: 'deleteNote',
value: function deleteNote(note) {
var dirty = this.deleteItem(note);
this.addDirtyItems(dirty);
}
}, {
key: 'deleteTag',
value: function deleteTag(tag) {
var dirty = this.deleteItem(tag);
this.addDirtyItems(dirty);
}
}, {
key: 'filteredNotes',
value: function filteredNotes() {
return Note.filterDummyNotes(this.notes);
}
}, {
key: 'items',
set: function set(items) {
_set(ModelManager.prototype.__proto__ || Object.getPrototypeOf(ModelManager.prototype), 'items', items, this);
this.notes = _.map(this.items.itemsForContentType("Note"), function (json_obj) {
return new Note(json_obj);
});
this.groups = _.map(this.items.itemsForContentType("Group"), function (json_obj) {
var group = Group(json_obj);
group.updateReferencesLocalMapping();
return group;
});
}
}, {
key: 'dirtyItems',
get: function get() {
return this.dirtyItems || [];
}
}, {
key: 'filteredNotes',
get: function get() {
return Note.filterDummyNotes(this.notes);
}
}]);
return ModelManager;
}(ItemManager);
angular.module('app.frontend').service('modelManager', ModelManager);
;angular.module('app.frontend').service('serverSideValidation', function ($sce) {
// Show validation errors in form.
this.showErrors = function (formErrors, form) {
angular.forEach(formErrors, function (errors, key) {
if (typeof form[key] !== 'undefined') {
form[key].$setDirty();
form[key].$setValidity('server', false);
form[key].$error.server = $sce.trustAsHtml(errors.join(', '));
}
});
};
// Get validation errors from server response and show them in form.
this.parseErrors = function (response, form) {
if (response.status === 422) {
this.showErrors(response.data, form);
}
};
});
;angular.module('app.frontend').directive('mbAutofocus', ['$timeout', function ($timeout) {
return {
restrict: 'A',
scope: {
shouldFocus: "="
},
link: function link($scope, $element) {
$timeout(function () {
if ($scope.shouldFocus) {
$element[0].focus();
}
});
}
};
}]);
;angular.module('app.frontend').directive('draggable', function () {
return {
scope: {
note: "="
},
link: function link(scope, element) {
// this gives us the native JS object
var el = element[0];
el.draggable = true;
el.addEventListener('dragstart', function (e) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('Note', JSON.stringify(scope.note));
this.classList.add('drag');
return false;
}, false);
el.addEventListener('dragend', function (e) {
this.classList.remove('drag');
return false;
}, false);
}
};
});
angular.module('app.frontend').directive('droppable', function () {
return {
scope: {
drop: '&',
bin: '=',
group: "="
},
link: function link(scope, element) {
// again we need the native object
var el = element[0];
el.addEventListener('dragover', function (e) {
e.dataTransfer.dropEffect = 'move';
// allows us to drop
if (e.preventDefault) e.preventDefault();
this.classList.add('over');
return false;
}, false);
var counter = 0;
el.addEventListener('dragenter', function (e) {
counter++;
this.classList.add('over');
return false;
}, false);
el.addEventListener('dragleave', function (e) {
counter--;
if (counter === 0) {
this.classList.remove('over');
}
return false;
}, false);
el.addEventListener('drop', function (e) {
// Stops some browsers from redirecting.
if (e.stopPropagation) e.stopPropagation();
this.classList.remove('over');
var binId = this.id;
var note = new Note(JSON.parse(e.dataTransfer.getData('Note')));
scope.$apply(function (scope) {
var fn = scope.drop();
if ('undefined' !== typeof fn) {
fn(e, scope.group, note);
}
});
return false;
}, false);
}
};
});
;angular.module('app.frontend').directive('fileChange', function () {
return {
restrict: 'A',
scope: {
handler: '&'
},
link: function link(scope, element) {
element.on('change', function (event) {
scope.$apply(function () {
scope.handler({ files: event.target.files });
});
});
}
};
});
;angular.module('app.frontend').directive('lowercase', function () {
return {
require: 'ngModel',
link: function link(scope, element, attrs, modelCtrl) {
var lowercase = function lowercase(inputValue) {
if (inputValue == undefined) inputValue = '';
var lowercased = inputValue.toLowerCase();
if (lowercased !== inputValue) {
modelCtrl.$setViewValue(lowercased);
modelCtrl.$render();
}
return lowercased;
};
modelCtrl.$parsers.push(lowercase);
lowercase(scope[attrs.ngModel]);
}
};
});
;angular.module('app.frontend').directive('selectOnClick', ['$window', function ($window) {
return {
restrict: 'A',
link: function link(scope, element, attrs) {
element.on('focus', function () {
if (!$window.getSelection().toString()) {
// Required for mobile Safari
this.setSelectionRange(0, this.value.length);
}
});
}
};
}]);
;angular.module('app.frontend').directive('note', function ($timeout) {
return {
restrict: 'E',
controller: 'SingleNoteCtrl',
templateUrl: "frontend/directives/note.html",
scope: {
note: "="
}
};
}).controller('SingleNoteCtrl', function ($rootScope, $scope, $state, markdownRenderer) {
$scope.renderedContent = function () {
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText($scope.note.text));
};
});
; /**
* AngularJS directive that simulates the effect of typing on a text editor - with a blinking cursor.
* This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation.
*
* There's also a simple less file included for basic styling of the dialog, which can be overridden.
* The config object also lets the user define custom CSS classes for the modal.
*
* How to use:
*
* Just add the desired text to the 'text' attribute of the element and the directive takes care of the rest.
* The 'text' attribute can be a single string or an array of string. In case an array is passed, the string
* on each index is erased so the next item can be printed. When the last index is reached, that string stays
* on the screen. (So if you want to erase the last string, just push an empty string to the end of the array)
*
* These are the optional preferences:
* - initial delay: set an 'initial-delay' attribute for the element
* - type delay: set a 'type-delay' attribute for the element
* - erase delay: set a 'erase-delay' attribute for the element
* - specify cursor : set a 'cursor' attribute for the element, specifying which cursor to use
* - turn off cursor blinking: set the 'blink-cursor' attribute to "false"
* - cursor blinking speed: set a 'blink-delay' attribute for the element
* - scope callback: pass the desired scope callback as the 'callback-fn' attribute of the element
*
* Note:
* Each time/delay value should be set either on seconds (1s) or milliseconds (1000)
*
* Dependencies:
* The directive needs the css file provided in order to replicate the cursor blinking effect.
*/
angular.module('app.frontend').directive('typewrite', ['$timeout', function ($timeout) {
function linkFunction($scope, $element, $attrs) {
var timer = null,
initialDelay = $attrs.initialDelay ? getTypeDelay($attrs.initialDelay) : 200,
typeDelay = $attrs.typeDelay || 200,
eraseDelay = $attrs.eraseDelay || typeDelay / 2,
blinkDelay = $attrs.blinkDelay ? getAnimationDelay($attrs.blinkDelay) : false,
cursor = $attrs.cursor || '|',
blinkCursor = typeof $attrs.blinkCursor !== 'undefined' ? $attrs.blinkCursor === 'true' : true,
currentText,
textArray,
running,
auxStyle;
if ($scope.text) {
if ($scope.text instanceof Array) {
textArray = $scope.text;
currentText = textArray[0];
} else {
currentText = $scope.text;
}
}
if (typeof $scope.start === 'undefined' || $scope.start) {
typewrite();
}
function typewrite() {
timer = $timeout(function () {
updateIt($element, 0, 0, currentText);
}, initialDelay);
}
function updateIt(element, charIndex, arrIndex, text) {
if (charIndex <= text.length) {
updateValue(element, text.substring(0, charIndex) + cursor);
charIndex++;
timer = $timeout(function () {
updateIt(element, charIndex, arrIndex, text);
}, typeDelay);
return;
} else {
charIndex--;
if ($scope.iterationCallback) {
$scope.iterationCallback()(arrIndex);
}
// check if it's an array
if (textArray && arrIndex < textArray.length - 1) {
timer = $timeout(function () {
cleanAndRestart(element, charIndex, arrIndex, textArray[arrIndex]);
}, $scope.iterationDelay);
} else {
if ($scope.callbackFn) {
$scope.callbackFn();
}
blinkIt(element, charIndex, currentText);
}
}
}
function blinkIt(element, charIndex) {
var text = element.text().substring(0, element.text().length - 1);
if (blinkCursor) {
if (blinkDelay) {
auxStyle = '-webkit-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-moz-animation:blink-it steps(1) ' + blinkDelay + ' infinite ' + '-ms-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-o-animation:blink-it steps(1) ' + blinkDelay + ' infinite; ' + 'animation:blink-it steps(1) ' + blinkDelay + ' infinite;';
updateValue(element, text.substring(0, charIndex) + '<span class="blink" style="' + auxStyle + '">' + cursor + '</span>');
} else {
updateValue(element, text.substring(0, charIndex) + '<span class="blink">' + cursor + '</span>');
}
} else {
updateValue(element, text.substring(0, charIndex));
}
}
function cleanAndRestart(element, charIndex, arrIndex, currentText) {
if (charIndex == 0) {
if ($scope.prebeginFn) {
$scope.prebeginFn()();
}
}
if (charIndex > 0) {
currentText = currentText.slice(0, -1);
// element.html(currentText.substring(0, currentText.length - 1) + cursor);
updateValue(element, currentText + cursor);
charIndex--;
timer = $timeout(function () {
cleanAndRestart(element, charIndex, arrIndex, currentText);
}, eraseDelay);
return;
} else {
arrIndex++;
currentText = textArray[arrIndex];
timer = $timeout(function () {
updateIt(element, 0, arrIndex, currentText);
}, typeDelay);
}
}
function getTypeDelay(delay) {
if (typeof delay === 'string') {
return delay.charAt(delay.length - 1) === 's' ? parseInt(delay.substring(0, delay.length - 1), 10) * 1000 : +delay;
} else {
return false;
}
}
function getAnimationDelay(delay) {
if (typeof delay === 'string') {
return delay.charAt(delay.length - 1) === 's' ? delay : parseInt(delay.substring(0, delay.length - 1), 10) / 1000;
}
}
function updateValue(element, value) {
if (element.prop('nodeName').toUpperCase() === 'INPUT') {
return element.val(value);
}
return element.html(value);
}
$scope.$on('$destroy', function () {
if (timer) {
$timeout.cancel(timer);
}
});
$scope.$watch('start', function (newVal) {
if (!running && newVal) {
running = !running;
typewrite();
}
});
$scope.$watch('text', function (newVal, oldVal) {
if (!(newVal instanceof Array)) {
currentText = newVal;
typewrite();
}
});
}
return {
restrict: 'A',
link: linkFunction,
replace: true,
scope: {
text: '=',
callbackFn: '&',
iterationCallback: '&',
iterationDelay: '=',
prebeginFn: '&',
start: '='
}
};
}]);
;var Neeto = Neeto || {};
Neeto.crypto = {
generateRandomKey: function generateRandomKey() {
return CryptoJS.lib.WordArray.random(256 / 8).toString();
},
decryptText: function decryptText(encrypted_content, key) {
return CryptoJS.AES.decrypt(encrypted_content, key).toString(CryptoJS.enc.Utf8);
},
encryptText: function encryptText(text, key) {
return CryptoJS.AES.encrypt(text, key).toString();
},
generateRandomEncryptionKey: function generateRandomEncryptionKey() {
var salt = Neeto.crypto.generateRandomKey();
var passphrase = Neeto.crypto.generateRandomKey();
return CryptoJS.PBKDF2(passphrase, salt, { keySize: 256 / 32 }).toString();
},
sha256: function sha256(text) {
return CryptoJS.SHA256(text).toString();
},
/** Generates two deterministic 256 bit keys based on one input */
generateAsymmetricKeyPair: function generateAsymmetricKeyPair(input, salt) {
var output = CryptoJS.PBKDF2(input, salt, { keySize: 512 / 32, hasher: CryptoJS.algo.SHA512, iterations: 3000 });
var firstHalf = _.clone(output);
var secondHalf = _.clone(output);
var sigBytes = output.sigBytes / 2;
var outputLength = output.words.length;
firstHalf.words = output.words.slice(0, outputLength / 2);
secondHalf.words = output.words.slice(outputLength / 2, outputLength);
firstHalf.sigBytes = sigBytes;
secondHalf.sigBytes = sigBytes;
return [firstHalf.toString(), secondHalf.toString()];
},
generateEncryptionKeysForUser: function generateEncryptionKeysForUser(password, email) {
var keys = Neeto.crypto.generateAsymmetricKeyPair(password, email);
var pw = keys[0];
var gk = keys[1];
return { pw: pw, gk: gk };
}
};
},{}]},{},[1]);