User:Wyang/AddAudio.js
Jump to navigation
Jump to search
Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.
- Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
- Konqueror and Chrome: click Reload or press F5;
- Opera: clear the cache in Tools → Preferences;
- Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.
- This script lacks a documentation subpage. Please create it.
- Useful links: root page • root page’s subpages • links • redirects • your own
// This will only work on Firefox, until the other browsers implement
// MediaRecorder (which might not actually be that far away).
// Written by User:Yair_rand; copied here to allow some customisation.
// UI suggestions would be most welcome.
$( function () {
var mediaDevices = navigator.mediaDevices || navigator,
getUserMedia = (
mediaDevices.getUserMedia ||
mediaDevices.webkitGetUserMedia ||
mediaDevices.mozGetUserMedia ||
mediaDevices.msGetUserMedia
),
hasTabs = 'tabbedLanguages' in window,
$sections = $( hasTabs ? languageContainers : '#mw-content-text > h2' );
var accentList = {
// I'll assume that capitalized versions of the region codes themselves
// are fine for the template display.
// Empty strings means no special categorization.
// I have no idea whether these are the right accents to have available
// as options.
en: { us: 'U.S.', uk: 'British', ca: 'Canadian', au: 'Australian', nz: 'New Zealand' },
de: { de: '', at: '', ch: '' },
fr: { fr: '', 'ca-qc': '' },
pt: { pt: '', br: '' },
zh: { cmn: 'Mandarin', 'cmn-2': 'Mandarin', yue: 'Cantonese', nan: 'Min Nan', cdo: 'Min Dong', hak: 'Hakka', wuu: 'Wu', 'wuu-2': 'Wu' },
};
if (
getUserMedia &&
'MediaRecorder' in window &&
( mw.config.get( 'wgNamespaceNumber' ) === 0 || mw.config.get( 'wgPageName' ) === 'Wiktionary:Sandbox' ) &&
mw.config.get( 'wgAction' ) === 'view' &&
!/&printable=yes|&diff=|&oldid=/.test( window.location.search )
) {
var stopIcon = '◼',
recordIcon = '⚫',
pauseIcon = '❚❚',
playIcon = '►',
saveIcon = '✔',
uploadsInProgress = 0;
mw.util.addCSS( '\
.YRAddAudio-Box { \
font-size: 10px; \
} \
.YRAddAudio-RecordButton { \
margin-right: 3px; \
cursor: pointer; \
} \
.YRAddAudio-RecordButton:hover { \
color: #DD0000; \
} \
.YRAddAudio-Recording { \
color: #DD0000; \
font-weight: bold; \
text-shadow: 0 0 1px #DD0000; \
} \
.YRAddAudio-PlayButton { \
display: none; \
margin-right: 3px; \
/* color: #AAAAAA; */ \
cursor: pointer; \
} \
.YRAddAudio-PlayButton:hover { \
color: #AAAAAA; \
} \
.YRAddAudio-SaveButton { \
display: none; \
margin-right: 3px; \
cursor: pointer; \
} \
.YRAddAudio-SaveButton:hover { \
color: #33FF22; \
} \
.YRAddAudio-accent { \
margin: 1px; \
padding: 1px; \
cursor: pointer; \
} \
.YRAddAudio-accent:hover { \
margin: 0px; \
border: 1px solid #AAA; \
padding: 1px; \
} \
.YRAddAudio-activeAccent { \
font-weight: bold; \
} \
'
);
function AButton( sectionIndex, language, headerLevel, oldHeader ) {
var recording = false,
recorder,
$elem,
$recordButton,
$playButton,
$saveButton,
$accentList,
accent,
langcode;
// Todo: Accent field.
// Todo: Licensing information in the form.
this.$elem = $elem = $( '<div>', {
addClass: 'YRAddAudio-Box',
append: [
$recordButton = $( '<span>', {
text: recordIcon,
addClass: 'YRAddAudio-RecordButton',
title: 'Record audio pronunciation for this word',
click: function recordOrStop() {
function startRecording() {
recorder.start();
$recordButton.addClass( 'YRAddAudio-Recording' );
}
try {
if ( !recorder ) {
setupRecorder( function ( r ) {
recorder = r;
startRecording();
} );
langcode = findLang( sectionIndex );
if ( accentList[ langcode ] ) {
var accentElemList = {};
$accentList = $( '<span>' )
.css( { display: 'inline-block' } )
.insertBefore( $saveButton )
.hide();
$.each( accentList[ langcode ], function ( accentcode, accentname ) {
$accentList.append(
accentElemList[ accentcode ] = $( '<span>' )
.text( accentcode.toUpperCase() )
.attr( 'title', 'Accent/dialect: ' + accentcode.toUpperCase() )
.addClass( 'YRAddAudio-accent' )
.on( 'click', function () {
if ( accent ) {
accentElemList[ accent ].removeClass( 'YRAddAudio-activeAccent' );
}
if ( accent === accentcode ) {
accent = undefined;
} else {
$( this ).addClass( 'YRAddAudio-activeAccent' );
accent = accentcode;
}
} )
);
} );
}
} else {
if ( recording ) {
recorder.stop();
$recordButton.removeClass( 'YRAddAudio-Recording' );
$accentList && $accentList.show();
$playButton.show();
$saveButton.show();
} else {
startRecording();
}
}
//this.innerText = recording ? recordIcon : stopIcon;
recording = !recording;
} catch( e ) {
console.log( e, 6 );
}
}
} ),
$playButton = $( '<span>', {
text: playIcon,
addClass: 'YRAddAudio-PlayButton',
css: {
//display: 'none'
},
title: 'Play recording',
click: function () {
recorder.play();
}
} ),
$saveButton = $( '<span>', {
text: saveIcon,
addClass: 'YRAddAudio-SaveButton',
title: 'Add this recording to the entry',
click: function () {
// addEdit, tying into upload
//mw.loader.using( 'mediawiki.ForeignApi', function () {
mw.loader.using( [ 'mediawiki.ForeignUpload', 'mediawiki.api.parse' ], function () {
recorder.add( langcode, accent );
} );
}
} ),
'Add audio pronunciation'
],
css: {
'font-size': '10px'
},
} );
function setupRecorder( cb ) {
function acceptStream( stream ) {
var mimeType = MediaRecorder.isTypeSupported( 'audio/ogg' ) ? 'audio/ogg' : 'audio/webm',
mediaRecorder = new MediaRecorder( stream, { mimeType: mimeType } ),
chunks = [],
blob;
mediaRecorder.ondataavailable = function ( e ) {
chunks.push( e.data );
};
mediaRecorder.onstop = function () {
blob = new Blob( chunks, { 'type': 'audio/ogg' } );
chunks = [];
};
function editPage( langcode, accent ) {
var editor = new Editor(),
title = mw.config.get( 'wgPageName' ),
//langcode = findLang( sectionIndex ),
// Todo: Accent
filename = ( langcode + '-' + ( accent ? accent + '-' : '' ) + title ).replace( /[\.:]/g, '-' ) + '.ogg',
// Todo: Accent text
audioTemplate =
'* \{\{audio|' + filename + ( accent ? '|Audio (' + accent.toUpperCase() + ')' : '' ) + '|lang=' + langcode + '\}\}',
addedWikitext =
// Use '{\{subst:=\}\}' so as not to interfere
// with other scripts.
( oldHeader ? '' : '\n\{\{subst:=\}\}==Pronunciation===\n' ) +
audioTemplate;
if ( !langcode ) {
return editor.error( 'Language not found.' );
}
// Capitalize
filename = filename.charAt( 0 ).toUpperCase() + filename.slice( 1 );
( new mw.Api() ).parse( audioTemplate ).done( function ( html ) {
// Hopefully this is wrapped.
var $addedElement = $( html ),
$addedHeader = oldHeader ||
$( '<span>' ).append( $( '<h3>').text( 'Pronunciation' ) ),
$redLink = $addedElement.find( '.audiofile a.new' );
if ( $redLink.length === 0 ) {
// No redlink, file space already exists.
// (Or there's a parsing error. Ignoring that.)
editor.error( 'Audio file already exists.' );
return;
}
// This isn't actually a direct transclusion of the
// file. Things may break or be inaccurate as a
// result.
$redLink.replaceWith( $( '<audio>' ).attr( {
'src': window.URL.createObjectURL( blob ),
controls: 'controls'
} ) );
editor.addEdit(
{
edit: function ( w ) {
var untilPostPronunciationHeaderReg = new RegExp(
'((?:\n|^)==' + language + '==' +
'[\\s\\S]*?' +
'(?=\n={3,}(?!Alternative|Etymology).+=+\n))'
);
var inPronunciationHeaderReg = new RegExp(
'((?:\n|^)==' + language + '==' +
'[\\s\\S]*?' +
'\n(?:\\{\\{subst:=\\}\\}|=)=+Pronunciation' +
'.+=+)'
);
if ( !oldHeader ) {
w = w.replace( untilPostPronunciationHeaderReg, '$1' +
addedWikitext +
'\n'
);
} else {
w = w.replace( inPronunciationHeaderReg, '$1' + '\n' +
addedWikitext
);
}
// console.log( 'wikitext', w );
// Then add '\{\{audio|' + filename + '|' + accent + '|lang=' + langcode + '\}\}'
// With a header, if applicable. Maybe use the whole
// '\{\{subst:=\}\}' stuff so as not to interfere
// with the other scripts.
return w;
},
redo: function () {
oldHeader || $addedHeader.insertBefore( $elem );
$addedElement.insertBefore( $elem );
$elem.hide();
},
undo: function () {
oldHeader || $addedHeader.remove();
$addedElement.remove();
$elem.show();
},
after_save: function upload() {
var FU = new mw.ForeignUpload(),
username = mw.config.get( 'wgUserName' );
if ( uploadsInProgress === 0 ) {
document.body.style.cursor = 'wait';
}
uploadsInProgress++;
FU.setFile( blob );
FU.setFilename( filename );
FU.setText(
// Copied from en-us-test.ogg.
// Dunno if it's what people usually use...
'==\{\{int:description\}\}==' +
'\n\{\{Information' +
'\n |description = \{\{en|Pronunciation of the term in ' + ( accent ? accent + ' ' : '' ) + language + '\}\}' +
'\n |date = ' + ( new Date() ).toISOString().split( 'T' )[ 0 ] +
'\n |source = \{\{own\}\}' +
'\n |author = \[\[User:' + username + '|' + username + '\]\]' +
'\n |permission =' +
'\n |other_versions =' +
'\n\}\}' +
'\n' +
'\n==\{\{int:license-header\}\}==' +
'\n\[\[Category:' + language + ' pronunciation|' + title + '\]\]' +
'\n\{\{self|Cc-by-sa-3.0\}\}'
);
FU.setComment(
'Upload ' + language + ( accent ? ' (' + accent + ')' : '' ) + ' audio for ' +
'\[\[wikt:' + title + '|' + title + '\]\] ' +
'(\[\[wikt:User:Yair rand/AddAudio.js|AddAudio.js\]\])'
);
FU.upload().done( function () {
uploadsInProgress--;
if ( uploadsInProgress === 0 ) {
document.body.style.cursor = '';
// Do a quick purge, so the file
// shows up right.
( new mw.Api() ).post( {
action: 'purge',
titles: title
} );
}
} ).fail( function () {
editor.error( 'Upload failed: ' + ( FU.stateDetails.error ? FU.stateDetails.error.info : '' ) );
} );
},
summary: '+[[File:' + filename + ']]'
},
$addedElement[ 0 ]
);
} );
}
cb( {
start: function () {
mediaRecorder.start();
},
stop: function () {
mediaRecorder.stop();
// Close the stream?
},
play: function () {
if ( blob ) {
new Audio( window.URL.createObjectURL( blob ) ).play();
}
},
add: editPage
} );
}
function noStream( err ) {
$elem
.css( 'font-color', '#A00' )
.text( 'Error: ' + err );
}
if ( navigator.mediaDevices ) {
getUserMedia.call( mediaDevices, { audio: true } )
.then( acceptStream )
.catch( noStream );
} else {
getUserMedia.call( navigator, { audio: true }, acceptStream, noStream );
}
}
}
function findLang( sectionIndex ) {
// Find lang code for this section by pulling it from the headword.
// If tabbedLanguages loaded after addaudio, hasTabs might be no
// longer accurate.
return ( 'tabbedLanguages' in window?
$( languageContainers[ sectionIndex ] ).find( '.headword' ) :
$sections.eq( sectionIndex ).find( '~* .headword' )
).attr( 'lang' );
}
// Todo: Editor log for uploading.
$sections.each( function ( sectionIndex ) {
var $section = $( this ),
language = hasTabs ?
tabbedLanguages[ sectionIndex ] :
$section.find( '.mw-headline' ).text();
// Maybe use nextUntil instead of ~ here.
$section.find( hasTabs ? 'h3, h4' : '~h3, ~h4' ).each( function () {
var $this = $( this ),
text = $this.find( '.mw-headline' ).text(),
$button,
oldHeader;
if ( !text.startsWith( 'Alternative' ) && !text.startsWith( 'Etymology' ) ) {
oldHeader = text.startsWith( 'Pronunciation' );
$button = ( new AButton( sectionIndex, language, this.nodeNode, oldHeader ) ).$elem;
if ( oldHeader ) {
// We already have a pronunciation header.
$button.insertAfter( $this );
} else {
// We don't have a pronunciation header, so we have to
// add one before the first pos header.
$button.insertBefore( $this );
}
return false;
}
} );
} );
}
} );