User:Smasongarrison/fixconverttosvg.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/**
* SORT CONVERT-TO-SVG TAGS on all sub-cats of [[:Category:Images that should use vector graphics]]
* @created 2006-02-21
* @source [[c:Template:Convert to SVG]]
* @revision 21:34, 15 October 2019 (UTC)
* @author [[User:Ilmari Karonen]], 2006, 2010
* @author [[c:User:Perhelion]], 2010, 2017-2019
* @author [[c:User:Smasongarrison]], 2022
* Commons: [[Category:User scripts|fixconverttosvg]]
* @license GPL v.3
* @ToDo:
** Redirect solving https://commons.wikimedia.org/w/index.php?title=File:Jawa_Cakra.png&action=history
** type simple support as 2nd parameter
*/
// <nowiki>
/* eslint no-var:"error", one-var:"error"*/
/* eslint-env es6*/
/* global mw, importScript, convertToSVGTypes, HotCat */

(function () {
'use strict';

const st = '{Convert to SVG|', // template name
	vt = 'Vector version available',
	ns = mw.config.get('wgNamespaceNumber'),
	isEdit = !mw.config.get('wgIsArticle'),
	ti = mw.config.get('wgTitle'),
	sts = ' set tag ',
	SLink = '([[User:Perhelion/fixconverttosvg.js|Script]])',
	catRegx = /([\S ]*) images that should use vector graphics/;
let cleanupContents = [], // list of file objects
	cleanupReady = -1,
	token;
if (window.HotCat)
	HotCat.blacklist = HotCat.blacklist ? new RegExp(HotCat.blacklist + '|' + catRegx) : catRegx;

function toSVGname(s) { // try to made valid SVG filename extension
	s = s || ti;
	s = s.replace(/^[Ff]ile:/, '');
	if (!/\.SVG$/.test(s))
		s = s.replace(/\.\D{3}\D?$/, '.svg');
	return s;
}

function simplifyVVA(s) {
	s = (s.replace(/\.SVG$/i, '') !== ti.replace(/\.\D{3}\D?$/, '')) ? '|' + s : '';
	if (!s) mw.notify('VVA name is equal and can be omitted.', { title: 'Simplified!' });
	return s;
}

/**
* Replace old existing redundant tags
*
* @param      {string}    txt       The text
* @param      {string}    type      The type
* @param      {Object}    textarea  The DOM textarea
* @return     {Array}               [tuple] (text, type)
*/
function replaceTag(txt, type, textarea) {
	const reSVG = /\{\{\s?(?:Convert[_ ]?to|to|Should[_ ]?Be)?[_ ]?(SVG|Vectorize|In SVG konvertieren)[^}]*\}\}\s*/i,
		reVVA = /\{\{\s?(((Superseded|Converted to )SVG)|VVA|(((Vector|SVG)[_ ]?(version)?[_ ]?)available))[^}]*\}\}\s*/i;
	let para = '', // parameter
		laMa = '',
		laPo = -1,
		vva = '',
		iO;
	while (reSVG.test(txt)) { // remove SVG, save parameter
		laMa = RegExp.lastMatch;
		laPo = txt.indexOf(laMa); // TEST
		laPo = (laPo === -1) ? reSVG.lastIndex - laMa.length : laPo;
		txt = txt.replace(laMa, '\n');
		// console.log("'%s'", laMa, laPo, txt.slice(0, laPo))
		if (/\|+([^|}]+)\s*\}\}\s*$/g.test(laMa)) // lastMatch

			para = RegExp.$1;

	}
	while (reVVA.test(txt)) { // remove VVA, save parameter
		laMa = RegExp.lastMatch;
		txt = txt.replace(laMa, '\n');
		if (/\|\s?([^|}]+\.svg)/i.test(laMa)) // lastMatch

			vva = RegExp.$1;

	}
	// Remove Cat from template
	txt = txt.replace(new RegExp('\\[\\[[Cc]ategory:' + catRegx.source + '(\\|[^\n]]+)?\\]\\] *\\n*', 'g'), '');

	if (vva || (para && /\.SVG$/i.test(para) && !/vectordata=/.test(para))) { // check wrong using of VVA template and correct it
		para = vva || para;
		para = toSVGname(para);
		para = simplifyVVA(para);
		type = '{{' + vt + para + '}}\n';
		return [type + txt, type];
	}

	if (type) { // Put SVG template
		if (type === '-') {
			type = st + ' removed-';
		} else {
			iO = /vectordata=/g.test(para);
			if (type === 'vectordata=')
				type = iO ? /\|([^|}]+\s*\|[^|}]+)/.exec(laMa)[1] : para + '|' + type;
			else if (iO)
				type += '|' + para;

			para = '{' + st + type + '}}\n';

			if (laPo < 0) {
				if (textarea)
					laPo = $(textarea).textSelection('getCaretPosition');

				// console.log("End '%s'", laMa, laPo)
				laPo = (laPo < 0) ? 0 : laPo;
			}

			laMa = txt.slice(laPo);
			// If in template remove extra line
			if (/^\n}}/.test(laMa))	laMa = laMa.slice(1);
			txt = txt.slice(0, laPo) + para + laMa;
			type = para;
		}
	}
	// cleanup
	txt = txt.replace(/\{\{ ?Bad ?JPE?G\}\}\s*/gi, '');

	return [txt, type];
}

