Discussion Gestion:Tâches/Liste/499

À propos de ce flux de discussion

Non modifiable

Antoine Mercier-Linteau (discussioncontributions)

@Charles-Éric Noël Laflamme, vu ta connaissance de l'éditeur visuel, je crois que tu serait l'homme de la situation.

Dans le fond, la présence d'un mot clé (genre __NOVEDELETE__) empêcherait un élément d'être supprimé par un éditeur (individuellement ou en bloc).

Penses-tu que ce serait complexe à faire?

Antoine Mercier-Linteau (discussioncontributions)

J'ai regardé rapido et nous pourrions passer par l’événement undoStackChange pour attraper la suppression d'un bloc de texte, le parser, lancer une message d'erreur et annuler les changements au besoin.

Charles-Éric Noël Laflamme (discussioncontributions)

Je crois que c'est effectivement possible. Il s'agirait comme tu l'as mentionner de vérifier chaque suppression de l'utilisateur en s'assurant de déselectionner les éléments portant le mot clé __NOVEDELETE__ avant de procéder à la suppression. Le plus grand défi est donc de trouver s'il y a un event lancé par le visual editor lors de la suppresion d'un élément. Je ne suis pas certain qu'undoStackChange corresponde effectivement à ce genre d'event. Je crois qu'il s'agit plutôt d'un event lancé lors de l'annulation de multiples modifications. Les methods this.undoStack ou this.undoIndex pourraient cependant être utilisées afin d'annuler immédiatement la suppression si elle contient un élément non suppressible.

Je n'ai toutefois pas trouvé jusqu'à maintenant d'event qui semble être lancé à la suppression d'éléments.


D'un autre côté, je me questionne à savoir à quel endroit devrait être stocké la propriété __NOVEDELETE__ afin qu'elle puisse facilement être récupérée.


Finalement, je me demande à quel endroit ce bout de code devrait-il être écrit?

Antoine Mercier-Linteau (discussioncontributions)

Effectivement, il n'y a pas d'événement de suppression, le truc que j'ai trouvé qui s'en rapproche le plus, c'est undoStackChange. Surveiller les entrées de l'utilisateurs ne fonctionnera pas, car des éléments peuvent être supprimés en copiant du texte par dessus et pas forcément en appuyant sur delete ou backspace. Je ne sais pas comment le stack de l'éditeurs visuel fonctionne aussi, mais je présume que ça stocke des différences et pas la pages en entier. Le parsing devrait donc être plus rapide.

La propriété __NOVEDELETE__ peut être stockée dans un modèle, comme par exemple Modèle:Information concept. Il faut que je code en PHP son évaluation, mais pour des fins de test, on peut fonctionner avec un pattern HTML à détecter (genre <span style="display:none;">__NOVEDELETE__</span>).

Le morceau de code sera implémenté par un gadget (voir MediaWiki:Gadgets-definition) et stocké dans MediaWiki:gadget-novedelete.js.

Pour en faire le développement, une fois l'éditeur visuel ouvert, tu peux charger ton script comme suit:

mw.loader.load( 
    '/index.php?title=MediaWiki:gadget-novedelete.js&action=raw&ctype=text/javascript' 
);


Charles-Éric Noël Laflamme (discussioncontributions)

J'ai tenté d'avancer le dossier, mais pour être franc, je ne comprends pas comment l'API de ve.ce.Surface fonctionne de sorte que je ne suis pas capabe de mettre en place un listener pour l'event undoStackChange. J'ai tenté de rechercher de la documentation autre que l'API, mais elle est quasi inexistante...

Une fois le listener en place je pense par contre que ça ne serait pas difficile à implémenter.

Antoine Mercier-Linteau (discussioncontributions)

Effectivement pas facile à utiliser. Voici l'appel pour surveiller l'event undoStackChange avec un début de code pour inspecter le stack:

ve.init.target.getSurface().getModel().on('undoStackChange', function() { 

    var change = this.undoStack[this.undoStack.length - 1];
    
    var operation = change.transactions[0].operations[1];
    
    if(!operation.remove.length) { return; } // If no text was removed.
    
    remove = "";
    
    for(var i = 0; i < operation.remove.length; i++) { remove += operation.remove[i][0][0]; }
    
    // Analyse du texte supprimé pour trouver le pattern.
    
    if(pattern) // If the pattern was found.
    {
        // Peut-être utiliser une erreur de l'éditeur visuel.
        alert("Vous ne pouvez pas supprimer ce bloc.");
        
        this.undo();
    }
});

