(Install package: wiki-nations-flag-border-fit-20260705 / js/NationsPanel.js) |
(Install package: wiki-nation-manager-unified-data-format-20260705 / js/NationsPanel.js) |
||
| 197번째 줄: | 197번째 줄: | ||
var nationListCache = {}; | var nationListCache = {}; | ||
var nationLinkMapCache = {}; | |||
function escapeHtml(value) { | function escapeHtml(value) { | ||
| 270번째 줄: | 271번째 줄: | ||
return (panel && panel.getAttribute('data-nation-list-url')) || replaceYearTemplate(template, era); | 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(url, { credentials: 'same-origin', cache: 'no-cache' }) | |||
.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; | |||
} | } | ||
| 354번째 줄: | 508번째 줄: | ||
var raw = String(value || '').trim(); | var raw = String(value || '').trim(); | ||
var match = raw.match(/^(\d+(?:\.\d+)?)\s*(px)?$/i); | var match = raw.match(/^(\d+(?:\.\d+)?)\s*(px)?$/i); | ||
var n = match ? parseFloat(match[1]) : | var n = match ? parseFloat(match[1]) : 16; | ||
if (!isFinite(n)) n = | if (!isFinite(n)) n = 16; | ||
if (n < | if (n < 8) n = 8; | ||
if (n > 32) n = 32; | if (n > 32) n = 32; | ||
| 402번째 줄: | 556번째 줄: | ||
function renderNationItem(item, index, items) { | function renderNationItem(item, index, items) { | ||
var flags = | var flags = collectNationItemFlags(item); | ||
var flagHtml = flags.map(renderNationFlag).join(''); | var flagHtml = flags.map(renderNationFlag).join(''); | ||
var label = item.label || item.name || item.name_ko || item.page || item.external_url || '미지정'; | var label = item.label || item.name || item.name_ko || item.page || item.external_url || '미지정'; | ||
| 566번째 줄: | 720번째 줄: | ||
panel.setAttribute('data-nation-list-loading', '1'); | panel.setAttribute('data-nation-list-loading', '1'); | ||
fetchNationList(panel, era).then(function ( | Promise.all([ | ||
renderNationListPanel(panel, data); | fetchNationList(panel, era), | ||
fetchNationLinkMap(panel, era) | |||
]).then(function (results) { | |||
var data = results[0]; | |||
var linkMapIndex = results[1]; | |||
renderNationListPanel(panel, applyNationLinkMapFlagFallback(data, linkMapIndex)); | |||
}).catch(function () { | }).catch(function () { | ||
panel.classList.add('clbi-nations-list-json-fallback'); | panel.classList.add('clbi-nations-list-json-fallback'); | ||
2026년 7월 5일 (일) 20:19 판
/* =========================================
COASTLINE: BLACK ICE - Nations Panel
국가 및 조합: 시대 전환 / 역사 연도 / 대륙 탭 제어
========================================= */
(function () {
'use strict';
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 = {};
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) {
return String(value || '')
.trim()
.replace(/^파일\s*:/i, '')
.replace(/^File\s*:/i, '')
.replace(/^이미지\s*:/i, '')
.replace(/^Image\s*:/i, '')
.replace(/^:+/, '')
.trim();
}
function buildNationFileUrl(file) {
file = normalizeNationFile(file);
return file ? '/index.php/Special:Redirect/file/' + encodeWikiTitle(file) : '';
}
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(url, { credentials: 'same-origin', cache: 'no-cache' })
.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(url, { credentials: 'same-origin', cache: 'no-cache' })
.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' : '');
return '<span class="clbi-nations-list-flag-slot" style="--flag-max-w:' + escapeHtml(width) + ';--flag-y:' + offsetY + 'px;" data-flag-file="' + escapeHtml(file) + '">' +
'<img class="' + classes + '" src="' + escapeHtml(url) + '" alt="" loading="lazy" decoding="async">' +
'</span>';
}
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);
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);
panel.setAttribute('data-nation-list-loading', '1');
Promise.all([
fetchNationList(panel, era),
fetchNationLinkMap(panel, era)
]).then(function (results) {
var data = results[0];
var linkMapIndex = results[1];
renderNationListPanel(panel, applyNationLinkMapFlagFallback(data, linkMapIndex));
}).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;
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;
}
window.CLBI_NATIONS_PANEL = {
init: init,
moveEra: moveEra,
setEra: setEra
};
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);
}
}());