Utilisateur:Escargot bleu/Gadget-BandeauxPortails.js

Note : après avoir enregistré la page, vous devrez forcer le rechargement complet du cache de votre navigateur pour voir les changements.

Mozilla / Firefox / Konqueror / Safari : maintenez la touche Majuscule (Shift) en cliquant sur le bouton Actualiser (Reload) ou pressez Maj-Ctrl-R (Cmd-R sur Apple Mac) ;

Firefox (sur GNU/Linux) / Chrome / Internet Explorer / Opera : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl-F5.
//
//<syntaxhighlight lang=javascript>

// Documentation : [[Projet:Scripts et gadgets/Notices/BandeauxPortails]]

// Licence : Domaine public

// <syntaxhighlight lang=javascript>
// ==================================================================================================================== DEBUT DU CODE
// ====================================================================================================================
//<pre><nowiki>

/* eslint-env browser */
/* jshint esversion: 8 */

'use strict';

/////////////////////////////////////////////////////////////////////////////////// Variables personnalisables

const defaultOptions = {
	singleEdits: true, // Affichage des anciens boutons du gadget
	autoLaunch: false, // Lancement automatique du gadget
	autoEdit: true, // Publication automatique
	minorEdit: true, // Modification mineure
	watchthis: '-1', // Ajout à la liste de suivi ( -1 = défaut préférences, 0 = jamais, 1 = toujours)
	showRedirect: false, // Affichage des redirections de bandeaux
	skipConfirm: false, // Confirmation de la suppression du bandeau
	tailleListe: 10, // Nombre d'éléments affichés pour les anciens select
	listeBas: false, // Positionnement des anciens select
};
const commonjsOptions = {};
if ( typeof window.BandeauxPortails_SingleEdits !== 'undefined' )	commonjsOptions.singleEdits = window.BandeauxPortails_SingleEdits;
if ( typeof window.BandeauxPortails_AutoLaunch !== 'undefined' )	commonjsOptions.autoLaunch = window.BandeauxPortails_AutoLaunch;
if ( typeof window.BandeauxPortails_AutoEdit !== 'undefined' )		commonjsOptions.autoEdit = window.BandeauxPortails_AutoEdit;
if ( typeof window.BandeauxPortails_MinorEdit !== 'undefined' )		commonjsOptions.minorEdit = window.BandeauxPortails_MinorEdit;
if ( typeof window.BandeauxPortails_Watchthis !== 'undefined' )		commonjsOptions.watchthis = window.BandeauxPortails_Watchthis;
if ( typeof window.BandeauxPortails_ShowRedirect !== 'undefined' )	commonjsOptions.showRedirect = window.BandeauxPortails_ShowRedirect;
if ( typeof window.BandeauxPortails_SkipConfirm !== 'undefined' )	commonjsOptions.skipConfirm = window.BandeauxPortails_SkipConfirm;
if ( typeof window.BandeauxPortails_TailleListe !== 'undefined' )	commonjsOptions.tailleListe = window.BandeauxPortails_TailleListe;
if ( typeof window.BandeauxPortails_ListeBas !== 'undefined' )		commonjsOptions.listeBas = window.BandeauxPortails_ListeBas;
const savedOptions = JSON.parse( mw.user.options.get( 'userjs-bandeauxPortails-settings' ) ) || {};
let userOptions = Object.assign( {}, defaultOptions, savedOptions, commonjsOptions );

// Groupe nécessaire pour appliquer une balise
const BandeauxPortails_TagGroup = 'user';
// Balise à appliquer aux modifs
const BandeauxPortails_Tag = mw.config.get( 'wgServer' ) === '//fr.wikipedia.org' && mw.config.get( 'wgUserGroups' ).includes( BandeauxPortails_TagGroup ) ? 'BandeauxPortails' : '';

