МедияУики:Gadget-AddTranslations.js

От Уикиречник

Забележка: За да се видят промените, необходимо е след публикуване на страницата, кешът на браузъра да бъде изтрит.

  • Firefox / Safari: Задържа се клавиш Shift и се щраква върху Презареждане (Reload) или чрез клавишната комбинация Ctrl-F5 or Ctrl-R (⌘-R за Mac);
  • Google Chrome: клавишна комбинация Ctrl-Shift-R (⌘-Shift-R за Mac)
  • Internet Explorer / Edge: Задържа се клавиш Ctrl и се щраква върху Refresh или чрез клавишната комбинация Ctrl-F5;
  • Opera: Press Ctrl-F5.
// https://bg.wiktionary.org/wiki/MediaWiki:Gadget-AddTranslations.js
// Maintained by User:V111P

(function ($) {

var pageText = '';
var pageTimestamp = '';
var unsaved = [];
var langs;
var editConflictLastTime = false;

var info = mw.config.get(['wgPageName', 'wgUserLanguage', 'wgAction', 'wgScriptPath']);
info.pageNameEncoded = encodeURIComponent(info.wgPageName);

if ( info.wgAction == 'edit' || info.wgAction == 'submit'
	|| document.location.search.match(/(&|\?)oldid=/) ) return;

var msgsBg = {
    gadgetName: 'Добавяне на превод',
    expand: 'Разгръщане',
    collapse: 'Свиване',
    alreadyPublished: 'Вече публикувани преводи:',
    toBePublished: 'Преводи очакващи публикуване:',
    addTranslation: 'Добавяне на превод:',
    publishBtn: 'Публикуване',
    removeAllBtn: 'Изтриване на всички',
    lang: 'език',
    langHelp: 'име (на бълг. или англ.) или код на езика',
    word: 'дума',
    translit: 'транслит.',
    notReqd: 'незадължително',
    gender: 'род',
    maleG: 'м.',
    femaleG: 'ж.',
    neuterG: 'ср.',
    commonG: 'общ',
    addBtn: 'Добре',
    uniddLang: 'неиндентифициран език',
    unrecognizedLang: 'Неразпознато име или код на език',
    enterLangAndWord: 'Моля, въведете език и дума',
    enterLang: 'Моля, въведете език',
    enterWord: 'Моля, въведете дума',
    errorOnReceing: 'Грешка при получаването на уикитекста на страницата от сървъра',
    errorOnSaving: 'Грешка при публикуването',
    editConflictRetrying: 'Конфликт на редакциите. Сега опитваме отново...',
    editConflictTryAgain: 'Конфликт на редакциите. Опитайте отново.',
    langListError: 'Грешка със списъка с езици №%num%',
    langListTryIn5: 'Можете да опитате отново след 5 секунди',
    langListDWFailed: 'Error: Language list download failed',
    notFoundTransTopNum: 'В кода на страницата няма шаблон trans-top номер %N%',
    notFoundTransBottomNum: 'В кода на страницата няма {{trans-bottom '
                          + 'след шаблон trans-top номер ',
    resumeAddTransl: 'добавяне на превод(и) за значение %meaningNum%',
    confirmDelAllUnsaved: 'Премахване на всички непубликувани преводи?'
};

var msgsEn = {
    gadgetName: 'Add translations',
    expand: 'Expand',
    collapse: 'Collapse',
    alreadyPublished: 'Already published translations:',
    toBePublished: 'Translations awaiting publication:',
    addTranslation: 'Add a translation:',
    publishBtn: 'Publish',
    removeAllBtn: 'Remove all',
    lang: 'language',
    langHelp: 'name or ISO code of the language',
    word: 'word',
    translit: 'translit.',
    notReqd: 'not required',
    gender: 'gender',
    maleG: 'm.',
    femaleG: 'f.',
    neuterG: 'n.',
    commonG: 'common',
    addBtn: 'Next',
    uniddLang: 'unidentified language',
    unrecognizedLang: 'Unrecognized language name or ISO code',
    enterLangAndWord: 'Please, enter both a language and a word',
    enterLang: 'Please, enter a language',
    enterWord: 'Please, enter a word',
    errorOnReceing: 'An error occured when trying to get the wikitext of the page from the server',
    errorOnSaving: 'An error occured while trying to publish the translations',
    editConflictRetrying: 'Edit conflict detected. Now retrying...',
    editConflictTryAgain: 'Edit conflict detected. Please, try again.',
    langListError: 'Language list error #%num%',
    langListTryIn5: 'You can try again in 5 seconds',
    langListDWFailed: 'Error: Language list download failed',
    notFoundTransTopNum: 'In the wikitext of the page there is no template trans-top number %N%',
    notFoundTransBottomNum: 'In the wikitext of the page there is no {{trans-bottom '
                          + 'after template trans-top number ',
    resumeAddTransl: 'adding translation(s) for meaning %meaningNum%',
    confirmDelAllUnsaved: 'Remove all unpublished translations?'
};

var msgs = ( info.wgUserLanguage == 'en' ? msgsEn : msgsBg );

var classNames =
[ 'mainDiv'
, 'contentDiv'
, 'savedDiv'
, 'unsavedDiv'
, 'addDiv'
, 'savedList'
, 'unsavedList'
, 'publishButton'
, 'delAllButton'
, 'langInput'
, 'langMsgSpan'
, 'wordInput'
, 'transcrInput'
, 'pageInput'
, 'genderDiv'
, 'addButton'
, 'addErrMsgDiv'
, 'publishErrMsgDiv'
, 'expandCollapseLink'
];

var classes = {}; // CSS class names
var sels = {};    // CSS selectors
var classPrefix = 'addTranslations_';
$.each(classNames, function (i, val) {
    classes[val] = classPrefix + val;
    sels[val] = '.' + classPrefix + val;
});

var mainDivDataName = classes.mainDiv + 'N';

$(sels.mainDiv).remove();

var twoColCss = ''; // '-moz-column-count: 2; column-count: 2; -webkit-column-count: 2';
var $mainDiv = $('<div class="' + classes.mainDiv + '" style="text-align:left"/>');

var $savedDiv = $('<div class="' + classes.savedDiv + '" style="display:none; margin-top:1em"><b>'
                 + msgs.alreadyPublished + '</b><div style="' + twoColCss + '"><ul class="'
                 + classes.savedList + '"></ul></div></div>');

var $unsavedDiv = $('<div class="' + classes.unsavedDiv + '" style="display:none; margin-top:1em"><b>'
                   + msgs.toBePublished + '</b><div style="' + twoColCss + '"><ul class="'
                   + classes.unsavedList + '" style="margin-bottom:1em"></ul></div></div>')
                  .append('<input type="button" class="' + classes.publishButton + '" value="'
                         + msgs.publishBtn + '"/> ')
                  .append('<input type="button" class="' + classes.delAllButton + '" value="'
                         + msgs.removeAllBtn + '"/>')
                  .append('<div class="' + classes.publishErrMsgDiv + '" style="color:red"/>');


var $addDiv = $('<div class="' + classes.addDiv + '" style="margin-top:1em"></div>');

$addDiv
.append(msgs.lang + ': <input type="text" class="' + classes.langInput + '" size="10" maxlength="50"/> <span class="'
       + classes.langMsgSpan + '"> ' + msgs.langHelp + '</span><br/>')
.append(msgs.word + ': <input type="text" class="' + classes.wordInput + '" maxlength="500"/><br/>')
//.append('№: <input type="text" class="' + classes.importanceNInput + '" size="2"/><br/>')
.append(msgs.translit + ': <input type="text" class="' + classes.transcrInput + '" maxlength="500"/> (' + msgs.notReqd + ')<br/>')
//.append('страница: <input type="text" class="' + classes.pageInput + '"/> (ако е различна от думата)<br/>')
/*.append('<div class=' + classes.genderDiv + '">' + msgs.gender + ': '
       + '<input type="checkbox" value="m"/> ' + msgs.maleG + ' '
       + '<input type="checkbox" value="f"/> ' + msgs.femaleG + ' '
       + '<input type="checkbox" value="n"/> ' + msgs.neuterG + ' '
       + '<input type="checkbox" value="c"/> ' + msgs.commonG + ' -- не работят все още</div>'
    )            */
.append('<input type="button" class="' + classes.addButton + '" value="' + msgs.addBtn + '" style="margin-top:1em">')
.append('<div class="' + classes.addErrMsgDiv + '" style="color:red"/>');

var $contentDiv = $('<div class="' + classes.contentDiv + '" style="display:none; margin-bottom:1em;"/>')
                  .append($addDiv, $unsavedDiv, $savedDiv);
var $headingRow = $('<div/>').append($('<span/>').append(msgs.addTranslation))
                  .append(' [<a class="' + classes.expandCollapseLink + '" style="cursor: pointer">'
                         + msgs.expand + ' ▼</a>]');
$mainDiv.append($headingRow, $contentDiv);

var els = []; // jQuery objects containing the created by this script DOM elements

$('table.translations').each(function (i, v) {
    unsaved[i] = [];
    var $mainDivClone = $mainDiv.clone().addClass(classes.mainDiv + i).data(mainDivDataName, i);
    $(this).after($mainDivClone);
    var elsN = els[i] = {mainDiv: $mainDivClone};
    $.each(classNames, function (i_, v) {
        if (v == 'mainDiv') return;
        elsN[v] = $mainDivClone.find(sels[v]);
    });
});

$(sels.expandCollapseLink).click(function () {
    var $this = $(this);
    var $contentDiv = $this.closest(sels.mainDiv).find(sels.contentDiv);
    if ($contentDiv.css('display') == 'none') {
        $this.text(msgs.collapse + ' ▲');
        $contentDiv.css('display', 'block');
        $contentDiv.find(sels.langInput).focus();
    }
    else {
        $this.text(msgs.expand + ' ▼');
        $contentDiv.css('display', 'none');
    }

});

$(sels.langInput).focus(function () {
    var $addDiv = $(this).closest(sels.addDiv);

    function callbk($msgBox) {
        var lang = $addDiv.find(sels.langInput).val().trim();
        if (lang != '') doLangId($msgBox, lang);
    }

    requireLangList(callbk, $addDiv.find(sels.langMsgSpan));
});

$(sels.langInput).on('change', function () {
    var $addDiv = $(this).closest(sels.addDiv);

    function callbk($msgBox) {
        var lang = $addDiv.find(sels.langInput).val().trim();
        if (lang !== '')
          doLangId($msgBox, lang);
    }

    requireLangList(callbk, $(this).closest(sels.addDiv).find(sels.langMsgSpan));
});


function error(msg) {
    alert(msgs.gadgetName + ':\n' + msg);
    console.log(msg);
}


function extractAndSavePageCode(obj) {
    pageText = null;
    try {
        $.each(obj.query.pages, function (key, val) {
            pageText = val.revisions[0]['*'];
            pageTimestamp = val.revisions[0].timestamp;
        });
    }
    catch (e) { error(msgs.errorOnReceing + '\n' + e.message); return; }
    $(sels.publishButton).removeClass('working');
}

function saveTranslations($mainDiv) {
    var n = $mainDiv.data(mainDivDataName);
    var elsN = els[n];
    var templStart = nthIndex(pageText, '{{trans-top', n + 1);
    var editConflictPrevTime = editConflictLastTime;
    editConflictLastTime = false;

    if (templStart == -1) {
        error( msgs.notFoundTransTopNum.replace(/%N%/, n + 1) );
        return;
    }
    var firstLine = pageText.indexOf('\n', templStart) + 1;
    var templEnd = pageText.indexOf('{{trans-bottom', templStart);
    if (templEnd == -1) {
        error( msgs.notFoundTransBottomNum.replace(/%N%/, n + 1) );
        return;
    }
    var t = pageText.slice(templStart, templEnd);
    var midStart = pageText.indexOf('{{trans-mid', firstLine);
    var midEnd = pageText.indexOf('\n', midStart);
    var midStr = pageText.slice(midStart, midEnd); // to save back line exactly as found
    var t1 = pageText.slice(firstLine, midStart - 1).trim().replace(/\n\n+/g, '\n');
    var t2 = pageText.slice(midEnd + 1, templEnd - 1).trim().replace(/\n\n+/g, '\n');
    var tAll = t1 + '\n' + t2;
    var tArr = tAll.split('\n');
    $.each(tArr, function (i, line) {
        // could be a lang name w/out a template:
        var lCode = (line.match(/[*: ]*(?:\{\{)?([^}:]+)(?:}}) *:/) || ['', ''])[1];
        var l = idLang(lCode, false);
        var lSortKey = (l ? l.bgName : lCode);
        tArr[i] = {sortKey: lSortKey, line: line};
    });

    // add the unsaved[n]'s elements
    var toBePublishedListMsgLns = [];
    var toBePublishedLangCodes = [];
    $.each(unsaved[n], function (i, v) {
        var sortKey = v.lang.bgName;
        var trscr = v.transcr;
        var lCode = v.lang.code;
        var lineStart = '*{{' + lCode + '}}: ';
        var entry = '{{п' + (trscr ? '+' : '') + '|' + lCode + '|'
                  + v.word + (trscr ? '|' + trscr : '') + '}}';
        var transcrP = ( trscr ? ' (' + trscr + ')' : '' );
        var addedToTempl = false;
        $.each(tArr, function (i, o) {
            if (o.sortKey == sortKey) { // add to this line
                o.line = o.line + ', ' + entry;
                addedToTempl = true;
                return false;
            }
        });
        if (!addedToTempl) {
            tArr.push({sortKey: sortKey, line: lineStart + entry});
        }
        tArr.sort(function (v1, v2) {
            if (v1.sortKey < v2.sortKey) return -1;
            else if (v1.sortKey == v2.sortKey) return 0;
            else return 1;
        });
        toBePublishedListMsgLns.push(v.lang.name + ' (' + v.lang.code + '): ' + v.word + transcrP);
        toBePublishedLangCodes.push(v.lang.code);
    });
    var sndColIndex = tArr.length;
    $.each(tArr, function (i, o) {
        if (o.sortKey[0] < 'н') {
            sndColIndex = i + 1;
        }
        tArr[i] = o.line;
    });
    tArr.splice(sndColIndex, 0, midStr);
    var newWikitext = pageText.slice(0, firstLine) + tArr.join('\n')
                    + '\n' + pageText.slice(templEnd);

    var editRequestObj = {
        action: 'edit',
        nocreate: '',
        title: info.wgPageName,
        basetimestamp: pageTimestamp,
        summary: msgs.resumeAddTransl.replace(/%meaningNum%/, n + 1) + ': ' + toBePublishedLangCodes.join(', '),
        text: newWikitext,
        token: mw.user.tokens.get('csrfToken'),
        format: 'json'
    };
    $.post(info.wgScriptPath + '/api.php', editRequestObj, function (data) {
        var err = data.error;
        var msg = '';
        if (err) {
            var editConflictMsg = err.code == 'editconflict' ? msgs.editConflict : '';
            if (err.code == 'editconflict')
              msg = editConflictPrevTime ? msgs.editConflictTryAgain : msgs.editConflictRetrying;
            else msg = err.info;
            elsN.publishErrMsgDiv.text(msg);
            if (err.code == 'editconflict') {
                editConflictLastTime = true;
                if (!editConflictPrevTime)
                  elsN.publishButton.click();
                else error(msg);
            }
            console.log( 'Add translations tool: Error while saving: ' + err.info + ' (' + err.code + ')' );
        } else {
            $.each(toBePublishedListMsgLns, function (i, v) {
                elsN.savedList.append('<li>' + v + '</li>');
            });

            pageText = newWikitext;
            elsN.savedDiv.css('display', 'block');
            elsN.unsavedDiv.css('display', 'none');
            elsN.unsavedList.empty();
            unsaved[n] = [];
            elsN.publishErrMsgDiv.empty();
        }
        elsN.publishButton.removeClass('working');
    });
}


$(sels.publishButton).click(function (e) {
    var $el = $(this).addClass('working');

    function go() {
        saveTranslations($el.closest(sels.mainDiv));
    }
    e.preventDefault();

    if (pageText != '' && !editConflictLastTime) go();
    else {
        $.get(info.wgScriptPath
             + '/api.php?action=query&prop=revisions&rvprop=content|timestamp&format=json&titles='
             + info.pageNameEncoded,
              function (obj) {
                  extractAndSavePageCode(obj);
                  if (pageText != null) go();
              }
        );
    }
});


$(sels.delAllButton).click(function (e) {
    var $mainDiv = $(this).closest(sels.mainDiv);
    var n = $mainDiv.data(mainDivDataName);
    if (unsaved[n].length == 0 || !confirm( msgs.confirmDelAllUnsaved ))
        return;
    unsaved[n] = [];
    els[n].unsavedDiv.css('display', 'none');
    els[n].unsavedList.empty();
    els[n].publishErrMsgDiv.empty();
});


$(sels.addButton).click(function (e) {
    e.preventDefault();
    var $mainDiv = $(this).closest(sels.mainDiv);
    var n = $mainDiv.data(mainDivDataName);
    var elsN = els[n];

    var lang = elsN.langInput.val().trim().substr(0, 50);
    var word = elsN.wordInput.val().trim().substr(0, 300);
    var transcr = elsN.transcrInput.val().trim().substr(0, 300);
    var $errorMsgBox = elsN.addErrMsgDiv;
    var err = '';

    $errorMsgBox.empty();
    if (lang == '' && word == '') err = msgs.enterLangAndWord;
    else if (lang == '')  err = msgs.enterLang;
    else if (word == '') err = msgs.enterWord;
    if (err) {
        $errorMsgBox.text(err);
        return;
    }

    requireLangList(reqLangListCallback, $errorMsgBox, lang, true);

    function reqLangListCallback($errorMsgBox, lang) {
        var langId = idLang(lang, true);

        if (!langId) {
            $errorMsgBox.text( msgs.unrecognizedLang + ': ' + lang );
            return;
        }

        elsN.wordInput.val('');
        elsN.transcrInput.val('');
        elsN.langInput.val('').focus();
        elsN.langMsgSpan.text( msgs.langHelp );
        var transcrP = ( transcr ? ' (' + transcr + ')' : '' );
        elsN.unsavedList.append('<li>' + langId.name + ' (' + langId.code + '): '
                               + word + transcrP + '</li>');

        unsaved[n].push( {lang: langId, word: word, transcr: transcr} );
        elsN.unsavedDiv.css('display', 'block');
    }
});


var langListLastRequested = 0;
var langListDWFailed = false;
function requireLangList(callback, $msgBox, param, important) {
    if (langs) {
        if (callback) callback($msgBox, param);
        return;
    }

    var time = new Date().getTime();
    var timeToWait = (important || langListDWFailed) ? 5000 : 15000 // in milliseconds
    if ( time - langListLastRequested < timeToWait ) {
        if (important) $msgBox.text(msgs.langListTryIn5);
        return;
    }
    if (important) $msgBox.empty();
    langListLastRequested = time;
    (new mw.Api()).get({
        action: 'expandtemplates',
        format: 'json',
        prop: 'wikitext',
        text: '{{#invoke:Langs|getLangsAsJson|userLang= ' + info.wgUserLanguage + ' }}'
    }).done(function(data) {
        var json = data && data.expandtemplates && data.expandtemplates.wikitext;

        if (!json) {
            if ($msgBox) $msgBox.text( msgs.langListError.replace(/%num%/, 1) );
            console.log('addTranslations.js: requireLangList(): No \'wikitext\' value in response.'
                       , '\n', json.substr(0, 200));
        }
        else {
            try {
                langs = JSON.parse(json);
            }
            catch (e) {
                if ($msgBox) $msgBox.text(  msgs.langListError.replace(/%num%/, 2) );
                console.log('addTranslations.js: requireLangList(): Error parsing JSON: ', e
                           , '\n', json.substr(0, 200), data);
                return;
            }
            if (callback) callback($msgBox, param);
        }
    }).fail(function (e) {
        if ($msgBox) $msgBox.text(msgs.langListDWFailed);
        console.log('addTranslations.js: requireLangList(): Lang list download failed.');
    });
}


var doLangId = function ($msgSpan, val) {
    langId = idLang(val, true);

    msg = (langId == null
          ? msgs.uniddLang + ': „' + val + '“'
          : langId.name + ' (' + langId.code + ')');

    $msgSpan.text(msg);
}


$(sels.langInput).on('keyup', function (e) {
    var $input = $(this);
    var $msgSpan = els[ $input.closest(sels.mainDiv).data(mainDivDataName) ].langMsgSpan;
    var val = $input.val().trim().substr(0, 50);
    var langId, msg;

    if (val == '') {
        msg = msgs.langHelp;
        $msgSpan.text(msg);
    }
    else {
        requireLangList(doLangId, $msgSpan, val);
    }
});


// by kennebec, http://stackoverflow.com/a/14482123
function nthIndex(str, pat, n) {
    var L = str.length, i = -1;
    while (n-- && i++ < L) {
        i = str.indexOf(pat, i);
        if (i < 0) break;
    }
    return i;
}


function idLang(lang, autocomplete) {
    var name = '';
    var code = '';
    var userLang = info.wgUserLanguage
    var namesArr, codesArr, displayName, bgName, doAC;
    lang = lang.trim().toLowerCase().replace(/[()[\]{}\\+*?^$|]/g, ''); // used in regexes below
    var namesSearchArrName = lang.match(/^[а-я]/) ? 'bgNames'  : 'enNames'
    var nameDisplayPropertyName = userLang == 'en' ? 'enNames'  : 'bgNames'
    var latin = lang.match(/^[a-z]/) ? true : false;
    var exactMatchFound = false;

    $.each(langs, function (i1, langObj) {
        namesArr = langObj[namesSearchArrName];
        displayName = langObj[nameDisplayPropertyName][0];
        codesArr = langObj.codes;

        if (latin) { // check for language code match
            $.each(codesArr, function (i2, code_) {
                doAC = autocomplete && name == '';
                exactMatchFound = code_ == lang;
                if ( exactMatchFound || ( doAC && code_.match("^" + lang) ) ) {
                    name = displayName;
                    bgName = langObj.bgNames[0];
                    code = codesArr[0];
                }
                return !exactMatchFound; // return false to break out of $.each()
            });
        }
        if (!exactMatchFound) { // check for language name match
            $.each(namesArr, function (i3, name_) {
                doAC = autocomplete && name == '' && (lang.length > 1 || !latin);
                exactMatchFound = name_ == lang;
                if ( exactMatchFound || ( doAC && name_.match("^" + lang) ) ) {
                    name = displayName;
                    bgName = langObj.bgNames[0];
                    code = codesArr[0];
                }
                return !exactMatchFound; // return false to break out of $.each()
            });
        }
        return !exactMatchFound; // return false to break out of $.each()
    });
    if (name.length > 0)
      name = userLang == 'en' ? toTitleCase(name) : name
    return ( name == '' ? null : { name: name, bgName: bgName, code: code } );
}


function toTitleCase(str)
{
    return str.replace(/[^\s-]+/g, function(txt) {
               return txt[0].toUpperCase() + txt.substr(1); // .toLowerCase();
           });
}


return {

};


})($);