MediaWiki:Gadget-lib-wikiprojects.js

Z Wikipedii, wolnej encyklopedii

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
 */