diff --git a/public/assets/css/app.css b/public/assets/css/app.css index b8783aaa8a52897be003770a110f04cb3c3192fa..09d1537538acb2def9c23555e020096e9de4ec86 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -200,15 +200,23 @@ table.label-table > tbody > tr > td { } .nav-tabs .nav-link { color: black; - border-bottom: 0; } .nav-tabs a.nav-link.active { font-weight: bold; } +.nav-tabs a.nav-link.active, .nav-tabs a.nav-link.active:hover, .nav-tabs a.nav-link.active:focus { + border-color: #767676; + border-bottom-color: transparent; +} +.nav-tabs, .nav-tabs a.nav-link { + border-bottom: 1px solid #767676; +} #tabContent { background-color: #FDF6F6; - padding: 15px; + border: 1px solid #767676;; + border-top-color: transparent; + padding: 30px; } #tabContent.tab-wiktionnary { background-color: #FFE4E4; @@ -216,4 +224,8 @@ table.label-table > tbody > tr > td { #tableLabels.table > tbody > tr > td { height: 52px; +} + +#tabContent #tableLabels { + background-color: white; } \ No newline at end of file diff --git a/public/assets/js/app.js b/public/assets/js/app.js index 2f1e070e38ef7a76f85804234ce5a80909f79fea..5bec1ce13b2803917dcce712176bb0b898b7eb7b 100644 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -12,6 +12,7 @@ $(function() { initializeForm(); initializeAjaxLinks(); + initializeSearchHeadwords(); // Pour améliorer en spécifiant la méthode (DELETE, POST...) Voir https://stackoverflow.com/a/8983053/4954580 initializeFormToggles(); initializeConfirmDialog(); // Confirmation des suppressions dans une modal Boostrap @@ -42,7 +43,7 @@ $(function() { var isForm = $(e.currentTarget).hasClass('modal-form'); var extraFormId = $(e.currentTarget).data('extra-form-id'); - if ($(extraFormId)) { + if (extraFormId && $(extraFormId)) { url = url + (url.indexOf('?') >= 0 ? '&' : '?') + $(extraFormId).serialize(); }; @@ -89,20 +90,6 @@ $(function() { $(':submit', $form).button('reset'); initializeForm(true); //$('#bootstrap-modal').scrollTop(0); // pour voir les erreurs de formulaire sur un long formulaire - }, - error: function() { // exécuté quand le serveur a rencontré une erreur - $(':submit', $form).button('reset'); - BootstrapDialog.show({ - type: BootstrapDialog.TYPE_WARNING, - title: "Erreur", - message: "Un problème est survenu. Il est possible que vos données n'aient pas été enregistrées.", - buttons: [{ - label: 'Fermer', - action: function(dialogRef){ - dialogRef.close(); - } - }] - }); } }); }); @@ -383,4 +370,32 @@ function initializeAjaxLinks() { } }) + function initializeSearchHeadwords() { + // cliqk sur search => ajax pour get url entry_show dan sle bon lexique + $form = $('#searchHeadwordsForm'); + $form.on('submit', function (e) { + e.preventDefault(); + var $overlay = $('#overlay').show(); + var search = $('#searchHeadwordBox').val(); + + $.ajax({ + type: 'GET', + url: $form.attr('action') + '?' + search, + dataType: "json", + success: function (response, textStatus, xhr) { + window.location.href = response; + }, + error: function (jqXHR, textStatus, errorThrown) { + console.log(jqXHR.responseText, textStatus, errorThrown); + $wordCreationConfirmationLink = $('headwordConfirmCreation'); + $wordCreationConfirmationLink.data('url', $wordCreationConfirmationLink.data('url') + '?' + search); + $wordCreationConfirmationLink.click(); + // $overlay.hide(); + } + }) + }); + // si pas présnete => modal-form ou confirm-dialog pour demander création dans Agora + + + } } \ No newline at end of file diff --git a/src/Controller/AppBaseController.php b/src/Controller/AppBaseController.php index 3851f0ea575fdcb74a11cff25e12056eb6fb912f..f5738e94552216b4c478edab566e021bb1ee134d 100644 --- a/src/Controller/AppBaseController.php +++ b/src/Controller/AppBaseController.php @@ -10,6 +10,7 @@ use App\Entity\Label; use App\Entity\Lexicon; use App\Entity\User; use App\Languages\LanguagesIso; +use App\Manager\LexiconManager; use App\Manager\WiktionaryManager; use Doctrine\ORM\EntityManager; use Doctrine\Persistence\ManagerRegistry; @@ -185,6 +186,15 @@ class AppBaseController extends AbstractController return LanguagesIso::getCodes(); } + //si un user a la possibilité de travailler avec 2 langues, il faudra stocker la langue utilisée en session. + // Certaines actions ont besoin de la langue (ajouter un mot-vedette à un label par ex, car le label n’est pas lié à une langue). + // Pour l’instant on retourne la première langue étudiée par l’user + // (qu'on utilise également pour déterminer la langue de son lexique perso lors de la création de l'utilisateur) + public function getLanguage() + { + return $this->getUser()->getFirstStudiedLanguage(); + } + /** * - Si on trouve un headword dont la valeur est word, on le retourne * - Sinon, si on trouve une graphie dont la valeur est word, on retourne le premier headword lié à cette graphie s'il existe. @@ -217,6 +227,15 @@ class AppBaseController extends AbstractController return $this->doctrine->getRepository(Lexicon::class)->findOneBy(['language' => $language, 'category' => Lexicon::TYPE_ZERO]); } + /** + * @param $language + * @return Lexicon|null + */ + public function getNewWordsLexicon($language) + { + return $this->doctrine->getRepository(Lexicon::class)->findOneBy(['language' => $language, 'category' => Lexicon::TYPE_NEW_WORDS]); + } + /** * * On crée la graphie dans Balex si elle n'existe pas, on crée un headword @@ -376,14 +395,46 @@ class AppBaseController extends AbstractController if ($zeroEntry) { $entry->setAttributes($zeroEntry->getAttributes()); } else { - $baseEntrySchema = json_decode(file_get_contents(__DIR__ . "/../JsonSchema/baseEntrySchema.json"), true); - $baseEntrySchema['Headword'] = $headword->getValue(); - $entry->setAttributes($baseEntrySchema); + $this->initializeWithBasicAttributes($entry); } return $entry; } + /** + * On initialise l'entrée passée en paramètre avec une structure minimale presque vide + * + * @param Entry $entry + */ + public function initializeWithBasicAttributes(Entry $entry) + { + $headword = $entry->getHeadword(); + $baseEntrySchema = json_decode(file_get_contents(__DIR__ . "/../JsonSchema/baseEntrySchema.json"), true); + $baseEntrySchema['Headword'] = $headword->getValue(); + $entry->setAttributes($baseEntrySchema); + } + + /** + * On crée l'entrée dans le lexique de proposition des nouveaux mots + * On initialise avec des infos minimales + * + * @param Headword $headword + * @return Entry + * @throws \Exception + */ + public function createEntryInNewWordsLexicon(Headword $headword) + { + $newWordsLexicon = $this->em->getRepository(Lexicon::class)->findOneBy(['language' => $headword->getLanguage(), 'category' => Lexicon::TYPE_NEW_WORDS]); + $entry = new Entry(); + $this->em->persist($entry); + $entry->setHeadword($headword); + $entry->setLanguage($headword->getLanguage()); + $entry->setLexicon($newWordsLexicon); + $this->initializeWithBasicAttributes($entry); + + return $entry; + } + /** * @param Request $request * @param $sortingColumn diff --git a/src/Controller/HeadwordController.php b/src/Controller/HeadwordController.php index a35ed0120e0cac855b6f92cd3864f9c1f447c0ae..7c6dca52eedb2217d27f5fcffdc999b29b049f01 100644 --- a/src/Controller/HeadwordController.php +++ b/src/Controller/HeadwordController.php @@ -35,22 +35,37 @@ class HeadwordController extends AppBaseController // 'headwords' => $headwordRepository->findAll(), // ]); // } -// -// /** -// * @Route("/{id}", name="app_headword_show", methods={"GET"}) -// */ -// public function show(Request $request, Headword $headword): Response -// { -// $form = $this->createForm(SearchStringType::class,null, array('method' => 'GET')); -// $form->handleRequest($request); -// if ($form->get('searchString')->getData()) { -//// TODO $labelManager->setSearchString($form->get('searchString')->getData()); -// } -// return $this->render('headword/show.html.twig', [ -// 'headword' => $headword, -// 'form' => $form->createView(), -// ]); -// } + + /** + * @Route("/search", name="app_headword_search") + */ + public function search(Request $request): Response + { + $search = $request->get('search'); + $orderedLexicons = array_merge($this->getUser()->getMyLexicons(), [ + $this->getZeroLexicon($this->getLanguage()), + $this->getNewWordsLexicon($this->getLanguage()), + ]); + $entries = $this->em->getRepository(Entry::class)->getByHeadwordValueInLexicons($search, $orderedLexicons); + + if ($entries) { + return new JsonResponse($this->generateUrl('app_entry_show', ['id' => $entries[0]->getId()])); + } else { + return new JsonResponse(null,404); + } + } + + /** + * @Route("/confirm-new-word-creation", name="app_headword_confirm_creation") + */ + public function confirmNewWordCreation(Request $request): Response + { + + + return $this->render("genericModalForm.html.twig", [ + 'form' => $form->crea + ]); + } /** * @Route("/{id}/toggle-known-headword/{userId}", name="app_headword_toggle_known", methods={"GET"}) diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index 2214d65387d337c6ff40b14260d1612be45c06f7..d56b89c342881ed6c655a922e099415eec7193dc 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -11,10 +11,12 @@ use App\Entity\Lexicon; use App\Entity\Log; use App\Form\CopyEntriesType; use App\Form\CopyHeadwordsType; +use App\Form\EnterWordType; use App\Form\LabelDescriptionType; use App\Form\LabelType; use App\Form\SearchStringType; use App\Manager\LabelManager; +use App\Manager\WiktionaryManager; use App\Repository\HeadwordRepository; use App\Repository\LabelRepository; use Doctrine\Persistence\ManagerRegistry; @@ -256,22 +258,52 @@ class LabelController extends AppBaseController * @Route("/{id}/add-headword", name="app_label_add_headword", methods={"GET", "POST"}) */ // * @IsGranted("LABEL_EDIT", subject="label") - public function addHeadwordDescription(Request $request, Label $label): Response + public function addHeadwordDescription(WiktionaryManager $wiktionaryManager, Request $request, Label $label): Response { - $form = $this->createForm(LabelDescriptionType::class, $label, [ + $displayForceValidation = false; + + $form = $this->createForm(EnterWordType::class, null, [ 'action' => $this->generateUrl('app_label_add_headword', ['id' => $label->getId()]) ]); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $labelRepository->add($label, true); + $word = $form->get('word')->getData(); + // On regarde si le mot-vedette existe + $headword = $this->em->getRepository(Headword::class)->findOneBy([ + 'value' => $word, + 'language' => $this->getLanguage(), + ]); + // si c'est le cas on lui ajoute le label + if ($headword) { + $headword->addLabel($label); + $this->em->flush(); - return $this->render('closeModalAndReload.html.twig'); + return $this->render('closeModalAndReload.html.twig'); + } else { + // Sinon on essaie de créer le mot-vedette à partir du Wiktionnaire + if ($form->get('forceValidation')->isClicked() || $wiktionaryManager->search($word, $this->getLanguage())) { + $headword = $this->newHeadword($word, $this->getLanguage()); + if ($form->get('forceValidation')->isClicked()) { + $this->createEntryInNewWordsLexicon($headword); + } else { + $this->getOrCreateEntryInZeroLexicon($headword); + } + $headword->addLabel($label); + $this->em->flush(); + + return $this->render('closeModalAndReload.html.twig'); + } else { + $displayForceValidation = true; + $this->addFlash('warning', sprintf("Le mot «%s» n'existe ni dans Balex ni dans le wiktionnaire. Voulez-vous le proposer dans l'Agora des nouveaux mots ?", $word)); + } + } } - return $this->render('genericModalForm.html.twig', [ + return $this->render('label/addHeadwordToLabel.html.twig', [ 'title' => "Modifier le label", 'form' => $form->createView(), + 'displayForceValidation' => $displayForceValidation, ]); } diff --git a/src/Form/EnterWordType.php b/src/Form/EnterWordType.php index ec1520078134a3e29c15149513f01967fd348d36..cf8c9a4261c1025fdba28c9d7662add1087b7dff 100644 --- a/src/Form/EnterWordType.php +++ b/src/Form/EnterWordType.php @@ -23,21 +23,23 @@ class EnterWordType extends AbstractType { $builder - ->add('description', TextareaType::class, [ - 'label' => 'Description', + ->add('word', TextType::class, [ + 'label' => 'Mot', ]) ; $builder + ->add('forceValidation', SubmitType::class, [ + 'label' => 'Confirmer', + ]) ->add('submit', SubmitType::class, [ - 'label' => 'Enregistrer', + 'label' => 'Valider', ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'data_class' => Label::class, 'attr' => [ 'data-ajax-form' => '', 'data-ajax-form-target' => '#bootstrap-modal .modal-content', diff --git a/src/Repository/EntryRepository.php b/src/Repository/EntryRepository.php index bfb480548630bb05df9f13900ecc225794f38438..c9fdff613779c07bfd6f6db896aec0b536989f23 100644 --- a/src/Repository/EntryRepository.php +++ b/src/Repository/EntryRepository.php @@ -58,6 +58,21 @@ class EntryRepository extends ServiceEntityRepository } + public function getByHeadwordValueInLexicons($value, $lexicons) + { + $qb = $this->createQueryBuilder('e') + ->leftJoin('e.headword', 'h') + ->leftJoin('e.lexicon', 'lex') + ->andWhere('e.lexicon IN (:lexicons)') + ->setParameter('lexicons', $lexicons) + ->andWhere('h.value = :value') + ->setParameter('value', $value) + ; + + return $qb->getQuery()->getResult(); + + } + public function add(Entry $entity, bool $flush = false): void { $this->getEntityManager()->persist($entity); diff --git a/templates/entry/show.html.twig b/templates/entry/show.html.twig index a81423148cf1ef99684a3f86c73e4ff0ce703c58..a3fcf6e27e1a62df0abdb6dad69b46311b13e18c 100644 --- a/templates/entry/show.html.twig +++ b/templates/entry/show.html.twig @@ -56,8 +56,10 @@ {% endfor %} {% if not entry.lexicon.isNewWords %} + {% set zeroLexicon = lexicon_manager.zeroLexicon(entry.language) %} + {% set entryZero = zeroLexicon.getEntryForHeadword(entry.headword) %} <li class="nav-item"> - <a class="nav-link tab-dark-pink" href="#">{{ "Wiktionnaire"|trans }}</a> + <a class="nav-link {{ zeroLexicon == entry.lexicon ? 'active' }} tab-dark-pink" href="{{ path('app_entry_show', {id: entryZero.id}) }}">{{ "Wiktionnaire"|trans }}</a> </li> {% endif %} </ul> diff --git a/templates/label/addHeadwordToLabel.html.twig b/templates/label/addHeadwordToLabel.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..376499a812f5f5aafd2ff6bfe580e4d884b9b421 --- /dev/null +++ b/templates/label/addHeadwordToLabel.html.twig @@ -0,0 +1,34 @@ +{% extends 'modal.html.twig' %} + +{% block modal_title %} + {{ "Ajouter un mot-vedette à ce label"|trans }} +{% endblock %} + +{% block modal_body %} + <div class="row"> + <div class="col-md-12"> + + {% include "flashes.html.twig" %} + + {{ form_start(form) }} + {{ form_errors(form) }} + {% for child in form %} + {% if child.vars.name not in ['submit', 'forceValidation'] %} + {{ form_row(child) }} + {% endif %} + {% endfor %} + <div class="row pt-3"> + <div class="col-sm-4"></div> + <div class="col-sm-8"> + {% if displayForceValidation %} + {{ form_widget(form.forceValidation) }} + {% else %} + {{ form_widget(form.submit) }} + {% endif %} + <button type="button" class="btn btn-light" data-bs-dismiss="modal">{{ 'Annuler'|trans }}</button> + </div> + </div> + {{ form_end(form, {render_rest: false}) }} + </div> + </div> +{% endblock %} \ No newline at end of file diff --git a/templates/label/show.html.twig b/templates/label/show.html.twig index 1edf582e8d0373de4a92369e0a87c5fcbd6df09c..35ab752eaef2eff46d877a7231ddabc17985c151 100644 --- a/templates/label/show.html.twig +++ b/templates/label/show.html.twig @@ -9,6 +9,10 @@ <div class="row my-5"> <div class="col-md-12"> + <h1 class="my-3"> + <i class="fa fa-tag"></i> {{ "Labels"|trans }} + </h1> + <div class="row"> <div class="col-md-6"> @@ -62,7 +66,7 @@ <th class="text-center"> <div class="d-flex justify-content-around align-items-center"> <span>{{ "Mots-vedettes"|trans }}</span> - <a title="{{ "Ajouter"|trans }}" href="#" actionUrl="{{ path('app_label_add_headword', {id: label.id}) }}" class="modal-form btn btn-dark btn-sm"><i class="fa fa-plus"></i></a> + <a title="{{ "Ajouter"|trans }}" href="#" data-url="{{ path('app_label_add_headword', {id: label.id}) }}" class="modal-form btn btn-dark btn-sm"><i class="fa fa-plus"></i></a> </th> <th colspan="{{ lexiconsNb }}" class="text-center">{{ "Présence des mots dans les lexiques"|trans }}</th> </tr> @@ -92,6 +96,8 @@ <tbody> {% for headword in headwords %} + {% set zeroLexicon = lexicon_manager.zeroLexicon(headword.language) %} + {% set entryZero = zeroLexicon.getEntryForHeadword(headword) %} {# @var headword \App\Entity\Headword #} <tr> {# <td>{{entry.addingOrder }}</td>#} @@ -99,7 +105,7 @@ {# "value" est utilisé par Symfo quand on soumet le form. "data-headword-id" est utilisé par la vue chooseLabel pour injecter du json dans le sliens ajax #} <input class="me-2" type="checkbox" name="form[selected_headwords][]" value="{{ headword.id }}" id="form_selected_headwords_{{ headword.id }}"/> - <a href="{{ path('app_wiktionnary_show', {id: headword.id}) }}"> + <a href="{{ path('app_entry_show', {id: entryZero.id}) }}"> {{ headword.value }} </a> </td> diff --git a/templates/nav.html.twig b/templates/nav.html.twig index 47c362ad6aafa6cf721342355926618043ee53d5..19a98568cfcecb7b3b7e1aea2ab98a61d917ede0 100644 --- a/templates/nav.html.twig +++ b/templates/nav.html.twig @@ -6,41 +6,52 @@ </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> - <form class="d-flex" role="search"> - <input class="form-control me-2" type="search" placeholder="{{ "Rechercher"|trans }}" aria-label="Search"> + <form id="searchHeadwordsForm" class="d-flex" role="search" action="{{ path('app_headword_search') }}"> + <input name="searchHeadwordBox" class="form-control me-2" type="search" placeholder="{{ "Rechercher"|trans }}" aria-label="Search"> <button class="btn btn-light me-3" type="submit"><i class="fa fa-search"></i> </button> + <a id="headwordConfirmCreation" href="#" class="modal-form d-none" data-url="{{ path('app_headword_confirm_creation') }}"></a> </form> {% if app.user %} <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> - <a class="nav-link {{ 'app_group' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_group_index') }}"><i class="fa fa-users"></i> Groupes</a> - </li> - <li class="nav-item"> - <a class="nav-link {{ 'app_friend' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_friend_index') }}"><i class="bi bi-person-heart"></i> Amis</a> - </li> - <li class="nav-item"> - <a class="nav-link {{ 'app_label' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_label_index') }}"><i class="bi bi-tags"></i> Labels</a> + <a class="nav-link {{ 'app_label' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_label_index') }}"> + <i class="bi bi-tags"></i> {{ "Labels"|trans }}</a> </li> <li class="nav-item dropdown"> - <a class="nav-link dropdown-toggle {{ 'app_lexicon' in app.request.attributes.get('_route') ? 'active' }}" data-bs-toggle="dropdown" href="#"><i class="bi bi-journals"></i> Lexiques</a> + <a class="nav-link dropdown-toggle {{ 'app_lexicon' in app.request.attributes.get('_route') ? 'active' }}" data-bs-toggle="dropdown" href="#"> + <i class="bi bi-journals"></i> {{ "Lexiques"|trans }}</a> <ul class="dropdown-menu"> {% for lexicon in app.user.myLexicons %} <li><a class="dropdown-item" href="{{ path('app_lexicon_show', {id: lexicon.id}) }}">{{ lexicon }}</a></li> {% endfor %} </ul> </li> + <li class="nav-item"> + <a class="nav-link {{ 'app_group' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_group_index') }}"> + <i class="fa fa-users"></i> {{ "Groupes"|trans }}</a> + </li> + <li class="nav-item"> + <a class="nav-link {{ 'app_friend' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_friend_index') }}"> + <i class="bi bi-person-heart"></i> {{ "Amis"|trans }}</a> + </li> + <li class="nav-item"> + <a class="nav-link {{ 'app_dashboard' in app.request.attributes.get('_route') ? 'active' }}" href="#"> + <i class="bi bi-speedometer"></i> {{ "Dashboard"|trans }}</a> + </li> {% if is_granted('ROLE_ADMIN') %} <li class="nav-item"> - <a class="nav-link {{ 'app_user' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_user_index') }}"><i class="bi bi-person"></i> Utilisateurs</a> + <a class="nav-link {{ 'app_user' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_user_index') }}"> + <i class="bi bi-person"></i> {{ "Utilisateurs"|trans }}</a> </li> {# <li class="nav-item">#} {# <a class="nav-link {{ 'app_lexicon' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_lexicon_index') }}"><i class="bi bi-card-list"></i> Lexiques</a>#} {# </li>#} <li class="nav-item"> - <a class="nav-link {{ 'app_client' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_client_index') }}"><i class="bi bi-window"></i> Applis clientes</a> + <a class="nav-link {{ 'app_client' in app.request.attributes.get('_route') ? 'active' }}" href="{{ path('app_client_index') }}"> + <i class="bi bi-window"></i> {{ "Applis clientes"|trans }}</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ path('app.swagger_ui') }}"><i class="bi bi-terminal"></i> Swagger</a>