function callbackTimer(cb) {
	setTimeout(cb, 500); // I'm feeling lucky!
}

/**
* Create VVA template string and link SVG file name
*
* @param      {string}  s      Existing VVA param
* @return     {Array}          [tuple] string, string for summary
*/
function makeVVA(s) {
	s = toSVGname(s);
	s = simplifyVVA(s);
	const VVA = '{{' + vt + s + '}}\n';
	s = s ? VVA.replace(s.replace(/^\|/, ''), '[[File:$&]]') : VVA;
	return [VVA, s];
}

/**
* Add VVA template with param (SVG file name)
*
* @param      {Array}     txt   [tuple] string, type
* @param      {string}    s     Existing VVA param
* @return     {Array}           [tuple] string, string
*/
function doVVA(txt, s) {
	const p = txt[1] || ''; // type (old VVA para)
	let VVA = makeVVA(s);
	txt = txt[0];
	s = VVA[1];
	VVA = VVA[0];

	txt = p ? txt.replace(p, VVA) : VVA + txt;
	return [txt, s];
}

function apiFail(code, r) {
	let warn;
	if (!code.indexOf('http'))
		warn = 'HTTP error: ' + r.textStatus;
	else if (code === 'ok-but-empty')
		warn = 'Got an empty response from the server';
	else
		warn = 'API error: ' + code;

	mw.log.warn(warn);

	mw.notify('There was an error processing your request.\n\t\t\t:' +
warn + '\n\n\t\t\t\tPlease try again.', {
		title: 'Error!',
		autoHide: 0,
		type: 'error'
	});
}

function savePage(content, file, summary) {
	new mw.Api().post({
		action: 'edit',
		summary: summary,
		watchlist: 'nochange',
		title: file,
		token: token,
		text: content,
		minor: 1
	}).done(function () {
		mw.notify('SVG parameter successfully inserted on ' + file, { title: 'Done!' });
	}).fail(apiFail);
}

function doCleanupHook(cContent) {
	if (typeof cContent === 'object') {
		const file = cContent.file;
		// console.log(cContent.text, file);
		cleanupReady--;
		if (cleanupReady > 0 && cleanupContents.length) {
			callbackTimer(function () {
				mw.hook('gadget.cleanup.run').fire(cleanupContents.shift());
			});
		}

		savePage(cContent.text, file, cContent.sum);
		// If direct on filepage
		if (mw.config.get('wgPageName') === file.replace(/ /g, '_')) {
			callbackTimer(function () {
				location.reload();
			});
		}
	}
}