if ( mw.config.get( 'wgAction' ) === 'view' && ( mw.config.get( 'wgNamespaceNumber' ) === 0 ) ) {
	mw.loader.using( [ 'mediawiki.api', 'oojs-ui' ], function () {
		$( function ( $ ) {
			// Objet mw.Api
			const api = new mw.Api({ userAgent: 'BandeauxPortails/1.0' });

			let formOpen = false;
				
			mw.util.addCSS(`
				.redirection-portail { font-style: italic }
				.bandeau-portail-edit {
					float: right;
					margin-top: 1ex;
				}
				.skin-minerva .bandeau-portail-edit {
					margin-top: 0;
				}
				.bandeauxportails-formulaire {
					max-width: none;
					margin-top: 1em;
				}
				.bandeauxportails-formulaire .bandeau-portail-element a,
				.bandeauxportails-formulaire .oo-ui-tagItemWidget a.new {
					pointer-events: none;
				}
				.bouton-settings-bandeauportail {
					margin-top: .2em;
				}
				.bouton-submit-bandeauportail {
					float: right;
					margin-top: .2em;
				}
				.bandeau-portail-vide {
					font-style: italic;
					color: var(--color-placeholder, #72777d);
				}
				.bandeauxportail-hidden-option {
					display:none;
				}
			`);

			// Enregistrement systématique de l'option singleEdits: true
			// Temporaire, pour pouvoir ensuite changer la valeur par défaut sans affecter les anciens utilisateurs
			if ( mw.user.options.get( 'userjs-bandeauxPortails-settings' ) === null ) {
				api.saveOption( 'userjs-bandeauxPortails-settings', JSON.stringify( { singleEdits: true } ) );
			}
/////////////////////////////////////////////////////////////////////////////////// Utilitaire

			const getPageContent = async function () {
				const data = await api.get({
					action: 'query',
					titles: mw.config.get( 'wgPageName' ),
					prop: 'revisions',
					rvprop: 'content',
					rvslots: 'main',
					formatversion: 2
				});
				return data.query.pages[ 0 ].revisions[ 0 ].slots.main.content;	
			}

			const parse = async function ( wikitext ) {
				const data = await api.get({
					action: 'parse',
					title: mw.config.get( 'wgPageName' ),
					text: wikitext,
					prop: 'text',
					disablelimitreport: true,
					wrapoutputclass: '',
					formatversion: 2,
					utf8: true
				})
				return data.parse.text;
			}

/////////////////////////////////////////////////////////////////////////////////// Édition
			const BandeauxPortails_Edit = async function ( anciensPortails, nouveauxPortails ) {

				// Vérification de l'absence de conflit d'édition
				let ancienTexte = await getPageContent();

				const regexParamsPortail = /\{\{\s*[Pp]ortail(.*?)\}\}/;
				const matchPortail = ancienTexte.match( regexParamsPortail );

				let portailsActuels = [];
				if ( matchPortail ) {
					portailsActuels = matchPortail[ 1 ]
						.split( '|' )
						.map( param => param.trim() )
						.filter( param => param !== '' );
				}

				if ( JSON.stringify( portailsActuels ) !== JSON.stringify( anciensPortails ) ) {
					mw.notify( 'Les portails ont changé pendant votre modification. Impossible d\'appliquer les changements.', { type: 'error' } );
					return;
				}

				let nouveauModele = '';
				if ( nouveauxPortails.length === 0 ) {
					if ( !userOptions.skipConfirm && ! await OO.ui.confirm( 'Supprimer le modèle {{Portail}} ?' ) ) {
						return;
					}
				} else {
					nouveauModele = '{{Portail|' + nouveauxPortails.join( '|' ) + '}}';
				}

				// Construction du sommaire
				let sommaire;
				const added = nouveauxPortails.filter( portail => !anciensPortails.includes( portail ));
				const removed = anciensPortails.filter( portail => !nouveauxPortails.includes( portail ));

				if ( added.length !== 0 && removed.length !== 0 ) {
					sommaire = 'Changement rapide de {{Portail}} : - ' + removed.join( ' , - ' ) + ' , + ' + added.join( ' , + ' );
				} else if ( removed.length !== 0 ) {
					sommaire = 'Retrait rapide de {{Portail}} : - ' + removed.join( ' , - ' );
				} else if ( added.length !== 0 ) {
					sommaire = 'Ajout rapide de {{Portail}} : + ' + added.join( ' , + ' );
				} else {
					sommaire = 'Changement rapide de {{Portail}}';
				}

				// Mise à jour du Portail
				let nouveauTexte = ancienTexte;
				if ( matchPortail ) {
					const regexPortail = /\{\{\s*[Pp]ortail.*?\}\}/;
					nouveauTexte = nouveauTexte.replace( regexPortail, nouveauModele );
				} else {
					const regexCategories = /(\{\{DEFAULTSORT:|\{\{CLEDETRI:|\{\{CLEFDETRI:|\[\[[Cc]at[ée]gor(ie|y):)/;
					const matchCategories = ancienTexte.match( regexCategories );
					if ( matchCategories ) {
						nouveauTexte = nouveauTexte.replace( regexCategories, nouveauModele + '\n\n$1' );
					} else {
						const interWiki = /^\s*\[\[([a-z][a-z].?(x?-[^]]+)?|simple|tokipona):([^\]]*)\]\]\s*$/;
						const blank = /^\s*$/;
						const lines = ancienTexte.split( '\n' );
						let DebutModele = '';
						let SuiteModele = '';
						for ( let lineId = lines.length - 1; lineId >= 0; --lineId) {
							if ( !interWiki.test( lines[ lineId ] ) && !blank.test( lines[ lineId ] ) ) {
								DebutModele = lines.slice( 0, lineId + 1 ).join( '\n' ) + '\n\n';
								SuiteModele = lines.slice( lineId + 1 ).join( '\n' );
								break;
							}
						}
						if ( DebutModele === '' ) {
							// edge case: source has nothing else than interwikis
							SuiteModele = ancienTexte;
						}
						while ( SuiteModele.indexOf( '\n\n' ) !== -1 ) {
							SuiteModele = SuiteModele.split( '\n\n' ).join( '\n' );
						}
						SuiteModele = SuiteModele.replace( /^\n/, '' );
					}
				}
				// Publication directe ou ouverture de l'éditeur
				if ( userOptions.autoEdit === false ) {
					mw.loader.using( 'mediawiki.storage', function () {
						mw.storage.set( 'bandeaux-portails-newtext', nouveauTexte, 3600 );
						mw.storage.set( 'bandeaux-portails-summary', sommaire, 3600 );
						location.href = mw.config.get( 'wgScript' ) + '?action=edit&title=' + mw.config.get( 'wgPageName' );
					});
				} else {
					const watchthisparam = {
						'-1': 'nochange',
						0: 'unwatch',
						1: 'watch'
					};
					const datas = {
						action	: 'edit',
						title	: mw.config.get( 'wgPageName' ),
						text	: nouveauTexte,
						minor	: userOptions.minorEdit,
						watchlist : watchthisparam[ userOptions.watchthis ]
					};
					if ( BandeauxPortails_Tag !== '' ) {
						datas.summary = sommaire;
						datas.tags = BandeauxPortails_Tag;
					} else {
						datas.summary = sommaire + ' ; avec [[Projet:Scripts et gadgets/Notices/BandeauxPortails|BandeauxPortails]]';
					}
					try {
						await api.postWithEditToken( datas );
						location.hash = '#bandeau-portail';
						location.reload();
					} catch ( e ) {
						mw.notify( `La publication a échoué : ${ e }`, { type: 'error' } );
					}
				}
			};

/////////////////////////////////////////////////////////////////////////////////// Formulaire
			let BandeauxPortails_tagMultiselectWidget, BandeauxPortails_layout;

			const PortalInputWidget = function () {
				OO.ui.TextInputWidget.call( this, {
					placeholder: 'Entrez un portail à ajouter...',
					autocomplete: false,
					validate: input => !/[{}|<>\[\]]/.test( input ) // eslint-disable-line no-useless-escape
				});
				OO.ui.mixin.LookupElement.call( this, { api } );
			};
			OO.inheritClass( PortalInputWidget, OO.ui.TextInputWidget );
			OO.mixinClass( PortalInputWidget, OO.ui.mixin.LookupElement );
			PortalInputWidget.prototype.getLookupRequest = function () {
				const inputValue = this.getValue();
				const params = {
					action: 'query',
					list: 'categorymembers',
					cmtitle: 'Catégorie:Modèle lien vers portail/Liste complète',
					cmstartsortkeyprefix: inputValue,
					cmtype: 'page',
					cmlimit: 200,
					formatversion: 2
				};

				return api.get( params );
			};
			PortalInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
				return response.query.categorymembers
					.map( page => page.title.replace( /Modèle:Portail /, '' ) );
			};
			PortalInputWidget.prototype.getLookupMenuOptionsFromData = async function ( data ) {
				let allPages = [];
				let portailRedirects = [];
				const inputValue = this.getValue();
				if ( userOptions.showRedirect ) {
					const params = {
						action: 'query',
						list: 'categorymembers',
						cmtitle: 'Catégorie:Redirection de modèle - portail',
						cmstartsortkeyprefix: inputValue,
						cmtype: 'page',
						cmlimit: 200,
						formatversion: 2
					};

					const dataRedirects = await api.get( params );
					portailRedirects = dataRedirects.query.categorymembers.map( page => page.title.replace( /Modèle:Portail /, '' ) );
					allPages = data
						.concat( portailRedirects );
				} else {
					allPages = data;
				}
				BandeauxPortails_tagMultiselectWidget.allowedValues = [ ...new Set( [ ...BandeauxPortails_tagMultiselectWidget.allowedValues, ...allPages ] ) ];
				const collator = new Intl.Collator( 'fr', { sensitivity: 'base' });
				const suggestedPages = allPages
					.filter( portail => {
						const portailSlice = portail.slice( 0, inputValue.length );
						return collator.compare( portailSlice, inputValue ) === 0;
					})
					.sort( ( a, b ) => a.localeCompare( b, 'fr', { sensitivity: 'base' } ) );

				return suggestedPages.map( portail => new OO.ui.MenuOptionWidget({
					data: portail,
					label: portail,
					classes: portailRedirects.includes( portail ) ? [ 'redirection-portail' ] : []
				}));
			};
			PortalInputWidget.prototype.onLookupMenuChoose = function ( item ) {
				BandeauxPortails_tagMultiselectWidget.addTag( item.getData() );
				this.setValue( '' );
			};
			const BandeauxPortails_TagMultiselectWidget = function ( selected ) {
				OO.ui.TagMultiselectWidget.call( this, {
					inputWidget: new PortalInputWidget(),
					inputPosition: 'outline',
					selected: selected,
					allowedValues: selected,
					tagLimit: 20, // 20 portails max, cf la documentation et le code de [[Modèle:Portail]]
					classes: [ 'bandeauxportails-formulaire' ]
				});
			};
			OO.inheritClass( BandeauxPortails_TagMultiselectWidget, OO.ui.TagMultiselectWidget );
			BandeauxPortails_TagMultiselectWidget.prototype.createTagItemWidget = function ( data, label ) {
				// mise en forme des TagItemWidget en remplaçant le nom brut du portail
				// par le HTML parsé du modèle de portail concerné
				const tagWidget = new OO.ui.TagItemWidget( { data: data, label: label || data } );
				parse( '{{Portail ' + data + '|parent=bandeau}}' )
				.then( ( parsedContent ) => {
					const parsedContentStripped = parsedContent.replace(/<li>|<\/li>|<p>|<\/p>/g, '');
					tagWidget.$element.find( '.oo-ui-labelElement-label' ).html( parsedContentStripped );
				});
				return tagWidget;
			};

			const BandeauxPortails_showForm = async function () {
				if ( formOpen ) return;
				formOpen = true;

// Solution temporaire en attendant la résolution de [[phab:T414331]]
$( '.vector-sticky-header-container' ).appendTo( '#bodyContent' );

				// Lecture des paramètres du modèle {{Portail}}
				let anciensPortails = [];
				const data = await api.get({
					action: 'query',
					titles: mw.config.get( 'wgPageName' ),
					prop: 'revisions',
					rvprop: 'content',
					rvslots: 'main',
					formatversion: 2
				});

				let ancienTexte = data.query.pages[ 0 ].revisions[ 0 ].slots.main.content;

				const regexParamsPortail = /\{\{\s*[Pp]ortail(.*?)\}\}/;
				const matchPortail = ancienTexte.match( regexParamsPortail );
				if ( matchPortail ) {
					anciensPortails = matchPortail[ 1 ]
						.split( '|' )
						.map( param => param.trim() )
						.filter( param => param !== '' );
				}

				// Création du formulaire
				BandeauxPortails_tagMultiselectWidget = new BandeauxPortails_TagMultiselectWidget( anciensPortails );
				BandeauxPortails_layout = new OO.ui.FieldLayout( BandeauxPortails_tagMultiselectWidget ); // Le FieldLayout sert à afficher des avertissements.
				const bandeau = $( '#bandeau-portail' );
				bandeau.hide();
				bandeau.after( BandeauxPortails_layout.$element );

				const paramButton = new OO.ui.ButtonWidget( {
					label: 'Paramètres',
					icon: 'settings',
					classes: [ 'bouton-settings-bandeauportail' ]
				} );
				paramButton.on( 'click', () => openOptionsDialog() );

				const submitButton = document.createElement( 'input' );
				submitButton.type = 'button';
				submitButton.value = 'Enregistrer';
				submitButton.disabled = true;
				submitButton.className = 'cdx-button cdx-button--action-progressive cdx-button--weight-primary bouton-submit-bandeauportail';
				submitButton.addEventListener( 'click', () => {
					const nouveauxPortails = BandeauxPortails_tagMultiselectWidget.getValue();
					BandeauxPortails_Edit( anciensPortails, nouveauxPortails );
				});

				const cancelButton = document.createElement( 'input' );
				cancelButton.type = 'button';
				cancelButton.value = 'Annuler';
				cancelButton.className = 'cdx-button cdx-button--action-destructive cdx-button--weight-quiet bouton-submit-bandeauportail';
				cancelButton.addEventListener( 'click', () => {
					bandeau.show();
					BandeauxPortails_layout.$element.remove();
					paramButton.$element.remove();
					submitButton.remove();
					cancelButton.remove();
					formOpen = false;
				} );

				BandeauxPortails_layout.$element.after( submitButton, cancelButton, paramButton.$element );
				BandeauxPortails_tagMultiselectWidget.on( 'change', function () {
					const nouveauxPortails = BandeauxPortails_tagMultiselectWidget.getValue();
					const warnings = !BandeauxPortails_tagMultiselectWidget.isUnderLimit() ? [ 'Le nombre maximal de portails a été atteint.' ] : [];
					BandeauxPortails_layout.setWarnings( warnings );
					submitButton.disabled = JSON.stringify( nouveauxPortails ) === JSON.stringify( anciensPortails );
				});
			};

/////////////////////////////////////////////////////////////////////////////////// Gestion des préférences

// Utilisation d'un Process Dialog, voir [[mw:OOUI/Windows/Process Dialogs]]

			const BandeauxPortailsOptionsDialog = function ( config ) {
				BandeauxPortailsOptionsDialog.super.call( this, config );
			};
			OO.inheritClass( BandeauxPortailsOptionsDialog, OO.ui.ProcessDialog );
			BandeauxPortailsOptionsDialog.static.name = 'BandeauxPortailsOptionsDialog';
			BandeauxPortailsOptionsDialog.static.title = 'Préférences de BandeauxPortails';
			BandeauxPortailsOptionsDialog.static.actions = [{
				action: 'save',
				label: 'Valider',
				flags: [ 'primary', 'progressive' ]
			}, {
				label: 'Annuler',
				flags: 'safe'
			}];
			BandeauxPortailsOptionsDialog.prototype.initialize = function () {
				BandeauxPortailsOptionsDialog.super.prototype.initialize.apply( this );
				this.content = new OO.ui.PanelLayout({
					padded: true,
					expanded: false
				});
				const BandeauxPortailsSettings = new OO.ui.FieldsetLayout({
					label: 'Préférences'
				});
				const singleEditsCheckbox = new OO.ui.CheckboxInputWidget({
					disabled: typeof commonjsOptions.singleEdits !== 'undefined',
					selected: userOptions.singleEdits
				})
				this.singleEditsInput = new OO.ui.FieldLayout( singleEditsCheckbox, {
					align: 'inline',
					label: 'Afficher les boutons de modification individuels',
					help: 'En cochant cette option, les boutons de suppression, de changement et d\'ajout de portail à côté de chaque portail s\'afficheront.'
				});
				this.tailleListeInput = new OO.ui.FieldLayout( new OO.ui.NumberInputWidget( {
					disabled: typeof commonjsOptions.tailleListe !== 'undefined',
					input: { value: userOptions.tailleListe },
					min: 0,
					max: 500,
				}), {
					align: 'inline',
					label: 'Nombre de suggestions à afficher',
					help: 'Pour les boutons de modification individuels uniquement : nombre de suggestions à afficher dans les champs de saisie.',
					classes: !userOptions.singleEdits ? [ 'bandeauxportail-hidden-option' ] : []
				});
				this.listeBasInput = new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget({
					disabled: typeof commonjsOptions.listeBas !== 'undefined',
					selected: userOptions.listeBas
				}), {
					align: 'inline',
					label: 'Positionner les suggestions en dessous de la saisie',
					help: 'Pour les boutons de modification individuels uniquement : positionner les suggestions au dessus ou au dessous des champs de saisie.',
					classes: !userOptions.singleEdits ? [ 'bandeauxportail-hidden-option' ] : []
				});
				singleEditsCheckbox.on( 'change', ( selected ) => {
					if ( !selected ) {
						this.tailleListeInput.$element.addClass( 'bandeauxportail-hidden-option' );
						this.listeBasInput.$element.addClass( 'bandeauxportail-hidden-option' );
					} else {
						this.tailleListeInput.$element.removeClass( 'bandeauxportail-hidden-option' );
						this.listeBasInput.$element.removeClass( 'bandeauxportail-hidden-option' );
					}
				});
				this.autoLaunchInput = new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget({
					disabled: typeof commonjsOptions.autoLaunch !== 'undefined',
					selected: userOptions.autoLaunch
				}), {
					align: 'inline',
					label: 'Charger automatiquement le formulaire de modifications multiples',
					help: 'En cochant cette option, le formulaire de modification du modèle Portail sera automatiquement chargé dans les articles, sans avoir à cliquer sur le bouton « modifier ».'
				});
				this.autoEditInput = new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget({
					disabled: typeof commonjsOptions.autoEdit !== 'undefined',
					selected: userOptions.autoEdit
				}), {
					align: 'inline',
					label: 'Publication automatique',
					help: 'En décochant cette option, la modification des portails ne sera pas effectuée automatiquement en cliquant sur « Enregistrer ». À la place, l\'éditeur s\'ouvrira automatiquement avec le remplacement des portails déjà effectué (l\'édition manuelle des portails est incompatible avec l\'éditeur visuel).'
				});
				this.minorEditInput = new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget({
					disabled: typeof commonjsOptions.minorEdit !== 'undefined',
					selected: userOptions.minorEdit
				}), {
					align: 'inline',
					label: 'Marquer les modifications comme mineures',
					help: 'En cochant cette option, les modifications effectuées à l\'aide du gadget seront automatiquement marquées comme mineures.'
				});
				this.watchThisInput = new OO.ui.FieldLayout( new OO.ui.ButtonSelectWidget( {
					disabled: typeof commonjsOptions.watchthis !== 'undefined',
					items: [
						new OO.ui.ButtonOptionWidget( {
							selected: userOptions.watchthis === '-1',
							data: '-1',
							label: 'défaut préférences'
						} ),
						new OO.ui.ButtonOptionWidget( {
							selected: userOptions.watchthis === 0,
							data: 0,
							label: 'jamais'
						} ),
						new OO.ui.ButtonOptionWidget( {
							selected: userOptions.watchthis === 1,
							data: 1,
							label: 'toujours'
						} )
					]
				} ), {
					align: 'inline',
					label: 'Ajout à la liste de suivi',
					help: 'Sélection des modalités d\'ajout automatique des pages modifiées avec le gadget à la liste de suivi.'
				});
				this.showRedirectInput = new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget({
					disabled: typeof commonjsOptions.showRedirect !== 'undefined',
					selected: userOptions.showRedirect
				}), {
					align: 'inline',
					label: 'Affichage des redirections dans les suggestions',
					help: 'En cochant cette option, les alias de noms de portails seront affichés en italique dans les suggestions de saisie.'
				});
				this.skipConfirmInput = new OO.ui.FieldLayout( new OO.ui.CheckboxInputWidget({
					disabled: typeof commonjsOptions.skipConfirm !== 'undefined',
					selected: userOptions.skipConfirm
				}), {
					align: 'inline',
					label: 'Ne pas confirmer la suppression du bandeau',
					help: 'En cochant cette option, la boîte de dialogue demandant la confirmation de suppression ne s\'affichera plus.',
				});
				BandeauxPortailsSettings.addItems( [
					this.singleEditsInput,
					this.tailleListeInput,
					this.listeBasInput,
					this.autoLaunchInput,
					this.autoEditInput,
					this.minorEditInput,
					this.watchThisInput,
					this.showRedirectInput,
					this.skipConfirmInput,
				] );
				this.content.$element.append( BandeauxPortailsSettings.$element, '<hr><p>Si des paramètres ont été définis depuis votre <a href="https://fr.m.vvikipidea.org/wiki/Spécial:Ma page/common.js" title="Spécial:Ma page/common.js" target="_blank">common.js</a>, ils ne peuvent pas être modifiés ici. Pour plus d\'informations sur ce gadget, voir <a href="https://fr.m.vvikipidea.org/wiki/Projet:Scripts_et_gadgets/Notices/BandeauxPortails" title="Projet:Scripts et gadgets/Notices/BandeauxPortails" target="_blank">Projet:Scripts et gadgets/Notices/BandeauxPortails</a>.</p>' );
				this.$body.append( this.content.$element );
			};

			BandeauxPortailsOptionsDialog.prototype.getActionProcess = function ( action ) {
				const dialog = this;
				if ( action === 'save' ) {
					return new OO.ui.Process( function () {
						const changedOptions = {};
						if ( dialog.singleEditsInput.getField().selected !== defaultOptions.singleEdits ) { changedOptions.singleEdits = dialog.singleEditsInput.getField().selected }
						if ( dialog.tailleListeInput.getField().getNumericValue() !== defaultOptions.tailleListe ) { changedOptions.tailleListe = dialog.tailleListeInput.getField().getNumericValue() }
						if ( dialog.listeBasInput.getField().selected !== defaultOptions.listeBas ) { changedOptions.listeBas = dialog.listeBas.getField().selected }
						if ( dialog.autoLaunchInput.getField().selected !== defaultOptions.autoLaunch ) { changedOptions.autoLaunch = dialog.autoLaunchInput.getField().selected }
						if ( dialog.autoEditInput.getField().selected !== defaultOptions.autoEdit )     { changedOptions.autoEdit = dialog.autoEditInput.getField().selected }
						if ( dialog.minorEditInput.getField().selected !== defaultOptions.minorEdit )   { changedOptions.minorEdit = dialog.minorEditInput.getField().selected }
						if ( dialog.watchThisInput.getField().findFirstSelectedItem().data !== defaultOptions.watchthis ) { changedOptions.watchthis = dialog.watchThisInput.getField().findFirstSelectedItem().data }
						if ( dialog.showRedirectInput.getField().selected !== defaultOptions.showRedirect ) { changedOptions.showRedirect = dialog.showRedirectInput.getField().selected }
						if ( dialog.skipConfirmInput.getField().selected !== defaultOptions.skipConfirm ) { changedOptions.skipConfirm = dialog.SkipConfirmInput.getField().selected }
						userOptions = Object.assign( {}, defaultOptions, changedOptions, commonjsOptions );
						changedOptions.singleEdits = userOptions.singleEdits;
						api.saveOption( 'userjs-bandeauxPortails-settings', JSON.stringify( changedOptions ) );
						dialog.close({
							action: action
						});
					});
				}
				return BandeauxPortailsOptionsDialog.super.prototype.getActionProcess.call( this, action );
			};

			const openOptionsDialog = function () {
				const windowManager = new OO.ui.WindowManager();
				$( document.body ).append( windowManager.$element );
				const bandeauxPortailsOptionsDialog = new BandeauxPortailsOptionsDialog();
				windowManager.addWindows( [ bandeauxPortailsOptionsDialog ] );
				windowManager.openWindow( bandeauxPortailsOptionsDialog );
			};


/////////////////////////////////////////////////////////////////////////////////// Formulaires

			const BandeauxPortails_Cancel = function( id ) {
				const Span = document.getElementById( id );
				Span.style.display = 'inline';
				const Parent = Span.parentNode;
				const Form = Parent.getElementsByTagName( 'form' )[ 0 ];
				Form.remove();
			};

			const BandeauxPortails_SupprOne = async function( anciensPortails, portailSuppr ) {
				if ( userOptions.skipConfirm || await OO.ui.confirm( `Voulez-vous vraiment supprimer le portail « ${ portailSuppr } » ?` ) ) {
					const nouveauxPortails = anciensPortails.filter( portail => portail !== portailSuppr );
					BandeauxPortails_Edit( anciensPortails, nouveauxPortails );
				}
			};

			const BandeauxPortails_ModifyThatOne = function( anciensPortails, id ) {
				const input = document.getElementById( 'input-modify_' + id );
				if ( input.value !== '' && input.value !== anciensPortails[ id ] ) {
					const portailsAjoutes = input.value.split( '|' );
					const nouveauxPortails = [ ...anciensPortails.slice( 0, id ), ...portailsAjoutes, ...anciensPortails.slice( parseInt( id ) + 1 ) ];
					BandeauxPortails_Edit( anciensPortails, nouveauxPortails );
				} else {
					BandeauxPortails_Cancel( 'lien-portail_' + id );
				}
			};

			const BandeauxPortails_ModifyOne = function( anciensPortails, id ) {
				const span = document.getElementById( 'lien-portail_' + id );
				span.style.display = 'none';
				const form = document.createElement( 'form' );
				form.addEventListener( 'submit', ( event ) => {
					event.preventDefault();
					BandeauxPortails_ModifyThatOne( anciensPortails, id );
				});

				form.style.display = 'inline';

				const select = document.createElement( 'select' );
				select.id = 'select-modify_' + id;
				select.style.display = 'none';
				select.title = 'Suggestions de bandeaux';

				const textInput = document.createElement( 'input' );
				textInput.id = 'input-modify_' + id;
				textInput.type = 'text';
				textInput.value = anciensPortails[ id ];
				textInput.onkeyup = () => BandeauxPortails_TextChanged( 'select-modify_' + id );

				const okButton = document.createElement( 'input' );
				okButton.type = 'button';
				okButton.className = 'cdx-button cdx-button--action-progressive cdx-button--weight-primary cdx-button--size-small';
				okButton.addEventListener( 'click', () => BandeauxPortails_ModifyThatOne( anciensPortails, id ) );
				okButton.addEventListener( 'select', () => BandeauxPortails_ModifyThatOne( anciensPortails, id ) );
				okButton.value = 'OK';

				const cancelButton = document.createElement( 'input' );
				cancelButton.type = 'button';
				cancelButton.className = 'cdx-button cdx-button--action-destructive cdx-button--weight-quiet cdx-button--size-small';
				cancelButton.addEventListener( 'click', () => BandeauxPortails_Cancel( 'lien-portail_' + id ) );
				cancelButton.addEventListener( 'select', () => BandeauxPortails_Cancel( 'lien-portail_' + id ) );
				cancelButton.value = 'Annuler';

				form.append( select, textInput, okButton, cancelButton );
				span.before( form );
				document.getElementById( 'input-modify_' + id ).focus() ;
			};

			const BandeauxPortails_AddThatOne = function( anciensPortails ) {
				const input = document.getElementById( 'input-add' );
				if ( input.value !== '' ) {
					const portailsAjoutes = input.value.split( '|' );
					BandeauxPortails_Edit( anciensPortails, [ ...anciensPortails, ...portailsAjoutes ] );
				} else {
					BandeauxPortails_Cancel( 'BandeauxPortails_Add' );
				}
			};

			const BandeauxPortails_AddOne = function ( anciensPortails ) {
				const span = document.getElementById( 'BandeauxPortails_Add' );
				span.style.display = 'none';
				const form = document.createElement( 'form' );
				form.style.display = 'inline';				
				form.addEventListener( 'submit', ( event ) => {
					event.preventDefault();
					BandeauxPortails_AddThatOne( anciensPortails );
				});

				const selectElement = document.createElement( 'select' );
				selectElement.id = 'select-add';
				selectElement.style.display = 'none';
				selectElement.title = 'Suggestions de bandeaux';

				const inputTextElement = document.createElement( 'input' );
				inputTextElement.id = 'input-add';
				inputTextElement.type = 'text';
				inputTextElement.value = '';
				inputTextElement.onkeyup = () => BandeauxPortails_TextChanged( 'select-add' );

				const buttonOkElement = document.createElement( 'input' );
				buttonOkElement.type = 'button';
				buttonOkElement.className = 'cdx-button cdx-button--action-progressive cdx-button--weight-primary cdx-button--size-small';
				buttonOkElement.addEventListener( 'click', () => BandeauxPortails_AddThatOne( anciensPortails ) );
				buttonOkElement.addEventListener( 'select', () => BandeauxPortails_AddThatOne( anciensPortails ) );
				buttonOkElement.value = 'OK';

				const buttonCancelElement = document.createElement( 'input' );
				buttonCancelElement.type = 'button';
				buttonCancelElement.className = 'cdx-button cdx-button--action-destructive cdx-button--weight-quiet cdx-button--size-small';
				buttonCancelElement.addEventListener( 'click', () => BandeauxPortails_Cancel( 'BandeauxPortails_Add' ) );
				buttonCancelElement.addEventListener( 'select', () => BandeauxPortails_Cancel( 'BandeauxPortails_Add' ) );
				buttonCancelElement.value = 'Annuler';

				form.append( selectElement, inputTextElement, buttonOkElement, buttonCancelElement );
				span.before( form );
				document.getElementById( 'input-add' ).focus() ;

			};

/////////////////////////////////////////////////////////////////////////////////// Suggestions des saisies individuelles

			const BandeauxPortails_TextChanged = async function ( id ) {
				if ( userOptions.tailleListe === 0 ) return;
				const select = document.getElementById( id ) ;
				const idInput = id.replace( 'select', 'input' );
				const input = document.getElementById( idInput ) ;
				if ( !input || !select ) return;
				const value = input.value;
				if ( value === '' ) return;

				const allParams  = value.split( '|' );
				// Récupération de la liste des suggestions
				const params = {
					action: 'query',
					list: 'categorymembers',
					cmtitle: 'Catégorie:Modèle lien vers portail/Liste complète',
					cmstartsortkeyprefix: allParams[ allParams.length - 1 ],
					cmtype: 'page',
					cmlimit: userOptions.tailleListe,
					formatversion: 2
				};
				const data = await api.get( params );
				const allPages = data.query.categorymembers.map( page => page.title.replace( /Modèle:Portail /, '' ) );
				let portailRedirects = [];
				if ( userOptions.showRedirect ) {
					params.cmtitle = 'Catégorie:Redirection de modèle - portail';
					const dataRedirects = await api.get( params );
					portailRedirects = dataRedirects.query.categorymembers.map( page => page.title.replace( /Modèle:Portail /, '' ) );
					allPages.push( ...portailRedirects );
				}
				const resultArray = allPages
					.sort( ( a, b ) => a.localeCompare( b, 'fr', { sensitivity: 'base' } ) )
					.slice( 0, userOptions.tailleListe );

				// Affichage des suggestions
				select.innerHTML = '';
				for ( const titrePage of resultArray ) {
					const option = document.createElement( 'option' );
					option.value = titrePage;
					option.innerHTML = titrePage;
					if ( portailRedirects.includes( titrePage ) ) option.style.fontStyle = 'italic';
					select.append( option );
				}
				if ( select.length > 0 ) {
					select.addEventListener( 'change', function () {
						const titrePage = this.value;
						const allParams = value.split( '|' );
						allParams[ allParams.length - 1 ] = titrePage
						input.value = allParams.join( '|' );
						input.focus() ;
					});
					select.size = select.length;
					select.style.display = 'inline';
					select.style.zIndex = 5 ;
					select.style.position = 'absolute' ;
					select.style.width = input.offsetWidth + 'px' ;

					if ( userOptions.listeBas ) {
						select.style.top = ( parseInt( input.offsetTop ) + parseInt( input.offsetHeight ) ) + 'px';
					} else {
						select.style.top = ( parseInt( input.offsetTop ) - select.offsetHeight ) + 'px';
					}
					select.style.left = ( parseInt( input.parentNode.offsetLeft ) - 1 ) + 'px';
				} else {
					select.style.display = 'none';
					select.innerHTML = '';
				}
			};

/////////////////////////////////////////////////////////////////////////////////// Fonction active au chargement de la page (saisie individuelle)
			
			const BandeauxPortails_ModifyUl = async function () {
				const bandeauUl = document.getElementById( 'bandeau-portail' );
				if ( !bandeauUl ) return;

				// Ajout des boutons (-) et (±) pour chaque portail
				const contenu = await getPageContent();

				let anciensPortails = [];
				const regexParamsPortail = /\{\{\s*[Pp]ortail(.*?)\}\}/;
				const match = contenu.match( regexParamsPortail );
				if ( match ) {
					anciensPortails = match[ 1 ]
						.split( '|' )
						.map( param => param.trim() )
						.filter( param => param !== '' );
	
					const portailItems = bandeauUl.querySelectorAll( ':scope > li, :scope > a.new' );
					for ( const [ i, param ] of Object.entries( anciensPortails ) ) {
						// Les éléments du bandeau du DOM correspondent aux portails du wikicode
						// y compris en cas de paramètre vide et de portail incorrect (lien rouge)
						const portailItem = portailItems[ i ];
						const lien = portailItem.querySelector( '.bandeau-portail-texte a' );
						let titre = '';
						let spanLien;
						if ( lien ) {
							titre = lien.title.replace( 'Portail:', '' );
							spanLien = lien.parentNode;
						} else {
							const lienRouge = portailItem;
							titre = lienRouge.innerHTML.replace( 'Modèle:Portail', '' );
							spanLien = document.createElement( 'span' );
						}
						spanLien.id = 'lien-portail_' + i;
	
						const deleteLink = document.createElement( 'a' );
						deleteLink.href = '#';
						deleteLink.textContent = '(-)';
						deleteLink.title = 'Supprimer le bandeau de portail ' + titre;
						deleteLink.addEventListener( 'click', function ( e ) {
							e.preventDefault();
							BandeauxPortails_SupprOne( anciensPortails, param );
						});
						const modifyLink = document.createElement( 'a' );
						modifyLink.href = '#';
						modifyLink.textContent = '(±)';
						modifyLink.title = 'Modifier le bandeau de portail ' + titre;
						modifyLink.addEventListener( 'click', function ( e ) {
							e.preventDefault();
							BandeauxPortails_ModifyOne( anciensPortails, i );
						});
						spanLien.append( ' • ', deleteLink, ' • ', modifyLink );
						if ( !lien ) {
							const lienRouge = portailItem;
							const newLi = document.createElement( 'li' );
							lienRouge.replaceWith( newLi );
							newLi.append( lienRouge, spanLien );
						}
					}
				}

				// Ajout du bouton (+)
				const addLi = document.createElement( 'li' );
				addLi.id = 'lien-portail_add';

				const span = document.createElement( 'span' );
				span.className = 'bandeau-portail-element';

				const spanTexte = document.createElement( 'span' );
				spanTexte.className = 'bandeau-portail-texte';
				spanTexte.id = 'BandeauxPortails_Add';

				const linkElement = document.createElement( 'a' );
				linkElement.textContent = '(+)';
				linkElement.title = 'Ajouter un bandeau de portail';
				linkElement.addEventListener( 'click', () => BandeauxPortails_AddOne( anciensPortails ) );

				spanTexte.append( linkElement );
				span.append( spanTexte );

				addLi.append( span );

				bandeauUl.appendChild( addLi );
			};
/////////////////////////////////////////////////////////////////////////////////// Initialisation au chargement de la page

			const homonymie = document.getElementById( 'homonymie' );
			let bandeauUl = document.getElementById( 'bandeau-portail' );
			if ( homonymie && !bandeauUl ) return;

			if ( !bandeauUl ) {
				const catLinks = document.getElementById( 'catlinks' );
				if ( !catLinks ) return;
				bandeauUl = document.createElement( 'ul' );
				bandeauUl.id = 'bandeau-portail';
				bandeauUl.className = 'bandeau-portail';
				bandeauUl.innerHTML = '<span class="bandeau-portail-vide">Aucun portail.</span>';
				catLinks.before( bandeauUl );
			}
			if ( userOptions.singleEdits ) {
				BandeauxPortails_ModifyUl();
			}
			if ( userOptions.autoLaunch ) {
				BandeauxPortails_showForm();
			}
			// Le bouton « modifier » est ajouté même pour le chargement automatique pour le cas où l'utilisateur clique sur « annuler »
			const editA = document.createElement( 'a' );
			editA.title = 'Modifier le bandeau portails';
			editA.setAttribute( 'role', 'button' );
			editA.addEventListener( 'click', () => BandeauxPortails_showForm() );

			const editSpan = document.createElement( 'span' );
			editSpan.className = 'bandeau-portail-edit mw-editsection-like';

			if ( mw.config.get( 'skin' ) === 'minerva' ) {
				editA.className = 'cdx-button cdx-button--size-small cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--icon-only cdx-button--weight-quiet mw-editsection-visualeditor';

				const spanIcon = document.createElement( 'span' );
				spanIcon.className = 'minerva-icon minerva-icon--edit';

				const spanModifier = document.createElement( 'span' );
				spanModifier.innerHTML = 'modifier';

				editA.append( spanIcon, spanModifier );
				editSpan.append( editA );
			} else {
				editA.innerHTML = 'modifier';

				const editLeftBracket = document.createElement( 'span' );
				editLeftBracket.className = 'mw-editsection-bracket';
				editLeftBracket.innerHTML = '[';
	
				const editRightBracket = document.createElement( 'span' );
				editRightBracket.className = 'mw-editsection-bracket';
				editRightBracket.innerHTML = ']';

				editSpan.append( editLeftBracket, editA, editRightBracket );
			}

			bandeauUl.prepend( editSpan );

		});
	} );
}

