User:TechnoSquirrel69/Scripts/diffedit.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | Documentation for this user script can be added at User:TechnoSquirrel69/Scripts/diffedit. |
/*
Customized fork of this script for TechnoSquirrel69's use. See the revision history at [[m:User:Jon Harald Søby/diffedit.js]] for attribution
*/
/*!
* diffedit.js – script that lets you edit pages directly from the diff view
*
* @author Jon Harald Søby
* @version 1.2.0 (2023-10-13)
* @licence CC-by-SA 4.0
*
* For documentation, see [[User:Jon Harald Søby/diffedit]]
*/
function initDiffedit( $, mw, OO ) {
'use strict';
var messages,
linetopCache = '',
tabIndex = 1,
pageId = mw.config.get( 'wgArticleId' ),
oldRevisionId = mw.config.get( 'wgDiffOldId' ),
newRevisionId = mw.config.get( 'wgDiffNewId' ),
currentRevisionId = mw.config.get( 'wgCurRevisionId' ),
contentModel = mw.config.get( 'wgPageContentModel' ),
allowedContentModels = [ 'wikitext', 'text', 'sanitized-css', 'json', 'javascript', 'css', 'Scribunto' ],
api = new mw.Api();
/* The 'messages' function is from [[d:MediaWiki:Gadget-Merge.js]],
* see that page's history for credits
*/
messages = function() {
var translations = {
en: {
editTitle: 'Edit this diff',
editTitleNewerRevs: 'This diff can\'t be edited, since there are newer revisions of the page.',
noPermission: 'You do not have permission to edit this page',
refresh: 'Show latest',
refreshTitle: 'Click to load the diff against the newest revision of the page'
},
ar: {
editTitle: 'عدّل هذا الفرق',
editTitleNewerRevs: 'لا يمكن تعديل هذا الفرق، نظرًا لوجود مراجعات أحدث للصفحة.',
noPermission: 'ليس لديك صلاحية تعديل هذه الصفحة',
refresh: 'عرض الأحدث',
refreshTitle: 'انقر لتحميل الفرق مقابل أحدث مراجعة للصفحة'
},
bn: {
editTitle: 'এই পার্থক্যটি সম্পাদনা করুন',
editTitleNewerRevs: 'পাতাটিতে নতুনতর সম্পাদনা থাকায়, এই পার্থক্যটি সম্পাদনা করা যাবে না।',
noPermission: 'আপনার এই পাতাটি সম্পাদনা করার অনুমতি নেই',
refresh: 'সর্বশেষটি দেখান',
refreshTitle: 'পাতাটির নতুনতর সংশোধনের পার্থক্য লোড করতে ক্লিক করুন'
},
ckb: {
editTitle: 'ئەم جیاوازییە دەستکاری بکە',
editTitleNewerRevs: 'ئەم جیاوازییە ناتوانرێت دەستکاری بکرێ، چونکە پەڕەکە پێداچوونەوەی نوێتری ھەیە.',
noPermission: 'دەسەڵاتت نییە دەستکاریی ئەم پەڕەیە بکەیت.',
refresh: 'کۆتا دانە پیشان بدە',
refreshTitle: 'کرتە بکە تا جیاوازییەکە لەگەڵ نوێترین پێداچوونەوەی پەڕەکە بار بکەیت'
},
de: {
editTitle: 'Diesen Versionsunterschied bearbeiten',
editTitleNewerRevs: 'Dieser Versionsunterschied kann nicht bearbeitet werden, da es neuere Versionen der Seite gibt.',
noPermission: 'Du hast keine Berechtigung, diese Seite zu bearbeiten.',
refresh: 'Letzte anzeigen',
refreshTitle: 'Klicke, um den Versionsunterschied zur neuesten Version der Seite zu laden'
},
es: {
editTitle: 'Editar esta diferencia',
editTitleNewerRevs: 'Esta diferencia no puede editarse ya que hay revisiones más recientes de la página.',
noPermission: 'No tienes permiso para editar esta página',
refresh: 'Mostrar la última revisión',
refreshTitle: 'Pulsa para cargar la diferencia con la revisión más reciente de la página'
},
fa: {
editTitle: 'ویرایش این تفاوت',
editTitleNewerRevs: 'این تفاوت را نمیتوان ویرایش کرد؛ زیرا نسخههای جدیدتری از صفحه موجود هستند.',
noPermission: 'شما اختیارات لازم برای ویرایش این صفحه را ندارید',
refresh: 'نمایش جدیدترین',
refreshTitle: 'برای بارگیری تفاوت با جدیدترین نسخهٔ صفحه کلیک کنید'
},
fr: {
editTitle: 'Modifier ce diff',
editTitleNewerRevs: 'Ce diff ne peut pas être édité, car il existe des révisions plus récentes de la page.',
noPermission: 'Vous n\'avez pas le droit de modifier cette page',
refresh: 'Montrer le dernier',
refreshTitle: 'Cliquez pour charger la comparaison avec la dernière révision de la page.'
},
he: {
editTitle: 'עריכת ההשוואה הזאת',
editTitleNewerRevs: 'ההשוואה הזאת לא ניתנת לעריכה, מכיוון שקיימות גרסאות חדשות יותר של הדף.',
noPermission: 'אין לך הרשאות לעריכת הדף הזה',
refresh: 'להראות את המאוחר ביותר',
refreshTitle: 'ללחוץ לקבלת ההשוואה עם הגרסה האחרונה של הדף'
},
hr: {
editTitle: 'Uredi prikazanu razliku inačica',
editTitleNewerRevs: 'Prikazana razlika ne može se uređivati zato što postoje novije inačice stranice.',
noPermission: 'Nije Vam dopušteno uređivati ovu stranicu',
refresh: 'Posljednja inačica',
refreshTitle: 'Učitaj razliku s najnovijom inačicom stranice'
},
ja: {
editTitle: 'この差分を編集',
editTitleNewerRevs: 'ページに新しい版があるため、この差分は編集できません。',
noPermission: 'このページを編集する権限がありません',
refresh: '最新版を表示',
refreshTitle: 'クリックして、ページの最新版との差分を読み込みます'
},
ka: {
editTitle: 'ამ განსხვავების რედაქტირება',
editTitleNewerRevs: 'ამ განსხვავების რედაქტირება შეუძლებელია, რადგან არსებობს გვერდის უფრო ახალი ვერსია.',
noPermission: 'თქვენ არ გაქვთ ამ გვერდის რედაქტირების უფლება',
refresh: 'უკანასკნელის ხილვა',
refreshTitle: 'დააწკაპეთ, რათა იხილოთ განსხვავება გვერდის უახლეს ვერსიასთან'
},
ko: {
editTitle: '이 diff 수정',
editTitleNewerRevs: '페이지의 최신 버전이 있으므로 이 diff를 수정할 수 없습니다.',
noPermission: '이 페이지를 편집할 수 있는 권한이 없습니다.',
refresh: '최신 보기',
refreshTitle: '페이지의 최신 개정판에 대한 diff를 로드하려면 클릭하십시오.'
},
nb: {
editTitle: 'Rediger denne diffen',
editTitleNewerRevs: 'Denne diffen kan ikke redigeres, siden det finnes nyere revisjoner av siden.',
noPermission: 'Du har ikke tillatelse til å redigere denne sida',
refresh: 'Vis nyeste',
refreshTitle: 'Klikk for å laste diffen mot den nyeste revisjonen av siden'
},
nl: {
editTitle: 'Dit verschil bewerken',
editTitleNewerRevs: 'Dit verschil kan niet worden bewerkt, omdat er een nieuwere revisie van de pagina is.',
noPermission: 'U mag deze pagina niet bewerken',
refresh: 'Toon de laatste',
refreshTitle: 'Klik om het verschil te laden met de nieuwste revisie van de pagina'
},
nn: {
editTitle: 'Endre denne diffen',
editTitleNewerRevs: 'Du kan ikkje endre denne diffen, av di det finst nyare revisjonar av sida.',
noPermission: 'Du har ikkje løyve til å endra denne sida',
refresh: 'Syn nyaste',
refreshTitle: 'Klikk for å lasta diffen mot den nyaste versjonen av sida'
},
pl: {
editTitle: 'Edytuj to porównanie',
editTitleNewerRevs: 'Nie możesz edytować porównania, bo istnieją nowsze wersje tej strony.',
noPermission: 'Nie masz uprawnień do edycji tej strony',
refresh: 'Pokaż najnowszą',
refreshTitle: 'Kliknij, aby załadować porównanie z najnowszą wersją tej strony'
},
sv: {
editTitle: 'Redigera denna diff',
editTitleNewerRevs: 'Denna diff kan inte redigeras då det finns nyare sidversioner.',
noPermission: 'Du har inte behörighet att redigera den här sidan',
refresh: 'Visa senaste',
refreshTitle: 'Klicka för att ladda in skillnaden mot den senaste sidversionen'
},
th: {
editTitle: 'แก้ไขความแตกต่างนี้',
editTitleNewerRevs: 'แก้ไขความแตกต่างนี้ไม่ได้ เนื่องจากมีการแก้ไขหน้าเว็บที่ใหม่กว่า',
noPermission: 'คุณไม่ได้รับอนุญาตให้แก้ไขหน้านี้',
refresh: 'แสดงการแก้ไขล่าสุด',
refreshTitle: 'คลิกเพื่อโหลดความแตกต่างกับรุ่นใหม่ล่าสุดของหน้า'
},
tl: {
editTitle: 'I-edit ang diff na ito',
editTitleNewerRevs: 'Hindi ma-edit ang diff na ito, dahil may mga mas bagong rebisyon ng page.',
noPermission: 'Wala kang permission mag-edit ang page.',
refresh: 'Ipakita ang pinakbago',
refreshTitle: 'I-click upang i-load ang diff laban sa pinakabagong rebisyon ng pahina'
},
vi: {
editTitle: 'Sửa đổi từ trang Khác này',
editTitleNewerRevs: 'Không thể sửa đổi vì có các phiên bản mới hơn.',
noPermission: 'Bạn không có quyền sửa trang này',
refresh: 'Xem sửa đổi mới nhất',
refreshTitle: 'Nhấp để xem khác biệt với phiên bản hiện tại của trang'
},
zh: {
editTitle: '編輯此差異',
editTitleNewerRevs: '由於有更新的版本,這個差異無法被修改。',
noPermission: '您沒有權限修改這個頁面。',
refresh: '顯示最新版本',
refreshTitle: '點擊以載入頁面與最新修改的差異'
},
},
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 = i - 1;
}
return ret;
}();
function enumerateLines() {
var currentLine = 0,
numberRegex = '0-9',
localNumbers = mw.language.getDigitTransformTable();
if ( ( localNumbers instanceof Array && localNumbers.length ) || Object.keys( localNumbers ).length ) {
for ( const i of Array( 10 ).keys() ) {
numberRegex = numberRegex.concat( localNumbers[ i ] );
}
}
$( 'table.diff tbody tr td:last-of-type' ).each( function() {
if ( $( this ).hasClass( 'diff-lineno' ) ) {
var lineNo = $( this ).text();
lineNo = mw.language.convertNumber( lineNo.replace( new RegExp( '[^' + numberRegex + ']', 'g' ), '' ), true );
currentLine = lineNo;
} else if ( $( this ).hasClass( 'diff-addedline' ) || $( this ).hasClass( 'diff-context' ) ) {
$( this ).addClass( 'diff-editable' ).attr( 'data-mw-diff-line', currentLine );
$( this ).attr( 'data-mw-diff-tabindex', tabIndex );
currentLine++;
tabIndex++;
}
});
}
function addEditButton( titleText ) {
var editTitle = titleText ? titleText : messages.editTitle,
editIcon = titleText ? 'editLock' : 'edit';
var editButton = new OO.ui.ButtonWidget( {
label: mw.message( 'edit' ).text(),
icon: editIcon,
title: editTitle,
flags: [ 'primary', 'progressive' ],
disabled: !!titleText
});
linetopCache = $( '.diff-linetop' ).html();
$( '.diffedit-editbutton' ).append( editButton.$element );
if ( !titleText ) {
editButton.on( 'click', function() {
toggleEditMode( 'enable' );
addEditLine();
});
}
}
function addRefreshButton() {
var refreshButton = new OO.ui.ButtonWidget( {
label: messages.refresh,
icon: 'reload',
title: messages.refreshTitle,
flags: [ 'progressive' ],
invisibleLabel: true,
framed: false
});
$( '.diffedit-editbutton' ).prepend( refreshButton.$element );
refreshButton.on( 'click', function() {
window.location.href = mw.config.get( 'wgServer' ) + mw.util.getUrl( mw.config.get( 'wgPageName' ), { 'diff': 'cur', 'oldid': oldRevisionId } );
});
}
function addEditLine() {
var editSummary = new OO.ui.TextInputWidget( {
icon: 'textSummary',
accessKey: mw.message( 'accesskey-summary' ).text(),
name: 'wpSummary',
tabIndex: tabIndex + 1,
placeholder: mw.message( 'revisionslider-label-comment' ).text(),
title: mw.message( 'tooltip-summary' ).text(),
classes: [ 'diffedit-editsummary' ]
}),
publishButton = new OO.ui.ButtonWidget( {
label: mw.message( 'publishchanges' ).text(),
title: mw.message( 'tooltip-publish' ).text(),
accessKey: mw.message( 'accesskey-publish' ).text(),
tabIndex: tabIndex + 2,
flags: [ 'primary', 'progressive' ],
classes: [ 'diffedit-publishbutton' ]
}),
cancelButton = new OO.ui.ButtonWidget( {
label: mw.message( 'cancel' ).text(),
icon: 'cancel',
invisibleLabel: true,
tabIndex: tabIndex + 3,
flags: [ 'destructive' ],
framed: false
}),
editLine = new OO.ui.FieldLayout( new OO.ui.Widget( {
content: [
new OO.ui.HorizontalLayout( {
items: [ editSummary, publishButton, cancelButton ]
})]
}));
publishButton.on( 'click', function() {
processEdit();
});
publishButton.$element.hover( function() {
if ( $( 'input[name=wpSummary]' ).val().length === 0 ) {
$( 'input[name=wpSummary]' ).css( { 'outline': '5px solid gold', 'transition': 'outline 500ms cubic-bezier(.5,2,.5,-1)' } );
} else {
$( 'input[name=wpSummary] ').css( { 'outline': '5px solid transparent' } );
}
});
editSummary.$element.keyup( function( e ) {
if ( e.key === 'Enter' ) {
processEdit();
}
});
cancelButton.on( 'click', function() {
toggleEditMode( 'disable' );
});
editLine.$field.css( 'float', 'none' );
$( '.diff-linetop' ).html( editLine.$element );
$( 'input[name=wpSummary]' ).on( 'keyup keydown change', function() {
$( this ).css( { 'outline': '5px solid transparent', 'transition': 'outline 500ms ease-out' } );
});
}
function toggleEditMode( state ) {
if ( state === 'enable' ) {
$( 'table.diff' ).addClass( 'diff-editmode' );
$( '.diff-editable' ).each( function() {
$( this ).attr( { 'contenteditable': 'true', 'tabindex': $( this ).attr( 'data-mw-diff-tabindex' ) } ).css( { 'word-wrap': 'break-word', 'white-space': 'pre-wrap' } );
});
$( '.diff-editable div' ).text();
$( '.diff-editable' ).first().attr( 'accesskey', ',' ).focus();
} else {
$( 'table.diff' ).removeClass( 'diff-editmode' );
$( '.diff-linetop' ).html( linetopCache );
addEditButton();
$( '.diff-editable' ).each( function() {
$( this ).attr( 'contenteditable', 'false').removeAttr( 'tabindex' ).css( { 'word-wrap': 'break-word', 'white-space': 'pre-wrap' } );
});
}
}
function processEdit() {
$( '.diffedit-publishbutton' ).addClass( 'oo-ui-pendingElement-pending' ).removeClass( 'oo-ui-flaggedElement-primary' );
var currentContent = '',
contentFromApi = api.get( {
action: 'query',
prop: 'revisions',
rvprop: 'content',
rvslots: 'main',
pageids: pageId,
rvstartid: currentRevisionId,
rvendid: currentRevisionId
} );
contentFromApi.done( function( data ) {
var mainSlot = data.query.pages[pageId].revisions[0].slots.main,
contentModel = mainSlot.contentmodel;
currentContent = mainSlot[ '*' ];
currentContent = currentContent.split( '\n' );
var newContent = currentContent;
$( '.diff-editable' ).each( function() {
var thisline = [],
lineNo = $( this ).attr( 'data-mw-diff-line' );
if ( $( this ).contents().length === 0 ) {
thisline.push( '' );
} else if ( $( this ).contents()[0].nodeName === '#text' ) {
thisline.push( $( this ).contents()[0].textContent );
} else {
$( this ).contents().each( function() {
thisline.push( this.textContent );
});
}
newContent[lineNo-1] = thisline.join( '\n' );
});
api.postWithEditToken( {
action: 'edit',
pageid: pageId,
baserevid: newRevisionId,
nocreate: true,
text: newContent.join( '\n' ),
minor: false,
summary: $( 'input[name=wpSummary]' ).val() + ' ' + mw.message( 'parentheses', '[[m:Special:MyLanguage/User:Jon Harald Søby/diffedit|diffedit]]' ).text()
} ).done( function( data ) {
window.location.href = mw.config.get( 'wgServer' ) + mw.util.getUrl( mw.config.get( 'wgPageName' ), { 'diff': 'cur' } );
}).fail( function( err ) {
console.log(err);
alert( 'Error: ' + err );
});
}).fail( function( err ) {
alert( 'Failed: ' + err );
return;
});
}
$( '.diff-lineno:first' ).next().addClass( 'diff-linetop' ).append( $( '<div />' ).addClass( 'diffedit-editbutton' ) );
if ( !mw.config.get( 'wgIsProbablymiEditable' ) ) {
addEditButton( messages.noPermission );
} else if ( $( '.mw-diff-slot-header' ).length ) {
addEditButton( mw.message( 'editpage-invalidcontentmodel-text', 'mixed content' ).text() );
} else if ( !allowedContentModels.includes( contentModel ) ) {
addEditButton( mw.message( 'editpage-invalidcontentmodel-text', contentModel ).text() );
} else if ( newRevisionId !== currentRevisionId ) {
addEditButton( messages.editTitleNewerRevs );
addRefreshButton();
} else {
enumerateLines();
addEditButton();
$( '.diff-linetop, .diff-editable' ).keyup( function( e ) {
if ( e.key === 'Escape' ) {
toggleEditMode( 'disable' );
} else if ( e.ctrlKey && e.key === 'Enter' ) {
processEdit();
}
});
}
}
( function() {
if ( !( mw.config.get( 'wgDiffNewId' ) ) ) {
return;
}
mw.loader.using( [
'mediawiki.api',
'mediawiki.jqueryMsg',
'mediawiki.language',
'oojs-ui-core',
'oojs-ui.styles.icons-editing-core',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-layout'
] ).then( function() {
mw.loader.load( 'https://meta.wikimedia.org/w/index.php?title=User:Jon_Harald_Søby/diffedit.css&action=raw&ctype=text/css', 'text/css' );
new mw.Api().loadMessagesIfMissing( [
'edit', // Edit
'publishchanges', // Publish
'tooltip-publish', // Publish these changes
'cancel', // Cancel
'accesskey-publish', // s
'accesskey-summary', // b
'editpage-invalidcontentmodel-text', // Invalid content model $1
'revisionslider-label-comment', // Edit summary
'tooltip-summary', // Summary tooltip
'parentheses' // ($1)
] ).done( function( data ) {
initDiffedit( jQuery, mediaWiki, OO );
} ).fail( function( err ) {
mw.notify( err, { title: 'diffedit error', type: 'error' } );
} );
} );
} )();