מדיה ויקי:סקריפטים/88.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','mediawiki.storage'] , function() {
const
storageTimestampKey = 'watchlistwatcher-newestseen',
storageSelectionKey = 'watchlistwatcher-userselection',
nbsp = ' ',
rlm = '‏';
var
api = new mw.Api(),
storage = mw.storage,
items,
newest,
watchButton,
watchwButtonAnchor,
timer,
counts,
popup,
contentDiv,
selectedOptions = storage.get(storageSelectionKey),
titleCheckboxes,
selectAll,
refreshButton,
optionsLabels = {
watchlisthideanons: 'anon',
watchlisthidebots: 'bot',
watchlisthideliu: 'registered', /* this strangely named option means "hide regitered users", IOW, show anons */
watchlisthideminor: 'minor',
watchlisthidepatrolled: 'patrolled',
watchlisthidecategorization: 'cats',
hidelog: 'log',
edits: 'edits',
},
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'
},
langStrings = {
he: {
watchlist: 'רשימת המעקב (שינויים שטרם נצפו)',
numpages: 'דפים ברשימת המעקב שטרם נצפו, מעודכן ל-',
'new': 'חדשות',
prev: 'קודמות',
hide: 'הסתרת שינויים',
remove: 'הסרת הדפים המסומנים מרשימת המעקב',
markread: 'סימון הדפים המסומנים כאילו נצפו',
refresh: 'רענון',
anon: 'אנונימים',
bot: 'בוטים',
registered: 'רשומים',
minor: 'משניות',
patrolled: 'בדוקות',
cats: 'קטגוריות',
log: 'פעולות יומן',
edits: 'עריכות',
dif: 'הבדל',
hist: 'היסטוריה',
added: 'נוספו',
removed: 'הוסרו',
bytes: 'בתים',
lastedit: 'בעריכה האחרונה',
multedits: 'מספר עריכות',
talk: 'שיחה',
contrib: 'תרומות',
nounviewed: 'אין שינויים שטרם נצפו',
selectall: 'בחירת הכל'
},
'en': {
watchlist: 'watchlist',
numpages: 'unviewed pages in your watchlist. updated to-',
'new': 'new',
prev: 'previous',
hide: 'hide changes',
remove: 'remove selected from watchlist',
markread: 'mark selected as viewed',
refresh: 'refresh',
anon: 'anonymous',
bot: 'bots',
registered: 'registered',
minor: 'minor',
patrolled: 'patrolled',
cats: 'categories',
log: 'log actions',
edits: 'edits',
dif: 'diff',
hist: 'history',
added: 'added',
removed: 'removed',
bytes: 'bytes',
lastedit: 'last change',
multedits: 'multiple changes',
talk: 'talk',
contrib: 'contrib',
nounviewed: 'no unviewed changes',
selectall: 'select all'
}
}
;
function i18n(key) {
switch (mw.config.get('wgUserLanguage')) {
case 'he':
return langStrings['he'][key] || key;
default: return langStrings['en'][key] || key;
}
}
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: i18n('dif') }) .text(i18n('dif')),
histLink = $('<a>').attr( { href: mw.util.getUrl(item.title, { action: 'history' }), title: i18n('hist') }).text(i18n('hist'));
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: i18n('contrib') }).text(i18n('contrib')),
talkLink = $('<a>', { href: talkl, title: i18n('talk') }).text(i18n('talk'));
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(mw.config.get('wgUserLanguage'), options);
}
function timeOrMany(item) {
var date = new Date(item.timestamp);
var timestamp = item.timestamp == item.notificationtimestamp
? localize(date) : i18n('multedits') + ' (' + 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
? i18n('added') + ' ' + change
: i18n('removed') + ' ' + (-change);
tooltip = i18n('lastedit') + ' ' + tooltip + ' ' + i18n('bytes');
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.get( 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(i18n('selectall')))
)
.append(counts ? $('<th>', { colspan: 3 }).text(i18n('new')) : '')
.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(i18n('nounviewed')));
storage.set( 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(i18n('prev')));
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: i18n(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(i18n('hide') + ':')
)
.append(layout.$element);
}
function triggerRefresh() { setTimeout(showPopup, 0); }
function buttonsPanel() {
var
p = $('<p>');
refreshButton = new OO.ui.ButtonWidget( {
label: i18n('refresh')
})
.on( 'click', triggerRefresh );
refreshButton.$element.appendTo(p);
var markseenButton = new OO.ui.ButtonWidget( {
label: i18n('markread')
})
.on( 'click', markAsSeen ),
unwatchButton = new OO.ui.ButtonWidget( {
label: i18n('remove')
})
.on( 'click', unwatch );
return $('<p>')
.css({ 'text-align': 'center' })
.append(refreshButton.$element)
.append(markseenButton.$element)
.append(unwatchButton.$element);
}
function updateStorageAndRefresh() {
storage.set( 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.set( 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,
$floatableContainer: watchButton,
$content: $('<div>')
.css( { overflow: 'auto' } )
.append(buttonsPanel())
.append(buildCheckboxWidget())
.append($('<p>'))
.append(contentDiv)
});
$('#mw-content-text').first('div, form').prepend(popup.$element);
}
popup.setLabel( i18n('numpages') + 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: i18n('watchlist') } );
$('.skin-vector-2022 #' + new_id).find('span').remove();
}
var lastClicked = new Date(storage.get( 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 && cb.value)
.map(cb => cb.value)
.slice(0, 50);
if (titles && titles.length) {
api.postWithToken('csrf', {
action: 'setnotificationtimestamp',
titles: titles.join('|')
} )
.then( () => setTimeout(triggerRefresh, 1500) );
}
}
function unwatch() {
var titles = titleCheckboxes && titleCheckboxes
.filter(cb => cb.selected && cb.value)
.map(cb => cb.value)
.slice(0, 50);
if (titles && titles.length) {
api.postWithToken('watch', {
action: 'watch',
unwatch: true,
titles: titles.join('|')
} )
.then(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 );
} );