MediaWiki:Gadget-lib-wikiprojects.js
Uwaga: aby zobaczyć zmiany po opublikowaniu, może zajść potrzeba wyczyszczenia pamięci podręcznej przeglądarki.
- Firefox / Safari: Przytrzymaj Shift podczas klikania Odśwież bieżącą stronę, lub naciśnij klawisze Ctrl+F5, lub Ctrl+R (⌘-R na komputerze Mac)
- Google Chrome: Naciśnij Ctrl-Shift-R (⌘-Shift-R na komputerze Mac)
- Internet Explorer / Edge: Przytrzymaj Ctrl, jednocześnie klikając Odśwież, lub naciśnij klawisze Ctrl+F5
- Opera: Naciśnij klawisze Ctrl+F5.
//@ts-check
/**
* Biblioteka do obsługi listy wikiprojektów na polskojęzycznej Wikipedii.
*
* Dokumentacja: [[Wikipedia:Narzędzia/lib-wikiprojects]]
*
* @author [[w:pl:User:Msz2001]]
*/
(function(){
/** Klucz pamięci podręcznej, odpowiadający liście wikiprojektów */
var WIKIPROJECTS_CACHE_KEY = 'wikiprojects.list';
/** Lokalizacja strony JSON z wikiprojektami na wiki */
var WIKIPROJECTS_LIST_PAGE = 'Wikipedia:Wikiprojekt/Spis_wikiprojektów.json';
/** Aktualna wersja formatu listy wikiprojektów */
var WIKIPROJECTS_LIST_VERSION = 1;
/** Domyślne opcje pobierania i filtrowania */
var DEFAULT_OPTIONS = {
/** Maksymalny wiek pamięci podręcznej (w dniach). Jeśli od ostatniego
* pobrania upłynęło więcej czasu, skrypt podejmie próbę odświeżenia
* zawartości pamięci podręcznej. W przypadku niepowodzenia, użyte zostaną
* przeterminowane dane. */
maxCacheAge: 7,
/** Jeśli ustawione na `true`, nie zwraca wikiprojektów nieaktywnych. */
skipInactive: true,
/** Jeśli ustawione na `true`, nie zwraca wikiprojektów instytucjonalnych. */
skipInstitutional: false,
/** Jeśli ustawione na `true`, nie zwraca wikiprojektów specjalnych. */
skipSpecial: true,
/** Niezależnie od ustawień pomijania, jeśli wikiprojekt jest związany z
* gadżetem, który pojawia się w tej tablicy, zostanie dołączony do wyniku. */
includeGadgets: [],
};
/**
* Odczytuje listę wikiprojektów z pamięci podręcznej i zwraca ją.
* Jeśli w pamięci nie ma listy, zwraca `null`.
* @private
* @returns {WikiprojectsResponse | null}
*/
function restoreWikiprojectsFromCache() {
var wikiprojects = mw.storage.getObject(WIKIPROJECTS_CACHE_KEY);
if(!wikiprojects) return null;
if(!wikiprojects.fetchDate) return null;
if(wikiprojects.formatVersion !== WIKIPROJECTS_LIST_VERSION) return null;
// Dołącz do wyniku informację o wieku listy w dniach
var fetchDate = wikiprojects.fetchDate;
var now = Date.now();
var ageInDays = (now - parseInt(fetchDate)) / 86400000;
return {
ageInDays: ageInDays,
wikiprojects: wikiprojects.list
};
}
/**
* Zapisuje listę wikiprojektów do pamięci podręcznej
* @private
* @param {Wikiproject[]} wikiprojects
*/
function saveWikiprojectsToCache(wikiprojects) {
mw.storage.setObject(WIKIPROJECTS_CACHE_KEY, {
fetchDate: Date.now(),
formatVersion: WIKIPROJECTS_LIST_VERSION,
list: wikiprojects
});
}
/**
* Pobiera z serwera aktualną listę projektów i zwraca ją w formie spłaszczonej.
* @private
* @returns {JQuery.Promise<Wikiproject[]>}
*/
function downloadWikiprojects(){
var deferred = $.Deferred();
var url = mw.util.getUrl(WIKIPROJECTS_LIST_PAGE, { action: 'raw', ctype: 'application/json' });
$.getJSON(url)
.done(function (/** @type {Wikiproject[]} */ fetchedData) {
// Przetwórz wynik do prostej, posortowanej listy
fetchedData = flatenList(fetchedData);
sortWikiprojects(fetchedData);
normalizeWikiprojects(fetchedData);
deferred.resolve(fetchedData);
})
.fail(function () {
deferred.reject();
});
return deferred.promise();
}
/**
* Spłaszcza strukturę listy wikiprojektów.
* @private
* @param {Wikiproject[]} wikiprojects Lista wikiprojektów
* @returns {Wikiproject[]}
*/
function flatenList(wikiprojects){
var result = [];
for(var i = 0; i < wikiprojects.length; i++) {
var item = wikiprojects[i];
result.push(item);
if(item.children) {
result = result.concat(flatenList(item.children));
delete item.children;
}
}
return result;
}
/**
* Sortuje listę projektów alfabetycznie.
* Sortowanie odbywa się w miejscu (źródłowa tablica jest zmieniana).
* @private
* @param {Wikiproject[]} wikiprojects
*/
function sortWikiprojects(wikiprojects){
return wikiprojects.sort(function(a, b){
// Projekty nieaktywne na końcu
if(a.active && !b.active) return -1;
if(!a.active && b.active) return 1;
// Projekty specjalne umieść na końcu
if(a.type !== b.type) {
if(a.type === 'special') return 1;
if(b.type === 'special') return -1;
}
// Posortuj alfabetycznie
return a.name.localeCompare(b.name);
});
}
/**
* Uzupełnia opcjonalne pola w opisie wikiprojektu.
* Wejściowa lista jest zmieniana.
* @private
* @param {Wikiproject[]} list Lista wikiprojektów
* @returns {Wikiproject[]} Odwołanie do listy wejściowej
*/
function normalizeWikiprojects(list){
// Zwraca pierwszy spośród argumentów, który nie jest nieokreślony
// Można to zrobić operatorem `||`, ale pojawi się problem z `0` i `false`.
var first = function (a, b) { return (a !== undefined) ? a : b; };
return list.map(function(item){
item.page = first(item.page, 'Wikiprojekt:' + item.name);
item.talk = first(item.talk, 'Dyskusja_wikiprojektu:' + item.name);
item.report_link = first(item.report_link, 'Specjalna:Nowa_sekcja/' + item.talk);
item.gadgets = first(item.gadgets, []);
if(typeof item.portal_name == 'string') {
item.portal_page = first(item.portal_page, 'Portal:' + item.portal_name);
} else if(item.portal_name) {
// portalname jest tablicą
item.portal_page = first(
item.portal_page,
item.portal_name.map(function (n) { return 'Portal:' + n; })
);
}
return item;
});
}
/**
* Filtruje listę wikiprojektów według podanych kryteriów.
* @private
* @param {Wikiproject[]} list Lista wikiprojektów
* @param {typeof DEFAULT_OPTIONS} options Opcje filtrowania
* @returns {Wikiproject[]}
*/
function filterWikiprojects(list, options){
// Zwraca część wspólną dwóch tablic
var intersect = function (a, b) {
return a.filter(function(n) {
return b.indexOf(n) !== -1;
});
};
return list.filter(function(item){
// Gadżet chce tego konkretnego wikiprojektu
if(intersect(item.gadgets, options.includeGadgets).length != 0) return true;
// Postąp zgodnie z opcjami
if(!item.active && options.skipInactive) return false;
if(item.type === 'institutional' && options.skipInstitutional) return false;
if(item.type === 'special' && options.skipSpecial) return false;
// Nie mamy wyrzucić - zostawiamy
return true;
});
}
/**
* Zwraca listę wikiprojektów.
* @param {Partial<typeof DEFAULT_OPTIONS>} userOptions Opcje pobierania i filtrowania listy
* @returns {JQuery.Promise<WikiprojectsResponse>}
*/
function getWikiprojects(userOptions) {
var options = $.extend({}, DEFAULT_OPTIONS, userOptions || {});
var deferred = $.Deferred();
// Przeprowadza filtrowanie i zwraca wynik
var returnList = function (/** @type {WikiprojectsResponse} */ response) {
response.wikiprojects = filterWikiprojects(response.wikiprojects, options);
deferred.resolve(response);
};
var cachedList = restoreWikiprojectsFromCache();
if(cachedList && cachedList.ageInDays < options.maxCacheAge) {
returnList(cachedList);
return deferred.promise();
}
downloadWikiprojects().then(
function (wikiprojects) {
saveWikiprojectsToCache(wikiprojects);
returnList({ ageInDays: 0, wikiprojects: wikiprojects });
}).fail(function () {
if(cachedList) returnList(cachedList);
else deferred.reject();
});
return deferred.promise();
}
window.gadget = window.gadget || {};
window.gadget.getWikiprojects = getWikiprojects;
})();
/**
* Opis pól w źródłowym JSON-ie
* @typedef {{
* name: string,
* page?: string,
* type: 'normal' | 'special' | 'institutional',
* active: boolean | 'cykliczny',
* started: string,
* category?: string,
* category_leader?: boolean,
* talk?: string,
* report_link?: string,
* users?: string,
* join?: string,
* children?: Wikiproject[],
* portal_name?: string | string[],
* portal_page?: string | string[],
* gadgets?: string[],
* }} Wikiproject
*
* @typedef {{
* ageInDays: number,
* wikiprojects: Wikiproject[]
* }} WikiprojectsResponse
*/