편집 요약 없음 |
편집 요약 없음 |
||
| 54번째 줄: | 54번째 줄: | ||
var file = normalizeFileTitle(value); | var file = normalizeFileTitle(value); | ||
return file ? '/index.php/Special:Redirect/file/' + encodeWikiTitle(file) : ''; | return file ? '/index.php/Special:Redirect/file/' + encodeWikiTitle(file) : ''; | ||
} | |||
function fileDisplayTitle(value) { | |||
var file = normalizeFileTitle(value); | |||
return file ? '파일:' + file : ''; | |||
} | |||
function getWikiSortBucket(value) { | |||
var text = String(value || '').trim(); | |||
var first = text.charAt(0); | |||
if (/^[A-Za-z0-9]/.test(first)) return 0; | |||
if (/^[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(first)) return 1; | |||
return 2; | |||
} | |||
function compareWikiLabels(a, b) { | |||
var av = String(a == null ? '' : a); | |||
var bv = String(b == null ? '' : b); | |||
var ab = getWikiSortBucket(av); | |||
var bb = getWikiSortBucket(bv); | |||
if (ab !== bb) return ab - bb; | |||
return av.localeCompare(bv, ['en', 'ko'], { numeric: true, sensitivity: 'base' }); | |||
} | |||
function normalizeFileKey(value) { | |||
return normalizeFileTitle(value).replace(/_/g, ' ').trim().toLowerCase(); | |||
} | |||
function collapseDuplicateFileExtension(value) { | |||
var file = normalizeFileTitle(value); | |||
return file.replace(/(\.(?:svg|png|jpe?g|gif|webp|avif|ico|pdf|geojson|json))\1$/i, '$1'); | |||
} | |||
function cleanupFlagFileSearchList(list) { | |||
var fileSet = {}; | |||
var seen = {}; | |||
var cleaned = []; | |||
(list || []).forEach(function (item) { | |||
var key = normalizeFileKey(item && item.file); | |||
if (key) fileSet[key] = true; | |||
}); | |||
(list || []).forEach(function (item) { | |||
var file = normalizeFileTitle(item && item.file); | |||
var key = normalizeFileKey(file); | |||
var collapsed = collapseDuplicateFileExtension(file); | |||
var collapsedKey = normalizeFileKey(collapsed); | |||
if (!file || !key) return; | |||
if (item && item.isRedirect) return; | |||
if (collapsedKey && collapsedKey !== key && fileSet[collapsedKey]) return; | |||
if (seen[key]) return; | |||
seen[key] = true; | |||
cleaned.push(item); | |||
}); | |||
return cleaned.sort(function (a, b) { | |||
return compareWikiLabels(a.file, b.file); | |||
}); | |||
} | } | ||
| 231번째 줄: | 295번째 줄: | ||
this.loading = false; | this.loading = false; | ||
this.dragSource = null; | this.dragSource = null; | ||
this.flagSearchKeyword = root.getAttribute('data-flag-search-keyword') || 'flag'; | |||
this.flagSearchLimit = Math.max(10, Math.min(200, parseInt(root.getAttribute('data-flag-search-limit') || '80', 10) || 80)); | |||
this.flagFiles = null; | |||
this.flagFilesLoading = false; | |||
this.flagPicker = null; | |||
this.activeFlagInput = null; | |||
} | } | ||
| 299번째 줄: | 369번째 줄: | ||
.catch(function () { | .catch(function () { | ||
self.mapPages = null; | self.mapPages = null; | ||
}); | |||
}; | |||
NationListManager.prototype.loadFlagFiles = function () { | |||
var self = this; | |||
var apiUrl; | |||
if (this.flagFiles) return Promise.resolve(this.flagFiles); | |||
if (this.flagFilesLoading) { | |||
return new Promise(function (resolve) { | |||
var timer = setInterval(function () { | |||
if (!self.flagFilesLoading) { | |||
clearInterval(timer); | |||
resolve(self.flagFiles || []); | |||
} | |||
}, 80); | |||
}); | |||
} | |||
this.flagFilesLoading = true; | |||
apiUrl = (window.mw && mw.util && mw.util.wikiScript) ? mw.util.wikiScript('api') : '/api.php'; | |||
return fetch(apiUrl + '?' + new URLSearchParams({ | |||
action: 'query', | |||
generator: 'search', | |||
gsrsearch: this.flagSearchKeyword, | |||
gsrnamespace: '6', | |||
gsrlimit: String(this.flagSearchLimit), | |||
prop: 'info|imageinfo', | |||
redirects: '1', | |||
iiprop: 'url|mime', | |||
iiurlwidth: '32', | |||
format: 'json' | |||
}).toString(), { credentials: 'same-origin', cache: 'no-cache' }) | |||
.then(function (res) { | |||
if (!res.ok) throw new Error('HTTP ' + res.status); | |||
return res.json(); | |||
}) | |||
.then(function (data) { | |||
var pages = data && data.query && data.query.pages ? data.query.pages : {}; | |||
var list = Object.keys(pages).map(function (id) { | |||
var page = pages[id]; | |||
var info = page.imageinfo && page.imageinfo[0] ? page.imageinfo[0] : {}; | |||
var file = normalizeFileTitle(page.title || ''); | |||
return { | |||
title: page.title || fileDisplayTitle(file), | |||
file: file, | |||
thumb: info.thumburl || info.url || buildFileRedirectUrl(file), | |||
url: info.url || buildFileRedirectUrl(file), | |||
isRedirect: !!page.redirect | |||
}; | |||
}).filter(function (item) { | |||
return item.file && item.file.toLowerCase().indexOf(self.flagSearchKeyword.toLowerCase()) !== -1; | |||
}); | |||
list = cleanupFlagFileSearchList(list); | |||
self.flagFiles = list; | |||
return list; | |||
}) | |||
.catch(function (err) { | |||
if (window.console && console.warn) console.warn('nation list flag file search failed:', err); | |||
self.flagFiles = []; | |||
return []; | |||
}) | |||
.finally(function () { | |||
self.flagFilesLoading = false; | |||
}); | }); | ||
}; | }; | ||
| 542번째 줄: | 679번째 줄: | ||
}).join('') + '</div>' + | }).join('') + '</div>' + | ||
'<div class="nation-list-body">' + (active ? this.renderContinent(active) : '') + '</div>' + | '<div class="nation-list-body">' + (active ? this.renderContinent(active) : '') + '</div>' + | ||
'<div class="nation-list-flag-picker" hidden></div>' + | |||
'</div>'; | '</div>'; | ||
| 619번째 줄: | 757번째 줄: | ||
'<td><input type="text" data-field="link" value="' + escapeHtml(linkValue) + '" placeholder="문서명 또는 https://..."></td>' + | '<td><input type="text" data-field="link" value="' + escapeHtml(linkValue) + '" placeholder="문서명 또는 https://..."></td>' + | ||
'<td><input type="text" data-field="label" value="' + escapeHtml(item.label) + '" placeholder="표시명"></td>' + | '<td><input type="text" data-field="label" value="' + escapeHtml(item.label) + '" placeholder="표시명"></td>' + | ||
'<td><div class="nation-list-flag-cell">' + (flagUrl ? '<img src="' + escapeHtml(flagUrl) + '" alt="">' : '<span></span>') + '<input type="text" data-field="flag" value="' + escapeHtml(flag.file) + '" placeholder="파일명.svg"></div></td>' + | '<td><div class="nation-list-flag-cell">' + (flagUrl ? '<img src="' + escapeHtml(flagUrl) + '" alt="">' : '<span></span>') + '<input type="text" data-field="flag" class="nation-list-flag-input" value="' + escapeHtml(flag.file) + '" placeholder="파일명.svg"></div></td>' + | ||
'<td class="nation-list-small-col"><input type="text" data-field="width" value="' + escapeHtml(flag.width) + '"></td>' + | '<td class="nation-list-small-col"><input type="text" data-field="width" value="' + escapeHtml(flag.width) + '"></td>' + | ||
'<td class="nation-list-check-col"><input type="checkbox" data-field="border"' + (flag.border !== false ? ' checked' : '') + '></td>' + | '<td class="nation-list-check-col"><input type="checkbox" data-field="border"' + (flag.border !== false ? ' checked' : '') + '></td>' + | ||
| 676번째 줄: | 814번째 줄: | ||
input.addEventListener('input', function () { | input.addEventListener('input', function () { | ||
self.updateRow(tr, input); | self.updateRow(tr, input); | ||
if (input.getAttribute('data-field') === 'flag') self.showFlagPicker(input); | |||
}); | }); | ||
input.addEventListener('change', function () { | input.addEventListener('change', function () { | ||
| 681번째 줄: | 820번째 줄: | ||
if (input.getAttribute('data-field') === 'flag') self.render(); | if (input.getAttribute('data-field') === 'flag') self.render(); | ||
}); | }); | ||
if (input.getAttribute('data-field') === 'flag') { | |||
input.addEventListener('focus', function () { self.showFlagPicker(input); }); | |||
input.addEventListener('keydown', function (event) { | |||
if (event.key === 'Escape') self.hideFlagPicker(); | |||
}); | |||
} | |||
}); | }); | ||
| 720번째 줄: | 865번째 줄: | ||
self.dropOnRegion(tbody, event); | self.dropOnRegion(tbody, event); | ||
}); | }); | ||
}); | |||
document.addEventListener('pointerdown', function onDocPointerDown(event) { | |||
if (!self.root.contains(event.target)) { | |||
self.hideFlagPicker(); | |||
document.removeEventListener('pointerdown', onDocPointerDown); | |||
} | |||
}); | }); | ||
}; | }; | ||
| 777번째 줄: | 929번째 줄: | ||
this.setStatus('수정됨'); | this.setStatus('수정됨'); | ||
}; | |||
NationListManager.prototype.showFlagPicker = function (input) { | |||
var self = this; | |||
var picker = this.root.querySelector('.nation-list-flag-picker'); | |||
var query = normalizeFileTitle(input.value).toLowerCase(); | |||
var rect; | |||
var rootRect; | |||
if (!picker) return; | |||
this.activeFlagInput = input; | |||
this.flagPicker = picker; | |||
picker.hidden = false; | |||
picker.innerHTML = '<div class="nation-list-flag-picker-loading">flag 파일 검색 중...</div>'; | |||
rect = input.getBoundingClientRect(); | |||
rootRect = this.root.getBoundingClientRect(); | |||
picker.style.left = Math.max(0, rect.left - rootRect.left) + 'px'; | |||
picker.style.top = Math.max(0, rect.bottom - rootRect.top + 2) + 'px'; | |||
picker.style.width = Math.max(280, rect.width + 44) + 'px'; | |||
this.loadFlagFiles().then(function (list) { | |||
var filtered = list.filter(function (item) { | |||
return !query || item.file.toLowerCase().indexOf(query) !== -1; | |||
}).slice(0, 48); | |||
if (!filtered.length) { | |||
picker.innerHTML = '<div class="nation-list-flag-picker-empty">flag 파일 없음 · 직접 입력 가능</div>'; | |||
return; | |||
} | |||
picker.innerHTML = filtered.map(function (item) { | |||
return '' + | |||
'<button type="button" class="nation-list-flag-option" data-file="' + escapeHtml(item.file) + '">' + | |||
'<img src="' + escapeHtml(item.thumb || buildFileRedirectUrl(item.file)) + '" alt="">' + | |||
'<span>' + escapeHtml(item.file) + '</span>' + | |||
'</button>'; | |||
}).join(''); | |||
picker.querySelectorAll('.nation-list-flag-option').forEach(function (btn) { | |||
btn.addEventListener('click', function () { | |||
var file = btn.getAttribute('data-file') || ''; | |||
var tr; | |||
if (!self.activeFlagInput) return; | |||
self.activeFlagInput.value = file; | |||
tr = self.activeFlagInput.closest('tr'); | |||
if (tr) self.updateRow(tr, self.activeFlagInput); | |||
self.setStatus('수정됨'); | |||
self.hideFlagPicker(); | |||
self.render(); | |||
}); | |||
}); | |||
}); | |||
}; | |||
NationListManager.prototype.hideFlagPicker = function () { | |||
var picker = this.flagPicker || this.root.querySelector('.nation-list-flag-picker'); | |||
if (picker) { | |||
picker.hidden = true; | |||
picker.innerHTML = ''; | |||
} | |||
this.activeFlagInput = null; | |||
}; | }; | ||
2026년 7월 5일 (일) 07:09 판
(function () {
'use strict';
var DEFAULT_YEARS = [
'1950', '1960', '1970', '1980', '1990', '2000', '2010', '2020',
'2030', '2040', '2050', '2060', '2070', '2080', '2090', '2100'
];
var CONTINENT_TEMPLATE = [
{ key: 'america', label: '아메리카', regions: ['북아메리카', '남아메리카'] },
{ key: 'europe', label: '유럽', regions: ['북유럽', '서유럽', '중앙유럽', '동유럽', '동남유럽', '남유럽', '남서유럽'] },
{ key: 'africa', label: '아프리카', regions: ['북아프리카', '서아프리카', '중앙아프리카', '동아프리카', '남아프리카'] },
{ key: 'asia', label: '아시아', regions: ['서아시아', '중앙아시아', '북아시아', '남아시아', '동아시아', '동남아시아'] },
{ key: 'oceania', label: '오세아니아', regions: ['오세아니아'] },
{ key: 'antarctica', label: '남극', regions: ['남극'] }
];
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 replaceYearTemplate(template, year) {
return String(template || '').replace(/\{year\}/g, String(year || ''));
}
function normalizeFileTitle(value) {
return String(value || '')
.trim()
.replace(/^\[\[:?\s*/i, '')
.replace(/\]\]$/i, '')
.replace(/^파일\s*:/i, '')
.replace(/^File\s*:/i, '')
.replace(/^이미지\s*:/i, '')
.replace(/^Image\s*:/i, '')
.replace(/^:+/, '')
.trim();
}
function buildFileRedirectUrl(value) {
var file = normalizeFileTitle(value);
return file ? '/index.php/Special:Redirect/file/' + encodeWikiTitle(file) : '';
}
function fileDisplayTitle(value) {
var file = normalizeFileTitle(value);
return file ? '파일:' + file : '';
}
function getWikiSortBucket(value) {
var text = String(value || '').trim();
var first = text.charAt(0);
if (/^[A-Za-z0-9]/.test(first)) return 0;
if (/^[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(first)) return 1;
return 2;
}
function compareWikiLabels(a, b) {
var av = String(a == null ? '' : a);
var bv = String(b == null ? '' : b);
var ab = getWikiSortBucket(av);
var bb = getWikiSortBucket(bv);
if (ab !== bb) return ab - bb;
return av.localeCompare(bv, ['en', 'ko'], { numeric: true, sensitivity: 'base' });
}
function normalizeFileKey(value) {
return normalizeFileTitle(value).replace(/_/g, ' ').trim().toLowerCase();
}
function collapseDuplicateFileExtension(value) {
var file = normalizeFileTitle(value);
return file.replace(/(\.(?:svg|png|jpe?g|gif|webp|avif|ico|pdf|geojson|json))\1$/i, '$1');
}
function cleanupFlagFileSearchList(list) {
var fileSet = {};
var seen = {};
var cleaned = [];
(list || []).forEach(function (item) {
var key = normalizeFileKey(item && item.file);
if (key) fileSet[key] = true;
});
(list || []).forEach(function (item) {
var file = normalizeFileTitle(item && item.file);
var key = normalizeFileKey(file);
var collapsed = collapseDuplicateFileExtension(file);
var collapsedKey = normalizeFileKey(collapsed);
if (!file || !key) return;
if (item && item.isRedirect) return;
if (collapsedKey && collapsedKey !== key && fileSet[collapsedKey]) return;
if (seen[key]) return;
seen[key] = true;
cleaned.push(item);
});
return cleaned.sort(function (a, b) {
return compareWikiLabels(a.file, b.file);
});
}
function clampNumber(value, min, max) {
value = parseFloat(value);
if (!isFinite(value)) value = 0;
return Math.max(min, Math.min(max, value));
}
function normalizeOffsetMode(value) {
value = String(value == null ? '' : value).toLowerCase().trim();
return value === 'manual' ? 'manual' : 'auto';
}
function getFlagOffsetY(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;
return Math.round(clampNumber(value, -3, 3));
}
function getFlagOffsetMode(flag) {
if (!flag || typeof flag !== 'object') return 'auto';
if (flag.offsetMode || flag.offset_mode) return normalizeOffsetMode(flag.offsetMode || flag.offset_mode);
if (flag.decorator === 'top') return 'manual';
return 'auto';
}
function normalizeRegionKey(label) {
return String(label || '')
.trim()
.replace(/\s+/g, '_')
.replace(/[^A-Za-z0-9가-힣_\-]/g, '') || 'region';
}
function makeEmptyState(year) {
return {
version: 1,
year: parseInt(year || '1950', 10),
description: '국가 및 조합 ' + year + '년 당대 존재 국가 패널 데이터. Manage:Nation_List에서 편집한다.',
continents: CONTINENT_TEMPLATE.map(function (continent) {
return {
key: continent.key,
label: continent.label,
regions: continent.regions.map(function (region) {
return {
key: normalizeRegionKey(region),
label: region,
items: []
};
})
};
})
};
}
function normalizeFlag(flag) {
var normalized;
if (!flag || typeof flag !== 'object') flag = {};
normalized = {
file: normalizeFileTitle(flag.file || flag.flag_file || flag.flag || ''),
width: String(flag.width || flag.flag_width || '16px').trim() || '16px',
border: flag.border !== false,
offsetMode: getFlagOffsetMode(flag),
offsetY: getFlagOffsetY(flag)
};
return normalized;
}
function normalizeItem(item) {
var flags;
item = item && typeof item === 'object' ? item : {};
flags = Array.isArray(item.flags) ? item.flags.map(normalizeFlag) : [];
if (!flags.length && (item.flag_file || item.flag)) {
flags.push(normalizeFlag({ file: item.flag_file || item.flag, width: item.flag_width || '16px', border: item.border !== false, decorator: item.decorator || '' }));
}
return {
page: String(item.page || item.wiki_title || '').trim(),
label: String(item.label || item.name || item.name_ko || item.page || '').trim(),
external_url: String(item.external_url || item.url || '').trim(),
flags: flags,
note: String(item.note || '').trim(),
enabled: item.enabled !== false
};
}
function normalizeState(payload, year) {
var state = makeEmptyState(year);
var byKey = {};
if (!payload || typeof payload !== 'object') return state;
if (Array.isArray(payload.continents)) {
payload.continents.forEach(function (continent) {
if (continent && continent.key) byKey[continent.key] = continent;
});
}
state.version = payload.version || 1;
state.year = payload.year || state.year;
state.description = payload.description || state.description;
state.continents = state.continents.map(function (baseContinent) {
var source = byKey[baseContinent.key] || {};
var regionsByKey = {};
if (Array.isArray(source.regions)) {
source.regions.forEach(function (region) {
if (!region) return;
if (region.key) regionsByKey[region.key] = region;
if (region.label) regionsByKey[normalizeRegionKey(region.label)] = region;
});
}
return {
key: baseContinent.key,
label: source.label || baseContinent.label,
regions: baseContinent.regions.map(function (baseRegion) {
var region = regionsByKey[baseRegion.key] || regionsByKey[normalizeRegionKey(baseRegion.label)] || {};
return {
key: region.key || baseRegion.key,
label: region.label || baseRegion.label,
items: Array.isArray(region.items) ? region.items.map(normalizeItem) : []
};
})
};
});
return state;
}
function getItemPrimaryFlag(item) {
if (!item.flags || !item.flags.length) item.flags = [normalizeFlag({})];
return item.flags[0];
}
function normalizeMapPages(payload) {
var items = payload && payload.items ? payload.items : {};
var pages = {};
Object.keys(items).forEach(function (key) {
var entry = items[key] || {};
var page = String(entry.page || key || '').trim();
if (page) pages[page] = true;
});
return pages;
}
function NationListManager(root) {
this.root = root;
this.years = String(root.getAttribute('data-years') || DEFAULT_YEARS.join(' ')).split(/[\s,|]+/).filter(Boolean);
this.year = root.getAttribute('data-year') || this.years[0] || '1950';
this.titleTemplate = root.getAttribute('data-list-title-template') || 'MediaWiki:{year}_Nation_List.json';
this.urlTemplate = root.getAttribute('data-list-url-template') || '/index.php?title=MediaWiki:{year}_Nation_List.json&action=raw&ctype=application/json';
this.mapTitleTemplate = root.getAttribute('data-map-title-template') || 'MediaWiki:{year}_Nation_Link_Map.json';
this.mapUrlTemplate = root.getAttribute('data-map-url-template') || '/index.php?title=MediaWiki:{year}_Nation_Link_Map.json&action=raw&ctype=application/json';
this.baseUrl = root.getAttribute('data-link-base') || '/index.php/';
this.state = null;
this.mapPages = null;
this.activeContinent = 'america';
this.query = '';
this.status = '';
this.sourceExists = false;
this.loading = false;
this.dragSource = null;
this.flagSearchKeyword = root.getAttribute('data-flag-search-keyword') || 'flag';
this.flagSearchLimit = Math.max(10, Math.min(200, parseInt(root.getAttribute('data-flag-search-limit') || '80', 10) || 80));
this.flagFiles = null;
this.flagFilesLoading = false;
this.flagPicker = null;
this.activeFlagInput = null;
}
NationListManager.prototype.getListTitle = function () {
return replaceYearTemplate(this.titleTemplate, this.year);
};
NationListManager.prototype.getListUrl = function () {
return replaceYearTemplate(this.urlTemplate, this.year);
};
NationListManager.prototype.getMapUrl = function () {
return replaceYearTemplate(this.mapUrlTemplate, this.year);
};
NationListManager.prototype.init = function () {
this.root.classList.add('nation-list-manager-ready');
this.loadYear(this.year);
};
NationListManager.prototype.loadYear = function (year) {
var self = this;
this.year = String(year || this.year || '1950');
this.loading = true;
this.status = this.year + '년 데이터 불러오는 중...';
this.renderShell();
return fetch(this.getListUrl(), { 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');
self.state = normalizeState(JSON.parse(trimmed), self.year);
self.sourceExists = true;
self.status = self.year + '년 목록 로드 완료';
})
.catch(function () {
self.state = makeEmptyState(self.year);
self.sourceExists = false;
self.status = self.year + '년 목록 문서가 없어 빈 틀로 시작합니다.';
})
.then(function () {
return self.loadMapWarnings();
})
.finally(function () {
self.loading = false;
self.render();
});
};
NationListManager.prototype.loadMapWarnings = function () {
var self = this;
this.mapPages = null;
return fetch(this.getMapUrl(), { 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();
self.mapPages = trimmed ? normalizeMapPages(JSON.parse(trimmed)) : {};
})
.catch(function () {
self.mapPages = null;
});
};
NationListManager.prototype.loadFlagFiles = function () {
var self = this;
var apiUrl;
if (this.flagFiles) return Promise.resolve(this.flagFiles);
if (this.flagFilesLoading) {
return new Promise(function (resolve) {
var timer = setInterval(function () {
if (!self.flagFilesLoading) {
clearInterval(timer);
resolve(self.flagFiles || []);
}
}, 80);
});
}
this.flagFilesLoading = true;
apiUrl = (window.mw && mw.util && mw.util.wikiScript) ? mw.util.wikiScript('api') : '/api.php';
return fetch(apiUrl + '?' + new URLSearchParams({
action: 'query',
generator: 'search',
gsrsearch: this.flagSearchKeyword,
gsrnamespace: '6',
gsrlimit: String(this.flagSearchLimit),
prop: 'info|imageinfo',
redirects: '1',
iiprop: 'url|mime',
iiurlwidth: '32',
format: 'json'
}).toString(), { credentials: 'same-origin', cache: 'no-cache' })
.then(function (res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function (data) {
var pages = data && data.query && data.query.pages ? data.query.pages : {};
var list = Object.keys(pages).map(function (id) {
var page = pages[id];
var info = page.imageinfo && page.imageinfo[0] ? page.imageinfo[0] : {};
var file = normalizeFileTitle(page.title || '');
return {
title: page.title || fileDisplayTitle(file),
file: file,
thumb: info.thumburl || info.url || buildFileRedirectUrl(file),
url: info.url || buildFileRedirectUrl(file),
isRedirect: !!page.redirect
};
}).filter(function (item) {
return item.file && item.file.toLowerCase().indexOf(self.flagSearchKeyword.toLowerCase()) !== -1;
});
list = cleanupFlagFileSearchList(list);
self.flagFiles = list;
return list;
})
.catch(function (err) {
if (window.console && console.warn) console.warn('nation list flag file search failed:', err);
self.flagFiles = [];
return [];
})
.finally(function () {
self.flagFilesLoading = false;
});
};
NationListManager.prototype.getActiveContinent = function () {
var continents = this.state && this.state.continents ? this.state.continents : [];
var active = continents.find(function (continent) { return continent.key === this.activeContinent; }, this);
return active || continents[0] || null;
};
NationListManager.prototype.getAllItems = function () {
var rows = [];
if (!this.state || !Array.isArray(this.state.continents)) return rows;
this.state.continents.forEach(function (continent) {
(continent.regions || []).forEach(function (region) {
(region.items || []).forEach(function (item) {
rows.push({ continent: continent, region: region, item: item });
});
});
});
return rows;
};
NationListManager.prototype.getWarnings = function () {
var mapPages = this.mapPages;
var listPages = {};
var missingInMap = [];
var missingInList = [];
if (mapPages === null) {
return [{ type: 'soft', text: this.year + '년 링크맵을 불러오지 못했습니다. 링크맵 문서가 없으면 이 경고는 정상입니다.' }];
}
this.getAllItems().forEach(function (row) {
var page = String(row.item.page || '').trim();
if (!page || row.item.enabled === false) return;
listPages[page] = true;
if (!mapPages[page]) missingInMap.push(page);
});
Object.keys(mapPages).forEach(function (page) {
if (!listPages[page]) missingInList.push(page);
});
if (!missingInMap.length && !missingInList.length) {
return [{ type: 'ok', text: '국가 목록과 지구본 링크맵의 page 값이 맞습니다.' }];
}
if (missingInMap.length) {
missingInList = missingInList.slice(0, 80);
}
return [
missingInMap.length ? { type: 'warn', text: '목록에는 있으나 링크맵에 없는 page: ' + missingInMap.slice(0, 80).join(', ') + (missingInMap.length > 80 ? ' 외 ' + (missingInMap.length - 80) + '개' : '') } : null,
missingInList.length ? { type: 'warn', text: '링크맵에는 있으나 목록에 없는 page: ' + missingInList.slice(0, 80).join(', ') + (missingInList.length > 80 ? ' 외 ' + (missingInList.length - 80) + '개' : '') } : null
].filter(Boolean);
};
NationListManager.prototype.toText = function () {
var out = normalizeState(this.state, this.year);
out.year = parseInt(this.year, 10);
out.description = this.state.description || ('국가 및 조합 ' + this.year + '년 당대 존재 국가 패널 데이터. Manage:Nation_List에서 편집한다.');
return JSON.stringify(out, null, 2);
};
NationListManager.prototype.save = function () {
var self = this;
var title = this.getListTitle();
if (!window.mw || !mw.Api) {
this.setStatus('저장 실패: mw.Api를 사용할 수 없습니다.');
return;
}
this.setStatus('국기 보정값 계산 중...');
this.computeFlagOffsetsForSave().then(function () {
self.setStatus(title + ' 저장 중...');
new mw.Api().postWithToken('csrf', {
action: 'edit',
title: title,
text: self.toText(),
summary: 'Update ' + self.year + ' nation list',
format: 'json'
}).done(function () {
self.sourceExists = true;
self.setStatus(title + ' 저장 완료');
self.loadMapWarnings().then(function () { self.render(); });
}).fail(function (code, data) {
var message = data && data.error && data.error.info ? data.error.info : code;
self.setStatus('저장 실패: ' + message);
});
}).catch(function (err) {
self.setStatus('국기 보정 계산 실패: ' + (err && err.message ? err.message : err));
});
};
NationListManager.prototype.computeFlagOffsetsForSave = function () {
var self = this;
var rows = [];
var fontsReady;
this.getAllItems().forEach(function (row) {
var item = row.item || {};
var label = item.label || item.page || item.external_url || '';
var flags = Array.isArray(item.flags) ? item.flags : [];
flags = flags.map(normalizeFlag);
flags.forEach(function (flag) {
if (!flag.file) return;
if (flag.offsetMode === 'manual') {
flag.offsetY = getFlagOffsetY(flag);
return;
}
rows.push({ flag: flag, label: label });
});
item.flags = flags;
});
if (!rows.length) return Promise.resolve();
fontsReady = document.fonts && document.fonts.ready ? document.fonts.ready.catch(function () {}) : Promise.resolve();
return fontsReady.then(function () {
return self.measureFlagOffsets(rows);
});
};
NationListManager.prototype.measureFlagOffsets = function (rows) {
var mount = document.createElement('div');
var waiters = [];
var timeout;
mount.className = 'nation-list-measure-stage';
mount.setAttribute('aria-hidden', 'true');
document.body.appendChild(mount);
rows.forEach(function (row, index) {
var item = document.createElement('span');
var img = document.createElement('img');
var label = document.createElement('span');
var width = String(row.flag.width || '16px').trim() || '16px';
item.className = 'clbi-nations-list-item nation-list-measure-item';
item.setAttribute('data-measure-index', String(index));
img.className = 'clbi-nations-list-flag' + (row.flag.border !== false ? ' has-border' : '');
img.alt = '';
img.src = buildFileRedirectUrl(row.flag.file);
img.style.width = width;
img.style.setProperty('--flag-y', '0px');
label.className = 'clbi-nations-list-label nation-list-measure-label';
label.textContent = row.label || '미지정';
item.appendChild(img);
item.appendChild(label);
mount.appendChild(item);
if (!img.complete) {
waiters.push(new Promise(function (resolve) {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
}));
}
});
timeout = new Promise(function (resolve) {
window.setTimeout(resolve, 2200);
});
return Promise.race([Promise.all(waiters), timeout]).then(function () {
rows.forEach(function (row, index) {
var item = mount.querySelector('[data-measure-index="' + index + '"]');
var img = item ? item.querySelector('.clbi-nations-list-flag') : null;
var label = item ? item.querySelector('.nation-list-measure-label') : null;
var imgRect;
var labelRect;
var offset = 0;
if (img && label) {
imgRect = img.getBoundingClientRect();
labelRect = label.getBoundingClientRect();
if (imgRect.height && labelRect.height) {
offset = Math.round(((labelRect.top + labelRect.height / 2) - (imgRect.top + imgRect.height / 2)) * 10) / 10;
}
}
row.flag.offsetMode = 'auto';
row.flag.offsetY = Math.round(clampNumber(offset, -3, 3));
});
}).finally(function () {
mount.remove();
});
};
NationListManager.prototype.setStatus = function (text) {
this.status = text || '';
var el = this.root.querySelector('.nation-list-status');
if (el) el.textContent = this.status;
};
NationListManager.prototype.renderShell = function () {
this.root.innerHTML = '<div class="nation-list-loading">' + escapeHtml(this.status || 'loading...') + '</div>';
};
NationListManager.prototype.render = function () {
var state = this.state || makeEmptyState(this.year);
var active = this.getActiveContinent();
var allItems = this.getAllItems();
var enabledCount = allItems.filter(function (row) { return row.item.enabled !== false; }).length;
var warnings = this.getWarnings();
this.root.innerHTML = '' +
'<div class="nation-list-shell">' +
'<div class="nation-list-head">' +
'<div class="nation-list-title">Nation List Manager</div>' +
'<div class="nation-list-summary">' + escapeHtml(this.year) + '년 · 전체 ' + allItems.length + ' · 사용 ' + enabledCount + ' · ' + (this.sourceExists ? '문서 있음' : '새 문서') + '</div>' +
'</div>' +
'<div class="nation-list-toolbar">' +
'<label>연도 <select class="nation-list-year-select">' + this.years.map(function (year) {
return '<option value="' + escapeHtml(year) + '"' + (year === this.year ? ' selected' : '') + '>' + escapeHtml(year) + '</option>';
}, this).join('') + '</select></label>' +
'<input type="search" class="nation-list-search" placeholder="문서명, 표시명, 국기 검색" value="' + escapeHtml(this.query) + '">' +
'<button type="button" class="nation-list-reload">다시 불러오기</button>' +
'<button type="button" class="nation-list-save">저장</button>' +
'<span class="nation-list-status">' + escapeHtml(this.status) + '</span>' +
'</div>' +
'<div class="nation-list-warning-box">' + warnings.map(function (warning) {
return '<div class="nation-list-warning is-' + escapeHtml(warning.type) + '">' + escapeHtml(warning.text) + '</div>';
}).join('') + '</div>' +
'<div class="nation-list-continent-tabs">' + state.continents.map(function (continent) {
return '<button type="button" class="nation-list-continent-tab' + (active && active.key === continent.key ? ' is-active' : '') + '" data-continent="' + escapeHtml(continent.key) + '">' + escapeHtml(continent.label) + '</button>';
}).join('') + '</div>' +
'<div class="nation-list-body">' + (active ? this.renderContinent(active) : '') + '</div>' +
'<div class="nation-list-flag-picker" hidden></div>' +
'</div>';
this.bind();
};
NationListManager.prototype.renderContinent = function (continent) {
return (continent.regions || []).map(function (region) {
return this.renderRegion(continent, region);
}, this).join('');
};
NationListManager.prototype.renderRegion = function (continent, region) {
var items = (region.items || []).filter(function (item) {
var q = this.query.trim().toLowerCase();
var flag = getItemPrimaryFlag(item);
var haystack = [item.page, item.label, item.external_url, flag.file, item.note].join(' ').toLowerCase();
return !q || haystack.indexOf(q) !== -1;
}, this);
return '' +
'<section class="nation-list-region" data-continent="' + escapeHtml(continent.key) + '" data-region="' + escapeHtml(region.key) + '">' +
'<div class="nation-list-region-head">' +
'<div><span class="nation-list-region-title">' + escapeHtml(region.label) + '</span><span class="nation-list-region-count">' + (region.items || []).length + '개</span></div>' +
'<button type="button" class="nation-list-add" data-continent="' + escapeHtml(continent.key) + '" data-region="' + escapeHtml(region.key) + '">항목 추가</button>' +
'</div>' +
'<div class="nation-list-table-wrap">' +
'<table class="nation-list-table">' +
'<thead><tr>' +
'<th class="nation-list-drag-col">이동</th>' +
'<th>문서명 / 외부 URL</th>' +
'<th>표시명</th>' +
'<th>국기 파일</th>' +
'<th class="nation-list-small-col">크기</th>' +
'<th class="nation-list-check-col">테두리</th>' +
'<th class="nation-list-offset-col">국기 보정</th>' +
'<th>메모</th>' +
'<th class="nation-list-check-col">사용</th>' +
'<th class="nation-list-action-col">작업</th>' +
'</tr></thead>' +
'<tbody data-continent="' + escapeHtml(continent.key) + '" data-region="' + escapeHtml(region.key) + '">' + (items.length ? items.map(function (item) {
return this.renderItemRow(continent, region, item, region.items.indexOf(item));
}, this).join('') : '<tr><td colspan="10" class="nation-list-empty">이 지역에는 항목이 없습니다.</td></tr>') + '</tbody>' +
'</table>' +
'</div>' +
'</section>';
};
NationListManager.prototype.renderOffsetSelect = function (flag) {
var mode = getFlagOffsetMode(flag);
var offset = getFlagOffsetY(flag);
var values = [
{ value: 'auto', label: '자동' },
{ value: '-3', label: '-3px' },
{ value: '-2', label: '-2px' },
{ value: '-1', label: '-1px' },
{ value: '0', label: '0px' },
{ value: '1', label: '+1px' },
{ value: '2', label: '+2px' },
{ value: '3', label: '+3px' }
];
var selected = mode === 'manual' ? String(offset) : 'auto';
return '<select data-field="offset">' + values.map(function (item) {
return '<option value="' + escapeHtml(item.value) + '"' + (item.value === selected ? ' selected' : '') + '>' + escapeHtml(item.label) + '</option>';
}).join('') + '</select>';
};
NationListManager.prototype.renderItemRow = function (continent, region, item, index) {
var flag = getItemPrimaryFlag(item);
var flagUrl = buildFileRedirectUrl(flag.file);
var linkValue = item.external_url ? item.external_url : item.page;
return '' +
'<tr draggable="true" data-continent="' + escapeHtml(continent.key) + '" data-region="' + escapeHtml(region.key) + '" data-index="' + index + '">' +
'<td class="nation-list-drag-col"><span class="nation-list-drag-handle" title="드래그해서 순서 변경">↕</span></td>' +
'<td><input type="text" data-field="link" value="' + escapeHtml(linkValue) + '" placeholder="문서명 또는 https://..."></td>' +
'<td><input type="text" data-field="label" value="' + escapeHtml(item.label) + '" placeholder="표시명"></td>' +
'<td><div class="nation-list-flag-cell">' + (flagUrl ? '<img src="' + escapeHtml(flagUrl) + '" alt="">' : '<span></span>') + '<input type="text" data-field="flag" class="nation-list-flag-input" value="' + escapeHtml(flag.file) + '" placeholder="파일명.svg"></div></td>' +
'<td class="nation-list-small-col"><input type="text" data-field="width" value="' + escapeHtml(flag.width) + '"></td>' +
'<td class="nation-list-check-col"><input type="checkbox" data-field="border"' + (flag.border !== false ? ' checked' : '') + '></td>' +
'<td class="nation-list-offset-col">' + this.renderOffsetSelect(flag) + '</td>' +
'<td><input type="text" data-field="note" value="' + escapeHtml(item.note) + '"></td>' +
'<td class="nation-list-check-col"><input type="checkbox" data-field="enabled"' + (item.enabled !== false ? ' checked' : '') + '></td>' +
'<td class="nation-list-action-col"><button type="button" class="nation-list-delete">삭제</button></td>' +
'</tr>';
};
NationListManager.prototype.bind = function () {
var self = this;
var yearSelect = this.root.querySelector('.nation-list-year-select');
var search = this.root.querySelector('.nation-list-search');
var save = this.root.querySelector('.nation-list-save');
var reload = this.root.querySelector('.nation-list-reload');
if (yearSelect) {
yearSelect.addEventListener('change', function () {
self.activeContinent = 'america';
self.query = '';
self.loadYear(yearSelect.value);
});
}
if (search) {
search.addEventListener('input', function () {
self.query = search.value;
self.render();
var next = self.root.querySelector('.nation-list-search');
if (next) {
next.focus();
next.setSelectionRange(next.value.length, next.value.length);
}
});
}
if (save) save.addEventListener('click', function () { self.save(); });
if (reload) reload.addEventListener('click', function () { self.loadYear(self.year); });
this.root.querySelectorAll('.nation-list-continent-tab').forEach(function (tab) {
tab.addEventListener('click', function () {
self.activeContinent = tab.getAttribute('data-continent') || self.activeContinent;
self.render();
});
});
this.root.querySelectorAll('.nation-list-add').forEach(function (button) {
button.addEventListener('click', function () {
self.addItem(button.getAttribute('data-continent'), button.getAttribute('data-region'));
});
});
this.root.querySelectorAll('.nation-list-table tbody tr[data-index]').forEach(function (tr) {
tr.querySelectorAll('input[data-field], select[data-field]').forEach(function (input) {
input.addEventListener('input', function () {
self.updateRow(tr, input);
if (input.getAttribute('data-field') === 'flag') self.showFlagPicker(input);
});
input.addEventListener('change', function () {
self.updateRow(tr, input);
if (input.getAttribute('data-field') === 'flag') self.render();
});
if (input.getAttribute('data-field') === 'flag') {
input.addEventListener('focus', function () { self.showFlagPicker(input); });
input.addEventListener('keydown', function (event) {
if (event.key === 'Escape') self.hideFlagPicker();
});
}
});
tr.addEventListener('dragstart', function (event) {
self.startDrag(tr, event);
});
tr.addEventListener('dragover', function (event) {
self.dragOverRow(tr, event);
});
tr.addEventListener('dragleave', function () {
tr.classList.remove('is-drop-before', 'is-drop-after');
});
tr.addEventListener('drop', function (event) {
self.dropOnRow(tr, event);
});
tr.addEventListener('dragend', function () {
self.clearDragState();
});
var del = tr.querySelector('.nation-list-delete');
if (del) {
del.addEventListener('click', function () {
self.deleteItem(tr);
});
}
});
this.root.querySelectorAll('.nation-list-table tbody[data-continent][data-region]').forEach(function (tbody) {
tbody.addEventListener('dragover', function (event) {
self.dragOverRegion(tbody, event);
});
tbody.addEventListener('drop', function (event) {
self.dropOnRegion(tbody, event);
});
});
document.addEventListener('pointerdown', function onDocPointerDown(event) {
if (!self.root.contains(event.target)) {
self.hideFlagPicker();
document.removeEventListener('pointerdown', onDocPointerDown);
}
});
};
NationListManager.prototype.findRegion = function (continentKey, regionKey) {
var continent = (this.state.continents || []).find(function (item) { return item.key === continentKey; });
if (!continent) return null;
return (continent.regions || []).find(function (item) { return item.key === regionKey; }) || null;
};
NationListManager.prototype.getRowItem = function (tr) {
var region = this.findRegion(tr.getAttribute('data-continent'), tr.getAttribute('data-region'));
var index = parseInt(tr.getAttribute('data-index') || '-1', 10);
if (!region || index < 0 || !region.items || !region.items[index]) return null;
return { region: region, index: index, item: region.items[index] };
};
NationListManager.prototype.updateRow = function (tr, input) {
var row = this.getRowItem(tr);
var field = input.getAttribute('data-field');
var flag;
var value;
if (!row) return;
flag = getItemPrimaryFlag(row.item);
value = input.type === 'checkbox' ? input.checked : input.value;
if (field === 'link') {
value = String(value || '').trim();
if (/^https?:\/\//i.test(value)) {
row.item.external_url = value;
row.item.page = '';
} else {
row.item.page = value;
row.item.external_url = '';
}
} else if (field === 'label') {
row.item.label = value;
} else if (field === 'flag') {
flag.file = normalizeFileTitle(value);
} else if (field === 'width') {
flag.width = String(value || '').trim() || '16px';
} else if (field === 'border') {
flag.border = !!value;
} else if (field === 'offset') {
if (String(value) === 'auto') {
flag.offsetMode = 'auto';
} else {
flag.offsetMode = 'manual';
flag.offsetY = Math.round(clampNumber(value, -3, 3));
}
} else if (field === 'note') {
row.item.note = value;
} else if (field === 'enabled') {
row.item.enabled = !!value;
}
this.setStatus('수정됨');
};
NationListManager.prototype.showFlagPicker = function (input) {
var self = this;
var picker = this.root.querySelector('.nation-list-flag-picker');
var query = normalizeFileTitle(input.value).toLowerCase();
var rect;
var rootRect;
if (!picker) return;
this.activeFlagInput = input;
this.flagPicker = picker;
picker.hidden = false;
picker.innerHTML = '<div class="nation-list-flag-picker-loading">flag 파일 검색 중...</div>';
rect = input.getBoundingClientRect();
rootRect = this.root.getBoundingClientRect();
picker.style.left = Math.max(0, rect.left - rootRect.left) + 'px';
picker.style.top = Math.max(0, rect.bottom - rootRect.top + 2) + 'px';
picker.style.width = Math.max(280, rect.width + 44) + 'px';
this.loadFlagFiles().then(function (list) {
var filtered = list.filter(function (item) {
return !query || item.file.toLowerCase().indexOf(query) !== -1;
}).slice(0, 48);
if (!filtered.length) {
picker.innerHTML = '<div class="nation-list-flag-picker-empty">flag 파일 없음 · 직접 입력 가능</div>';
return;
}
picker.innerHTML = filtered.map(function (item) {
return '' +
'<button type="button" class="nation-list-flag-option" data-file="' + escapeHtml(item.file) + '">' +
'<img src="' + escapeHtml(item.thumb || buildFileRedirectUrl(item.file)) + '" alt="">' +
'<span>' + escapeHtml(item.file) + '</span>' +
'</button>';
}).join('');
picker.querySelectorAll('.nation-list-flag-option').forEach(function (btn) {
btn.addEventListener('click', function () {
var file = btn.getAttribute('data-file') || '';
var tr;
if (!self.activeFlagInput) return;
self.activeFlagInput.value = file;
tr = self.activeFlagInput.closest('tr');
if (tr) self.updateRow(tr, self.activeFlagInput);
self.setStatus('수정됨');
self.hideFlagPicker();
self.render();
});
});
});
};
NationListManager.prototype.hideFlagPicker = function () {
var picker = this.flagPicker || this.root.querySelector('.nation-list-flag-picker');
if (picker) {
picker.hidden = true;
picker.innerHTML = '';
}
this.activeFlagInput = null;
};
NationListManager.prototype.addItem = function (continentKey, regionKey) {
var region = this.findRegion(continentKey, regionKey);
if (!region) return;
region.items = region.items || [];
region.items.push(normalizeItem({ page: '', label: '', flags: [normalizeFlag({})], enabled: true }));
this.setStatus('항목 추가됨');
this.render();
};
NationListManager.prototype.deleteItem = function (tr) {
var row = this.getRowItem(tr);
if (!row) return;
row.region.items.splice(row.index, 1);
this.setStatus('항목 삭제됨');
this.render();
};
NationListManager.prototype.startDrag = function (tr, event) {
var row = this.getRowItem(tr);
if (!row) return;
this.dragSource = {
continent: tr.getAttribute('data-continent'),
region: tr.getAttribute('data-region'),
index: row.index
};
tr.classList.add('is-dragging');
if (event && event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', JSON.stringify(this.dragSource));
}
};
NationListManager.prototype.clearDragState = function () {
this.dragSource = null;
this.root.querySelectorAll('.is-dragging, .is-drop-before, .is-drop-after, .is-region-drop').forEach(function (node) {
node.classList.remove('is-dragging', 'is-drop-before', 'is-drop-after', 'is-region-drop');
});
};
NationListManager.prototype.dragOverRow = function (tr, event) {
var rect;
var after;
if (!this.dragSource) return;
event.preventDefault();
rect = tr.getBoundingClientRect();
after = event.clientY > rect.top + rect.height / 2;
this.root.querySelectorAll('.is-drop-before, .is-drop-after').forEach(function (node) {
if (node !== tr) node.classList.remove('is-drop-before', 'is-drop-after');
});
tr.classList.toggle('is-drop-before', !after);
tr.classList.toggle('is-drop-after', after);
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
};
NationListManager.prototype.dragOverRegion = function (tbody, event) {
if (!this.dragSource) return;
event.preventDefault();
tbody.classList.add('is-region-drop');
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
};
NationListManager.prototype.moveDraggedItem = function (targetContinent, targetRegion, targetIndex) {
var source = this.dragSource;
var sourceRegion;
var destRegion;
var item;
if (!source) return false;
sourceRegion = this.findRegion(source.continent, source.region);
destRegion = this.findRegion(targetContinent, targetRegion);
if (!sourceRegion || !destRegion || !sourceRegion.items || !destRegion.items) return false;
if (source.index < 0 || source.index >= sourceRegion.items.length) return false;
item = sourceRegion.items.splice(source.index, 1)[0];
if (sourceRegion === destRegion && targetIndex > source.index) {
targetIndex -= 1;
}
targetIndex = Math.max(0, Math.min(targetIndex, destRegion.items.length));
destRegion.items.splice(targetIndex, 0, item);
return true;
};
NationListManager.prototype.dropOnRow = function (tr, event) {
var target = this.getRowItem(tr);
var rect;
var targetIndex;
if (!this.dragSource || !target) return;
event.preventDefault();
event.stopPropagation();
rect = tr.getBoundingClientRect();
targetIndex = target.index + (event.clientY > rect.top + rect.height / 2 ? 1 : 0);
if (this.moveDraggedItem(tr.getAttribute('data-continent'), tr.getAttribute('data-region'), targetIndex)) {
this.setStatus('순서 변경됨');
this.clearDragState();
this.render();
}
};
NationListManager.prototype.dropOnRegion = function (tbody, event) {
var targetContinent;
var targetRegion;
var region;
if (!this.dragSource) return;
if (event.target && event.target.closest && event.target.closest('tr[data-index]')) return;
event.preventDefault();
targetContinent = tbody.getAttribute('data-continent');
targetRegion = tbody.getAttribute('data-region');
region = this.findRegion(targetContinent, targetRegion);
if (!region) return;
if (this.moveDraggedItem(targetContinent, targetRegion, region.items.length)) {
this.setStatus('순서 변경됨');
this.clearDragState();
this.render();
}
};
function init(root) {
(root || document).querySelectorAll('.nation-list-manager').forEach(function (node) {
if (node.getAttribute('data-nation-list-manager-ready') === '1') return;
node.setAttribute('data-nation-list-manager-ready', '1');
new NationListManager(node).init();
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { init(document); });
} else {
init(document);
}
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
init($content && $content[0] ? $content[0] : document);
});
}
}());