Cette page qui explique comment écrire des plugins pour l'éditeur visuel donne de bons indices sur son fonctionnement.

Charles-Éric Noël Laflamme (discussioncontributions)

J'ai écrit un petit prototype dans MediaWiki:Gadget-novedelete.js qui empêche du supprimer les Headings. Il a l'air de marcher assez bien. Comme amélioration il pourrait être intéressant de seulement annuler la suppresion des éléments non-suppressible au lieu d'annuler la suppresion complète, quoique cela pourrait être compliqué pour rien. Il pourrait aussi être intéressant de rajouter au message d'erreur quelques spécifications sur quel type d'éléments a tenté d'être supprimé et pourquoi cela n'est pas permis.

Pour le reste, il reste seulement à stocké la propriété__NOVEDELETE__ qui semblerait pouvoir être récupéré assez facilement par dans le undoStack et à vérifier cette propriété au lieu du type mwHeading.

Antoine Mercier-Linteau (discussioncontributions)

Ça fonctionnait bien effectivement. J'ai par contre un peu galéré pour aller récupérer le HTML associé à un item du undoStack mais j'y suis parvenu.

Le switch __NOVEDELETE__ a été implanté en PHP. Il a pour effet de wrapper les inclusions dans les quelles il est définit avec <div data-novedelete="1"> ... </div>.

Je l'ai ajouté à Modèle:Information concept, ce qui aura pour effet d'empêcher la suppression de toutes les infobox.

Reste à trouver ce div en jQuery, à récupérer le nom du modèle pour l'afficher, et à empêcher la suppression lorsqu'il est présent.

Ce truc pourrait vraiment faire l'objet d'une extension MediaWiki. Je suis certain que ce serait utile à d'autres.

Charles-Éric Noël Laflamme (discussioncontributions)

Je ne suis pas certain de la manière de procéder pour vérifier si un élément supprimer fait partie du div data-novedelete. En fait, lorsque le modèle:Information concept est supprimé, voici ce que le callback undoStackchange émet:

{

  "type":"mwTransclusionBlock",
  "attributes":{
     "mw":{
        "parts":[
           {
              "template":{
                 "target":{
                    "wt":"Information maladie\n",
                    "href":"./Modèle:Information_maladie"
                 },
                 "params":{
                    "page":{
                       "wt":"Rupture d'anévrisme de l'aorte abdominale"
                    },
                    "nom":{
                       "wt":"Wksoth06xud1ooqn"
                    },
                    "acronyme":{
                       "wt":"Rupture d'AAA"
                    },
                    "image":{
                       "wt":"RupturedAAA.png"
                    },
                    "description_image":{
                       "wt":"Un AAA rupturé tel que vu au TDM"
                    },
                    "mesh_id":{
                       "wt":""
                    },
                    "autres_noms":{
                       "wt":""
                    },
                    "terme_anglais":{
                       "wt":"Abdominal aortic aneurysm rupture"
                    },
                    "vidéo":{
                       "wt":""
                    },
                    "son":{
                       "wt":""
                    },
                    "spécialités":{
                       "wt":"Urgentologie, chirurgie vasculaire"
                    },
                    "version_de_classe":{
                       "wt":"1 "
                    },
                    "démo":{
                       "wt":"0"
                    }
                 },
                 "i":0
              }
           }
        ]
     },
     "originalMw":"{\"parts\":[{\"template\":{\"target\":{\"wt\":\"Information maladie\\n\",\"href\":\"./Modèle:Information_maladie\"},\"params\":{\"page\":{\"wt\":\"Rupture d'anévrisme de l'aorte abdominale\"},\"nom\":{\"wt\":\"Wksoth06xud1ooqn\"},\"acronyme\":{\"wt\":\"Rupture d'AAA\"},\"image\":{\"wt\":\"RupturedAAA.png\"},\"description_image\":{\"wt\":\"Un AAA rupturé tel que vu au TDM\"},\"mesh_id\":{\"wt\":\"\"},\"autres_noms\":{\"wt\":\"\"},\"terme_anglais\":{\"wt\":\"Abdominal aortic aneurysm rupture\"},\"vidéo\":{\"wt\":\"\"},\"son\":{\"wt\":\"\"},\"spécialités\":{\"wt\":\"Urgentologie, chirurgie vasculaire\"},\"version_de_classe\":{\"wt\":\"1 \"},\"démo\":{\"wt\":\"0\"}},\"i\":0}}]}"
  },
  "originalDomElementsHash":"h2d7492dad8f7b109"


}