function loadCleanupScript(cb) {
	// Cleanup script loaded?
	if (cleanupReady === -1) {
		mw.hook('gadget.cleanup.done').add(doCleanupHook);
		cleanupReady = 0;
	}
	if (!mw.libs.fastCleanup) {
		mw.hook('gadget.cleanup.loaded').add(cb);
		importScript('User:Perhelion/cleanup.js');
	} else {
		cb();
	}
}

function doPageContent(txt, file, type, SVG) {
	txt = replaceTag(txt, type);
	if (!type && SVG)
		txt = doVVA(txt, SVG);

	type = txt[1];
	txt = txt[0];
	txt = {
		file: file, text: txt, sum: SLink + sts + type
	};
	cleanupContents.push(txt);
	loadCleanupScript(function () {
		if (!cleanupReady) {
			mw.hook('gadget.cleanup.run').fire(cleanupContents.shift());
			cleanupReady++;
		}
	});
}

/**
* Ajax Api to open file
*
* @param     {string}  file    The file
* @param     {string}  t       The type
* @param     {string}  SVG     The SVG file param
* @return    {(Function|void)} doPageContent callback
*/
function getPage(file, t, SVG) {
	new mw.Api().get({
		prop: 'revisions',
		meta: 'tokens',
		rvprop: 'content',
		titles: file
	}).done(function (r) {
		r = r.query;
		let pages = r.pages,
			id;
		const tokens = r.tokens;
		mw.log('Processing… got page contents from ' + file);
		mw.notify('Got page contents from ' + file, { title: 'Processing…' });
		if ('csrftoken' in tokens)
			token = tokens.csrftoken;

		for (id in pages) {
			if (id in pages) {
				pages = pages[id];
				r = pages.revisions[0]['*'];
				return doPageContent(r, pages.title, t, SVG);
			}
		}
	}).fail(apiFail);
}

function toSummary(tag) { // In edit mode
	const summary = document.editform.wpSummary;
	let sum = summary.value,
		dblSum = sum.indexOf(SLink);
	tag = SLink + sts + tag;
	// don't add double cmt
	if (!dblSum)
		sum = sum.slice(sum.indexOf('}}') + 2);
	else
		sum = (dblSum === -1) ? sum : sum.slice(0, dblSum);

	summary.value = sum.trim() + ' ' + tag;
}

function toSVGpage(e) { // In edit mode
	let type = e,
		textarea,
		txt;
	const editform = document.editform;
	if (!editform)
		return;

	if (typeof e !== 'string') {
		if (e.preventDefault)
			e.preventDefault();

		e = e.target;
		type = $(e).text();
		// if (e.nodeName === "A") return;
	}
	textarea = editform.wpTextbox1;
	txt = replaceTag(textarea.value, type, textarea);

	type = txt[1];
	txt = txt[0];
	toSummary(type);

	$(textarea).val(txt); // textarea.value = txt; not working with CodeMirror :-o
	editform.wpMinoredit.checked = true;
	if (!$('#ca-unwatch').length) // don't change watch status

		editform.wpWatchthis.checked = false;

	/* if (e && typeof e === 'string') {
loadCleanupScript(function () {
callbackTimer($('#wpSave').click);
});
}*/
	return false;
}

