משתמש:קיפודנחש/88plus.js

מתוך ויקיפדיה, האנציקלופדיה החופשית

הערה: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.

  • פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload) או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
  • גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
  • אינטרנט אקספלורר / אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh) או ללחוץ על צירוף המקשים Ctrl-F5.
  • אופרה: ללחוץ על Ctrl-F5.
mw.loader.using( ['mediawiki.api', 'mediawiki.util', 'mediawiki.user'] , function() {
	const 
		storageTimestampKey = 'watchlistwatcher-newestseen',
		storageSelectionKey = 'watchlistwatcher-userselection',
		nbsp = '   ',
		rlm = '‏';
	var 
		api = new mw.Api(),
		storage = window.localStorage,
		items,
		newest,
		watchButton,
		watchwButtonAnchor,
		timer,
		counts,
		popup,
		contentDiv,
		selectedOptions = storage.getItem(storageSelectionKey),
		titleCheckboxes,
		selectAll,
		refreshButton,
		optionsLabels = {
				watchlisthideanons: 'אנונימים',
				watchlisthidebots:	'בוטים',
				watchlisthideliu: 	'רשומים',  /* this strangely named option means "hide regitered users", IOW, show anons */
				watchlisthideminor:	'משניות',
				watchlisthidepatrolled: 'בדוקות',
				watchlisthidecategorization: 'קטגוריות',
				hidelog: 'פעולות יומן',
				hideedit: 'עריכות',
				
		},
		lastOption = 'hideedit',
		opts = {
			/* watchlisthideown: we do not care about this one, since "unread" doesn't have own edits anyway */
			watchlisthideanons: '!anon',
			watchlisthidebots:	'!bot',
			watchlisthideliu: 	'anon',  /* this strangely named option means "hide regitered users", IOW, show anons */
			watchlisthideminor:	'!minor',
			watchlisthidepatrolled: '!patrolled'
		};
		
	function titleLine(item) {
		var titleLink = $('<a>').attr( { href: mw.util.getUrl(item.title), title: item.title }).text(item.title),
			diffLink =  $('<a>').attr( { href: mw.util.getUrl('special:diff/' + item.revid), title: 'הבדל' })	.text('הבדל'),
			histLink =  $('<a>').attr( { href: mw.util.getUrl(item.title, { action: 'history' }), title: 'היסטוריה' }).text('היסטוריה');
		if (item.timestamp != item.notificationtimestamp) histLink = $('<strong>').append(histLink);
		return $('<span>')
			.append(titleLink)
			.append(nbsp)
			.append('(')
			.append(diffLink)
			.append('|')
			.append(histLink)
			.append(')');
	}

	function userLink(item) {
		var user = item.user,
			contribl = mw.util.getUrl('Special:Contributions/' + user),
			pagel = mw.util.getUrl('User:' + user),
			talkl = mw.util.getUrl('User talk:' + user),
			pageLink = $('<a>', { href: pagel, title: user }).text(user),
			contribLink = $('<a>', { href: contribl, title: 'תרומות' }).text('תרומות'),
			talkLink = $('<a>', { href: talkl, title: 'שיחה' }).text('שיחה');
		return $('<td>')
			.css('max-width', '36em')
			.append(pageLink)
			.append(' (')
			.append(talkLink)
			.append('|')
			.append(contribLink)
			.append(')')
			.append(nbsp)
			.append($('<span>').html(rlm +  (item.parsedcomment || '') + rlm))
			;
	}

	function localize(date, short) {
		var options = short
			? { month: 'numeric', day: 'numeric', hour:'numeric', minute:'numeric' }
			: { hour:'numeric', minute:'numeric', second: 'numeric', year: 'numeric', day: 'numeric', month: 'long' };
		return date.toLocaleDateString('he', options);
	}

	function timeOrMany(item) {
		var date = new Date(item.timestamp);
		var timestamp = item.timestamp == item.notificationtimestamp
			? localize(date) : 'מספר עריכות  (' + localize(date, true) + ')';
		return $('<td>').html(timestamp);
	}

	function makeCheckbox(item) {
		var checkbox = new OO.ui.CheckboxInputWidget( { value: item.title } );
		titleCheckboxes.push(checkbox);
		return $('<td>').append(checkbox.$element);
	}
	
	function buildLine(item) {
		var change = item.newlen - item.oldlen,
			tooltip = change > 0 
				? 'נוספו ' + change
				: 'הוסרו ' + (-change);
		tooltip = 'בעריכה האחרונה ' + tooltip + ' בתים';
		return $('<tr>')
			.attr('title', tooltip)
			.css( 'vertical-align', 'top' )
			.append(makeCheckbox(item))
			.append( timeOrMany(item) )
			.append( titleLine(item) )
			.append( userLink(item) )				;
	}

	function buildContent() {
		var div = $('<div>'),
			already = false,
 			lastClicked = new Date(storage.getItem( storageTimestampKey ) || 0),
 			optionsDiv = $('<div>'),
			table = $('<table>')
				.css( { 'border-spacing': '1em 0' } );

		selectAll = new OO.ui.CheckboxInputWidget( );
		table.append(
			$('<tr>')
				.append($('<td>').append(selectAll.$element))
				.append($('<td>').text('בחירת הכל'))
			)
			.append(counts ? $('<th>', { colspan: 3 }).text('חדשות') : '')
			.append($('<tr>'))
			.appendTo(div);
			
		titleCheckboxes = [selectAll]; //reset every time

		selectAll.on( 'change', function(selected) {
			for (var i = 1; i < titleCheckboxes.length; i++) {
				titleCheckboxes[i].setSelected(selected, true);  
			}
		}) ; 
		
		if (! counts) return div.append($('<p>').css( { 'text-align': 'center', 'font-weight': 'bold' }).text('אין עריכות שטרם נצפו'));  
		
		storage.setItem( storageTimestampKey, new Date(items[0].timestamp ));
		table;

		for (var i in items) {
			var item = items[i];
			if (! already && new Date(item.timestamp) <= lastClicked) {
				 table.append($('<th>', { colspan: 3 }).text('קודמות'));
				 already = true;
			}
			table.append(buildLine(item));
		}
		return div;
	}

	function buildCheckboxWidget() {
		var checkboxes = [];
		
		function createChangeFunc(key) {
			return function(value) {
				selectedOptions[key] = value; 
				triggerRefresh();
			};
		}

		for (var key in optionsLabels) {
			checkboxes.push(
				new OO.ui.FieldLayout( 
					new OO.ui.CheckboxInputWidget( {
						value: key,
						selected: selectedOptions[key]
					} ).on( 'change', createChangeFunc(key) ),
					{ label: optionsLabels[key], align: 'inline' } 
				)
			);
		}
		
		var layout = new OO.ui.FieldLayout( new OO.ui.Widget( {
			content: [
				new OO.ui.HorizontalLayout( {
					items: checkboxes
				})
			]
		} ) );
		return $('<div>')
			.css( { 'margin-right': '1em' } )
			.append($('<p>')
				.css({ 'font-size': '150%', 'font-weight': 'bolder', margin: '1em' })
				.text('הסתרת שינויים:')
			)
			.append(layout.$element);
	}
	
	function triggerRefresh() { setTimeout(showPopup, 0); }
	
	function buttonsPanel() {
		var
			p = $('<p>');
		
		refreshButton = new OO.ui.ButtonWidget( {
			label: 'רענון'
		})
		.on( 'click', triggerRefresh );
		refreshButton.$element.appendTo(p);
		
		var markseenButton = new OO.ui.ButtonWidget( {
				label: 'סימון הדפים המסומנים כאילו נצפו'
			})
			.on( 'click', markAsSeen ),
			unwatchButton = new OO.ui.ButtonWidget( {
				label: 'הסרת הדפים המסומנים מרשימת המעקב'
			})
			.on( 'click', unwatch );
		return $('<p>')
			.css({ 'text-align': 'center' })
			.append(refreshButton.$element)
			.append(markseenButton.$element)
			.append(unwatchButton.$element);
	}
	
	function updateStorageAndRefresh() {
		storage.setItem( storageTimestampKey, newest ); // remember the click: only items newer than latest click will trigger coloring the bubble.
		triggerQuery( 0 );	// initiate click, so when it returns we can color the icon accordingly
	}

	function showPopup( e ) {
		if (e) {
			e.stopImmediatePropagation();
			e.preventDefault();
		}
		
		storage.setItem( storageTimestampKey, newest ); // remember the click: only items newer than latest click will trigger coloring the bubble.
		
		mw.loader.using( 'oojs-ui' ).done(function() {
			queryAndUpdate().done(function() {
				if (! popup) {
					contentDiv = $('<div>')
						.addClass('wlw-content');
					popup = new OO.ui.PopupWidget( {
								label: '',
								head: true,
								width: 1000,
								autoClose: true,
								$content: $('<div>')
									.css( { overflow: 'auto' } )
									.append(buttonsPanel())
									.append(buildCheckboxWidget())
									.append($('<p>'))
									.append(contentDiv)
							});
					popup.$element
							.appendTo(watchButton);
				}
				popup.setLabel( 'דפים ברשימת המעקב שטרם נצפו, מעודכן ל-' + localize( new Date() ) );
				contentDiv
					.empty()
					.append(buildContent());
				
				if ( e ) popup.toggle();
			} ); // queryAndUpdate,done
		} ); // using.done 
	}
	
	function announce() {
		const new_id = 'pt-notifications-watchlist';
		if ( ! watchButton ) {
			var url,
				notif = $( '#pt-notifications-notice, #pt-notifications-alert' ).eq(-1);
			if ( ! notif.length )
				return;
			watchButton = notif.clone()
				.attr( { id: new_id } )
				.insertAfter( notif );
			watchwButtonAnchor = watchButton.find( 'a' )
				.removeClass('oo-ui-icon-tray')
				.click( showPopup )
				.attr( { href: '', title: 'רשימת המעקב '  } );
		}
		var lastClicked = new Date(storage.getItem( storageTimestampKey ) || 0),
			newChanges = newest > lastClicked;
		if (refreshButton) {
			refreshButton.setDisabled(! newChanges);
		}
		watchwButtonAnchor
			.toggleClass( 'mw-echo-notifications-badge-unseen', newChanges )
			.toggleClass( 'mw-echo-notifications-badge-all-read', counts === 0 )
			.attr( { 'data-counter-text': counts || '', 'data-counter-num': counts } );
	}
	function calcParams() {
		var
			wlshow;
		if (! selectedOptions || ! selectedOptions.hasOwnProperty(lastOption)) {
			selectedOptions = {};
			for (var key in optionsLabels) {
				selectedOptions[key] = mw.user.options.get(key);
			}
		}		
		
		wlshow = Object.keys( opts )
			.filter( function( opt) { return selectedOptions[opt]; } )
			.map( function( k ) { return opts[k]; } )
			.concat( 'unread' )
			.join( '|' ),
		
		wltype = ['new'];
		if (! selectedOptions.watchlisthidecategorization) wltype.push('categorize');
		if (! selectedOptions.hidelog) wltype.push('log');
		if (! selectedOptions.hideedit) wltype.push('edit');

		return {
			list: 'watchlist',
			wlprop: 'ids|user|title|timestamp|notificationtimestamp|parsedcomment|sizes',
			wlshow: wlshow,
			wltype: wltype.join('|'),
			wllimit: window.script88limit || 50
		};
	}
	
	function triggerQuery( timeout ) {
		clearTimeout( timer );
		if ( typeof(timeout) != 'number')
			timeout = 1000;
		timer = setTimeout( queryAndUpdate, timeout );
	}
	
	function markAsSeen() {
		var titles =  titleCheckboxes && titleCheckboxes
				.filter(cb => cb.selected)
				.map(cb => cb.value);
		if (titles && titles.length) {
			var promisses = titles.map(title => api.post( {
					action: 'setnotificationtimestamp',
					titles: title,
					newerthanrevid: 0,
					token: mw.user.tokens.get('csrfToken')
				} ) );
			Promise.all(promisses).then(function() {
				setTimeout(triggerRefresh, 1500);
			});
		}
	}
	
	function unwatch() {
		var titles =  titleCheckboxes && titleCheckboxes
				.filter(cb => cb.selected)
				.map(cb => cb.value);
		if (titles && titles.length) {
			api.post( {
					action: 'watch',
					unwatch: true,
					titles: titles.join("|"),
					token: mw.user.tokens.get('watchToken')
				} )
				.done(triggerRefresh);
		}
	}
	
	function queryAndUpdate() {
		return api.get( calcParams() )
			.done( function(data) {
				counts = {};
				if ( data && data.query && data.query.watchlist ) {
					items = data.query.watchlist;
					counts = items.length;
					newest = counts ? new Date(items[0].timestamp) : null;
					announce();
					triggerQuery( 60000 );
				}
			} ); // promise?
	} // queryandupdate
	//load css page. would do it locally, but javascript does not support multiline strings, and putting all the CSS locally is just too much.
	mw.loader.load( '//he.wikipedia.org/w/index.php?title=מדיה_ויקי:סקריפטים/88.css&action=raw&ctype=text/css', 'text/css' );
	queryAndUpdate();
	$('#mw-watchlist-resetbutton').submit( triggerQuery );
	$( 'body ').on( 'script-88-refresh', triggerQuery );
	mw.hook('wikipage.content').add( triggerQuery );
	$('.oo-ui-buttonElement-button').click( triggerQuery );
	$( 'body ').on( 'script-88-pretend-clicked', updateStorageAndRefresh );
} );