Utilizador:LBelo (WMB)/Common.js/addin-gpgp.js

Nota: Depois de publicar, poderá ter de contornar a cache do seu navegador para ver as alterações.

  • Firefox / Safari: Pressione Shift enquanto clica Recarregar, ou pressione Ctrl-F5 ou Ctrl-R (⌘-R no Mac)
  • Google Chrome: Pressione Ctrl-Shift-R (⌘-Shift-R no Mac)
  • Internet Explorer / Edge: Pressione Ctrl enquanto clica Recarregar, ou pressione Ctrl-F5
  • Opera: Pressione Ctrl-F5.
/**
 * Esse arquivo JavaScript é parte do Gerador de Perfis de Grupos de Pesquisa (GPGP) (https://pt.wikiversity.org/wiki/Gerador_de_Perfis_de_Grupos_de_Pesquisa).
 * 
 * Autor:
 * @author Wiki Movimento Brasil (WMB) (https://meta.wikimedia.org/wiki/Wiki_Movement_Brazil_User_Group)
 * 
 * Contribuidores:
 * @contributors Lucas Belo (https://en.wikiversity.org/wiki/User:LBelo_(WMB))
 * @contributors Éder Porto (https://en.wikiversity.org/wiki/User:EPorto_(WMB))
 * 
 * O código JavaScript faz uso extensivo de JQuery e permite recursos principais da interface de usuário, como:
 *   1.) Caixas modais para edições locais
 *   2.) Criação de páginas
 *   3.) Obtenção de conteúdos de páginas já existentes
 *
 * O MediaWiki API (https://www.mediawiki.org/wiki/API:Main_page) é usado para fazer edições e recuperar o conteúdo de páginas.
 * Para algumas solicitações e para o uso de mensagens, o MediaWiki JS API (https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.Api) é usado adicionalmente.
 * 
 * Notas:
 * A refatoração adicional deve incluir todas as funções e variáveis globais com "AddinGpgp_".
 * O código MOOC desenvolvido por Sebastian Schlicht e René Pickhardt serviram de base para o desenvolvimento do código atual (https://pt.wikiversity.org/wiki/MediaWiki:Common.js/addin-mooc.js)
 *
 */
// <nowiki>

var AddinGpgp_VERSION = '0.1';

/**
 * Objeto de configuração global para trabalhar com constantes e mensagens.
 */
var AddinGpgp_CONFIG = {
    LOADED: 0,
    MSG_PREFIX: 'AddinGpgp-',
    store: {},
    get: function (key) {
        return this.store[key];
    },
    set: function (key, value) {
        this.store[key] = value;
    },
    log: function (logLevel, key, params) {
        if (arguments.length > 0) {
            var minLevel = arguments[0];
            var crrLevel = this.get('LOG_LEVEL');
            if (crrLevel != -1 && minLevel >= crrLevel) {
                var msgParams = [];
                for (var i = 1; i < arguments.length; ++i) {
                    msgParams[i - 1] = arguments[i];
                }
                console.log(this.message.apply(this, msgParams));
            }
        }
    },
    message: function (key, params) {
        var msgParams = [];
        msgParams[0] = this.MSG_PREFIX + key;
        for (var i = 1; i < arguments.length; ++i) {
            msgParams[i] = arguments[i];
        }
        // construtor de mensagens de chamada com parâmetros de função adicionais em objeto separado
        return mw.message.apply(mw.message, msgParams).text();
    },
    setMessage: function (key, message) {
        mw.messages.set(this.MSG_PREFIX + key, message);
    }
};

// declara variáveis globais
var AddinGpgp_root;
var _base;
var _fullPath;
var nItemNav;

/*####################
  # PONTO DE ENTRADA
  # inicialização do sistema
  ####################*/

// carrega a configuração
importScript('User:LBelo (WMB)/Common.js/addin-gpgp-config.js');

// carrega as mensagens
importScript('User:LBelo (WMB)/Common.js/addin-gpgp-localization.js');