/**
* Vector version available in edit mode
*
* @param   {Object}		$textarea	Wikieditor Object | $textarea element
* @param   {string}		s			Existing VVA param
* @return  {string}		txt			To input textarea
* @return  {string}		s			To input summary
*/
function toVVApage($textarea, s) {
	let txt = '',
		c;
	if (!s)
		$textarea = $textarea.$textarea;

	$textarea = $textarea || $('#wpTextbox1');
	s = s || $textarea.textSelection('getSelection');
	c = $textarea.textSelection('getCaretPosition') || 0;
	txt = $textarea.val();
	txt = replaceTag(txt.replace(s, ''), $textarea[0]); // Remove templates

	if (s) {
		txt = doVVA(txt, s);
		s = txt[1];
		txt = txt[0];
	} else {
		txt = txt[0];
		s = makeVVA();
		if (!txt[1]) { // No type?
			txt = txt.slice(0, c) + s[0] + txt.slice(c);
		}
		s = s[1];
	}

	$textarea.val(txt);
	toSummary(s);
}

// Create array list
function makeTypes() {
	let types = ['simple','text'],
		typec = '',
		t;

	if (window.convertToSVGTypes) {
		typec = convertToSVGTypes;
		if (typeof typec === 'string')
			typec = typec.split(/,\s*/);
		else if (Array.isArray(typec))
			types = [];
			// reset default
	}

	// Append default values
	for (t = 0; t < typec.length; t++) {
		if (types.indexOf(typec[t]) === -1)
			types.push(typec[t]);

	}
	return types.sort().concat('-');
}

function createSection() {
	const types = ['vectordata='].concat(makeTypes()),
		$textarea = $('#wpTextbox1');
	$textarea.wikiEditor('addToToolbar', {
		sections: { ToSVG: {
			label: 'ToSVG',
			type: 'booklet',
			pages: { tosvg: {
				label: '{' + st, // replaced
				layout: 'characters',
				characters: types
			} }
		} },
		section: 'main', // Button Vector version available
		groups: { SVG: { tools: { vva: {
			label: vt,
			type: 'button',
			icon: '//upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gnome-x-office-drawing.svg/22px-Gnome-x-office-drawing.svg.png',
			action: {
				type: 'callback',
				execute: toVVApage
			}
		} } } }
	});
	$('#wikiEditor-ui-toolbar .page.page-tosvg').find('span').off('click').on('click', toSVGpage).css('font-size', '14px'); // HACK (callback API don't work!?) + made smaller
}

function createList(s) {
	let types = makeTypes();
	const frag = document.createDocumentFragment();

	if (s) { // remove current type from sub-cat
		s = s[0].toLowerCase() + s.slice(1); // types need to be start lower case
		types = $(types).not([s]);
	}

	frag.appendChild(document.createElement('br'));

	for (let t = 0, tyLen = types.length; t < tyLen; t++) { // this is faster than jQuery
		const link = document.createElement('a');
		link.text = types[t];
		frag.appendChild(document.createTextNode('['));
		frag.appendChild(link);
		frag.appendChild(document.createTextNode('] '));
	}
	return frag;
}

function doGetType(e) {
	e.preventDefault();
	let a = e.target,
		$a = $(a);
	const t = $a.text();
	if (a.title && a.href)
		return;

	a = $a.parent();
	$a = a.find('a').first();
	$a.attr('target', '_blank');
	// console.log(a, $a.attr("title"), t)
	return [$a.attr('title'), t, a];
}

function OpenSVGfile(e) {
	// if (e.stopPropagation) e.stopPropagation();
	if (typeof e !== 'string') {
		const type = doGetType(e); // [file , type]
		if (type && type[0]) {
			type[2].parents('li.gallerybox').remove();
			getPage(type[0], type[1]);
			// e.href = $a.attr("href") + "&fixconverttosvg=" + encodeURI(t);
		}
	}
}

function toSVGcategory() {
	const frag = createList((catRegx.test(ti) ? RegExp.$1 : ''));
	$('#mw-category-media div.gallerytext').children('a').after(function () {
		this.parentNode.onclick = OpenSVGfile;
		return frag.cloneNode(true);
	});
	mw.util.addCSS(
		'.gallerybox,.gallerybox>div{width:280px !important}\n.gallerytext>a{white-space:nowrap}'
	);
}

