diff --git a/appinfo/routes.php b/appinfo/routes.php index 744c438..e4f211c 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -5,6 +5,28 @@ return ['resources' => 'noteApi' => ['url' => '/api/v1/notes'] ], 'routes' => [ - ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'] + // Main page + [ + 'name' => 'page#index', + 'url' => '/', + 'verb' => 'GET' + ], + // Share + [ + 'name' => 'share#destroy', + 'url' => '/share/{noteId}', + 'verb' => 'DELETE' + ], + // User Settings + [ + 'name' => 'settings#setUserValue', + 'url' => '/setuservalue', + 'verb' => 'POST' + ], + [ + 'name' => 'settings#getUserValue', + 'url' => '/getuservalue', + 'verb' => 'GET' + ] ] ]; diff --git a/css/style.scss b/css/style.scss index 079c112..a82a5ce 100644 --- a/css/style.scss +++ b/css/style.scss @@ -226,7 +226,8 @@ div[contenteditable="true"] { border: none; } -.note-options { +.note-options, +.note-disable-options { padding: 8px; padding-top: 0px; } @@ -249,6 +250,7 @@ div[contenteditable="true"] { opacity: 0.5; } +.slim-share, .slim-tag { display: inline-block; cursor: pointer; @@ -260,14 +262,17 @@ div[contenteditable="true"] { font-size: 90%; } +.slim-share:hover, .slim-tag:hover { background-color: rgba(0,0,0,0.12); } -.note-tags { - margin-top: 8px; +.note-shares > .slim-share, +.note-tags > .slim-tag { + margin-top: 5px; } + /* Restore defaults select2 rules */ .select2-container-multi @@ -294,6 +299,10 @@ div[contenteditable="true"] { z-index: 10000; } +div.select2-container { + width: 100%; +} + /* Modal Content */ .modal-content { diff --git a/js/qn-dialogs.js b/js/qn-dialogs.js index 360c115..c992f39 100644 --- a/js/qn-dialogs.js +++ b/js/qn-dialogs.js @@ -56,6 +56,9 @@ const QnDialogs = { data.push({id: item.id, text: item.name}); }); return data; + }, + formatNoMatches: function() { + return t('quicknotes', 'No tags found'); } }); @@ -125,6 +128,113 @@ const QnDialogs = { $('.select2-input').focus(); }); }, + shares: function (availableUsers, selectedUsers, callback) { + return $.when(this._getMessageTemplate()).then(function ($tmpl) { + var dialogName = 'qn-dialog-content'; + var dialogId = '#' + dialogName; + var $dlg = $tmpl.octemplate({ + dialog_name: dialogName, + title: t('quicknotes', 'Share note'), + message: t('quicknotes', 'Select the users to share. By default you only share the note. Attachments should be shared from files so they can view it.'), + type: 'none' + }); + + var input = $(''); + input.attr('type', 'text'); + input.attr('id', dialogName + '-input'); + input.attr('multiple', 'multiple'); + + $dlg.append(input); + $('body').append($dlg); + + input.select2({ + placeholder: t('quicknotes', 'Select the users to share'), + multiple: true, + allowClear: true, + toggleSelect: true, + createSearchChoice: function(params) { + return undefined; + }, + tags: function () { + var data = []; + availableUsers.forEach(function (item, index) { + // Select2 expect id, text. + data.push({id: item, text: item}); + }); + return data; + }, + formatNoMatches: function() { + return t('quicknotes', 'No user found'); + } + }); + + input.val(selectedUsers.map(function (value) { return value.name })); + input.trigger("change"); + + $('.select2-input').on("keyup", function (event) { + if (event.keyCode === 27) { + event.preventDefault(); + event.stopPropagation(); + input.select2('close'); + if (callback !== undefined) { + callback(false, []); + } + $(dialogId).ocdialog('close'); + } + }); + + // wrap callback in _.once(): + // only call callback once and not twice (button handler and close + // event) but call it for the close event, if ESC or the x is hit + if (callback !== undefined) { + callback = _.once(callback); + } + + var buttonlist = [{ + text: t('quicknotes', 'Cancel'), + click: function () { + input.select2('close'); + if (callback !== undefined) { + callback(false, []); + } + $(dialogId).ocdialog('close'); + } + }, { + text: t('quicknotes', 'Done'), + click: function () { + input.select2('close'); + if (callback !== undefined) { + var users = []; + // Quicknotes shares expect id, shared_user + newUsers = input.select2("data"); + newUsers.forEach(function (item) { + item['shared_user'] = item.text; + users.push(item); + }); + callback(true, users); + } + $(dialogId).ocdialog('close'); + }, + defaultButton: true + } + ]; + + $(dialogId).ocdialog({ + closeOnEscape: false, + modal: true, + buttons: buttonlist, + close: function () { + input.select2("close"); + // callback is already fired if Yes/No is clicked directly + if (callback !== undefined) { + callback(false, input.val()); + } + } + }); + + $('.select2-input').focus(); + }); + }, _getMessageTemplate: function () { var defer = $.Deferred(); if (!this.$messageTemplate) { diff --git a/js/script.js b/js/script.js index b54033e..ee2de34 100644 --- a/js/script.js +++ b/js/script.js @@ -29,6 +29,9 @@ var Notes = function (baseUrl) { this._baseUrl = baseUrl; this._notes = []; this._loaded = false; + + this._usersSharing = []; + this._loadUsersSharing(); }; Notes.prototype = { @@ -71,6 +74,9 @@ Notes.prototype = { }); return Ccolors; }, + getUsersSharing: function () { + return this._usersSharing; + }, // Get the tags used in the notes getTags: function () { var tags = []; @@ -136,6 +142,42 @@ Notes.prototype = { deferred.reject(); }); return deferred.promise(); + }, + // Delete shared note. + forgetShare: function (note) { + var self = this; + var deferred = $.Deferred(); + $.ajax({ + url: OC.generateUrl('/apps/quicknotes/share') + '/' + note.id, + method: 'DELETE' + }).done(function () { + var index = self._notes.findIndex((aNote) => aNote.id === note.id); + self._notes.splice(index, 1); + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + return deferred.promise(); + }, + // Get the users to share in the notes + _loadUsersSharing: function () { + var self = this; + $.get(OC.linkToOCS('apps/files_sharing/api/v1/', 1) + 'sharees', { + format: 'json', + perPage: 50, + itemType: 1 + }).done(function (shares) { + var users = []; + $.each(shares.ocs.data.exact.users, function(index, user) { + users.push(user.value.shareWith); + }); + $.each(shares.ocs.data.users, function(index, user) { + users.push(user.value.shareWith); + }); + self._usersSharing = users; + }).fail(function () { + console.error("Could not get users to share."); + }); } }; @@ -164,11 +206,12 @@ View.prototype = { this._editableContent(note.content); this._editablePinned(note.ispinned); this._editableColor(note.color); + this._editableShares(note.shared_with, note.shared_by); this._editableTags(note.tags); - this._editableAttachts(note.attachts); + this._editableAttachts(note.attachts, !note.is_shared); // Create medium div editor. - this._initEditor(); + this._isEditable(!note.is_shared); // Show modal editor this._showEditor(id); @@ -182,7 +225,8 @@ View.prototype = { attachts: this._editableAttachts(), color: this._editableColor(), pinned: this._editablePinned(), - tags: this._editableTags() + tags: this._editableTags(), + shares: this._editableShares() }; var self = this; this._notes.update(fakeNote).done(function (note) { @@ -304,18 +348,33 @@ View.prototype = { t('quicknotes', 'Delete note'), function(result) { if (result) { - self._notes.remove(note).done(function () { - if (self._notes.length() > 0) { - $(".notes-grid").isotope('remove', gridnote.parent()) - .isotope('layout'); - self.showAll(); - self.renderNavigation(); - } else { - self.render(); - } - }).fail(function () { - alert('Could not delete note, not found'); - }); + if (!note.is_shared) { + self._notes.remove(note).done(function () { + if (self._notes.length() > 0) { + $(".notes-grid").isotope('remove', gridnote.parent()) + .isotope('layout'); + self.showAll(); + self.renderNavigation(); + } else { + self.render(); + } + }).fail(function () { + alert('Could not delete note, not found'); + }); + } else { + self._notes.forgetShare(note).done(function () { + if (self._notes.length() > 0) { + $(".notes-grid").isotope('remove', gridnote.parent()) + .isotope('layout'); + self.showAll(); + self.renderNavigation(); + } else { + self.render(); + } + }).fail(function () { + alert('Could not delete note, not found'); + }); + } } }, true @@ -454,6 +513,20 @@ View.prototype = { $('#modal-note-div #tag-button').trigger( "click"); }); + // handle tags button. + $('#modal-note-div #share-button').click(function (event) { + event.stopPropagation(); + QnDialogs.shares( + self._notes.getUsersSharing(), + self._editableShares(), + function(result, newShares) { + if (result === true) { + self._editableShares(newShares, []); + } + } + ); + }); + // handle attach button. $('#modal-note-div #attach-button').click(function (event) { event.stopPropagation(); @@ -465,7 +538,7 @@ View.prototype = { preview_url: OC.generateUrl('core') + '/preview.png?file=' + encodeURI(datapath) + '&x=512&y=512', redirect_url: OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', {dir: fileInfo.path, scrollto: fileInfo.name}) }); - self._editableAttachts(attachts); + self._editableAttachts(attachts, true); }).fail(() => { console.log("ERRORRR"); }); @@ -483,13 +556,16 @@ View.prototype = { if (result === true) { self._editableTags(newTags); } - }, - true, - t('quicknotes', 'Tags'), - false + } ); }); + // handle cancel editing notes. + $('#modal-note-div #close-button').click(function (event) { + event.stopPropagation(); + self.cancelEdit(); + }); + // handle cancel editing notes. $('#modal-note-div #cancel-button').click(function (event) { event.stopPropagation(); @@ -516,36 +592,13 @@ View.prototype = { $('#app-navigation ul').html(html); - // show all notes - $('#all-notes').click(function () { - $('.notes-grid').isotope({ filter: '*'}); + /* Create a new note */ - var oldColorTool = $('#app-navigation .circle-toolbar.icon-checkmark'); - $.each(oldColorTool, function(i, oct) { - $(oct).removeClass('icon-checkmark'); - }); - $('#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 () { var fakenote = { title: t('quicknotes', 'New note'), - content: '', - color: '#F7EB96' + content: '' }; self._notes.create(fakenote).done(function(note) { if (self._notes.length() > 1) { @@ -564,6 +617,38 @@ View.prototype = { }); }); + /* Show all notes */ + + $('#all-notes').click(function () { + $('.notes-grid').isotope({ filter: '*'}); + + var oldColorTool = $('#colors-folder .circle-toolbar.icon-checkmark'); + $.each(oldColorTool, function(i, oct) { + $(oct).removeClass('icon-checkmark'); + }); + $('#app-navigation .any-color').addClass('icon-checkmark'); + }); + + /* Shares Navigation */ + + $('#shared-folder').click(function () { + $(this).toggleClass("open"); + }); + + $('#shared-with-you').click(function (event) { + event.stopPropagation(); + $('.notes-grid').isotope({ filter: function() { + return $(this).children().hasClass('shared'); + } }); + }); + + $('#shared-by-you').click(function (event) { + event.stopPropagation(); + $('.notes-grid').isotope({ filter: function() { + return $(this).children().hasClass('shareowner'); + } }); + }); + /* Colors Navigation */ $('#colors-folder').click(function () { @@ -574,9 +659,9 @@ View.prototype = { event.stopPropagation(); }); - $('#app-navigation .circle-toolbar').click(function (event) { + $('#colors-folder .circle-toolbar').click(function (event) { event.stopPropagation(); - var oldColorTool = $('#app-navigation .circle-toolbar.icon-checkmark'); + var oldColorTool = $('#colors-folder .circle-toolbar.icon-checkmark'); $.each(oldColorTool, function(i, oct) { $(oct).removeClass('icon-checkmark'); }); @@ -636,8 +721,48 @@ View.prototype = { $(oct).removeClass('icon-checkmark'); }); }); - }, + }, + renderSettings: function () { + /* Render view */ + var html = Handlebars.templates['settings']({}); + $('#app-settings-content').html(html); + var self = this; + $.get(OC.generateUrl('apps/quicknotes/getuservalue'), {'type': 'default_color'}) + .done(function (response) { + var color = response.value;; + var colors = $("#setting-defaul-color")[0].getElementsByClassName("circle-toolbar"); + $.each(colors, function(i, c) { + if (color === self._colorToHex(c.style.backgroundColor)) { + c.className += " icon-checkmark"; + } + }); + }); + + /* Settings */ + + $("#app-settings-content").off(); + + $('#app-settings-content').on('click', '.circle-toolbar', function (event) { + event.stopPropagation(); + + var currentColor = $(this); + var color = self._colorToHex(currentColor.css("background-color")); + + $.ajax({ + url: OC.generateUrl('apps/quicknotes/setuservalue'), + type: 'POST', + data: { + 'type': 'default_color', + 'value': color + }, + success: function (response) { + $('#setting-defaul-color .circle-toolbar').removeClass('icon-checkmark'); + currentColor.addClass('icon-checkmark'); + } + }); + }); + }, /** * Some 'private' functions as helpers. */ @@ -655,6 +780,25 @@ View.prototype = { return digits[1] + '#' + rgb.toString(16).toUpperCase(); }, + _isEditable: function(editable) { + if (editable === undefined) + return $('#title-editable').prop('contenteditable'); + else { + if (editable) { + $('#modal-note-div .icon-header-note').show(); + $('#title-editable').prop('contenteditable', true); + $('#modal-note-div .note-options').show(); + $('#modal-note-div .note-disable-options').hide(); + this._initEditor(); + } else { + $('#modal-note-div .icon-header-note').hide(); + $('#title-editable').removeAttr("contentEditable"); + $('#content-editable').removeAttr("contentEditable"); + $('#modal-note-div .note-options').hide(); + $('#modal-note-div .note-disable-options').show(); + } + } + }, _editableId: function(id) { if (id === undefined) return $("#modal-note-div .quicknote").data('id'); @@ -706,6 +850,19 @@ View.prototype = { $("#modal-note-div .quicknote").css("background-color", color); } }, + _editableShares: function(shared_with, shared_by) { + if (shared_with === undefined) { + return $("#modal-note-div .slim-share").toArray().map(function (value) { + return { + id: value.getAttribute('share-id'), + name: value.textContent.trim() + }; + }); + } else { + var html = Handlebars.templates['shares']({ shared_by: shared_by, shared_with: shared_with}); + $("#modal-note-div .note-shares").replaceWith(html); + } + }, _editableTags: function(tags) { if (tags === undefined) { return $("#modal-note-div .slim-tag").toArray().map(function (value) { @@ -719,7 +876,7 @@ View.prototype = { $("#modal-note-div .note-tags").replaceWith(html); } }, - _editableAttachts: function(attachts) { + _editableAttachts: function(attachts, can_delete) { if (attachts === undefined) { return $("#modal-note-div .note-attach").toArray().map(function (value) { return { @@ -729,7 +886,7 @@ View.prototype = { }; }); } else { - var html = Handlebars.templates['attachts']({ attachts: attachts}); + var html = Handlebars.templates['attachts']({ attachts: attachts, can_delete: can_delete}); $("#modal-note-div .note-attachts").replaceWith(html); lozad('.attach-preview').observe(); @@ -797,8 +954,10 @@ View.prototype = { this._editor = editor; }, _destroyEditor: function() { - this._editor.destroy(); - this._editor = undefined; + if (this._editor != undefined) { + this._editor.destroy(); + this._editor = undefined; + } this._changed = false; this._editableId(-1); @@ -821,7 +980,6 @@ View.prototype = { "left" : note.offset().left, "top" : note.offset().top, "width" : note.width(), - "min-height": note.height(), "height:" : "auto" }); @@ -893,6 +1051,7 @@ View.prototype = { render: function () { this.renderNavigation(); this.renderContent(); + this.renderSettings(); } }; @@ -926,6 +1085,19 @@ new OCA.Search(search, function() { }); +/** + * Add Helpers to handlebars + */ + +Handlebars.registerHelper('tSW', function(user) { + return t('quicknotes', 'Shared with {user}', {user: user}); +}); + +Handlebars.registerHelper('tSB', function(user) { + return t('quicknotes', 'Shared by {user}', {user: user}); +}); + + /* * Create modules */ @@ -941,8 +1113,7 @@ view.renderContent(); * Loading notes and render final view. */ notes.load().done(function () { - view.renderNavigation(); - view.renderContent(); + view.render(); }).fail(function () { alert('Could not load notes'); }); diff --git a/js/templates/attachts.handlebars b/js/templates/attachts.handlebars index d1f1b90..63d04d3 100644 --- a/js/templates/attachts.handlebars +++ b/js/templates/attachts.handlebars @@ -4,7 +4,9 @@
-
+ {{#if ../can_delete}} +
+ {{/if}}
{{/each}}
\ No newline at end of file diff --git a/js/templates/navigation.handlebars b/js/templates/navigation.handlebars index 4b5dc3c..6442545 100644 --- a/js/templates/navigation.handlebars +++ b/js/templates/navigation.handlebars @@ -8,6 +8,18 @@ {{allNotesTxt}} +
  • + + {{t "quicknotes" "Shared" }} + +
  • {{colorsTxt}} diff --git a/js/templates/note-item.handlebars b/js/templates/note-item.handlebars index 517acf9..5c36109 100644 --- a/js/templates/note-item.handlebars +++ b/js/templates/note-item.handlebars @@ -1,16 +1,5 @@
    -
    - {{#if isshared}} -
    -
    -
    - {{{ title }}} -
    -
    -
    - {{{ content }}} -
    - {{else}} +
    {{#each attachts}} @@ -28,11 +17,6 @@
    {{/if}}
    -
    {{{ title }}}
    @@ -40,12 +24,16 @@
    {{{ content }}}
    +
    + {{#each shared_with}} + + {{/each}} +
    {{#each tags}}
    {{{ name }}}
    {{/each}}
    - {{/if}}
    \ No newline at end of file diff --git a/js/templates/notes.handlebars b/js/templates/notes.handlebars index a05d93e..1de82bf 100644 --- a/js/templates/notes.handlebars +++ b/js/templates/notes.handlebars @@ -2,18 +2,7 @@
    {{#each notes}}
    -
    - {{#if isshared}} -
    -
    -
    - {{{ title }}} -
    -
    -
    - {{{ content }}} -
    - {{else}} +
    {{#each attachts}} @@ -25,17 +14,17 @@
    - {{#if ispinned}} -
    + {{#if is_shared}} +
    +
    {{else}} -
    + {{#if ispinned}} +
    + {{else}} +
    + {{/if}} +
    {{/if}} -
    -
    {{{ title }}}
    @@ -43,6 +32,11 @@
    {{{ content }}}
    +
    + {{#each shared_with}} + + {{/each}} +
    {{#each tags}}
    @@ -51,7 +45,6 @@ {{/each}}
    - {{/if}}
    {{/each}} @@ -68,6 +61,7 @@
    +
    @@ -87,9 +81,9 @@
    - + @@ -102,10 +96,14 @@ -
    -
    -
    +
    +
    +
    +
    + +
    +
    diff --git a/js/templates/settings.handlebars b/js/templates/settings.handlebars new file mode 100644 index 0000000..fcb44c2 --- /dev/null +++ b/js/templates/settings.handlebars @@ -0,0 +1,19 @@ +
    + +
    +
    +
    +
    + + + + + + + + + + +
    +
    +
    \ No newline at end of file diff --git a/js/templates/shares.handlebars b/js/templates/shares.handlebars new file mode 100644 index 0000000..77c5a7d --- /dev/null +++ b/js/templates/shares.handlebars @@ -0,0 +1,5 @@ +
    + {{#each shared_with}} + + {{/each}} +
    \ No newline at end of file diff --git a/lib/Controller/NoteController.php b/lib/Controller/NoteController.php index 4be01ca..623e96a 100644 --- a/lib/Controller/NoteController.php +++ b/lib/Controller/NoteController.php @@ -91,7 +91,7 @@ class NoteController extends Controller { * @param string $content * @param string $color */ - public function create($title, $content, $color = "#F7EB96") { + public function create($title, $content, $color = NULL) { $note = $this->noteService->create($this->userId, $title, $content, $color); $etag = md5(json_encode($note)); @@ -111,10 +111,11 @@ class NoteController extends Controller { * @param array $attachts * @param bool $pinned * @param array $tags + * @param array $shares * @param string $color */ - public function update(int $id, string $title, string $content, array $attachts, bool $pinned, array $tags, string $color = "#F7EB96"): JSONResponse { - $note = $this->noteService->update($this->userId, $id, $title, $content, $attachts, $pinned, $tags, $color); + public function update(int $id, string $title, string $content, array $attachts, bool $pinned, array $tags, array $shares, string $color = "#F7EB96"): JSONResponse { + $note = $this->noteService->update($this->userId, $id, $title, $content, $attachts, $pinned, $tags, $shares, $color); if (is_null($note)) { return new JSONResponse([], Http::STATUS_NOT_FOUND); } diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php new file mode 100644 index 0000000..6cff97a --- /dev/null +++ b/lib/Controller/SettingsController.php @@ -0,0 +1,112 @@ + + * + * @author Matias De lellis + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\QuickNotes\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IRequest; + +use OCA\QuickNotes\Service\SettingsService; + + +class SettingsController extends Controller { + + /** @var SettingsService */ + private $settingsService; + + /** @var string */ + private $userId; + + const STATE_OK = 0; + const STATE_FALSE = 1; + const STATE_SUCCESS = 2; + const STATE_ERROR = 3; + + public function __construct ($appName, + IRequest $request, + SettingsService $settingsService, + $userId) + { + parent::__construct($appName, $request); + + $this->appName = $appName; + $this->settingsService = $settingsService; + $this->userId = $userId; + } + + /** + * @NoAdminRequired + * @param $type + * @param $value + * @return JSONResponse + */ + public function setUserValue($type, $value) { + $status = self::STATE_SUCCESS; + + switch ($type) { + case SettingsService::COLOR_FOR_NEW_NOTES_KEY: + $this->settingsService->setColorForNewNotes($value); + break; + default: + $status = self::STATE_ERROR; + break; + } + + // Response + $result = [ + 'status' => $status, + 'value' => $value + ]; + + return new JSONResponse($result); + } + + /** + * @NoAdminRequired + * @param $type + * @return JSONResponse + */ + public function getUserValue($type) { + $status = self::STATE_OK; + $value ='nodata'; + + switch ($type) { + case SettingsService::COLOR_FOR_NEW_NOTES_KEY: + $value = $this->settingsService->getColorForNewNotes($this->userId); + break; + default: + $status = self::STATE_FALSE; + break; + } + + $result = [ + 'status' => $status, + 'value' => $value + ]; + + return new JSONResponse($result); + } + +} diff --git a/lib/Controller/ShareController.php b/lib/Controller/ShareController.php new file mode 100644 index 0000000..1d5b6fe --- /dev/null +++ b/lib/Controller/ShareController.php @@ -0,0 +1,68 @@ + + * + * @author 2020 Matias De lellis + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\QuickNotes\Controller; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Controller; + +use OCP\IRequest; + +use OCA\QuickNotes\Db\NoteShare; +use OCA\QuickNotes\Db\NoteShareMapper; + + +class ShareController extends Controller { + + private $noteShareMapper; + private $userId; + + public function __construct($AppName, + IRequest $request, + NoteShareMapper $noteShareMapper, + $userId) + { + parent::__construct($AppName, $request); + + $this->noteShareMapper = $noteShareMapper; + $this->userId = $userId; + } + + /** + * @NoAdminRequired + * + * @param int $noteId + */ + public function destroy(int $noteId): JSONResponse { + try { + $noteShare = $this->noteShareMapper->findByNoteAndUser($noteId, $this->userId); + } catch (DoesNotExistException $e) { + return new JSONResponse([], Http::STATUS_NOT_FOUND); + } + + $this->noteShareMapper->delete($noteShare); + + return new JSONResponse([]); + } + +} diff --git a/lib/Db/AttachMapper.php b/lib/Db/AttachMapper.php index 9fa896a..158b085 100644 --- a/lib/Db/AttachMapper.php +++ b/lib/Db/AttachMapper.php @@ -73,8 +73,7 @@ class AttachMapper extends QBMapper { * @param string $userId * @param int $noteId * @throws \OCP\AppFramework\Db\DoesNotExistException if not found - * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result - * @return Note[] + * @return Attach[] */ public function findFromNote($userId, $noteId) { $qb = $this->db->getQueryBuilder(); @@ -86,4 +85,5 @@ class AttachMapper extends QBMapper { ); return $this->findEntities($qb); } + } diff --git a/lib/Db/Note.php b/lib/Db/Note.php index c941ee8..15007da 100644 --- a/lib/Db/Note.php +++ b/lib/Db/Note.php @@ -13,7 +13,8 @@ class Note extends Entity implements JsonSerializable { protected $timestamp; protected $colorId; protected $userId; - protected $sharedWith; + protected $sharedWith = []; + protected $sharedBy = []; protected $isShared; protected $tags; protected $attachts; @@ -40,8 +41,9 @@ class Note extends Entity implements JsonSerializable { 'colorid' => $this->colorId, 'color' => $this->color, 'userid' => $this->userId, - 'sharedwith' => $this->sharedWith, - 'isshared' => $this->isShared, + 'shared_with' => $this->sharedWith, + 'shared_by' => $this->sharedBy, + 'is_shared' => $this->isShared, 'tags' => $this->tags, 'attachts' => $this->attachts ]; diff --git a/lib/Db/NoteMapper.php b/lib/Db/NoteMapper.php index 5b7e287..c8ca2a0 100644 --- a/lib/Db/NoteMapper.php +++ b/lib/Db/NoteMapper.php @@ -31,6 +31,22 @@ class NoteMapper extends QBMapper { return $this->findEntity($qb); } + /** + * @param int $id + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * @return Note + */ + public function findShared($id) { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->tableName) + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + ); + return $this->findEntity($qb); + } + public function findAll($userId) { $qb = $this->db->getQueryBuilder(); $qb->select('*') diff --git a/lib/Db/NoteShare.php b/lib/Db/NoteShare.php index bf32689..f522a1c 100644 --- a/lib/Db/NoteShare.php +++ b/lib/Db/NoteShare.php @@ -8,15 +8,18 @@ use OCP\AppFramework\Db\Entity; class NoteShare extends Entity implements JsonSerializable { protected $noteId; + protected $userId; protected $sharedUser; protected $sharedGroup; public function jsonSerialize() { return [ 'id' => $this->id, - 'noteid' => $this->noteId, - 'shareduser' => $this->sharedUser, - 'sharedgroup' => $this->sharedGroup + 'user_id' => $this->userId, + 'note_id' => $this->noteId, + 'shared_user' => $this->sharedUser, + 'shared_group' => $this->sharedGroup ]; } + } diff --git a/lib/Db/NoteShareMapper.php b/lib/Db/NoteShareMapper.php index f9d9d6b..50fcc67 100644 --- a/lib/Db/NoteShareMapper.php +++ b/lib/Db/NoteShareMapper.php @@ -45,4 +45,14 @@ class NoteShareMapper extends Mapper { $sql = 'DELETE FROM *PREFIX*quicknotes_shares WHERE note_id = ?'; $this->execute($sql, [$noteId]); } + + public function existsByNoteAndUser($noteId, $userId) { + $sql = 'SELECT * FROM *PREFIX*quicknotes_shares WHERE shared_user = ? AND note_id = ?'; + try { + return $this->findEntities($sql, [$userId, $noteId]); + } catch (DoesNotExistException $e) { + return false; + } + return true; + } } diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index c2dbc06..6123392 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -57,13 +57,16 @@ class FileService { * @param int $fileId file id to show * @param int $sideSize side lenght to show */ - public function getPreviewUrl(int $fileId, int $sideSize): string { + public function getPreviewUrl(int $fileId, int $sideSize): ?string { $userFolder = $this->rootFolder->getUserFolder($this->userId); - $node = current($userFolder->getById($fileId)); - $path = $userFolder->getRelativePath($node->getPath()); + $file = current($userFolder->getById($fileId)); + + if (!($file instanceof File)) { + return null; + } return $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreview', [ - 'file' => $path, + 'file' => $userFolder->getRelativePath($file->getPath()), 'x' => $sideSize, 'y' => $sideSize ]); @@ -78,7 +81,7 @@ class FileService { $userFolder = $this->rootFolder->getUserFolder($this->userId); $file = current($userFolder->getById($fileId)); - if(!($file instanceof File)) { + if (!($file instanceof File)) { return null; } diff --git a/lib/Service/NoteService.php b/lib/Service/NoteService.php index 9110d85..99c7b9b 100644 --- a/lib/Service/NoteService.php +++ b/lib/Service/NoteService.php @@ -48,26 +48,29 @@ class NoteService { private $notemapper; private $notetagmapper; private $colormapper; - private $notesharemapper; + private $noteShareMapper; private $attachMapper; private $tagmapper; private $fileService; + private $settingsService; public function __construct(NoteMapper $notemapper, NoteTagMapper $notetagmapper, - NoteShareMapper $notesharemapper, + NoteShareMapper $noteShareMapper, ColorMapper $colormapper, AttachMapper $attachMapper, TagMapper $tagmapper, - FileService $fileService) + FileService $fileService, + SettingsService $settingsService) { $this->notemapper = $notemapper; $this->notetagmapper = $notetagmapper; $this->colormapper = $colormapper; - $this->notesharemapper = $notesharemapper; + $this->noteShareMapper = $noteShareMapper; $this->attachMapper = $attachMapper; $this->tagmapper = $tagmapper; $this->fileService = $fileService; + $this->settingsService = $settingsService; } /** @@ -75,57 +78,62 @@ class NoteService { */ public function getAll(string $userId): array { $notes = $this->notemapper->findAll($userId); + + // Set shares with others. 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); - } - $note->setTags($this->tagmapper->getTagsForNote($userId, $note->getId())); + $note->setSharedWith($this->noteShareMapper->getSharesForNote($note->getId())); } - $shareEntries = $this->notesharemapper->findForUser($userId); - $shares = array(); - foreach($shareEntries as $entry) { - try { - //find is only to check if current user is owner - $this->notemapper->find($entry->getNoteId(), $userId); - //user is owner, nothing to do - } catch(\OCP\AppFramework\Db\DoesNotExistException $e) { - $share = $this->notemapper->findById($entry->getNoteId()); - $share->setIsShared(true); - $shares[] = $share; - } + + // Get shares from others. + $shares = []; + $sharedEntries = $this->noteShareMapper->findForUser($userId); + foreach($sharedEntries as $sharedEntry) { + $sharedNote = $this->notemapper->findShared($sharedEntry->getNoteId()); + $sharedNote->setIsShared(true); + + $sharedEntry->setUserId($sharedNote->getUserId()); + $sharedNote->setSharedBy([$sharedEntry]); + $shares[] = $sharedNote; } + + // Attahch shared notes from others to same response $notes = array_merge($notes, $shares); - foreach ($notes as $note) { - $note->setTitle(strip_tags($note->getTitle())); + // Set tags to response. + foreach($notes as $note) { + $note->setTags($this->tagmapper->getTagsForNote($userId, $note->getId())); } - // Insert true color to response + // Insert color to response foreach ($notes as $note) { $note->setColor($this->colormapper->find($note->getColorId())->getColor()); } - // Insert true color to response + // Insert pin to response foreach ($notes as $note) { $note->setIsPinned($note->getPinned() ? true : false); } - // Insert true attachts to response + // Insert attachts to response. foreach ($notes as $note) { - $attachts = $this->attachMapper->findFromNote($userId, $note->getId()); + $rAttachts = []; + $attachts = $this->attachMapper->findFromNote($note->getUserId(), $note->getId()); foreach ($attachts as $attach) { - $attach->setPreviewUrl($this->fileService->getPreviewUrl($attach->getFileId(), 512)); - $attach->setRedirectUrl($this->fileService->getRedirectToFileUrl($attach->getFileId())); + $previewUrl = $this->fileService->getPreviewUrl($attach->getFileId(), 512); + if (is_null($previewUrl)) + continue; + + $redirectUrl = $this->fileService->getRedirectToFileUrl($attach->getFileId()); + if (is_null($redirectUrl)) + continue; + + $attach->setPreviewUrl($previewUrl); + $attach->setRedirectUrl($redirectUrl); + + $rAttachts[] = $attach; } - $note->setAttachts($attachts); + $note->setAttachts($rAttachts); } return $notes; @@ -149,7 +157,11 @@ class NoteService { * @param string $content * @param string $color */ - public function create(string $userId, string $title, string $content, string $color = "#F7EB96"): Note { + public function create(string $userId, string $title, string $content, string $color = NULL): Note { + if (is_null($color)) { + $color = $this->settingsService->getColorForNewNotes(); + } + // Get color or append it if ($this->colormapper->colorExists($color)) { $hcolor = $this->colormapper->findByColor($color); @@ -187,6 +199,7 @@ class NoteService { * @param array $attachts * @param bool $pinned * @param array $tags + * @param array $shares * @param string $color */ public function update(string $userId, @@ -196,14 +209,14 @@ class NoteService { array $attachts, bool $pinned, array $tags, - string $color): Note + array $shares, + string $color): ?Note { // Get current Note and Color. - try { - $note = $this->notemapper->find($id, $userId); - } catch(Exception $e) { + $note = $this->get($userId, $id); + if (is_null($note)) return null; - } + $oldcolorid = $note->getColorId(); // Get new Color or append it. @@ -242,6 +255,31 @@ class NoteService { } } + // Delete old shares + $dbShares = $this->noteShareMapper->getSharesForNote($id); + foreach ($dbShares as $dbShare) { + $delete = true; + foreach ($shares as $share) { + if ($dbShare->getSharedUser() === $share['name']) { + $delete = false; + break; + } + } + if ($delete) { + $this->noteShareMapper->delete($dbShare); + } + } + + // Add new shares + foreach ($shares as $share) { + if (!$this->noteShareMapper->existsByNoteAndUser($id, $share['name'])) { + $hShare = new NoteShare(); + $hShare->setNoteId($id); + $hShare->setSharedUser($share['name']); + $this->noteShareMapper->insert($hShare); + } + } + // Delete old tag relations $dbTags = $this->tagmapper->getTagsForNote($userId, $id); foreach ($dbTags as $dbTag) { @@ -304,6 +342,10 @@ class NoteService { } $newnote->setAttachts($attachts); + // Fill shared with with others + $newnote->setIsShared(false); + $newnote->setSharedWith($this->noteShareMapper->getSharesForNote($newnote->getId())); + // Remove old color if necessary if (($oldcolorid !== $hcolor->getId()) && (!$this->notemapper->colorIdCount($oldcolorid))) { @@ -330,7 +372,7 @@ class NoteService { } $oldcolorid = $note->getColorId(); - $this->notesharemapper->deleteByNoteId($note->getId()); + $this->noteShareMapper->deleteByNoteId($note->getId()); // Delete note. $this->notemapper->delete($note); @@ -381,7 +423,7 @@ class NoteService { } $pos_users = array(); $pos_groups = array(); - $shares = $this->notesharemapper->getSharesForNote($noteId); + $shares = $this->noteShareMapper->getSharesForNote($noteId); foreach($shares as $s) { $shareType = $s->getSharedUser(); if(strlen($shareType) !== 0) { @@ -406,14 +448,14 @@ class NoteService { $share = new NoteShare(); $share->setSharedGroup($groupId); $share->setNoteId($noteId); - $this->notesharemapper->insert($share); + $this->noteShareMapper->insert($share); } /** */ public function removeGroupShare($groupId, $noteId) { - $share = $this->notesharemapper->findByNoteAndGroup($noteId, $groupId); - $this->notesharemapper->delete($share); + $share = $this->noteShareMapper->findByNoteAndGroup($noteId, $groupId); + $this->noteShareMapper->delete($share); } /** @@ -422,13 +464,13 @@ class NoteService { $share = new NoteShare(); $share->setSharedUser($userId); $share->setNoteId($noteId); - $this->notesharemapper->insert($share); + $this->noteShareMapper->insert($share); } /** */ public function removeUserShare($userId, $noteId) { - $share = $this->notesharemapper->findByNoteAndUser($noteId, $userId); - $this->notesharemapper->delete($share); + $share = $this->noteShareMapper->findByNoteAndUser($noteId, $userId); + $this->noteShareMapper->delete($share); } } diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php new file mode 100644 index 0000000..97ff5bc --- /dev/null +++ b/lib/Service/SettingsService.php @@ -0,0 +1,65 @@ + + * + * @author Matias De lellis + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\QuickNotes\Service; + +use OCA\QuickNotes\AppInfo\Application; + +use OCP\IConfig; + +class SettingsService { + + /** + * Settings keys and default values. + */ + const COLOR_FOR_NEW_NOTES_KEY = 'default_color'; + const DEFAULT_COLOR_FOR_NEW_NOTES = '#F7EB96'; + + /** @var IConfig Config */ + private $config; + + /** @var string|null */ + private $userId; + + /** + * @param IConfig $config + * @param string $userId + */ + public function __construct(IConfig $config, + $userId) + { + $this->config = $config; + $this->userId = $userId; + } + + + public function getColorForNewNotes(): string { + return $this->config->getUserValue($this->userId, Application::APP_NAME, self::COLOR_FOR_NEW_NOTES_KEY, self::DEFAULT_COLOR_FOR_NEW_NOTES); + } + + public function setColorForNewNotes(string $color) { + $this->config->setUserValue($this->userId, Application::APP_NAME, self::COLOR_FOR_NEW_NOTES_KEY, $color); + } + +} diff --git a/templates/fake.php b/templates/fake.php index 5857412..73b15c4 100644 --- a/templates/fake.php +++ b/templates/fake.php @@ -4,3 +4,9 @@ */ p($l->t('Delete attachment')); p($l->t('Attach file')); +p($l->t('Shared')); +p($l->t('Shared with others')); +p($l->t('Shared with you')); +p($l->t('Share note')); +p($l->t('Close')); +p($l->t('Default color for new notes')); \ No newline at end of file diff --git a/templates/part.settings.php b/templates/part.settings.php index 7eebe65..594ef30 100644 --- a/templates/part.settings.php +++ b/templates/part.settings.php @@ -1,8 +1,8 @@
    - +