//DEBUG
// Aguarda recursos serem carregados na página atual
var execOnReady = function (callback) {
    if (AddinGpgp_CONFIG.LOADED < 2) {
        setTimeout(function () {
            execOnReady(callback);
        }, 200);
    } else {
        callback();
    }
};

execOnReady(function () {

    // carrega jQuery
    $(document).ready(function () {
        // configura o agente de usuário para solicitações de API
        $.ajaxSetup({
            beforeSend: function (request) {
                request.setRequestHeader("User-Agent", AddinGpgp_CONFIG.get('USER_AGENT_NAME') + '/' + AddinGpgp_VERSION + ' (' + AddinGpgp_CONFIG.get('USER_AGENT_URL') + '; ' + AddinGpgp_CONFIG.get('USER_AGENT_EMAIL') + ')');
            }
        });

        // conecta à interface de usuário via DOM tree
        AddinGpgp_root = $('#addin-gpgp');
        _base = $('#baseUrl').text();
        _fullPath = $('#path').text();
        nItemNav = $('#item-navigation');

        if (AddinGpgp_root.length === 0) {// não é uma página GPGP
            AddinGpgp_CONFIG.log(0, 'LOG_PAGE_NOGPGP');
            return;
        }

        // inicializa
        if (_fullPath === '') {// caminho do item raiz igual à base
            _fullPath = _base;
        }

        // caixas modais
        prepareModalBoxes();

        // torna clicáveis os botões de edição
        var showModalBox = function () {
            var btn = $(this);
            var modal = btn.next('.modal-box');
            if (modal.length == 0) {
                modal = btn.next().next('.modal-box');
            }
            var header = modal.parent().parent();
            nItemNav.css('z-index', 1);
            header.css('z-index', 2);
            // Mostra caixa modal com foco no campo de edição
            var editField = modal.find('fieldset').children('textarea');
            modal.toggle('fast', function () {
                editField.focus();
            });
            return false;
        };
        $('.btn-edit').each(function () {
            var btn = $(this);
            btn.click(showModalBox);
        });

        // torna adicionar GPGP clicável
        var divAddGpgp = $('#addGpgp');
        var imgAddGpgp = divAddGpgp.find('img');
        divAddGpgp.find('span').append(imgAddGpgp).children('a').remove();
        divAddGpgp.click(showModalBox);
    });
});
//DEBUG END


/**
 * Prepara todas as caixas modais. Registra todos os eventos de caixa da interface do usuário.
 */
function prepareModalBoxes() {
    // preenche caixa modal para um novo Grupo de pesquisa
    var newGpgp = [
    {field: 'title', numLines: 1},
    {field: 'description', numLines: 5},
    {field: 'color', numLines: 1},
    {field: 'researchers', numLines: 5},
    ];
    
    prepareModalBox('createGpgp', 'create-gpgp', newGpgp, function (idSection, title, summary) {
    	title = cleanAndFormatInput(title);
    	summary = cleanAndFormatInput(summary);
        createGpgp(title, summary, function () {
            return null;
        });
    }).find('.btn-save').prop('disabled', false);

    // faz caixas modais fechavéis através de botão
    $('.modal-box').each(function () {
        var modal = $(this);
        modal.find('.btn-close').click(function () {
            closeModalBox(modal);
            return false;
        });
    });

    // faz caixas modais fecháveis por meio de clique no plano de fundo
    $('.modal-box > .background').click(function (e) {
        closeModalBox($(e.target).parent());
        return false;
    });

    // faz caixas modais fecháveis pelo ESC
    $('.modal-box').on('keydown', function (e) {
        if (e.which == 27) {
            closeModalBox($(this));
        }
    });
}

/**
 * Obtem os valores da caixa modal.
 * @param {String} identificador da caixa (adicionar/editar). Tipo de intenção.
 * @param {String} identificador do parâmetro/recurso pelo qual a caixa modal é responsável
 */
function getValuesModalBox(intentType, idSection) {
    var _modal = $('#modal-' + intentType + '-' + idSection);
    var fields = _modal.find('input, textarea');
    var dict = {};

    fields.each((index, field) => {
        var key = $(field).attr('id');
        var value = $(field).val();
        
        value = cleanAndFormatInput(value);

        if (key !== undefined) {
            dict[key] = value;
        }
    });

    return dict;
}

