참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
- 오페라: Ctrl-F5를 입력.
/* =========================================
PortalSectionNav
대문 상단 카테고리 하단 부착형 섹션 버튼
========================================= */
(function (window, document, mw) {
'use strict';
var XLINK_NS = 'http://www.w3.org/1999/xlink';
var COMPONENT = 'portal-section-nav';
var VERSION = '20260717-pixel-stack-042';
var renderTimer = 0;
var ITEMS = [
{ key:'black-ice', label:'블랙 아이스', title:'대문' },
{ key:'anecdote', label:'에넥도트', href:'/index.php/특수:모든문서?namespace=3000' }
];
function api() {
return window.CLBI && window.CLBI.frame;
}
function buildWikiPath(title) {
if (typeof window.buildWikiPath === 'function') return window.buildWikiPath(title);
return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_'));
}
function ensureMount(portal, topMount) {
var mount = portal.querySelector('[data-component="' + COMPONENT + '"]');
if (!mount) {
mount = document.createElement('div');
mount.setAttribute('data-component', COMPONENT);
}
if (mount.parentNode !== topMount.parentNode || topMount.nextSibling !== mount) {
topMount.parentNode.insertBefore(mount, topMount.nextSibling);
}
return mount;
}
function outerGeometry(seams) {
var frame = api();
var d = frame.DIMENSIONS;
var phase = seams.bottomStartAbsY % frame.FRAME.SLANT_RISE;
var leftSeamX = frame.slantX(
seams.settingsLeftTopX,
seams.bottomStartAbsY,
seams.settingsLeftDir,
0
);
var rightSeamX = frame.slantX(
seams.corpRightTopX,
seams.bottomStartAbsY,
seams.corpRightDir,
0
);
return {
phase:phase,
leftSeamX:leftSeamX,
rightSeamX:rightSeamX,
leftDir:seams.settingsLeftDir,
rightDir:seams.corpRightDir,
points:frame.segment(
leftSeamX,
rightSeamX,
d.ROW_HEIGHT,
seams.settingsLeftDir,
seams.corpRightDir,
phase
)
};
}
function appendFrame(svg, geometry) {
var frame = api();
var f = frame.FRAME;
var d = frame.DIMENSIONS;
var group = frame.create('g', 'portal-section-frame');
var surface = frame.segmentAt(
geometry.leftSeamX,
geometry.rightSeamX,
d.FRAME_SURFACE_TOP,
d.ROW_HEIGHT - f.BORDER,
geometry.leftDir,
geometry.rightDir,
geometry.phase,
f.BORDER,
f.BORDER
);
var well = frame.segmentAt(
geometry.leftSeamX,
geometry.rightSeamX,
d.WELL_TOP,
d.WELL2_BOTTOM,
geometry.leftDir,
geometry.rightDir,
geometry.phase,
d.WELL_TOP,
d.WELL_TOP
);
var joinCover = frame.segmentAt(
geometry.leftSeamX,
geometry.rightSeamX,
0,
d.OVERLAP,
geometry.leftDir,
geometry.rightDir,
geometry.phase,
0,
0
);
var frameTopRow = frame.rowAt(
geometry.leftSeamX,
geometry.rightSeamX,
d.OVERLAP,
geometry.leftDir,
geometry.rightDir,
geometry.phase,
f.BORDER,
f.BORDER
);
var frameBottomRow = frame.rowAt(
geometry.leftSeamX,
geometry.rightSeamX,
d.ROW_HEIGHT - f.BORDER - f.BEVEL,
geometry.leftDir,
geometry.rightDir,
geometry.phase,
f.BORDER,
f.BORDER
);
var wellTopRow = frame.rowAt(
geometry.leftSeamX,
geometry.rightSeamX,
d.WELL_TOP,
geometry.leftDir,
geometry.rightDir,
geometry.phase,
d.WELL_TOP,
d.WELL_TOP
);
var wellBottomRow = frame.rowAt(
geometry.leftSeamX,
geometry.rightSeamX,
d.WELL2_BOTTOM - f.BEVEL,
geometry.leftDir,
geometry.rightDir,
geometry.phase,
d.WELL_TOP,
d.WELL_TOP
);
group.appendChild(frame.polygon('portal-section-frame-edge', geometry.points));
group.appendChild(frame.polygon('portal-section-frame-surface', surface));
group.appendChild(frame.polygon('portal-section-frame-well', well));
group.appendChild(frame.polygon('portal-section-join-side-cover', joinCover));
group.appendChild(frame.line('portal-section-frame-shadow', frameBottomRow[0], frameTopRow[0]));
group.appendChild(frame.line('portal-section-frame-highlight', frameTopRow[1], frameBottomRow[1]));
group.appendChild(frame.line('portal-section-well-shadow', wellTopRow[0], wellTopRow[1]));
group.appendChild(frame.line('portal-section-well-shadow', wellTopRow[1], wellBottomRow[1]));
group.appendChild(frame.line('portal-section-well-highlight', wellBottomRow[0], wellTopRow[0]));
group.appendChild(frame.line('portal-section-well-highlight', wellBottomRow[1], wellBottomRow[0]));
svg.appendChild(group);
}
function splitButtons(geometry) {
var frame = api();
var f = frame.FRAME;
var d = frame.DIMENSIONS;
var leftBase = geometry.leftSeamX + d.WELL_BOTTOM;
var rightBase = geometry.rightSeamX - d.WELL_BOTTOM;
var leftTop = frame.slantX(leftBase, d.BUTTON_TOP, geometry.leftDir, geometry.phase);
var rightTop = frame.slantX(rightBase, d.BUTTON_TOP, geometry.rightDir, geometry.phase);
var topMid = Math.round((leftTop + rightTop) / 2);
var halfGap = f.GAP / 2;
return [
{
leftTopX:leftBase,
rightTopX:topMid - halfGap,
leftDir:geometry.leftDir,
rightDir:0,
phase:geometry.phase
},
{
leftTopX:topMid + halfGap,
rightTopX:rightBase,
leftDir:0,
rightDir:geometry.rightDir,
phase:geometry.phase
}
];
}
function appendButton(svg, item, button) {
var frame = api();
var f = frame.FRAME;
var d = frame.DIMENSIONS;
var points = frame.segmentAt(
button.leftTopX,
button.rightTopX,
d.BUTTON_EDGE_TOP,
d.BUTTON_EDGE_BOTTOM,
button.leftDir,
button.rightDir,
button.phase,
0,
0
);
var surface = frame.segmentAt(
button.leftTopX,
button.rightTopX,
d.BUTTON_SURFACE_TOP,
d.BUTTON_SURFACE_BOTTOM,
button.leftDir,
button.rightDir,
button.phase,
f.BORDER,
f.BORDER
);
var topInset = frame.rowAt(
button.leftTopX,
button.rightTopX,
d.BUTTON_SURFACE_TOP,
button.leftDir,
button.rightDir,
button.phase,
f.BORDER,
f.BORDER
);
var bottomInset = frame.rowAt(
button.leftTopX,
button.rightTopX,
d.BUTTON_SURFACE_BOTTOM - f.BEVEL,
button.leftDir,
button.rightDir,
button.phase,
f.BORDER,
f.BORDER
);
var anchor = frame.create('a', 'portal-cat-anchor portal-section-anchor');
var group = frame.create('g', 'portal-section-button-geometry');
var hit = frame.polygon('portal-cat-hit', points);
var text = frame.create('text', 'portal-cat-label portal-section-label');
var href = item.href || buildWikiPath(item.title);
anchor.setAttribute('data-category-key', item.key);
anchor.setAttribute('href', href);
anchor.setAttributeNS(XLINK_NS, 'xlink:href', href);
anchor.setAttribute('aria-label', item.label);
group.appendChild(frame.polygon('portal-cat-edge', points));
group.appendChild(frame.polygon('portal-cat-surface', surface));
group.appendChild(frame.line('portal-cat-inset portal-cat-inset-top', topInset[0], topInset[1]));
group.appendChild(frame.line('portal-cat-inset portal-cat-inset-right', topInset[1], bottomInset[1]));
group.appendChild(frame.line('portal-cat-inset portal-cat-inset-bottom', bottomInset[1], bottomInset[0]));
group.appendChild(frame.line('portal-cat-inset portal-cat-inset-left', bottomInset[0], topInset[0]));
hit.setAttribute('pointer-events', 'all');
group.appendChild(hit);
anchor.appendChild(group);
text.setAttribute('x', frame.centerX(surface));
text.setAttribute('y', Math.round((d.BUTTON_SURFACE_TOP + d.BUTTON_SURFACE_BOTTOM) / 2) + 1);
text.setAttribute('font-size', '12');
text.setAttribute('font-weight', '700');
text.setAttribute('text-anchor', 'middle');
text.setAttribute('dominant-baseline', 'middle');
text.textContent = item.label;
anchor.appendChild(text);
svg.appendChild(anchor);
}
function renderPortal(portal, seams) {
var frame = api();
var current = seams || (frame && frame.seams);
var topMount;
var mount;
var nav;
var svg;
var geometry;
var buttons;
if (!frame || !portal || !current || !current.width) return false;
topMount = portal.querySelector('[data-component="category-nav"]');
if (!topMount) return false;
mount = ensureMount(portal, topMount);
geometry = outerGeometry(current);
buttons = splitButtons(geometry);
nav = document.createElement('nav');
nav.className = 'portal-section-nav';
nav.setAttribute('aria-label', '작품 영역');
svg = frame.create('svg', 'portal-section-svg');
svg.setAttribute('viewBox', '0 0 ' + current.width + ' ' + frame.DIMENSIONS.ROW_HEIGHT);
svg.setAttribute('width', '100%');
svg.setAttribute('height', frame.DIMENSIONS.ROW_HEIGHT);
svg.setAttribute('preserveAspectRatio', 'none');
svg.setAttribute('data-frame-width', current.width);
appendFrame(svg, geometry);
ITEMS.forEach(function (item, index) { appendButton(svg, item, buttons[index]); });
nav.appendChild(svg);
mount.replaceChildren(nav);
mount.setAttribute('data-portal-section-rendered', '1');
mount.setAttribute('data-portal-section-width', String(current.width));
mount.setAttribute('data-portal-section-phase', String(geometry.phase));
return true;
}
function collectPortals(root) {
var scope = root && root.querySelectorAll ? root : document;
var portals = [];
if (scope.matches && scope.matches('.main-portal')) portals.push(scope);
Array.prototype.forEach.call(scope.querySelectorAll('.main-portal'), function (portal) {
if (portals.indexOf(portal) === -1) portals.push(portal);
});
return portals;
}
function renderAll(root, seams) {
var rendered = 0;
collectPortals(root).forEach(function (portal) {
if (renderPortal(portal, seams)) rendered += 1;
});
return rendered;
}
function schedule(root, seams) {
window.clearTimeout(renderTimer);
renderTimer = window.setTimeout(function () {
renderTimer = 0;
renderAll(root || document, seams);
}, 0);
}
if (api()) api().onSeams(function (seams) { schedule(document, seams); });
if (mw && mw.hook) {
mw.hook('wikipage.content').add(function (content) {
schedule(content && content[0] ? content[0] : document);
});
}
window.PortalSectionNav = {
version:VERSION,
init:renderAll,
render:renderPortal,
renderAll:renderAll
};
}(window, document, window.mw));