참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
- 오페라: Ctrl-F5를 입력.
/* =========================================
COASTLINE: BLACK ICE - CategoryNav
대문 카테고리 SVG 네비 생성기
========================================= */
/*
새 공통 기능에는 프로젝트 접두사를 사용하지 않는다.
window.CLBI.categoryNav는 기존 Common.js 호출과의 호환을 위해서만 유지한다.
*/
(function () {
'use strict';
var SVG_NS = 'http://www.w3.org/2000/svg';
var XLINK_NS = 'http://www.w3.org/1999/xlink';
var ITEMS = [
{ key: 'era', label: '시대', title: '시대' },
{ key: 'settings', label: '설정', title: '설정' },
{ key: 'project', label: '프로젝트', title: '프로젝트:소개' },
{ key: 'corporations', label: '기업 및 공동체', title: '기업_및_공동체' },
{ key: 'military', label: '군, 정치집단', title: '군_정치집단' }
];
var BUTTON_GAP = 2;
var OUTER_BORDER = 1;
var INSET_LINE_OFFSET = 1.5;
/* NEWS recent item height baseline: 36px. */
function buildPath(title) {
if (typeof window.buildWikiPath === 'function') {
return window.buildWikiPath(title);
}
return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_'));
}
function readWidth(element) {
if (!element) return 0;
var rect = element.getBoundingClientRect ? element.getBoundingClientRect() : null;
var width = rect ? Math.floor(rect.width || 0) : 0;
if (!width && element.clientWidth) {
width = Math.floor(element.clientWidth);
}
return width || 0;
}
function getRenderableWidth(nav, mount) {
var candidates = [
nav,
mount,
mount ? mount.parentElement : null,
mount ? mount.closest('.main-portal') : null,
document.querySelector('.liberty-content-main .mw-parser-output'),
document.querySelector('.liberty-content-main'),
document.querySelector('.liberty-content')
];
for (var i = 0; i < candidates.length; i++) {
var width = readWidth(candidates[i]);
if (width > 0) return width;
}
if (document.documentElement && document.documentElement.clientWidth) {
return Math.max(320, Math.floor(document.documentElement.clientWidth - 48));
}
return 1200;
}
function calculateGeometry(width) {
var usableWidth = Math.max(320, Math.floor(width || 0));
var count = ITEMS.length;
var height = 36;
var cut = Math.round((usableWidth / count) * 0.0675);
var top = [];
var bottom = [];
var half = count / 2;
var i;
cut = Math.max(9, Math.min(15, cut));
for (i = 0; i <= count; i++) {
var x = Math.round((usableWidth * i) / count);
if (i === 0 || i === count || i === half) {
top.push(x);
bottom.push(x);
} else if (i < half) {
top.push(x - cut);
bottom.push(x);
} else {
top.push(x);
bottom.push(x - cut);
}
}
return {
width: usableWidth,
height: height,
count: count,
cut: cut,
top: top,
bottom: bottom,
gap: BUTTON_GAP
};
}
function createSvgElement(name) {
return document.createElementNS(SVG_NS, name);
}
function roundCoordinate(value) {
return Math.round(value * 100) / 100;
}
function pointsToString(points) {
return points.map(function (point) {
return roundCoordinate(point[0]) + ',' + roundCoordinate(point[1]);
}).join(' ');
}
function getSegmentPoints(index, geometry) {
var halfGap = geometry.gap / 2;
var leftOffset = index === 0 ? 0 : halfGap;
var rightOffset = index === geometry.count - 1 ? 0 : halfGap;
return [
[geometry.top[index] + leftOffset, 0],
[geometry.top[index + 1] - rightOffset, 0],
[geometry.bottom[index + 1] - rightOffset, geometry.height],
[geometry.bottom[index] + leftOffset, geometry.height]
];
}
function getCentroid(points) {
var totalX = 0;
var totalY = 0;
points.forEach(function (point) {
totalX += point[0];
totalY += point[1];
});
return [totalX / points.length, totalY / points.length];
}
function intersectLines(lineA, lineB) {
var p = lineA.point;
var r = lineA.direction;
var q = lineB.point;
var s = lineB.direction;
var cross = r[0] * s[1] - r[1] * s[0];
if (Math.abs(cross) < 0.000001) {
return [p[0], p[1]];
}
var qMinusP = [q[0] - p[0], q[1] - p[1]];
var t = (qMinusP[0] * s[1] - qMinusP[1] * s[0]) / cross;
return [p[0] + t * r[0], p[1] + t * r[1]];
}
function insetPolygon(points, distance) {
var center = getCentroid(points);
var shiftedLines = [];
var count = points.length;
var i;
for (i = 0; i < count; i++) {
var start = points[i];
var end = points[(i + 1) % count];
var dx = end[0] - start[0];
var dy = end[1] - start[1];
var length = Math.sqrt(dx * dx + dy * dy) || 1;
var normal = [-dy / length, dx / length];
var midpoint = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];
var towardCenter = [center[0] - midpoint[0], center[1] - midpoint[1]];
if (normal[0] * towardCenter[0] + normal[1] * towardCenter[1] < 0) {
normal[0] *= -1;
normal[1] *= -1;
}
shiftedLines.push({
point: [
start[0] + normal[0] * distance,
start[1] + normal[1] * distance
],
direction: [dx, dy]
});
}
return shiftedLines.map(function (line, index) {
var previous = shiftedLines[(index + count - 1) % count];
return intersectLines(previous, line);
});
}
function createPolygon(className, points) {
var polygon = createSvgElement('polygon');
polygon.setAttribute('class', className);
polygon.setAttribute('points', pointsToString(points));
return polygon;
}
function createEdgeLine(className, start, end) {
var line = createSvgElement('line');
line.setAttribute('class', className);
line.setAttribute('x1', roundCoordinate(start[0]));
line.setAttribute('y1', roundCoordinate(start[1]));
line.setAttribute('x2', roundCoordinate(end[0]));
line.setAttribute('y2', roundCoordinate(end[1]));
line.setAttribute('vector-effect', 'non-scaling-stroke');
line.setAttribute('pointer-events', 'none');
return line;
}
function createSegment(item, index, geometry) {
var anchor = createSvgElement('a');
var outerPoints = getSegmentPoints(index, geometry);
var surfacePoints = insetPolygon(outerPoints, OUTER_BORDER);
var insetPoints = insetPolygon(outerPoints, INSET_LINE_OFFSET);
var outer = createPolygon('portal-cat-edge', outerPoints);
var surface = createPolygon('portal-cat-surface', surfacePoints);
var topEdge = createEdgeLine('portal-cat-inset portal-cat-inset-top', insetPoints[0], insetPoints[1]);
var rightEdge = createEdgeLine('portal-cat-inset portal-cat-inset-right', insetPoints[1], insetPoints[2]);
var bottomEdge = createEdgeLine('portal-cat-inset portal-cat-inset-bottom', insetPoints[2], insetPoints[3]);
var leftEdge = createEdgeLine('portal-cat-inset portal-cat-inset-left', insetPoints[3], insetPoints[0]);
var hit = createPolygon('portal-cat-hit', outerPoints);
var text = createSvgElement('text');
var href = buildPath(item.title);
var center = getCentroid(surfacePoints);
anchor.setAttribute('class', 'portal-cat-anchor');
anchor.setAttribute('href', href);
anchor.setAttributeNS(XLINK_NS, 'xlink:href', href);
anchor.setAttribute('aria-label', item.label);
anchor.setAttribute('data-category-key', item.key);
outer.setAttribute('pointer-events', 'none');
surface.setAttribute('pointer-events', 'none');
hit.setAttribute('pointer-events', 'all');
text.setAttribute('class', 'portal-cat-label');
text.setAttribute('x', Math.round(center[0]));
text.setAttribute('y', Math.round(geometry.height / 2) + 1);
text.setAttribute('font-size', '12');
text.setAttribute('font-weight', '700');
text.setAttribute('text-anchor', 'middle');
text.setAttribute('dominant-baseline', 'middle');
text.setAttribute('pointer-events', 'none');
text.textContent = item.label;
anchor.appendChild(outer);
anchor.appendChild(surface);
anchor.appendChild(topEdge);
anchor.appendChild(rightEdge);
anchor.appendChild(bottomEdge);
anchor.appendChild(leftEdge);
anchor.appendChild(hit);
anchor.appendChild(text);
return anchor;
}
function clearNode(node) {
while (node.firstChild) node.removeChild(node.firstChild);
}
function buildSvg(width) {
var geometry = calculateGeometry(width);
var svg = createSvgElement('svg');
svg.setAttribute('class', 'portal-category-svg');
svg.setAttribute('viewBox', '0 0 ' + geometry.width + ' ' + geometry.height);
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.setAttribute('preserveAspectRatio', 'none');
svg.setAttribute('aria-hidden', 'false');
ITEMS.forEach(function (item, index) {
svg.appendChild(createSegment(item, index, geometry));
});
return {
svg: svg,
key: geometry.width + '|' + geometry.height + '|' + geometry.cut + '|' + geometry.gap + '|' + ITEMS.length
};
}
function renderSvgIntoNav(nav, mount) {
var result = buildSvg(getRenderableWidth(nav, mount));
if (nav.__categoryNavRenderKey === result.key && nav.querySelector('svg')) return;
nav.__categoryNavRenderKey = result.key;
clearNode(nav);
nav.appendChild(result.svg);
}
function scheduleRender(nav, mount) {
if (!nav || nav.__categoryNavFrame) return;
nav.__categoryNavFrame = window.requestAnimationFrame(function () {
nav.__categoryNavFrame = null;
renderSvgIntoNav(nav, mount);
});
}
function renderCategoryNavMount(mount) {
if (!mount) return;
if (mount.__categoryNavObserver) {
mount.__categoryNavObserver.disconnect();
mount.__categoryNavObserver = null;
}
clearNode(mount);
var nav = document.createElement('nav');
nav.className = 'portal-category-nav';
nav.setAttribute('aria-label', 'Main categories');
mount.appendChild(nav);
mount.setAttribute('data-category-nav-rendered', '1');
scheduleRender(nav, mount);
window.setTimeout(function () { scheduleRender(nav, mount); }, 80);
window.setTimeout(function () { scheduleRender(nav, mount); }, 250);
if (window.ResizeObserver) {
var observer = new ResizeObserver(function () {
scheduleRender(nav, mount);
});
observer.observe(mount);
observer.observe(nav);
mount.__categoryNavObserver = observer;
} else {
window.addEventListener('resize', function () {
scheduleRender(nav, mount);
});
}
}
function renderAllCategoryNavs(root) {
var scope = root || document;
var mounts = scope.querySelectorAll('[data-component="category-nav"]');
Array.prototype.forEach.call(mounts, function (mount) {
renderCategoryNavMount(mount);
});
}
window.CLBI = window.CLBI || {};
window.CLBI.categoryNav = {
init: renderAllCategoryNavs,
renderMount: renderCategoryNavMount
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
renderAllCategoryNavs(document);
});
} else {
renderAllCategoryNavs(document);
}
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
var root = $content && $content[0] ? $content[0] : document;
renderAllCategoryNavs(root);
});
}
}());