/**
 * Cria uma caixa modal.
 * @param {String} identificador do parâmetro/recurso pelo qual a caixa modal é responsável
 * @param {String} identificador da caixa (adicionar/editar). Tipo de intenção.
 * @param {Object} objeto com instruções para os campos da caixa modal
 * @param {function} onSave retorno de chamada
 * @return {jQuery} nó da caixa modal criada
 */
function prepareModalBox(idSection, intentType, params, finishCallback) {
    // cria a estrutura da caixa modal
    var modalBox = $('#modal-' + intentType + '-' + idSection);
    modalBox.append($('<div>', {
        'class': 'background'
    }));
    var boxContent = $('<div>', {
        'class': 'content border-box'
    });
    boxContent.append($('<div>', {
        'class': 'btn-close'
    }));
    boxContent.append($('<div>', {
    	'id': 'spinner-modal-box',
    }));
    var editFieldset = $('<fieldset>', {
        'class': 'edit-field'
    });
    
    
    // cria um campo para cada parametro
    var firstEditField;
    var editField;
    params.forEach(function(item, index) {
	    editFieldset.append($('<label>', {
	        'for': 'edit-field-' + item.field,
	        'class': 'label-title',
	        'text': AddinGpgp_CONFIG.message('UI_MODAL_LABEL_TITLE_' + item.field + '-' + idSection)
	    }));
	    // define o campo de edicação
	    if (item.numLines > 1) {
	        editField = $('<textarea>', {
	        	'id': 'edit-field-' + item.field,
	            'class': 'border-box'
	        });
	    } else {
	        editField = $('<input>', {
	        	'id': 'edit-field-' + item.field,
	            'class': 'border-box',
	            'type': 'text'
	        });
	    }
	    
	    // Se for o primeiro campo, armazene a referência (título da página)
        if (index === 0) {
            firstEditField = editField;
        }
        
	    editFieldset.append(editField);
    });
    
    // sumário de edição
    editFieldset.append($('<label>', {
        'for': 'summary-' + idSection,
        'class': 'label-summary',
        'text': AddinGpgp_CONFIG.message('UI_MODAL_LABEL_SUMMARY')
    }));
    var ibSummary = $('<input>', {
        'id': 'summary-' + idSection,
        'class': 'border-box summary',
        'type': 'text'
    });
    editFieldset.append(ibSummary);
    
    // texto de ajuda
    var divHelpText = $('<div>', {
        'class': 'help'
    }).html(AddinGpgp_CONFIG.message('UI_MODAL_HELP_' + idSection, _fullPath));
    editFieldset.append(divHelpText);
    boxContent.append(editFieldset);
    
    // botão de finalização
    var btnSave = $('<input>', {
        'class': 'btn-save',
        'disabled': true,
        'type': 'button',
        'value': AddinGpgp_CONFIG.message('UI_MODAL_BTN_' + intentType)
    });
    boxContent.append(btnSave);
    btnSave.click(function () {
        if (!btnSave.prop('disabled')) {
            btnSave.prop('disabled', true);
            $('#spinner-modal-box').show();
            finishCallback(idSection, firstEditField.val(), ibSummary.val());
        }
        return false;
    });
    modalBox.append(boxContent);
    return modalBox;
}


/**
 * Cria um Grupo de Pesquisa (GP)
 * @param {String} nome do Grupo de Pesquisa
 * @param {String} resumo de edição
 * @param {function} success callback
 */
