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
.config(function (RestangularProvider, apiControllerProvider) {
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
var url = apiControllerProvider.defaultServerURL();
RestangularProvider.setBaseUrl(url);
console.log(url);
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
var token = localStorage.getItem("jwt");
@@ -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 = [
{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: "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: "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: "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.showSampler = !this.user.id && modelManager.filteredNotes.length == 0;
this.demoNoteNames = _.map(this.demoNotes, function(note){
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.showNewPasswordForm = !this.showNewPasswordForm;
@@ -58,12 +58,12 @@ angular.module('app.frontend')
}
this.hasLocalData = function() {
return this.user.filteredNotes().length > 0;
return modelManager.filteredNotes.length > 0;
}
this.mergeLocalChanged = function() {
if(!this.user.shouldMerge) {
if(!confirm("Unchecking this option means any locally stored groups and notes you have now will be deleted. Are you sure you want to continue?")) {
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;
}
}
@@ -109,7 +109,7 @@ angular.module('app.frontend')
}
this.encryptionStatusForNotes = function() {
var allNotes = this.user.filteredNotes();
var allNotes = modelManager.filteredNotes;
var countEncrypted = 0;
allNotes.forEach(function(note){
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.";
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});
$scope.groups = modelManager.groups;
apiController.verifyEncryptionStatusOfAllItems($scope.defaultUser, function(success){
});
// apiController.verifyEncryptionStatusOfAllItems($scope.defaultUser, function(success){});
}
apiController.getCurrentUser(function(response){
if(response && !response.errors) {
console.log("Get user response", 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";
onUserSet();
} else {
@@ -27,44 +27,44 @@ angular.module('app.frontend')
});
/*
Groups Ctrl Callbacks
Tags Ctrl Callbacks
*/
$scope.updateAllGroup = function() {
$scope.allGroup.notes = modelManager.filteredNotes;
$scope.updateAllTag = function() {
$scope.allTag.notes = modelManager.filteredNotes;
}
$scope.groupsWillMakeSelection = function(group) {
if(group.all) {
$scope.updateAllGroup();
$scope.tagsWillMakeSelection = function(tag) {
if(tag.all) {
$scope.updateAllTag();
}
}
$scope.groupsSelectionMade = function(group) {
if(!group.notes) {
group.notes = [];
$scope.tagsSelectionMade = function(tag) {
if(!tag.notes) {
tag.notes = [];
}
$scope.selectedGroup = group;
$scope.selectedTag = tag;
}
$scope.groupsAddNew = function(group) {
modelManager.addTag(group);
$scope.tagsAddNew = function(tag) {
modelManager.addTag(tag);
}
$scope.groupsSave = function(group, callback) {
apiController.saveItems([group], callback);
$scope.tagsSave = function(tag, 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
*/
$scope.groupsUpdateNoteGroup = function(noteCopy, newGroup, oldGroup) {
$scope.tagsUpdateNoteTag = function(noteCopy, newTag, oldTag) {
var originalNote = _.find($scope.defaultUser.notes, {uuid: noteCopy.uuid});
modelManager.removeTagFromNote(oldGroup, originalNote);
if(!newGroup.all) {
modelManager.addTagToNote(newGroup, originalNote);
modelManager.removeTagFromNote(oldTag, originalNote);
if(!newTag.all) {
modelManager.addTagToNote(newTag, originalNote);
}
apiController.saveDirtyItems(function(){});
@@ -74,19 +74,19 @@ angular.module('app.frontend')
Notes Ctrl Callbacks
*/
$scope.notesRemoveGroup = function(group) {
var validNotes = Note.filterDummyNotes(group.notes);
$scope.notesRemoveTag = function(tag) {
var validNotes = Note.filterDummyNotes(tag.notes);
if(validNotes == 0) {
// if no more notes, delete group
apiController.deleteItem($scope.defaultUser, group, function(){
// force scope groups to update on sub directives
$scope.groups = [];
// if no more notes, delete tag
apiController.deleteItem($scope.defaultUser, tag, function(){
// force scope tags to update on sub directives
$scope.tags = [];
$timeout(function(){
$scope.groups = modelManager.groups;
$scope.tags = modelManager.tags;
})
});
} 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) {
if(!$scope.defaultUser.id) {
// generate local id for note
note.id = Neeto.crypto.generateRandomKey();
}
modelManager.addNote(note);
if(!$scope.selectedGroup.all) {
modelManager.addTagToNote($scope.selectedGroup, note);
if(!$scope.selectedTag.all) {
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) {
apiController.saveItems([note], function(){
modelManager.addDirtyItems(note);
apiController.saveDirtyItems(function(){
modelManager.addNote(note);
note.hasChanges = false;
@@ -144,7 +144,7 @@ angular.module('app.frontend')
$scope.headerLogout = function() {
$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: "&",
selectionMade: "&",
remove: "&",
group: "=",
tag: "=",
user: "=",
removeGroup: "&"
removeTag: "&"
},
templateUrl: 'frontend/notes.html',
replace: true,
controller: 'NotesCtrl',
@@ -16,15 +17,15 @@ angular.module('app.frontend')
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.group', function(group, oldGroup){
if(group) {
ctrl.groupDidChange(group, oldGroup);
scope.$watch('ctrl.tag', function(tag, oldTag){
if(tag) {
ctrl.tagDidChange(tag, oldTag);
}
});
}
}
})
.controller('NotesCtrl', function (apiController, $timeout, ngDialog, $rootScope) {
.controller('NotesCtrl', function (apiController, modelManager, $timeout, ngDialog, $rootScope) {
$rootScope.$on("editorFocused", function(){
this.showMenu = false;
@@ -32,15 +33,15 @@ angular.module('app.frontend')
var isFirstLoad = true;
this.groupDidChange = function(group, oldGroup) {
this.tagDidChange = function(tag, oldTag) {
this.showMenu = false;
if(this.selectedNote && this.selectedNote.dummy) {
_.remove(oldGroup.notes, this.selectedNote);
_.remove(oldTag.notes, this.selectedNote);
}
this.noteFilter.text = "";
this.setNotes(group.notes, false);
this.setNotes(tag.notes, false);
if(isFirstLoad) {
$timeout(function(){
@@ -53,31 +54,31 @@ angular.module('app.frontend')
isFirstLoad = false;
}
}.bind(this))
} else if(group.notes.length == 0) {
} else if(tag.notes.length == 0) {
this.createNewNote();
}
}
this.selectedGroupDelete = function() {
this.selectedTagDelete = function() {
this.showMenu = false;
this.removeGroup()(this.group);
this.removeTag()(this.tag);
}
this.selectedGroupShare = function() {
this.selectedTagShare = function() {
this.showMenu = false;
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;
}
if(this.group.all) {
alert("You cannot share the 'All' group.");
if(this.tag.all) {
alert("You cannot share the 'All' tag.");
return;
}
var callback = function(username) {
apiController.shareItem(this.user, this.group, function(response){
apiController.shareItem(this.user, this.tag, function(response){
})
}.bind(this);
@@ -97,23 +98,23 @@ angular.module('app.frontend')
}
}
this.selectedGroupUnshare = function() {
this.selectedTagUnshare = function() {
this.showMenu = false;
apiController.unshareItem(this.user, this.group, function(response){
apiController.unshareItem(this.user, this.tag, function(response){
})
}
this.publicUrlForGroup = function() {
return this.group.presentation.url;
this.publicUrlForTag = function() {
return this.tag.presentation.url;
}
this.setNotes = function(notes, createNew) {
this.notes = notes;
console.log("set notes", notes);
notes.forEach(function(note){
note.visible = true;
})
apiController.decryptNotesWithLocalKey(notes);
this.selectFirstNote(createNew);
}
@@ -138,7 +139,9 @@ angular.module('app.frontend')
var title = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : "");
this.newNote = new Note({dummy: true});
this.newNote.content.title = title;
modelManager.addTagToNote(this.group, this.newNote);
if(this.tag && !this.tag.all) {
modelManager.addTagToNote(this.tag, this.newNote);
}
this.selectNote(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 {
constructor(json_obj) {
var content;
Object.defineProperty(this, "content", {
@@ -12,14 +12,13 @@ class Item {
if(typeof value === 'string') {
try {
decodedValue = JSON.parse(value);
var decodedValue = JSON.parse(value);
finalValue = decodedValue;
}
catch(e) {
finalValue = value;
}
}
content = finalValue;
},
enumerable: true,
@@ -27,14 +26,35 @@ class Item {
_.merge(this, json_obj);
if(!this.uuid) {
this.uuid = Neeto.crypto.generateUUID();
}
this.setContentRaw = function(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) {
return this.references.filter(function(reference){
return reference.content_type == content_type;
return this.content.references.filter(function(reference){
return reference.content_type == contentType;
});
}
@@ -42,7 +62,7 @@ class Item {
// 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() {
return this.presentation;
}
@@ -58,4 +78,5 @@ class Item {
presentationURL() {
return this.presentation.url;
}
}

View File

@@ -1,33 +1,37 @@
class Note extends Item {
constructor(json_obj) {
super(json_obj);
if(!this.content) {
this.content = {title: "", text: ""};
if(!this.tags) {
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});
return filtered;
}
get hasOnePublicGroup() {
var hasPublicGroup = false;
this.groups.forEach(function(group){
if(group.isPublic()) {
hasPublicGroup = true;
get hasOnePublicTag() {
var hasPublicTag = false;
this.tags.forEach(function(tag){
if(tag.isPublic()) {
hasPublicTag = true;
return;
}
})
return hasPublicGroup;
return hasPublicTag;
}
isPublic() {
return super.isPublic() || this.hasOnePublicGroup;
return super.isPublic() || this.hasOnePublicTag;
}
get content_type() {

View File

@@ -2,9 +2,23 @@ class Tag extends Item {
constructor(json_obj) {
super(json_obj);
if(!this.notes) {
this.notes = [];
}
if(!this.content.name) {
this.content.name = "";
}
}
get content_type() {
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',
parent: 'base',
views: {

View File

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

View File

@@ -41,7 +41,7 @@ angular
scope: {
drop: '&',
bin: '=',
group: "="
tag: "="
},
link: function(scope, element) {
// again we need the native object
@@ -96,7 +96,7 @@ angular
scope.$apply(function(scope) {
var fn = scope.drop();
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();
},
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) {
return CryptoJS.AES.decrypt(encrypted_content, key).toString(CryptoJS.enc.Utf8);
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
.panel-body
%p We'll send reset instructions to your email.
%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'}/
%span.glyphicon.glyphicon-envelope.form-control-feedback
.row

View File

@@ -3,10 +3,10 @@
%h3.panel-title Sign in to start your session
.panel-body
%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'}/
%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'}/
%span.glyphicon.glyphicon-lock.form-control-feedback
.row

View File

@@ -4,10 +4,10 @@
.panel-body
%p Type your new password.
%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'}/
%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'}/
%span.glyphicon.glyphicon-lock.form-control-feedback
.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
.desc
%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'}
.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'}
.checkbox{"ng-if" => "ctrl.hasLocalData()"}
%label
@@ -38,7 +38,7 @@
.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-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'}
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
%span Send Reset Email
@@ -73,7 +73,7 @@
.desc Use a custom Neeto server to store and retrieve your account data.
.action-container
%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'}
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
%span.ladda-label Change Server
@@ -82,11 +82,11 @@
.link-item
%a{"ng-click" => "ctrl.changePasswordPressed()"} Change Password
%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'}
.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"}
.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"}
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
%span.ladda-label Change Password

View File

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

View File

@@ -1,12 +1,12 @@
.section.notes
.content
.section-title-bar.notes-title-bar
.title {{ctrl.group.name}} notes
.title {{ctrl.tag.name}} notes
.add-button{"ng-click" => "ctrl.createNewNote()"} +
%br
.filter-section
%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
%li.dropdown
%a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"}
@@ -14,17 +14,17 @@
%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"}
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupShare($event)"} Share Group
%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
%li{"ng-if" => "!ctrl.tag.presentation"}
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagShare($event)"} Share Tag
%li{"ng-if" => "ctrl.tag.presentation"}
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagUnshare()"} Unshare Tag
%li{"ng-if" => "!ctrl.tag.all"}
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
.menu-right-container
.public-link{"ng-if" => "ctrl.group.presentation"}
%a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForGroup(ctrl.group)}}", "target" => "_blank"}
.public-link{"ng-if" => "ctrl.tag.presentation"}
%a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForTag(ctrl.tag)}}", "target" => "_blank"}
%span.icon-rss.icon
{{ctrl.publicUrlForGroup()}}
{{ctrl.publicUrlForTag()}}
.edit-url{"ng-if" => "ctrl.editingUrl"}
{{ctrl.url.base}}
%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