/////////////////////////////////////////////////////////////////////////////////// Édition manuelle de la page lorsque userOptions.autoEdit === false

const BandeauxPortails_manualEdit = function () {
	const newText = mw.storage.get( 'bandeaux-portails-newtext' );
	if ( newText === null ) return;
	const summary = mw.storage.get( 'bandeaux-portails-summary' ) || '';
	mw.storage.remove( 'bandeaux-portails-newtext' );
	mw.storage.remove( 'bandeaux-portails-summary' );
	$( '#wpTextbox1' ).textSelection( 'setContents', newText ); // éditeurs de wikicode bureau
	$( '#wikitext-editor' ).textSelection( 'setContents', newText ); // éditeur de wikicode mobile
	/*if ( usesVe ) {
		// éditeur visuel
		// globals ve
		const surface = ve.init.target.getSurface();
		if ( surface.getMode() === 'visual' ) {
			const surfaceModel = ve.init.target.getSurface().getModel();
			const rangeToRemove = surfaceModel.getDocument().getDocumentRange();
			const fragment = surfaceModel.getLinearFragment( rangeToRemove );
			fragment.insertContent( newText );
		}
	}*/
	$( '#wpSummary' ).textSelection( 'setContents', summary );
	$( '#wpMinoredit' ).prop( { 'checked': userOptions.minorEdit } );
	if ( userOptions.watchthis === 1 ) $( '#wpWatchthis' ).prop( { 'checked': true } );
	if ( userOptions.watchthis === 0 ) $( '#wpWatchthis' ).prop( { 'checked': false } );
};

if ( ( mw.config.get( 'wgAction' ) === 'edit' && userOptions.autoEdit === false && mw.config.get( 'wgNamespaceNumber' ) === 0 ) ) {
	mw.loader.using( [ 'jquery.textSelection', 'mediawiki.storage' ], function () {
		mw.hook( 'wikipage.editform' ).add( () => BandeauxPortails_manualEdit() );
		mw.hook( 've.activationComplete' ).add( () => BandeauxPortails_manualEdit() );
		// Le hook mobile est marqué comme interne et est susceptible de changer sans préavis.
		mw.hook( 'mobileFrontend.editorOpened' ).add( () => BandeauxPortails_manualEdit() );
	});
}

//</nowiki></pre>
// ====================================================================================================================
// ==================================================================================================================== FIN DU CODE
//</syntaxhighlight>