async function createGpgp(title, summary, sucCallback) {
    var rootModelPage = 'Utilizador:LBelo (WMB)/GPGP';
    var payloadNewPages = [
        { titleNewPage: title, modelPage: rootModelPage + '/Início' },
        { titleNewPage: title + '/Quem somos', modelPage: rootModelPage + '/Quem somos' },
        { titleNewPage: title + '/O que estudamos', modelPage: rootModelPage + '/O que estudamos' },
        { titleNewPage: title + '/Quando nos encontramos', modelPage: rootModelPage + '/Quando nos encontramos' },
        { titleNewPage: title + '/Bolsas e Recursos - Eventos', modelPage: rootModelPage + '/Bolsas e Recursos - Eventos' },
        { titleNewPage: title + '/Cabeçalho', modelPage: rootModelPage + '/Cabeçalho' },
        { titleNewPage: 'Categoria:' + title, modelPage: 'Categoria:' + rootModelPage }
    ];

    var idSection = 'createGpgp';
    var intentType = 'create-gpgp';
    var valuesModalBox = getValuesModalBox(intentType, idSection);
    
	var n = payloadNewPages.length;
    for (var i = 0; i < n; i++) {
        var pageContent = await waitDoPageContentRequest(payloadNewPages[i].modelPage);

        // Insere os valores da caixa modal no conteúdo da nova página
        for (var key in valuesModalBox) {
            var value = valuesModalBox[key];
            key = key.replace('edit-field-', '');
            var regex = new RegExp('\\$\\{' + key + '\\}', 'g');
            pageContent = pageContent.replace(regex, value);
        }

        await waitCreatePage(payloadNewPages[i].titleNewPage, pageContent, summary, sucCallback, i, n);
    }
}


/**
 * Aguarda a execução de doPageContentRequest()
 * @param {String} título da página com conteúdo
 */
async function waitDoPageContentRequest(title) {
    return new Promise((resolve, reject) => {
        doPageContentRequest(title, null, function (pageContent) {
            resolve(pageContent);
        });
    });
}


/**
 * Aguarda a execução de createPage()
 * @param {String} título da nova página
 */
async function waitCreatePage(title, content, summary, sucCallback, i, n) {
    return new Promise((resolve, reject) => {
        createPage(title, content, summary, function () {
        	if (i == (n-1)){
        		reloadPage();
        		resolve();
        	}else{
        		resolve(sucCallback);
        	}
        });
    });
}


/**
 * Fecha uma caixa modal.
 * @param {jQuery} caixa modal a ser fechada
 */
function closeModalBox(modal) {
    nItemNav.css('z-index', 1001);
    modal.parent().parent().css('z-index', 1);
    modal.fadeOut();
}


/**
 * Cria uma página Wiki.
 * @param {String} título da nova página
 * @param {String} conteúdo da nova página
 * @param {String} resumo de edição
 * @param {function} success callback
 */
function createPage(pageTitle, content, summary, sucCallback) {
    doEditRequest(pageTitle, null, content, summary, sucCallback);
}


/**
 * Edita uma página Wiki. (páginas não existentes serão criadas automaticamente)
 * @param {String} título de uma página wiki
 * @param {int} seção dentro da página wiki; passe null para editar a página inteira
 * @param {String} conteúdo a ser inserido na página
 * @param {String} resumo de edição
 * @param {function} success callback
 */
function doEditRequest(pageTitle, section, content, summary, sucCallback) {
    AddinGpgp_CONFIG.log(0, 'LOG_WEDIT', pageTitle, section);
    doEditTokenRequest([pageTitle], function (editTokens) {
        var editToken = editTokens.get(pageTitle);
        var editData = {
            'title': pageTitle,
            'text': content,
            'summary': summary,
            'watchlist': 'watch',
            'token': editToken
        };
        if (section !== null) {
            editData.section = section;
        }
        $.ajax({
            type: "POST",
            url: AddinGpgp_CONFIG.get("MW_API_URL") + "?action=edit&format=json",
            data: editData
        }).fail(function (jqXHR) {
            AddinGpgp_CONFIG.log(1, 'ERR_WEDIT_REQ', pageTitle, jqXHR.status);
        }).done(function (response) {//TODO handle errors
            AddinGpgp_CONFIG.log(0, 'LOG_WEDIT_RES', JSON.stringify(response));
            sucCallback();
        });
    });
}


/**
 * Recupera tokens de edição para qualquer número de páginas wiki.
 * @param {Array<String>} títulos de páginas das páginas wiki
 * @param {function} success callback (Object editTokens: editTokens.get(pageTitle) = token)
 */