function doPrompt(text, kind, $element, title, prefill, cb) {
	const $input = $('<input>').attr({
		value: prefill,
		type: 'text',
		id: 'SVGdialog',
		size: 48
	});
	if (!text) { // TODO check file exists
		title = 'INVALID PARAMETER';
		text = 'You must enter a valid name';
	} else {
		title += '}}';
		text = $('<label>').attr({
			'for': kind + 'dialog',
			'class': 'text'
		}).text(text);
	}
	$input.after($element);
	$element = $('<div>').attr({
		type: 'text',
		id: kind + 'dialog'
	}).append($input);
	$('<div>').append([text, $element]).dialog({
		width: 400,
		dialogClass: 'wikiEditor-toolbar-dialog',
		resizable: false,
		modal: true,
		title: title,
		close: function () {
			$(this).dialog('destroy');
			$(this).remove();
		},
		buttons: [{
			text: 'OK',
			click: function (e) {
				$(this).dialog('close');
				e = $input.val();
				if (e) { // Check validity then run callback
					return cb(e);
				}
				doPrompt('', kind, $element, title);
			}
		}
		]
	});
}

function addSVG() {
	let type = $.grep(mw.config.get('wgCategories'), function (i) {
		return catRegx.test(i);
	});
	const prefill = type.length ? type[0].replace(catRegx, '$1') : '';
	type = $('<br>').after($('<div>').append(createList(prefill)).on('click', function (e) {
		e = doGetType(e);
		$('#SVGdialog').val(e[1]);
	}));
	/* 	type = ['<br>', $('<a>').attr({
'href': '#',
'title': 'File:' + ti
})
type]; */
	doPrompt('Choose a SVG category type:',
		'ToSVG',
		type,
		'Parameter insert for {' + st,
		prefill,
		function (res) {
			if (isEdit) toSVGpage(res);
			else getPage('File:' + ti, res);
		});
}

function addVVA() {
	let prefill,
		$textarea;
	if (isEdit && ($textarea = $('#wpTextbox1')).length)
		prefill = $textarea.textSelection('getSelection');

	prefill = prefill ? toSVGname(prefill) : toSVGname();

	doPrompt('Choose one (or more) SVG file(s):',
		'VVA',
		$(),
		'Insert file name for {{' + vt,
		(prefill || '.svg'),
		function (res) {
			if (isEdit) toVVApage($textarea, res);
			else getPage('File:' + ti, '', res);
		});
}

// if (mw.config.get("wgUserGroups").indexOf("autoconfirmed") > -1)
$.when(mw.loader.using(['mediawiki.user', 'mediawiki.util', 'user.options', 'mediawiki.api']), $.ready).then(function () {
	if (ns === 14 && ti.indexOf('mages that should use vector graphics') !== -1) {
		if (!mw.messages.get('schnark-imagepopups-missing'))
			mw.loader.load('https://de.wikipedia.org/w/index.php?title=Benutzer:Schnark/js/imagepopups.js&action=raw&ctype=text/javascript');

		mw.loader.using([], toSVGcategory());
	} else if (ns === 6 && !/SVG$/i.test(ti)) {
		if (isEdit) {
			// Omit double run (in live preview)
			if (!$('#wikiEditor-ui-toolbar span.tab.tab-ToSVG').length && mw.user.options.get('usebetatoolbar'))
			// && mw.user.options.get("showtoolbar")

				mw.loader.using('ext.wikiEditor', createSection);

		}
		mw.loader.using('ext.gadget.editDropdown').always(function () {
			mw.libs.commons.ui.addEditLink('#ToSVG', 'ToSVG +', 'e-edit-tosvg', 'Add {' + st + '}}')
				.addEventListener('click', addSVG);
			mw.libs.commons.ui.addEditLink('#VVA', 'VVA +', 'e-edit-vva', 'Add {{' + vt + '}}')
				.addEventListener('click', addVVA);
		});
	}
});
}());
// EOF </nowiki>