MediaWiki:Gadget-CustomSidebar.js: Difference between revisions
Jump to navigation
Jump to search
Wikipédia>0x010C quick fix pour l'affichage du gadget avec l'environement du nouveau thème Vector |
m 1 revision imported |
||
(No difference)
|
Latest revision as of 12:55, 2 May 2024
/*
* CustomSidebar
*
* ...
*
* {{Projet:JavaScript/Script|CustomSidebar}}
*/
/* <nowiki> */
// Note for cleanup after debugging - To delete the saved sidebar:
// new mw.Api().saveOption( 'userjs-customsidebar', null );
/* globals mw, OO, $ */
mw.loader.using( [ 'mediawiki.util', 'oojs-ui.styles.icons-interactions', 'oojs-ui-widgets' ], function () {
'use strict';
// Site-related parameters
const PREFID = 'userjs-customsidebar',
PORTLETID = 'p-cs';
const MAGIC_WORDS = {
day: 'jour',
week: 'semaine',
month: 'mois',
monthname: 'nom du mois',
year: 'année',
page: 'page',
title: 'titre',
diff: 'diff',
user: 'pseudo'
};
const MONTH_NAMES = [
'janvier',
'février',
'mars',
'avril',
'mai',
'juin',
'juillet',
'août',
'septembre',
'octobre',
'novembre',
'décembre'
];
const deltaDayRegex = new RegExp( '{ *' + MAGIC_WORDS.day + '([\+\-][0-9]+) *}' ),
deltaWeekRegex = new RegExp( '{ *' + MAGIC_WORDS.week + '([\+\-][0-9]+) *}' ),
deltaMonthRegex = new RegExp( '{ *(?:' + MAGIC_WORDS.month + '|' + MAGIC_WORDS.monthname + ')([\+\-][0-9]+) *}' ),
deltaYearRegex = new RegExp( '{ *' + MAGIC_WORDS.year + '([\+\-][0-9]+) *}' ),
dayRegex = new RegExp( '{ *' + MAGIC_WORDS.day + '([\+\-][0-9]+)* *}', 'g' ),
weekRegex = new RegExp( '{ *' + MAGIC_WORDS.week + '([\+\-][0-9]+)* *}', 'g' ),
monthRegex = new RegExp( '{ *' + MAGIC_WORDS.month + '([\+\-][0-9]+)* *}', 'g' ),
monthnameRegex = new RegExp( '{ *' + MAGIC_WORDS.monthname + '([\+\-][0-9]+)* *}', 'g' ),
yearRegex = new RegExp( '{ *' + MAGIC_WORDS.year + '([\+\-][0-9]+)* *}', 'g' ),
pageRegex = new RegExp( '{ *' + MAGIC_WORDS.page + ' *}', 'g' ),
titleRegex = new RegExp( '{ *' + MAGIC_WORDS.title + ' *}', 'g' ),
diffRegex = new RegExp( '{ *' + MAGIC_WORDS.diff + ' *}', 'g' ),
userRegex = new RegExp( '{ *' + MAGIC_WORDS.user + ' *}', 'g' );
// global vars
var $links,
links = [];
var isBootstrapped = false;
var instanceWindowManager;
var instanceSidebarEditor;
/**
* I18N Messages
*/
const messages = {
'fr': {
'cside-portlet-label': 'Mes liens',
'cside-edit-title': 'Ajouter et supprimer les liens',
'cside-edit-label': 'Modifier les liens',
'sidebareditor-title': 'Personnaliser le menu latéral',
'sidebareditor-nolinkmessage': 'Aucun lien présent.<br>Ajoutez-en via le bouton ci-dessous !',
'sidebareditor-action-save': 'Sauvegarder',
'sidebareditor-action-cancel': 'Annuler',
'sidebareditor-action-add': 'Ajouter',
'sidebareditor-action-delete': 'Supprimer',
'sidebareditor-notif-success': 'Le nouveau menu latéral a été sauvegardé.',
'cside-linkfield-label': 'Texte',
'cside-linkfield-target': 'Lien cible',
'cside-linkfield-target-help': 'Ce champ peut contenir au choix un lien interne ou externe. Des mots magiques permettent de changer dynamiquement certaines valeurs : {$1}, {$2}, {$3}, {$4}, {$5}, {$6}, {$7}, {$8} et {$9}. Les 5 premiers mots magiques peuvent être accompagnés d\'un nombre pour décaler la période ; par exemple {$1-2} pour avant-hier ou {$4+1} pour le mois prochain.',
}
};
mw.messages.set( messages.fr );
var lang = mw.config.get( 'wgUserLanguage' );
if ( lang !== 'fr' && lang in messages ) {
mw.messages.set( messages[ lang ] );
}
/**
* ...
*/
function fetchLinks() {
var fetchedLinks,
raw = mw.user.options.get( PREFID );
// If there is nothing stored, default to an empty array
if ( raw === null ) {
return [];
}
fetchedLinks = JSON.parse( raw );
if ( ! Array.isArray( fetchedLinks ) ) {
return [];
}
return fetchedLinks;
}
function createPortlet() {
var $portlet = $( '<div class="vector-main-menu-group vector-menu mw-portlet mw-portlet-tb vector-menu-portal portal" role="navigation" id="' + PORTLETID + '" aria-labelledby="' + PORTLETID + '-label">' ),
editButton = new OO.ui.ButtonWidget( {
framed: false,
icon: 'settings',
label: mw.msg( 'cside-edit-label' ),
invisibleLabel: true,
title: mw.msg( 'cside-edit-title' ),
classes: [ 'cs-editbutton' ]
} ),
$title = $( '<p class="vector-menu-heading"><span class="vector-menu-heading-label" id="' + PORTLETID + '-label">' ).text( mw.msg( 'cside-portlet-label' ) ),
$wrapper = $( '<div class="vector-menu-content">' );
$portlet.append( editButton.$element, $title, $wrapper );
$links = $( '<ul class="vector-menu-content-list">' );
$wrapper.append( $links );
// Manage events
editButton.on( 'click', function() {
mw.loader.using( [ 'oojs-ui', 'mediawiki.notification' ], function() {
bootstrapOnce();
instanceSidebarEditor.open();
} );
} );
// Add the new portlet to the DOM
$( '#p-tb' ).after( $portlet );
}
// Returns the ISO week of the date.
function getWeek( d ) {
var date = new Date(d.getTime());
date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
// January 4 is always in week 1.
var week1 = new Date(date.getFullYear(), 0, 4);
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
}
function daysInYear( year ) {
if ( year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ) {
return 366;
}
return 365;
}
function daysInMonth( month, year ) {
return new Date( year, month + 1, 0 ).getDate();
}
function fillPortlet( links ) {
var i, j, target, today, deltaDay, deltaWeek, deltaMonth, deltaYear, coef, offset;
// Empty the portlet in case of a second run
$links.empty();
// Fill the portlet with all the given links
for ( i = 0; i < links.length; i++ ) {
target = links[ i ][ 0 ];
today = new Date();
// Change the date if asked to
deltaDay = parseInt( ( deltaDayRegex.exec( target ) || [0,0])[1]);
deltaWeek = parseInt( ( deltaWeekRegex.exec( target ) || [0,0])[1]);
deltaMonth = parseInt( ( deltaMonthRegex.exec( target ) || [0,0])[1]);
deltaYear = parseInt( ( deltaYearRegex.exec( target ) || [0,0])[1]);
if ( mw.config.get( 'debug' ) === true ) {
console.log( '===', links[ i ][ 1 ] );
console.log( 'deltaDay', deltaDay );
console.log( 'deltaWeek', deltaWeek );
console.log( 'deltaMonth', deltaMonth );
console.log( 'deltaYear', deltaYear );
}
today.setTime( today.getTime() + ( ( deltaDay + ( deltaWeek * 7 ) ) * 24 * 3600 * 1000 ) );
coef = deltaMonth < 0 ? -1 : 1;
offset = deltaMonth < 0 ? -1 : 0;
for ( j = Math.abs( deltaMonth ); j > 0; j--) {
today.setTime( today.getTime() + ( coef * daysInMonth( today.getMonth() + offset, today.getYear() ) * 24 * 3600 * 1000 ) );
}
coef = deltaYear < 0 ? -1 : 1;
offset = deltaYear < 0 ? -1 : 0;
for ( j = Math.abs( deltaYear ); j > 0; j--) {
today.setTime( today.getTime() + ( coef * daysInYear( today.getYear() + offset ) * 24 * 3600 * 1000 ) );
}
// Render magic words
target = target.replace( dayRegex, today.getDate() );
target = target.replace( weekRegex, getWeek( today ) );
target = target.replace( monthRegex, ( today.getMonth() + 1 ) );
target = target.replace( monthnameRegex, MONTH_NAMES[ today.getMonth() ] );
target = target.replace( yearRegex, today.getFullYear() );
target = target.replace( pageRegex, mw.config.get( 'wgPageName' ) );
target = target.replace( titleRegex, mw.config.get( 'wgTitle' ) );
target = target.replace( diffRegex, mw.config.get( 'wgRevisionId' ) );
target = target.replace( userRegex, mw.config.get( 'wgRelevantUserName' ) || '' );
// Convert internal links to URL
if ( ! ( target.lastIndexOf( 'https://', 0) === 0 || target.lastIndexOf( 'http://', 0) === 0 ) ) {
target = mw.util.getUrl( target );
}
mw.util.addPortletLink( PORTLETID, target, links[ i ][ 1 ] );
}
}
/**
* Main function
*/
$( function ( $ ) {
links = fetchLinks();
createPortlet();
fillPortlet( links );
} );
// Instanciate SidebarEditor and add it to MediaWiki's UI.
function bootstrapOnce() {
if (isBootstrapped) {
return;
}
isBootstrapped = true;
/**
* Main class of the configuration windows SidebarEditor,
* which is displayed as a ProcessDialog
*
* @class
* @extends OO.ui.ProcessDialog
*
* @constructor
*/
var SidebarEditor = function () {
// Initialize config
var config = { size: 'large' };
// Parent constructor
SidebarEditor.parent.call( this, config );
// Properties
this.api = new mw.Api( { timeout: 7000 } );
// Graphical properties
this.linkFields = [];
this.$noLinkMessage = $( '<p class="cs-nolinkmessage">' ).html( mw.msg( 'sidebareditor-nolinkmessage' ) );
this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
this.layout = new OO.ui.Widget( { content: [] } );
this.$body;
};
/* Setup */
OO.inheritClass( SidebarEditor, OO.ui.ProcessDialog );
/* Static Properties */
SidebarEditor.static.name = 'sidebareditor';
SidebarEditor.static.title = mw.msg( 'sidebareditor-title' );
SidebarEditor.static.actions = [
{ action: 'save', label: mw.msg( 'sidebareditor-action-save' ), flags: [ 'primary', 'progressive' ] },
{ action: 'cancel', label: mw.msg( 'sidebareditor-action-cancel' ), flags: [ 'safe', 'back' ] },
{ action: 'add', label: mw.msg( 'sidebareditor-action-add' ), flags: 'other' }
];
/* ProcessDialog-related Methods */
/**
* Build the interface displayed inside the ProcessDialog box.
*/
SidebarEditor.prototype.initialize = function () {
var i = 0;
SidebarEditor.parent.prototype.initialize.apply( this, arguments );
for ( i = 0; i < links.length; i++ ) {
this.addField( links[ i ][ 0 ], links[ i ][ 1 ] );
}
this.content.$element.append( this.$noLinkMessage );
this.content.$element.append( this.layout.$element );
this.$body.append( this.content.$element );
this.setSize( this.size );
this.updateSize();
};
/**
* Get a process for taking action.
*
* This method is called within the ProcessDialog when the user clicks
* on an action button (the one defined in SidebarEditor.static.actions).
* @param {string} action Name of the action button clicked.
* @return {OO.ui.Process} Action process.
*/
SidebarEditor.prototype.getActionProcess = function ( action ) {
var process = new OO.ui.Process();
if ( action === 'cancel' || action === '' ) { // empty string when closing with Escape key
process.next( this.closeDialog, this );
}
else if ( action === 'add' ) {
process.next( this.addField, this );
}
else if ( action === 'save' ) {
process.next( this.save, this )
.next( this.success, this )
.next( this.closeDialog, this );
}
return process;
};
/**
* Close the window.
*
* @return {jQuery.Promise} Promise resolved when window is closed
*/
SidebarEditor.prototype.closeDialog = function () {
var dialog = this;
var lifecycle = dialog.close();
return lifecycle.closed;
};
/**
* Get the height of the window body.
* Used by the ProcessDialog to set an accurate height to the dialog.
*
* @return {number} Height in px the dialog should be.
*/
SidebarEditor.prototype.getBodyHeight = function () {
return 400; //this.content.$element.outerHeight( true );
};
/* Process step methods */
SidebarEditor.prototype.addField = function ( target, label ) {
var i = this.linkFields.push( new LinkField( target, label ) );
this.linkFields[ i - 1 ].on( 'delete', this.removeField.bind( this, this.linkFields[ i - 1 ] ) );
this.layout.$element.append( this.linkFields[ i - 1 ].$element );
this.$noLinkMessage.hide();
};
SidebarEditor.prototype.removeField = function ( field ) {
var i = this.linkFields.indexOf( field );
this.linkFields[ i ].$element.detach();
this.linkFields.splice( i, 1 );
if ( this.linkFields.length === 0 ) {
this.$noLinkMessage.show();
}
};
SidebarEditor.prototype.save = function () {
var i;
this.newLinks = [];
for ( i = 0; i < this.linkFields.length; i++ ) {
this.newLinks.push( this.linkFields[ i ].getValue() );
}
return this.api.saveOption( PREFID, JSON.stringify( this.newLinks ) );
};
SidebarEditor.prototype.success = function () {
// Reload the sidebar with the new links
fillPortlet( this.newLinks );
links = this.newLinks;
// Notify the user that it's a success!
mw.notification.notify( mw.msg( 'sidebareditor-notif-success' ), { autoHide: true } );
};
/**
* ...
*
* @class
* @extends OO.ui.Widget
*
* @constructor
*/
var LinkField = function ( target, label, config ) {
// Initialize config
config = config || {};
// Parent constructor
LinkField.parent.call( this, config );
// Properties
this.api = new mw.Api( { timeout: 15000 } );
// Graphical properties
this.targetTextInput = new OO.ui.TextInputWidget( {
value: target,
} );
this.labelTextInput = new OO.ui.TextInputWidget( {
value: label,
} );
this.deleteButton = new OO.ui.ButtonWidget( {
icon: 'trash',
framed: false,
label: mw.msg( 'sidebareditor-action-delete' ),
invisibleLabel: true,
title: mw.msg( 'sidebareditor-action-delete' ),
flags: [ 'destructive' ]
} );
// Layouts
var line1 = new OO.ui.ActionFieldLayout( this.labelTextInput, this.deleteButton, {
align: 'left',
label: mw.msg( 'cside-linkfield-label' )
} );
var line2 = new OO.ui.FieldLayout( this.targetTextInput, {
align: 'left',
label: mw.msg( 'cside-linkfield-target' ),
help: mw.msg( 'cside-linkfield-target-help', MAGIC_WORDS.day, MAGIC_WORDS.week, MAGIC_WORDS.month, MAGIC_WORDS.monthname, MAGIC_WORDS.year, MAGIC_WORDS.page, MAGIC_WORDS.title, MAGIC_WORDS.diff, MAGIC_WORDS.user )
} );
// Events
this.deleteButton.on( 'click', this.emit.bind( this, 'delete' ) );
// Add all those widgets to the layout
this.$element.append( line1.$element )
.append( line2.$element );
this.$element.addClass( 'cs-linkfield' );
};
/* Setup */
OO.inheritClass( LinkField, OO.ui.Widget );
LinkField.prototype.getValue = function () {
return [ this.targetTextInput.getValue(), this.labelTextInput.getValue() ];
};
instanceWindowManager = new OO.ui.WindowManager();
$( 'body' ).append( instanceWindowManager.$element );
instanceSidebarEditor = new SidebarEditor();
instanceWindowManager.addWindows( [ instanceSidebarEditor ] );
}
} );
/* </nowiki> */