imported>Hsl0 |
imported>Hsl0 |
1번째 줄: |
1번째 줄: |
| var punycode = require('ext.gadget.punycode');
| | /** [[틀:일방통행링크]]에서 사용하는 링크 리다이렉트 함수 |
| | | * class="OWLink-container" |
| function requestMetadata() {
| | * 작성자: [[사용자:Gustmd7410|Gustmd7410]] |
| var queue = metadataQueue;
| | **/ |
| $.get('/w/api.php', {
| | $(function useSCGI() { |
| action: "query",
| | if(!URLSearchParams) return; |
| 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() {
| |
| OO.ui.confirm('정말로 ' + (name === 'global'? '모든 전역키' : '"' + label + '"의 모든 키') + '를 제거하시겠습니까?').then(function(response) {
| |
| var process;
| |
| if(response) {
| |
| remove.setDisabled(true);
| |
| remove.setFlags('pending');
| |
|
| |
| for(var key in keys) process = hybridStorage.removeItem(keys[key]);
| |
|
| |
| process.then(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]);
| |
| }
| |
|
| |
| remove.setDisabled(false);
| |
| remove.setFlags({pending: false});
| |
| }, notifyApiError.bind(null, '항목을 제거하지 못했습니다.', null));
| |
| }
| |
| });
| |
| });
| |
| 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([
| |
| $('<h2 />').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(punycode.toASCII(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 punycode.toUnicode(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%" />')
| |
| .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 | | $('.OWLink-container a').each(function() { |
| scopes.forEach(function(scope) {
| | var href = new URL(this.href); |
| var current = {}; | | var params = new URLSearchParams(location.search); |
|
| |
| 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; | | if( |
| | location.host === href.host && decodeURI( |
| | location.pathname === '/w/index.php' && params.get('title') || |
| | location.pathname.startsWith('/wiki/') && location.pathname.replace('/wiki/', '') |
| | ) === decodeURI( |
| | href.pathname === '/w/index.php' && href.searchParams.get('title') || |
| | href.pathname.startsWith('/wiki/') && href.pathname.replace('/wiki/', '') |
| | ) && params.get('action') === href.searchParams.get('action') |
| | ) $(this).click(function(event) { |
| | event.preventDefault(); |
| | location.replace(event.currentTarget.href); |
| | }).addClass('OWLink'); |
| }); | | }); |
|
| |
| 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 metadataQueue = {};
| |
| var metaRqTimeout = null;
| |
|
| |
| 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));
| |
| }
| |
| }); | | }); |