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 += "§ion=" + 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>