/home/arranoyd/public_html/wp-content/plugins/loco-translate/pub/js/editor.js
/**
* Script for PO file editor pages
*/
!function( window, $ ){
var loco = window.locoScope,
conf = window.locoConf,
syncParams = null,
saveParams = null,
ajaxUpload = conf.multipart,
// UI translation
translator = loco.l10n,
sprintf = loco.string.sprintf,
// PO file data
locale = conf.locale,
messages = loco.po.init( locale ).wrap( conf.powrap ),
template = ! locale,
// form containing action buttons
elForm = document.getElementById('loco-actions'),
filePath = conf.popath,
syncPath = conf.potpath,
// file system connect when file is locked
elFilesys = document.getElementById('loco-fs'),
fsConnect = elFilesys && loco.fs.init( elFilesys ),
// prevent all write operations if readonly mode
readonly = conf.readonly,
editable = ! readonly,
// Editor components
editor,
saveButton,
innerDiv = document.getElementById('loco-editor-inner')
;
// warn if ajax uploads are enabled but not supported
if( ajaxUpload && ! ( window.FormData && window.Blob ) ){
ajaxUpload = false;
loco.notices.warn("Your browser doesn't support Ajax file uploads. Falling back to standard postdata");
}
/**
*
*/
function doSyncAction( callback ){
function onSuccess( result ){
var info = [],
doc = messages,
exp = result.po,
src = result.pot,
pot = loco.po.init().load( exp ),
done = doc.merge( pot ),
nadd = done.add.length,
ndel = done.del.length,
t = translator;
// reload even if unchanged, cos indexes could be off
editor.load( doc );
// Show summary
if( nadd || ndel ){
if( src ){
// Translators: Where %s is the name of the POT template file. Message appears after sync
info.push( sprintf( t._('Merged from %s'), src ) );
}
else {
// Translators: Message appears after sync operation
info.push( t._('Merged from source code') );
}
// Translators: Summary of new strings after running in-editor Sync
nadd && info.push( sprintf( t._n('1 new string added','%s new strings added', nadd ), nadd ) );
// Translators: Summary of existing strings that no longer exist after running in-editor Sync
ndel && info.push( sprintf( t._n('1 obsolete string removed','%s obsolete strings removed', ndel ), ndel ) );
// editor thinks it's saved, but we want the UI to appear otherwise
$(innerDiv).trigger('poUnsaved',[]);
updateStatus();
// debug info in lieu of proper merge confirmation:
window.console && debugMerge( console, done );
}
else if( src ){
// Translators: Message appears after sync operation when nothing has changed. %s refers to a POT file.
info.push( sprintf( t._('Already up to date with %s'), src ) );
}
else {
// Translators: Message appears after sync operation when nothing has changed
info.push( t._('Already up to date with source code') );
}
loco.notices.success( info.join('. ') );
$(innerDiv).trigger('poMerge',[result]);
// done sync
callback && callback();
}
loco.ajax.post( 'sync', syncParams, onSuccess, callback );
}
function debugMerge( console, result ){
var i = -1, t = result.add.length;
while( ++i < t ){
console.log(' + '+result.add[i].source() );
}
i = -1, t = result.del.length;
while( ++i < t ){
console.log(' - '+result.del[i].source() );
}
}
/**
* @param params {Object}
* @return FormData
*/
function initMultiPart( params ){
var p, data = new FormData;
for( p in params ){
if( params.hasOwnProperty(p) ) {
data.append(p, params[p]);
}
}
return data;
}
/**
* Post full editor contents to "posave" endpoint
*/
function doSaveAction( callback ){
function onSuccess( result ){
callback && callback();
editor.save( true );
// Update saved time update
$('#loco-po-modified').text( result.datetime||'[datetime error]' );
}
var postData = $.extend( {locale:String(messages.locale()||'')}, saveParams||{} );
if( fsConnect ){
fsConnect.applyCreds(postData);
}
// submit PO as concrete file if configured
if( ajaxUpload ){
postData = initMultiPart(postData);
postData.append('po', new Blob([String(messages)],{type:'application/x-gettext'}), String(postData.path).split('/').pop()||'untitled.po' );
}
else {
postData.data = String(messages);
}
loco.ajax.post( 'save', postData, onSuccess, callback );
}
function saveIfDirty(){
editor.dirty && doSaveAction();
}
function onUnloadWarning(){
// Translators: Warning appears when user tries to refresh or navigate away when editor work is unsaved
return translator._("Your changes will be lost if you continue without saving");
}
function registerSaveButton( button ){
saveButton = button;
// enables and disable according to save/unsave events
editor
.on('poUnsaved', function(){
enable();
$(button).addClass( 'button-primary loco-flagged' );
} )
.on('poSave', function(){
disable();
$(button).removeClass( 'button-primary loco-flagged' );
} )
;
function disable(){
button.disabled = true;
}
function enable(){
button.disabled = false;
}
function think(){
disable();
$(button).addClass('loco-loading');
}
function unthink(){
enable();
$(button).removeClass('loco-loading');
}
saveParams = $.extend( { path: filePath }, conf.project||{} );
$(button).click( function(event){
event.preventDefault();
think();
doSaveAction( unthink );
return false;
} );
return true;
};
function registerSyncButton( button ){
var project = conf.project;
if( project ){
function disable(){
button.disabled = true;
}
function enable(){
button.disabled = false;
}
function think(){
disable();
$(button).addClass('loco-loading');
}
function unthink(){
enable();
$(button).removeClass('loco-loading');
}
// Only permit sync when document is saved
editor
.on('poUnsaved', function(){
disable();
} )
.on('poSave', function(){
enable();
} )
;
// params for sync end point
syncParams = {
bundle: project.bundle,
domain: project.domain,
type: template ? 'pot' : 'po',
sync: syncPath||''
};
// enable syncing on button click
$(button)
.click( function(event){
event.preventDefault();
think();
doSyncAction( unthink );
return false;
} )
//.attr('title', syncPath ? sprintf( translator._('Update from %s'), syncPath ) : translator._('Update from source code') )
;
enable();
}
return true;
}
function registerFuzzyButton( button ){
var toggled = false,
enabled = false
;
function redraw( message, state ){
// fuzziness only makes sense when top-level string is translated
var allowed = message && message.translated(0) || false;
if( enabled !== allowed ){
button.disabled = ! allowed;
enabled = allowed;
}
// toggle on/off according to new fuzziness
if( state !== toggled ){
$(button)[ state ? 'addClass' : 'removeClass' ]('inverted');
toggled = state;
}
}
// state changes depending on whether an asset is selected and is fuzzy
editor
.on('poSelected', function( event, message ){
redraw( message, message && message.fuzzy() || false );
} )
.on( 'poEmpty', function( event, blank, message, pluralIndex ){
if( 0 === pluralIndex && blank === enabled ){
redraw( message, toggled );
}
} )
.on( 'poFuzzy', function( event, message, newState ){
redraw( message, newState );
} )
;
// click toggles current state
$(button).click( function( event ){
event.preventDefault();
editor.fuzzy( ! editor.fuzzy() );
return false;
} );
return true;
};
function registerRevertButton( button ){
// No need for revert when document is saved
editor
.on('poUnsaved', function(){
button.disabled = false;
} )
.on('poSave', function(){
button.disabled = true;
} )
;
// handling unsaved state prompt with onbeforeunload, see below
$(button).click( function( event ){
event.preventDefault();
location.reload();
return false;
} );
return true;
};
function registerInvisiblesButton( button ){
var $button = $(button);
button.disabled = false;
editor.on('poInvs', function( event, state ){
$button[ state ? 'addClass' : 'removeClass' ]('inverted');
});
$button.click( function( event ){
event.preventDefault();
editor.setInvs( ! editor.getInvs() );
return false;
} );
locoScope.tooltip.init($button);
return true;
}
function registerCodeviewButton( button ){
var $button = $(button);
button.disabled = false;
$button.click( function(event){
event.preventDefault();
var state = ! editor.getMono();
editor.setMono( state );
$button[ state ? 'addClass' : 'removeClass' ]('inverted');
return false;
} );
locoScope.tooltip.init($button);
return true;
};
function registerAddButton( button ){
button.disabled = false;
$(button).click( function( event ){
event.preventDefault();
// Need a placeholder guaranteed to be unique for new items
var i = 1, baseid, msgid, regex = /(\d+)$/;
msgid = baseid = 'New message';
while( messages.get( msgid ) ){
i = regex.exec(msgid) ? Math.max(i,RegExp.$1) : i;
msgid = baseid+' '+( ++i );
}
editor.add( msgid );
return false;
} );
return true;
};
function registerDelButton( button ){
button.disabled = false;
$(button).click( function(event){
event.preventDefault();
editor.del();
return false;
} );
return true;
};
function registerDownloadButton( button, id ){
button.disabled = false;
$(button).click( function( event ){
var form = button.form,
path = filePath;
// swap out path
if( 'binary' === id ){
path = path.replace(/\.po$/,'.mo');
}
form.path.value = path;
form.source.value = messages.toString();
// allow form to submit
return true;
} );
return true;
}
// event handler that stops dead
function noop( event ){
event.preventDefault();
return false;
}
/*/ dummy function for enabling buttons that do nothing (or do something inherently)
function registerNoopButton( button ){
return true;
}*/
/**
* Update status message above editor.
* This is dynamic version of PHP Loco_gettext_Metadata::getProgressSummary
* TODO implement progress bar, not just text.
*/
function updateStatus(){
var t = translator,
stats = editor.stats(),
total = stats.t,
fuzzy = stats.f,
empty = stats.u,
// Translators: Shows total string count at top of editor
stext = sprintf( t._n('1 string','%s strings',total ), total.format(0) ),
extra = [];
if( locale ){
// Translators: Shows percentage translated at top of editor
stext = sprintf( t._('%s%% translated'), stats.p.replace('%','') ) +', '+ stext;
// Translators: Shows number of fuzzy strings at top of editor
fuzzy && extra.push( sprintf( t._('%s fuzzy'), fuzzy.format(0) ) );
// Translators: Shows number of untranslated strings at top of editor
empty && extra.push( sprintf( t._('%s untranslated'), empty.format(0) ) );
if( extra.length ){
stext += ' ('+extra.join(', ')+')';
}
}
$('#loco-po-status').text( stext );
}
/**
* Enable text filtering
*/
function initSearchFilter( elSearch ){
editor.searchable( loco.fulltext.init() );
// prep search text field
elSearch.disabled = false;
elSearch.value = '';
function showValidFilter( numFound ){
$(elSearch.parentNode)[ numFound || null == numFound ? 'removeClass' : 'addClass' ]('invalid');
}
var listener = loco.watchtext( elSearch, function( value ){
var numFound = editor.filter( value, true );
showValidFilter( numFound );
} );
editor
.on( 'poFilter', function( event, value, numFound ){
listener.val( value||'' );
showValidFilter( numFound );
} )
.on( 'poMerge', function( event, result ){
var value = listener.val();
value && editor.filter( value );
} )
;
}
// resize function fits editor to screen, accounting for headroom and touching bottom of screen.
var resize = function(){
function top( el, ancestor ){
var y = el.offsetTop||0;
while( ( el = el.offsetParent ) && el !== ancestor ){
y += el.offsetTop||0;
}
return y;
}
var fixHeight,
minHeight = parseInt($(innerDiv).css('min-height')||0)
;
return function(){
var padBottom = 20,
topBanner = top( innerDiv, document.body ),
winHeight = window.innerHeight,
setHeight = Math.max( minHeight, winHeight - topBanner - padBottom )
;
if( fixHeight !== setHeight ){
innerDiv.style.height = String(setHeight)+'px';
fixHeight = setHeight;
}
};
}();
// ensure outer resize is handled before editor's internal resize
resize();
$(window).resize( resize );
// initialize editor
innerDiv.innerHTML = '';
editor = loco.po.ed
.init( innerDiv )
.localise( translator )
;
loco.po.kbd
.init( editor )
.add( 'save', saveIfDirty )
.enable('copy','clear','enter','next','prev','fuzzy','save','invis')
;
// initialize toolbar button actions
var buttons = {
// help: registerNoopButton,
save: editable && registerSaveButton,
sync: editable && registerSyncButton,
revert: registerRevertButton,
// editor mode togglers
invs: registerInvisiblesButton,
code: registerCodeviewButton,
// downloads / post-throughs
source: registerDownloadButton,
binary: template ? null : registerDownloadButton
};
// POT only
if( template ){
buttons.add = editable && registerAddButton;
buttons.del = editable && registerDelButton;
}
// PO only
else {
buttons.fuzzy = registerFuzzyButton;
};
$('#loco-toolbar').find('button').each( function(i,el){
var id = el.getAttribute('data-loco'), register = buttons[id];
register && register(el,id) || $(el).hide();
} );
// disable submit on dummy form
$(elForm).submit( noop );
// enable text filtering
initSearchFilter( document.getElementById('loco-search') );
// editor event behaviours
editor
.on('poUnsaved', function(){
window.onbeforeunload = onUnloadWarning;
} )
.on('poSave', function(){
updateStatus();
window.onbeforeunload = null;
} )
.on( 'poUpdate', updateStatus );
;
// load raw message data
messages.load( conf.podata );
// ready to render editor
editor.load( messages );
// locale should be cast to full object once set in editor
if( locale = editor.targetLocale ){
locale.isRTL() && $(innerDiv).addClass('trg-rtl');
}
// enable template mode when no target locale
else {
editor.unlock();
}
// ok, editor ready
updateStatus();
// clean up
delete window.locoConf;
conf = buttons = null;
}( window, jQuery );