미디어위키:Minerva.js

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

  • 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
  • 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
  • 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
/* All JavaScript here will be loaded for users of the MinervaNeue skin */
/* 가온 위키 Minerva Neue 전용 커스텀 메뉴
 * - PC/모바일 공통
 * - Minerva 기본 메뉴와 같은 구조/클래스 사용
 *   (toggle-list__list / toggle-list-item / toggle-list-item__anchor /
 *    minerva-icon / toggle-list-item__label)
 * - 상위 그룹(게시판, 버그/기능 개선, 도구, 도움말)은 접고 펼칠 수 있음
 * - "파일 올리기" 아래에 3단계 메뉴 포함
 */

( function ( mw, $ ) {
	'use strict';

	// Minerva 스킨이 아닐 때는 아무 것도 하지 않는다.
	if ( mw.config.get( 'skin' ) !== 'minerva' ) {
		return;
	}

	/* -------------------------------------------------
	 * 1. 메뉴 설정 (질문에서 주신 내용을 JS 객체로 변환)
	 * ------------------------------------------------- */

	// icon 필드에는 Minerva 메뉴에서 사용하는
	// `minerva-icon minerva-icon--XXX` 의 XXX 부분만 넣는다.
	// 예: icon: 'minerva-icon--speechBubbles'
	var gaonMenuConfig = [
		{
			// 게시판 그룹
			label: '게시판',
			icon: 'minerva-icon--speechBubbles',
			children: [
				{
					label: '위키방',
					href: 'https://bbs.gaonwiki.com',
					title: 'https://bbs.gaonwiki.com',
					icon: 'minerva-icon--speechBubbles'
				},
				{
					label: '저작권 처리 게시판',
					href: 'https://bbs.gaonwiki.com/copyrights',
					title: 'https://bbs.gaonwiki.com/copyrights',
					icon: 'minerva-icon--speechBubbles'
				},
				{
					label: '자유게시판',
					href: 'https://bbs.gaonwiki.com/free',
					title: 'https://bbs.gaonwiki.com/free',
					icon: 'minerva-icon--speechBubbles'
				}
			]
		},
		{
			// 버그/기능 개선 그룹
			label: '버그/기능 개선',
			icon: 'minerva-icon--die',
			children: [
				{
					label: '이슈 등록',
					href: 'https://bbs.gaonwiki.com/issue',
					title: 'https://bbs.gaonwiki.com/issue',
					icon: 'minerva-icon--die'
				},
				{
					label: '기능 구현 도움',
					href: 'https://www.gaonwiki.com/w/%EA%B0%80%EC%98%A8_%EC%9C%84%ED%82%A4:%EA%B8%B0%EB%8A%A5_%EA%B5%AC%ED%98%84_%EB%B6%88%EA%B0%80-%EB%88%84%EA%B0%80_%ED%95%B4%EA%B2%B0%EC%B1%85%EC%9D%84_%EC%95%8C%EB%A0%A4%EC%A3%BC%EC%84%B8%EC%9A%94!',
					title: 'https://www.gaonwiki.com/w/가온_위키:기능_구현_불가-누가_해결책을_알려주세요!',
					icon: 'minerva-icon--speechBubbles'
				},
				{
					label: '알려진 문제들',
					href: 'https://www.gaonwiki.com/w/%EA%B0%80%EC%98%A8_%EC%9C%84%ED%82%A4/%EC%95%8C%EB%A0%A4%EC%A7%84_%EB%AC%B8%EC%A0%9C%EB%93%A4',
					title: 'https://www.gaonwiki.com/w/가온_위키/알려진_문제들',
					icon: 'minerva-icon--recentChanges'
				}
			]
		},
		{
			// 도구 그룹
			label: '도구',
			icon: 'minerva-icon--specialPages',
			children: [
				{
					label: '특수 문서 목록',
					href: mw.util.getUrl( '특수:특수문서' ),
					title: '특수:특수문서',
					icon: 'minerva-icon--specialPages'
				},
				{
					// "파일 올리기" (3단계 메뉴를 가진 항목)
					label: '파일 올리기',
					href: '#', // 드롭다운 전용이므로 실제 링크는 없음
					title: '파일 올리기',
					icon: 'minerva-icon--specialPages',
					children: [
						{
							label: '(기존) 업로드',
							href: mw.util.getUrl( '특수:올리기' ),
							title: '특수:올리기',
							icon: 'minerva-icon--specialPages'
						},
						{
							label: '(신규) 업로드 마법사',
							href: mw.util.getUrl( '특수:UploadWizard' ),
							title: '특수:UploadWizard',
							icon: 'minerva-icon--specialPages'
						}
					]
				},
				{
					label: '이슈 트래커',
					href: 'https://bbs.gaonwiki.com/issue/',
					title: 'https://bbs.gaonwiki.com/issue/',
					icon: 'minerva-icon--die'
				},
				{
					label: '가온 위키 베타',
					href: 'https://beta.gaonwiki.com',
					title: 'https://beta.gaonwiki.com',
					icon: 'minerva-icon--specialPages'
				}
			]
		},
		{
			// 도움말 그룹
			label: '도움말',
			icon: 'minerva-icon--specialPages',
			children: [
				{
					label: '위키 문법',
					href: mw.util.getUrl( '도움말:위키_문법' ),
					title: '도움말:위키_문법',
					icon: 'minerva-icon--specialPages'
				},
				{
					label: '설명서',
					href: mw.util.getUrl( '도움말:설명서' ),
					title: '도움말:설명서',
					icon: 'minerva-icon--specialPages'
				}
			]
		}
	];

	/* -------------------------------------------------
	 * 2. DOM 생성 유틸리티
	 * ------------------------------------------------- */

	/**
	 * Minerva 스타일의 아이콘 <span> 요소를 생성한다.
	 *
	 * @param {string} iconClass  "minerva-icon--XXX" 형식의 클래스
	 * @return {jQuery}
	 */
	function buildIcon( iconClass ) {
		var $icon = $( '<span>' ).addClass( 'minerva-icon' );
		if ( iconClass ) {
			$icon.addClass( iconClass );
		}
		return $icon;
	}

	/**
	 * 메뉴 라벨 <span> 요소를 생성한다.
	 *
	 * @param {string} text  화면에 보일 텍스트
	 * @return {jQuery}
	 */
	function buildLabel( text ) {
		return $( '<span>' )
			.addClass( 'toggle-list-item__label' )
			.text( text || '' );
	}

	/**
	 * Minerva 스타일의 <a> 요소를 생성한다.
	 *
	 * @param {string} href   링크 URL (없으면 '#')
	 * @param {string} title  title 속성
	 * @return {jQuery}
	 */
	function buildAnchor( href, title ) {
		return $( '<a>' )
			.addClass( 'toggle-list-item__anchor gaon-menu-link' )
			.attr( 'href', href || '#' )
			.attr( 'title', title || '' );
	}

	/**
	 * 하위 메뉴 항목(레벨 2, 3)을 재귀적으로 생성하여 <ul> 안에 추가한다.
	 *
	 * @param {jQuery} $ul     현재 레벨의 <ul>
	 * @param {Object} item    메뉴 정의 객체
	 * @param {number} level   레벨 번호(2, 3, ...)
	 */
	function appendGaonMenuItem( $ul, item, level ) {
		var hasChildren = Array.isArray( item.children ) && item.children.length > 0;

		// <li> 생성
		var $li = $( '<li>' )
			.addClass( 'toggle-list-item gaon-menu-item gaon-menu-item-level' + level );

		if ( hasChildren ) {
			$li.addClass( 'gaon-menu-item--has-children gaon-menu-item--collapsed' );
		}

		// <a> 생성
		var $a = buildAnchor( item.href, item.title || item.label );
		$a.append( buildIcon( item.icon ) );
		$a.append( buildLabel( item.label ) );
		$li.append( $a );
		$ul.append( $li );

		// 하위 레벨이 있으면 <ul> 을 만들어 재귀 호출
		if ( hasChildren ) {
			var nextLevel = level + 1;
			var $subUl = $( '<ul>' )
				.addClass( 'gaon-menu gaon-menu-level' + nextLevel );

			item.children.forEach( function ( child ) {
				appendGaonMenuItem( $subUl, child, nextLevel );
			} );

			// 이 부분이 중요:
			// 하위 <ul> 은 반드시 <li> 안에 들어가야 한다.
			$li.append( $subUl );
		}
	}

	/**
	 * 한 개의 상위 그룹(게시판 / 버그/기능 개선 / 도구 / 도움말)을
	 * Minerva 스타일의 <ul> 블록으로 생성한다.
	 *
	 * @param {Object} group   그룹 정의
	 * @param {number} index   그룹 index
	 * @return {jQuery}        완성된 <ul> 요소
	 */
	function buildGroup( group, index ) {
		// Minerva 기본 메뉴와 같은 class 사용
		var $level1 = $( '<ul>' )
			.addClass( 'toggle-list__list gaon-menu gaon-menu-level1' )
			.attr( 'data-gaon-group-index', index );

		var hasChildren = Array.isArray( group.children ) && group.children.length > 0;

		// 헤더 <li>
		var $headerLi = $( '<li>' )
			.addClass( 'toggle-list-item gaon-menu-header gaon-menu-header--collapsed' );

		var $headerA = buildAnchor( '#', group.label );
		$headerA
			.addClass( 'gaon-menu-header__anchor' )
			.append( buildIcon( group.icon ) )
			.append( buildLabel( group.label ) );

		$headerLi.append( $headerA );

		// 그룹에 하위 항목이 있으면 level2 <ul> 생성
		if ( hasChildren ) {
			var $level2 = $( '<ul>' )
				.addClass( 'gaon-menu gaon-menu-level2' );

			group.children.forEach( function ( item ) {
				appendGaonMenuItem( $level2, item, 2 );
			} );

			// 헤더 <li> 안에 하위 <ul> 을 넣는다.
			$headerLi.append( $level2 );
		}

		$level1.append( $headerLi );
		return $level1;
	}

	/* -------------------------------------------------
	 * 3. 메뉴를 실제 Minerva 햄버거 메뉴 안에 삽입
	 * ------------------------------------------------- */

	/**
	 * 가온 메뉴를 Minerva 왼쪽 메뉴에 삽입한다.
	 */
	function insertGaonMenuIntoMinerva() {
		// 기본 내비게이션 블록(대문/임의의 문서로)이 들어 있는 첫 번째 toggle-list__list 를 찾는다.
		var $firstList = $( '#p-navigation.toggle-list__list' ).first();

		// 혹시 id 가 다르다면 fallback: 메뉴 안의 첫 toggle-list__list
		if ( !$firstList.length ) {
			$firstList = $( '#mw-mf-page-left .toggle-list__list' ).first();
		}

		if ( !$firstList.length ) {
			return;
		}

		// 중복 삽입 방지
		if ( $firstList.data( 'gaonMenuInjected' ) ) {
			return;
		}
		$firstList.data( 'gaonMenuInjected', true );

		// 여러 그룹을 한 번에 생성하여 첫 번째 리스트 뒤에 삽입
		var $allGroups = $( [] );
		gaonMenuConfig.forEach( function ( group, index ) {
			$allGroups = $allGroups.add( buildGroup( group, index ) );
		} );

		$firstList.after( $allGroups );

		// 모든 하위 레벨을 기본적으로 숨긴다.
		$( '.gaon-menu-level2, .gaon-menu-level3' ).hide();

		// 상위 그룹 헤더 클릭 시 level2 토글
		$( '.gaon-menu-header__anchor' ).on( 'click', function ( e ) {
			e.preventDefault();

			var $headerLi = $( this ).closest( '.gaon-menu-header' );
			var $sub = $headerLi.children( '.gaon-menu-level2' );

			if ( !$sub.length ) {
				return;
			}

			$headerLi.toggleClass( 'gaon-menu-header--collapsed' );
			$sub.slideToggle( 150 );
		} );

		// "파일 올리기"처럼 children 이 있는 2단계 항목 클릭 시 level3 토글
		$( '.gaon-menu-item--has-children > .gaon-menu-link' ).on( 'click', function ( e ) {
			e.preventDefault();

			var $li = $( this ).closest( '.gaon-menu-item--has-children' );
			var $sub = $li.children( '.gaon-menu-level3' );

			if ( !$sub.length ) {
				return;
			}

			$li.toggleClass( 'gaon-menu-item--collapsed' );
			$sub.slideToggle( 150 );
		} );
	}

	/* -------------------------------------------------
	 * 4. 메뉴 HTML 로드 완료를 기다렸다가 삽입
	 * ------------------------------------------------- */

	var maxAttempts = 50; // 최대 50번(약 5초 정도) 시도
	var attemptCount = 0;

	function waitForMinervaMenu() {
		var $firstList = $( '#mw-mf-page-left .toggle-list__list' ).first();

		if ( $firstList.length ) {
			insertGaonMenuIntoMinerva();
		} else if ( attemptCount < maxAttempts ) {
			attemptCount += 1;
			setTimeout( waitForMinervaMenu, 100 );
		}
	}

	$( waitForMinervaMenu );

}( mediaWiki, jQuery ) );