From 8f3b9ad73e121040b8d0bfff2cf7e74a16245363 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Fri, 26 Mar 2021 00:47:08 -0300 Subject: [PATCH] Implement an basic search provider. Just search within the title and main note. On the other hand, it implements consistent urls, which allow you to mark a note, tag or color as a favorite in the browser to easily access the notes. --- js/script.js | 165 +++++++++++++++++++++--------- lib/AppInfo/Application.php | 2 + lib/Db/NoteMapper.php | 28 +++++ lib/Search/NoteSearchProvider.php | 114 +++++++++++++++++++++ 4 files changed, 259 insertions(+), 50 deletions(-) create mode 100644 lib/Search/NoteSearchProvider.php diff --git a/js/script.js b/js/script.js index 8bd6cb6..f87abc0 100644 --- a/js/script.js +++ b/js/script.js @@ -295,6 +295,9 @@ View.prototype = { } }); + // Save instance of View + var self = this; + // Show delete and pin icons when hover over the notes. $("#notes-grid-div").on("mouseenter", ".quicknote", function() { $(this).find(".icon-header-note").addClass( "show-header-icon"); @@ -319,23 +322,12 @@ View.prototype = { $('#notes-grid-div').on('click', '.slim-tag', function (event) { event.stopPropagation(); var tagId = parseInt($(this).attr('tag-id'), 10); - $('.notes-grid').isotope({ filter: function() { - var match = false; - $(this).find(".slim-tag").siblings().addBack().each(function() { - var id = parseInt($(this).attr('tag-id'), 10); - if (tagId === id) - match = true; - }); - return match; - }}); - var oldColorTool = $('#app-navigation .circle-toolbar.icon-checkmark'); - $.each(oldColorTool, function(i, oct) { - $(oct).removeClass('icon-checkmark'); - }); + self._cleanNavigation(); + self._filterTag(tagId); + setFilterUrl('t', tagId); }); // Remove note when click icon - var self = this; $('#notes-grid-div').on("click", ".icon-delete-note", function (event) { event.stopPropagation(); @@ -613,13 +605,10 @@ View.prototype = { /* Show all notes */ $('#all-notes').click(function () { + event.preventDefault(); + self._cleanNavigation(); $('.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'); + setFilterUrl(); }); /* Shares Navigation */ @@ -629,17 +618,23 @@ View.prototype = { }); $('#shared-with-you').click(function (event) { + event.preventDefault(); event.stopPropagation(); + self._cleanNavigation(); $('.notes-grid').isotope({ filter: function() { return $(this).children().hasClass('shared'); } }); + setFilterUrl(); }); $('#shared-by-you').click(function (event) { + event.preventDefault(); event.stopPropagation(); + self._cleanNavigation(); $('.notes-grid').isotope({ filter: function() { return $(this).children().hasClass('shareowner'); } }); + setFilterUrl(); }); /* Colors Navigation */ @@ -654,18 +649,13 @@ View.prototype = { $('#colors-folder .circle-toolbar').click(function (event) { event.stopPropagation(); - var oldColorTool = $('#colors-folder .circle-toolbar.icon-checkmark'); - $.each(oldColorTool, function(i, oct) { - $(oct).removeClass('icon-checkmark'); - }); + self._cleanNavigation(); $(this).addClass('icon-checkmark'); if (!$(this).hasClass("any-color")) { var color = $(this).css("background-color"); - $('.notes-grid').isotope({ filter: function() { - var itemColor = $(this).children().css("background-color"); - return color == itemColor; - }}); + self._filterColor(color); + setFilterUrl('c', color); } else { self.showAll(); @@ -679,16 +669,12 @@ View.prototype = { }); $('#app-navigation .nav-note > a').click(function (event) { + event.preventDefault(); event.stopPropagation(); var id = parseInt($(this).parent().data('id'), 10); - $('.notes-grid').isotope({ filter: function() { - var itemId = parseInt($(this).children().data('id'), 10); - return id == itemId; - }}); - var oldColorTool = $('#app-navigation .circle-toolbar.icon-checkmark'); - $.each(oldColorTool, function(i, oct) { - $(oct).removeClass('icon-checkmark'); - }); + self._cleanNavigation(); + self._filterNote(id); + setFilterUrl('n', id); }); /* Tags Navigation */ @@ -698,23 +684,13 @@ View.prototype = { }); $('#app-navigation .nav-tag > a').click(function (event) { + event.preventDefault(); event.stopPropagation(); var tagId = parseInt($(this).parent().attr('tag-id'), 10); - $('.notes-grid').isotope({ filter: function() { - var match = false; - $(this).find(".slim-tag").siblings().addBack().each(function() { - var id = parseInt($(this).attr('tag-id'), 10); - if (tagId === id) - match = true; - }); - return match; - }}); - var oldColorTool = $('#app-navigation .circle-toolbar.icon-checkmark'); - $.each(oldColorTool, function(i, oct) { - $(oct).removeClass('icon-checkmark'); - }); + self._cleanNavigation(); + self._filterTag(tagId); + setFilterUrl('t', tagId); }); - }, renderSettings: function () { /* Render view */ @@ -1040,6 +1016,47 @@ View.prototype = { } ); }, + _filterNote: function (noteId) { + $('.notes-grid').isotope({ + filter: function() { + return noteId == parseInt($(this).children().data('id'), 10); + } + }); + }, + _filterTag: function (tagId) { + $('.notes-grid').isotope({ + filter: function() { + var match = false; + $(this).find(".slim-tag").siblings().addBack().each(function() { + var id = parseInt($(this).attr('tag-id'), 10); + if (tagId == id) + match = true; + }); + return match; + } + }); + }, + _filterColor: function (color) { + $('.notes-grid').isotope({ + filter: function() { + return color == $(this).children().css("background-color"); + } + }); + }, + _selectColor: function (color) { + var circles = $("#colors-folder")[0].getElementsByClassName("circle-toolbar"); + $.each(circles, function(i, c) { + if (color == c.style.backgroundColor) { + c.className += " icon-checkmark"; + } + }); + }, + _cleanNavigation: function () { + var oldColorTool = $('#app-navigation .circle-toolbar.icon-checkmark'); + $.each(oldColorTool, function(i, oct) { + $(oct).removeClass('icon-checkmark'); + }); + }, render: function () { this.renderNavigation(); this.renderContent(); @@ -1048,6 +1065,40 @@ View.prototype = { }; + +/** + * Get the filter as URL parameter + */ +var getFilterUrl = function (filterParam) { + var filter = undefined; + var parser = document.createElement('a'); + parser.href = window.location.href; + var query = parser.search.substring(1); + var vars = query.split('&'); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split('='); + if (pair[0] === filterParam) { + filter = decodeURIComponent(pair[1]); + break; + } + } + return filter; +}; + +/** + * Change the URL location with query as parameter + */ +var setFilterUrl = function (filterParam, filter) { + var cleanUrl = window.location.href.split("?")[0]; + var title = t('quicknotes', 'Quick notes'); + if (filter) { + cleanUrl += '?'+ filterParam + '=' + encodeURIComponent(filter); + } + window.history.replaceState({}, title, cleanUrl); + document.title = title; +}; + + /** * Filter notes. */ @@ -1106,6 +1157,20 @@ view.renderContent(); */ notes.load().done(function () { view.render(); + + var noteId = getFilterUrl('n'); + if (noteId !== undefined) + view._filterNote(noteId); + + var tagId = getFilterUrl('t'); + if (tagId !== undefined) + view._filterTag(tagId); + + var color = getFilterUrl('c'); + if (color !== undefined) { + view._selectColor(color); + view._filterColor(color); + } }).fail(function () { alert('Could not load notes'); }); diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index fae1df2..803d445 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -31,6 +31,7 @@ use OCP\IL10N; use OCP\IURLGenerator; use OCP\IServerContainer; +use OCA\QuickNotes\Search\NoteSearchProvider; class Application extends App implements IBootstrap { @@ -45,6 +46,7 @@ class Application extends App implements IBootstrap { } public function register(IRegistrationContext $context): void { + $context->registerSearchProvider(NoteSearchProvider::class); $context->registerCapability(Capabilities::class); } diff --git a/lib/Db/NoteMapper.php b/lib/Db/NoteMapper.php index c8ca2a0..9e9504a 100644 --- a/lib/Db/NoteMapper.php +++ b/lib/Db/NoteMapper.php @@ -31,6 +31,34 @@ class NoteMapper extends QBMapper { return $this->findEntity($qb); } + /** + * @param string $userId + * @param string $queryStr + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * @return Note[] + */ + public function findLike($userId, $queryStr, $offset = null, $limit = null) { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->tableName) + ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))) + ->andWhere( + $qb->expr()->orX( + $qb->expr()->like($qb->func()->lower('title'), $qb->createParameter('query')), + $qb->expr()->like($qb->func()->lower('content'), $qb->createParameter('query')) + ) + ); + + $query = '%' . $this->db->escapeLikeParameter(strtolower($queryStr)) . '%'; + $qb->setParameter('query', $query); + + $qb->setFirstResult($offset); + $qb->setMaxResults($limit); + + return $this->findEntities($qb); + } + /** * @param int $id * @throws \OCP\AppFramework\Db\DoesNotExistException if not found diff --git a/lib/Search/NoteSearchProvider.php b/lib/Search/NoteSearchProvider.php new file mode 100644 index 0000000..a8baee8 --- /dev/null +++ b/lib/Search/NoteSearchProvider.php @@ -0,0 +1,114 @@ + + * + * @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\Search; + +use OCP\Search\IProvider; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\Search\ISearchQuery; +use OCP\Search\SearchResult; +use OCP\Search\SearchResultEntry; + +use OCA\QuickNotes\Db\Note; +use OCA\QuickNotes\Db\NoteMapper; + +/** + * Provide search results from the 'quicknotes' app + */ +class NoteSearchProvider implements IProvider { + + /** @var NoteMapper noteMapper */ + private $noteMapper; + + /** @var IL10N */ + private $l10n; + + /** @var IURLGenerator */ + private $urlGenerator; + + public function __construct( + IL10N $l10n, + IURLGenerator $urlGenerator, + NoteMapper $noteMapper + ) { + $this->l10n = $l10n; + $this->urlGenerator = $urlGenerator; + $this->noteMapper = $noteMapper; + } + + /** + * @inheritDoc + */ + public function getId(): string { + return 'quicknotes'; + } + + /** + * @inheritDoc + */ + public function getName(): string { + return $this->l10n->t('Quick notes'); + } + + /** + * @inheritDoc + */ + public function getOrder(string $route, array $routeParameters): int { + if (strpos($route, 'quicknotes.') === 0) { + return -1; + } + return 10; + } + + /** + * @inheritDoc + */ + public function search(IUser $user, ISearchQuery $query) : SearchResult { + $page = $query->getCursor() ?? 0; + $limit = $query->getLimit(); + return SearchResult::paginated( + $this->l10n->t('Quick notes'), + array_map(function (Note $result) { + $noteId = $result->getId(); + $noteTitle = strip_tags($result->getTitle()); + return new SearchResultEntry( + '', + $noteTitle, + '', + $this->urlGenerator->linkToRoute('quicknotes.page.index', ['n' => $noteId]), + 'icon-edit', + true, + ); + }, + $this->noteMapper->findLike($user->getUID(), + $query->getTerm(), + $page * $limit, + $limit) + ), + $page); + } + +}