diff --git a/.eslintrc.json b/.eslintrc.json index 1506633d..9631017e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -15,6 +15,13 @@ "sourceType": "module" }, "rules": { + "comma-dangle": ["error", { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never" + }], "semi": ["error", "always"], "indent": ["error", 4] } diff --git a/locales/fr_FR/LC_MESSAGES/main.mo b/locales/fr_FR/LC_MESSAGES/main.mo index 0de89ff4..a8342522 100644 Binary files a/locales/fr_FR/LC_MESSAGES/main.mo and b/locales/fr_FR/LC_MESSAGES/main.mo differ diff --git a/locales/fr_FR/LC_MESSAGES/main.po b/locales/fr_FR/LC_MESSAGES/main.po index 5cb42615..17d2e32a 100644 --- a/locales/fr_FR/LC_MESSAGES/main.po +++ b/locales/fr_FR/LC_MESSAGES/main.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: flusio\n" -"POT-Creation-Date: 2020-07-17 12:23+0200\n" -"PO-Revision-Date: 2020-07-17 12:23+0200\n" +"POT-Creation-Date: 2020-07-21 15:45+0200\n" +"PO-Revision-Date: 2020-07-21 15:58+0200\n" "Last-Translator: Marien Fressinaud \n" "Language-Team: \n" "Language: fr_FR\n" @@ -35,7 +35,8 @@ msgstr "Désolé mais vous ne pouvez pas supprimer le compte de démo 😉" msgid "This collection doesn’t exist." msgstr "Cette collection n’existe pas." -#: Collections.php:288 Links.php:369 Links.php:557 Sessions.php:191 +#: Collections.php:288 Links.php:369 Links.php:550 Links.php:596 +#: Sessions.php:191 msgid "A security verification failed." msgstr "Une vérification de sécurité a échoué." @@ -55,9 +56,9 @@ msgstr "Le lien doit être associé à une collection." msgid "One of the associated collection doesn’t exist." msgstr "L’une des collections associées n’existe pas." -#: Links.php:551 -msgid "This link-collection relation doesn’t exist." -msgstr "La relation lien-collection n’existe pas." +#: Links.php:544 Links.php:590 +msgid "The link doesn’t exist." +msgstr "Le lien n’existe pas." #: Registrations.php:107 msgid "An account already exists with this email address." @@ -85,6 +86,18 @@ msgstr "Cacher" msgid "Show" msgstr "Afficher" +#: assets/javascripts/controllers/link_card_controller.js:35 +#: assets/javascripts/controllers/link_card_controller.js:36 +#: views/collections/show_bookmarks.phtml:56 +#: views/collections/show_bookmarks.phtml:57 +msgid "Remove from bookmarks" +msgstr "Retirer des signets" + +#: assets/javascripts/controllers/link_card_controller.js:52 +#: assets/javascripts/controllers/link_card_controller.js:53 +msgid "Add to bookmarks" +msgstr "Ajouter aux signets" + #: mailers/Users.php:9 msgid "[flusio] Confirm your registration" msgstr "[flusio] Confirmer votre inscription" @@ -358,8 +371,8 @@ msgid_plural "%d links" msgstr[0] "%d lien" msgstr[1] "%d liens" -#: views/collections/index.phtml:38 views/collections/show.phtml:72 -#: views/collections/show_bookmarks.phtml:55 +#: views/collections/index.phtml:38 views/collections/show.phtml:59 +#: views/collections/show_bookmarks.phtml:64 msgid "see" msgstr "voir" @@ -403,12 +416,7 @@ msgstr "Annuler et retourner aux collections" msgid "Settings" msgstr "Paramètres" -#: views/collections/show.phtml:64 views/collections/show.phtml:65 -#, php-format -msgid "Remove from %s" -msgstr "Retirer de %s" - -#: views/collections/show.phtml:81 views/collections/show_bookmarks.phtml:64 +#: views/collections/show.phtml:68 views/collections/show_bookmarks.phtml:73 msgid "" "This collection is empty. The links will appear here once you start adding " "them." @@ -420,11 +428,6 @@ msgstr "" msgid "Place here the links you want to consult later on." msgstr "Placez ici les liens que vous souhaitez consulter plus tard." -#: views/collections/show_bookmarks.phtml:47 -#: views/collections/show_bookmarks.phtml:48 -msgid "Remove from bookmarks" -msgstr "Retirer des signets" - #: views/internal_server_error.phtml:3 msgid "Something bad happened" msgstr "Quelque chose s’est mal passé" diff --git a/src/Application.php b/src/Application.php index da260af9..ee4e5023 100644 --- a/src/Application.php +++ b/src/Application.php @@ -80,12 +80,8 @@ public function __construct() $router->addRoute('post', '/links/:id/fetch', 'Links#fetch', 'fetch link'); $router->addRoute('get', '/links/:id/collections', 'Links#collections', 'link collections'); $router->addRoute('post', '/links/:id/collections', 'Links#updateCollections', 'update link collections'); - $router->addRoute( - 'post', - '/links/:id/remove_collection', - 'Links#removeCollection', - 'remove link collection' - ); + $router->addRoute('post', '/links/:id/bookmark', 'Links#bookmark', 'bookmark link'); + $router->addRoute('post', '/links/:id/unbookmark', 'Links#unbookmark', 'unbookmark link'); // This should be used only for source mapping $router->addRoute('get', '/src/assets/*', 'Assets#show'); diff --git a/src/Links.php b/src/Links.php index 1772dc53..f76adf66 100644 --- a/src/Links.php +++ b/src/Links.php @@ -513,16 +513,15 @@ public function updateCollections($request) } /** - * Remove a link from a collection. + * Add a link to the bookmarks * * @request_param string csrf * @request_param string from (default is /bookmarks) * @request_param string id - * @request_param string collection_id * * @response 302 /login?redirect_to=:from if not connected - * @response 404 if the link or collection (or their relation) don't exist, - * or are not associated to the current user + * @response 404 if the link doesn't exist, or is not associated to the + * current user * @response 302 :from if CSRF is invalid * @response 302 :from on success * @@ -530,25 +529,78 @@ public function updateCollections($request) * * @return \Minz\Response */ - public function removeCollection($request) + public function bookmark($request) { $user = utils\CurrentUser::get(); $from = $request->param('from', \Minz\Url::for('bookmarks')); $link_id = $request->param('id'); - $collection_id = $request->param('collection_id'); if (!$user) { return Response::redirect('login', ['redirect_to' => $from]); } + $link = $user->link($link_id); + if (!$link) { + utils\Flash::set('error', _('The link doesn’t exist.')); + return Response::found($from); + } + + $bookmarks = $user->bookmarks(); + $actual_collection_ids = array_column($link->collections(), 'id'); + if (in_array($bookmarks->id, $actual_collection_ids)) { + utils\Flash::set('error', _('This link is already bookmarked.')); + return Response::found($from); + } + + $csrf = new \Minz\CSRF(); + if (!$csrf->validateToken($request->param('csrf'))) { + utils\Flash::set('error', _('A security verification failed.')); + return Response::found($from); + } + $links_to_collections_dao = new models\dao\LinksToCollections(); - $db_link_to_collection = $links_to_collections_dao->findRelation( - $user->id, - $link_id, - $collection_id - ); - if (!$db_link_to_collection) { - utils\Flash::set('error', _('This link-collection relation doesn’t exist.')); + $links_to_collections_dao->attach($link->id, [$bookmarks->id]); + + return Response::found($from); + } + + /** + * Remove a link from bookmarks + * + * @request_param string csrf + * @request_param string from (default is /bookmarks) + * @request_param string id + * + * @response 302 /login?redirect_to=:from if not connected + * @response 302 :from if the link doesn't exist, or is not associated to the + * current user, or not in the bookmarks + * @response 302 :from if CSRF is invalid + * @response 302 :from on success + * + * @param \Minz\Request $request + * + * @return \Minz\Response + */ + public function unbookmark($request) + { + $user = utils\CurrentUser::get(); + $from = $request->param('from', \Minz\Url::for('bookmarks')); + $link_id = $request->param('id'); + + if (!$user) { + return Response::redirect('login', ['redirect_to' => $from]); + } + + $link = $user->link($link_id); + if (!$link) { + utils\Flash::set('error', _('The link doesn’t exist.')); + return Response::found($from); + } + + $bookmarks = $user->bookmarks(); + $actual_collection_ids = array_column($link->collections(), 'id'); + if (!in_array($bookmarks->id, $actual_collection_ids)) { + utils\Flash::set('error', _('This link is not bookmarked.')); return Response::found($from); } @@ -558,7 +610,8 @@ public function removeCollection($request) return Response::found($from); } - $links_to_collections_dao->delete($db_link_to_collection['id']); + $links_to_collections_dao = new models\dao\LinksToCollections(); + $links_to_collections_dao->detach($link->id, [$bookmarks->id]); return Response::found($from); } diff --git a/src/assets/javascripts/application.js b/src/assets/javascripts/application.js index 9ab84564..c2dc2c72 100644 --- a/src/assets/javascripts/application.js +++ b/src/assets/javascripts/application.js @@ -4,6 +4,7 @@ import { Application } from 'stimulus'; import ConfirmationController from 'js/controllers/confirmation_controller.js'; import InputPasswordController from 'js/controllers/input_password_controller.js'; import FormAutosubmitController from 'js/controllers/form_autosubmit_controller.js'; +import LinkCardController from 'js/controllers/link_card_controller.js'; import LinkFetcherController from 'js/controllers/link_fetcher_controller.js'; import PopupController from 'js/controllers/popup_controller.js'; @@ -15,5 +16,6 @@ const application = Application.start(); application.register('confirmation', ConfirmationController); application.register('input-password', InputPasswordController); application.register('form-autosubmit', FormAutosubmitController); +application.register('link-card', LinkCardController); application.register('link-fetcher', LinkFetcherController); application.register('popup', PopupController); diff --git a/src/assets/javascripts/controllers/link_card_controller.js b/src/assets/javascripts/controllers/link_card_controller.js new file mode 100644 index 00000000..9c639625 --- /dev/null +++ b/src/assets/javascripts/controllers/link_card_controller.js @@ -0,0 +1,60 @@ +import { Controller } from 'stimulus'; + +import _ from 'js/l10n.js'; + +export default class extends Controller { + static get targets () { + return ['bookmarkForm']; + } + + toggleBookmarked (event) { + event.preventDefault(); + + const isBookmarked = this.data.get('bookmarked') === 'true'; + if (isBookmarked) { + this.unbookmark(); + } else { + this.bookmark(); + } + } + + bookmark () { + const action = this.data.get('bookmark-action'); + const card = this.element; + const form = this.bookmarkFormTarget; + const button = form.querySelector('button'); + const icon = form.querySelector('.icon--bookmark'); + + fetch(action, { + method: 'post', + body: new FormData(form), + }).then(() => { + this.data.set('bookmarked', 'true'); + }); + + card.classList.remove('link--transparent'); + icon.classList.add('icon--solid'); + button.title = _('Remove from bookmarks'); + button.setAttribute('aria-label', _('Remove from bookmarks')); + } + + unbookmark () { + const action = this.data.get('unbookmark-action'); + const card = this.element; + const form = this.bookmarkFormTarget; + const button = form.querySelector('button'); + const icon = form.querySelector('.icon--bookmark'); + + fetch(action, { + method: 'post', + body: new FormData(form), + }).then(() => { + this.data.set('bookmarked', 'false'); + }); + + card.classList.add('link--transparent'); + icon.classList.remove('icon--solid'); + button.title = _('Add to bookmarks'); + button.setAttribute('aria-label', _('Add to bookmarks')); + } +}; diff --git a/src/assets/stylesheets/components/links.css b/src/assets/stylesheets/components/links.css index 6bcba86f..70a1c6eb 100644 --- a/src/assets/stylesheets/components/links.css +++ b/src/assets/stylesheets/components/links.css @@ -16,6 +16,8 @@ flex-grow: 1; box-shadow: 0 1px 4px 1px var(--color-grey-2); + + transition: opacity 0.2s ease-in-out; } @media (min-width: 800px) { @@ -24,6 +26,10 @@ } } +.link--transparent { + opacity: 0.5; +} + .link__body { padding: var(--space-small); diff --git a/src/models/dao/LinksToCollections.php b/src/models/dao/LinksToCollections.php index 8192acf7..a3379486 100644 --- a/src/models/dao/LinksToCollections.php +++ b/src/models/dao/LinksToCollections.php @@ -73,36 +73,4 @@ public function detach($link_id, $collection_ids) $statement = $this->prepare($sql); return $statement->execute($values); } - - /** - * Find a relation between a link and a collection for the given user. - * - * @param string $user_id - * @param string $link_id - * @param string $collection_id - * - * return array|null - */ - public function findRelation($user_id, $link_id, $collection_id) - { - $sql = <<<'SQL' - SELECT * FROM links_to_collections - WHERE link_id = ( - SELECT id FROM links - WHERE id = ? AND user_id = ? - ) AND collection_id = ( - SELECT id FROM collections - WHERE id = ? AND user_id = ? - ); - SQL; - - $statement = $this->prepare($sql); - $statement->execute([$link_id, $user_id, $collection_id, $user_id]); - $result = $statement->fetch(); - if ($result) { - return $result; - } else { - return null; - } - } } diff --git a/src/utils/javascript_configuration.php b/src/utils/javascript_configuration.php index 51f0f2dc..5c7d064d 100644 --- a/src/utils/javascript_configuration.php +++ b/src/utils/javascript_configuration.php @@ -1,7 +1,9 @@