Il n'y a donc pas de manière évident de directement récupérer à quel élément du DOM cet élément correspond. Il y a une propriété originalDomElementHash. Il semble toutefois s'agir d'une propriété générée par Media Wiki et j'ignore s'il existe une méthode pouvant associer l'élément supprimé à un élément du DOM.

Par ailleurs, le callback est seulement émis une fois l'élément supprimé et l'opération est simplement annulée si l'élément n'est pas supprimable. Donc, même s'il était possible d'associé l'élément supprimé à un élément du DOM et de vérifier si l'élément fait partie du div data-novedelete, je ne sais pas si ce dernier serait présent dans le DOM Tree parce qu'il serait théoriquement supprimé pendant une fraction de seconde le temps de la vérification et donc innaccessible par le DOM.

D'un autre côté, le callback semble récupérer plusieurs attributs de l'object supprimé par originalMw. Je ne sais pas s'il serait possible d'ajouter directement le switch NO-VE-DELETE en PHP parmi ces attributs, ce qui simplifierait grandement la tâche.

Charles-Éric Noël Laflamme (discussioncontributions)

Ou simplement passer par la propriété "href":"./Modèle:Information_maladie" directement?

Antoine Mercier-Linteau (discussioncontributions)

C'est le problème auquel j'ai fait face. J'aurais du le spécifier mais j'ai implanté une partie de la solution dans MediaWiki:Gadget-novedelete.js. Comme tu l'as évoqué, ça passe par le hash.

if (removedItem.hasOwnProperty('type') && removedItem.type == 'mwTransclusionBlock'){
    var templateName = removedItem.attributes.mw.parts[0].template.target.wt; // Get the name of the deleted template.
    var dom = $(ve.init.target.getSurface().getModel().getDocument().getStore().value(removedItem.originalDomElementsHash)); // Get the DOM item from the hash store.
    
    // Parse for __NOVEDELETE__ pattern here using normal jQuery syntax.
    
    OO.ui.alert( 'Vous ne pouvez pas supprimer le modèle ' + templateName, { size: 'large' } );

    ve.init.target.getSurface().getModel().undo();
    ve.init.target.getSurface().getModel().truncateUndoStack();
}

Ce qui reste à faire, c'est trouver si le HTML contient dans un div data-novedelete, embellir un peu le message d'erreur et s'assurer que ça marche en mobile.

Charles-Éric Noël Laflamme (discussioncontributions)

Ah j'ai pigé!

En fait dans la variable dom contient les éléments du DOM qui ont été retiré lors de la suppression, donc il est impossible de récupérer leur emplacement dans le DOM actuel puisqu'ils y ont été supprimé.

Cela dit, même s'il n'est pas possible de retrouver les parent originaux de ces éléments, le div NOVEDELETE fait lui-même partie de ces éléments. Donc on peut vérifier la présence d'un div NOVEDELETE parmi les éléments supprimés afin de choisir si la suppresion est valide ou non.

Pour ce qui est de la version mobile, ça l'a l'air de bien fonctionner également. Par contre, il y a un petit délai entre la suppression de l'élément et le message d'erreur, si bien qu'on obtient la séquence suivante: suppression de l'élément->disparition de l'élément->2-3 secondes->Message d'erreur->Réapparition de l'élément.

Je ne crois toutefois pas que ça pose problème.

Antoine Mercier-Linteau (discussioncontributions)

Exact! J'ai fait des améliorations au code et j'ai publié le gadget pour qu'il s'active pour tous.

@Michaël St-Gelais, ouvre une page de maladie et tente de supprimer une infobox svp.

Michaël St-Gelais (discussioncontributions)

Ça marche ! C'est un excellent ajout. :)

Il n’y a aucun sujet plus ancien