diff --git a/appinfo/database.xml b/appinfo/database.xml index 5389d05..99b4165 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -108,4 +108,37 @@ - \ No newline at end of file + + *dbprefix*quicknotes_shares + + + id + integer + true + true + true + true + 8 + + + note_id + integer + true + true + 8 + + + shared_user + text + 200 + + + + shared_group + text + 200 + + + +
+ diff --git a/appinfo/info.xml b/appinfo/info.xml index 4fe1fab..f0b9b76 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ Quick notes with text, check lists and pictures AGPL Matias De lellis - 0.1.0 + 0.1.1 QuickNotes tool https://github.com/matiasdelellis @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/appinfo/routes.php b/appinfo/routes.php index 6062580..1ab9af6 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -7,6 +7,11 @@ return [ 'routes' => [ ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], ['name' => 'note_api#preflighted_cors', 'url' => '/api/0.1/{path}', - 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']] + 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']], + ['name' => 'note#get_user_groups_and_users_with_share', 'url' => '/api/0.1/getusergroups', 'verb' => 'POST'], + ['name' => 'note#add_group_share', 'url' => '/api/0.1/groups/addshare', 'verb' => 'POST'], + ['name' => 'note#remove_group_share', 'url' => '/api/0.1/groups/removeshare', 'verb' => 'POST'], + ['name' => 'note#add_user_share', 'url' => '/api/0.1/users/addshare', 'verb' => 'POST'], + ['name' => 'note#remove_user_share', 'url' => '/api/0.1/users/removeshare', 'verb' => 'POST'] ] -]; \ No newline at end of file +]; diff --git a/controller/notecontroller.php b/controller/notecontroller.php index 5c5b847..6658a76 100644 --- a/controller/notecontroller.php +++ b/controller/notecontroller.php @@ -14,23 +14,28 @@ namespace OCA\QuickNotes\Controller; use OCP\IRequest; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Controller; use OCA\QuickNotes\Db\Note; use OCA\QuickNotes\Db\Color; +use OCA\QuickNotes\Db\NoteShare; use OCA\QuickNotes\Db\NoteMapper; use OCA\QuickNotes\Db\ColorMapper; +use OCA\QuickNotes\Db\NoteShareMapper; class NoteController extends Controller { private $notemapper; private $colormapper; + private $notesharemapper; private $userId; - public function __construct($AppName, IRequest $request, NoteMapper $notemapper, ColorMapper $colormapper, $UserId) { + public function __construct($AppName, IRequest $request, NoteMapper $notemapper, NoteShareMapper $notesharemapper, ColorMapper $colormapper, $UserId) { parent::__construct($AppName, $request); $this->notemapper = $notemapper; $this->colormapper = $colormapper; + $this->notesharemapper = $notesharemapper; $this->userId = $UserId; } @@ -39,6 +44,33 @@ class NoteController extends Controller { */ public function index() { $notes = $this->notemapper->findAll($this->userId); + foreach($notes as $note) { + $note->setIsShared(false); + $sharedWith = $this->notesharemapper->getSharesForNote($note->getId()); + if(count($sharedWith) > 0) { + $shareList = array(); + foreach($sharedWith as $share) { + $shareList[] = $share->getSharedUser(); + } + $note->setSharedWith(implode(", ", $shareList)); + } else { + $note->setSharedWith(null); + } + } + $shareEntries = $this->notesharemapper->findForUser($this->userId); + $shares = array(); + foreach($shareEntries as $entry) { + try { + //find is only to check if current user is owner + $this->notemapper->find($entry->getNoteId(), $this->userId); + //user is owner, nothing to do + } catch(\OCP\AppFramework\Db\DoesNotExistException $e) { + $share = $this->notemapper->findById($entry->getNoteId()); + $share->setIsShared(true); + $shares[] = $share; + } + } + $notes = array_merge($notes, $shares); // Insert true color to response foreach ($notes as $note) { $note->setColor($this->colormapper->find($note->getColorId())->getColor()); @@ -151,6 +183,8 @@ class NoteController extends Controller { } $oldcolorid = $note->getColorId(); + $this->notesharemapper->deleteByNoteId($note->getId()); + // Delete note. $this->notemapper->delete($note); @@ -162,4 +196,94 @@ class NoteController extends Controller { return new DataResponse($note); } -} \ No newline at end of file + + /** + * @NoAdminRequired + */ + public function getUserGroupsAndUsersWithShare($noteId) { + $userMgr = \OC::$server->getUserManager(); + $grpMgr = \OC::$server->getGroupManager(); + $users = array(); + $groups = array(); + if($grpMgr->isAdmin($this->userId)) { + $igroups = $grpMgr->search(""); + $iusers = $userMgr->search(""); + foreach($igroups as $g) { + $groups[] = $g->getGID(); + } + foreach($iusers as $u) { + $users[] = $u->getUID(); + } + } else { + $igroups = $grpMgr->getUserGroups($userMgr->get($this->userId)); + foreach($igroups as $g) { + $iusers = $g->getUsers(); + foreach($iusers as $u) { + $users[] = $u->getUID(); + } + $groups[] = $g->getGID(); + } + } + + $users = array_unique($users); + if(($i = array_search($this->userId, $users)) !== false) { + unset($users[$i]); + } + $pos_users = array(); + $pos_groups = array(); + $shares = $this->notesharemapper->getSharesForNote($noteId); + foreach($shares as $s) { + $shareType = $s->getSharedUser(); + if(strlen($shareType) != 0) { + if(($i = array_search($shareType, $users)) !== false) { + unset($users[$i]); + $pos_users[] = $shareType; + } + } else { + $shareType = $s->getSharedGroup(); + if(($i = array_search($shareType, $groups)) !== false) { + unset($groups[$i]); + $pos_groups[] = $shareType; + } + } + } + $params = array('groups' => $groups, 'users' => $users, 'posGroups' => $pos_groups, 'posUsers' => $pos_users); + return new JSONResponse($params); + } + + /** + * @NoAdminRequired + */ + public function addGroupShare($groupId, $noteId) { + $share = new NoteShare(); + $share->setSharedGroup($groupId); + $share->setNoteId($noteId); + $this->notesharemapper->insert($share); + } + + /** + * @NoAdminRequired + */ + public function removeGroupShare($groupId, $noteId) { + $share = $this->notesharemapper->findByNoteAndGroup($noteId, $groupId); + $this->notesharemapper->delete($share); + } + + /** + * @NoAdminRequired + */ + public function addUserShare($userId, $noteId) { + $share = new NoteShare(); + $share->setSharedUser($userId); + $share->setNoteId($noteId); + $this->notesharemapper->insert($share); + } + + /** + * @NoAdminRequired + */ + public function removeUserShare($userId, $noteId) { + $share = $this->notesharemapper->findByNoteAndUser($noteId, $userId); + $this->notesharemapper->delete($share); + } +} diff --git a/css/style.css b/css/style.css index 75e7523..26a219c 100644 --- a/css/style.css +++ b/css/style.css @@ -31,6 +31,10 @@ top: 6px; } +.save-button #unshare-button { + display: none; +} + #div-content .save-button { float: right; padding-right: 8px; @@ -47,8 +51,6 @@ /* Grid Note */ #div-content .note-title { - height: 28px; - width: calc(100% - 20px); font-size: 18px; font-weight: bold; text-overflow: ellipsis; @@ -56,6 +58,16 @@ overflow: hidden; } +#div-content .shared-title, #div-content .shared-title-owner { + float: right; + margin: 2px; + opacity: 0.5; +} + +#div-content .shared-title-owner { + margin-right: 25px; +} + .noselect { -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Chrome/Safari/Opera */ @@ -141,6 +153,19 @@ div[data-placeholder]:not([data-placeholder=""]):empty::before { content: attr(data-placeholder); } +#note-share-options { + display: none; + padding-bottom: 5px; +} + +.selected-share:hover, .unselected-share:hover, .selected-share span:hover, .unselected-share span:hover { + cursor: pointer; +} + +.selected-share, .unselected-share { + padding-left: 5px; +} + /* Modal Content */ .modal-content { diff --git a/db/note.php b/db/note.php index 301e229..a96095a 100644 --- a/db/note.php +++ b/db/note.php @@ -12,6 +12,8 @@ class Note extends Entity implements JsonSerializable { protected $timestamp; protected $colorId; protected $userId; + protected $sharedWith; + protected $isShared; protected $color; @@ -26,7 +28,10 @@ class Note extends Entity implements JsonSerializable { 'content' => $this->content, 'timestamp' => $this->timestamp, 'colorid' => $this->colorId, - 'color' => $this->color + 'color' => $this->color, + 'userid' => $this->userId, + 'sharedwith' => $this->sharedWith, + 'isshared' => $this->isShared ]; } -} \ No newline at end of file +} diff --git a/db/notemapper.php b/db/notemapper.php index 1cb3261..50ce0c0 100644 --- a/db/notemapper.php +++ b/db/notemapper.php @@ -11,11 +11,23 @@ class NoteMapper extends Mapper { parent::__construct($db, 'quicknotes_notes', '\OCA\QuickNotes\Db\Note'); } + /** + * @param int $id + * @param string $userId + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * @return Note + */ public function find($id, $userId) { $sql = 'SELECT * FROM *PREFIX*quicknotes_notes WHERE id = ? AND user_id = ?'; return $this->findEntity($sql, [$id, $userId]); } + public function findById($id) { + $sql = 'SELECT * FROM *PREFIX*quicknotes_notes WHERE id = ?'; + return $this->findEntity($sql, [$id]); + } + public function findAll($userId) { $sql = 'SELECT * FROM *PREFIX*quicknotes_notes WHERE user_id = ?'; return $this->findEntities($sql, [$userId]); @@ -29,4 +41,4 @@ class NoteMapper extends Mapper { return $row['count']; } -} \ No newline at end of file +} diff --git a/db/noteshare.php b/db/noteshare.php new file mode 100644 index 0000000..bf32689 --- /dev/null +++ b/db/noteshare.php @@ -0,0 +1,22 @@ + $this->id, + 'noteid' => $this->noteId, + 'shareduser' => $this->sharedUser, + 'sharedgroup' => $this->sharedGroup + ]; + } +} diff --git a/db/notesharemapper.php b/db/notesharemapper.php new file mode 100644 index 0000000..aefbd45 --- /dev/null +++ b/db/notesharemapper.php @@ -0,0 +1,48 @@ +findEntity($sql, [$id, $userId]); + }*/ + + public function findForUser($userId) { + $sql = 'SELECT * FROM *PREFIX*quicknotes_shares WHERE shared_user = ?'; + return $this->findEntities($sql, [$userId]); + } + + public function findForGroup($groupId) { + $sql = 'SELECT * FROM *PREFIX*quicknotes_shares WHERE shared_group = ?'; + return $this->findEntities($sql, [$groupId]); + } + + public function findByNoteAndUser($noteId, $userId) { + $sql = 'SELECT * FROM *PREFIX*quicknotes_shares WHERE shared_user = ? AND note_id = ?'; + return $this->findEntity($sql, [$userId, $noteId]); + } + + public function findByNoteAndGroup($noteId, $groupId) { + $sql = 'SELECT * FROM *PREFIX*quicknotes_shares WHERE shared_group = ? AND note_id = ?'; + return $this->findEntity($sql, [$groupId, $noteId]); + } + + public function getSharesForNote($noteId) { + $sql = 'SELECT * FROM *PREFIX*quicknotes_shares WHERE note_id = ?'; + return $this->findEntities($sql, [$noteId]); + } + + public function deleteByNoteId($noteId) { + $sql = 'DELETE FROM *PREFIX*quicknotes_shares WHERE note_id = ?'; + $this->execute($sql, [$noteId]); + } +} diff --git a/js/script.js b/js/script.js index c23f6ed..06aacfa 100644 --- a/js/script.js +++ b/js/script.js @@ -24,6 +24,65 @@ var Notes = function (baseUrl) { this._activeNote = undefined; }; +var moveToUnselectedShare = function() { + var curr = $(this).clone(); + var groupIndex = curr.html().indexOf('(group)'); + var id = $('.note-active').data('id'); + if(groupIndex >= 0) { + var groupId = curr.html().substring(0, groupIndex); + var formData = { + groupId : groupId, + noteId : id + }; + $.post(OC.generateUrl('/apps/quicknotes/api/0.1/groups/removeshare'), formData, function(data){ + }); + } else { + var userId = curr.html(); + var formData = { + userId : userId, + noteId : id + }; + $.post(OC.generateUrl('/apps/quicknotes/api/0.1/users/removeshare'), formData, function(data){ + }); + } + curr.switchClass('selected-share', 'unselected-share', 0); + curr.hide(); + curr.click(moveToSelectedShare); + $(curr).appendTo($('#share-neg')); + $(this).remove(); + var pos = $('#share-pos'); + if(pos.children().length == 0) pos.hide(); +} + +var moveToSelectedShare = function() { + var curr = $(this).clone(); + var groupIndex = curr.html().indexOf('(group)'); + var id = $('.note-active').data('id'); + if(groupIndex >= 0) { + var groupId = curr.html().substring(0, groupIndex); + var formData = { + groupId : groupId, + noteId : id + }; + $.post(OC.generateUrl('/apps/quicknotes/api/0.1/groups/addshare'), formData, function(data){ + }); + } else { + var userId = curr.html(); + var formData = { + userId : userId, + noteId : id + }; + $.post(OC.generateUrl('/apps/quicknotes/api/0.1/users/addshare'), formData, function(data){ + }); + } + curr.switchClass('unselected-share', 'selected-share', 0); + curr.click(moveToUnselectedShare); + $(curr).appendTo($('#share-pos')); + $(this).remove(); + $('#share-pos').show(); + $('#share-search').val(''); +} + Notes.prototype = { load: function (id) { var self = this; @@ -269,6 +328,7 @@ View.prototype = { $("#app-content").on("click", ".quicknote", function (event) { event.stopPropagation(); // Not work so need fix on next binding.. + if($(this).hasClass('shared')) return; //shares don't allow editing var modalnote = $("#modal-note-editable .quicknote"); var modalid = modalnote.data('id'); if (modalid > 0) return; @@ -335,6 +395,80 @@ View.prototype = { modalnote.css("background-color", color); }); + // handle share editing notes. + $('#modal-note-div #share-button').click(function (event) { + var id = $('.note-active').data('id'); + var formData = { + noteId: id + } + $.post(OC.generateUrl('/apps/quicknotes/api/0.1/getusergroups'), formData, function(data) { + var shareOptions = $('#note-share-options'); + var groups = data.groups; + var users = data.users; + var pos_groups = data.posGroups; + var pos_users = data.posUsers; + var neg = $('#share-neg'); + var pos = $('#share-pos'); + var sear = $('#share-search'); + for(var i=0; i= 0) { + $(lis[i]).show(); + } else { + $(lis[i]).hide(); + } + } + } + modalNote.outerHeight(startHeight + shareOptions.outerHeight(true)); + }); + }); + }); + // handle cancel editing notes. $('#modal-note-div #cancel-button').click(function (event) { self.cancelEdit(); @@ -384,6 +518,18 @@ View.prototype = { $('#app-navigation .any-color').addClass('icon-checkmark'); }); + $('#shared-with-you').click(function () { + $('.notes-grid').isotope({ filter: function() { + return $(this).children().hasClass('shared'); + } }); + }); + + $('#shared-by-you').click(function () { + $('.notes-grid').isotope({ filter: function() { + return $(this).children().hasClass('shareowner'); + } }); + }); + // create a new note var self = this; $('#new-note').click(function () { diff --git a/templates/part.navigation.php b/templates/part.navigation.php index 247255a..34e5a3c 100644 --- a/templates/part.navigation.php +++ b/templates/part.navigation.php @@ -4,6 +4,8 @@