Jump to content
Toggle menu
  • 6 articles
  • 6 users
  • 196 edits
Adventure Builder 2 Wiki
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
Player role order, colors, and inline fields
Use WikiEditor dialogs for level player image
 
(5 intermediate revisions by the same user not shown)
Line 568: Line 568:
})
})
.catch(function () {});
.catch(function () {});
});
});
$(function () {
var action = mw.config.get('wgAction');
if (action !== 'edit' && action !== 'submit') {
return;
}
if (!mw.hook) {
return;
}
mw.messages.set({
'ab-insert-level-title': 'Insert level',
'ab-insert-player-title': 'Insert player',
'ab-insert-image-title': 'Insert image'
});
var AB_INSERT_LEVEL_FIELDS = [
{ data: '', label: 'Infobox' },
{ data: 'plays', label: 'Plays' },
{ data: 'creator', label: 'Creator' },
{ data: 'title', label: 'Title' },
{ data: 'description', label: 'Description' },
{ data: 'clears', label: 'Clears' },
{ data: 'favorites', label: 'Favorites' },
{ data: 'difficulty', label: 'Difficulty' }
];
var AB_INSERT_PLAYER_FIELDS = [
{ data: '', label: 'Infobox' },
{ data: 'name', label: 'Name' },
{ data: 'role', label: 'Role' },
{ data: 'levels', label: 'Levels' },
{ data: 'clears', label: 'Clears' },
{ data: 'followers', label: 'Followers' }
];
var AB_INSERT_ALIGN = [
{ data: '', label: 'None' },
{ data: 'left', label: 'Left' },
{ data: 'right', label: 'Right' },
{ data: 'center', label: 'Center' },
{ data: 'inline', label: 'Inline' }
];
function abInsertSanitize(value) {
return String(value || '')
.replace(/\|/g, '')
.replace(/\{\{/g, '')
.replace(/\}\}/g, '')
.trim();
}
function abInsertDigits(value) {
return String(value || '')
.replace(/^rbxassetid:\/\//i, '')
.replace(/[^0-9]/g, '');
}
function abInsertIsUrl(value) {
return /^https?:\/\//i.test(String(value || '').trim());
}
function abInsertTemplate(name, id, field) {
var clean = abInsertDigits(id);
if (!clean) {
return '';
}
if (field) {
return '{{' + name + '|' + clean + '|' + field + '}}';
}
return '{{' + name + '|' + clean + '}}';
}
function abInsertImageWikitext(values) {
var source = abInsertSanitize(values.source);
if (!source) {
return '';
}
if (!abInsertIsUrl(source)) {
source = abInsertDigits(source);
if (!source) {
return '';
}
}
var bits = ['{{image', source];
var width = abInsertDigits(values.width);
var height = abInsertDigits(values.height);
if (width && height) {
bits.push(width + 'x' + height + 'px');
} else if (width) {
bits.push(width + 'px');
} else if (height) {
bits.push('x' + height + 'px');
}
if (values.align) {
bits.push(values.align);
}
if (values.caption) {
bits.push(abInsertSanitize(values.caption));
}
if (values.alt) {
bits.push('alt=' + abInsertSanitize(values.alt));
}
if (values.link) {
bits.push('link=' + abInsertSanitize(values.link));
}
return bits[0] + '|' + bits.slice(1).join('|') + '}}';
}
function abEscapeAttr(value) {
return mw.html.escape(String(value || ''));
}
function abOptionsHtml(options) {
var html = '';
var i;
for (i = 0; i < options.length; i++) {
html +=
'<option value="' +
abEscapeAttr(options[i].data) +
'">' +
abEscapeAttr(options[i].label) +
'</option>';
}
return html;
}
function abFieldHtml(id, label, control) {
return (
'<div class="wikieditor-toolbar-field-wrapper">' +
'<label for="' +
abEscapeAttr(id) +
'">' +
abEscapeAttr(label) +
'</label>' +
control +
'</div>'
);
}
function abInputHtml(id, placeholder) {
return (
'<input type="text" id="' +
abEscapeAttr(id) +
'"' +
(placeholder ? ' placeholder="' + abEscapeAttr(placeholder) + '"' : '') +
'/>'
);
}
function abSelectHtml(id, options) {
return (
'<select id="' + abEscapeAttr(id) + '">' + abOptionsHtml(options) + '</select>'
);
}
function abSetInsertEnabled($dialog, enabled) {
var $button = $dialog.closest('.ui-dialog').find('.ui-dialog-buttonpane button').first();
if ($button.length && $button.button) {
$button.button('option', 'disabled', !enabled);
}
}
function abBindDialogEnter($dialog) {
if ($dialog.data('dialogkeypressset')) {
return;
}
$dialog.data('dialogkeypressset', true);
var $ui = $dialog.closest('.ui-dialog');
$ui.on('keypress', function (e) {
if (e.which === 13) {
var dialogAction = $ui.data('dialogaction');
var $button = dialogAction ? $(dialogAction) : $ui.find('button').first();
if ($button && $button.trigger) {
$button.trigger('click');
}
e.preventDefault();
}
});
$ui.find('button').on('focus', function (e) {
$ui.data('dialogaction', e.delegateTarget);
});
}
function abInsertIntoTextarea($dialog, wikitext) {
if (!wikitext) {
return;
}
var context = $dialog.data('context');
$dialog.dialog('close');
if (!context || !context.$textarea) {
return;
}
context.$textarea.textSelection('encapsulateSelection', {
pre: '',
peri: wikitext,
post: '',
replace: true,
selectPeri: false
});
}
function abMakeTemplateDialog(kind) {
var prefix = 'ab-insert-' + kind;
var idField = prefix + '-id';
var fieldField = prefix + '-field';
var templateName = kind === 'player' ? 'player' : 'level';
var fields = kind === 'player' ? AB_INSERT_PLAYER_FIELDS : AB_INSERT_LEVEL_FIELDS;
var idLabel = kind === 'player' ? 'Player ID' : 'Level ID';
function currentWikitext($root) {
return abInsertTemplate(
templateName,
$root.find('#' + idField).val(),
$root.find('#' + fieldField).val()
);
}
return {
title: mw.message(prefix + '-title'),
id: prefix + '-dialog',
html:
'<div class="wikieditor-toolbar-dialog-wrapper"><fieldset>' +
abFieldHtml(idField, idLabel, abInputHtml(idField, '123')) +
abFieldHtml(fieldField, 'Insert as', abSelectHtml(fieldField, fields)) +
'</fieldset></div>',
init: function () {
var $dialog = $(this);
$dialog.find('#' + idField + ', #' + fieldField).on('input change', function () {
abSetInsertEnabled($dialog, !!currentWikitext($dialog));
});
},
dialog: {
resizable: false,
dialogClass: 'wikiEditor-toolbar-dialog ab-insert-dialog',
width: 500,
buttons: {
'wikieditor-toolbar-tool-file-insert': function () {
abInsertIntoTextarea($(this), currentWikitext($(this)));
},
'wikieditor-toolbar-tool-file-cancel': function () {
$(this).dialog('close');
}
},
open: function () {
var $dialog = $(this);
var context = $dialog.data('context');
var selected = '';
if (context && context.$textarea) {
selected = String(context.$textarea.textSelection('getSelection') || '').trim();
}
$dialog.find('#' + idField).val(abInsertDigits(selected) || '');
$dialog.find('#' + fieldField).val('');
abSetInsertEnabled($dialog, !!currentWikitext($dialog));
$dialog.find('#' + idField).trigger('focus');
abBindDialogEnter($dialog);
}
}
};
}
function abMakeImageDialog() {
function currentWikitext($root) {
return abInsertImageWikitext({
source: $root.find('#ab-insert-image-source').val(),
width: $root.find('#ab-insert-image-width').val(),
height: $root.find('#ab-insert-image-height').val(),
align: $root.find('#ab-insert-image-align').val(),
caption: $root.find('#ab-insert-image-caption').val(),
alt: $root.find('#ab-insert-image-alt').val(),
link: $root.find('#ab-insert-image-link').val()
});
}
return {
title: mw.message('ab-insert-image-title'),
id: 'ab-insert-image-dialog',
html:
'<div class="wikieditor-toolbar-dialog-wrapper"><fieldset>' +
abFieldHtml(
'ab-insert-image-source',
'Asset ID or URL',
abInputHtml('ab-insert-image-source', '123 or https://')
) +
'<div class="wikieditor-toolbar-field-wrapper ab-insert-size-row">' +
'<div class="wikieditor-toolbar-floated-field-wrapper">' +
'<label for="ab-insert-image-width">Width (px)</label>' +
abInputHtml('ab-insert-image-width', '300') +
'</div>' +
'<div class="wikieditor-toolbar-floated-field-wrapper">' +
'<label for="ab-insert-image-height">Height (px)</label>' +
abInputHtml('ab-insert-image-height', 'optional') +
'</div>' +
'</div>' +
abFieldHtml(
'ab-insert-image-align',
'Align',
abSelectHtml('ab-insert-image-align', AB_INSERT_ALIGN)
) +
abFieldHtml(
'ab-insert-image-caption',
'Caption',
abInputHtml('ab-insert-image-caption')
) +
abFieldHtml('ab-insert-image-alt', 'Alt text', abInputHtml('ab-insert-image-alt')) +
abFieldHtml('ab-insert-image-link', 'Link', abInputHtml('ab-insert-image-link')) +
'</fieldset></div>',
init: function () {
var $dialog = $(this);
$dialog.find('input, select').on('input change', function () {
abSetInsertEnabled($dialog, !!currentWikitext($dialog));
});
},
dialog: {
resizable: false,
dialogClass: 'wikiEditor-toolbar-dialog ab-insert-dialog',
width: 590,
buttons: {
'wikieditor-toolbar-tool-file-insert': function () {
abInsertIntoTextarea($(this), currentWikitext($(this)));
},
'wikieditor-toolbar-tool-file-cancel': function () {
$(this).dialog('close');
}
},
open: function () {
var $dialog = $(this);
var context = $dialog.data('context');
var selected = '';
if (context && context.$textarea) {
selected = String(context.$textarea.textSelection('getSelection') || '').trim();
}
$dialog.find('input').val('');
$dialog.find('#ab-insert-image-align').val('');
if (selected) {
$dialog.find('#ab-insert-image-source').val(selected);
}
abSetInsertEnabled($dialog, !!currentWikitext($dialog));
$dialog.find('#ab-insert-image-source').trigger('focus');
abBindDialogEnter($dialog);
}
}
};
}
function abRegisterInsertDialogs($textarea) {
var context = $textarea.data('wikiEditor-context');
if (!context || !$.wikiEditor || !$.wikiEditor.modules.dialogs) {
return;
}
$.wikiEditor.modules.dialogs.fn.create(context, {
'ab-insert-level': abMakeTemplateDialog('level'),
'ab-insert-player': abMakeTemplateDialog('player'),
'ab-insert-image': abMakeImageDialog()
});
}
mw.hook('wikiEditor.toolbarReady').add(function ($textarea) {
if (!$textarea || !$textarea.wikiEditor) {
return;
}
abRegisterInsertDialogs($textarea);
$textarea.wikiEditor('removeFromToolbar', {
section: 'main',
group: 'insert',
tool: 'file'
});
mw.loader.using('oojs-ui.styles.icons-user').then(function () {
$textarea.wikiEditor('addToToolbar', {
section: 'main',
group: 'insert',
tools: {
ablevel: {
label: 'Level',
type: 'button',
oouiIcon: 'play',
action: {
type: 'dialog',
module: 'ab-insert-level'
}
},
abplayer: {
label: 'Player',
type: 'button',
oouiIcon: 'userAvatar',
action: {
type: 'dialog',
module: 'ab-insert-player'
}
},
abimage: {
label: 'Image',
type: 'button',
oouiIcon: 'image',
action: {
type: 'dialog',
module: 'ab-insert-image'
}
}
}
});
});
});
});
});
});

