미디어위키:Minerva.js

Admin (토론 / 기여)님의 2025년 11월 19일 (수) 20:03 판 (미네르바 스킨 메뉴 추가)
(차이) ← 이전 판 / 최신판 (차이) / 다음 판 → (차이)

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

  • 파이어폭스 / 사파리: 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 */
/* 미네르바 노이에 전용 커스텀 메뉴 (PC/모바일 공통)
 * MediaWiki:Minerva.js 에 넣는 것을 기준으로 작성되었습니다.
 * 이 스크립트는 미네르바 스킨의 햄버거 메뉴 안에
 * "게시판 / 버그/기능 개선 / 도구 / 도움말" 메뉴와 하위 메뉴를 추가합니다.
 */

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

	// 미네르바 스킨이 아닐 경우 아무 것도 하지 않는다.
	if ( mw.config.get( 'skin' ) !== 'minerva' ) {
		return;
	}

	/* -----------------------------
	 * 1. 메뉴 설정 (질문에서 주신 구조 그대로)
	 * ----------------------------- */

	// 미네르바에서 사용할 아이콘 클래스는
	// https://www.mediawiki.org/wiki/Topic:Vqy1kx6q4e0bzvyb 등에서 정리된 목록에 기반합니다. :contentReference[oaicite:5]{index=5}
	// 필요하면 iconClass 값을 수정하여 원하는 아이콘으로 교체하면 됩니다.

	var gaonMenuConfig = [
		{
			// * icon=comments | display=게시판
			label: '게시판',
			iconClass: 'mw-ui-icon-minerva-speechBubbles',
			children: [
				// ** icon=pencil-alt | display=위키방 | link=https://bbs.gaonwiki.com | title=https://bbs.gaonwiki.com
				{
					label: '위키방',
					href: 'https://bbs.gaonwiki.com',
					title: 'https://bbs.gaonwiki.com',
					iconClass: 'mw-ui-icon-minerva-speechBubbles'
				},
				// ** icon=copyright | display=저작권 처리 게시판 | link=https://bbs.gaonwiki.com/copyrights | title=https://bbs.gaonwiki.com/copyrights
				{
					label: '저작권 처리 게시판',
					href: 'https://bbs.gaonwiki.com/copyrights',
					title: 'https://bbs.gaonwiki.com/copyrights',
					iconClass: 'mw-ui-icon-minerva-speechBubbles'
				},
				// ** icon=comments | display=자유게시판 | link=https://bbs.gaonwiki.com/free | title=https://bbs.gaonwiki.com/free
				{
					label: '자유게시판',
					href: 'https://bbs.gaonwiki.com/free',
					title: 'https://bbs.gaonwiki.com/free',
					iconClass: 'mw-ui-icon-minerva-speechBubbles'
				}
			]
		},
		{
			// * icon=exclamation | display=버그/기능 개선
			label: '버그/기능 개선',
			iconClass: 'mw-ui-icon-minerva-die', // 경고/이슈 느낌의 주사위 아이콘 사용 예시
			children: [
				// ** icon=bullhorn | display=이슈 등록 | link=https://bbs.gaonwiki.com/issue | title=https://bbs.gaonwiki.com/issue
				{
					label: '이슈 등록',
					href: 'https://bbs.gaonwiki.com/issue',
					title: 'https://bbs.gaonwiki.com/issue',
					iconClass: 'mw-ui-icon-minerva-die'
				},
				// ** icon=hands-helping | display=기능 구현 도움 | link=... | title=...
				{
					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/가온_위키:기능_구현_불가-누가_해결책을_알려주세요!',
					iconClass: 'mw-ui-icon-wikimedia-edit-base20'
				},
				// ** icon=bug | display=알려진 문제들 | link=... | title=...
				{
					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/가온_위키/알려진_문제들',
					iconClass: 'mw-ui-icon-wikimedia-history-base20'
				}
			]
		},
		{
			// * icon=gear | display=도구
			label: '도구',
			iconClass: 'mw-ui-icon-minerva-settings',
			children: [
				// ** icon=wrench | display=특수 문서 목록 | link=특수:특수문서 | title=특수:특수문서
				{
					label: '특수 문서 목록',
					// mw.util.getUrl 을 사용하면 위키 내 링크를 환경에 맞게 자동으로 생성합니다.
					href: mw.util.getUrl( '특수:특수문서' ),
					title: '특수:특수문서',
					iconClass: 'mw-ui-icon-minerva-settings'
				},
				// ** icon=upload | display=파일 올리기
				{
					label: '파일 올리기',
					href: '#', // 상위 항목은 드롭다운용이므로 실제 링크는 서브 메뉴에 둡니다.
					title: '파일 올리기',
					iconClass: 'mw-ui-icon-minerva-download',
					children: [
						// *** icon=cloud-upload-alt | display=(기존) 업로드 | link=특수:올리기 | title=특수:올리기
						{
							label: '(기존) 업로드',
							href: mw.util.getUrl( '특수:올리기' ),
							title: '특수:올리기',
							iconClass: 'mw-ui-icon-minerva-download'
						},
						// *** icon=file-upload | display=(신규) 업로드 마법사 | link=특수:UploadWizard | title=특수:UploadWizard
						{
							label: '(신규) 업로드 마법사',
							href: mw.util.getUrl( '특수:UploadWizard' ),
							title: '특수:UploadWizard',
							iconClass: 'mw-ui-icon-minerva-download'
						}
					]
				},
				// ** icon=flag | display=이슈 트래커 | link=https://bbs.gaonwiki.com/issue/ | title=...
				{
					label: '이슈 트래커',
					href: 'https://bbs.gaonwiki.com/issue/',
					title: 'https://bbs.gaonwiki.com/issue/',
					iconClass: 'mw-ui-icon-minerva-die'
				},
				// ** icon=flask | display=가온 위키 베타 | link=https://beta.gaonwiki.com | title=...
				{
					label: '가온 위키 베타',
					href: 'https://beta.gaonwiki.com',
					title: 'https://beta.gaonwiki.com',
					iconClass: 'mw-ui-icon-minerva-mapPin'
				}
			]
		},
		{
			// * icon=book | display=도움말
			label: '도움말',
			iconClass: 'mw-ui-icon-wikimedia-help-base20', // 도움말 느낌의 아이콘
			children: [
				// ** icon=spell-check | display=위키 문법 | link=도움말:위키_문법 | title=도움말:위키_문법
				{
					label: '위키 문법',
					href: mw.util.getUrl( '도움말:위키_문법' ),
					title: '도움말:위키_문법',
					iconClass: 'mw-ui-icon-wikimedia-help-base20'
				},
				// ** icon=book | display=설명서 | link=도움말:설명서 | title=도움말:설명서
				{
					label: '설명서',
					href: mw.util.getUrl( '도움말:설명서' ),
					title: '도움말:설명서',
					iconClass: 'mw-ui-icon-wikimedia-help-base20'
				}
			]
		}
	];

	/* -----------------------------
	 * 2. DOM 유틸리티 함수
	 * ----------------------------- */

	/**
	 * 하위 항목(2단계, 3단계)을 생성하고, 지정된 <ul> 안에 넣는 재귀 함수입니다.
	 *
	 * @param {jQuery} $ul    현재 레벨의 <ul> 요소(jQuery 객체)
	 * @param {Object} item   메뉴 항목 정의 (label, href, title, iconClass, children 등)
	 * @param {number} level  현재 레벨(2, 3 ...)
	 */
	function appendGaonMenuItem( $ul, item, level ) {
		// <li> 생성
		var $li = $( '<li>' )
			.addClass( 'gaon-menu-item' )
			.addClass( 'gaon-menu-item-level' + level );

		// 실제 링크가 없고 드롭다운 전용인 항목이면 href 를 '#' 으로 둡니다.
		var href = item.href || '#';

		// <a> 생성 및 속성 설정
		var $a = $( '<a>' )
			.attr( 'href', href )
			// title 이 지정되어 있으면 title 사용, 없으면 label 사용
			.attr( 'title', item.title || item.label || '' )
			// 미네르바 아이콘 스타일 적용
			.addClass( 'mw-ui-icon mw-ui-icon-before' );

		// 아이콘 클래스가 지정되어 있다면 추가
		if ( item.iconClass ) {
			$a.addClass( item.iconClass );
		}

		// 화면에 표시될 텍스트는 <span> 안에 넣습니다.
		var $span = $( '<span>' ).text( item.label || '' );
		$a.append( $span );

		$li.append( $a );
		$ul.append( $li );

		// children 이 있다면 하위 <ul> 을 하나 더 만들어 재귀적으로 추가합니다.
		if ( Array.isArray( item.children ) && item.children.length > 0 ) {
			var nextLevel = level + 1;
			var $subUl = $( '<ul>' )
				.addClass( 'gaon-menu' )
				.addClass( 'gaon-menu-level' + nextLevel );

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

			// 여기서는 부모 <li> 아래에 중첩 <ul> 을 넣어 구조를 명확히 합니다.
			$li.append( $subUl );
		}
	}

	/**
	 * 상위 그룹(게시판, 버그/기능 개선 등)을 생성합니다.
	 * 각 그룹은 level1 <ul> 안에 헤더 <li> 와 하위 level2 <ul> 을 가지게 됩니다.
	 *
	 * @returns {jQuery} 햄버거 메뉴에 삽입할 상위 DOM 묶음(jQuery 객체)
	 */
	function buildGaonMenu() {
		// 루트 컨테이너 역할을 할 <div> 를 하나 만든 뒤,
		// 그 안에 여러 개의 <ul class="gaon-menu gaon-menu-level1"> 을 쌓습니다.
		var $container = $( '<div>' ).addClass( 'gaon-menu-container' );

		gaonMenuConfig.forEach( function ( group, index ) {
			// 각 그룹마다 level1 <ul> 하나씩 생성
			var $level1 = $( '<ul>' )
				.addClass( 'gaon-menu' )
				.addClass( 'gaon-menu-level1' )
				.attr( 'data-gaon-group-index', index );

			// 헤더(클릭 시 하위 level2 열고 닫는 부분) <li>
			var $headerLi = $( '<li>' )
				.addClass( 'gaon-menu-header' );

			var $headerA = $( '<a>' )
				// 상위 그룹은 토글 전용이므로 href="#" 로 두고 기본 동작을 막습니다.
				.attr( 'href', '#' )
				.attr( 'title', group.label || '' )
				.addClass( 'mw-ui-icon mw-ui-icon-before' );

			if ( group.iconClass ) {
				$headerA.addClass( group.iconClass );
			}

			$headerA.append(
				$( '<span>' ).text( group.label || '' )
			);

			$headerLi.append( $headerA );
			$level1.append( $headerLi );

			// 하위 메뉴(level2) 생성
			if ( Array.isArray( group.children ) && group.children.length > 0 ) {
				var $level2 = $( '<ul>' )
					.addClass( 'gaon-menu' )
					.addClass( 'gaon-menu-level2' );

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

				$level1.append( $level2 );
			}

			$container.append( $level1 );
		} );

		// 컨테이너 안의 자식 요소들만 반환합니다.
		return $container.children();
	}

	/* -----------------------------
	 * 3. 메뉴를 미네르바 햄버거 메뉴 안에 삽입
	 * ----------------------------- */

	/**
	 * 실제로 DOM 을 찾아서, 기본 메뉴 아래에 가온 메뉴를 삽입하는 함수입니다.
	 */
	function insertGaonMenuIntoMinerva() {
		// 미네르바 햄버거 메뉴에서, 기존 최상위 <ul> 을 찾습니다.
		// Qiita 예제에서는 '.menu ul:first' 를 기준으로 사용합니다. :contentReference[oaicite:6]{index=6}
		var $firstUl = $( '.menu ul:first' );

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

		// 중복 삽입 방지: 이미 가온 메뉴를 추가했으면 다시 하지 않습니다.
		if ( $firstUl.data( 'gaonMenuInjected' ) ) {
			return;
		}
		$firstUl.data( 'gaonMenuInjected', true );

		// 우리의 메뉴 DOM 을 생성합니다.
		var $gaonMenuNodes = buildGaonMenu();

		// 기존 첫 번째 <ul> 바로 뒤에 삽입합니다.
		$firstUl.after( $gaonMenuNodes );

		// 하위 레벨 메뉴(level2, level3 …)는 기본적으로 숨겨 둡니다.
		$( '.gaon-menu-level2, .gaon-menu-level3' ).hide();

		// 상위 그룹(게시판 / 버그/기능 개선 / 도구 / 도움말) 클릭 시,
		// 해당 그룹 내부의 level2 <ul> 을 토글합니다.
		$( '.gaon-menu-header > a' ).on( 'click', function ( e ) {
			e.preventDefault();

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

			$level2.slideToggle( 200 );
		} );

		// "파일 올리기" 같은 중간 단계에 children 이 있는 항목은
		// 자신의 하위 level3 <ul> 을 토글합니다.
		$( '.gaon-menu-item-level2 > a' ).each( function () {
			var $a = $( this );
			var $li = $a.closest( 'li' );
			var $subUl = $li.children( '.gaon-menu-level3' );

			if ( $subUl.length ) {
				// children 이 있는 항목은 토글 버튼 역할을 하도록 클릭 핸들러를 붙입니다.
				$a.on( 'click', function ( e ) {
					e.preventDefault();
					$subUl.slideToggle( 200 );
				} );
			}
		} );
	}

	/* -----------------------------
	 * 4. 메뉴 DOM 생성 시점 기다리기
	 * ----------------------------- */

	// 미네르바 메뉴 HTML 은 JS 로 동적으로 만들어지는 경우가 있기 때문에,
	// DOM 이 준비될 때까지 주기적으로 검사하는 방식(Qiita 예제와 동일한 방식)을 사용합니다. :contentReference[oaicite:7]{index=7}

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

	function waitForMinervaMenu() {
		var $firstUl = $( '.menu ul:first' );

		if ( $firstUl.length ) {
			// 메뉴를 찾았으면 바로 삽입 시도
			insertGaonMenuIntoMinerva();
		} else if ( attemptCount < maxAttempts ) {
			attemptCount += 1;
			setTimeout( waitForMinervaMenu, 100 );
		}
	}

	// 문서 로드 이후에 검사 시작
	$( waitForMinervaMenu );

}( mediaWiki, jQuery ) );

최근 바뀜

더 보기