MediaWiki:Functions-list.js

From Wikifunctions

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
(function () {
	'use strict';

	// For now only opens in https://www.wikifunctions.org/wiki/Special:Blankpage/functions-list?withJS=MediaWiki:Functions-list.js
	if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' ||
		mw.config.get('wgPageName').split('/')[1] !== 'functions-list') {
		return;
	}
	
	const api = new mw.Api({ ajax: { headers: { 'Api-User-Agent': 'functions-list/0.1' } } });

	async function* allFunctions() {
		let result = { 'continue': { apcontinue: '' } };
		while (Object.hasOwn(result, 'continue')) {
			result = await api.get({
				action: 'query',
				list: 'allpages',
				apnamespace: 0,
				aplimit: 500,
				apcontinue: result.continue.apcontinue
			});
			yield* result.query.allpages;
		}
	}
	
	async function wikiLambdaMultiFetch(zIds) {
		try {
			return await api.post({ 'action': 'wikilambda_fetch', zids: zIds.join('|') });
		} catch (_) {
			return await api.post({ 'action': 'wikilambda_fetch', zids: zIds.join('|') });
		}
	}
	
	async function wikiLambdaFetch(zId) {
		const result = await api.get({ 'action': 'wikilambda_fetch', zids: zId });
		return JSON.parse(Object.values(result)[0].wikilambda_fetch);
	}

	function init(require) {
		const Vue = require('vue');
        const { CdxButton, CdxAccordion, CdxProgressBar } = require('@wikimedia/codex');

		Vue.createMwApp({
			components: { CdxButton, CdxAccordion, CdxProgressBar },
			data() {
				const functions = Vue.ref([]);
				const fetchAllIsInProgress = Vue.ref(false);
				const fetchAllIsDoneOpenAttribute = Vue.ref('close');
				const longestShortDescription = Vue.ref({});
				const details = Vue.ref({});
				const excludeList = Vue.ref([]);
				const codeStore = Vue.ref({});

				(async function () {
					const list = [];
					for await (let item of allFunctions()) {
						list.push(item.title);
					}
					list.sort((x, y) => +x.slice(1) - +y.slice(1));
					functions.value = list;
				}());

				async function fetchDetails(item) {
					if (Object.hasOwn(details.value, item)) return;
					details.value[item] = await wikiLambdaFetch(item);
				}

				async function fetchAll() {
					fetchAllIsInProgress.value = true;
					const chunkSize = mw.config.get('wgUserGroups').includes('sysop') ? 500 : 50;
					for (let i = 0; i < functions.value.length; i += chunkSize) {
						const result = await wikiLambdaMultiFetch(functions.value.slice(i, i + chunkSize));
						for (const [key, value] of Object.entries(result)) {
							details.value[key] = JSON.parse(value.wikilambda_fetch);
						}
					}
					fetchAllIsInProgress.value = false;
					fetchAllIsDoneOpenAttribute.value = 'open';
				}
				
				function sortByLongestShortDescription() {
					const sizes = Object.fromEntries(functions.value.map(zId => {
						return [zId, Math.max(...[0].concat(
							details.value[zId]?.Z2K5?.Z12K1?.slice(1)
								?.map(x => [...x.Z11K2].length) ?? 0
						))];
					}));
					functions.value = functions.value.toSorted((x, y) => {
						return sizes[y] - sizes[x];
					});
					longestShortDescription.value = sizes;
				}
				
				function showOnlyItemsWithCode() {
					excludeList.value = functions.value
						.filter(x => !Object.hasOwn(details.value[x]?.Z2K2 ?? {}, 'Z14K3'));
					codeStore.value = Object.fromEntries(
						functions.value
							.filter(x => Object.hasOwn(details.value[x]?.Z2K2 ?? {}, 'Z14K3'))
							.map(x => [x, details.value[x].Z2K2.Z14K3.Z16K2])
					);
				}
				
				return {
					functions,
					details,
					fetchDetails,
					fetchAll,
					fetchAllIsInProgress,
					fetchAllIsDoneOpenAttribute,
					longestShortDescription,
					getUrl: mw.util.getUrl,
					sortByLongestShortDescription,
					showOnlyItemsWithCode,
					excludeList,
					codeStore,
				};
			},
			// available components, https://doc.wikimedia.org/codex/latest/components/demos/
			template: `
<cdx-button v-if="functions.length !== Object.keys(details).length && !fetchAllIsInProgress" @click="fetchAll()">Fetch All ({{ functions.length }})</cdx-button>
<cdx-button v-if="functions.length !== 0 && functions.length === Object.keys(details).length" @click="sortByLongestShortDescription()">Sort by longest short description</cdx-button>
<cdx-button v-if="functions.length !== 0 && functions.length === Object.keys(details).length" @click="showOnlyItemsWithCode()">Show only items with code</cdx-button>
<cdx-progress-bar v-if="!functions.length || fetchAllIsInProgress" />
<!--progress v-if="fetchAllIsInProgress" :value="Object.keys(details).length" :max="functions.length" /-->
<div v-for="item in functions" :key="item">
	<details @toggle="fetchDetails(item)" :[fetchAllIsDoneOpenAttribute]="true" v-if="!excludeList.includes(item)">
		<summary style="padding: .25em"><a :href="getUrl(item)" target="_blank">{{ item }}</a></summary>
		<p v-if="longestShortDescription[item] !== undefined">Longest Description Size: {{ longestShortDescription[item] ?? '' }}</p>
		<p v-if="!Object.hasOwn(codeStore, item)">{{ details[item] ?? '' }}</p>
		<p v-if="Object.hasOwn(codeStore, item)" style="white-space: pre; font-family: monospace">{{ codeStore[item] }}</p>
		<cdx-progress-bar inline v-if="details[item] === undefined" />
	</details>
</div>
`,
		}).mount($('<div>').appendTo($('#mw-content-text').empty())[0]);

		document.title = 'Functions List';
		document.querySelector('h1').innerText = 'Functions List';
	}
	
	$(() => mw.loader.using(['vue', '@wikimedia/codex']).then(init));
}());