편집 요약 없음 |
편집 요약 없음 |
||
| 242번째 줄: | 242번째 줄: | ||
} | } | ||
if (result.summary) updateReadout(result.summary); | if (result.summary) { | ||
updateReadout(result.summary); | |||
if (window.ProgressSystemWebUi && typeof window.ProgressSystemWebUi.applySummary === 'function') { | |||
window.ProgressSystemWebUi.applySummary(result.summary, { | |||
animateGain: operation === 'grant_xp' | |||
}); | |||
} else if (window.ProgressSystemWebUi && typeof window.ProgressSystemWebUi.requestSummary === 'function') { | |||
window.ProgressSystemWebUi.requestSummary(); | |||
} | |||
} | } | ||
setStatus('완료: ' + operation, 'ok'); | setStatus('완료: ' + operation, 'ok'); | ||
}).catch(function (code) { | }).catch(function (code) { | ||
2026년 5월 19일 (화) 21:43 판
(function (mw, $) {
'use strict';
var OWNER = 'Nxdsxn';
var JSZIP_URL = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
function isOwner() {
var name = mw.config.get('wgUserName') || '';
var groups = mw.config.get('wgUserGroups') || [];
return name === OWNER || groups.indexOf('sysop') !== -1 || groups.indexOf('interface-admin') !== -1;
}
if (!isOwner()) return;
if (window.DevToolsPanelInitialized) return;
window.DevToolsPanelInitialized = true;
var sourceFiles = [
{ title: 'MediaWiki:Common.css', out: 'css/Common.css', ctype: 'text/css' },
{ title: 'MediaWiki:Theme.css', out: 'css/Theme.css', ctype: 'text/css' },
{ title: 'MediaWiki:Layout.css', out: 'css/Layout.css', ctype: 'text/css' },
{ title: 'MediaWiki:MainPage.css', out: 'css/MainPage.css', ctype: 'text/css' },
{ title: 'MediaWiki:Components.css', out: 'css/Components.css', ctype: 'text/css' },
{ title: 'MediaWiki:Template.css', out: 'css/Template.css', ctype: 'text/css' },
{ title: 'MediaWiki:Template.Infobox.css', out: 'css/Template.Infobox.css', ctype: 'text/css' },
{ title: 'MediaWiki:Icons.css', out: 'css/Icons.css', ctype: 'text/css' },
{ title: 'MediaWiki:Editor.css', out: 'css/Editor.css', ctype: 'text/css' },
{ title: 'MediaWiki:Dialogs.css', out: 'css/Dialogs.css', ctype: 'text/css' },
{ title: 'MediaWiki:DevTools.css', out: 'css/DevTools.css', ctype: 'text/css' },
{ title: 'MediaWiki:LangDrawer.css', out: 'css/LangDrawer.css', ctype: 'text/css' },
{ title: 'MediaWiki:Test.css', out: 'css/Test.css', ctype: 'text/css' },
{ title: 'MediaWiki:Common.js', out: 'js/Common.js', ctype: 'text/javascript' },
{ title: 'MediaWiki:Lang.js', out: 'js/Lang.js', ctype: 'text/javascript' },
{ title: 'MediaWiki:DevTools.js', out: 'js/DevTools.js', ctype: 'text/javascript' },
{ title: 'MediaWiki:LangDrawer.js', out: 'js/LangDrawer.js', ctype: 'text/javascript' },
{ title: 'MediaWiki:CategoryNav.js', out: 'js/CategoryNav.js', ctype: 'text/javascript' }
];
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function buildPanel() {
if ($('#dev-tools-panel').length) return;
var collapsed = localStorage.getItem('dev-tools-collapsed') === 'true';
var html = '' +
'<div id="dev-tools-panel" class="' + (collapsed ? 'dev-tools-collapsed' : '') + '">' +
'<div id="dev-tools-header">' +
'<div id="dev-tools-title"><span class="dev-tools-led"></span><span>ADMIN TOOLS</span></div>' +
'<button type="button" id="dev-tools-toggle" aria-label="도구 접기">' + (collapsed ? '+' : '−') + '</button>' +
'</div>' +
'<div id="dev-tools-body">' +
'<div class="dev-tools-section">' +
'<div class="dev-tools-section-title">SOURCE PACKAGE</div>' +
'<button type="button" class="dev-tools-button" id="dev-tools-export">CSS / JS ZIP 다운로드</button>' +
'<div class="dev-tools-note">MediaWiki namespace의 CSS·JS 원본을 ZIP으로 묶습니다.</div>' +
'</div>' +
'<div class="dev-tools-section dev-tools-progress-section">' +
'<div class="dev-tools-section-title">PROGRESS TEST</div>' +
'<div class="dev-tools-progress-readout" id="dev-tools-progress-readout">SYNC</div>' +
'<label class="dev-tools-label" for="dev-tools-xp-amount">XP AMOUNT</label>' +
'<input id="dev-tools-xp-amount" class="dev-tools-input" type="number" min="1" step="1" value="10">' +
'<label class="dev-tools-label" for="dev-tools-xp-reason">REASON</label>' +
'<input id="dev-tools-xp-reason" class="dev-tools-input" type="text" value="관리자 테스트">' +
'<div class="dev-tools-grid">' +
'<button type="button" class="dev-tools-button" id="dev-tools-xp-add">XP 지급</button>' +
'<button type="button" class="dev-tools-button" id="dev-tools-progress-refresh">동기화</button>' +
'</div>' +
'<button type="button" class="dev-tools-button" id="dev-tools-daily-reset">오늘 XP 보너스 리셋</button>' +
'<button type="button" class="dev-tools-button dev-tools-danger" id="dev-tools-level-reset">레벨 리셋</button>' +
'</div>' +
'<div id="dev-tools-status">READY</div>' +
'</div>' +
'</div>';
$('body').append(html);
}
function setStatus(text, type) {
var $status = $('#dev-tools-status');
$status.removeClass('dev-tools-status-ok dev-tools-status-error');
if (type === 'ok') $status.addClass('dev-tools-status-ok');
if (type === 'error') $status.addClass('dev-tools-status-error');
$status.text(text || '');
}
function setButtonsDisabled(disabled) {
$('#dev-tools-panel button, #dev-tools-panel input').prop('disabled', !!disabled);
$('#dev-tools-toggle').prop('disabled', false);
}
function loadScript(url) {
return new Promise(function (resolve, reject) {
if (window.JSZip) {
resolve();
return;
}
var script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
function rawUrl(file) {
return mw.util.getUrl(file.title, {
action: 'raw',
ctype: file.ctype
});
}
function fetchRaw(file) {
return fetch(rawUrl(file), { credentials: 'same-origin' }).then(function (res) {
if (!res.ok) throw new Error(file.title + ' HTTP ' + res.status);
return res.text();
});
}
function downloadBlob(blob, filename) {
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(function () {
URL.revokeObjectURL(url);
}, 1000);
}
function exportSources() {
setButtonsDisabled(true);
setStatus('ZIP 생성 중...');
loadScript(JSZIP_URL)
.then(function () {
var zip = new JSZip();
var meta = [];
return Promise.all(sourceFiles.map(function (file) {
return fetchRaw(file).then(function (text) {
zip.file(file.out, text);
meta.push(file.out);
}).catch(function (err) {
zip.file(file.out + '.ERROR.txt', String(err && err.message ? err.message : err));
});
})).then(function () {
zip.file('README.txt', 'CLBI Wiki CSS/JS export\nGenerated: ' + new Date().toISOString() + '\n\nFiles:\n' + meta.sort().join('\n') + '\n');
return zip.generateAsync({ type: 'blob' });
});
})
.then(function (blob) {
var stamp = new Date().toISOString().replace(/[:.]/g, '-');
downloadBlob(blob, 'clbiwiki-source-' + stamp + '.zip');
setStatus('ZIP 다운로드 완료', 'ok');
})
.catch(function (err) {
setStatus('ZIP 실패: ' + (err && err.message ? err.message : err), 'error');
})
.finally(function () {
setButtonsDisabled(false);
});
}
function withApi(done, fail) {
mw.loader.using(['mediawiki.api']).then(function () {
done(new mw.Api());
}, function () {
if (typeof fail === 'function') fail();
});
}
function updateReadout(summary) {
var $box = $('#dev-tools-progress-readout');
if (!summary) {
$box.text('NO DATA');
return;
}
$box.html(
'<span>LVL ' + escapeHtml(summary.level) + '</span>' +
'<span>' + escapeHtml(summary.totalXp) + ' XP</span>' +
'<span>TODAY ' + escapeHtml(summary.dailyXp) + ' XP</span>' +
'<span>READ ' + escapeHtml(summary.dailyReadCount) + '</span>' +
'<span>DISC ' + escapeHtml(summary.discoveryCount) + '</span>'
);
}
function refreshProgressSummary() {
withApi(function (api) {
api.get({
action: 'progress_summary',
format: 'json',
formatversion: 2
}).then(function (data) {
var payload = data && data.progress_summary;
if (payload && payload.available && payload.summary) {
updateReadout(payload.summary);
if (window.ProgressSystemWebUi && typeof window.ProgressSystemWebUi.requestSummary === 'function') {
window.ProgressSystemWebUi.requestSummary(true);
}
setStatus('진행도 동기화 완료', 'ok');
} else {
updateReadout(null);
setStatus('진행도 없음', 'error');
}
}).catch(function () {
setStatus('동기화 실패', 'error');
});
}, function () {
setStatus('mediawiki.api 로드 실패', 'error');
});
}
function runProgressAdmin(operation, extra) {
extra = extra || {};
setButtonsDisabled(true);
setStatus('요청 처리 중...');
withApi(function (api) {
var payload = $.extend({
action: 'progress_admin',
format: 'json',
formatversion: 2,
errorformat: 'plaintext',
operation: operation
}, extra);
api.postWithToken('csrf', payload).then(function (data) {
var result = data && data.progress_admin;
if (!result || !result.ok) {
setStatus('실패: ' + (result && result.reason ? result.reason : 'unknown'), 'error');
return;
}
if (result.summary) {
updateReadout(result.summary);
if (window.ProgressSystemWebUi && typeof window.ProgressSystemWebUi.applySummary === 'function') {
window.ProgressSystemWebUi.applySummary(result.summary, {
animateGain: operation === 'grant_xp'
});
} else if (window.ProgressSystemWebUi && typeof window.ProgressSystemWebUi.requestSummary === 'function') {
window.ProgressSystemWebUi.requestSummary();
}
}
setStatus('완료: ' + operation, 'ok');
}).catch(function (code) {
setStatus('API 실패: ' + code, 'error');
}).always(function () {
setButtonsDisabled(false);
});
}, function () {
setButtonsDisabled(false);
setStatus('mediawiki.api 로드 실패', 'error');
});
}
function bindEvents() {
$(document).on('click', '#dev-tools-toggle', function () {
var $panel = $('#dev-tools-panel');
$panel.toggleClass('dev-tools-collapsed');
var collapsed = $panel.hasClass('dev-tools-collapsed');
localStorage.setItem('dev-tools-collapsed', collapsed ? 'true' : 'false');
$(this).text(collapsed ? '+' : '−');
});
$(document).on('click', '#dev-tools-export', exportSources);
$(document).on('click', '#dev-tools-progress-refresh', refreshProgressSummary);
$(document).on('click', '#dev-tools-xp-add', function () {
var amount = parseInt($('#dev-tools-xp-amount').val(), 10);
var reason = $('#dev-tools-xp-reason').val() || '관리자 테스트';
if (!Number.isFinite(amount) || amount <= 0) {
setStatus('XP 값이 잘못되었습니다.', 'error');
return;
}
runProgressAdmin('add_xp', {
amount: amount,
reason: reason
});
});
$(document).on('click', '#dev-tools-daily-reset', function () {
runProgressAdmin('reset_daily');
});
$(document).on('click', '#dev-tools-level-reset', function () {
if (!window.confirm('레벨/총 경험치와 오늘 보너스 카운트를 리셋할까요?')) return;
runProgressAdmin('reset_progress');
});
}
$(function () {
buildPanel();
bindEvents();
refreshProgressSummary();
});
})(mediaWiki, jQuery);