미디어위키:Gadget-DB2-SpecialPage.js

리버티게임(개발), 모두가 만들어가는 자유로운 게임
둘러보기로 이동 검색으로 이동

참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.

  • 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
  • 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
  • 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
  • 오페라: Ctrl-F5를 입력.
/**
 * DB2로 저장한 키들을 관리하는 페이지 전용 기능들
 * 원 저작자: [[사용자:hsl0|hsl0]]
 * 코드 관리: [[사용자:Senouis|Senouis]]
**/

var metadataQueue = {};
var metaRqTimeout = null;

function requestMetadata() {
	var queue = metadataQueue;
	$.get('/w/api.php', {
		action: "query",
		format: "json",
		prop: "revisions",
		titles: Object.keys(queue).join('|'),
		formatversion: 2,
		rvprop: "content",
		rvslots: "main"
	}).then(function(res) {
		res.query.pages.forEach(function(obj) {
			queue[obj.title](JSON.parse(obj.revisions[0].slots.main.content));
		});
	});
	metadataQueue = {};
	metaRqTimeout = null;
}
function newPage(name, label, prefix) {
	function create(res) {
		var $table = keyList(keys, res.about, res.loc);
		
		var reload = new OO.ui.ButtonInputWidget({
			label: '새로고침',
			icon: 'reload'
		});
		reload.$button.click(function() {
			reload.setDisabled(true);
			reload.setFlags('pending');
			
			Promise.all([hybridStorage.refresh(), loadAbout()]).then(function(res) {
				allkeys = getKeys();
				keys = name === 'global'? allkeys.global : allkeys.local[prefix];
				page.$element.find('table').remove();
				$table = keyList(keys, res[1].about, res[1].loc);
				page.$element.append($table);
				
				reload.setDisabled(false);
				reload.setFlags({pending: false});
			}, notifyApiError.bind(null, '항목을 불러오는 데 실패했습니다.', null));
		});
		
		var remove = new OO.ui.ButtonInputWidget({
			classes: ['removeall'],
			label: '모두 제거',
			icon: 'trash',
			flags: 'destructive'
		});
		remove.$button.click(function() {
			$table.find('.remove button').click();
			if(name === 'global') {
				keys = allkeys.global = {};
				page.$element.find('table').remove();
				$table = keyList(keys);
				page.$element.append($table);
			} else {
				delete allkeys.local[prefix];
				layout.removePages([page]);
			}
		});
		/*
		remove.$button.click(function() {
			OO.ui.confirm('정말로 ' + (name === 'global'? '모든 전역키' : '"' + label + '"의 모든 키') + '를 제거하시겠습니까?').then(function(response) {
				var process = null;
				var Processfunc = function(){
					function resolvedProcess(check, length) {
						if (check === length) process.resolved();
					}
					var check = 0;
					process = $.Deferred();
					for(var key in keys) {
						check++;
						hybridStorage.removeItem(keys[key]).then(resolvedProcess(check,keys.length),notifyApiError.bind(null, '항목을 제거하지 못했습니다.', null));
					}
					return process.promise();
				};
				var postProcessfunc = function() {
					if(name === 'global') {
						keys = allkeys.global = {};
						page.$element.find('table').remove();
						$table = keyList(keys);
						page.$element.append($table);
					} else {
						delete allkeys.local[prefix];
						layout.removePages([page]);
					}
				};
				if(response) {
					remove.setDisabled(true);
					remove.setFlags('pending');
					
					Processfunc().done(function (){
						postProcessfunc();
						remove.setDisabled(false);
						remove.setFlags({pending: false});	
					});
				}
			});
		});
		*/
		var revert = new OO.ui.ButtonInputWidget({
			classes: ['revertall'],
			label: '모두 복원',
			icon: 'undo',
			flags: 'progressive'
		});
		revert.$button.click(function() {
			$table.find('.removed .revert button').click();
		});
		
		
		page.$element.empty().append([
			$('<h3 />').html(name === 'global'? '전역키' : $('<a />', {href: mw.util.getUrl(label), target: '_blank'}).text(label)),
			$('<div />', {
				class: 'buttons'
			}).append(new OO.ui.ButtonGroupWidget({
				items: [reload],
				classes: ['buttons-left']
			}).$element).append(new OO.ui.ButtonGroupWidget({
				items: [revert, remove],
				classes: ['buttons-right']
			}).$element),
			$table
		]);
	}
	
	function loadAbout() {
		if(name === 'global') return $.get('/w/index.php?title=틀:DB2/전역키&action=render').then(function(res) {
			function process(key) {
				var $row = $(res).find('tr:has(td:nth-child(1):contains(' + JSON.stringify(key) +'))');
				about[key] = $row.find('td')[1].innerText;
				loc[key] = $row.find('td:nth-child(4)');
				loc[key].find(':not(a)').contents().unwrap();
				loc[key].find('a').filter(function() {
					var href = new URL(this.href, location);
					href.search = '';
					this.href = href;
					this.target = '_blank';
					return href.origin !== location.origin || !href.pathname.startsWith('/w');
				}).contents().unwrap();
				loc[key] = loc[key].html();
			}
			
			var about = {};
			var loc = {};
			
			if(DEFAULT_KEY in keys) process(DEFAULT_KEY);
			for(var key in keys) process(key);
			
			return {
				about: about,
				loc: loc
			};
		});
		else return new Promise(function(resolve) {
			if(metaRqTimeout) clearTimeout(metaRqTimeout);
			metadataQueue[prefix + '/game.json'] = function(res) {
				var about = {};
				if(res.gameDB.default) about[DEFAULT_KEY] = res.gameDB.default.description;
				
				if(res.gameDB.keys) for(var key in res.gameDB.keys) {
					about[key] = res.gameDB.keys[key].description;
				}
				
				resolve({about: about});
			};
			metaRqTimeout = setTimeout(requestMetadata, 100);
		});
	}
	loadAbout().then(create);
	
	var keys = name === 'global'? allkeys.global : allkeys.local[prefix];
	
	var page = new OO.ui.PageLayout(name, {
		content: [new OO.ui.ProgressBarWidget()]
	});
	page.setupOutlineItem = function() {
		this.outlineItem.setLabel(label);
	};
	
	return page;
}
function encode(key) {
	return encodeURIComponent(key)
		.replace(/\./g, '%2E')
		.replace(/!/g, '%21')
		.replace(/~/g, '%7E')
		.replace(/\*/g, '%2A')
		.replace(/'/g, '%27')
		.replace(/\(/g, '%28')
		.replace(/\)/g, '%29')
		.replace(/_/g, '__')
		.replace(/%/g, '_');
}
function decode(key) {
	return decodeURIComponent(key.replace(/_(?=[a-zA-Z0-9]{2})/g, '%').replace(/__/g, '_'));
}
function keyList(keys, abouts, locs) {
	function keyRow(key) {
		var origin = keys[key];
		var val = hybridStorage.getItem(origin);
		var about = abouts && abouts[key];
		var loc = locs && locs[key];
		
		if(val === null) return;
		
		var copy = new OO.ui.ButtonWidget({
			classes: ['copy'],
			label: '복사'
		});
		copy.$button.click(function() {
			navigator.clipboard.writeText(val);
		});
		
		var remove = new OO.ui.ButtonInputWidget({
			classes: ['remove'],
			label: '제거',
			flags: 'destructive'
		});
		remove.$button.click(function() {
			remove.setDisabled(true);
			remove.setFlags('pending');
			
			hybridStorage.removeItem(origin).then(function() {
				$element.addClass('removed');
				$options.data('sort-value', 0);
				
				remove.$element.addClass('hidden');
				revert.$element.removeClass('hidden');
				revert.setDisabled(false);
				remove.setFlags({pending: false});
			}, notifyApiError.bind(null, '항목을 제거하지 못했습니다.', null));
		});
		var revert = new OO.ui.ButtonInputWidget({
			classes: ['revert', 'hidden'],
			label: '복원',
			flags: 'progressive',
			disabled: true
		});
		revert.$button.click(function() {
			revert.setDisabled(true);
			revert.setFlags('pending');
			
			hybridStorage.setItem(origin, val).then(function() {
				keys[key] = origin;
				
				$element.removeClass('removed');
				
				revert.$element.addClass('hidden');
				remove.$element.removeClass('hidden');
				remove.setDisabled(false);
				revert.setFlags({pending: false});
			}, notifyApiError.bind(null, '항목을 복원하지 못했습니다.', null));
		});
		
		var $options = $('<td />', {'data-sort-value': 1}).append(copy.$element).append(remove.$element).append(revert.$element);
		
		var $element = $('<tr />', {id: 'key-' + origin})
			.append($('<td />').append(key === DEFAULT_KEY? $('<span />', {class: 'key key-default'}).text('기본키') : $('<span />', {class: 'key'}).text(key)))
			.append($('<td />', {class: 'about'}).text(about))
			.append($('<td />').html($('<pre />', {class: 'content'}).text(val)))
			.append(loc && $('<td />', {class: 'location'}).html(loc))
			.append($options);
		
		return $element;
	}
	
	var rows = [];
	
	if(DEFAULT_KEY in keys) rows.push(keyRow(DEFAULT_KEY));
	
	for(var key in keys) rows.push(keyRow(key));
	
	if(!rows.length) rows.push($('<tr />').append($('<td />', {
		colspan: locs? 4 : 5,
		class: 'key-empty'
	}).text('(키 없음)')));
	
	return $('<table class="wikitable sortable" width="100%" style="left:auto;"/>')
		.append('<thead><tr><th>키</th><th>설명</th><th>값</th>' + (locs && '<th>사용처</th>') + '<th>옵션</th></tr></thead>')
		.append($('<tbody />').append(rows))//.tablesorter();
}
var fromKey, toKey;
if(mw.user.isAnon()) {
	fromKey = function(key) {
		return key.slice(7);
	};
	toKey = function(key) {
		return 'gamedb-' + key;
	};
} else {
	fromKey = function(key) {
		return decode(key.slice(7));
	};
	toKey = function(key) {
		return 'gamedb-' + encode(key);
	};
}

var layout = new OO.ui.BookletLayout({
	outlined: true
});

var DEFAULT_KEY = Symbol('defaultkey');

var allkey, scopes;

function getKeys() {
	allkey = Object.getOwnPropertyNames(hybridStorage).filter(function(key) {
		return key.startsWith('gamedb-');
	}).map(fromKey);
	
	var keys = {global:{}, local:{}};
	
	scopes = new Set();
	
	allkey.forEach(function(key) {
		key = key.split('/')[0];
		if(!key.startsWith('#') && key.includes(':')) scopes.add(key);
	});
	
	// global
	allkey.filter(function(key) {
		return key.startsWith('#');
	}).forEach(function(key) {
		keys.global[key.slice(1)] = toKey(key);
	});
	
	// local
	scopes.forEach(function(scope) {
		var current = {};
		
		allkey.filter(function(key) {
			return key.startsWith(scope);
		}).forEach(function(key) {
			key = key.length < scope.length + 1? DEFAULT_KEY : key.slice(scope.length + 1);
			current[key] = toKey(key === DEFAULT_KEY? scope : scope + '/' + key);
		});
		
		keys.local[scope] = current;
	});
	
	return keys;
}

var allkeys = getKeys();

layout.addPages([newPage('global', '전역키')]);

layout.addPages(Array.from(scopes).map(function(prefix) {
	return newPage('local-' + prefix, prefix, prefix);
}));

function handleReset(wrong) {
	var rand = Math.floor(Math.random() * 999);
	rand = '0'.repeat(3 - rand.toString().length) + rand;
	
	OO.ui.prompt($('<p>' + (wrong? '잘못 입력하였습니다.' : 'DB2의 모든 데이터가 초기화됩니다.') + ' 계속하시려면 <b>' + rand + '</b>를 입력해 주세요.</p>'), {
		textInput: {
			validate: new RegExp(rand)
		}
	}).then(function(res) {
		if(res === rand) layout.$element.find('.removeall').click();
		else if(res !== null) handleReset(true);
	})
}

$(function() {
	try {
		var reset = new OO.ui.ButtonInputWidget({
			id: 'reset',
			label: '전체 초기화',
			icon: 'trash',
			flags: 'destructive'
		});
		reset.$button.click(handleReset.bind(null, false));
		
		$('#mw-content-text').html(new OO.ui.PanelLayout({
			expanded: false,
			framed: true,
			content: [layout]
		}).$element).prepend($('<div />', {id: 'globalbuttons'}).append(reset.$element));
	} catch(err) {
		$('#mw-content-text').html($('<span />', {
			class: 'error'
		}).text(err));
	}
});