User:Jon Harald Søby/senseItemLabel.js
Appearance
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)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* Add item labels based on lexemes
*
* When adding P5137 (item for this sense) to lexemes, add buttons
* to add the lexeme's lemma as a label for that item, if there is
* no label for the item in that language, or if the label is
* different.
*
* @version 1.2.0 (2025-06-18)
* @author Jon Harald Søby
*/
mw.hook( 'wikibase.entityPage.entityLoaded' ).add( ( e ) => {
'use strict';
if ( e.type !== 'lexeme' ) {
return;
}
mw.loader.using( [
'mediawiki.api',
'mediawiki.language',
'mediawiki.util',
'oojs',
'oojs-ui-core',
'oojs-ui-widgets',
'oojs-ui.styles.icons-interactions',
'wikibase.WikibaseContentLanguages'
]).then( async function() {
const api = new mw.Api();
await api.loadMessagesIfMissing( [ 'postedit-confirmation-published', 'wikibase-label-empty' ] );
mw.util.addCSS( `
.userjs-senseitemlabel-language {
font-family: monospace, monospace;
}
.userjs-senseitemlabel-nolabel {
color: var(--color-placeholder, #72777d);
font-style: italic;
}
.userjs-senseitemlabel-oldlabel {
color: var(--color-subtle, #54595d);
}
.userjs-senseitemlabel-newlabel {
font-weight: bold;
}`.replace( /\t\t\t/g, '' ) ); // Replace tabs for prettier output
const ARROW = document.querySelector( 'html' ).getAttribute( 'dir' ) === 'ltr' ? '→' : '←';
const ITEMFORSENSE = 'P5137';
const userLanguage = mw.config.get( 'wgUserLanguage' );
const validTermLanguages = wb.WikibaseContentLanguages.getTermLanguages()._languageCodes;
async function lookUpLabels( items, languages ) {
return api.get( {
action: 'wbgetentities',
format: 'json',
formatversion: 2,
ids: items,
props: [ 'labels' ],
languages: languages
} );
}
function addLabel( item, language, oldLabel, newLabel ) {
let query = {
action: 'wbsetlabel',
id: item,
format: 'json',
formatversion: 2,
language: language,
value: newLabel,
summary: `Add label from [[Lexeme:${e.id}|${e.id}]] ([[User:Jon Harald Søby/senseItemLabel.js|senseItemLabel.js]])`
};
if ( oldLabel && oldLabel.toLowerCase() !== newLabel.toLowerCase() ) {
let blob = {
labels: {
[ language ]: {
language: language,
value: newLabel
}
},
aliases: {
[ language ]: [
{
language: language,
value: oldLabel,
add: ''
}
]
}
};
query.action = 'wbeditentity';
query.data = JSON.stringify( blob );
delete query.language;
delete query.value;
}
return api.postWithEditToken( query );
}
function addLabelButton( sense, item, language, oldLabel, newLabel, disabled, icon, flags ) {
const button = new OO.ui.ButtonWidget( {
icon: icon,
label: 'Add label',
invisibleLabel: true,
flags: flags,
disabled: disabled
} );
button.on( 'click', () => {
addLabel( item, language, oldLabel, newLabel ).then( ( res ) => {
console.log( 'res', res );
button.setIcon( 'check' );
button.setDisabled( true );
if ( language === userLanguage ) {
const aElements = document.querySelectorAll( '[data-property-id=' + ITEMFORSENSE + '] a[title=' + item + ']' );
aElements.forEach( ( aElement ) => {
aElement.textContent = newLabel;
aElement.removeAttribute( 'lang' );
aElement.parentNode.querySelector( '.wb-language-fallback-indicator' ).remove();
} );
}
mw.notify( mw.message( 'postedit-confirmation-published', mw.user, res.entity.lastrevid ).parseDom(), { type: 'success' } );
});
} );
return button;
}
function buildLabelRow( sense, item, language, newLabel, oldLabel ) {
const rowElement = document.createElement( 'tr' );
const languageCell = document.createElement( 'td' );
languageCell.classList.add( 'userjs-senseitemlabel-language' );
languageCell.textContent = language;
const buttonCell = document.createElement( 'td' );
let buttonIcon = 'add';
let buttonDisabled = false;
let buttonFlags = [ 'primary', 'progressive' ];
if ( !validTermLanguages.includes( language ) ) {
buttonIcon = 'cancel';
buttonDisabled = true;
buttonFlags = [];
}
if ( newLabel === oldLabel ) {
buttonIcon = 'check';
buttonDisabled = true;
buttonFlags = [];
}
let button = addLabelButton( sense, item, language, oldLabel, newLabel, buttonDisabled, buttonIcon, buttonFlags );
buttonCell.appendChild( button.$element[ 0 ] );
const labelCell = document.createElement( 'td' );
labelCell.classList.add( 'userjs-senseitemlabel-label' );
const oldLabelSpan = document.createElement( 'span' );
oldLabelSpan.classList.add( 'userjs-senseitemlabel-oldlabel' );
const newLabelSpan = document.createElement( 'span' );
newLabelSpan.classList.add( 'userjs-senseitemlabel-newlabel' );
newLabelSpan.textContent = newLabel;
if ( oldLabel && oldLabel === newLabel ) {
labelCell.append( newLabelSpan );
} else if ( oldLabel ) {
oldLabelSpan.textContent = oldLabel;
labelCell.setAttribute( 'lang', mw.language.bcp47( language ) );
labelCell.append( oldLabelSpan, ` ${ARROW} `, newLabelSpan );
} else {
oldLabelSpan.textContent = mw.msg( 'wikibase-label-empty' );
oldLabelSpan.classList.add( 'userjs-senseitemlabel-nolabel' );
oldLabelSpan.setAttribute( 'lang', mw.language.bcp47( userLanguage ) );
newLabelSpan.setAttribute( 'lang', mw.language.bcp47( language ) );
labelCell.append( oldLabelSpan, ` ${ARROW} `, newLabelSpan );
}
rowElement.append( languageCell, buttonCell, labelCell );
return rowElement;
}
function buildLabelTable( sense, item, lemmas, existingLabels ) {
const tableElement = document.createElement( 'table' );
tableElement.classList.add( 'userjs-senseitemlabel-table' );
let buttonIcon = 'add';
let buttonDisabled = false;
for ( const language in lemmas ) {
const rowElement = buildLabelRow( sense, item, language, lemmas[ language ].value, existingLabels[ language ] );
tableElement.appendChild( rowElement );
}
return tableElement;
}
const lemmas = e.lemmas;
const senses = e.senses;
const senseItems = {};
for ( const sense of senses ) {
if ( ITEMFORSENSE in sense.claims ) {
for ( const item of sense.claims[ ITEMFORSENSE ] ) {
senseItems[ item.mainsnak.datavalue.value.id ] = {};
}
}
}
const labelLookup = await lookUpLabels( Object.keys( senseItems ), Object.keys( lemmas ) );
for ( const entity in labelLookup.entities ) {
for ( const labelLang in labelLookup.entities[ entity ].labels ) {
senseItems[ entity ][ labelLang ] = labelLookup.entities[ entity ].labels[ labelLang ].value;
}
}
const senseItemStatementGroups = document.querySelectorAll( '[data-property-id=' + ITEMFORSENSE + ']' );
senseItemStatementGroups.forEach( ( senseElement ) => {
const senseId = senseElement.id.split( '-' )[ 0 ];
const senseItemStatements = senseElement.querySelectorAll( '.wikibase-statementlistview-listview .wikibase-statementview' );
senseItemStatements.forEach( ( statementElement ) => {
const qid = statementElement.querySelector( '.wikibase-snakview-value a' ).getAttribute( 'title' );
const indicatorElement = statementElement.querySelector( '.wikibase-snakview-indicators' );
const table = buildLabelTable( senseId, qid, lemmas, senseItems[ qid ] );
indicatorElement.appendChild( table );
} );
} );
mw.hook( 'wikibase.statement.saved' ).add( async function ( entity, statement, oldStatement, newStatement ) {
const propertyId = newStatement._claim._mainSnak._propertyId;
if ( propertyId !== ITEMFORSENSE ) {
return;
}
const guid = newStatement._claim._guid;
const senseId = guid.split( '$' )[ 0 ].split( '-' )[ 1 ];
const qid = newStatement._claim._mainSnak._value._serialization;
const specialSenseItems = { [ qid ]: {} };
const specialLabelLookup = await lookUpLabels( [ qid ], Object.keys( lemmas ) );
for ( const labelLang in specialLabelLookup.entities[ qid ].labels ) {
specialSenseItems[ qid ][ labelLang ] = specialLabelLookup.entities[ qid ].labels[ labelLang ].value;
}
const table = buildLabelTable( senseId, qid, lemmas, specialSenseItems[ qid ] );
// Have to use [id=] instead of # because of JavaScript quirk related to the
// dollar sign in the GUID.
const indicatorElement = document.querySelector( `[id="${guid}"] .wikibase-snakview-indicators` );
indicatorElement.appendChild( table );
} );
} );
} );
