Merge branch 'spec-work' of https://github.com/neeto-project/neeto-web-client
This commit is contained in:
@@ -9,6 +9,20 @@ angular
|
||||
.config(function (RestangularProvider, apiControllerProvider) {
|
||||
var url = apiControllerProvider.defaultServerURL();
|
||||
RestangularProvider.setBaseUrl(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.
|
||||
|
||||
@@ -17,4 +17,12 @@ angular.module('app.frontend')
|
||||
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));
|
||||
|
||||
});
|
||||
|
||||
@@ -66,25 +66,25 @@ angular.module('app.frontend')
|
||||
.controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope) {
|
||||
|
||||
this.demoNotes = [
|
||||
{name: "Live print a file with tail", content: "tail -f log/production.log"},
|
||||
{name: "Create SSH tunnel", content: "ssh -i .ssh/key.pem -N -L 3306:example.com:3306 ec2-user@example.com"},
|
||||
{name: "List of processes running on port", content: "lsof -i:8080"},
|
||||
{name: "Set ENV from file", content: "export $(cat .envfile | xargs)"},
|
||||
{name: "Find process by name", content: "ps -ax | grep <application name>"},
|
||||
{name: "NPM install without sudo", content: "sudo chown -R $(whoami) ~/.npm"},
|
||||
{name: "Email validation regex", content: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"},
|
||||
{name: "Ruby generate 256 bit key", content: "Digest::SHA256.hexdigest(SecureRandom.random_bytes(32))"},
|
||||
{name: "Mac add user to user group", content: "sudo dseditgroup -o edit -a USERNAME -t user GROUPNAME"},
|
||||
{name: "Kill Mac OS System Apache", content: "sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist"},
|
||||
{name: "Docker run with mount binding and port", content: "docker run -v /home/vagrant/www/app:/var/www/app -p 8080:80 -d kpi/s3"},
|
||||
{name: "MySQL grant privileges", content: "GRANT [type of permission] ON [database name].[table name] TO ‘[username]’@'%’;"},
|
||||
{name: "MySQL list users", content: "SELECT User FROM mysql.user;"},
|
||||
{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.name;
|
||||
return note.title;
|
||||
});
|
||||
|
||||
this.currentDemoContent = {text: null};
|
||||
@@ -94,7 +94,7 @@ angular.module('app.frontend')
|
||||
}.bind(this)
|
||||
|
||||
this.callback = function(index) {
|
||||
this.currentDemoContent.text = this.demoNotes[index].content;
|
||||
this.currentDemoContent.text = this.demoNotes[index].text;
|
||||
}.bind(this)
|
||||
|
||||
this.contentCallback = function(index) {
|
||||
@@ -102,7 +102,7 @@ angular.module('app.frontend')
|
||||
|
||||
this.setNote = function(note, oldNote) {
|
||||
this.editorMode = 'edit';
|
||||
if(note.content.length == 0) {
|
||||
if(note.content.text.length == 0) {
|
||||
this.focusTitle(100);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ angular.module('app.frontend')
|
||||
}
|
||||
|
||||
this.renderedContent = function() {
|
||||
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.content));
|
||||
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.content.text));
|
||||
}
|
||||
|
||||
var statusTimeout;
|
||||
@@ -235,11 +235,13 @@ angular.module('app.frontend')
|
||||
|
||||
this.saveUrl = function($event) {
|
||||
$event.target.blur();
|
||||
var original = this.note.presentation.root_path;
|
||||
this.note.presentation.root_path = this.url.token;
|
||||
apiController.saveNote(this.user, this.note, function(note){
|
||||
if(!note) {
|
||||
this.note.token = original;
|
||||
|
||||
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 {
|
||||
|
||||
@@ -53,7 +53,7 @@ angular.module('app.frontend')
|
||||
return;
|
||||
}
|
||||
|
||||
this.newGroup = {notes : []};
|
||||
this.newGroup = new Group({notes : []});
|
||||
if(!this.user.id) {
|
||||
this.newGroup.id = Neeto.crypto.generateRandomKey()
|
||||
}
|
||||
@@ -92,7 +92,7 @@ angular.module('app.frontend')
|
||||
}
|
||||
|
||||
this.noteCount = function(group) {
|
||||
var validNotes = apiController.filterDummyNotes(group.notes);
|
||||
var validNotes = Note.filterDummyNotes(group.notes);
|
||||
return validNotes.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,7 @@ angular.module('app.frontend')
|
||||
bindToController: true,
|
||||
|
||||
link:function(scope, elem, attrs, ctrl) {
|
||||
scope.$on('auth:login-success', function(event, user) {
|
||||
ctrl.onAuthSuccess(user);
|
||||
});
|
||||
|
||||
scope.$on('auth:validation-success', function(ev) {
|
||||
setTimeout(function(){
|
||||
ctrl.onValidationSuccess();
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -45,9 +36,8 @@ angular.module('app.frontend')
|
||||
|
||||
this.signOutPressed = function() {
|
||||
this.showAccountMenu = false;
|
||||
$auth.signOut();
|
||||
this.logout()();
|
||||
apiController.clearGk();
|
||||
apiController.signout();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
@@ -55,50 +45,16 @@ angular.module('app.frontend')
|
||||
this.passwordChangeData.status = "Generating New Keys...";
|
||||
|
||||
$timeout(function(){
|
||||
var current_keys = Neeto.crypto.generateEncryptionKeysForUser(this.passwordChangeData.current_password, this.user.email);
|
||||
var new_keys = Neeto.crypto.generateEncryptionKeysForUser(this.passwordChangeData.new_password, this.user.email);
|
||||
// var new_pw_conf_keys = Neeto.crypto.generateEncryptionKeysForUser(this.passwordChangeData.new_password_confirmation, this.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;
|
||||
|
||||
if(data.password == data.password_confirmation) {
|
||||
$auth.updatePassword(data)
|
||||
.then(function(response) {
|
||||
this.showNewPasswordForm = false;
|
||||
if(user.local_encryption_enabled) {
|
||||
// reencrypt data with new gk
|
||||
apiController.reencryptAllNotesAndSave(user, new_keys.gk, current_keys.gk, function(success){
|
||||
if(success) {
|
||||
apiController.setGk(new_keys.gk);
|
||||
alert("Your password has been changed and your data re-encrypted.");
|
||||
} else {
|
||||
// rollback password
|
||||
$auth.updatePassword({current_password: new_keys.pw, password: current_keys.pw, password_confirmation: current_keys.pw })
|
||||
.then(function(response){
|
||||
alert("There was an error changing your password. Your password has been rolled back.");
|
||||
window.location.reload();
|
||||
})
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert("Your password has been changed.");
|
||||
}
|
||||
}.bind(this))
|
||||
.catch(function(response){
|
||||
this.showNewPasswordForm = false;
|
||||
alert("There was an error changing your password. Please try again.");
|
||||
}.bind(this))
|
||||
|
||||
} else {
|
||||
if(data.password != data.password_confirmation) {
|
||||
alert("Your new password does not match its confirmation.");
|
||||
return;
|
||||
}
|
||||
}.bind(this))
|
||||
|
||||
apiController.changePassword(this.user, this.passwordChangeData.current_password, this.passwordChangeData.new_password, function(response){
|
||||
|
||||
})
|
||||
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
this.hasLocalData = function() {
|
||||
@@ -116,17 +72,13 @@ angular.module('app.frontend')
|
||||
this.loginSubmitPressed = function() {
|
||||
this.loginData.status = "Generating Login Keys...";
|
||||
$timeout(function(){
|
||||
var keys = Neeto.crypto.generateEncryptionKeysForUser(this.loginData.user_password, this.loginData.email);
|
||||
var data = {password: keys.pw, email: this.loginData.email};
|
||||
|
||||
apiController.setGk(keys.gk);
|
||||
$auth.submitLogin(data)
|
||||
.then(function(response){
|
||||
|
||||
})
|
||||
.catch(function(response){
|
||||
this.loginData.status = response.errors[0];
|
||||
}.bind(this))
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -134,18 +86,12 @@ angular.module('app.frontend')
|
||||
this.loginData.status = "Generating Account Keys...";
|
||||
|
||||
$timeout(function(){
|
||||
var keys = Neeto.crypto.generateEncryptionKeysForUser(this.loginData.user_password, this.loginData.email);
|
||||
var data = {password: keys.pw, email: this.loginData.email};
|
||||
|
||||
apiController.setGk(keys.gk);
|
||||
|
||||
$auth.submitRegistration(data)
|
||||
.then(function(response) {
|
||||
$auth.user.id = response.data.data.id;
|
||||
this.onAuthSuccess($auth.user);
|
||||
}.bind(this))
|
||||
.catch(function(response) {
|
||||
this.loginData.status = response.data.errors.full_messages[0];
|
||||
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))
|
||||
}
|
||||
@@ -162,19 +108,11 @@ angular.module('app.frontend')
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.onValidationSuccess = function() {
|
||||
if(this.user.local_encryption_enabled) {
|
||||
apiController.verifyEncryptionStatusOfAllNotes(this.user, function(success){
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.encryptionStatusForNotes = function() {
|
||||
var allNotes = this.user.filteredNotes();
|
||||
var countEncrypted = 0;
|
||||
allNotes.forEach(function(note){
|
||||
if(note.isEncrypted()) {
|
||||
if(note.encryptionEnabled()) {
|
||||
countEncrypted++;
|
||||
}
|
||||
}.bind(this))
|
||||
@@ -182,31 +120,6 @@ angular.module('app.frontend')
|
||||
return countEncrypted + "/" + allNotes.length + " notes encrypted";
|
||||
}
|
||||
|
||||
this.toggleEncryptionStatus = function() {
|
||||
this.encryptionConfirmation = true;
|
||||
}
|
||||
|
||||
this.cancelEncryptionChange = function() {
|
||||
this.encryptionConfirmation = false;
|
||||
}
|
||||
|
||||
this.confirmEncryptionChange = function() {
|
||||
|
||||
var callback = function(success, enabled) {
|
||||
if(success) {
|
||||
this.encryptionConfirmation = false;
|
||||
this.user.local_encryption_enabled = enabled;
|
||||
}
|
||||
}.bind(this)
|
||||
|
||||
if(this.user.local_encryption_enabled) {
|
||||
apiController.disableEncryptionForUser(this.user, callback);
|
||||
} else {
|
||||
apiController.enableEncryptionForUser(this.user, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.downloadDataArchive = function() {
|
||||
var link = document.createElement('a');
|
||||
link.setAttribute('download', 'neeto.json');
|
||||
@@ -214,6 +127,22 @@ angular.module('app.frontend')
|
||||
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;
|
||||
|
||||
|
||||
@@ -6,33 +6,23 @@ angular.module('app.frontend')
|
||||
|
||||
var onUserSet = function() {
|
||||
|
||||
$scope.defaultUser.notes = _.map($scope.defaultUser.notes, function(json_obj) {
|
||||
return new Note(json_obj);
|
||||
});
|
||||
$scope.allGroup = new Group({name: "All", all: true});
|
||||
$scope.groups = $scope.defaultUser.groups;
|
||||
|
||||
$scope.defaultUser.filteredNotes = function() {
|
||||
return apiController.filterDummyNotes($scope.defaultUser.notes);
|
||||
}
|
||||
var groups = $scope.defaultUser.groups;
|
||||
var allNotes = $scope.defaultUser.notes;
|
||||
groups.forEach(function(group){
|
||||
var notes = allNotes.filter(function(note){return note.group_id && note.group_id == group.id});
|
||||
group.notes = notes;
|
||||
})
|
||||
$scope.allGroup = {name: "All", all: true};
|
||||
$scope.groups = groups;
|
||||
apiController.verifyEncryptionStatusOfAllNotes($scope.defaultUser, function(success){
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
$auth.validateUser()
|
||||
.then(function () {
|
||||
$scope.defaultUser = new User($auth.user);
|
||||
apiController.getCurrentUser(function(response){
|
||||
if(response && !response.errors) {
|
||||
$scope.defaultUser = new User(response.plain());
|
||||
$rootScope.title = "Notes — Neeto";
|
||||
onUserSet();
|
||||
|
||||
})
|
||||
.catch(function () {
|
||||
} else {
|
||||
$scope.defaultUser = new User(apiController.localUser());
|
||||
onUserSet();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -40,7 +30,7 @@ angular.module('app.frontend')
|
||||
*/
|
||||
|
||||
$scope.updateAllGroup = function() {
|
||||
var allNotes = apiController.filterDummyNotes($scope.defaultUser.notes);
|
||||
var allNotes = Note.filterDummyNotes($scope.defaultUser.notes);
|
||||
$scope.defaultUser.notes = allNotes;
|
||||
$scope.allGroup.notes = allNotes;
|
||||
}
|
||||
@@ -79,6 +69,8 @@ angular.module('app.frontend')
|
||||
originalNote.group_id = null;
|
||||
} else {
|
||||
originalNote.group_id = newGroup.id
|
||||
originalNote.group = newGroup;
|
||||
|
||||
newGroup.notes.unshift(originalNote);
|
||||
newGroup.notes.sort(function(a,b){
|
||||
//subtract to get a value that is either negative, positive, or zero.
|
||||
@@ -86,8 +78,6 @@ angular.module('app.frontend')
|
||||
});
|
||||
}
|
||||
|
||||
originalNote.shared_via_group = newGroup.presentation && newGroup.presentation.enabled;
|
||||
|
||||
apiController.saveNote($scope.defaultUser, originalNote, function(note){
|
||||
_.merge(originalNote, note);
|
||||
});
|
||||
@@ -98,7 +88,7 @@ angular.module('app.frontend')
|
||||
*/
|
||||
|
||||
$scope.notesRemoveGroup = function(group) {
|
||||
var validNotes = apiController.filterDummyNotes(group.notes);
|
||||
var validNotes = Note.filterDummyNotes(group.notes);
|
||||
if(validNotes == 0) {
|
||||
// if no more notes, delete group
|
||||
apiController.deleteGroup($scope.defaultUser, group, function(){
|
||||
@@ -165,8 +155,6 @@ angular.module('app.frontend')
|
||||
$scope.selectedNote = null;
|
||||
}
|
||||
|
||||
note.onDelete();
|
||||
|
||||
if(note.dummy) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,18 +76,12 @@ angular.module('app.frontend')
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.user.local_encryption_enabled) {
|
||||
if(!confirm("Sharing this group will disable local encryption on all group notes.")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var callback = function(username) {
|
||||
apiController.shareGroup(this.user, this.group, function(response){
|
||||
})
|
||||
}.bind(this);
|
||||
|
||||
if(!this.user.getUsername()) {
|
||||
if(!this.user.username) {
|
||||
ngDialog.open({
|
||||
template: 'frontend/modals/username.html',
|
||||
controller: 'UsernameModalCtrl',
|
||||
@@ -99,7 +93,7 @@ angular.module('app.frontend')
|
||||
disableAnimation: true
|
||||
});
|
||||
} else {
|
||||
callback(this.user.getUsername());
|
||||
callback(this.user.username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,16 +132,13 @@ angular.module('app.frontend')
|
||||
this.selectNote = function(note) {
|
||||
this.selectedNote = note;
|
||||
this.selectionMade()(note);
|
||||
|
||||
note.onDelete = function() {
|
||||
this.setNotes(this.group.notes, false);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
this.createNewNote = function() {
|
||||
var name = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : "");
|
||||
this.newNote = new Note({name: name, content: '', dummy: true});
|
||||
this.newNote.shared_via_group = this.group.presentation && this.group.presentation.enabled;
|
||||
var title = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : "");
|
||||
this.newNote = new Note({dummy: true});
|
||||
this.newNote.content.title = title;
|
||||
this.newNote.group = this.group;
|
||||
this.selectNote(this.newNote);
|
||||
this.addNew()(this.newNote);
|
||||
}
|
||||
@@ -158,7 +149,7 @@ angular.module('app.frontend')
|
||||
if(this.noteFilter.text.length == 0) {
|
||||
note.visible = true;
|
||||
} else {
|
||||
note.visible = note.name.toLowerCase().includes(this.noteFilter.text) || note.content.toLowerCase().includes(this.noteFilter.text);
|
||||
note.visible = note.title.toLowerCase().includes(this.noteFilter.text) || note.text.toLowerCase().includes(this.noteFilter.text);
|
||||
}
|
||||
return note.visible;
|
||||
}.bind(this)
|
||||
|
||||
3
app/assets/javascripts/app/frontend/models/group.js
Normal file
3
app/assets/javascripts/app/frontend/models/group.js
Normal file
@@ -0,0 +1,3 @@
|
||||
var Group = function (json_obj) {
|
||||
_.merge(this, json_obj);
|
||||
};
|
||||
@@ -1,18 +1,55 @@
|
||||
var Note = function (json_obj) {
|
||||
var content;
|
||||
|
||||
Object.defineProperty(this, "content", {
|
||||
get: function() {
|
||||
return content;
|
||||
},
|
||||
set: function(value) {
|
||||
var finalValue = value;
|
||||
|
||||
if(typeof value === 'string') {
|
||||
try {
|
||||
decodedValue = JSON.parse(value);
|
||||
finalValue = decodedValue;
|
||||
}
|
||||
catch(e) {
|
||||
finalValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
content = finalValue;
|
||||
},
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
this.setContentRaw = function(rawContent) {
|
||||
content = rawContent;
|
||||
}
|
||||
|
||||
_.merge(this, json_obj);
|
||||
|
||||
if(!this.content) {
|
||||
this.content = {title: "", text: ""};
|
||||
}
|
||||
};
|
||||
|
||||
Note.filterDummyNotes = function(notes) {
|
||||
var filtered = notes.filter(function(note){return note.dummy == false || note.dummy == null});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/* Returns true if note is shared individually or via group */
|
||||
Note.prototype.isPublic = function() {
|
||||
return this.hasEnabledPresentation() || this.shared_via_group;
|
||||
return this.presentation || (this.group && this.group.presentation);
|
||||
};
|
||||
|
||||
Note.prototype.isEncrypted = function() {
|
||||
return this.local_eek ? true : false;
|
||||
return this.encryptionEnabled() && typeof this.content === 'string' ? true : false;
|
||||
}
|
||||
|
||||
Note.prototype.hasEnabledPresentation = function() {
|
||||
return this.presentation && this.presentation.enabled;
|
||||
Note.prototype.encryptionEnabled = function() {
|
||||
return this.loc_eek;
|
||||
}
|
||||
|
||||
Note.prototype.presentationURL = function() {
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
var User = function (json_obj) {
|
||||
_.merge(this, json_obj);
|
||||
|
||||
this.notes = _.map(this.notes, function(json_obj) {
|
||||
return new Note(json_obj);
|
||||
});
|
||||
|
||||
this.groups = _.map(this.groups, function(json_obj) {
|
||||
return new Group(json_obj);
|
||||
});
|
||||
|
||||
this.groups.forEach(function(group){
|
||||
var notes = this.notes.filter(function(note){return note.group_id && note.group_id == group.id});
|
||||
notes.forEach(function(note){
|
||||
note.group = group;
|
||||
})
|
||||
group.notes = notes;
|
||||
}.bind(this))
|
||||
};
|
||||
|
||||
User.prototype.getUsername = function() {
|
||||
if(!this.presentation) {
|
||||
return null;
|
||||
}
|
||||
return this.presentation.root_path;
|
||||
};
|
||||
User.prototype.filteredNotes = function() {
|
||||
return Note.filterDummyNotes(this.notes);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,86 @@ angular.module('app.services')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Auth
|
||||
*/
|
||||
|
||||
this.getCurrentUser = function(callback) {
|
||||
if(!localStorage.getItem("jwt")) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
Restangular.one("users/current").get().then(function(response){
|
||||
callback(response);
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -60,46 +140,6 @@ angular.module('app.services')
|
||||
})
|
||||
}
|
||||
|
||||
this.enableEncryptionForUser = function(user, callback) {
|
||||
Restangular.one("users", user.id).one('enable_encryption').post().then(function(response){
|
||||
|
||||
var enabled = response.plain().local_encryption_enabled;
|
||||
if(!enabled) {
|
||||
callback(false, enabled);
|
||||
return;
|
||||
}
|
||||
this.handleEncryptionStatusChange(user, enabled, callback);
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
this.disableEncryptionForUser = function(user, callback) {
|
||||
|
||||
Restangular.one("users", user.id).one('disable_encryption').post().then(function(response){
|
||||
var enabled = response.plain().local_encryption_enabled;
|
||||
|
||||
if(enabled) {
|
||||
// something went wrong
|
||||
callback(false, enabled);
|
||||
return;
|
||||
}
|
||||
this.handleEncryptionStatusChange(user, enabled, callback);
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
this.handleEncryptionStatusChange = function(user, encryptionEnabled, callback) {
|
||||
var allNotes = user.filteredNotes();
|
||||
if(encryptionEnabled) {
|
||||
allNotes = allNotes.filter(function(note){return note.isPublic() == false});
|
||||
this.encryptNotes(allNotes, this.retrieveGk());
|
||||
} else {
|
||||
this.decryptNotes(allNotes, this.retrieveGk());
|
||||
}
|
||||
|
||||
this.saveBatchNotes(user, allNotes, encryptionEnabled, function(success) {
|
||||
callback(success, encryptionEnabled);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
/*
|
||||
Ensures that if encryption is disabled, all local notes are uncrypted,
|
||||
and that if it's enabled, that all local notes are encrypted
|
||||
@@ -107,25 +147,21 @@ angular.module('app.services')
|
||||
this.verifyEncryptionStatusOfAllNotes = function(user, callback) {
|
||||
var allNotes = user.filteredNotes();
|
||||
var notesNeedingUpdate = [];
|
||||
var key = this.retrieveGk();
|
||||
allNotes.forEach(function(note){
|
||||
if(user.local_encryption_enabled && !note.isPublic()) {
|
||||
if(!note.isEncrypted()) {
|
||||
// needs encryption
|
||||
this.encryptSingleNote(note, key);
|
||||
if(!note.isPublic()) {
|
||||
if(note.encryptionEnabled() && !note.isEncrypted()) {
|
||||
notesNeedingUpdate.push(note);
|
||||
}
|
||||
} else {
|
||||
if(note.isEncrypted()) {
|
||||
// needs decrypting
|
||||
this.decryptSingleNote(note, key);
|
||||
notesNeedingUpdate.push(note);
|
||||
}
|
||||
}
|
||||
}.bind(this))
|
||||
|
||||
if(notesNeedingUpdate.length > 0) {
|
||||
this.saveBatchNotes(user, notesNeedingUpdate, user.local_encryption_enabled, callback)
|
||||
console.log("verifying encryption, notes need updating", notesNeedingUpdate);
|
||||
this.saveBatchNotes(user, notesNeedingUpdate, callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,37 +206,31 @@ angular.module('app.services')
|
||||
}
|
||||
|
||||
this.shareGroup = function(user, group, callback) {
|
||||
var shareFn = function() {
|
||||
Restangular.one("users", user.id).one("groups", group.id).one("share").post()
|
||||
Restangular.one("users", user.id).one("groups", group.id).one("presentations").post()
|
||||
.then(function(response){
|
||||
var obj = response.plain();
|
||||
group.notes.forEach(function(note){
|
||||
note.shared_via_group = true;
|
||||
});
|
||||
_.merge(group, {presentation: obj.presentation});
|
||||
callback(obj);
|
||||
})
|
||||
}
|
||||
var presentation = response.plain();
|
||||
_.merge(group, {presentation: presentation});
|
||||
callback(presentation);
|
||||
|
||||
if(user.local_encryption_enabled && group.notes.length > 0) {
|
||||
// decrypt group notes first
|
||||
var notes = group.notes;
|
||||
this.decryptNotesWithLocalKey(notes);
|
||||
this.saveBatchNotes(user, notes, false, function(success){
|
||||
shareFn();
|
||||
})
|
||||
} else {
|
||||
shareFn();
|
||||
}
|
||||
if(group.notes.length > 0) {
|
||||
// decrypt notes
|
||||
this.saveBatchNotes(user, group.notes, function(success){})
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
this.unshareGroup = function(user, group, callback) {
|
||||
Restangular.one("users", user.id).one("groups", group.id).one("unshare").post()
|
||||
.then(function(response){
|
||||
var obj = response.plain();
|
||||
_.merge(group, {presentation: obj.presentation});
|
||||
callback(obj);
|
||||
})
|
||||
var request = Restangular.one("users", user.id).one("groups", group.id).one("presentations", group.presentation.id);
|
||||
request.remove().then(function(response){
|
||||
group.presentation = null;
|
||||
callback(null);
|
||||
|
||||
if(group.notes.length > 0) {
|
||||
// encrypt notes
|
||||
var notes = group.notes;
|
||||
this.saveBatchNotes(user, notes, function(success){})
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
|
||||
@@ -211,41 +241,17 @@ angular.module('app.services')
|
||||
Notes
|
||||
*/
|
||||
|
||||
this.saveBatchNotes = function(user, notes, encryptionEnabled, callback) {
|
||||
notes = _.cloneDeep(notes);
|
||||
notes.forEach(function(note){
|
||||
if(encryptionEnabled && !note.isPublic()) {
|
||||
note.content = null;
|
||||
note.name = null;
|
||||
} else {
|
||||
note.local_encrypted_content = null;
|
||||
note.local_eek = null;
|
||||
}
|
||||
})
|
||||
|
||||
this.saveBatchNotes = function(user, notes, callback) {
|
||||
var request = Restangular.one("users", user.id).one("notes/batch_update");
|
||||
request.notes = notes;
|
||||
request.notes = _.map(notes, function(note){
|
||||
return this.createRequestParamsFromNote(note, user);
|
||||
}.bind(this));
|
||||
request.put().then(function(response){
|
||||
var success = response.plain().success;
|
||||
callback(success);
|
||||
})
|
||||
}
|
||||
|
||||
this.preprocessNoteForSaving = function(note, user) {
|
||||
if(user.local_encryption_enabled && !note.pending_share && !note.isPublic()) {
|
||||
// encrypt
|
||||
this.encryptSingleNote(note, this.retrieveGk());
|
||||
note.content = null; // dont send unencrypted content to server
|
||||
note.name = null;
|
||||
}
|
||||
else {
|
||||
// decrypt
|
||||
note.local_encrypted_content = null;
|
||||
note.local_eek = null;
|
||||
note.local_encryption_scheme = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.saveNote = function(user, note, callback) {
|
||||
if(!user.id) {
|
||||
this.writeUserToLocalStorage(user);
|
||||
@@ -253,18 +259,14 @@ angular.module('app.services')
|
||||
return;
|
||||
}
|
||||
|
||||
var snipCopy = _.cloneDeep(note);
|
||||
|
||||
this.preprocessNoteForSaving(snipCopy, user);
|
||||
var params = this.createRequestParamsFromNote(note, user);
|
||||
|
||||
var request = Restangular.one("users", user.id).one("notes", note.id);
|
||||
_.merge(request, snipCopy);
|
||||
|
||||
_.merge(request, params);
|
||||
request.customOperation(request.id ? "put" : "post")
|
||||
.then(function(response) {
|
||||
var responseObject = response.plain();
|
||||
responseObject.content = note.content;
|
||||
responseObject.name = note.name;
|
||||
_.merge(note, responseObject);
|
||||
callback(note);
|
||||
})
|
||||
@@ -273,6 +275,25 @@ angular.module('app.services')
|
||||
})
|
||||
}
|
||||
|
||||
this.createRequestParamsFromNote = function(note, user) {
|
||||
var params = {id: note.id};
|
||||
|
||||
if(!note.pending_share && !note.isPublic()) {
|
||||
// encrypted
|
||||
var noteCopy = _.cloneDeep(note);
|
||||
this.encryptSingleNote(noteCopy, this.retrieveGk());
|
||||
params.content = noteCopy.content;
|
||||
params.loc_eek = noteCopy.loc_eek;
|
||||
}
|
||||
else {
|
||||
// decrypted
|
||||
params.content = JSON.stringify(note.content);
|
||||
params.loc_eek = null;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
this.deleteNote = function(user, note, callback) {
|
||||
if(!user.id) {
|
||||
this.writeUserToLocalStorage(user);
|
||||
@@ -289,10 +310,10 @@ angular.module('app.services')
|
||||
if(!user.id) {
|
||||
if(confirm("Note: You are not signed in. Any note you share cannot be edited or unshared.")) {
|
||||
var request = Restangular.one("notes").one("share");
|
||||
_.merge(request, {name: note.name, content: note.content});
|
||||
_.merge(request, {name: note.content.title, content: note.content});
|
||||
request.post().then(function(response){
|
||||
var obj = response.plain();
|
||||
_.merge(note, {presentation: obj.presentation});
|
||||
var presentation = response.plain();
|
||||
_.merge(note, {presentation: presentation});
|
||||
note.locked = true;
|
||||
this.writeUserToLocalStorage(user);
|
||||
callback(note);
|
||||
@@ -300,36 +321,86 @@ angular.module('app.services')
|
||||
}
|
||||
} else {
|
||||
var shareFn = function(note, callback) {
|
||||
Restangular.one("users", user.id).one("notes", note.id).one("share").post()
|
||||
Restangular.one("users", user.id).one("notes", note.id).one("presentations").post()
|
||||
.then(function(response){
|
||||
var obj = response.plain();
|
||||
_.merge(note, {presentation: obj.presentation});
|
||||
var presentation = response.plain();
|
||||
_.merge(note, {presentation: presentation});
|
||||
callback(note);
|
||||
})
|
||||
}
|
||||
|
||||
if(user.local_encryption_enabled) {
|
||||
if(confirm("Note: Sharing this note will remove its local encryption.")) {
|
||||
note.pending_share = true;
|
||||
this.saveNote(user, note, function(saved_note){
|
||||
shareFn(saved_note, callback);
|
||||
})
|
||||
}
|
||||
} else {
|
||||
shareFn(note, callback);
|
||||
}
|
||||
note.pending_share = true;
|
||||
this.saveNote(user, note, function(saved_note){
|
||||
shareFn(saved_note, callback);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.unshareNote = function(user, note, callback) {
|
||||
Restangular.one("users", user.id).one("notes", note.id).one("unshare").post()
|
||||
.then(function(response){
|
||||
var obj = response.plain();
|
||||
_.merge(note, {presentation: obj.presentation});
|
||||
callback(note);
|
||||
var request = Restangular.one("users", user.id).one("notes", note.id).one("presentations", note.presentation.id);
|
||||
request.remove().then(function(response){
|
||||
note.presentation = null;
|
||||
callback(null);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Presentations
|
||||
*/
|
||||
|
||||
this.updatePresentation = function(resource, presentation, callback) {
|
||||
var request = Restangular.one("users", user.id)
|
||||
.one(resource.constructor.name.toLowerCase() + "s", 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) {
|
||||
@@ -347,18 +418,52 @@ angular.module('app.services')
|
||||
return textFile;
|
||||
}.bind(this);
|
||||
|
||||
// remove irrelevant keys
|
||||
var presentationParams = function(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,
|
||||
name: note.name,
|
||||
uuid: note.uuid,
|
||||
content: note.content,
|
||||
group_id: note.group_id,
|
||||
created_at: note.created_at,
|
||||
modified_at: note.modified_at,
|
||||
group_id: note.group_id
|
||||
presentation: presentationParams(note.presentation)
|
||||
}
|
||||
});
|
||||
return makeTextFile(JSON.stringify(notes, null, 2 /* pretty print */));
|
||||
|
||||
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 */));
|
||||
}
|
||||
|
||||
|
||||
@@ -388,18 +493,13 @@ angular.module('app.services')
|
||||
|
||||
|
||||
|
||||
this.filterDummyNotes = function(notes) {
|
||||
var filtered = notes.filter(function(note){return note.dummy == false || note.dummy == null});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
this.staticifyObject = function(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
|
||||
this.writeUserToLocalStorage = function(user) {
|
||||
var saveUser = _.cloneDeep(user);
|
||||
saveUser.notes = this.filterDummyNotes(saveUser.notes);
|
||||
saveUser.notes = Note.filterDummyNotes(saveUser.notes);
|
||||
saveUser.groups.forEach(function(group){
|
||||
group.notes = null;
|
||||
}.bind(this))
|
||||
@@ -455,20 +555,20 @@ angular.module('app.services')
|
||||
localStorage.setItem('gk', gk);
|
||||
}
|
||||
|
||||
this.clearGk = function() {
|
||||
this.signout = function() {
|
||||
localStorage.removeItem("jwt");
|
||||
localStorage.removeItem("gk");
|
||||
}
|
||||
|
||||
this.encryptSingleNote = function(note, key) {
|
||||
var ek = null;
|
||||
if(note.isEncrypted()) {
|
||||
ek = Neeto.crypto.decryptText(note.local_eek, key);
|
||||
if(note.loc_eek) {
|
||||
ek = Neeto.crypto.decryptText(note.loc_eek, key);
|
||||
} else {
|
||||
ek = Neeto.crypto.generateRandomEncryptionKey();
|
||||
note.local_eek = Neeto.crypto.encryptText(ek, key);
|
||||
note.loc_eek = Neeto.crypto.encryptText(ek, key);
|
||||
}
|
||||
var text = JSON.stringify({name: note.name, content: note.content});
|
||||
note.local_encrypted_content = Neeto.crypto.encryptText(text, ek);
|
||||
note.content = Neeto.crypto.encryptText(JSON.stringify(note.content), ek);
|
||||
note.local_encryption_scheme = "1.0";
|
||||
}
|
||||
|
||||
@@ -486,19 +586,27 @@ angular.module('app.services')
|
||||
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.local_eek, key);
|
||||
var obj = JSON.parse(Neeto.crypto.decryptText(note.local_encrypted_content, ek));
|
||||
note.name = obj.name;
|
||||
note.content = obj.content;
|
||||
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);
|
||||
}
|
||||
@@ -514,13 +622,13 @@ angular.module('app.services')
|
||||
notes.forEach(function(note){
|
||||
if(note.isEncrypted()) {
|
||||
// first decrypt eek with old key
|
||||
var ek = Neeto.crypto.decryptText(note.local_eek, oldKey);
|
||||
var ek = Neeto.crypto.decryptText(note.loc_eek, oldKey);
|
||||
// now encrypt ek with new key
|
||||
note.local_eek = Neeto.crypto.encryptText(ek, newKey);
|
||||
note.loc_eek = Neeto.crypto.encryptText(ek, newKey);
|
||||
}
|
||||
});
|
||||
|
||||
this.saveBatchNotes(user, notes, true, function(success) {
|
||||
this.saveBatchNotes(user, notes, function(success) {
|
||||
callback(success);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
angular
|
||||
.module('app.services')
|
||||
.directive('fileChange', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
handler: '&'
|
||||
},
|
||||
link: function (scope, element) {
|
||||
element.on('change', function (event) {
|
||||
scope.$apply(function(){
|
||||
scope.handler({files: event.target.files});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -12,6 +12,6 @@ angular
|
||||
})
|
||||
.controller('SingleNoteCtrl', function ($rootScope, $scope, $state, markdownRenderer) {
|
||||
$scope.renderedContent = function() {
|
||||
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText($scope.note.content));
|
||||
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText($scope.note.text));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.content
|
||||
.section-title-bar.editor-heading{"ng-class" => "{'shared' : ctrl.note.isPublic() }"}
|
||||
.title
|
||||
%input.input#note-title-editor{"ng-model" => "ctrl.note.name", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)",
|
||||
%input.input#note-title-editor{"ng-model" => "ctrl.note.content.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)",
|
||||
"ng-disabled" => "ctrl.note.locked", "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()",
|
||||
"select-on-click" => "true"}
|
||||
.save-status {{ctrl.noteStatus}}
|
||||
@@ -26,11 +26,11 @@
|
||||
%li
|
||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleMarkdown()"} Toggle Markdown Preview
|
||||
.shortcut Cmd + M
|
||||
%li{"ng-if" => "!ctrl.note.hasEnabledPresentation()"}
|
||||
%li{"ng-if" => "!ctrl.note.presentation"}
|
||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.shareNote()"} Share
|
||||
%li{"ng-if" => "ctrl.note.hasEnabledPresentation()"}
|
||||
%li{"ng-if" => "ctrl.note.presentation"}
|
||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.editUrlPressed()"} Edit URL
|
||||
%li{"ng-if" => "ctrl.note.hasEnabledPresentation()"}
|
||||
%li{"ng-if" => "ctrl.note.presentation"}
|
||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.unshareNote()"} Unshare
|
||||
%li
|
||||
%a.text{"ng-click" => "ctrl.deleteNote()"} Delete
|
||||
@@ -40,7 +40,7 @@
|
||||
.panel-body{"style" => "text-align: center; color: black;"}
|
||||
This editor is Markdown enabled.
|
||||
.menu-right-container
|
||||
.public-link{"ng-if" => "ctrl.note.hasEnabledPresentation()"}
|
||||
.public-link{"ng-if" => "ctrl.note.presentation"}
|
||||
%a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForNote(ctrl.note)}}", "target" => "_blank"}
|
||||
%span.icon-rss.icon
|
||||
{{ctrl.publicUrlForNote(note)}}
|
||||
@@ -56,6 +56,6 @@
|
||||
"iteration-callback" => "ctrl.callback", "prebegin-fn" => "ctrl.prebeginFn", "iteration-delay" => "2000", "cursor" => ""}
|
||||
%code{"ng-if" => "ctrl.currentDemoContent.text"}
|
||||
.content-sampler.sampler{"typewrite" => "true", "text" => "ctrl.currentDemoContent.text", "type-delay" => "10", "iteration-callback" => "ctrl.contentCallback"}
|
||||
%textarea.editable#note-text-editor{"ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.content",
|
||||
%textarea.editable#note-text-editor{"ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.content.text",
|
||||
"ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"}
|
||||
.preview{"ng-if" => "ctrl.editorMode == 'preview'", "ng-bind-html" => "ctrl.renderedContent()", "ng-dblclick" => "ctrl.onPreviewDoubleClick()"}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
.count {{ctrl.noteCount(ctrl.allGroup)}}
|
||||
.group{"ng-repeat" => "group in ctrl.groups", "ng-click" => "ctrl.selectGroup(group)", "ng-class" => "{'selected' : ctrl.selectedGroup == group}",
|
||||
"droppable" => true, "drop" => "ctrl.handleDrop", "group" => "group"}
|
||||
.icon.icon-rss{"ng-if" => "group.presentation.enabled"}
|
||||
.icon.icon-rss{"ng-if" => "group.presentation"}
|
||||
%input.title{"ng-disabled" => "group != ctrl.selectedGroup", "ng-model" => "group.name",
|
||||
"ng-keyup" => "$event.keyCode == 13 && ctrl.saveGroup($event, group)", "mb-autofocus" => "true", "should-focus" => "ctrl.newGroup",
|
||||
"ng-change" => "ctrl.groupTitleDidChange(group)", "ng-focus" => "ctrl.onGroupTitleFocus(group)"}
|
||||
|
||||
@@ -52,28 +52,8 @@
|
||||
.title Local Encryption
|
||||
.desc Encrypt notes locally before sending to server. Neither the server owner nor an intrusive government can decrypt your locally encrypted notes.
|
||||
.action-container
|
||||
%span.status-title Status:
|
||||
{{ctrl.user.local_encryption_enabled ? 'enabled' : 'disabled'}}
|
||||
{{" | "}}
|
||||
%a{"ng-click" => "ctrl.toggleEncryptionStatus()"}
|
||||
{{ctrl.user.local_encryption_enabled ? 'Disable' : 'Enable'}}
|
||||
.subtext{"ng-if" => "ctrl.user.local_encryption_enabled"}
|
||||
{{ctrl.encryptionStatusForNotes()}} (shared notes not encrypted)
|
||||
.encryption-confirmation{"ng-if" => "ctrl.encryptionConfirmation"}
|
||||
%div{"ng-if" => "ctrl.user.local_encryption_enabled"}
|
||||
%p Are you sure you want to disable local encryption? All currently encrypted notes will be decrypted locally, then sent back to Neeto servers over a secure connection.
|
||||
%div{"ng-if" => "!ctrl.user.local_encryption_enabled"}
|
||||
%p We're glad you're taking privacy and security into your own hands. There are a couple things you should note about moving to local encryption:
|
||||
%ul
|
||||
%li
|
||||
If you forget your password, there is no way to reset it or recover it. Your data will be forever lost without your password.
|
||||
(You can however still change your password, as long as you know your current password.)
|
||||
%li
|
||||
The strength of the encryption is tied to the strength of your password. If you take your security seriously, you should use a password of at least 32 characters long.
|
||||
%p Are you sure you want to enable local encryption?
|
||||
.buttons
|
||||
%a.cancel{"ng-click" => "ctrl.cancelEncryptionChange()"} Cancel
|
||||
%a.confirm{"ng-click" => "ctrl.confirmEncryptionChange()"} Confirm
|
||||
%span.status-title Status: enabled.
|
||||
{{ctrl.encryptionStatusForNotes()}} (shared notes not encrypted)
|
||||
.account-item{"ng-if" => "ctrl.user.email"}
|
||||
.icon-container
|
||||
%img.icon.archive{"lazy-img" => "assets/archive.png"}
|
||||
@@ -82,6 +62,10 @@
|
||||
.desc Note: data archives that you download using the link below are decrypted before save. You should take care to store them in a safe location.
|
||||
.action-container
|
||||
%a#download-archive{"ng-click" => "ctrl.downloadDataArchive()"} Download Latest Data Archive
|
||||
%label#import-archive
|
||||
%input{"type" => "file", "style" => "display: none;", "file-change" => "", "handler" => "ctrl.importFileSelected(files)"}
|
||||
%span
|
||||
Import Data from Archive
|
||||
|
||||
.account-item
|
||||
.meta-container
|
||||
@@ -134,4 +118,3 @@
|
||||
%strong.question How does local encryption work?
|
||||
%p.answer Users who opt into using local encryption can add an additional layer of security over their notes. These notes will be encrypted locally on your machine before being sent over the wire. This means that when Neeto receives your notes, we have no idea what the contents are. And if a government ever forces us to give up your data, we couldn't decrypt it for them even if we wanted to.
|
||||
%p This encryption is based on your password, which is also never sent over the air. The strength of this encryption is directly tied to the strength of your password.
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
%span.caret
|
||||
%span.sr-only
|
||||
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
|
||||
%li{"ng-if" => "!ctrl.group.presentation.enabled"}
|
||||
%li{"ng-if" => "!ctrl.group.presentation"}
|
||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupShare($event)"} Share Group
|
||||
%li{"ng-if" => "ctrl.group.presentation.enabled"}
|
||||
%li{"ng-if" => "ctrl.group.presentation"}
|
||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupUnshare()"} Unshare Group
|
||||
%li{"ng-if" => "!ctrl.group.all"}
|
||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupDelete()"} Delete Group
|
||||
.menu-right-container
|
||||
.public-link{"ng-if" => "ctrl.group.presentation.enabled"}
|
||||
.public-link{"ng-if" => "ctrl.group.presentation"}
|
||||
%a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForGroup(ctrl.group)}}", "target" => "_blank"}
|
||||
%span.icon-rss.icon
|
||||
{{ctrl.publicUrlForGroup()}}
|
||||
@@ -34,5 +34,5 @@
|
||||
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}",
|
||||
"ng-attr-draggable" => "{{note.dummy ? undefined : 'true'}}", "note" => "note"}
|
||||
.name
|
||||
{{note.name}}
|
||||
{{note.content.title}}
|
||||
.date {{note.created_at || 'Now'}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Configure sensitive parameters which will be filtered from the log file.
|
||||
Rails.application.config.filter_parameters += [:password, :content, :name, :local_encrypted_content, :local_eek]
|
||||
Rails.application.config.filter_parameters += [:password]
|
||||
|
||||
Reference in New Issue
Block a user