(Install package: wiki-nations-panel-flag-resolve-fallback-20260705 / js/NationsPanel.js) |
(Install package: wiki-nations-panel-inline-svg-flag-crop-20260705 / js/NationsPanel.js) |
||
| 6번째 줄: | 6번째 줄: | ||
'use strict'; | 'use strict'; | ||
var NATIONS_PANEL_BUILD = '20260705- | var NATIONS_PANEL_BUILD = '20260705-inline-svg-flag-crop'; | ||
var DEFAULT_ERAS = [ | var DEFAULT_ERAS = [ | ||
| 202번째 줄: | 202번째 줄: | ||
var nationRuntimeCacheBust = String(Date.now()); | var nationRuntimeCacheBust = String(Date.now()); | ||
var nationResolvedFileUrlCache = {}; | var nationResolvedFileUrlCache = {}; | ||
var nationInlineSvgCache = {}; | |||
function addNationCacheBust(url) { | function addNationCacheBust(url) { | ||
| 789번째 줄: | 790번째 줄: | ||
offsetY = getNationFlagOffsetY(flag); | offsetY = getNationFlagOffsetY(flag); | ||
classes = 'clbi-nations-list-flag' + (flag.border !== false ? ' has-border' : ''); | classes = 'clbi-nations-list-flag' + (flag.border !== false ? ' has-border' : ''); | ||
if (/\.svg(?:[?#]|$)/i.test(file) || /\.svg(?:[?#]|$)/i.test(url)) { | |||
classes += ' is-svg'; | |||
} | |||
if (!url) { | if (!url) { | ||
| 794번째 줄: | 798번째 줄: | ||
} | } | ||
return '<span class="clbi-nations-list-flag-slot" style="--flag-max-w:' + escapeHtml(width) + ';--flag-y:' + offsetY + 'px;" data-flag-file="' + escapeHtml(file) + '">' + | return '<span class="clbi-nations-list-flag-slot" style="--flag-max-w:' + escapeHtml(width) + ';--flag-y:' + offsetY + 'px;" data-flag-file="' + escapeHtml(file) + '" data-flag-src="' + escapeHtml(url) + '">' + | ||
'<img class="' + classes + '" src="' + escapeHtml(url) + '" alt="" decoding="async">' + | '<img class="' + classes + '" src="' + escapeHtml(url) + '" alt="" decoding="async" data-flag-file="' + escapeHtml(file) + '" data-flag-src="' + escapeHtml(url) + '">' + | ||
'</span>'; | '</span>'; | ||
} | |||
function getCleanSvgFetchUrl(url) { | |||
url = String(url || '').trim(); | |||
if (!url) return ''; | |||
return url.replace(/([?&])_clbiBust=[^&]+&?/g, '$1').replace(/[?&]$/g, ''); | |||
} | |||
function cleanupInlineSvgElement(svg) { | |||
toArray(svg.querySelectorAll('script, foreignObject, iframe, object, embed')).forEach(function (node) { | |||
if (node && node.parentNode) node.parentNode.removeChild(node); | |||
}); | |||
toArray(svg.querySelectorAll('*')).forEach(function (node) { | |||
toArray(node.attributes || []).forEach(function (attr) { | |||
if (/^on/i.test(attr.name || '')) node.removeAttribute(attr.name); | |||
}); | |||
}); | |||
} | |||
function measureSvgVisibleBox(svg) { | |||
var host; | |||
var clone; | |||
var box; | |||
var viewBox; | |||
var values; | |||
var w; | |||
var h; | |||
if (!svg || !document.body) return null; | |||
host = document.createElement('div'); | |||
host.style.cssText = 'position:absolute;left:-99999px;top:-99999px;width:2000px;height:2000px;visibility:hidden;overflow:visible;pointer-events:none;'; | |||
clone = svg.cloneNode(true); | |||
clone.removeAttribute('width'); | |||
clone.removeAttribute('height'); | |||
clone.style.width = '1000px'; | |||
clone.style.height = '1000px'; | |||
clone.style.overflow = 'visible'; | |||
host.appendChild(clone); | |||
document.body.appendChild(host); | |||
try { | |||
box = clone.getBBox ? clone.getBBox() : null; | |||
} catch (err) { | |||
box = null; | |||
} | |||
if (host.parentNode) host.parentNode.removeChild(host); | |||
if (box && isFinite(box.width) && isFinite(box.height) && box.width > 0 && box.height > 0) { | |||
return { x: box.x, y: box.y, width: box.width, height: box.height }; | |||
} | |||
viewBox = svg.getAttribute('viewBox') || ''; | |||
values = viewBox.trim().split(/[\s,]+/).map(parseFloat); | |||
if (values.length === 4 && values.every(isFinite) && values[2] > 0 && values[3] > 0) { | |||
return { x: values[0], y: values[1], width: values[2], height: values[3] }; | |||
} | |||
w = parseFloat(svg.getAttribute('width')); | |||
h = parseFloat(svg.getAttribute('height')); | |||
if (isFinite(w) && isFinite(h) && w > 0 && h > 0) { | |||
return { x: 0, y: 0, width: w, height: h }; | |||
} | |||
return { x: 0, y: 0, width: 3, height: 2 }; | |||
} | |||
function buildInlineFlagSvg(svgText, file, hasBorder) { | |||
var doc; | |||
var svg; | |||
var box; | |||
var ratio; | |||
var drawW; | |||
var drawH; | |||
var padX; | |||
var padY; | |||
doc = new DOMParser().parseFromString(String(svgText || ''), 'image/svg+xml'); | |||
svg = doc && doc.documentElement; | |||
if (!svg || String(svg.nodeName || '').toLowerCase() !== 'svg') return null; | |||
cleanupInlineSvgElement(svg); | |||
box = measureSvgVisibleBox(svg); | |||
if (!box || !box.width || !box.height) return null; | |||
padX = Math.max(box.width * 0.002, 0.01); | |||
padY = Math.max(box.height * 0.002, 0.01); | |||
box = { | |||
x: box.x - padX, | |||
y: box.y - padY, | |||
width: box.width + padX * 2, | |||
height: box.height + padY * 2 | |||
}; | |||
ratio = box.width / box.height; | |||
if (!isFinite(ratio) || ratio <= 0) ratio = 1.5; | |||
if (ratio >= 1) { | |||
drawW = 16; | |||
drawH = Math.max(1, Math.min(16, 16 / ratio)); | |||
} else { | |||
drawH = 16; | |||
drawW = Math.max(1, Math.min(16, 16 * ratio)); | |||
} | |||
svg.setAttribute('class', 'clbi-nations-list-flag clbi-nations-list-flag-inline-svg' + (hasBorder ? ' has-border' : '')); | |||
svg.setAttribute('viewBox', [box.x, box.y, box.width, box.height].join(' ')); | |||
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); | |||
svg.setAttribute('aria-hidden', 'true'); | |||
svg.setAttribute('focusable', 'false'); | |||
svg.setAttribute('data-flag-file', file || ''); | |||
svg.setAttribute('width', String(Math.round(drawW * 100) / 100)); | |||
svg.setAttribute('height', String(Math.round(drawH * 100) / 100)); | |||
svg.style.width = (Math.round(drawW * 100) / 100) + 'px'; | |||
svg.style.height = (Math.round(drawH * 100) / 100) + 'px'; | |||
return svg; | |||
} | |||
function inlineOneSvgFlag(img) { | |||
var src; | |||
var cleanSrc; | |||
var slot; | |||
var file; | |||
var hasBorder; | |||
if (!img || img.getAttribute('data-inline-svg-state')) return; | |||
src = img.getAttribute('data-flag-src') || img.getAttribute('src') || ''; | |||
file = img.getAttribute('data-flag-file') || ''; | |||
if (!/\.svg(?:[?#]|$)/i.test(src) && !/\.svg(?:[?#]|$)/i.test(file)) return; | |||
cleanSrc = getCleanSvgFetchUrl(src); | |||
if (!cleanSrc) return; | |||
img.setAttribute('data-inline-svg-state', 'loading'); | |||
slot = img.closest ? img.closest('.clbi-nations-list-flag-slot') : null; | |||
hasBorder = img.classList.contains('has-border'); | |||
function apply(svg) { | |||
if (!svg || !img.parentNode) return; | |||
if (slot) { | |||
slot.classList.add('has-inline-svg'); | |||
slot.classList.remove('is-missing'); | |||
} | |||
img.parentNode.replaceChild(svg, img); | |||
} | |||
if (nationInlineSvgCache[cleanSrc]) { | |||
if (nationInlineSvgCache[cleanSrc] !== 'failed') { | |||
apply(nationInlineSvgCache[cleanSrc].cloneNode(true)); | |||
} | |||
return; | |||
} | |||
fetch(cleanSrc, { credentials: 'same-origin', cache: 'no-store' }) | |||
.then(function (res) { | |||
if (!res.ok) throw new Error('svg fetch failed: ' + res.status); | |||
return res.text(); | |||
}) | |||
.then(function (text) { | |||
var svg = buildInlineFlagSvg(text, file, hasBorder); | |||
if (!svg) throw new Error('svg parse failed'); | |||
nationInlineSvgCache[cleanSrc] = svg.cloneNode(true); | |||
apply(svg); | |||
}) | |||
.catch(function () { | |||
nationInlineSvgCache[cleanSrc] = 'failed'; | |||
img.setAttribute('data-inline-svg-state', 'failed'); | |||
}); | |||
} | |||
function normalizeInlineSvgFlags(scope) { | |||
var root = scope || document; | |||
toArray(root.querySelectorAll('.clbi-nations-list-flag-slot .clbi-nations-list-flag.is-svg')).forEach(inlineOneSvgFlag); | |||
} | } | ||
| 955번째 줄: | 1,133번째 줄: | ||
bindNationFlagFallbacks(body); | bindNationFlagFallbacks(body); | ||
normalizeInlineSvgFlags(body); | |||
panel.classList.add('clbi-nations-list-json-ready'); | panel.classList.add('clbi-nations-list-json-ready'); | ||
panel.setAttribute('data-nation-list-year-loaded', String(data.year || getPanelEra(panel))); | panel.setAttribute('data-nation-list-year-loaded', String(data.year || getPanelEra(panel))); | ||
| 1,256번째 줄: | 1,435번째 줄: | ||
return { | return { | ||
build: NATIONS_PANEL_BUILD, | build: NATIONS_PANEL_BUILD, | ||
inlineSvgFlagCount: document.querySelectorAll('.clbi-nations-list-flag-inline-svg').length, | |||
resolvedFileCount: Object.keys(nationResolvedFileUrlCache || {}).length, | resolvedFileCount: Object.keys(nationResolvedFileUrlCache || {}).length, | ||
unresolvedFiles: Object.keys(nationResolvedFileUrlCache || {}).filter(function (key) { | unresolvedFiles: Object.keys(nationResolvedFileUrlCache || {}).filter(function (key) { | ||
| 1,272번째 줄: | 1,452번째 줄: | ||
window.CLBI_NATIONS_PANEL = { | window.CLBI_NATIONS_PANEL = { | ||
build: NATIONS_PANEL_BUILD, | build: NATIONS_PANEL_BUILD, | ||
inlineSvgFlagCount: document.querySelectorAll('.clbi-nations-list-flag-inline-svg').length, | |||
init: init, | init: init, | ||
refresh: refresh, | refresh: refresh, | ||
2026년 7월 5일 (일) 21:17 판
/* =========================================
COASTLINE: BLACK ICE - Nations Panel
국가 및 조합: 시대 전환 / 역사 연도 / 대륙 탭 제어
========================================= */
(function () {
'use strict';
var NATIONS_PANEL_BUILD = '20260705-inline-svg-flag-crop';
var DEFAULT_ERAS = [
'1950', '1960', '1970', '1980', '1990', '2000', '2010', '2020',
'2030', '2040', '2050', '2060', '2070', '2080', '2090', '2100'
];
function toArray(list) {
return Array.prototype.slice.call(list || []);
}
function unique(values) {
var seen = {};
var out = [];
values.forEach(function (value) {
value = String(value || '').trim();
if (!value || seen[value]) return;
seen[value] = true;
out.push(value);
});
return out;
}
function parseEraList(stack) {
var raw = stack ? stack.getAttribute('data-nations-era-years') : '';
var values = raw ? raw.split(/[\s,|]+/) : [];
values = unique(values.filter(function (value) {
return /^\d{4}$/.test(value);
}));
return values.length ? values : DEFAULT_ERAS.slice();
}
function getEraIndex(eras, era) {
var index = eras.indexOf(String(era || ''));
return index >= 0 ? index : 0;
}
function getWrappedEra(eras, index) {
if (!eras.length) return '';
index = ((index % eras.length) + eras.length) % eras.length;
return eras[index];
}
function setHidden(element, hidden) {
if (!element) return;
if (hidden) {
element.setAttribute('hidden', 'hidden');
element.setAttribute('aria-hidden', 'true');
element.classList.remove('is-active');
} else {
element.removeAttribute('hidden');
element.setAttribute('aria-hidden', 'false');
element.classList.add('is-active');
}
}
function setActiveButton(buttons, activeButton) {
buttons.forEach(function (button) {
var active = button === activeButton;
button.classList.toggle('is-active', active);
button.setAttribute('aria-selected', active ? 'true' : 'false');
button.setAttribute('tabindex', active ? '0' : '-1');
});
}
function activatePanelsByAttribute(panels, attrName, value) {
panels.forEach(function (panel) {
setHidden(panel, panel.getAttribute(attrName) !== value);
});
}
function replaceYearTemplate(template, era) {
return String(template || '').replace(/\{year\}/g, String(era || ''));
}
function readTemplate(root, attrName, fallback) {
if (!root) return fallback;
return root.getAttribute(attrName) || fallback;
}
function setAttributeFromTemplate(root, attrName, templateName, fallback, era) {
var template = readTemplate(root, templateName, fallback);
if (!template) {
root.removeAttribute(attrName);
return;
}
root.setAttribute(attrName, replaceYearTemplate(template, era));
}
function resetGlobeDom(globe) {
var instance;
if (!globe) return;
instance = globe.CLBI_NationsGlobeInstance || null;
if (instance && typeof instance.dispose === 'function') {
try { instance.dispose(); } catch (err) {}
}
globe.CLBI_NationsGlobeInstance = null;
globe.removeAttribute('data-nations-globe-ready');
globe.classList.remove('is-ready', 'has-error', 'is-dragging');
globe.innerHTML = '';
}
function reloadGlobe(globe) {
if (!globe) return;
if (window.CLBI_NationsGlobe && typeof window.CLBI_NationsGlobe.reload === 'function') {
window.CLBI_NationsGlobe.reload(globe);
return;
}
resetGlobeDom(globe);
if (window.CLBI_NationsGlobe && typeof window.CLBI_NationsGlobe.init === 'function') {
window.CLBI_NationsGlobe.init(globe.parentNode || document);
return;
}
window.setTimeout(function () {
if (window.CLBI_NationsGlobe && typeof window.CLBI_NationsGlobe.init === 'function') {
window.CLBI_NationsGlobe.init(globe.parentNode || document);
}
}, 0);
}
function applyGlobeEra(globe, era, reload) {
if (!globe || !era) return;
globe.setAttribute('data-era-active', era);
globe.setAttribute('data-era', replaceYearTemplate(
readTemplate(globe, 'data-era-readout-template', '{year} · CLBI BORDER LAYER'),
era
));
setAttributeFromTemplate(
globe,
'data-geojson-url',
'data-era-border-url-template',
'/index.php/Special:Redirect/file/{year}_Border_Lines.geojson',
era
);
setAttributeFromTemplate(
globe,
'data-polygon-overlay-url',
'data-era-polygon-overlay-url-template',
'/index.php/Special:Redirect/file/{year}_Polygon_Overlay.webp',
era
);
setAttributeFromTemplate(
globe,
'data-polygon-hit-url',
'data-era-polygon-hit-url-template',
'/index.php/Special:Redirect/file/{year}_Polygon_Hit.geojson',
era
);
setAttributeFromTemplate(
globe,
'data-nation-link-map-url',
'data-era-nation-link-map-url-template',
'/index.php?title=MediaWiki:{year}_Nation_Link_Map.json&action=raw&ctype=application/json',
era
);
setAttributeFromTemplate(
globe,
'data-lakes-overlay-url',
'data-era-lakes-overlay-url-template',
'/index.php/Special:Redirect/file/{year}_Lakes_Overlay.webp',
era
);
if (reload) {
reloadGlobe(globe);
}
}
var NATION_CONTINENT_FALLBACK = [
{ key: 'america', label: '아메리카', regions: ['북아메리카', '남아메리카'] },
{ key: 'europe', label: '유럽', regions: ['북유럽', '서유럽', '중앙유럽', '동유럽', '동남유럽', '남유럽', '남서유럽'] },
{ key: 'africa', label: '아프리카', regions: ['북아프리카', '서아프리카', '중앙아프리카', '동아프리카', '남아프리카'] },
{ key: 'asia', label: '아시아', regions: ['서아시아', '중앙아시아', '북아시아', '남아시아', '동아시아', '동남아시아'] },
{ key: 'oceania', label: '오세아니아', regions: ['오세아니아'] },
{ key: 'antarctica', label: '남극', regions: ['남극'] }
];
var nationListCache = {};
var nationLinkMapCache = {};
var nationRuntimeCacheBust = String(Date.now());
var nationResolvedFileUrlCache = {};
var nationInlineSvgCache = {};
function addNationCacheBust(url) {
url = String(url || '').trim();
if (!url) return '';
if (/[?&]_clbiBust=/.test(url)) return url;
return url + (url.indexOf('?') >= 0 ? '&' : '?') + '_clbiBust=' + encodeURIComponent(nationRuntimeCacheBust);
}
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function encodeWikiTitle(title) {
return String(title || '')
.trim()
.replace(/ /g, '_')
.split('/')
.map(function (part) { return encodeURIComponent(part); })
.join('/');
}
function normalizeNationFile(value) {
var text = String(value || '').trim();
var titleMatch;
var redirectMatch;
var previous;
var i;
var j;
if (!text) return '';
text = text
.replace(/^\[\[:?\s*/i, '')
.replace(/\]\]$/i, '')
.trim();
for (i = 0; i < 8; i += 1) {
previous = text;
for (j = 0; j < 3; j += 1) {
try {
if (/%[0-9a-f]{2}/i.test(text)) {
text = decodeURIComponent(text);
}
} catch (err) {
break;
}
}
text = text.replace(/\\/g, '/').trim();
titleMatch = text.match(/[?&]title=([^&#]+)/i);
if (titleMatch) {
text = titleMatch[1];
continue;
}
text = text
.replace(/[?#].*$/g, '')
.replace(/^https?:\/\/[^/]+/i, '')
.replace(/^\/+/, '')
.replace(/^index\.php\/?/i, '')
.replace(/^wiki\/?/i, '')
.trim();
for (j = 0; j < 6; j += 1) {
var stripped = text
.replace(/^\s*파일\s*:/i, '')
.replace(/^\s*File\s*:/i, '')
.replace(/^\s*이미지\s*:/i, '')
.replace(/^\s*Image\s*:/i, '')
.replace(/^:+/, '')
.trim();
if (stripped === text) break;
text = stripped;
}
redirectMatch = text.match(/^(?:(?:Special|특수)\s*[:/]\s*)+(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i) ||
text.match(/^(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i) ||
text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)+(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i);
if (redirectMatch) {
text = redirectMatch[1].trim();
continue;
}
if (text === previous) break;
}
text = text.replace(/^\/+/, '').trim();
return text;
}
function normalizeNationFileKey(file) {
return normalizeNationFile(file).replace(/_/g, ' ').trim().toLowerCase();
}
function getWikiApiUrl(params) {
var query = [];
Object.keys(params || {}).forEach(function (key) {
if (params[key] == null) return;
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(params[key])));
});
return (window.mw && mw.util && mw.util.wikiScript ? mw.util.wikiScript('api') : '/api.php') + '?' + query.join('&');
}
function getWikiApiEndpoint() {
return (window.mw && mw.util && mw.util.wikiScript ? mw.util.wikiScript('api') : '/api.php');
}
function fetchWikiApi(params) {
var body;
try {
body = new URLSearchParams();
Object.keys(params || {}).forEach(function (key) {
if (params[key] == null) return;
body.append(key, String(params[key]));
});
return fetch(getWikiApiEndpoint(), {
method: 'POST',
credentials: 'same-origin',
cache: 'no-store',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: body.toString()
}).then(function (res) {
if (!res.ok) throw new Error('wiki api failed: ' + res.status);
return res.json();
});
} catch (err) {
return fetch(getWikiApiUrl(params), { credentials: 'same-origin', cache: 'no-store' }).then(function (res) {
if (!res.ok) throw new Error('wiki api failed: ' + res.status);
return res.json();
});
}
}
function buildNationRedirectFileUrl(file) {
var clean = normalizeNationFile(file);
var script;
if (!clean) return '';
if (window.mw && mw.util && typeof mw.util.getUrl === 'function') {
return addNationCacheBust(mw.util.getUrl('Special:Redirect/file/' + clean));
}
script = (window.mw && mw.config ? mw.config.get('wgScript') : '') || '/index.php';
return addNationCacheBust(script + '/Special:Redirect/file/' + encodeURIComponent(clean).replace(/%20/g, '_'));
}
function buildNationFileUrl(file) {
var clean = normalizeNationFile(file);
var key = normalizeNationFileKey(clean);
var resolved;
if (!clean || !key) return '';
resolved = nationResolvedFileUrlCache[key];
if (resolved) return addNationCacheBust(resolved);
/*
Some CLBI file names are visible through Special:Redirect/file even when
a bulk imageinfo query cannot resolve them yet. Use a canonical English
Special path as a display fallback, never the localized 특수:넘겨주기 path.
*/
return buildNationRedirectFileUrl(clean);
}
function collectNationFlagFileNames(data) {
var files = [];
function addFlag(flag) {
var file = normalizeNationFile(flag && (flag.file || flag.flag_file || flag.flag || flag.flag_title || flag));
if (file) files.push(file);
}
function scanItems(items) {
(items || []).forEach(function (item) {
if (!item || typeof item !== 'object') return;
if (Array.isArray(item.flags)) item.flags.forEach(addFlag);
addFlag(item.flag_file || item.flag || item.flag_title || '');
});
}
(data && data.continents || []).forEach(function (continent) {
(continent.regions || []).forEach(function (region) {
scanItems(region.items || []);
});
});
return unique(files);
}
function resolveNationFlagFileUrls(files) {
var cleanFiles = unique((files || []).map(normalizeNationFile).filter(Boolean));
var pending = cleanFiles.filter(function (file) {
var key = normalizeNationFileKey(file);
return key && nationResolvedFileUrlCache[key] === undefined;
});
var chunks = [];
while (pending.length) chunks.push(pending.splice(0, 20));
if (!chunks.length) return Promise.resolve(nationResolvedFileUrlCache);
return Promise.all(chunks.map(function (chunk) {
var titleToKey = {};
var titles = [];
chunk.forEach(function (file) {
var key = normalizeNationFileKey(file);
var canonical = 'File:' + file;
var local = '파일:' + file;
if (!key) return;
titleToKey[normalizeNationFileKey(canonical)] = key;
titleToKey[normalizeNationFileKey(local)] = key;
titles.push(canonical);
titles.push(local);
});
return fetchWikiApi({
action: 'query',
format: 'json',
formatversion: '2',
redirects: '1',
prop: 'imageinfo',
iiprop: 'url',
titles: titles.join('|')
}).then(function (json) {
var pages = (json && json.query && json.query.pages) || [];
var seen = {};
pages.forEach(function (page) {
var file = normalizeNationFile(page && page.title);
var pageKey = normalizeNationFileKey(file);
var originalKey = titleToKey[pageKey] || pageKey;
var imageinfo = page && page.imageinfo && page.imageinfo[0];
if (!originalKey) return;
seen[originalKey] = true;
if (imageinfo && imageinfo.url) {
nationResolvedFileUrlCache[originalKey] = imageinfo.url;
if (pageKey) nationResolvedFileUrlCache[pageKey] = imageinfo.url;
} else if (nationResolvedFileUrlCache[originalKey] === undefined) {
nationResolvedFileUrlCache[originalKey] = '';
}
});
chunk.forEach(function (file) {
var key = normalizeNationFileKey(file);
if (key && !seen[key] && nationResolvedFileUrlCache[key] === undefined) {
nationResolvedFileUrlCache[key] = '';
}
});
}).catch(function () {
chunk.forEach(function (file) {
var key = normalizeNationFileKey(file);
if (key && nationResolvedFileUrlCache[key] === undefined) nationResolvedFileUrlCache[key] = '';
});
});
})).then(function () {
return nationResolvedFileUrlCache;
});
}
function isLegacyNationIndexFileUrl(src) {
src = String(src || '');
if (!src) return false;
if (!/\.svg(?:[?#]|$)/i.test(src)) return false;
if (/\/images\//i.test(src)) return false;
return /(?:Special|%ED%8A%B9%EC%88%98|특수).*?(?:Redirect|%EB%84%98%EA%B2%A8%EC%A3%BC%EA%B8%B0|넘겨주기).*?file/i.test(src) || /\/index\.php\//i.test(src);
}
function purgeLegacyNationIndexFileImages(scope) {
var root = scope || document;
toArray(root.querySelectorAll ? root.querySelectorAll('.clbi-nations-tabpanel-body img, .clbi-nations-tabpanel img') : []).forEach(function (img) {
var src = img.currentSrc || img.getAttribute('src') || '';
if (!isLegacyNationIndexFileUrl(src)) return;
img.removeAttribute('src');
img.setAttribute('data-clbi-legacy-src-removed', src);
});
}
function getNationFlagOffsetY(flag) {
var value;
if (!flag || typeof flag !== 'object') return 0;
value = flag.offsetY;
if (value == null) value = flag.offset_y;
if (value == null) value = flag.flagOffsetY;
if (value == null && flag.decorator === 'top') value = -1;
value = parseFloat(value);
if (!isFinite(value)) value = 0;
return Math.max(-3, Math.min(3, Math.round(value)));
}
function buildNationPageUrl(page) {
page = String(page || '').trim();
return page ? '/index.php/' + encodeWikiTitle(page) : '';
}
function getPanelEra(panel) {
var eraPanel = panel && panel.closest ? panel.closest('.clbi-nations-era-content[data-era-content]') : null;
var stack = panel && panel.closest ? panel.closest('.clbi-nations-panel-stack') : null;
return (eraPanel && eraPanel.getAttribute('data-era-content')) ||
(panel && panel.getAttribute('data-era')) ||
(stack && (stack.getAttribute('data-nations-current-era') || stack.getAttribute('data-era'))) ||
'1950';
}
function getNationListUrl(panel, era) {
var stack = panel && panel.closest ? panel.closest('.clbi-nations-panel-stack') : null;
var template = (panel && panel.getAttribute('data-nation-list-url-template')) ||
(stack && stack.getAttribute('data-nation-list-url-template')) ||
'/index.php?title=MediaWiki:{year}_Nation_List.json&action=raw&ctype=application/json';
return (panel && panel.getAttribute('data-nation-list-url')) || replaceYearTemplate(template, era);
}
function getNationLinkMapUrl(panel, era) {
var stack = panel && panel.closest ? panel.closest('.clbi-nations-panel-stack') : null;
var template = (panel && panel.getAttribute('data-nation-link-map-url-template')) ||
(stack && stack.getAttribute('data-nation-link-map-url-template')) ||
'/index.php?title=MediaWiki:{year}_Nation_Link_Map.json&action=raw&ctype=application/json';
return (panel && panel.getAttribute('data-nation-link-map-url')) || replaceYearTemplate(template, era);
}
function normalizeNationTag(value) {
value = String(value == null ? '' : value).trim().toUpperCase();
return /^[A-Z0-9]{3}$/.test(value) ? value : '';
}
function normalizeLookupKey(value) {
return String(value == null ? '' : value)
.trim()
.replace(/_/g, ' ')
.replace(/\s+/g, ' ')
.toLowerCase();
}
function getNationItemTag(item) {
if (!item || typeof item !== 'object') return '';
return normalizeNationTag(item.tag || item.code || item.map_tag || item.mapTag || '');
}
function collectNationItemFlags(item) {
var flags = [];
if (!item || typeof item !== 'object') return flags;
if (Array.isArray(item.flags)) {
item.flags.forEach(function (flag) {
if (!flag) return;
if (typeof flag === 'string') {
flag = { file: flag };
}
if (typeof flag !== 'object') return;
if (normalizeNationFile(flag.file || flag.flag_file || flag.flag || '')) {
flags.push(flag);
}
});
}
if (!flags.length && (item.flag_file || item.flag || item.flag_title)) {
flags.push({
file: item.flag_file || item.flag || item.flag_title,
width: item.flag_width || '16px',
border: item.border !== false,
offsetMode: item.offsetMode || item.offset_mode || 'auto',
offsetY: item.offsetY || item.offset_y || 0
});
}
return flags;
}
function normalizeNationLinkMap(payload) {
var source = payload && payload.items ? payload.items : {};
var index = { byTag: {}, byPage: {}, byLabel: {} };
Object.keys(source || {}).forEach(function (key) {
var entry = source[key];
var tag;
var pageKey;
var labelKey;
if (!entry || typeof entry !== 'object') return;
tag = normalizeNationTag(entry.tag || key || entry.page || '');
pageKey = normalizeLookupKey(entry.wiki_title || entry.page_title || entry.title || '');
labelKey = normalizeLookupKey(entry.name_ko || entry.label || entry.page_name || '');
if (tag && !index.byTag[tag]) index.byTag[tag] = entry;
if (pageKey && !index.byPage[pageKey]) index.byPage[pageKey] = entry;
if (labelKey && !index.byLabel[labelKey]) index.byLabel[labelKey] = entry;
});
return index;
}
function fetchNationLinkMap(panel, era) {
var url = getNationLinkMapUrl(panel, era);
var cacheKey = era + '|' + url;
if (nationLinkMapCache[cacheKey]) return nationLinkMapCache[cacheKey];
nationLinkMapCache[cacheKey] = fetch(addNationCacheBust(url), { credentials: 'same-origin', cache: 'no-store' })
.then(function (res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.text();
})
.then(function (text) {
var trimmed = String(text || '').trim();
if (!trimmed) throw new Error('empty nation link map');
return normalizeNationLinkMap(JSON.parse(trimmed));
})
.catch(function () {
return normalizeNationLinkMap(null);
});
return nationLinkMapCache[cacheKey];
}
function findNationLinkEntryForItem(item, index) {
var tag;
var pageKey;
var labelKey;
if (!item || !index) return null;
tag = getNationItemTag(item);
if (tag && index.byTag && index.byTag[tag]) return index.byTag[tag];
pageKey = normalizeLookupKey(item.page || item.wiki_title || '');
if (pageKey && index.byPage && index.byPage[pageKey]) return index.byPage[pageKey];
labelKey = normalizeLookupKey(item.label || item.name || item.name_ko || '');
if (labelKey && index.byLabel && index.byLabel[labelKey]) return index.byLabel[labelKey];
return null;
}
function applyNationLinkMapFlagFallback(data, linkMapIndex) {
if (!data || !Array.isArray(data.continents)) return data;
data.continents.forEach(function (continent) {
(continent.regions || []).forEach(function (region) {
(region.items || []).forEach(function (item) {
var entry;
var file;
if (!item || collectNationItemFlags(item).length) return;
entry = findNationLinkEntryForItem(item, linkMapIndex);
file = entry ? normalizeNationFile(entry.flag_file || entry.flag || entry.flag_title || '') : '';
if (!file) return;
item.flags = [{
file: file,
width: '16px',
border: true,
offsetMode: 'auto',
offsetY: 0,
source: 'Nation_Link_Map'
}];
});
});
});
return data;
}
function getNationListFallback() {
return {
version: 1,
year: 1950,
continents: NATION_CONTINENT_FALLBACK.map(function (continent) {
return {
key: continent.key,
label: continent.label,
regions: continent.regions.map(function (region) {
return { key: region, label: region, items: [] };
})
};
})
};
}
function normalizeNationList(payload, era) {
var fallback = getNationListFallback();
var sourceContinents = payload && Array.isArray(payload.continents) ? payload.continents : [];
var sourceByKey = {};
sourceContinents.forEach(function (continent) {
if (continent && continent.key) sourceByKey[continent.key] = continent;
});
return {
version: payload && payload.version ? payload.version : 1,
year: payload && payload.year ? payload.year : parseInt(era || '1950', 10),
description: payload && payload.description ? payload.description : '',
continents: fallback.continents.map(function (base) {
var source = sourceByKey[base.key] || {};
var regionsByLabel = {};
if (Array.isArray(source.regions)) {
source.regions.forEach(function (region) {
if (!region) return;
if (region.key) regionsByLabel[region.key] = region;
if (region.label) regionsByLabel[region.label] = region;
});
}
return {
key: base.key,
label: source.label || base.label,
regions: base.regions.map(function (baseRegion) {
var region = regionsByLabel[baseRegion.key] || regionsByLabel[baseRegion.label] || baseRegion;
return {
key: region.key || baseRegion.key,
label: region.label || baseRegion.label,
items: Array.isArray(region.items) ? region.items.filter(function (item) {
return item && item.enabled !== false;
}) : []
};
})
};
})
};
}
function fetchNationList(panel, era) {
var url = getNationListUrl(panel, era);
var cacheKey = era + '|' + url;
if (nationListCache[cacheKey]) return nationListCache[cacheKey];
nationListCache[cacheKey] = fetch(addNationCacheBust(url), { credentials: 'same-origin', cache: 'no-store' })
.then(function (res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.text();
})
.then(function (text) {
var trimmed = String(text || '').trim();
if (!trimmed) throw new Error('empty nation list');
return normalizeNationList(JSON.parse(trimmed), era);
});
return nationListCache[cacheKey];
}
function normalizeNationFlagMaxWidth(value) {
var raw = String(value || '').trim();
var match = raw.match(/^(\d+(?:\.\d+)?)\s*(px)?$/i);
var n = match ? parseFloat(match[1]) : 16;
if (!isFinite(n)) n = 16;
if (n < 8) n = 8;
if (n > 32) n = 32;
return Math.round(n * 100) / 100 + 'px';
}
function renderNationFlag(flag) {
var file;
var url;
var width;
var classes;
var offsetY;
if (!flag) return '';
file = normalizeNationFile(flag.file || flag.flag_file || flag.flag || '');
if (!file) return '';
url = buildNationFileUrl(file);
width = normalizeNationFlagMaxWidth(flag.width || flag.flag_width || '24px');
offsetY = getNationFlagOffsetY(flag);
classes = 'clbi-nations-list-flag' + (flag.border !== false ? ' has-border' : '');
if (/\.svg(?:[?#]|$)/i.test(file) || /\.svg(?:[?#]|$)/i.test(url)) {
classes += ' is-svg';
}
if (!url) {
return '<span class="clbi-nations-list-flag-slot is-missing" style="--flag-max-w:' + escapeHtml(width) + ';--flag-y:' + offsetY + 'px;" data-flag-file="' + escapeHtml(file) + '" title="국기 파일 URL 미확인: ' + escapeHtml(file) + '"></span>';
}
return '<span class="clbi-nations-list-flag-slot" style="--flag-max-w:' + escapeHtml(width) + ';--flag-y:' + offsetY + 'px;" data-flag-file="' + escapeHtml(file) + '" data-flag-src="' + escapeHtml(url) + '">' +
'<img class="' + classes + '" src="' + escapeHtml(url) + '" alt="" decoding="async" data-flag-file="' + escapeHtml(file) + '" data-flag-src="' + escapeHtml(url) + '">' +
'</span>';
}
function getCleanSvgFetchUrl(url) {
url = String(url || '').trim();
if (!url) return '';
return url.replace(/([?&])_clbiBust=[^&]+&?/g, '$1').replace(/[?&]$/g, '');
}
function cleanupInlineSvgElement(svg) {
toArray(svg.querySelectorAll('script, foreignObject, iframe, object, embed')).forEach(function (node) {
if (node && node.parentNode) node.parentNode.removeChild(node);
});
toArray(svg.querySelectorAll('*')).forEach(function (node) {
toArray(node.attributes || []).forEach(function (attr) {
if (/^on/i.test(attr.name || '')) node.removeAttribute(attr.name);
});
});
}
function measureSvgVisibleBox(svg) {
var host;
var clone;
var box;
var viewBox;
var values;
var w;
var h;
if (!svg || !document.body) return null;
host = document.createElement('div');
host.style.cssText = 'position:absolute;left:-99999px;top:-99999px;width:2000px;height:2000px;visibility:hidden;overflow:visible;pointer-events:none;';
clone = svg.cloneNode(true);
clone.removeAttribute('width');
clone.removeAttribute('height');
clone.style.width = '1000px';
clone.style.height = '1000px';
clone.style.overflow = 'visible';
host.appendChild(clone);
document.body.appendChild(host);
try {
box = clone.getBBox ? clone.getBBox() : null;
} catch (err) {
box = null;
}
if (host.parentNode) host.parentNode.removeChild(host);
if (box && isFinite(box.width) && isFinite(box.height) && box.width > 0 && box.height > 0) {
return { x: box.x, y: box.y, width: box.width, height: box.height };
}
viewBox = svg.getAttribute('viewBox') || '';
values = viewBox.trim().split(/[\s,]+/).map(parseFloat);
if (values.length === 4 && values.every(isFinite) && values[2] > 0 && values[3] > 0) {
return { x: values[0], y: values[1], width: values[2], height: values[3] };
}
w = parseFloat(svg.getAttribute('width'));
h = parseFloat(svg.getAttribute('height'));
if (isFinite(w) && isFinite(h) && w > 0 && h > 0) {
return { x: 0, y: 0, width: w, height: h };
}
return { x: 0, y: 0, width: 3, height: 2 };
}
function buildInlineFlagSvg(svgText, file, hasBorder) {
var doc;
var svg;
var box;
var ratio;
var drawW;
var drawH;
var padX;
var padY;
doc = new DOMParser().parseFromString(String(svgText || ''), 'image/svg+xml');
svg = doc && doc.documentElement;
if (!svg || String(svg.nodeName || '').toLowerCase() !== 'svg') return null;
cleanupInlineSvgElement(svg);
box = measureSvgVisibleBox(svg);
if (!box || !box.width || !box.height) return null;
padX = Math.max(box.width * 0.002, 0.01);
padY = Math.max(box.height * 0.002, 0.01);
box = {
x: box.x - padX,
y: box.y - padY,
width: box.width + padX * 2,
height: box.height + padY * 2
};
ratio = box.width / box.height;
if (!isFinite(ratio) || ratio <= 0) ratio = 1.5;
if (ratio >= 1) {
drawW = 16;
drawH = Math.max(1, Math.min(16, 16 / ratio));
} else {
drawH = 16;
drawW = Math.max(1, Math.min(16, 16 * ratio));
}
svg.setAttribute('class', 'clbi-nations-list-flag clbi-nations-list-flag-inline-svg' + (hasBorder ? ' has-border' : ''));
svg.setAttribute('viewBox', [box.x, box.y, box.width, box.height].join(' '));
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
svg.setAttribute('data-flag-file', file || '');
svg.setAttribute('width', String(Math.round(drawW * 100) / 100));
svg.setAttribute('height', String(Math.round(drawH * 100) / 100));
svg.style.width = (Math.round(drawW * 100) / 100) + 'px';
svg.style.height = (Math.round(drawH * 100) / 100) + 'px';
return svg;
}
function inlineOneSvgFlag(img) {
var src;
var cleanSrc;
var slot;
var file;
var hasBorder;
if (!img || img.getAttribute('data-inline-svg-state')) return;
src = img.getAttribute('data-flag-src') || img.getAttribute('src') || '';
file = img.getAttribute('data-flag-file') || '';
if (!/\.svg(?:[?#]|$)/i.test(src) && !/\.svg(?:[?#]|$)/i.test(file)) return;
cleanSrc = getCleanSvgFetchUrl(src);
if (!cleanSrc) return;
img.setAttribute('data-inline-svg-state', 'loading');
slot = img.closest ? img.closest('.clbi-nations-list-flag-slot') : null;
hasBorder = img.classList.contains('has-border');
function apply(svg) {
if (!svg || !img.parentNode) return;
if (slot) {
slot.classList.add('has-inline-svg');
slot.classList.remove('is-missing');
}
img.parentNode.replaceChild(svg, img);
}
if (nationInlineSvgCache[cleanSrc]) {
if (nationInlineSvgCache[cleanSrc] !== 'failed') {
apply(nationInlineSvgCache[cleanSrc].cloneNode(true));
}
return;
}
fetch(cleanSrc, { credentials: 'same-origin', cache: 'no-store' })
.then(function (res) {
if (!res.ok) throw new Error('svg fetch failed: ' + res.status);
return res.text();
})
.then(function (text) {
var svg = buildInlineFlagSvg(text, file, hasBorder);
if (!svg) throw new Error('svg parse failed');
nationInlineSvgCache[cleanSrc] = svg.cloneNode(true);
apply(svg);
})
.catch(function () {
nationInlineSvgCache[cleanSrc] = 'failed';
img.setAttribute('data-inline-svg-state', 'failed');
});
}
function normalizeInlineSvgFlags(scope) {
var root = scope || document;
toArray(root.querySelectorAll('.clbi-nations-list-flag-slot .clbi-nations-list-flag.is-svg')).forEach(inlineOneSvgFlag);
}
function bindNationFlagFallbacks(scope) {
var root = scope || document;
toArray(root.querySelectorAll('.clbi-nations-list-flag-slot .clbi-nations-list-flag')).forEach(function (img) {
if (img.getAttribute('data-flag-missing-handler') === '1') return;
img.setAttribute('data-flag-missing-handler', '1');
img.addEventListener('error', function () {
var slot = img.closest ? img.closest('.clbi-nations-list-flag-slot') : null;
if (slot) {
slot.classList.add('is-missing');
slot.setAttribute('title', '국기 파일 로드 실패: ' + (slot.getAttribute('data-flag-file') || ''));
}
img.removeAttribute('src');
});
});
}
function renderNationItem(item, index, items) {
var flags = collectNationItemFlags(item);
var flagHtml = flags.map(renderNationFlag).join('');
var label = item.label || item.name || item.name_ko || item.page || item.external_url || '미지정';
var href = item.external_url || buildNationPageUrl(item.page || item.wiki_title || '');
var linkHtml = href ? '<a class="clbi-nations-list-link" href="' + escapeHtml(href) + '">' + escapeHtml(label) + '</a>' : '<span class="clbi-nations-list-label">' + escapeHtml(label) + '</span>';
var divider = index < items.length - 1 ? '<span class="clbi-nations-list-divider">·</span>' : '';
return '<span class="clbi-nations-list-item">' + flagHtml + linkHtml + '</span>' + divider;
}
function renderNationEmpty() {
return '<span class="clbi-nations-era-wip-message"><span class="clbi-nations-era-wip-message-ko">작업 중...</span><span class="clbi-nations-era-wip-message-en">Working in Progress...</span></span>';
}
function buildNationTabSkeletonHtml(extraClass) {
var tabs = NATION_CONTINENT_FALLBACK.map(function (continent, index) {
var active = index === 0;
return '<span class="clbi-nations-tabpanel-tab' + (active ? ' is-active' : '') + '" role="tab" tabindex="' + (active ? '0' : '-1') + '" aria-selected="' + (active ? 'true' : 'false') + '" data-continent="' + escapeHtml(continent.key) + '">' + escapeHtml(continent.label) + '</span>';
}).join('');
var bodies = NATION_CONTINENT_FALLBACK.map(function (continent, index) {
var rows = continent.regions.map(function (region) {
return '<div class="clbi-nations-tabpanel-row">' +
'<div class="clbi-nations-tabpanel-region">' + escapeHtml(region) + '</div>' +
'<div class="clbi-nations-tabpanel-well">' + renderNationEmpty() + '</div>' +
'</div>';
}).join('');
return '<div class="clbi-nations-tabpanel-continent' + (index === 0 ? ' is-active' : '') + '" data-continent-panel="' + escapeHtml(continent.key) + '"' + (index === 0 ? '' : ' hidden="hidden"') + '>' + rows + '</div>';
}).join('');
return '<div class="clbi-nations-tabpanel' + (extraClass ? ' ' + extraClass : '') + '" data-clbi-nations-simple data-nation-list-source="1">' +
'<div class="clbi-nations-tabpanel-tabs" role="tablist" aria-label="대륙 선택">' +
tabs +
'<span class="clbi-nations-tabpanel-title" aria-hidden="true">당대 존재 국가</span>' +
'</div>' +
'<div class="clbi-nations-tabpanel-body">' + bodies + '</div>' +
'</div>';
}
function buildHistoryPanelSkeletonHtml(era) {
var start = parseInt(era || '1950', 10);
var years = [];
if (!isFinite(start)) start = 1950;
for (var year = start; year < start + 10; year += 1) {
years.push(String(year));
}
var pages = years.map(function (year, index) {
return '<div class="clbi-nations-history-list clbi-nations-history-page' + (index === 0 ? ' is-active' : '') + '" data-year-panel="' + escapeHtml(year) + '"' + (index === 0 ? '' : ' hidden="hidden"') + '>' +
'<div class="clbi-nations-history-empty"><span class="clbi-nations-history-empty-ko">작업 중...</span><span class="clbi-nations-history-empty-en">Working in Progress...</span></div>' +
'</div>';
}).join('');
var tabs = years.map(function (year, index) {
return '<span class="clbi-nations-history-year-button' + (index === 0 ? ' is-active' : '') + '" role="tab" tabindex="' + (index === 0 ? '0' : '-1') + '" aria-selected="' + (index === 0 ? 'true' : 'false') + '" data-year="' + escapeHtml(year) + '">' + escapeHtml(year) + '</span>';
}).join('');
return '<div class="clbi-nations-forces-panel clbi-nations-module-panel clbi-nations-history-panel">' +
'<div class="clbi-nations-module-title">역사적 사건</div>' +
'<div class="clbi-nations-module-body clbi-nations-history-body">' +
'<div class="clbi-nations-history-pages">' + pages + '</div>' +
'<div class="clbi-nations-history-year-well"><div class="clbi-nations-history-year-list" data-decade-only="1" role="tablist" aria-label="역사적 사건 연도 선택">' + tabs + '</div></div>' +
'</div>' +
'</div>';
}
function buildEraContentSkeletonHtml(era) {
return '<div class="clbi-nations-era-content clbi-nations-era-wip" data-era-content="' + escapeHtml(era) + '" hidden="hidden">' +
buildHistoryPanelSkeletonHtml(era) +
buildNationTabSkeletonHtml('clbi-nations-era-wip-tabpanel') +
'</div>';
}
function ensureEraContentPanels(stack, eras) {
var insertAfter;
if (!stack || !eras || !eras.length) return;
insertAfter = toArray(stack.querySelectorAll('.clbi-nations-era-content[data-era-content]')).pop() || stack.querySelector('.clbi-nations-map-row');
eras.forEach(function (era) {
var selector = '.clbi-nations-era-content[data-era-content="' + String(era).replace(/"/g, '\\"') + '"]';
var wrapper;
var panel;
if (stack.querySelector(selector)) return;
wrapper = document.createElement('div');
wrapper.innerHTML = buildEraContentSkeletonHtml(era);
panel = wrapper.firstElementChild;
if (!panel) return;
if (insertAfter && insertAfter.parentNode) {
insertAfter.parentNode.insertBefore(panel, insertAfter.nextSibling);
} else {
stack.appendChild(panel);
}
insertAfter = panel;
});
}
function renderNationContinent(continent, activeKey) {
var rows = (continent.regions || []).map(function (region) {
var items = Array.isArray(region.items) ? region.items : [];
var content = items.length ? items.map(function (item, index) {
return renderNationItem(item, index, items);
}).join(' ') : renderNationEmpty();
return '' +
'<div class="clbi-nations-tabpanel-row">' +
'<div class="clbi-nations-tabpanel-region">' + escapeHtml(region.label || region.key || '') + '</div>' +
'<div class="clbi-nations-tabpanel-well">' + content + '</div>' +
'</div>';
}).join('');
return '<div class="clbi-nations-tabpanel-continent' + (continent.key === activeKey ? ' is-active' : '') + '" data-continent-panel="' + escapeHtml(continent.key) + '"' + (continent.key === activeKey ? '' : ' hidden="hidden"') + '>' + rows + '</div>';
}
function renderNationListPanel(panel, data) {
var body = panel.querySelector('.clbi-nations-tabpanel-body');
var activeTab = panel.querySelector('.clbi-nations-tabpanel-tab.is-active[data-continent], .clbi-nations-tabpanel-tab[aria-selected="true"][data-continent]');
var activeKey = activeTab ? activeTab.getAttribute('data-continent') : '';
var continents = data && Array.isArray(data.continents) ? data.continents : [];
if (!body || !continents.length) return;
if (!activeKey) activeKey = continents[0].key;
body.innerHTML = continents.map(function (continent) {
return renderNationContinent(continent, activeKey);
}).join('');
bindNationFlagFallbacks(body);
normalizeInlineSvgFlags(body);
panel.classList.add('clbi-nations-list-json-ready');
panel.setAttribute('data-nation-list-year-loaded', String(data.year || getPanelEra(panel)));
}
function isNationListPanelHidden(panel) {
var node = panel;
while (node && node !== document && node.nodeType === 1) {
if (node.hasAttribute('hidden') || node.getAttribute('aria-hidden') === 'true') return true;
node = node.parentElement;
}
return false;
}
function initNationListPanel(panel) {
var era;
if (!panel || panel.getAttribute('data-nation-list-source') !== '1') return;
if (isNationListPanelHidden(panel)) return;
if (panel.getAttribute('data-nation-list-loading') === '1') return;
if (panel.getAttribute('data-nation-list-year-loaded') === getPanelEra(panel)) return;
era = getPanelEra(panel);
purgeLegacyNationIndexFileImages(panel);
panel.setAttribute('data-nation-list-loading', '1');
Promise.all([
fetchNationList(panel, era),
fetchNationLinkMap(panel, era)
]).then(function (results) {
var data = applyNationLinkMapFlagFallback(results[0], results[1]);
return resolveNationFlagFileUrls(collectNationFlagFileNames(data)).then(function () {
renderNationListPanel(panel, data);
});
}).catch(function () {
panel.classList.add('clbi-nations-list-json-fallback');
}).finally(function () {
panel.removeAttribute('data-nation-list-loading');
});
}
function initContinentPanel(panel) {
var tabs;
var bodies;
if (!panel || panel.getAttribute('data-nations-tabpanel-ready') === '1') return;
panel.setAttribute('data-nations-tabpanel-ready', '1');
tabs = toArray(panel.querySelectorAll('.clbi-nations-tabpanel-tab[data-continent]'));
bodies = toArray(panel.querySelectorAll('.clbi-nations-tabpanel-continent[data-continent-panel]'));
function activate(continent) {
var activeTab = null;
tabs.forEach(function (tab) {
if (tab.getAttribute('data-continent') === continent) activeTab = tab;
});
if (!activeTab && tabs.length) {
activeTab = tabs[0];
continent = activeTab.getAttribute('data-continent');
}
setActiveButton(tabs, activeTab);
activatePanelsByAttribute(toArray(panel.querySelectorAll('.clbi-nations-tabpanel-continent[data-continent-panel]')), 'data-continent-panel', continent);
}
tabs.forEach(function (tab) {
tab.setAttribute('role', 'tab');
tab.setAttribute('tabindex', tab.classList.contains('is-active') ? '0' : '-1');
tab.addEventListener('click', function () {
activate(tab.getAttribute('data-continent'));
});
tab.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
activate(tab.getAttribute('data-continent'));
}
});
});
activate((panel.querySelector('.clbi-nations-tabpanel-tab.is-active[data-continent]') || tabs[0] || {}).getAttribute && (panel.querySelector('.clbi-nations-tabpanel-tab.is-active[data-continent]') || tabs[0]).getAttribute('data-continent'));
}
function initHistoryYearPanel(panel) {
var tabs;
var pages;
if (!panel || panel.getAttribute('data-nations-history-ready') === '1') return;
panel.setAttribute('data-nations-history-ready', '1');
tabs = toArray(panel.querySelectorAll('.clbi-nations-history-year-button[data-year]'));
pages = toArray(panel.querySelectorAll('.clbi-nations-history-page[data-year-panel]'));
function activate(year) {
var activeTab = null;
tabs.forEach(function (tab) {
if (tab.getAttribute('data-year') === year) activeTab = tab;
});
if (!activeTab && tabs.length) {
activeTab = tabs[0];
year = activeTab.getAttribute('data-year');
}
setActiveButton(tabs, activeTab);
activatePanelsByAttribute(pages, 'data-year-panel', year);
}
tabs.forEach(function (tab) {
tab.setAttribute('role', 'tab');
tab.setAttribute('tabindex', tab.classList.contains('is-active') ? '0' : '-1');
tab.addEventListener('click', function () {
activate(tab.getAttribute('data-year'));
});
tab.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
activate(tab.getAttribute('data-year'));
}
});
});
activate((panel.querySelector('.clbi-nations-history-year-button.is-active[data-year]') || tabs[0] || {}).getAttribute && (panel.querySelector('.clbi-nations-history-year-button.is-active[data-year]') || tabs[0]).getAttribute('data-year'));
}
function initEraStack(stack) {
var eras;
var titleNode;
var titlePlates;
var contentPanels;
var leftButton;
var rightButton;
var globe;
var currentEra;
if (!stack || stack.getAttribute('data-nations-era-controller-ready') === '1') return;
stack.setAttribute('data-nations-era-controller-ready', '1');
eras = parseEraList(stack);
ensureEraContentPanels(stack, eras);
titleNode = stack.querySelector('[data-nations-era-title]');
titlePlates = toArray(stack.querySelectorAll('.clbi-nations-era-title-plate[data-era]'));
contentPanels = toArray(stack.querySelectorAll('.clbi-nations-era-content[data-era-content]'));
leftButton = stack.querySelector('[data-nations-era-move="prev"], .clbi-nations-era-side-button-left');
rightButton = stack.querySelector('[data-nations-era-move="next"], .clbi-nations-era-side-button-right');
globe = stack.querySelector('.clbi-nations-globe-window');
currentEra = stack.getAttribute('data-nations-current-era') || stack.getAttribute('data-era') || eras[0];
function setEra(era, options) {
var index;
var prevEra;
var nextEra;
var reload;
options = options || {};
era = String(era || eras[0]);
index = getEraIndex(eras, era);
era = eras[index];
prevEra = getWrappedEra(eras, index - 1);
nextEra = getWrappedEra(eras, index + 1);
reload = !!options.reloadGlobe;
stack.setAttribute('data-nations-current-era', era);
stack.setAttribute('data-era', era);
if (titleNode) {
titleNode.textContent = era + '년';
titleNode.setAttribute('data-era', era);
}
titlePlates.forEach(function (plate) {
setHidden(plate, plate.getAttribute('data-era') !== era);
});
if (leftButton) {
leftButton.setAttribute('data-target-era', prevEra);
leftButton.setAttribute('title', prevEra + '년으로 이동');
leftButton.setAttribute('aria-label', prevEra + '년으로 이동');
}
if (rightButton) {
rightButton.setAttribute('data-target-era', nextEra);
rightButton.setAttribute('title', nextEra + '년으로 이동');
rightButton.setAttribute('aria-label', nextEra + '년으로 이동');
}
contentPanels.forEach(function (panel) {
var inactive = panel.getAttribute('data-era-content') !== era;
setHidden(panel, inactive);
if (!inactive) {
toArray(panel.querySelectorAll('.clbi-nations-tabpanel')).forEach(initNationListPanel);
}
});
applyGlobeEra(globe, era, reload);
}
function move(direction) {
var active = stack.getAttribute('data-nations-current-era') || currentEra || eras[0];
var index = getEraIndex(eras, active);
var target = getWrappedEra(eras, index + direction);
if (!target) return false;
setEra(target, { reloadGlobe: true });
return true;
}
if (leftButton) {
leftButton.addEventListener('click', function (event) {
event.preventDefault();
move(-1);
});
}
if (rightButton) {
rightButton.addEventListener('click', function (event) {
event.preventDefault();
move(1);
});
}
stack.CLBI_NationsEraController = {
eras: eras.slice(),
setEra: function (era) { setEra(era, { reloadGlobe: true }); },
move: move
};
setEra(currentEra, { reloadGlobe: false });
}
function init(root) {
var scope = root && root.querySelectorAll ? root : document;
purgeLegacyNationIndexFileImages(scope);
toArray(scope.querySelectorAll('.clbi-nations-panel-stack')).forEach(initEraStack);
toArray(scope.querySelectorAll('.clbi-nations-tabpanel')).forEach(initContinentPanel);
toArray(scope.querySelectorAll('.clbi-nations-history-panel')).forEach(initHistoryYearPanel);
toArray(scope.querySelectorAll('.clbi-nations-tabpanel')).forEach(initNationListPanel);
}
function findEraStack() {
return document.querySelector('.clbi-nations-panel-stack');
}
function moveEra(direction) {
var stack = findEraStack();
if (!stack || !stack.CLBI_NationsEraController) return false;
return stack.CLBI_NationsEraController.move(direction);
}
function setEra(era) {
var stack = findEraStack();
if (!stack || !stack.CLBI_NationsEraController) return false;
stack.CLBI_NationsEraController.setEra(era);
return true;
}
function refresh(root) {
var scope = root && root.querySelectorAll ? root : document;
nationRuntimeCacheBust = String(Date.now());
nationListCache = {};
nationLinkMapCache = {};
toArray(scope.querySelectorAll('.clbi-nations-tabpanel[data-nation-list-source="1"]')).forEach(function (panel) {
panel.removeAttribute('data-nation-list-loading');
panel.removeAttribute('data-nation-list-year-loaded');
panel.classList.remove('clbi-nations-list-json-ready', 'clbi-nations-list-json-fallback');
});
init(scope);
}
function diagnostics() {
var flags = toArray(document.querySelectorAll('.clbi-nations-list-flag-slot')).map(function (slot) {
var img = slot.querySelector('img');
return {
file: slot.getAttribute('data-flag-file') || '',
src: img ? img.getAttribute('src') : '',
missing: slot.classList.contains('is-missing')
};
});
var legacyImages = toArray(document.images).filter(function (img) {
return isLegacyNationIndexFileUrl(img.currentSrc || img.getAttribute('src') || '');
}).map(function (img) {
return img.currentSrc || img.getAttribute('src') || '';
});
var perfLegacy = (window.performance && performance.getEntriesByType ? performance.getEntriesByType('resource') : []).filter(function (entry) {
return isLegacyNationIndexFileUrl(entry && entry.name);
}).map(function (entry) { return entry.name; });
return {
build: NATIONS_PANEL_BUILD,
inlineSvgFlagCount: document.querySelectorAll('.clbi-nations-list-flag-inline-svg').length,
resolvedFileCount: Object.keys(nationResolvedFileUrlCache || {}).length,
unresolvedFiles: Object.keys(nationResolvedFileUrlCache || {}).filter(function (key) {
return nationResolvedFileUrlCache[key] === '';
}),
renderedFlagCount: flags.length,
missingRenderedFlags: flags.filter(function (flag) { return flag.missing || !flag.src; }),
redirectFallbackRenderedFlags: flags.filter(function (flag) { return /Special:Redirect\/file/i.test(flag.src || ''); }).length,
legacyImageCount: legacyImages.length,
legacyImages: legacyImages.slice(0, 40),
performanceLegacyRequests: perfLegacy.slice(-40),
sampleFlags: flags.slice(0, 20)
};
}
window.CLBI_NATIONS_PANEL = {
build: NATIONS_PANEL_BUILD,
inlineSvgFlagCount: document.querySelectorAll('.clbi-nations-list-flag-inline-svg').length,
init: init,
refresh: refresh,
moveEra: moveEra,
setEra: setEra,
diagnostics: diagnostics
};
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
init($content && $content[0] ? $content[0] : document);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { init(document); });
} else {
init(document);
}
}());