User:Putnik/Events-calendar-editor.js

From Meta, a Wikimedia project coordination wiki

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/*
* EventsEditor
*
* Allow the user to easily add new events to an [[Template:Events calendar]].
*
* Author: [[User:0x010C]], [[User:Putnik]]
*/
//<nowiki>

( function ( mw ) {
mw.loader.using( [ 'oojs-ui', 'mediawiki.util', 'mediawiki.widgets.datetime' ], function() {
	var messages = ( function () {
	    var translations = {
	      en: {
	      	addEvent: 'Add an event',
	      	add: 'Add!',
	      	cancel: 'Cancel',
	      	generalInformation: 'General information',
	      	eventDetails: 'Event details:',
	      	name: 'Name',
	      	dateStart: 'Start date',
	      	dateEnd: 'End date',
	      	description: 'Description',
	      	link: 'Event wikipage',
	      	location: 'Location',
	      	locationHelp: 'If your city is not in the list, it must be added to the list [[Events calendar/parameters.json]]',
	      	tags: 'Tags',
	      	languageCodes: 'Language codes',
	      	repeatRules: 'Repeat rules',
	      	repeat: 'Repeat event',
	      	repeatHelp: 'Event will be shown in the calendar several times in accordance with the recurrence conditions',
	      	frequency: 'Frequency',
	      	frequencyHelp: 'Period after which event will occur again; can be extended by the Interval parameter',
	      	weekly: 'Weekly',
	      	interval: 'Interval',
	      	intervalHelp: 'Allows to specify that the event occurs once in N periods, e.g. every 2 weeks',
	      	dateUntil: 'Until date',
	      	dateUntilHelp: 'The last date when event will take place (optional)',
	      },
	      fr: {
	      	addEvent: 'Ajouter un évènement',
	      	add: 'Ajouter !',
	      	cancel: 'Annuler',
	      	generalInformation: 'Informations générales',
	      	eventDetails: 'Détails de l\'évènement :',
	      	name: 'Nom',
	      	dateStart: 'Date de début',
	      	dateEnd: 'Date de fin',
	      	description: 'Description',
	      	link: 'Page wiki de l\'évènement',
	      	location: 'Lieu',
	      	locationHelp: 'Si votre ville n\'est pas dans la liste, il faut l\'ajouter à la liste [[Events calendar/parameters.json]]',
	      	tags: 'Tags',
	      },
	      ru: {
	      	addEvent: 'Добавить событие',
	      	add: 'Добавить!',
	      	cancel: 'Отменить',
	      	generalInformation: 'Общая информация',
	      	eventDetails: 'Детали события:',
	      	name: 'Название',
	      	dateStart: 'Дата начала',
	      	dateEnd: 'Дата окончания',
	      	description: 'Описание',
	      	link: 'Вики-страница события',
	      	location: 'Место',
	      	locationHelp: 'Если вашего города нет в списке, его необходимо добавить в список [[Events calendar/parameters.json]]',
	      	tags: 'Тэги',
	      	languageCodes: 'Коды языков',
	      	repeatRules: 'Правила повтора',
	      	repeat: 'Повторять событие',
	      	repeatHelp: 'Событие будет показано в календаре несколько раз в соответствии с условиями повторения',
	      	frequency: 'Частота',
	      	frequencyHelp: 'Период, через который событие произойдет снова; может быть расширено параметром «Интервал»',
  	      	weekly: 'Еженедельно',
	      	interval: 'Интервал',
	      	intervalHelp: 'Позволяет указать, что событие происходит один раз в N периодов, например каждые 2 недели',
	      	dateUntil: 'До даты',
	      	dateUntilHelp: 'Последняя дата проведения мероприятия (необязательно)',
	      }
	    },
		chain = mw.language.getFallbackLanguageChain(),
		len = chain.length,
		ret = {},
		i = len - 1;
		while ( i >= 0 ) {
			if ( translations.hasOwnProperty( chain[i] ) ) {
				$.extend( ret, translations[chain[i]] );
			}
			i--;
		}
		return ret;
	}() );

	function EventsEditor( config ) {
		EventsEditor.parent.call( this, config );
	}

	OO.inheritClass( EventsEditor, OO.ui.ProcessDialog );

	EventsEditor.static.name = 'eventsEditorDialog';
	EventsEditor.static.title = messages.addEvent;
	EventsEditor.static.size = 'larger';
	EventsEditor.static.height = 400;
	EventsEditor.static.actions = [
		{ label: messages.add, action: 'save', flags: [ 'primary', 'progressive' ] },
		{ label: messages.cancel, flags: 'safe' }
	];
	EventsEditor.prototype.getBodyHeight = function () {
		return 500;
	};

	EventsEditor.prototype.initialize = function () {
		EventsEditor.parent.prototype.initialize.apply( this, arguments );
		
		var self = this;
		var MainPageLayout = function( name, config ) {
			MainPageLayout.super.call( this, name, config );

			this.nameInput = new OO.ui.TextInputWidget();
			this.dateStartInput = new mw.widgets.datetime.DateTimeInputWidget( { type: 'datetime', clearable: false, } );
			this.dateEndInput = new mw.widgets.datetime.DateTimeInputWidget( { type: 'datetime', clearable: false, } );
			this.descriptionInput = new OO.ui.TextInputWidget( { multiline: true, autosize: true, rows: 4, } );
			this.linkInput = new OO.ui.TextInputWidget();
			this.locationInput = new OO.ui.DropdownInputWidget();
			this.tagsInput = new OO.ui.MenuTagMultiselectWidget( { label: 'TagMultiselectWidget', menu: { items: [] }, allowArbitrary: true, allowDuplicates: false, $overlay: self.$overlay, } );
			this.langsInput = new OO.ui.MenuTagMultiselectWidget( { label: 'TagMultiselectWidget', menu: { items: [] }, allowArbitrary: true, allowDuplicates: false, $overlay: self.$overlay, } );
			//TODO: Change the min/max date of dateStartInput and dateEndInput depending of the value choosen on the other field
			/*this.dateStartInput.on( 'change', function( date ) {
			} );*/
			
			//Populate location and tags inputs
			var _self = this;
			new mw.Api().get( {
				action: 'query',
				prop: 'revisions',
				rvprop: [ 'content' ],
				titles: 'Events calendar/parameters.json',
				formatversion: '2',
			} ).then( function( data ) {
				var content = JSON.parse( data.query.pages[ 0 ].revisions[ 0 ].content );
				
				_self.locationInput.setOptions( _self.populateMenu( content.locations ) );
				_self.tagsInput.addOptions( _self.populateMenu( content.tags ) );
			} );

			// Create a Fieldset layout.
			var fieldset = new OO.ui.FieldsetLayout( { 
				label: messages.eventDetails,
			} );

			fieldset.addItems( [ 
				new OO.ui.FieldLayout( this.nameInput, { label: messages.name, align: 'left', help: '', } ),
				new OO.ui.FieldLayout( this.dateStartInput, { label: messages.dateStart, align: 'left', help: '', } ),
				new OO.ui.FieldLayout( this.dateEndInput, { label: messages.dateEnd, align: 'left', help: '', } ),
				new OO.ui.FieldLayout( this.descriptionInput, { label: messages.description, align: 'left', help: '', } ),
				new OO.ui.FieldLayout( this.linkInput, { label: messages.link, align: 'left', help: '', } ),
				new OO.ui.FieldLayout( this.locationInput, { label: messages.location, align: 'left', help: messages.locationHelp, } ),
				new OO.ui.FieldLayout( this.tagsInput, { label: messages.tags, align: 'left', help: '', } ),
				new OO.ui.FieldLayout( this.langsInput, { label: messages.languageCodes, align: 'left', help: '', } ),
			] );

			this.$element.append( fieldset.$element );
		};
		OO.inheritClass( MainPageLayout, OO.ui.PageLayout );
		MainPageLayout.prototype.setupOutlineItem = function () {
			this.outlineItem.setLabel( messages.generalInformation );
		};
		MainPageLayout.prototype.populateMenu = function( data, level, parents ) {
			level = level || 0;
			parents = parents || '';
			var list = [];
			var _self = this;
			$.each( data, function( key, value ) {
				var data = '',
						text = '',
						offset = '';
				for(var i=0; i<level; i++) {
					offset = offset + ' ';
				}
				if ( typeof value.text === 'string' ) {
					data = value.text;
					text = value.text;
				}
				else {
					if ( value.text.en !== undefined ) {
						data = value.text.en;
						text = value.text.en;
					}
					if ( value.text[ self.lang ] !== undefined ) {
						text = value.text[ self.lang ];
					}
				}
				
				list.push( { data: parents + data, label: offset + text, } );
				
				if ( value.sub !== undefined ) {
					list = list.concat( _self.populateMenu( value.sub, level + 1, parents + data + ',' ) );
				}
			} );
			return list;
		};

		var RrulePageLayout = function( name, config ) {
			RrulePageLayout.super.call( this, name, config );

			this.repeatSwitch = new OO.ui.ToggleSwitchWidget( { value: false } );
			this.freqSelect = new OO.ui.SelectWidget( { items: [
		        new OO.ui.OptionWidget( { data: 'weekly', label: messages.weekly } )
		    ] } );
		    this.freqSelect.selectItemByData( 'weekly' );
			this.intervalInput = new OO.ui.NumberInputWidget( { input: { value: 1 }, min: 1, max: 100 } );
			this.dateUntilInput = new mw.widgets.datetime.DateTimeInputWidget( { type: 'datetime', clearable: true } );

			// Create a Fieldset layout.
			var fieldset = new OO.ui.FieldsetLayout( { 
				label: messages.repeatRules,
			} );

			fieldset.addItems( [ 
				new OO.ui.FieldLayout( this.repeatSwitch, { label: messages.repeat, align: 'left', help: messages.repeatHelp, } ),
				new OO.ui.FieldLayout( this.freqSelect, { label: messages.frequency, align: 'left', help: messages.frequencyHelp, } ),
				new OO.ui.FieldLayout( this.intervalInput, { label: messages.interval, align: 'left', help: messages.intervalHelp, } ),
				new OO.ui.FieldLayout( this.dateUntilInput, { label: messages.dateUntil, align: 'left', help: messages.dateUntilHelp, } ),
			] );

			this.$element.append( fieldset.$element );
		};
		OO.inheritClass( RrulePageLayout, OO.ui.PageLayout );
		RrulePageLayout.prototype.setupOutlineItem = function () {
			this.outlineItem.setLabel( messages.repeatRules );
		};

		this.booklet = new OO.ui.BookletLayout( { 
			outlined: true
		} );
		
		this.mainPage = new MainPageLayout( 'main' );
		this.rrulePage = new RrulePageLayout( 'rrule' );
		this.booklet.addPages( [ this.mainPage, this.rrulePage ] );

		this.$body.append( this.booklet.$element );
	};
	EventsEditor.prototype.getSetupProcess = function ( data ) {
		data = data || {};
		var dialog = this;
		
		return EventsEditor.super.prototype.getSetupProcess.call( this, data ).next( function () {
			if ( data.id !== undefined ) {
				dialog.preload = data;
				dialog.mainPage.dateStartInput.setValue( new Date( data.dtstart * 1000 ) );
				dialog.mainPage.dateEndInput.setValue( new Date( data.dtend * 1000 ) );
				dialog.mainPage.nameInput.setValue( data.title || '' );
				dialog.mainPage.descriptionInput.setValue( data.description || '' );
				dialog.mainPage.linkInput.setValue( data.link || '' );
				dialog.mainPage.locationInput.setValue( data.location.join( ',' ) );
				if ( data.tags !== undefined ) {
					dialog.mainPage.tagsInput.setValue( data.tags );
				}
				if ( data.langs !== undefined ) {
					dialog.mainPage.langsInput.setValue( data.langs );
				}
				if ( data.rrule !== undefined ) {
					dialog.rrulePage.repeatSwitch.setValue( true );
					dialog.rrulePage.freqSelect.selectItemByData( data.rrule.freq || 'weekly' );
					dialog.rrulePage.intervalInput.setValue( data.rrule.interval || 1 );
					if ( data.rrule.until !== undefined ) {
						dialog.rrulePage.dateUntilInput.setValue( new Date( data.rrule.until * 1000 ) );
					}
				}
			}
			else {
				dialog.preload = {};
				dialog.preload.id = 0;
			}
		}, this );
	};
	EventsEditor.prototype.getActionProcess = function ( action ) {
		var dialog = this;

		if ( action ) {
			if ( action === 'save' ) {
				var now = parseInt( new Date().getTime()/1000 );
				var event = {
					id: now + '-' + mw.config.get( 'wgUserName' ).replace( / /g, '_' ) + '@meta.wikimedia.org',
					dtcreated: now,
					dtmodified: now,
					dtstart: parseInt( this.mainPage.dateStartInput.getValueAsDate().getTime()/1000 ),
					dtend: parseInt( this.mainPage.dateEndInput.getValueAsDate().getTime()/1000 ),
					title: this.mainPage.nameInput.getValue(),
					description: this.mainPage.descriptionInput.getValue(),
					link: this.mainPage.linkInput.getValue(),
					registration: false, //TODO
					geoloc: {}, //TODO
					location: this.mainPage.locationInput.getValue().split( ',' ),
					tags: this.mainPage.tagsInput.getValue(), 
					langs: this.mainPage.langsInput.getValue(), 
				};
				if ( dialog.preload.id !== 0 ) {
					event.id = dialog.preload.id;
					event.dtcreated = dialog.preload.dtcreated;
				}
				if ( this.rrulePage.repeatSwitch.getValue() ) {
					event.rrule = {
						freq: this.rrulePage.freqSelect.getData() || 'weekly',
					};
					var interval = this.rrulePage.intervalInput.getValue();
					if ( interval !== 1 ) {
						event.rrule.interval = interval;
					}
					if ( this.rrulePage.dateUntilInput.getValue() ) {
						event.rrule.until = parseInt( this.rrulePage.dateUntilInput.getValueAsDate().getTime() / 1000 );
					}
				}
				mw.loader.using( 'mediawiki.api', function() {
					new mw.Api().edit(
						'Events calendar/events.json',
						function ( revision ) {
							var jsonContent = JSON.parse( revision.content );
							var summary;
							if ( dialog.preload.id === 0 ) {
								jsonContent.push( event );
								summary = 'Add new event "' + event.title + '" (' + event.location.join( ', ' ) + ')';
							}
							else {
								$.each( jsonContent, function( key, _ ) {
									if ( jsonContent[ key ].id === event.id ) {
										jsonContent[ key ] = event;
										summary = 'Edit event "' + event.title + '" (' + event.location.join( ', ' ) + ')';
										return false;
									}
								} );
							}
							return {
								text: JSON.stringify( jsonContent, null, 4 ),
								summary: summary,
							};
						}
					)
						.then( function () {
						console.log( 'Saved! ');
					} );
				} );
				return new OO.ui.Process( function () {
					dialog.close( { action: action } );
				} );
			}
		}
		return EventsEditor.parent.prototype.getActionProcess.call( this, action );
	};

	var popup, eventsEditor;
	mw.hook( 'eventscalendar.ready' ).add( function( $eventsCalendar ) {
		if ( popup === undefined ) {
			popup = new OO.ui.WindowManager();
			$( 'body' ).append( popup.$element );
			eventsEditor = new EventsEditor();
			popup.addWindows( [ eventsEditor ] );
		}
		$( '.ec-button-add' ).show().click( function( e ) {
			e.preventDefault();
			popup.openWindow( eventsEditor );
		} );
		$( '.ec-pencil' ).css( 'cursor', 'pointer' ).show().click( function( e ) {
			e.preventDefault();
			var eventId = $( this ).attr( 'id' );
			console.log( eventId );
			new mw.Api().get( {
				action: 'query',
				prop: 'revisions',
				rvprop: [ 'content' ],
				titles: String( 'Events calendar/events.json' ),
				formatversion: '2',
			} ).fail( function( data ) {
				console.log( data );
			} ).then( function( data ) {
				var content  = JSON.parse( data.query.pages[ 0 ].revisions[ 0 ].content );
				$.each( content, function( _, event ) {
					if ( event.id.split( '@' )[ 0 ].replace( ' ', '_' ) === eventId ) {
						console.log( 'found' );
						popup.openWindow( eventsEditor, event );
						return false;
					}
				} );
				
			} );
		} );
	} );
	mw.hook( 'eventscalendar.deleting' ).add( function( $eventsCalendar ) {
		$eventsCalendar.find( '.mw-ui-button' ).off( 'click' );
	} );
} );
} )( mediaWiki );

//</nowiki>