User:Perhelion/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
* 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 = ['architecture', 'art', 'astronomical map', 'biology', 'chemical', 'chemistry', 'circuit', 'coat of arms', 'diagram', 'emblem', 'flag', 'graph', 'icon', 'locator map', 'logo', 'map', 'math', 'military insignia', 'musical notation', 'physics', 'realistic', 'ribbon', 'sign', 'signature', 'simple', 'sport', 'symbol', 'technology', 'text', 'transport map'],
		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>