From efe1610101ba3a61afc9efdf09229f0c6a8bafda Mon Sep 17 00:00:00 2001 From: nicod_ <nicod@lerebooteux.fr> Date: Sun, 16 Apr 2023 18:30:41 +0200 Subject: [PATCH] Gestion des rangs des blocs sur un objet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code repris en grande partie du plugin medias (markup html, javascript et action) TODO : mutualiser ce code relativement générique au niveau du core de SPIP ? --- TODO.md | 2 +- action/ordonner_liens_blocs.php | 104 +++++++++++++++++++++ javascript/gestion_listes_blocs.js.html | 107 ++++++++++++++++++++++ prive/squelettes/inclure/blocs_objet.html | 37 ++++++-- prive/style_prive_plugin_blocks.html | 41 +++++++++ 5 files changed, 281 insertions(+), 10 deletions(-) create mode 100644 action/ordonner_liens_blocs.php create mode 100644 javascript/gestion_listes_blocs.js.html create mode 100644 prive/style_prive_plugin_blocks.html diff --git a/TODO.md b/TODO.md index a27d974..34a1dc6 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,7 @@ [x] Ajouter des blocs sous les objets liés -[ ] Gérer les rangs des blocs sous les objets liés +[x] Gérer les rangs des blocs sous les objets liés [ ] Afficher les blocs sous les objets liés : largeur limitée, affichage simplifié + preview en popin ? diff --git a/action/ordonner_liens_blocs.php b/action/ordonner_liens_blocs.php new file mode 100644 index 0000000..10cf385 --- /dev/null +++ b/action/ordonner_liens_blocs.php @@ -0,0 +1,104 @@ +<?php + +/** + * Action ordonnant un lien sur une table de liens + * + * @plugin Medias + * @copyright 2017 + * @author Matthieu Marcillaud + * @licence GNU/GPL + * @package SPIP\Ordoc\Action + */ + +if (!defined('_ECRIRE_INC_VERSION')) { + return; +} + +function action_ordonner_liens_blocs_dist() { + action_ordonner_liens_dist(); +} + +function action_ordonner_liens_dist() { + include_spip('inc/autoriser'); + include_spip('base/objets'); + include_spip('action/editer_liens'); + + // source (table spip_xx_liens) + $objet = objet_type(_request('objet_source')); + + // objet lié + $objet_lie = objet_type(_request('objet_lie')); + $id_objet_lie = intval(_request('id_objet_lie')); + + // ordre des éléments + $ordre = _request('ordre'); + + if (!$objet or !$objet_lie or !$id_objet_lie or !$ordre or !is_array($ordre) or !objet_associable($objet)) { + return envoyer_json_erreur(_T('medias:erreur_objet_absent') . ' ' . _T('medias:erreur_deplacement_impossible')); + } + + if (!autoriser('modifier', $objet_lie, $id_objet_lie)) { + return envoyer_json_erreur(_T('medias:erreur_autorisation') . ' ' . _T('medias:erreur_deplacement_impossible')); + } + + [$_id_objet, $table_liens] = objet_associable($objet); + + $success = $errors = []; + + $actuels = sql_allfetsel( + [$_id_objet . ' AS id', 'rang_lien'], + $table_liens, + [ + sql_in($_id_objet, $ordre), + 'objet = ' . sql_quote($objet_lie), + 'id_objet = ' . sql_quote($id_objet_lie), + ] + ); + + $futurs = array_flip($ordre); + // ordre de 1 à n (pas de 0 à n). + array_walk($futurs, function (&$v) { + $v++; + }); + + $updates = []; + + foreach ($actuels as $l) { + if ($futurs[$l['id']] !== $l['rang_lien']) { + $updates[$l['id']] = $futurs[$l['id']]; + } + } + + if ($updates) { + foreach ($updates as $id => $ordre) { + sql_updateq( + $table_liens, + ['rang_lien' => $ordre], + [ + $_id_objet . ' = ' . $id, + 'objet = ' . sql_quote($objet_lie), + 'id_objet = ' . sql_quote($id_objet_lie), + ] + ); + } + } + + return envoyer_json_envoi([ + 'done' => true, + 'success' => $success, + 'errors' => $errors, + ]); +} + +function envoyer_json_envoi($data) { + header('Content-Type: application/json; charset=' . $GLOBALS['meta']['charset']); + echo json_encode($data, JSON_THROW_ON_ERROR); +} + +function envoyer_json_erreur($msg) { + return envoyer_json_envoi([ + 'done' => false, + 'success' => [], + 'errors' => [$msg], + ]); +} diff --git a/javascript/gestion_listes_blocs.js.html b/javascript/gestion_listes_blocs.js.html new file mode 100644 index 0000000..c5acba2 --- /dev/null +++ b/javascript/gestion_listes_blocs.js.html @@ -0,0 +1,107 @@ +#HTTP_HEADER{Content-Type: text/javascript; charset=#CHARSET} +[(#REM)<script>/* + + Gestion des listes de blocs : + - Gestion du tri par glisser-déposer + + Markup : + - Listes : .liste_items.blocs + - Listes ordonnables : .liste_items.blocs.ordonner_rang_lien\[data-lien\] + +*/] + +/* Gestion du tri des listes de blocs et de leur enregistrement */ +function ordonner_listes_blocs() { + + if (typeof Sortable === 'function') { + $(".liste_items.blocs.ordonner_rang_lien[data-lien]").find('> .sortable').each(function () { + // détruire / recréer le sortable à chaque appel ajax + if (Sortable.get(this)) { + Sortable.get(this).destroy(); + } + // pas de tri possible s'il n'y a qu'un seul élément. + if ($(this).find('> .item').length < 2) { + $(this).find('.deplacer-bloc').hide(); + $(this).parent().find('.tout_desordonner').hide(); + return true; // continue + } else { + $(this).find('.deplacer-bloc').show(); + } + new Sortable(this, { + direction: 'vertical', + swapThreshold: .8, + ghostClass: "deplacer-bloc-placeholder", + onStart: function(event) { + $(event.item).addClass('bloc-en-mouvement'); + }, + onEnd: function(event) { + $(event.item).removeClass('bloc-en-mouvement'); + }, + onUpdate: function (event) { + const ordre = this.toArray(); + const $items = $(event.from); + const $item = $(event.item); + + // l'objet lié est indiqué dans l'attribut data-lien sur la liste + const [objet_lie, id_objet_lie] = $items.parents(".liste_items.blocs").data("lien").split("/"); + const action = '[(#VAL{ordonner_liens_blocs}|generer_url_action{"", 1})]'; + const params = { + objet_source: 'bloc', + objet_lie: objet_lie, + id_objet_lie: id_objet_lie, + ordre: ordre, + }; + + $item.animateLoading(); + + $.post({ + url: action, + data: params, + dataType: 'json', + cache: false, + }).done(function(data) { + + const couleur_origine = $item.css('background-color'); + const couleur_erreur = $("<div class='remove'></div>").css('background-color'); + const couleur_succes = $("<div class='append'></div>").css('background-color'); + $item.endLoading(true); + + if (data.errors.length) { + $item.css({backgroundColor: couleur_erreur}).animate({backgroundColor: couleur_origine}, 'normal', () => { + $item.css({backgroundColor: ''}); + }); + } else { + $item.css({backgroundColor: couleur_succes}).animate({backgroundColor: couleur_origine}, 'normal', () => { + $item.css({backgroundColor: ''}); + }); + $items.parent().find('.tout_desordonner').show(); + } + }); + } + }); + + // bouton "désordonner" + if ($(this).parent().find('.deplacer-bloc[data-rang!=0]').length) { + $(this).parent().find('.tout_desordonner').show(); + } else { + $(this).parent().find('.tout_desordonner').hide(); + } + }); + } +} + + +/* Initialisation et relance en cas de chargement ajax */ +if (window.jQuery) { + jQuery(function($){ + if (!$.js_portfolio_blocs_charge) { + $.js_portfolio_blocs_charge = true; + if (typeof Sortable === "undefined") { + jQuery.getScript('[(#CHEMIN{prive/javascript/Sortable.js}|timestamp)]').done(ordonner_listes_blocs); + } else { + ordonner_listes_blocs(); + } + onAjaxLoad(ordonner_listes_blocs); + } + }); +} diff --git a/prive/squelettes/inclure/blocs_objet.html b/prive/squelettes/inclure/blocs_objet.html index ff9d194..37c5ce9 100644 --- a/prive/squelettes/inclure/blocs_objet.html +++ b/prive/squelettes/inclure/blocs_objet.html @@ -1,20 +1,39 @@ <B> [<h2>(#GRAND_TOTAL|singulier_ou_pluriel{bloc:info_1_bloc,bloc:info_nb_blocs})</h2>] -<BOUCLE(BLOCS) {objet}{id_objet} {statut?} {par rang_lien}> - <div id="bloc#ID_BLOC"> - #BOITE_OUVRIR{#INFO_TITRE{blocs_types,#ID_BLOCS_TYPE}} - #GENERER_BLOCK +<div class="liste_items blocs ordonner_rang_lien" data-lien="#ENV{objet}/#ENV{id_objet}"> + <div class="sortable"> + <BOUCLE(BLOCS) {objet}{id_objet} {statut?} {par rang_lien}> + <div class="item bloc statut_#STATUT" id="bloc#ID_BLOC" data-id="#ID_BLOC"> + <h3 class="titrem bloc__type"> + [(#CHEMIN_IMAGE{bloc-16.png}|balise_img)]#INFO_TITRE{blocs_types,#ID_BLOCS_TYPE} + [(#AUTORISER{modifier,bloc,#ID_BLOC}) + <span class="deplacer-bloc" data-rang="#RANG_LIEN"> + <img src='#CHEMIN_IMAGE{deplacer-16.png}' width='16' height='16' alt='<:medias:ordonner_ce_document|attribut_html:>' title='<:medias:ordonner_ce_document|attribut_html:>' /> + </span> + ] + </h3> - [(#AUTORISER{modifier,bloc,#ID_BLOC}) - [(#URL_ECRIRE{#VAL{bloc}|objet_info{url_edit},id_bloc=#ID_BLOC}|parametre_url{redirect,#SELF}|concat{'#bloc',#ID_BLOC}|icone_verticale{<:bloc:icone_modifier_bloc:/>,bloc,edit,right})] - ] + #GENERER_BLOCK - #BOITE_FERMER + [(#AUTORISER{modifier,bloc,#ID_BLOC}) + <div class="deplacer-modifier"> + [(#URL_ECRIRE{#VAL{bloc}|objet_info{url_edit},id_bloc=#ID_BLOC}|parametre_url{redirect,#SELF}|concat{'#bloc',#ID_BLOC}|icone_verticale{<:bloc:icone_modifier_bloc:/>,bloc,edit,right})] + </div> + ] + + </div> + </BOUCLE> </div> -</BOUCLE> +</div> + </B> <div class="ajax"> #FORMULAIRE_EDITER_BLOC{new,#OBJET,#ID_OBJET,#GET{redirect}} </div> + +<script type="text/javascript"> +/* Gestion du tri des blocs */ +[(#INCLURE{fond=javascript/gestion_listes_blocs.js}|compacte{js})] +</script> diff --git a/prive/style_prive_plugin_blocks.html b/prive/style_prive_plugin_blocks.html new file mode 100644 index 0000000..8d42976 --- /dev/null +++ b/prive/style_prive_plugin_blocks.html @@ -0,0 +1,41 @@ +[(#REM) + + Ce squelette definit les styles de l'espace prive + + Note: l'entete "Vary:" sert a repousser l'entete par + defaut "Vary: Cookie,Accept-Encoding", qui est (un peu) + genant en cas de "rotation du cookie de session" apres + un changement d'IP (effet de clignotement). + <style> +] +#CACHE{3600*100,cache-client} +#HTTP_HEADER{Content-Type: text/css; charset=utf-8} +#HTTP_HEADER{Vary: Accept-Encoding} + +#SET{claire,##ENV{couleur_claire,edf3fe}} +#SET{foncee,##ENV{couleur_foncee,3874b0}} +#SET{left,#ENV{ltr}|choixsiegal{left,left,right}} +#SET{right,#ENV{ltr}|choixsiegal{left,right,left}} + +.liste_items.blocs { + +} + +.liste_items.blocs .item.bloc { + padding: calc(var(--spip-list-spacing-y) / 2) calc(var(--spip-list-spacing-x) / 2); + border:1px solid var(--spip-color-gray-light); + border-radius:5px; + margin-bottom: 1rem; +} + +.liste_items.blocs .item.bloc .bloc__type { + margin-bottom: 1rem; +} + +.deplacer-bloc { + display: inline-flex; + float: var(--spip-right); + margin-top: 1px; + cursor: move; +} +.item.deplacer-bloc-placeholder { background-color: var(--spip-color-theme-lighter); } -- GitLab