Latest revision as of 21:08, 29 August 2026

$(function () {
	if (!mw.config.get('wgIsMainPage')) {
		return;
	}
	function hideMainPageComments() {
		$('#ext-comments-container, .ext-comments-comments-list, .comment-list-toolbar').hide();
	}
	hideMainPageComments();
	$('.home-card-action a').attr({
		target: '_blank',
		rel: 'noopener noreferrer'
	});
	if (mw.hook) {
		mw.hook('wikipage.content').add(hideMainPageComments);
	}
	setTimeout(hideMainPageComments, 400);
});

var AB_DIFFICULTY = { 1: 'Easy', 2: 'Normal', 3: 'Hard', 4: 'INSANE' };

function abSetDifficulty($els, difficulty) {
	if (!$els || !$els.length) {
		return;
	}
	$els.removeClass('ab-difficulty-1 ab-difficulty-2 ab-difficulty-3 ab-difficulty-4');
	var label = AB_DIFFICULTY[difficulty];
	if (!label) {
		$els.text('-').attr('data-difficulty', '0');
		return;
	}
	$els
		.addClass('ab-difficulty ab-difficulty-' + difficulty)
		.attr('data-difficulty', String(difficulty))
		.text(label);
}

function abCompactCount(n) {
	var num = Number(n) || 0;
	if (num >= 1e9) {
		return (num / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
	}
	if (num >= 1e6) {
		return (num / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
	}
	if (num >= 1e3) {
		return (num / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
	}
	return String(Math.floor(num));
}

var AB_ROLE_NAMES = {
	owner: 'Owner',
	moderator: 'Moderator',
	admin: 'Admin',
	scout: 'Scout'
};

function abFormatRole(role) {
	if (role == null) {
		return '';
	}
	role = String(role).trim();
	if (!role) {
		return '';
	}
	var lower = role.toLowerCase();
	if (lower === 'user') {
		return '';
	}
	if (AB_ROLE_NAMES[lower]) {
		return AB_ROLE_NAMES[lower];
	}
	return lower.replace(/[_-]+/g, ' ').replace(/\b\w/g, function (ch) {
		return ch.toUpperCase();
	});
}

function abSetRole($els, role) {
	if (!$els || !$els.length) {
		return;
	}
	$els.removeClass('ab-role-owner ab-role-moderator ab-role-scout');
	var slug = String(role || '')
		.trim()
		.toLowerCase();
	var label = abFormatRole(role);
	if (!label) {
		$els.text('').removeAttr('data-role').removeClass('ab-role');
		return;
	}
	$els.addClass('ab-role');
	if (slug === 'owner' || slug === 'moderator' || slug === 'scout') {
		$els.addClass('ab-role-' + slug);
	}
	$els.attr('data-role', slug).text(label);
}

$(function () {
	var levelId = mw.config.get('abLevelId');
	var $box = $('.ab-level-infobox');
	if (!levelId || !$box.length) {
		return;
	}

	var defaultThumbs = ['82374078680299', '105249369413891', '104825146904235'];

	function thumbAssetId(level) {
		if (!level || level.locked) {
			return defaultThumbs[0];
		}
		var raw = String(level.thumbnail || '').replace(/^rbxassetid:\/\//i, '').replace(/[^0-9]/g, '');
		if (raw && raw !== '0') {
			return raw;
		}
		return defaultThumbs[Math.abs(Number(level.id) || Number(levelId) || 0) % defaultThumbs.length];
	}

	function setThumb(assetId) {
		var $thumb = $box.find('.ab-level-thumb');
		if (!$thumb.length || !assetId) {
			return;
		}
		fetch(
			'https://adventure-builder.net/api/roblox-proxy?service=thumbnails&assetIds=' +
				encodeURIComponent(assetId) +
				'&size=' +
				encodeURIComponent('420x420')
		)
			.then(function (res) {
				return res.ok ? res.json() : null;
			})
			.then(function (data) {
				var item = data && data.data && data.data[0];
				if (!item || item.state !== 'Completed' || !item.imageUrl) {
					return;
				}
				$thumb.attr('src', item.imageUrl).removeClass('ab-level-thumb--placeholder');
			})
			.catch(function () {});
	}

	function atUsername(name) {
		name = String(name || '')
			.replace(/^@+/, '')
			.trim();
		return name ? '@' + name : '';
	}

	function setCreator(level) {
		var $el = $box.find('.ab-level-creator');
		if (!$el.length || !level) {
			return;
		}
		var creatorId = String(level.creator || '').replace(/[^0-9]/g, '');

		function apply(name) {
			var label = atUsername(name);
			if (!label) {
				return;
			}
			var $link = $el.find('a');
			if ($link.length) {
				$link.text(label);
				return;
			}
			if (!creatorId) {
				$el.text(label);
				return;
			}
			$('<a>')
				.attr('href', 'https://adventure-builder.net/players/' + encodeURIComponent(creatorId))
				.attr('target', '_blank')
				.attr('rel', 'noopener noreferrer')
				.text(label)
				.appendTo($el.empty());
		}

		var seeded = level.creator_name || level.creatorName;
		if (seeded) {
			apply(seeded);
			return;
		}
		if (!creatorId) {
			return;
		}
		fetch(
			'https://adventure-builder.net/api/roblox-proxy?service=username&userId=' +
				encodeURIComponent(creatorId)
		)
			.then(function (res) {
				return res.ok ? res.json() : null;
			})
			.then(function (data) {
				apply(data && (data.name || data.displayName));
			})
			.catch(function () {});
	}

	fetch('https://adventure-builder.net/api/v1/levels/' + encodeURIComponent(levelId))
		.then(function (res) {
			return res.ok ? res.json() : null;
		})
		.then(function (data) {
			var level = data && (data.level || data);
			if (!level || level.locked || !level.id) {
				setThumb(defaultThumbs[0]);
				return;
			}
			if (level.title) {
				$box.find('.pi-title').text(level.title);
			}
			if (level.id) {
				$box.find('.ab-level-stat[data-stat="id"]').text(String(level.id));
			}
			setCreator(level);
			[
				['plays', level.plays],
				['clears', level.clears],
				['favorites', level.favorites]
			].forEach(function (pair) {
				$box.find('.ab-level-stat[data-stat="' + pair[0] + '"]').text(
					abCompactCount(pair[1])
				);
			});
			$box.find('.ab-level-stat[data-stat="featured"]').text(
				level.featured ? 'Yes' : 'No'
			);
			abSetDifficulty($box.find('.ab-level-stat[data-stat="difficulty"]'), level.difficulty);
			setThumb(thumbAssetId(level));
		})
		.catch(function () {
			if ($box.find('.ab-level-thumb--placeholder').length) {
				setThumb(defaultThumbs[Math.abs(Number(levelId) || 0) % defaultThumbs.length]);
			}
		});
});

$(function () {
	var $bits = $('.ab-inline-level');
	if (!$bits.length) {
		return;
	}

	function atUsername(name) {
		name = String(name || '')
			.replace(/^@+/, '')
			.trim();
		return name ? '@' + name : '';
	}

	function applyLevel(id, level) {
		var $els = $('.ab-inline-level[data-level-id="' + id + '"]');
		if (!level || level.locked || !level.id) {
			return;
		}
		$els.filter('[data-stat="plays"]').text(abCompactCount(level.plays));
		$els.filter('[data-stat="clears"]').text(abCompactCount(level.clears));
		$els.filter('[data-stat="favorites"]').text(abCompactCount(level.favorites));
		if (level.title) {
			$els.filter('[data-stat="title"]').text(level.title);
		}
		if (Object.prototype.hasOwnProperty.call(level, 'description')) {
			var desc = String(level.description || '')
				.replace(/\s+/g, ' ')
				.trim();
			$els.filter('[data-stat="description"]').text(desc || '-');
		}
		$els.filter('[data-stat="featured"]').text(level.featured ? 'Yes' : 'No');
		abSetDifficulty($els.filter('[data-stat="difficulty"]'), level.difficulty);
		var $creator = $els.filter('[data-stat="creator"]');
		if ($creator.length) {
			var creatorId = String(level.creator || $creator.attr('data-creator-id') || '').replace(
				/[^0-9]/g,
				''
			);
			function setCreatorName(name) {
				var label = atUsername(name);
				if (!label) {
					return;
				}
				$creator.text(label);
				if (creatorId) {
					$creator.attr(
						'href',
						'https://adventure-builder.net/players/' + encodeURIComponent(creatorId)
					);
				}
			}
			if (level.creator_name || level.creatorName) {
				setCreatorName(level.creator_name || level.creatorName);
				return;
			}
			if (!creatorId) {
				return;
			}
			fetch(
				'https://adventure-builder.net/api/roblox-proxy?service=username&userId=' +
					encodeURIComponent(creatorId)
			)
				.then(function (res) {
					return res.ok ? res.json() : null;
				})
				.then(function (data) {
					setCreatorName(data && (data.name || data.displayName));
				})
				.catch(function () {});
		}
	}

	var ids = [];
	$bits.each(function () {
		var id = String($(this).attr('data-level-id') || '').replace(/[^0-9]/g, '');
		if (id && ids.indexOf(id) === -1) {
			ids.push(id);
		}
	});
	ids.forEach(function (id) {
		fetch('https://adventure-builder.net/api/v1/levels/' + encodeURIComponent(id))
			.then(function (res) {
				return res.ok ? res.json() : null;
			})
			.then(function (data) {
				applyLevel(id, data && (data.level || data));
			})
			.catch(function () {});
	});
});

$(function () {
	var $bits = $('.ab-inline-player');
	if (!$bits.length) {
		return;
	}

	function atUsername(name) {
		name = String(name || '')
			.replace(/^@+/, '')
			.trim();
		return name ? '@' + name : '';
	}

	function applyPlayer(id, profile, name) {
		var $els = $('.ab-inline-player[data-player-id="' + id + '"]');
		if (name) {
			$els.filter('[data-stat="name"]').text(atUsername(name));
		}
		if (!profile) {
			return;
		}
		$els.filter('[data-stat="levels"]').text(abCompactCount(profile.levels_count));
		$els.filter('[data-stat="clears"]').text(abCompactCount(profile.clears));
		$els.filter('[data-stat="followers"]').text(abCompactCount(profile.followers));
		abSetRole($els.filter('[data-stat="role"]'), profile.role);
	}

	var ids = [];
	$bits.each(function () {
		var id = String($(this).attr('data-player-id') || '').replace(/[^0-9]/g, '');
		if (id && ids.indexOf(id) === -1) {
			ids.push(id);
		}
	});
	ids.forEach(function (id) {
		fetch(
			'https://adventure-builder.net/api/roblox-proxy?service=username&userId=' +
				encodeURIComponent(id)
		)
			.then(function (res) {
				return res.ok ? res.json() : null;
			})
			.then(function (data) {
				var name = data && (data.name || data.displayName);
				if (name) {
					applyPlayer(id, null, name);
				}
			})
			.catch(function () {});

		fetch('https://adventure-builder.net/api/v1/profiles/' + encodeURIComponent(id))
			.then(function (res) {
				return res.ok ? res.json() : null;
			})
			.then(function (data) {
				applyPlayer(id, data && (data.profile || data), null);
			})
			.catch(function () {});
	});
});

$(function () {
	var userId = mw.config.get('abRobloxUserId');
	if (!userId) {
		return;
	}

	var $img = $('.profile-avatar-image');
	if ($img.length) {
		var fallback = $img.attr('src');
		$img.on('error', function () {
			$img.attr('src', fallback);
		});
		$img.attr(
			'src',
			'https://adventure-builder.net/api/roblox-proxy?service=profile-picture&userId=' +
				encodeURIComponent(userId)
		);
	}

	var $stats = $('.profile-header-statistics');
	if (!$stats.length) {
		return;
	}

	fetch(
		'https://adventure-builder.net/api/v1/profiles/' + encodeURIComponent(userId)
	)
		.then(function (res) {
			return res.ok ? res.json() : null;
		})
		.then(function (profile) {
			if (!profile) {
				return;
			}
			[
				[profile.levels_count, 'Levels'],
				[profile.clears, 'Clears'],
				[profile.followers, 'Followers']
			].forEach(function (pair) {
				$('<li>')
					.addClass('ab-central-stat')
					.text(abCompactCount(pair[0]) + ' ' + pair[1])
					.appendTo($stats);
			});
		})
		.catch(function () {});
});

$(function () {
	var playerId = mw.config.get('abPlayerId');
	var $box = $('.ab-player-infobox');
	if (!playerId || !$box.length) {
		return;
	}

	function atUsername(name) {
		name = String(name || '')
			.replace(/^@+/, '')
			.trim();
		return name ? '@' + name : '';
	}

	function setPfp() {
		var $thumb = $box.find('.ab-player-thumb');
		if (!$thumb.length) {
			return;
		}
		$thumb
			.on('error', function () {
				$thumb.css('visibility', 'hidden');
			})
			.attr(
				'src',
				'https://adventure-builder.net/api/roblox-proxy?service=profile-picture&userId=' +
					encodeURIComponent(playerId)
			)
			.removeClass('ab-player-thumb--placeholder');
	}

	fetch(
		'https://adventure-builder.net/api/roblox-proxy?service=username&userId=' +
			encodeURIComponent(playerId)
	)
		.then(function (res) {
			return res.ok ? res.json() : null;
		})
		.then(function (data) {
			var label = atUsername(data && (data.name || data.displayName));
			if (label) {
				$box.find('.ab-player-title').text(label);
				$box.find('.ab-player-thumb').attr('alt', label);
			}
		})
		.catch(function () {});

	fetch('https://adventure-builder.net/api/v1/profiles/' + encodeURIComponent(playerId))
		.then(function (res) {
			return res.ok ? res.json() : null;
		})
		.then(function (data) {
			var profile = data && (data.profile || data);
			if (!profile) {
				return;
			}
			[
				['levels', profile.levels_count],
				['clears', profile.clears],
				['followers', profile.followers]
			].forEach(function (pair) {
				$box.find('.ab-level-stat[data-stat="' + pair[0] + '"]').text(
					abCompactCount(pair[1])
				);
			});
			setRole(profile.role);
		})
		.catch(function () {});

	function setRole(role) {
		var label = abFormatRole(role);
		var $stat = $box.find('.ab-level-stat[data-stat="role"]');
		var $row = $stat.closest('.pi-data');
		if (!label) {
			$row.remove();
			return;
		}
		if (!$row.length) {
			$row = $(
				'<div class="pi-item pi-data pi-item-spacing pi-border-color">' +
					'<div class="pi-data-label">Role</div>' +
					'<div class="pi-data-value ab-level-stat" data-stat="role"></div>' +
					'</div>'
			);
			$stat = $row.find('[data-stat="role"]');
		}
		abSetRole($stat, role);
		var $levels = $box.find('.ab-level-stat[data-stat="levels"]').closest('.pi-data');
		if ($levels.length) {
			$levels.before($row);
		} else {
			var $nav = $box.find('.pi-navigation');
			if ($nav.length) {
				$nav.before($row);
			} else {
				$box.append($row);
			}
		}
	}

	setPfp();
});

$(function () {
	var $imgs = $('.roblox-asset img[data-asset-id].roblox-asset-img--pending');
	if (!$imgs.length) {
		return;
	}
	$imgs.each(function () {
		var $img = $(this);
		var assetId = String($img.attr('data-asset-id') || '').replace(/[^0-9]/g, '');
		var size = String($img.attr('data-thumb-size') || '420x420');
		if (!assetId) {
			return;
		}
		fetch(
			'https://adventure-builder.net/api/roblox-proxy?service=thumbnails&assetIds=' +
				encodeURIComponent(assetId) +
				'&size=' +
				encodeURIComponent(size)
		)
			.then(function (res) {
				return res.ok ? res.json() : null;
			})
			.then(function (data) {
				var item = data && data.data && data.data[0];
				if (!item || item.state !== 'Completed' || !item.imageUrl) {
					return;
				}
				$img.attr('src', item.imageUrl).removeClass('roblox-asset-img--pending');
			})
			.catch(function () {});
	});
});

$(function () {
	var action = mw.config.get('wgAction');
	if (action !== 'edit' && action !== 'submit') {
		return;
	}
	if (!mw.hook) {
		return;
	}

	mw.messages.set({
		'ab-insert-level-title': 'Insert level',
		'ab-insert-player-title': 'Insert player',
		'ab-insert-image-title': 'Insert image'
	});

	var AB_INSERT_LEVEL_FIELDS = [
		{ data: '', label: 'Infobox' },
		{ data: 'plays', label: 'Plays' },
		{ data: 'creator', label: 'Creator' },
		{ data: 'title', label: 'Title' },
		{ data: 'description', label: 'Description' },
		{ data: 'clears', label: 'Clears' },
		{ data: 'favorites', label: 'Favorites' },
		{ data: 'difficulty', label: 'Difficulty' }
	];
	var AB_INSERT_PLAYER_FIELDS = [
		{ data: '', label: 'Infobox' },
		{ data: 'name', label: 'Name' },
		{ data: 'role', label: 'Role' },
		{ data: 'levels', label: 'Levels' },
		{ data: 'clears', label: 'Clears' },
		{ data: 'followers', label: 'Followers' }
	];
	var AB_INSERT_ALIGN = [
		{ data: '', label: 'None' },
		{ data: 'left', label: 'Left' },
		{ data: 'right', label: 'Right' },
		{ data: 'center', label: 'Center' },
		{ data: 'inline', label: 'Inline' }
	];

	function abInsertSanitize(value) {
		return String(value || '')
			.replace(/\|/g, '')
			.replace(/\{\{/g, '')
			.replace(/\}\}/g, '')
			.trim();
	}

	function abInsertDigits(value) {
		return String(value || '')
			.replace(/^rbxassetid:\/\//i, '')
			.replace(/[^0-9]/g, '');
	}

	function abInsertIsUrl(value) {
		return /^https?:\/\//i.test(String(value || '').trim());
	}

	function abInsertTemplate(name, id, field) {
		var clean = abInsertDigits(id);
		if (!clean) {
			return '';
		}
		if (field) {
			return '{{' + name + '|' + clean + '|' + field + '}}';
		}
		return '{{' + name + '|' + clean + '}}';
	}

	function abInsertImageWikitext(values) {
		var source = abInsertSanitize(values.source);
		if (!source) {
			return '';
		}
		if (!abInsertIsUrl(source)) {
			source = abInsertDigits(source);
			if (!source) {
				return '';
			}
		}
		var bits = ['{{image', source];
		var width = abInsertDigits(values.width);
		var height = abInsertDigits(values.height);
		if (width && height) {
			bits.push(width + 'x' + height + 'px');
		} else if (width) {
			bits.push(width + 'px');
		} else if (height) {
			bits.push('x' + height + 'px');
		}
		if (values.align) {
			bits.push(values.align);
		}
		if (values.caption) {
			bits.push(abInsertSanitize(values.caption));
		}
		if (values.alt) {
			bits.push('alt=' + abInsertSanitize(values.alt));
		}
		if (values.link) {
			bits.push('link=' + abInsertSanitize(values.link));
		}
		return bits[0] + '|' + bits.slice(1).join('|') + '}}';
	}

	function abEscapeAttr(value) {
		return mw.html.escape(String(value || ''));
	}

	function abOptionsHtml(options) {
		var html = '';
		var i;
		for (i = 0; i < options.length; i++) {
			html +=
				'<option value="' +
				abEscapeAttr(options[i].data) +
				'">' +
				abEscapeAttr(options[i].label) +
				'</option>';
		}
		return html;
	}

	function abFieldHtml(id, label, control) {
		return (
			'<div class="wikieditor-toolbar-field-wrapper">' +
			'<label for="' +
			abEscapeAttr(id) +
			'">' +
			abEscapeAttr(label) +
			'</label>' +
			control +
			'</div>'
		);
	}

	function abInputHtml(id, placeholder) {
		return (
			'<input type="text" id="' +
			abEscapeAttr(id) +
			'"' +
			(placeholder ? ' placeholder="' + abEscapeAttr(placeholder) + '"' : '') +
			'/>'
		);
	}

	function abSelectHtml(id, options) {
		return (
			'<select id="' + abEscapeAttr(id) + '">' + abOptionsHtml(options) + '</select>'
		);
	}

	function abSetInsertEnabled($dialog, enabled) {
		var $button = $dialog.closest('.ui-dialog').find('.ui-dialog-buttonpane button').first();
		if ($button.length && $button.button) {
			$button.button('option', 'disabled', !enabled);
		}
	}

	function abBindDialogEnter($dialog) {
		if ($dialog.data('dialogkeypressset')) {
			return;
		}
		$dialog.data('dialogkeypressset', true);
		var $ui = $dialog.closest('.ui-dialog');
		$ui.on('keypress', function (e) {
			if (e.which === 13) {
				var dialogAction = $ui.data('dialogaction');
				var $button = dialogAction ? $(dialogAction) : $ui.find('button').first();
				if ($button && $button.trigger) {
					$button.trigger('click');
				}
				e.preventDefault();
			}
		});
		$ui.find('button').on('focus', function (e) {
			$ui.data('dialogaction', e.delegateTarget);
		});
	}

	function abInsertIntoTextarea($dialog, wikitext) {
		if (!wikitext) {
			return;
		}
		var context = $dialog.data('context');
		$dialog.dialog('close');
		if (!context || !context.$textarea) {
			return;
		}
		context.$textarea.textSelection('encapsulateSelection', {
			pre: '',
			peri: wikitext,
			post: '',
			replace: true,
			selectPeri: false
		});
	}

	function abMakeTemplateDialog(kind) {
		var prefix = 'ab-insert-' + kind;
		var idField = prefix + '-id';
		var fieldField = prefix + '-field';
		var templateName = kind === 'player' ? 'player' : 'level';
		var fields = kind === 'player' ? AB_INSERT_PLAYER_FIELDS : AB_INSERT_LEVEL_FIELDS;
		var idLabel = kind === 'player' ? 'Player ID' : 'Level ID';

		function currentWikitext($root) {
			return abInsertTemplate(
				templateName,
				$root.find('#' + idField).val(),
				$root.find('#' + fieldField).val()
			);
		}

		return {
			title: mw.message(prefix + '-title'),
			id: prefix + '-dialog',
			html:
				'<div class="wikieditor-toolbar-dialog-wrapper"><fieldset>' +
				abFieldHtml(idField, idLabel, abInputHtml(idField, '123')) +
				abFieldHtml(fieldField, 'Insert as', abSelectHtml(fieldField, fields)) +
				'</fieldset></div>',
			init: function () {
				var $dialog = $(this);
				$dialog.find('#' + idField + ', #' + fieldField).on('input change', function () {
					abSetInsertEnabled($dialog, !!currentWikitext($dialog));
				});
			},
			dialog: {
				resizable: false,
				dialogClass: 'wikiEditor-toolbar-dialog ab-insert-dialog',
				width: 500,
				buttons: {
					'wikieditor-toolbar-tool-file-insert': function () {
						abInsertIntoTextarea($(this), currentWikitext($(this)));
					},
					'wikieditor-toolbar-tool-file-cancel': function () {
						$(this).dialog('close');
					}
				},
				open: function () {
					var $dialog = $(this);
					var context = $dialog.data('context');
					var selected = '';
					if (context && context.$textarea) {
						selected = String(context.$textarea.textSelection('getSelection') || '').trim();
					}
					$dialog.find('#' + idField).val(abInsertDigits(selected) || '');
					$dialog.find('#' + fieldField).val('');
					abSetInsertEnabled($dialog, !!currentWikitext($dialog));
					$dialog.find('#' + idField).trigger('focus');
					abBindDialogEnter($dialog);
				}
			}
		};
	}

	function abMakeImageDialog() {
		function currentWikitext($root) {
			return abInsertImageWikitext({
				source: $root.find('#ab-insert-image-source').val(),
				width: $root.find('#ab-insert-image-width').val(),
				height: $root.find('#ab-insert-image-height').val(),
				align: $root.find('#ab-insert-image-align').val(),
				caption: $root.find('#ab-insert-image-caption').val(),
				alt: $root.find('#ab-insert-image-alt').val(),
				link: $root.find('#ab-insert-image-link').val()
			});
		}

		return {
			title: mw.message('ab-insert-image-title'),
			id: 'ab-insert-image-dialog',
			html:
				'<div class="wikieditor-toolbar-dialog-wrapper"><fieldset>' +
				abFieldHtml(
					'ab-insert-image-source',
					'Asset ID or URL',
					abInputHtml('ab-insert-image-source', '123 or https://')
				) +
				'<div class="wikieditor-toolbar-field-wrapper ab-insert-size-row">' +
				'<div class="wikieditor-toolbar-floated-field-wrapper">' +
				'<label for="ab-insert-image-width">Width (px)</label>' +
				abInputHtml('ab-insert-image-width', '300') +
				'</div>' +
				'<div class="wikieditor-toolbar-floated-field-wrapper">' +
				'<label for="ab-insert-image-height">Height (px)</label>' +
				abInputHtml('ab-insert-image-height', 'optional') +
				'</div>' +
				'</div>' +
				abFieldHtml(
					'ab-insert-image-align',
					'Align',
					abSelectHtml('ab-insert-image-align', AB_INSERT_ALIGN)
				) +
				abFieldHtml(
					'ab-insert-image-caption',
					'Caption',
					abInputHtml('ab-insert-image-caption')
				) +
				abFieldHtml('ab-insert-image-alt', 'Alt text', abInputHtml('ab-insert-image-alt')) +
				abFieldHtml('ab-insert-image-link', 'Link', abInputHtml('ab-insert-image-link')) +
				'</fieldset></div>',
			init: function () {
				var $dialog = $(this);
				$dialog.find('input, select').on('input change', function () {
					abSetInsertEnabled($dialog, !!currentWikitext($dialog));
				});
			},
			dialog: {
				resizable: false,
				dialogClass: 'wikiEditor-toolbar-dialog ab-insert-dialog',
				width: 590,
				buttons: {
					'wikieditor-toolbar-tool-file-insert': function () {
						abInsertIntoTextarea($(this), currentWikitext($(this)));
					},
					'wikieditor-toolbar-tool-file-cancel': function () {
						$(this).dialog('close');
					}
				},
				open: function () {
					var $dialog = $(this);
					var context = $dialog.data('context');
					var selected = '';
					if (context && context.$textarea) {
						selected = String(context.$textarea.textSelection('getSelection') || '').trim();
					}
					$dialog.find('input').val('');
					$dialog.find('#ab-insert-image-align').val('');
					if (selected) {
						$dialog.find('#ab-insert-image-source').val(selected);
					}
					abSetInsertEnabled($dialog, !!currentWikitext($dialog));
					$dialog.find('#ab-insert-image-source').trigger('focus');
					abBindDialogEnter($dialog);
				}
			}
		};
	}

	function abRegisterInsertDialogs($textarea) {
		var context = $textarea.data('wikiEditor-context');
		if (!context || !$.wikiEditor || !$.wikiEditor.modules.dialogs) {
			return;
		}
		$.wikiEditor.modules.dialogs.fn.create(context, {
			'ab-insert-level': abMakeTemplateDialog('level'),
			'ab-insert-player': abMakeTemplateDialog('player'),
			'ab-insert-image': abMakeImageDialog()
		});
	}

	mw.hook('wikiEditor.toolbarReady').add(function ($textarea) {
		if (!$textarea || !$textarea.wikiEditor) {
			return;
		}
		abRegisterInsertDialogs($textarea);
		$textarea.wikiEditor('removeFromToolbar', {
			section: 'main',
			group: 'insert',
			tool: 'file'
		});
		mw.loader.using('oojs-ui.styles.icons-user').then(function () {
			$textarea.wikiEditor('addToToolbar', {
				section: 'main',
				group: 'insert',
				tools: {
					ablevel: {
						label: 'Level',
						type: 'button',
						oouiIcon: 'play',
						action: {
							type: 'dialog',
							module: 'ab-insert-level'
						}
					},
					abplayer: {
						label: 'Player',
						type: 'button',
						oouiIcon: 'userAvatar',
						action: {
							type: 'dialog',
							module: 'ab-insert-player'
						}
					},
					abimage: {
						label: 'Image',
						type: 'button',
						oouiIcon: 'image',
						action: {
							type: 'dialog',
							module: 'ab-insert-image'
						}
					}
				}
			});
		});
	});
});