function doEditTokenRequest(pageTitles, sucCallback) {
    var sPageTitles = pageTitles.join('|');
    // get edit tokens
    var tokenData = {
        'meta': 'tokens',
        'type': '*'
    };
    $.ajax({
        type: "POST",
        url: AddinGpgp_CONFIG.get("MW_API_URL") + "?action=query&prop=info&format=json&titles=" + sPageTitles,
        data: tokenData
    }).fail(function (jqXHR) {
        AddinGpgp_CONFIG.log(1, 'ERR_WTOKEN_REQ', sPageTitles, jqXHR.status);
    }).done(function (response) {
        var editTokens = parseEditTokens(response);
        if (editTokens.hasTokens()) {
            sucCallback(editTokens);
        } else {
            AddinGpgp_CONFIG.log(1, 'ERR_WTOKEN_MISSING', sPageTitles, JSON.stringify(response));
        }
    });
}


/**
 * Analisa uma resposta do servidor contendo um ou vários tokens de edição.
 * @param {JSON} tokenResponse
 * @return {Object} edit tokens object - você pode recuperar o token de edição passando o título da página para o objeto 'get'-function
 */
function parseEditTokens(tokenResponse) {
    var hasTokens = false;
    var editTokens = {
        'tokens': [],
        'add': function (title, edittoken) {
            var lTitle = title.toLowerCase();
            AddinGpgp_CONFIG.log(0, 'LOG_WTOKEN_TOKEN', title, edittoken);
            this.tokens[lTitle] = edittoken;
            hasTokens = true;
        },
        'get': function (title) {
            return this.tokens[title.toLowerCase()];
        },
        'hasTokens': function () {
            return hasTokens;
        }
    };
    var path = ['query', 'pages'];
    var crr = tokenResponse;
    for (var i = 0; i < path.length; ++i) {
        if (crr && crr.hasOwnProperty(path[i])) {
            crr = crr[path[i]];
        } else {
            AddinGpgp_CONFIG.log(1, 'ERR_WTOKEN_PARSING', path[i]);
            crr = null;
            break;
        }
    }
    if (crr) {
        var pages = crr;
        for (var pageId in pages) {
            // page exists
            if (pages.hasOwnProperty(pageId)) {
                var page = pages[pageId];
                editTokens.add(page.title, tokenResponse.query.tokens.csrftoken);
            }
        }
    }
    return editTokens;
}


/**
 * Recarrega a página atual.
 * @param {String} (opcional) página âncora a ser carregada
 */
function reloadPage(anchor) {
    if (typeof anchor === 'undefined') {
        document.location.search = document.location.search + '&action=purge';
    } else {
        window.location.href = document.URL.replace(/#.*$/, '') + '?action=purge' + anchor;
    }
}


/**
 * Solicite o conteúdo wikitexto simples de uma página wiki.
 * Use 'action=raw' para obter o conteúdo da página.
 * @param {String} título de uma página wiki
 * @param {int} seção dentro da página wiki; passe null para recuperar a página inteira
 * @param {function} success callback (String pageContent)
 * @param {function} (opcional) failure callback (Object jqXHR: HTTP request object)
 */
function doPageContentRequest(pageTitle, section, sucCallback, errorCallback) {
    var url = AddinGpgp_CONFIG.get("MW_ROOT_URL") + "?action=raw&title=" + pageTitle;
    if (section !== null) {
        url += "&section=" + section;
    }
    $.ajax({
        url: url,
        cache: false
    }).fail(function (jqXHR) {
        AddinGpgp_CONFIG.log(1, 'ERR_WCONTENT_REQ', pageTitle, section, jqXHR.status);
        if (typeof errorCallback !== 'undefined') {
            errorCallback(jqXHR);
        }
    }).done(sucCallback);
}


/**
 * Limpa e formata um texto fornecido pelo usuário
 */
function cleanAndFormatInput(value){
	value = value.replace(/\s+/g, ' ');
    value = value.trim();
    return value;
}


// </nowiki>