working functionality

This commit is contained in:
Mo Bitar
2016-12-15 17:37:26 -06:00
parent 6a2e3e9ec1
commit 1722eb299c
31 changed files with 944 additions and 760 deletions

View File

@@ -14,9 +14,10 @@ angular
]) ])
// Configure path to API // Configure path to API
.config(function (RestangularProvider, apiControllerProvider) { .config(function (RestangularProvider, apiControllerProvider) {
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
var url = apiControllerProvider.defaultServerURL(); var url = apiControllerProvider.defaultServerURL();
RestangularProvider.setBaseUrl(url); RestangularProvider.setBaseUrl(url);
console.log(url);
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) { RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
var token = localStorage.getItem("jwt"); var token = localStorage.getItem("jwt");
@@ -32,4 +33,3 @@ angular
}; };
}); });
}) })
}

View File

@@ -63,7 +63,7 @@ angular.module('app.frontend')
} }
} }
}) })
.controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope) { .controller('EditorCtrl', function ($sce, $timeout, apiController, modelManager, markdownRenderer, $rootScope) {
this.demoNotes = [ this.demoNotes = [
{title: "Live print a file with tail", content: "tail -f log/production.log"}, {title: "Live print a file with tail", content: "tail -f log/production.log"},
@@ -74,14 +74,14 @@ angular.module('app.frontend')
{title: "NPM install without sudo", content: "sudo chown -R $(whoami) ~/.npm"}, {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: "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: "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: "Mac add user to user tag", content: "sudo dsedittag -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: "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: "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 grant privileges", content: "GRANT [type of permission] ON [database name].[table name] TO [username]@'%;"},
{title: "MySQL list users", content: "SELECT User FROM mysql.user;"}, {title: "MySQL list users", content: "SELECT User FROM mysql.user;"},
]; ];
this.showSampler = !this.user.id && this.user.filteredNotes().length == 0; this.showSampler = !this.user.id && modelManager.filteredNotes.length == 0;
this.demoNoteNames = _.map(this.demoNotes, function(note){ this.demoNoteNames = _.map(this.demoNotes, function(note){
return note.title; return note.title;

View File

@@ -1,104 +0,0 @@
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(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)
});

View File

@@ -17,7 +17,7 @@ angular.module('app.frontend')
} }
} }
}) })
.controller('HeaderCtrl', function ($auth, $state, apiController, serverSideValidation, $timeout) { .controller('HeaderCtrl', function ($auth, $state, apiController, modelManager, serverSideValidation, $timeout) {
this.changePasswordPressed = function() { this.changePasswordPressed = function() {
this.showNewPasswordForm = !this.showNewPasswordForm; this.showNewPasswordForm = !this.showNewPasswordForm;
@@ -58,12 +58,12 @@ angular.module('app.frontend')
} }
this.hasLocalData = function() { this.hasLocalData = function() {
return this.user.filteredNotes().length > 0; return modelManager.filteredNotes.length > 0;
} }
this.mergeLocalChanged = function() { this.mergeLocalChanged = function() {
if(!this.user.shouldMerge) { 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?")) { if(!confirm("Unchecking this option means any locally stored tags and notes you have now will be deleted. Are you sure you want to continue?")) {
this.user.shouldMerge = true; this.user.shouldMerge = true;
} }
} }
@@ -109,7 +109,7 @@ angular.module('app.frontend')
} }
this.encryptionStatusForNotes = function() { this.encryptionStatusForNotes = function() {
var allNotes = this.user.filteredNotes(); var allNotes = modelManager.filteredNotes;
var countEncrypted = 0; var countEncrypted = 0;
allNotes.forEach(function(note){ allNotes.forEach(function(note){
if(note.encryptionEnabled()) { if(note.encryptionEnabled()) {

View File

@@ -5,19 +5,19 @@ angular.module('app.frontend')
$rootScope.description = "A secure code box for developers to store common commands and useful notes."; $rootScope.description = "A secure code box for developers to store common commands and useful notes.";
var onUserSet = function() { var onUserSet = function() {
apiController.setUser($scope.defaultUser);
$scope.allTag = new Tag({all: true});
$scope.allTag.content.name = "All";
$scope.tags = modelManager.tags;
$scope.allGroup = new Group({name: "All", all: true}); // apiController.verifyEncryptionStatusOfAllItems($scope.defaultUser, function(success){});
$scope.groups = modelManager.groups;
apiController.verifyEncryptionStatusOfAllItems($scope.defaultUser, function(success){
});
} }
apiController.getCurrentUser(function(response){ apiController.getCurrentUser(function(response){
if(response && !response.errors) { if(response && !response.errors) {
console.log("Get user response", response);
$scope.defaultUser = new User(response); $scope.defaultUser = new User(response);
modelManager.items = response.items; modelManager.items = _.map(response.items, function(json_obj){return new Item(json_obj)});
$rootScope.title = "Notes — Neeto"; $rootScope.title = "Notes — Neeto";
onUserSet(); onUserSet();
} else { } else {
@@ -27,44 +27,44 @@ angular.module('app.frontend')
}); });
/* /*
Groups Ctrl Callbacks Tags Ctrl Callbacks
*/ */
$scope.updateAllGroup = function() { $scope.updateAllTag = function() {
$scope.allGroup.notes = modelManager.filteredNotes; $scope.allTag.notes = modelManager.filteredNotes;
} }
$scope.groupsWillMakeSelection = function(group) { $scope.tagsWillMakeSelection = function(tag) {
if(group.all) { if(tag.all) {
$scope.updateAllGroup(); $scope.updateAllTag();
} }
} }
$scope.groupsSelectionMade = function(group) { $scope.tagsSelectionMade = function(tag) {
if(!group.notes) { if(!tag.notes) {
group.notes = []; tag.notes = [];
} }
$scope.selectedGroup = group; $scope.selectedTag = tag;
} }
$scope.groupsAddNew = function(group) { $scope.tagsAddNew = function(tag) {
modelManager.addTag(group); modelManager.addTag(tag);
} }
$scope.groupsSave = function(group, callback) { $scope.tagsSave = function(tag, callback) {
apiController.saveItems([group], callback); apiController.saveItems([tag], callback);
} }
/* /*
Called to update the group of a note after drag and drop change Called to update the tag of a note after drag and drop change
The note object is a copy of the original The note object is a copy of the original
*/ */
$scope.groupsUpdateNoteGroup = function(noteCopy, newGroup, oldGroup) { $scope.tagsUpdateNoteTag = function(noteCopy, newTag, oldTag) {
var originalNote = _.find($scope.defaultUser.notes, {uuid: noteCopy.uuid}); var originalNote = _.find($scope.defaultUser.notes, {uuid: noteCopy.uuid});
modelManager.removeTagFromNote(oldGroup, originalNote); modelManager.removeTagFromNote(oldTag, originalNote);
if(!newGroup.all) { if(!newTag.all) {
modelManager.addTagToNote(newGroup, originalNote); modelManager.addTagToNote(newTag, originalNote);
} }
apiController.saveDirtyItems(function(){}); apiController.saveDirtyItems(function(){});
@@ -74,19 +74,19 @@ angular.module('app.frontend')
Notes Ctrl Callbacks Notes Ctrl Callbacks
*/ */
$scope.notesRemoveGroup = function(group) { $scope.notesRemoveTag = function(tag) {
var validNotes = Note.filterDummyNotes(group.notes); var validNotes = Note.filterDummyNotes(tag.notes);
if(validNotes == 0) { if(validNotes == 0) {
// if no more notes, delete group // if no more notes, delete tag
apiController.deleteItem($scope.defaultUser, group, function(){ apiController.deleteItem($scope.defaultUser, tag, function(){
// force scope groups to update on sub directives // force scope tags to update on sub directives
$scope.groups = []; $scope.tags = [];
$timeout(function(){ $timeout(function(){
$scope.groups = modelManager.groups; $scope.tags = modelManager.tags;
}) })
}); });
} else { } else {
alert("To delete this group, remove all its notes first."); alert("To delete this tag, remove all its notes first.");
} }
} }
@@ -95,15 +95,13 @@ angular.module('app.frontend')
} }
$scope.notesAddNew = function(note) { $scope.notesAddNew = function(note) {
if(!$scope.defaultUser.id) {
// generate local id for note
note.id = Neeto.crypto.generateRandomKey();
}
modelManager.addNote(note); modelManager.addNote(note);
if(!$scope.selectedGroup.all) { if(!$scope.selectedTag.all) {
modelManager.addTagToNote($scope.selectedGroup, note); console.log("add tag");
modelManager.addTagToNote($scope.selectedTag, note);
} else {
$scope.selectedTag.notes.unshift(note);
} }
} }
@@ -112,7 +110,9 @@ angular.module('app.frontend')
*/ */
$scope.saveNote = function(note, callback) { $scope.saveNote = function(note, callback) {
apiController.saveItems([note], function(){ modelManager.addDirtyItems(note);
apiController.saveDirtyItems(function(){
modelManager.addNote(note); modelManager.addNote(note);
note.hasChanges = false; note.hasChanges = false;
@@ -144,7 +144,7 @@ angular.module('app.frontend')
$scope.headerLogout = function() { $scope.headerLogout = function() {
$scope.defaultUser = apiController.localUser(); $scope.defaultUser = apiController.localUser();
$scope.groups = $scope.defaultUser.groups; $scope.tags = $scope.defaultUser.tags;
} }

View File

@@ -5,10 +5,11 @@ angular.module('app.frontend')
addNew: "&", addNew: "&",
selectionMade: "&", selectionMade: "&",
remove: "&", remove: "&",
group: "=", tag: "=",
user: "=", user: "=",
removeGroup: "&" removeTag: "&"
}, },
templateUrl: 'frontend/notes.html', templateUrl: 'frontend/notes.html',
replace: true, replace: true,
controller: 'NotesCtrl', controller: 'NotesCtrl',
@@ -16,15 +17,15 @@ angular.module('app.frontend')
bindToController: true, bindToController: true,
link:function(scope, elem, attrs, ctrl) { link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.group', function(group, oldGroup){ scope.$watch('ctrl.tag', function(tag, oldTag){
if(group) { if(tag) {
ctrl.groupDidChange(group, oldGroup); ctrl.tagDidChange(tag, oldTag);
} }
}); });
} }
} }
}) })
.controller('NotesCtrl', function (apiController, $timeout, ngDialog, $rootScope) { .controller('NotesCtrl', function (apiController, modelManager, $timeout, ngDialog, $rootScope) {
$rootScope.$on("editorFocused", function(){ $rootScope.$on("editorFocused", function(){
this.showMenu = false; this.showMenu = false;
@@ -32,15 +33,15 @@ angular.module('app.frontend')
var isFirstLoad = true; var isFirstLoad = true;
this.groupDidChange = function(group, oldGroup) { this.tagDidChange = function(tag, oldTag) {
this.showMenu = false; this.showMenu = false;
if(this.selectedNote && this.selectedNote.dummy) { if(this.selectedNote && this.selectedNote.dummy) {
_.remove(oldGroup.notes, this.selectedNote); _.remove(oldTag.notes, this.selectedNote);
} }
this.noteFilter.text = ""; this.noteFilter.text = "";
this.setNotes(group.notes, false); this.setNotes(tag.notes, false);
if(isFirstLoad) { if(isFirstLoad) {
$timeout(function(){ $timeout(function(){
@@ -53,31 +54,31 @@ angular.module('app.frontend')
isFirstLoad = false; isFirstLoad = false;
} }
}.bind(this)) }.bind(this))
} else if(group.notes.length == 0) { } else if(tag.notes.length == 0) {
this.createNewNote(); this.createNewNote();
} }
} }
this.selectedGroupDelete = function() { this.selectedTagDelete = function() {
this.showMenu = false; this.showMenu = false;
this.removeGroup()(this.group); this.removeTag()(this.tag);
} }
this.selectedGroupShare = function() { this.selectedTagShare = function() {
this.showMenu = false; this.showMenu = false;
if(!this.user.id) { if(!this.user.id) {
alert("You must be signed in to share a group."); alert("You must be signed in to share a tag.");
return; return;
} }
if(this.group.all) { if(this.tag.all) {
alert("You cannot share the 'All' group."); alert("You cannot share the 'All' tag.");
return; return;
} }
var callback = function(username) { var callback = function(username) {
apiController.shareItem(this.user, this.group, function(response){ apiController.shareItem(this.user, this.tag, function(response){
}) })
}.bind(this); }.bind(this);
@@ -97,23 +98,23 @@ angular.module('app.frontend')
} }
} }
this.selectedGroupUnshare = function() { this.selectedTagUnshare = function() {
this.showMenu = false; this.showMenu = false;
apiController.unshareItem(this.user, this.group, function(response){ apiController.unshareItem(this.user, this.tag, function(response){
}) })
} }
this.publicUrlForGroup = function() { this.publicUrlForTag = function() {
return this.group.presentation.url; return this.tag.presentation.url;
} }
this.setNotes = function(notes, createNew) { this.setNotes = function(notes, createNew) {
this.notes = notes; this.notes = notes;
console.log("set notes", notes);
notes.forEach(function(note){ notes.forEach(function(note){
note.visible = true; note.visible = true;
}) })
apiController.decryptNotesWithLocalKey(notes);
this.selectFirstNote(createNew); this.selectFirstNote(createNew);
} }
@@ -138,7 +139,9 @@ angular.module('app.frontend')
var title = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : ""); var title = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : "");
this.newNote = new Note({dummy: true}); this.newNote = new Note({dummy: true});
this.newNote.content.title = title; this.newNote.content.title = title;
modelManager.addTagToNote(this.group, this.newNote); if(this.tag && !this.tag.all) {
modelManager.addTagToNote(this.tag, this.newNote);
}
this.selectNote(this.newNote); this.selectNote(this.newNote);
this.addNew()(this.newNote); this.addNew()(this.newNote);
} }

View File

@@ -0,0 +1,114 @@
angular.module('app.frontend')
.directive("tagsSection", function(){
return {
restrict: 'E',
scope: {
addNew: "&",
selectionMade: "&",
willSelect: "&",
save: "&",
tags: "=",
allTag: "=",
user: "=",
updateNoteTag: "&"
},
templateUrl: 'frontend/tags.html',
replace: true,
controller: 'TagsCtrl',
controllerAs: 'ctrl',
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.tags', function(newTags){
if(newTags) {
ctrl.setTags(newTags);
}
});
scope.$watch('ctrl.allTag', function(allTag){
if(allTag) {
ctrl.setAllTag(allTag);
}
});
}
}
})
.controller('TagsCtrl', function () {
var initialLoad = true;
this.setAllTag = function(allTag) {
this.selectTag(this.allTag);
}
this.setTags = function(tags) {
if(initialLoad) {
initialLoad = false;
this.selectTag(this.allTag);
} else {
if(tags && tags.length > 0) {
this.selectTag(tags[0]);
}
}
}
this.selectTag = function(tag) {
this.willSelect()(tag);
this.selectedTag = tag;
this.selectionMade()(tag);
}
this.clickedAddNewTag = function() {
if(this.editingTag) {
return;
}
this.newTag = new Tag({notes : []});
if(!this.user.uuid) {
this.newTag.uuid = Neeto.crypto.generateRandomKey()
}
this.selectedTag = this.newTag;
this.editingTag = this.newTag;
this.addNew()(this.newTag);
}
var originalTagName = "";
this.onTagTitleFocus = function(tag) {
originalTagName = tag.content.name;
}
this.tagTitleDidChange = function(tag) {
this.editingTag = tag;
}
this.saveTag = function($event, tag) {
this.editingTag = null;
if(tag.content.name.length == 0) {
tag.content.name = originalTagName;
originalTagName = "";
return;
}
$event.target.blur();
if(!tag.content.name || tag.content.name.length == 0) {
return;
}
this.save()(tag, function(savedTag){
_.merge(tag, savedTag);
this.selectTag(tag);
this.newTag = null;
}.bind(this));
}
this.noteCount = function(tag) {
var validNotes = Note.filterDummyNotes(tag.notes);
return validNotes.length;
}
this.handleDrop = function(e, newTag, note) {
this.updateNoteTag()(note, newTag, this.selectedTag);
}.bind(this)
});

View File

@@ -1,6 +1,6 @@
class Item { class Item {
constructor(json_obj) { constructor(json_obj) {
var content; var content;
Object.defineProperty(this, "content", { Object.defineProperty(this, "content", {
@@ -12,14 +12,13 @@ class Item {
if(typeof value === 'string') { if(typeof value === 'string') {
try { try {
decodedValue = JSON.parse(value); var decodedValue = JSON.parse(value);
finalValue = decodedValue; finalValue = decodedValue;
} }
catch(e) { catch(e) {
finalValue = value; finalValue = value;
} }
} }
content = finalValue; content = finalValue;
}, },
enumerable: true, enumerable: true,
@@ -27,14 +26,35 @@ class Item {
_.merge(this, json_obj); _.merge(this, json_obj);
if(!this.uuid) {
this.uuid = Neeto.crypto.generateUUID();
}
this.setContentRaw = function(rawContent) { this.setContentRaw = function(rawContent) {
content = rawContent; content = rawContent;
} }
if(!this.content) {
this.content = {};
}
if(!this.content.references) {
this.content.references = [];
}
}
addReference(reference) {
this.content.references.push(reference);
this.content.references = _.uniq(this.content.references);
}
removeReference(reference) {
_.remove(this.content.references, _.find(this.content.references, {uuid: reference.uuid}));
} }
referencesMatchingContentType(contentType) { referencesMatchingContentType(contentType) {
return this.references.filter(function(reference){ return this.content.references.filter(function(reference){
return reference.content_type == content_type; return reference.content_type == contentType;
}); });
} }
@@ -42,7 +62,7 @@ class Item {
// should be overriden to manage local properties // should be overriden to manage local properties
} }
/* Returns true if note is shared individually or via group */ /* Returns true if note is shared individually or via tag */
isPublic() { isPublic() {
return this.presentation; return this.presentation;
} }
@@ -58,4 +78,5 @@ class Item {
presentationURL() { presentationURL() {
return this.presentation.url; return this.presentation.url;
} }
} }

View File

@@ -1,33 +1,37 @@
class Note extends Item { class Note extends Item {
constructor(json_obj) { constructor(json_obj) {
super(json_obj); super(json_obj);
if(!this.content) { if(!this.tags) {
this.content = {title: "", text: ""}; this.tags = [];
}
if(!this.content.title) {
this.content.title = "";
this.content.text = "";
} }
} }
filterDummyNotes(notes) { static filterDummyNotes(notes) {
var filtered = notes.filter(function(note){return note.dummy == false || note.dummy == null}); var filtered = notes.filter(function(note){return note.dummy == false || note.dummy == null});
return filtered; return filtered;
} }
get hasOnePublicGroup() { get hasOnePublicTag() {
var hasPublicGroup = false; var hasPublicTag = false;
this.groups.forEach(function(group){ this.tags.forEach(function(tag){
if(group.isPublic()) { if(tag.isPublic()) {
hasPublicGroup = true; hasPublicTag = true;
return; return;
} }
}) })
return hasPublicGroup; return hasPublicTag;
} }
isPublic() { isPublic() {
return super.isPublic() || this.hasOnePublicGroup; return super.isPublic() || this.hasOnePublicTag;
} }
get content_type() { get content_type() {

View File

@@ -2,9 +2,23 @@ class Tag extends Item {
constructor(json_obj) { constructor(json_obj) {
super(json_obj); super(json_obj);
if(!this.notes) {
this.notes = [];
}
if(!this.content.name) {
this.content.name = "";
}
} }
get content_type() { get content_type() {
return "Tag"; return "Tag";
} }
updateReferencesLocalMapping() {
super.updateReferencesLocalMapping();
this.notes = this.referencesMatchingContentType("Note");
console.log("notes after maping", this.notes);
}
} }

View File

@@ -32,7 +32,7 @@ angular.module('app.frontend')
} }
}) })
.state('group', { .state('tag', {
url: '/:root_path/:secondary_path', url: '/:root_path/:secondary_path',
parent: 'base', parent: 'base',
views: { views: {

View File

@@ -20,11 +20,15 @@ angular.module('app.frontend')
} }
this.$get = function(Restangular) { this.$get = function(Restangular, modelManager) {
return new ApiController(Restangular); return new ApiController(Restangular, modelManager);
} }
function ApiController(Restangular) { function ApiController(Restangular, modelManager) {
this.setUser = function(user) {
this.user = user;
}
/* /*
Config Config
@@ -59,7 +63,13 @@ angular.module('app.frontend')
return; return;
} }
Restangular.one("users/current").get().then(function(response){ Restangular.one("users/current").get().then(function(response){
callback(response.plain()); var plain = response.plain();
var items = plain.items;
this.decryptItemsWithLocalKey(items);
callback(plain);
}.bind(this))
.catch(function(error){
callback(null);
}) })
} }
@@ -100,7 +110,7 @@ angular.module('app.frontend')
if(response && !response.errors) { if(response && !response.errors) {
// this.showNewPasswordForm = false; // this.showNewPasswordForm = false;
// reencrypt data with new gk // reencrypt data with new gk
this.reencryptAllNotesAndSave(user, new_keys.gk, current_keys.gk, function(success){ this.reencryptAllItemsAndSave(user, new_keys.gk, current_keys.gk, function(success){
if(success) { if(success) {
this.setGk(new_keys.gk); this.setGk(new_keys.gk);
alert("Your password has been changed and your data re-encrypted."); alert("Your password has been changed and your data re-encrypted.");
@@ -141,27 +151,27 @@ angular.module('app.frontend')
} }
/* /*
Ensures that if encryption is disabled, all local notes are uncrypted, Ensures that if encryption is disabled, all local items are uncrypted,
and that if it's enabled, that all local notes are encrypted and that if it's enabled, that all local items are encrypted
*/ */
this.verifyEncryptionStatusOfAllItems = function(user, callback) { this.verifyEncryptionStatusOfAllItems = function(user, callback) {
var allNotes = user.filteredNotes(); var allItems = user.filteredItems();
var notesNeedingUpdate = []; var itemsNeedingUpdate = [];
allNotes.forEach(function(note){ allItems.forEach(function(item){
if(!note.isPublic()) { if(!item.isPublic()) {
if(note.encryptionEnabled() && !note.isEncrypted()) { if(item.encryptionEnabled() && !item.isEncrypted()) {
notesNeedingUpdate.push(note); itemsNeedingUpdate.push(item);
} }
} else { } else {
if(note.isEncrypted()) { if(item.isEncrypted()) {
notesNeedingUpdate.push(note); itemsNeedingUpdate.push(item);
} }
} }
}.bind(this)) }.bind(this))
if(notesNeedingUpdate.length > 0) { if(itemsNeedingUpdate.length > 0) {
console.log("verifying encryption, notes need updating", notesNeedingUpdate); console.log("verifying encryption, items need updating", itemsNeedingUpdate);
this.saveBatchNotes(user, notesNeedingUpdate, callback) this.saveBatchItems(user, itemsNeedingUpdate, callback)
} }
} }
@@ -176,31 +186,39 @@ angular.module('app.frontend')
this.saveItems(dirtyItems, function(response){ this.saveItems(dirtyItems, function(response){
modelManager.clearDirtyItems(); modelManager.clearDirtyItems();
callback();
}) })
} }
this.saveItems = function(items, callback) { this.saveItems = function(items, callback) {
var request = Restangular.one("users", user.uuid).one("items"); console.log("saving items", items);
var request = Restangular.one("users", this.user.uuid).one("items");
request.items = _.map(items, function(item){ request.items = _.map(items, function(item){
return this.createRequestParamsFromItem(item, user); return this.createRequestParamsForItem(item);
}.bind(this)); }.bind(this));
console.log("sending request items", request.items);
request.post().then(function(response) { request.post().then(function(response) {
var savedItems = response.items; var savedItems = response.items;
items.forEach(function(item){ console.log("response items", savedItems);
_.merge(item, _.find(savedItems, {uuid: item.uuid})); // items.forEach(function(item) {
}) // _.merge(item, _.find(savedItems, {uuid: item.uuid}));
// })
callback(response); callback(response);
}) })
} }
this.createRequestParamsForItem = function(item, user) { this.createRequestParamsForItem = function(item) {
var params = {uuid: item.uuid}; var itemCopy = _.cloneDeep(item);
var params = {uuid: item.uuid, content_type: item.content_type};
itemCopy.content.references = _.map(itemCopy.content.references, function(reference){
return {uuid: reference.uuid, content_type: reference.content_type};
})
if(!item.isPublic()) { if(!item.isPublic()) {
// encrypted // encrypted
var itemCopy = _.cloneDeep(item); this.encryptSingleItem(itemCopy, this.retrieveGk());
this.encryptSingleNote(itemCopy, this.retrieveGk());
params.content = itemCopy.content; params.content = itemCopy.content;
params.loc_eek = itemCopy.loc_eek; params.loc_eek = itemCopy.loc_eek;
} }
@@ -213,23 +231,23 @@ angular.module('app.frontend')
} }
this.deleteItem = function(user, item, callback) { this.deleteItem = function(item, callback) {
if(!user.id) { if(!this.user.id) {
this.writeUserToLocalStorage(user); this.writeUserToLocalStorage(this.user);
callback(true); callback(true);
} else { } else {
Restangular.one("users", user.uuid).one("items", item.uuid).remove() Restangular.one("users", this.user.uuid).one("items", item.uuid).remove()
.then(function(response) { .then(function(response) {
callback(true); callback(true);
}) })
} }
} }
this.shareItem = function(user, item, callback) { this.shareItem = function(item, callback) {
if(!user.id) { if(!this.user.id) {
alert("You must be signed in to share."); alert("You must be signed in to share.");
} else { } else {
Restangular.one("users", user.uuid).one("items", item.uuid).one("presentations").post() Restangular.one("users", this.user.uuid).one("items", item.uuid).one("presentations").post()
.then(function(response){ .then(function(response){
var presentation = response.plain(); var presentation = response.plain();
_.merge(item, {presentation: presentation}); _.merge(item, {presentation: presentation});
@@ -237,21 +255,21 @@ angular.module('app.frontend')
// decrypt references // decrypt references
if(item.references.length > 0) { if(item.references.length > 0) {
this.saveBatchItems(user, item.references, function(success){}) this.saveBatchItems(item.references, function(success){})
} }
}) })
} }
} }
this.unshareItem = function(user, item, callback) { this.unshareItem = function(item, callback) {
var request = Restangular.one("users", user.uuid).one("notes", item.uuid).one("presentations", item.presentation.uuid); var request = Restangular.one("users", this.user.uuid).one("items", item.uuid).one("presentations", item.presentation.uuid);
request.remove().then(function(response){ request.remove().then(function(response){
item.presentation = null; item.presentation = null;
callback(null); callback(null);
// encrypt references // encrypt references
if(item.references.length > 0) { if(item.references.length > 0) {
this.saveBatchItems(user, item.references, function(success){}) this.saveBatchItems(item.references, function(success){})
} }
}) })
} }
@@ -262,7 +280,7 @@ angular.module('app.frontend')
*/ */
this.updatePresentation = function(resource, presentation, callback) { this.updatePresentation = function(resource, presentation, callback) {
var request = Restangular.one("users", user.id) var request = Restangular.one("users", this.user.id)
.one("items", resource.id) .one("items", resource.id)
.one("presentations", resource.presentation.id); .one("presentations", resource.presentation.id);
_.merge(request, presentation); _.merge(request, presentation);
@@ -283,24 +301,24 @@ angular.module('app.frontend')
var data = JSON.parse(jsonString); var data = JSON.parse(jsonString);
var user = new User(data); var user = new User(data);
console.log("importing data", JSON.parse(jsonString)); console.log("importing data", JSON.parse(jsonString));
user.notes.forEach(function(note) { user.items.forEach(function(item) {
if(note.isPublic()) { if(item.isPublic()) {
note.setContentRaw(JSON.stringify(note.content)); item.setContentRaw(JSON.stringify(item.content));
} else { } else {
this.encryptSingleNoteWithLocalKey(note); this.encryptSingleItemWithLocalKey(item);
} }
// prevent circular links // prevent circular links
note.group = null; item.tag = null;
}.bind(this)) }.bind(this))
user.groups.forEach(function(group){ user.tags.forEach(function(tag){
// prevent circular links // prevent circular links
group.notes = null; tag.items = null;
}) })
var request = Restangular.one("import"); var request = Restangular.one("import");
request.data = {notes: user.notes, groups: user.groups}; request.data = {items: user.items, tags: user.tags};
request.post().then(function(response){ request.post().then(function(response){
callback(true, response); callback(true, response);
}) })
@@ -313,7 +331,7 @@ angular.module('app.frontend')
Export Export
*/ */
this.notesDataFile = function(user) { this.itemsDataFile = function(user) {
var textFile = null; var textFile = null;
var makeTextFile = function (text) { var makeTextFile = function (text) {
var data = new Blob([text], {type: 'text/json'}); var data = new Blob([text], {type: 'text/json'});
@@ -347,32 +365,32 @@ angular.module('app.frontend')
} }
} }
var notes = _.map(user.filteredNotes(), function(note){ var items = _.map(user.filteredItems(), function(item){
return { return {
id: note.id, id: item.id,
uuid: note.uuid, uuid: item.uuid,
content: note.content, content: item.content,
group_id: note.group_id, tag_id: item.tag_id,
created_at: note.created_at, created_at: item.created_at,
modified_at: note.modified_at, modified_at: item.modified_at,
presentation: presentationParams(note.presentation) presentation: presentationParams(item.presentation)
} }
}); });
var groups = _.map(user.groups, function(group){ var tags = _.map(user.tags, function(tag){
return { return {
id: group.id, id: tag.id,
uuid: group.uuid, uuid: tag.uuid,
name: group.name, name: tag.name,
created_at: group.created_at, created_at: tag.created_at,
modified_at: group.modified_at, modified_at: tag.modified_at,
presentation: presentationParams(group.presentation) presentation: presentationParams(tag.presentation)
} }
}); });
var data = { var data = {
notes: notes, items: items,
groups: groups tags: tags
} }
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */)); return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
@@ -386,12 +404,12 @@ angular.module('app.frontend')
*/ */
this.mergeLocalDataRemotely = function(user, callback) { this.mergeLocalDataRemotely = function(user, callback) {
var request = Restangular.one("users", user.id).one("merge"); var request = Restangular.one("users", user.id).one("merge");
var groups = user.groups; var tags = user.tags;
request.notes = user.notes; request.items = user.items;
request.notes.forEach(function(note){ request.items.forEach(function(item){
if(note.group_id) { if(item.tag_id) {
var group = groups.filter(function(group){return group.id == note.group_id})[0]; var tag = tags.filter(function(tag){return tag.id == item.tag_id})[0];
note.group_name = group.name; item.tag_name = tag.name;
} }
}) })
request.post().then(function(response){ request.post().then(function(response){
@@ -411,9 +429,9 @@ angular.module('app.frontend')
this.writeUserToLocalStorage = function(user) { this.writeUserToLocalStorage = function(user) {
var saveUser = _.cloneDeep(user); var saveUser = _.cloneDeep(user);
saveUser.notes = Note.filterDummyNotes(saveUser.notes); saveUser.items = Item.filterDummyItems(saveUser.items);
saveUser.groups.forEach(function(group){ saveUser.tags.forEach(function(tag){
group.notes = null; tag.items = null;
}.bind(this)) }.bind(this))
this.writeToLocalStorage('user', saveUser); this.writeToLocalStorage('user', saveUser);
} }
@@ -425,7 +443,7 @@ angular.module('app.frontend')
this.localUser = function() { this.localUser = function() {
var user = JSON.parse(localStorage.getItem('user')); var user = JSON.parse(localStorage.getItem('user'));
if(!user) { if(!user) {
user = {notes: [], groups: []}; user = {items: [], tags: []};
} }
user.shouldMerge = true; user.shouldMerge = true;
return user; return user;
@@ -436,7 +454,7 @@ angular.module('app.frontend')
*/ */
this.saveDraftToDisk = function(draft) { this.saveDraftToDisk = function(draft) {
localStorage.setItem("draft", JSON.stringify(draft)); // localStorage.setItem("draft", JSON.stringify(draft));
} }
this.clearDraft = function() { this.clearDraft = function() {
@@ -448,7 +466,7 @@ angular.module('app.frontend')
if(!draftString || draftString == 'undefined') { if(!draftString || draftString == 'undefined') {
return null; return null;
} }
return new Note(JSON.parse(draftString)); return new Item(JSON.parse(draftString));
} }
@@ -472,75 +490,75 @@ angular.module('app.frontend')
localStorage.removeItem("gk"); localStorage.removeItem("gk");
} }
this.encryptSingleNote = function(note, key) { this.encryptSingleItem = function(item, key) {
var ek = null; var ek = null;
if(note.loc_eek) { if(item.loc_eek) {
ek = Neeto.crypto.decryptText(note.loc_eek, key); ek = Neeto.crypto.decryptText(item.loc_eek, key);
} else { } else {
ek = Neeto.crypto.generateRandomEncryptionKey(); ek = Neeto.crypto.generateRandomEncryptionKey();
note.loc_eek = Neeto.crypto.encryptText(ek, key); item.loc_eek = Neeto.crypto.encryptText(ek, key);
} }
note.content = Neeto.crypto.encryptText(JSON.stringify(note.content), ek); item.content = Neeto.crypto.encryptText(JSON.stringify(item.content), ek);
note.local_encryption_scheme = "1.0"; item.local_encryption_scheme = "1.0";
} }
this.encryptNotes = function(notes, key) { this.encryptItems = function(items, key) {
notes.forEach(function(note){ items.forEach(function(item){
this.encryptSingleNote(note, key); this.encryptSingleItem(item, key);
}.bind(this)); }.bind(this));
} }
this.encryptSingleNoteWithLocalKey = function(note) { this.encryptSingleItemWithLocalKey = function(item) {
this.encryptSingleNote(note, this.retrieveGk()); this.encryptSingleItem(item, this.retrieveGk());
} }
this.encryptNotesWithLocalKey = function(notes) { this.encryptItemsWithLocalKey = function(items) {
this.encryptNotes(notes, this.retrieveGk()); this.encryptItems(items, this.retrieveGk());
} }
this.encryptNonPublicNotesWithLocalKey = function(notes) { this.encryptNonPublicItemsWithLocalKey = function(items) {
var nonpublic = notes.filter(function(note){ var nonpublic = items.filter(function(item){
return !note.isPublic() && !note.pending_share; return !item.isPublic() && !item.pending_share;
}) })
this.encryptNotes(nonpublic, this.retrieveGk()); this.encryptItems(nonpublic, this.retrieveGk());
} }
this.decryptSingleNoteWithLocalKey = function(note) { this.decryptSingleItemWithLocalKey = function(item) {
this.decryptSingleNote(note, this.retrieveGk()); this.decryptSingleItem(item, this.retrieveGk());
} }
this.decryptSingleNote = function(note, key) { this.decryptSingleItem = function(item, key) {
var ek = Neeto.crypto.decryptText(note.loc_eek || note.local_eek, key); var ek = Neeto.crypto.decryptText(item.loc_eek || item.local_eek, key);
var content = Neeto.crypto.decryptText(note.content, ek); var content = Neeto.crypto.decryptText(item.content, ek);
// console.log("decrypted contnet", content); // console.log("decrypted contnet", content);
note.content = content; item.content = content;
} }
this.decryptNotes = function(notes, key) { this.decryptItems = function(items, key) {
notes.forEach(function(note){ items.forEach(function(item){
// console.log("is encrypted?", note); // console.log("is encrypted?", item);
if(note.isEncrypted()) { if(item.loc_eek && typeof item.content === 'string') {
this.decryptSingleNote(note, key); this.decryptSingleItem(item, key);
} }
}.bind(this)); }.bind(this));
} }
this.decryptNotesWithLocalKey = function(notes) { this.decryptItemsWithLocalKey = function(items) {
this.decryptNotes(notes, this.retrieveGk()); this.decryptItems(items, this.retrieveGk());
} }
this.reencryptAllNotesAndSave = function(user, newKey, oldKey, callback) { this.reencryptAllItemsAndSave = function(user, newKey, oldKey, callback) {
var notes = user.filteredNotes(); var items = user.filteredItems();
notes.forEach(function(note){ items.forEach(function(item){
if(note.isEncrypted()) { if(item.loc_eek && typeof item.content === 'string') {
// first decrypt eek with old key // first decrypt eek with old key
var ek = Neeto.crypto.decryptText(note.loc_eek, oldKey); var ek = Neeto.crypto.decryptText(item.loc_eek, oldKey);
// now encrypt ek with new key // now encrypt ek with new key
note.loc_eek = Neeto.crypto.encryptText(ek, newKey); item.loc_eek = Neeto.crypto.encryptText(ek, newKey);
} }
}); });
this.saveBatchNotes(user, notes, function(success) { this.saveBatchItems(user, items, function(success) {
callback(success); callback(success);
}.bind(this)); }.bind(this));
} }

View File

@@ -41,7 +41,7 @@ angular
scope: { scope: {
drop: '&', drop: '&',
bin: '=', bin: '=',
group: "=" tag: "="
}, },
link: function(scope, element) { link: function(scope, element) {
// again we need the native object // again we need the native object
@@ -96,7 +96,7 @@ angular
scope.$apply(function(scope) { scope.$apply(function(scope) {
var fn = scope.drop(); var fn = scope.drop();
if ('undefined' !== typeof fn) { if ('undefined' !== typeof fn) {
fn(e, scope.group, note); fn(e, scope.tag, note);
} }
}); });

View File

@@ -6,6 +6,19 @@ Neeto.crypto = {
return CryptoJS.lib.WordArray.random(256/8).toString(); return CryptoJS.lib.WordArray.random(256/8).toString();
}, },
generateUUID: function() {
var d = new Date().getTime();
if(window.performance && typeof window.performance.now === "function"){
d += performance.now(); //use high-precision timer if available
}
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
},
decryptText: function(encrypted_content, key) { decryptText: function(encrypted_content, key) {
return CryptoJS.AES.decrypt(encrypted_content, key).toString(CryptoJS.enc.Utf8); return CryptoJS.AES.decrypt(encrypted_content, key).toString(CryptoJS.enc.Utf8);
}, },

View File

@@ -1,8 +1,12 @@
class ItemManager { class ItemManager {
set items(items) { set items(items) {
this.items = items; this._items = items;
resolveReferences(); this.resolveReferences();
}
get items() {
return this._items;
} }
referencesForItemId(itemId) { referencesForItemId(itemId) {
@@ -12,14 +16,14 @@ class ItemManager {
resolveReferences() { resolveReferences() {
this.items.forEach(function(item){ this.items.forEach(function(item){
// build out references // build out references
_.map(item.references, function(reference){ item.content.references = _.map(item.content.references, function(reference){
return referencesForItemId(reference.uuid); return this.referencesForItemId(reference.uuid);
}) }.bind(this))
}); }.bind(this));
} }
itemsForContentType(contentType) { itemsForContentType(contentType) {
this.items.filter(function(item){ return this.items.filter(function(item){
return item.content_type == contentType; return item.content_type == contentType;
}); });
} }
@@ -27,22 +31,22 @@ class ItemManager {
// returns dirty item references that need saving // returns dirty item references that need saving
deleteItem(item) { deleteItem(item) {
_.remove(this.items, item); _.remove(this.items, item);
item.references.forEach(function(referencedItem){ item.content.references.forEach(function(referencedItem){
removeReferencesBetweenItems(referencedItem, item); this.removeReferencesBetweenItems(referencedItem, item);
}) }.bind(this))
return item.references; return item.content.references;
} }
removeReferencesBetweenItems(itemOne, itemTwo) { removeReferencesBetweenItems(itemOne, itemTwo) {
_.remove(itemOne.references, _.find(itemOne.references, {uuid: itemTwo.uuid})); itemOne.removeReference(itemTwo);
_.remove(itemTwo.references, _.find(itemTwo.references, {uuid: itemOne.uuid})); itemTwo.removeReference(itemOne);
return [itemOne, itemTwo]; return [itemOne, itemTwo];
} }
createReferencesBetweenItems(itemOne, itemTwo) { createReferencesBetweenItems(itemOne, itemTwo) {
itemOne.references.push(itemTwo); itemOne.addReference(itemTwo);
itemTwo.references.push(itemOne); itemTwo.addReference(itemOne);
return [itemOne, itemTwo]; return [itemOne, itemTwo];
} }
} }

View File

@@ -1,29 +1,37 @@
class ModelManager extends ItemManager { class ModelManager extends ItemManager {
constructor() {
super();
this.notes = [];
this.groups = [];
this.dirtyItems = [];
}
set items(items) { set items(items) {
super.items = items; super.items = items;
this.notes = _.map(this.itemsForContentType("Note"), function(json_obj) {
this.notes = _.map(this.items.itemsForContentType("Note"), function(json_obj) {
return new Note(json_obj); return new Note(json_obj);
}) })
this.groups = _.map(this.items.itemsForContentType("Group"), function(json_obj) { this.tags = _.map(this.itemsForContentType("Tag"), function(json_obj) {
var group = Group(json_obj); var tag = new Tag(json_obj);
group.updateReferencesLocalMapping(); console.log("tag references upon import", tag.content.references);
return group; tag.updateReferencesLocalMapping();
return tag;
}) })
} }
addDirtyItems(items) { get items() {
if(this.dirtyItems) { return super.items;
this.dirtyItems = [];
}
this.dirtyItems.concat(items);
} }
get dirtyItems() { addDirtyItems(items) {
return this.dirtyItems || []; if(!(items instanceof Array)) {
items = [items];
}
this.dirtyItems = this.dirtyItems.concat(items);
this.dirtyItems = _.uniq(this.dirtyItems);
} }
get filteredNotes() { get filteredNotes() {
@@ -45,6 +53,7 @@ class ModelManager extends ItemManager {
} }
addTagToNote(tag, note) { addTagToNote(tag, note) {
console.log("adding tag to note", tag, note);
var dirty = this.createReferencesBetweenItems(tag, note); var dirty = this.createReferencesBetweenItems(tag, note);
this.refreshRelationshipsForTag(tag); this.refreshRelationshipsForTag(tag);
this.refreshRelationshipsForNote(note); this.refreshRelationshipsForNote(note);
@@ -59,7 +68,7 @@ class ModelManager extends ItemManager {
} }
refreshRelationshipsForNote(note) { refreshRelationshipsForNote(note) {
note.groups = note.referencesMatchingContentType("Group"); note.tags = note.referencesMatchingContentType("Tag");
} }
removeTagFromNote(tag, note) { removeTagFromNote(tag, note) {

View File

@@ -337,7 +337,7 @@ input, button, select, textarea {
position: relative; position: relative;
} }
.form-group { .form-tag {
margin-bottom: 15px; margin-bottom: 15px;
} }

View File

@@ -6,7 +6,7 @@
height: 136px !important; height: 136px !important;
} }
.group-menu-bar { .tag-menu-bar {
position: relative; position: relative;
margin: 0 -20px; margin: 0 -20px;
width: auto; width: auto;

View File

@@ -1,11 +1,11 @@
.groups { .tags {
width: 22%; width: 22%;
.groups-title-bar { .tags-title-bar {
color: #0707ff; color: #0707ff;
} }
.group { .tag {
height: 50px; height: 50px;
border-bottom: 1px solid $bg-color; border-bottom: 1px solid $bg-color;
padding: 12px; padding: 12px;
@@ -47,7 +47,7 @@
} }
} }
/* When a note is dragged over group */ /* When a note is dragged over tag */
&.over { &.over {
background-color: rgba(#5151ff, 0.8); background-color: rgba(#5151ff, 0.8);
color: white; color: white;

View File

@@ -4,7 +4,7 @@ $dark-gray: #2e2e2e;
@import "app/common"; @import "app/common";
@import "app/main"; @import "app/main";
@import "app/header"; @import "app/header";
@import "app/groups"; @import "app/tags";
@import "app/notes"; @import "app/notes";
@import "app/editor"; @import "app/editor";

View File

@@ -4,7 +4,7 @@
.panel-body .panel-body
%p We'll send reset instructions to your email. %p We'll send reset instructions to your email.
%form{'ng-submit' => 'requestPasswordReset(forgotData)', 'ng-init' => 'forgotData = {}'} %form{'ng-submit' => 'requestPasswordReset(forgotData)', 'ng-init' => 'forgotData = {}'}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'forgotData.email'}/ %input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'forgotData.email'}/
%span.glyphicon.glyphicon-envelope.form-control-feedback %span.glyphicon.glyphicon-envelope.form-control-feedback
.row .row

View File

@@ -3,10 +3,10 @@
%h3.panel-title Sign in to start your session %h3.panel-title Sign in to start your session
.panel-body .panel-body
%form{'ng-submit' => 'submitLogin(loginData)', 'ng-init' => 'loginData = {}'} %form{'ng-submit' => 'submitLogin(loginData)', 'ng-init' => 'loginData = {}'}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Username', :required => true, :type => 'email', 'ng-model' => 'loginData.email'}/ %input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Username', :required => true, :type => 'email', 'ng-model' => 'loginData.email'}/
%span.glyphicon.glyphicon-user.form-control-feedback %span.glyphicon.glyphicon-user.form-control-feedback
.form-group.has-feedback .form-tag.has-feedback
%input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'loginData.password'}/ %input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'loginData.password'}/
%span.glyphicon.glyphicon-lock.form-control-feedback %span.glyphicon.glyphicon-lock.form-control-feedback
.row .row

View File

@@ -4,10 +4,10 @@
.panel-body .panel-body
%p Type your new password. %p Type your new password.
%form{'ng-submit' => 'resetPasswordSubmit()'} %form{'ng-submit' => 'resetPasswordSubmit()'}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'resetData.password'}/ %input.form-control{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'resetData.password'}/
%span.glyphicon.glyphicon-lock.form-control-feedback %span.glyphicon.glyphicon-lock.form-control-feedback
.form-group.has-feedback .form-tag.has-feedback
%input.form-control{:placeholder => 'Password confirmation', :name => 'password_confirmation', :required => true, :type => 'password', 'ng-model' => 'resetData.password_confirmation'}/ %input.form-control{:placeholder => 'Password confirmation', :name => 'password_confirmation', :required => true, :type => 'password', 'ng-model' => 'resetData.password_confirmation'}/
%span.glyphicon.glyphicon-lock.form-control-feedback %span.glyphicon.glyphicon-lock.form-control-feedback
.row .row

View File

@@ -1,16 +0,0 @@
.section.groups
.content
.section-title-bar.groups-title-bar
.title Groups
.add-button{"ng-click" => "ctrl.clickedAddNewGroup()"} +
.group{"ng-if" => "ctrl.allGroup", "ng-click" => "ctrl.selectGroup(ctrl.allGroup)", "ng-class" => "{'selected' : ctrl.selectedGroup == ctrl.allGroup}",
"droppable" => true, "drop" => "ctrl.handleDrop", "group" => "ctrl.allGroup"}
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.allGroup.name"}
.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"}
%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)"}
.count {{ctrl.noteCount(group)}}

View File

@@ -20,9 +20,9 @@
.title Sign in or Register .title Sign in or Register
.desc .desc
%form.account-form{'name' => "loginForm"} %form.account-form{'name' => "loginForm"}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control.login-input{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'ctrl.loginData.email'} %input.form-control.login-input{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'ctrl.loginData.email'}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control.login-input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.loginData.user_password'} %input.form-control.login-input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.loginData.user_password'}
.checkbox{"ng-if" => "ctrl.hasLocalData()"} .checkbox{"ng-if" => "ctrl.hasLocalData()"}
%label %label
@@ -38,7 +38,7 @@
.panel-status-text{"ng-if" => "ctrl.loginData.status", "style" => "font-size: 14px;"} {{ctrl.loginData.status}} .panel-status-text{"ng-if" => "ctrl.loginData.status", "style" => "font-size: 14px;"} {{ctrl.loginData.status}}
%form{"style" => "margin-top: 20px;", "ng-if" => "ctrl.showResetForm", "ng-init" => "ctrl.resetData = {}", 'ng-submit' => 'ctrl.forgotPasswordSubmit()', 'name' => "resetForm"} %form{"style" => "margin-top: 20px;", "ng-if" => "ctrl.showResetForm", "ng-init" => "ctrl.resetData = {}", 'ng-submit' => 'ctrl.forgotPasswordSubmit()', 'name' => "resetForm"}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control.login-input{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'ctrl.resetData.email'} %input.form-control.login-input{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'ctrl.resetData.email'}
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"} %button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
%span Send Reset Email %span Send Reset Email
@@ -73,7 +73,7 @@
.desc Use a custom Neeto server to store and retrieve your account data. .desc Use a custom Neeto server to store and retrieve your account data.
.action-container .action-container
%form.account-form{'ng-submit' => 'ctrl.changeServer()', 'name' => "serverChangeForm"} %form.account-form{'ng-submit' => 'ctrl.changeServer()', 'name' => "serverChangeForm"}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'ctrl.serverData.url'} %input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'ctrl.serverData.url'}
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"} %button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
%span.ladda-label Change Server %span.ladda-label Change Server
@@ -82,11 +82,11 @@
.link-item .link-item
%a{"ng-click" => "ctrl.changePasswordPressed()"} Change Password %a{"ng-click" => "ctrl.changePasswordPressed()"} Change Password
%form.account-form{"ng-if" => "ctrl.showNewPasswordForm", 'ng-submit' => 'ctrl.submitPasswordChange()', 'name' => "passwordChangeForm"} %form.account-form{"ng-if" => "ctrl.showNewPasswordForm", 'ng-submit' => 'ctrl.submitPasswordChange()', 'name' => "passwordChangeForm"}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control.login-input{:autofocus => 'autofocus', :name => 'current', :placeholder => 'Current password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.current_password'} %input.form-control.login-input{:autofocus => 'autofocus', :name => 'current', :placeholder => 'Current password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.current_password'}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control.login-input{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password', "autocomplete" => "new-password"} %input.form-control.login-input{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password', "autocomplete" => "new-password"}
.form-group.has-feedback .form-tag.has-feedback
%input.form-control.login-input{:placeholder => 'Confirm password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password_confirmation', "autocomplete" => "new-password"} %input.form-control.login-input{:placeholder => 'Confirm password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password_confirmation', "autocomplete" => "new-password"}
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"} %button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
%span.ladda-label Change Password %span.ladda-label Change Password

View File

@@ -2,11 +2,11 @@
%header{"user" => "defaultUser", "logout" => "headerLogout"} %header{"user" => "defaultUser", "logout" => "headerLogout"}
.app-container .app-container
.app .app
%groups-section{"save" => "groupsSave", "add-new" => "groupsAddNew", "will-select" => "groupsWillMakeSelection", "selection-made" => "groupsSelectionMade", "all-group" => "allGroup", %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "will-select" => "tagsWillMakeSelection", "selection-made" => "tagsSelectionMade", "all-tag" => "allTag",
"groups" => "groups", "user" => "defaultUser", "update-note-group" => "groupsUpdateNoteGroup"} "tags" => "tags", "user" => "defaultUser", "update-note-tag" => "tagsUpdateNoteTag"}
%notes-section{"remove-group" => "notesRemoveGroup", "user" => "defaultUser", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", %notes-section{"remove-tag" => "notesRemoveTag", "user" => "defaultUser", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade",
"group" => "selectedGroup", "user-id" => "defaultUser.id", "remove" => "deleteNote"} "tag" => "selectedTag", "user-id" => "defaultUser.id", "remove" => "deleteNote"}
%editor-section{"ng-if" => "selectedNote", "note" => "selectedNote", "remove" => "deleteNote", %editor-section{"ng-if" => "selectedNote", "note" => "selectedNote", "remove" => "deleteNote",
"user" => "defaultUser", "save" => "saveNote"} "user" => "defaultUser", "save" => "saveNote"}

View File

@@ -1,2 +1,2 @@
%strong Choose a public username for all your shared note groups. %strong Choose a public username for all your shared note tags.
%input{"style" => "margin-top: 10px; padding-left: 8px;", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && saveUsername($event)", "ng-model" => "formData.username"} %input{"style" => "margin-top: 10px; padding-left: 8px;", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && saveUsername($event)", "ng-model" => "formData.username"}

View File

@@ -1,12 +1,12 @@
.section.notes .section.notes
.content .content
.section-title-bar.notes-title-bar .section-title-bar.notes-title-bar
.title {{ctrl.group.name}} notes .title {{ctrl.tag.name}} notes
.add-button{"ng-click" => "ctrl.createNewNote()"} + .add-button{"ng-click" => "ctrl.createNewNote()"} +
%br %br
.filter-section .filter-section
%input.filter-bar{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Filter", "ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true"} %input.filter-bar{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Filter", "ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true"}
.editor-menu.group-menu-bar .editor-menu.tag-menu-bar
%ul.nav.nav-pills %ul.nav.nav-pills
%li.dropdown %li.dropdown
%a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"} %a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"}
@@ -14,17 +14,17 @@
%span.caret %span.caret
%span.sr-only %span.sr-only
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"} %ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
%li{"ng-if" => "!ctrl.group.presentation"} %li{"ng-if" => "!ctrl.tag.presentation"}
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupShare($event)"} Share Group %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagShare($event)"} Share Tag
%li{"ng-if" => "ctrl.group.presentation"} %li{"ng-if" => "ctrl.tag.presentation"}
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupUnshare()"} Unshare Group %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagUnshare()"} Unshare Tag
%li{"ng-if" => "!ctrl.group.all"} %li{"ng-if" => "!ctrl.tag.all"}
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupDelete()"} Delete Group %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
.menu-right-container .menu-right-container
.public-link{"ng-if" => "ctrl.group.presentation"} .public-link{"ng-if" => "ctrl.tag.presentation"}
%a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForGroup(ctrl.group)}}", "target" => "_blank"} %a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForTag(ctrl.tag)}}", "target" => "_blank"}
%span.icon-rss.icon %span.icon-rss.icon
{{ctrl.publicUrlForGroup()}} {{ctrl.publicUrlForTag()}}
.edit-url{"ng-if" => "ctrl.editingUrl"} .edit-url{"ng-if" => "ctrl.editingUrl"}
{{ctrl.url.base}} {{ctrl.url.base}}
%input.input{"ng-model" => "ctrl.url.token", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveUrl($event)", %input.input{"ng-model" => "ctrl.url.token", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveUrl($event)",

View File

@@ -0,0 +1,18 @@
.section.tags
.content
.section-title-bar.tags-title-bar
.title Tags
.add-button{"ng-click" => "ctrl.clickedAddNewTag()"} +
{{ctrl.test}}
.tag{"ng-if" => "ctrl.allTag", "ng-click" => "ctrl.selectTag(ctrl.allTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.allTag}",
"droppable" => true, "drop" => "ctrl.handleDrop", "tag" => "ctrl.allTag"}
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.allTag.content.name"}
.count {{ctrl.noteCount(ctrl.allTag)}}
.tag{"ng-repeat" => "tag in ctrl.tags", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}",
"droppable" => true, "drop" => "ctrl.handleDrop", "tag" => "tag"}
.icon.icon-rss{"ng-if" => "tag.presentation"}
%input.title{"ng-disabled" => "tag != ctrl.selectedTag", "ng-model" => "tag.content.name",
"ng-keyup" => "$event.keyCode == 13 && ctrl.saveTag($event, tag)", "mb-autofocus" => "true", "should-focus" => "ctrl.newTag",
"ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-focus" => "ctrl.onTagTitleFocus(tag)"}
.count {{ctrl.noteCount(tag)}}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long