미디어위키:CategoryNav.js

Nxdsxn (토론 | 기여)님의 2026년 5월 19일 (화) 07:07 판 (새 문서: →‎========================================= COASTLINE: BLACK ICE - CategoryNav 대문 카테고리 네비 DOM 생성기 =========================================: /* 설계 목적 ----------------------------------------- 이 파일은 대문 카테고리 네비를 MediaWiki 본문 위키문법이 아니라 JavaScript로 생성한다. 왜 JS를 쓰는가 ----------------------------------------- 테스트 단계에서는 다음 방식들을 시도했다. 1. 본문에 <span...)
(차이) ← 이전 판 | 최신판 (차이) | 다음 판 → (차이)

참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.

  • 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
  • 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
  • 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
  • 오페라: Ctrl-F5를 입력.
/* =========================================
COASTLINE: BLACK ICE - CategoryNav
대문 카테고리 네비 DOM 생성기
========================================= */

/*
설계 목적
-----------------------------------------
이 파일은 대문 카테고리 네비를 MediaWiki 본문 위키문법이 아니라
JavaScript로 생성한다.

왜 JS를 쓰는가
-----------------------------------------
테스트 단계에서는 다음 방식들을 시도했다.

1. 본문에 <span class="dir-link">[[문서|라벨]]</span>을 직접 작성
   - MediaWiki가 내부에 <a>를 자동 생성한다.
   - span은 모양을 담당하고, a는 클릭을 담당하게 된다.
   - 결과적으로 보이는 면, 호버 면, 클릭 면이 서로 분리되었다.

2. span에 clip-path를 주고, 검은 획은 span::after로 처리
   - clip-path가 ::after까지 같이 잘라먹었다.
   - 왼쪽 항목의 구분 획이 사라지거나, 일부만 보였다.

3. 검은 획과 호버를 별도 overlay로 분리
   - 호버 좌표와 구분선 좌표를 직접 계산해야 했다.
   - 프로젝트 버튼은 가운데 남은 공간을 차지하므로 폭이 유동적이다.
   - 수동 좌표는 화면 폭이 바뀔 때 다시 틀어질 가능성이 높았다.

4. 버튼들을 음수 margin으로 겹침
   - 잘린 사선 영역의 빈 공간은 메웠다.
   - 대신 호버 배경이 옆 칸으로 살짝 넘어갔다.

결론
-----------------------------------------
카테고리 항목 하나가 다음 역할을 모두 맡아야 한다.

- 실제 링크
- 실제 클릭 영역
- 실제 호버 영역
- 실제 사선 형태

그러려면 JS가 <a class="portal-cat-link ...">를 직접 만들어야 한다.
CSS는 이 실제 a 요소에 clip-path와 hover를 적용한다.

JS의 역할
-----------------------------------------
JS는 구조만 만든다.
색, 표면, 호버, 사선 모양은 MainPage.css가 담당한다.
JS에서 좌표 계산이나 시각 보정은 하지 않는다.
*/

(function () {
    'use strict';

    /*
    기본 라벨과 링크
    -----------------------------------------
    여기서는 한국어를 기본값으로 둔다.

    나중에 다국어 구조가 안정되면
    window.LANG, window.CAT_LINKS 또는 별도 MainPageConfig.js에서
    라벨과 링크를 받아오게 확장할 수 있다.

    현재 Lang.js에는 일부 카테고리 라벨과 CAT_LINKS가 있지만,
    '설정'과 '프로젝트'처럼 이번 대문 네비에 새로 들어간 항목은
    기존 CAT_LINKS에 완전히 대응되어 있지 않을 수 있다.

    따라서 이 파일은 자체 fallback을 가진다.
    */

    var CATEGORY_NAV_ITEMS = [
        {
            key: 'history',
            label: '역사적 사건',
            title: '역사적_사건',
            className: 'portal-cat-left-start'
        },
        {
            key: 'settings',
            label: '설정',
            title: '설정',
            className: 'portal-cat-left-mid'
        },
        {
            key: 'nations',
            label: '국가 및 조합',
            title: '국가_및_조합',
            className: 'portal-cat-left-end'
        },
        {
            key: 'project',
            label: '프로젝트',
            title: '프로젝트:소개',
            className: 'portal-cat-project'
        },
        {
            key: 'corporations',
            label: '기업 및 공동체',
            title: '기업_및_공동체',
            className: 'portal-cat-right-start'
        },
        {
            key: 'military',
            label: '군, 정치집단',
            title: '군_정치집단',
            className: 'portal-cat-right-mid'
        },
        {
            key: 'personnel',
            label: '인물',
            title: '인물',
            className: 'portal-cat-right-end'
        }
    ];

    /*
    언어 감지
    -----------------------------------------
    기존 Common.js에 getCurrentLang() 함수가 있을 수 있다.
    하지만 이 파일은 단독으로 로드되어도 깨지지 않아야 하므로
    함수 존재 여부를 확인하고 fallback을 둔다.
    */

    function getCurrentLanguageSafe() {
        if (typeof window.getCurrentLang === 'function') {
            return window.getCurrentLang();
        }

        var langData = document.getElementById('clbi-lang-data');

        if (langData) {
            return langData.getAttribute('data-lang') || 'ko';
        }

        return 'ko';
    }

    /*
    링크 생성
    -----------------------------------------
    MediaWiki 내부 링크는 /index.php/문서명 형식으로 맞춘다.

    buildWikiPath()가 이미 전역에 있으면 그것을 사용한다.
    기존 Common.js에는 buildWikiPath(title) 함수가 존재할 수 있으므로
    중복 구현을 피한다.

    단, 이 파일은 단독 테스트도 가능해야 하므로 fallback도 제공한다.
    */

    function buildPath(title) {
        if (typeof window.buildWikiPath === 'function') {
            return window.buildWikiPath(title);
        }

        return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_'));
    }

    /*
    라벨 선택
    -----------------------------------------
    지금은 한국어 대문 테스트를 우선한다.

    향후 다국어 확장 시에는 이 함수에서
    window.LANG[lang][key] 또는 별도 navLabels를 읽으면 된다.

    현재는 기존 LANG 키와 완전히 맞지 않는 항목이 있으므로,
    섣불리 LANG만 신뢰하지 않는다.
    */

    function getLabel(item, lang) {
        if (lang === 'ko') {
            return item.label;
        }

        /*
        다국어 임시 fallback.
        실서비스 다국어 도입 전까지는 한국어 라벨을 유지한다.

        이유:
        - 라벨을 일부만 번역하면 네비 구조가 언어별로 흔들린다.
        - 오른쪽 보조 라벨에 영어를 넣었다가 다국어 일관성 문제가 생겼던 것처럼,
          아직 전체 번역 체계가 확정되지 않은 상태에서는 부분 번역을 피한다.
        */
        return item.label;
    }

    /*
    단일 mount 렌더링
    -----------------------------------------
    data-component="category-nav"가 붙은 요소를 찾아
    내부를 비우고 nav를 생성한다.

    같은 페이지에서 SPA 이동이나 wikipage.content 훅으로
    여러 번 호출될 수 있으므로 매번 내부를 재생성해도 안전하게 한다.
    */

    function renderCategoryNavMount(mount) {
        if (!mount) return;

        var lang = getCurrentLanguageSafe();
        var nav = document.createElement('nav');

        nav.className = 'portal-category-nav';
        nav.setAttribute('aria-label', 'Main categories');

        CATEGORY_NAV_ITEMS.forEach(function (item) {
            var link = document.createElement('a');

            link.className = 'portal-cat-link ' + item.className;
            link.href = buildPath(item.title);
            link.textContent = getLabel(item, lang);

            /*
            data-key는 CSS가 아니라 디버깅과 향후 다국어 갱신용이다.
            나중에 라벨만 다시 바꿔야 할 때 이 key를 기준으로 찾을 수 있다.
            */
            link.setAttribute('data-category-key', item.key);

            nav.appendChild(link);
        });

        mount.innerHTML = '';
        mount.appendChild(nav);
        mount.setAttribute('data-category-nav-rendered', '1');
    }

    /*
    전체 렌더링
    -----------------------------------------
    대문 본문에는 아래처럼 자리만 남긴다.

    <div data-component="category-nav"></div>

    이 함수는 그 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);
        });
    }

    /*
    초기화
    -----------------------------------------
    1. 일반 페이지 로드 시 DOMContentLoaded에서 실행한다.
    2. MediaWiki SPA/동적 본문 갱신을 고려해 wikipage.content 훅에서도 실행한다.
    3. 전역 객체에도 init 함수를 노출해 디버깅이나 수동 재실행이 가능하게 한다.
    */

    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);
        });
    }
}());