From 42763dadf5aafe4e663f800343394230bace8505 Mon Sep 17 00:00:00 2001 From: Jan <96944229+modelrailroader@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:52:34 +0200 Subject: [PATCH] feat: improved bookmark management --- phpmyfaq/assets/src/api/bookmarks.js | 62 +++++++++---------- phpmyfaq/assets/src/faq/faq.js | 36 ++++++++++- phpmyfaq/assets/src/frontend.js | 17 ++++- phpmyfaq/assets/src/user/bookmarks.js | 38 ++++++++++++ phpmyfaq/assets/src/user/index.js | 1 + phpmyfaq/assets/templates/bookmarks.twig | 52 ++++++++++++++-- phpmyfaq/assets/templates/faq.twig | 32 +++++++++- phpmyfaq/bookmarks.php | 2 + phpmyfaq/faq.php | 16 +++-- phpmyfaq/src/api-routes.php | 11 +++- phpmyfaq/src/phpMyFAQ/Bookmark.php | 3 +- .../Frontend/BookmarkController.php | 38 +++++++++++- phpmyfaq/translations/language_de.php | 4 ++ phpmyfaq/translations/language_en.php | 4 ++ phpmyfaq/translations/language_pl.php | 2 + tests/phpMyFAQ/BookmarkTest.php | 10 +-- 16 files changed, 264 insertions(+), 64 deletions(-) create mode 100644 phpmyfaq/assets/src/user/bookmarks.js diff --git a/phpmyfaq/assets/src/api/bookmarks.js b/phpmyfaq/assets/src/api/bookmarks.js index 55ae1122d1..8f6a00825b 100644 --- a/phpmyfaq/assets/src/api/bookmarks.js +++ b/phpmyfaq/assets/src/api/bookmarks.js @@ -13,38 +13,36 @@ * @since 2023-09-19 */ -export const handleBookmarks = () => { - const bookmarkTrashIcons = document.querySelectorAll('.pmf-delete-bookmark'); - - if (bookmarkTrashIcons) { - bookmarkTrashIcons.forEach((element) => { - element.addEventListener('click', async (event) => { - event.preventDefault(); - const bookmarkId = event.target.getAttribute('data-pmf-bookmark-id'); - - try { - const response = await fetch(`api/bookmark/${bookmarkId}`, { - method: 'DELETE', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - redirect: 'follow', - referrerPolicy: 'no-referrer', - }); - - if (!response.ok) { - throw new Error('Network response was not ok'); - } +export const addBookmark = async (faqId) => { + try { + const response = await fetch(`api/bookmark/add/${faqId}`, { + method: 'POST', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + return await response.json(); + } catch (error) { + console.error('Error adding bookmark:', error); + } +} - const responseData = await response.json(); - const bookmarkToDelete = document.getElementById(`delete-bookmark-${bookmarkId}`); - bookmarkToDelete.remove(); - } catch (error) { - // Handle error here - console.error('Error deleting bookmark:', error); - } - }); +export const removeBookmark = async (faqId) => { + try { + const response = await fetch(`api/bookmark/remove/${faqId}`, { + method: 'DELETE', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', }); + return await response.json(); + } catch (error) { + console.error('Error removing bookmark:', error); } -}; +} diff --git a/phpmyfaq/assets/src/faq/faq.js b/phpmyfaq/assets/src/faq/faq.js index c8e6f9df18..b6a19cc51d 100644 --- a/phpmyfaq/assets/src/faq/faq.js +++ b/phpmyfaq/assets/src/faq/faq.js @@ -1,5 +1,6 @@ import { addElement } from '../utils'; -import { createFaq } from '../api'; +import { addBookmark, createFaq, handleBookmarks, removeBookmark } from '../api'; +import { pushErrorNotification, pushNotification } from '../../../admin/assets/src/utils'; export const handleAddFaq = () => { const addFaqSubmit = document.getElementById('pmf-submit-faq'); @@ -40,3 +41,36 @@ export const handleAddFaq = () => { }); } }; + +export const handleShowFaq = async () => { + const bookmarkToggle = document.getElementById('pmf-bookmark-toggle'); + if (bookmarkToggle) { + bookmarkToggle.addEventListener('click', async (event) => { + event.preventDefault(); + event.stopPropagation(); + if (bookmarkToggle.getAttribute('data-pmf-action') === 'remove') { + const response = await removeBookmark(bookmarkToggle.getAttribute('data-pmf-id')); + if (response.success) { + pushNotification(response.success); + document.getElementById('pmf-bookmark-icon').classList.remove('bi-bookmark-fill'); + document.getElementById('pmf-bookmark-icon').classList.add('bi-bookmark'); + bookmarkToggle.innerText = response.linkText; + bookmarkToggle.setAttribute('data-pmf-action', 'add'); + } else { + pushErrorNotification(response.error); + } + } else { + const response = await addBookmark(bookmarkToggle.getAttribute('data-pmf-id')); + if (response.success) { + pushNotification(response.success); + document.getElementById('pmf-bookmark-icon').classList.remove('bi-bookmark'); + document.getElementById('pmf-bookmark-icon').classList.add('bi-bookmark-fill'); + bookmarkToggle.innerText = response.linkText; + bookmarkToggle.setAttribute('data-pmf-action', 'remove'); + } else { + pushErrorNotification(response.error); + } + } + }); + } +}; diff --git a/phpmyfaq/assets/src/frontend.js b/phpmyfaq/assets/src/frontend.js index 9e5e172fd8..25387d4c5e 100644 --- a/phpmyfaq/assets/src/frontend.js +++ b/phpmyfaq/assets/src/frontend.js @@ -16,13 +16,19 @@ import 'bootstrap'; import Masonry from 'masonry-layout'; -import { handleBookmarks } from './api'; import { handleContactForm } from './contact'; -import { handleAddFaq, handleComments, handleSaveComment, handleShare, handleUserVoting } from './faq'; +import { handleAddFaq, handleComments, handleSaveComment, handleShare, handleShowFaq, handleUserVoting } from './faq'; import { handleAutoComplete, handleQuestion } from './search'; -import { handleRegister, handleRequestRemoval, handleUserControlPanel, handleUserPassword } from './user'; +import { + handleBookmarks, + handleRegister, + handleRequestRemoval, + handleUserControlPanel, + handleUserPassword, +} from './user'; import { calculateReadingTime, handlePasswordStrength, handlePasswordToggle, handleReloadCaptcha } from './utils'; import './utils/tooltip'; +import { initializeTooltips } from '../../admin/assets/src/utils'; // // Reload Captchas @@ -67,6 +73,11 @@ handleShare(); // handleAddFaq(); +// +// Handle show FAQ +// +handleShowFaq(); + // // Handle Add a Question // diff --git a/phpmyfaq/assets/src/user/bookmarks.js b/phpmyfaq/assets/src/user/bookmarks.js new file mode 100644 index 0000000000..c905bae9f9 --- /dev/null +++ b/phpmyfaq/assets/src/user/bookmarks.js @@ -0,0 +1,38 @@ +/** + * Handle bookmarks page + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + * + * @package phpMyFAQ + * @author Thorsten Rinne + * @author Jan Harms + * @copyright 2023-2024 phpMyFAQ Team + * @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 + * @link https://www.phpmyfaq.de + * @since 2024-07-03 + */ + +import { removeBookmark } from '../api'; +import { pushErrorNotification, pushNotification } from '../../../admin/assets/src/utils'; + +export const handleBookmarks = () => { + const bookmarkTrashIcons = document.querySelectorAll('.pmf-delete-bookmark'); + if (bookmarkTrashIcons) { + bookmarkTrashIcons.forEach((element) => { + element.addEventListener('click', async (event) => { + event.preventDefault(); + const bookmarkId = event.target.getAttribute('data-pmf-bookmark-id'); + const bookmarkToDelete = document.getElementById(`delete-bookmark-${bookmarkId}`); + bookmarkToDelete.remove(); + const response = await removeBookmark(bookmarkId); + if (response.success) { + pushNotification(response.success); + } else { + pushErrorNotification(response.error); + } + }); + }); + } +}; diff --git a/phpmyfaq/assets/src/user/index.js b/phpmyfaq/assets/src/user/index.js index 14e118ada0..29e53ca4b5 100644 --- a/phpmyfaq/assets/src/user/index.js +++ b/phpmyfaq/assets/src/user/index.js @@ -2,3 +2,4 @@ export * from './password'; export * from './register'; export * from './request-removal'; export * from './ucp'; +export * from './bookmarks'; diff --git a/phpmyfaq/assets/templates/bookmarks.twig b/phpmyfaq/assets/templates/bookmarks.twig index c72714f732..4fea752087 100644 --- a/phpmyfaq/assets/templates/bookmarks.twig +++ b/phpmyfaq/assets/templates/bookmarks.twig @@ -1,15 +1,59 @@ + +
+ + + +
+

{{ msgMyBookmarks }}

-
+
+ {% set count = 1 %} {% for bookmark in bookmarksList %} - + + + {% set count = count + 1 %} {% endfor %}
diff --git a/phpmyfaq/assets/templates/faq.twig b/phpmyfaq/assets/templates/faq.twig index d8e3543043..a283a742ae 100644 --- a/phpmyfaq/assets/templates/faq.twig +++ b/phpmyfaq/assets/templates/faq.twig @@ -1,3 +1,28 @@ + +
+ + + +
+

{{ question }}

@@ -38,15 +63,16 @@ {% endif %} {% if userId != -1 %}
  • - - + + {{ msgAddBookmark }}
  • {% endif %}
  • - + {{ msgPdf }}
  • diff --git a/phpmyfaq/bookmarks.php b/phpmyfaq/bookmarks.php index fe54688815..c2c6242fec 100644 --- a/phpmyfaq/bookmarks.php +++ b/phpmyfaq/bookmarks.php @@ -41,6 +41,8 @@ $templateVars = [ 'msgMyBookmarks' => Translation::get('msgMyBookmarks'), 'bookmarksList' => $bookmark->getBookmarkList(), + 'removeBookmark' => Translation::get('removeBookmark'), + 'msgLinkToFAQ' => Translation::get('msgLinkToFAQ') ]; $template->addRenderedTwigOutput( diff --git a/phpmyfaq/faq.php b/phpmyfaq/faq.php index 11205c6cc5..f12d0111cd 100644 --- a/phpmyfaq/faq.php +++ b/phpmyfaq/faq.php @@ -87,7 +87,7 @@ // Handle bookmarks $bookmark = new Bookmark($faqConfig, $user); if ($bookmarkAction === 'add' && isset($faqId)) { - $bookmark->saveFaqAsBookmarkById($faqId); + $bookmark->add($faqId); } if ($bookmarkAction === 'remove' && isset($faqId)) { @@ -221,14 +221,9 @@ $templateVars = [ ...$templateVars, 'bookmarkIcon' => $bookmark->isFaqBookmark($faqId) ? 'bi bi-bookmark-fill' : 'bi bi-bookmark', - 'bookmarkLink' => - $bookmark->isFaqBookmark($faqId) - ? - sprintf('index.php?action=faq&bookmark_action=remove&id=%d', $faqId) - : - sprintf('index.php?action=faq&bookmark_action=add&id=%d', $faqId), 'msgAddBookmark' => - $bookmark->isFaqBookmark($faqId) ? Translation::get('removeBookmark') : Translation::get('msgAddBookmark') + $bookmark->isFaqBookmark($faqId) ? Translation::get('removeBookmark') : Translation::get('msgAddBookmark'), + 'isFaqBookmark' => $bookmark->isFaqBookmark($faqId) ]; } @@ -307,7 +302,7 @@ 'msgPdf' => Translation::get('msgPDF'), 'msgPrintFaq' => Translation::get('msgPrintArticle'), 'suggestLink' => $faqServices->getSuggestLink(), - 'enableSendToFriend' => $this->configuration->get('main.enableSendToFriend'), + 'enableSendToFriend' => $faqConfig->get('main.enableSendToFriend'), 'msgShareViaWhatsappText' => Translation::get('msgShareViaWhatsappText'), 'msgShareViaWhatsapp' => Translation::get('msgShareViaWhatsapp'), 'msgSend2Friend' => Translation::get('msgSend2Friend'), @@ -344,6 +339,9 @@ 'permissionEditFaq' => $user->perm->hasPermission($user->getUserId(), PermissionType::FAQ_EDIT->value), 'ad_entry_edit_1' => Translation::get('ad_entry_edit_1'), 'ad_entry_edit_2' => Translation::get('ad_entry_edit_2'), + 'bookmarkAction' => $bookmarkAction ?? '', + 'msgBookmarkAdded' => Translation::get('msgBookmarkAdded'), + 'msgBookmarkRemoved' => Translation::get('msgBookmarkRemoved') ]; $twig = new TwigWrapper(PMF_ROOT_DIR . '/assets/templates'); diff --git a/phpmyfaq/src/api-routes.php b/phpmyfaq/src/api-routes.php index d4758fdd7e..3ae1816df9 100644 --- a/phpmyfaq/src/api-routes.php +++ b/phpmyfaq/src/api-routes.php @@ -201,11 +201,16 @@ 'controller' => [AutoCompleteController::class, 'search'], 'methods' => 'GET' ], - 'api.private.bookmark' => [ - 'path' => 'bookmark/{bookmarkId}', - 'controller' => [BookmarkController::class, 'delete'], + 'api.private.bookmark.remove' => [ + 'path' => 'bookmark/remove/{bookmarkId}', + 'controller' => [BookmarkController::class, 'remove'], 'methods' => 'DELETE' ], + 'api.private.bookmark.add' => [ + 'path' => 'bookmark/add/{bookmarkId}', + 'controller' => [BookmarkController::class, 'add'], + 'methods' => 'POST' + ], 'api.private.captcha' => [ 'path' => 'captcha', 'controller' => [CaptchaController::class, 'renderImage'], diff --git a/phpmyfaq/src/phpMyFAQ/Bookmark.php b/phpmyfaq/src/phpMyFAQ/Bookmark.php index 445053b22c..6fb4ca2ff1 100644 --- a/phpmyfaq/src/phpMyFAQ/Bookmark.php +++ b/phpmyfaq/src/phpMyFAQ/Bookmark.php @@ -60,7 +60,7 @@ public function isFaqBookmark(int $faqId): bool * * @param int $faqId ID of the Faq */ - public function saveFaqAsBookmarkById(int $faqId): bool + public function add(int $faqId): bool { $query = sprintf( "INSERT INTO %sfaqbookmarks(userid, faqid) VALUES (%d, %d)", @@ -130,6 +130,7 @@ public function getBookmarkList(): array 'url' => $link->toString(), 'title' => htmlspecialchars_decode((string) $faqData['title']), 'id' => $faqData['id'], + 'answer' => $faqData['content'] ]; } diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Frontend/BookmarkController.php b/phpmyfaq/src/phpMyFAQ/Controller/Frontend/BookmarkController.php index 71b86b5d74..fe41c62960 100644 --- a/phpmyfaq/src/phpMyFAQ/Controller/Frontend/BookmarkController.php +++ b/phpmyfaq/src/phpMyFAQ/Controller/Frontend/BookmarkController.php @@ -21,6 +21,7 @@ use phpMyFAQ\Controller\AbstractController; use phpMyFAQ\Core\Exception; use phpMyFAQ\Filter; +use phpMyFAQ\Translation; use phpMyFAQ\User\CurrentUser; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -31,8 +32,8 @@ class BookmarkController extends AbstractController /** * @throws Exception */ - #[Route('api/bookmark')] - public function delete(Request $request): JsonResponse + #[Route('api/bookmark/remove')] + public function remove(Request $request): JsonResponse { $this->userIsAuthenticated(); @@ -42,6 +43,37 @@ public function delete(Request $request): JsonResponse $bookmark = new Bookmark($this->configuration, $currentUser); - return $this->json(['success' => $bookmark->remove($id)], JsonResponse::HTTP_OK); + if ($bookmark->remove($id)) { + return $this->json([ + 'success' => Translation::get('msgBookmarkRemoved'), + 'linkText' => Translation::get('msgAddBookmark') + ], JsonResponse::HTTP_OK); + } else { + return $this->json(['error' => Translation::get('msgError')], JsonResponse::HTTP_BAD_REQUEST); + } + } + + /** + * @throws Exception + */ + #[Route('api/bookmark/add')] + public function add(Request $request): JsonResponse + { + $this->userIsAuthenticated(); + + $id = Filter::filterVar($request->get('bookmarkId'), FILTER_VALIDATE_INT); + + $currentUser = CurrentUser::getCurrentUser($this->configuration); + + $bookmark = new Bookmark($this->configuration, $currentUser); + + if ($bookmark->add($id)) { + return $this->json([ + 'success' => Translation::get('msgBookmarkAdded'), + 'linkText' => Translation::get('removeBookmark') + ], JsonResponse::HTTP_OK); + } else { + return $this->json(['error' => Translation::get('msgError')], JsonResponse::HTTP_BAD_REQUEST); + } } } diff --git a/phpmyfaq/translations/language_de.php b/phpmyfaq/translations/language_de.php index 792469f864..231f9b1663 100755 --- a/phpmyfaq/translations/language_de.php +++ b/phpmyfaq/translations/language_de.php @@ -1349,6 +1349,8 @@ $PMF_LANG['removeBookmark'] = 'Lesezeichen entfernen'; $PMF_LANG['msgBookmarks'] = 'Lesezeichen'; $PMF_LANG['msgMyBookmarks'] = 'Meine Lesezeichen'; +$PMF_LANG['msgBookmarkAdded'] = 'Lesezeichen erfolgreich hinzugefügt!'; +$PMF_LANG['msgBookmarkRemoved'] = 'Lesezeichen erfolgreich entfernt!'; // added v4.0.0-alpha - 2023-09-20 by Jan $PMF_LANG['msgNoHashAllowed'] = "Die Frage darf '#' nicht enthalten."; @@ -1442,6 +1444,8 @@ $PMF_LANG['msgRemoveTwofactorConfigSuccessful'] = 'Das Löschen der aktuellen 2-Faktor-Konfiguration war erfolgreich.'; $PMF_LANG['msgShareViaWhatsapp'] = 'Per WhatsApp empfehlen'; $PMF_LANG['msgShareViaWhatsappText'] = 'Ich habe einen spannenden FAQ-Beitrag für dich entdeckt:'; +$PMF_LANG['msgError'] = 'Es ist ein Fehler aufgetreten.'; +$PMF_LANG['msgLinkToFAQ'] = 'Link zum FAQ'; // added v4.0.0-alpha.3 - 2024-06-30 by Thorsten $LANG_CONF['main.enablePrivacyLink'] = ['checkbox', 'Aktiviere Link zur Datenschutzerklärung']; diff --git a/phpmyfaq/translations/language_en.php b/phpmyfaq/translations/language_en.php index aab38d98d0..42f2b84f8f 100644 --- a/phpmyfaq/translations/language_en.php +++ b/phpmyfaq/translations/language_en.php @@ -1370,6 +1370,8 @@ $PMF_LANG['removeBookmark'] = 'Remove bookmark'; $PMF_LANG['msgBookmarks'] = 'Bookmarks'; $PMF_LANG['msgMyBookmarks'] = 'My Bookmarks'; +$PMF_LANG['msgBookmarkAdded'] = 'Bookmark successfully added!'; +$PMF_LANG['msgBookmarkRemoved'] = 'Bookmark successfully removed!'; // added v4.0.0-alpha - 2023-09-20 by Jan $PMF_LANG['msgNoHashAllowed'] = "It is not allowed to use '#'."; @@ -1463,6 +1465,8 @@ $PMF_LANG['msgRemoveTwofactorConfigSuccessful'] = 'The deletion of the current 2-factor configuration was successful.'; $PMF_LANG['msgShareViaWhatsapp'] = 'Send to a friend via WhatsApp'; $PMF_LANG['msgShareViaWhatsappText'] = 'I discovered a cool faq-article for you:'; +$PMF_LANG['msgError'] = 'An error occurred.'; +$PMF_LANG['msgLinkToFAQ'] = 'Link to FAQ'; // added v4.0.0-alpha.3 - 2024-06-30 by Thorsten $LANG_CONF['main.enablePrivacyLink'] = ['checkbox', 'Activate link to privacy policy']; diff --git a/phpmyfaq/translations/language_pl.php b/phpmyfaq/translations/language_pl.php index 86ab63e5f0..a152ef60b3 100644 --- a/phpmyfaq/translations/language_pl.php +++ b/phpmyfaq/translations/language_pl.php @@ -1359,6 +1359,8 @@ // added v4.0.0-alpha - 2023-07-19 by Jan $PMF_LANG['msgAddBookmark'] = 'Dodaj zakładkę'; $PMF_LANG['removeBookmark'] = 'Usuń zakładkę'; +$PMF_LANG['msgBookmarkAdded'] = 'Zakładka została pomyślnie dodana!'; +$PMF_LANG['msgBookmarkRemoved'] = 'Zakładka została pomyślnie usunięta!'; // added v4.0.0-alpha - 2023-07-11 by Jan $PMF_LANG['headerCheckHealth'] = '1. Sprawdź stan systemu'; diff --git a/tests/phpMyFAQ/BookmarkTest.php b/tests/phpMyFAQ/BookmarkTest.php index b172a1d8e6..5fa2cf647b 100644 --- a/tests/phpMyFAQ/BookmarkTest.php +++ b/tests/phpMyFAQ/BookmarkTest.php @@ -35,7 +35,7 @@ protected function setUp(): void public function testSaveFaqAsBookmarkById(): void { - $result = $this->bookmark->saveFaqAsBookmarkById(1); + $result = $this->bookmark->add(1); $this->assertTrue($result); // Clean up @@ -44,7 +44,7 @@ public function testSaveFaqAsBookmarkById(): void public function testIsFaqBookmark(): void { - $this->bookmark->saveFaqAsBookmarkById(1); + $this->bookmark->add(1); $result = $this->bookmark->isFaqBookmark(1); $this->assertTrue($result); @@ -54,7 +54,7 @@ public function testIsFaqBookmark(): void public function testGetAll(): void { - $this->bookmark->saveFaqAsBookmarkById(1); + $this->bookmark->add(1); $result = $this->bookmark->getAll(); $this->assertIsArray($result); $this->assertEquals(1, count($result)); @@ -65,13 +65,13 @@ public function testGetAll(): void public function testRemove(): void { - $this->bookmark->saveFaqAsBookmarkById(1); + $this->bookmark->add(1); $this->assertTrue($this->bookmark->remove(1)); } public function testRenderBookmarkTree(): void { - $this->bookmark->saveFaqAsBookmarkById(1); + $this->bookmark->add(1); $result = $this->bookmark->getBookmarkList(); $this->assertIsArray($result); $this->assertEquals(1, count($result));