미디어위키:Gadget-newtoolbar.js: 두 판 사이의 차이

편집 요약 없음
태그: 되돌려진 기여
편집 요약 없음
태그: 되돌려진 기여
473번째 줄: 473번째 줄:
} )
} )
);
);
// ... initNewToolbar 안쪽 ...


// --- 그룹 4: 기타 도구 ---
// --- 그룹 4: 기타 도구 ---
525번째 줄: 523번째 줄:
$oldToolbar.hide().before( $toolbar );
$oldToolbar.hide().before( $toolbar );
}
}
);


// wikiEditor 준비되면 호출
// wikiEditor 준비되면 호출

2025년 11월 13일 (목) 22:33 판

/* MediaWiki:Gadget-NewToolbar.js */
/* MediaWiki:Gadget-NewToolbar.js */
( function ( mw, $ ) {
	'use strict';

	// ---- 찾기/바꾸기 상태 ----
	var findState = {
		query: '',
		lastIndex: 0
	};

	/**
	 * 선택 영역을 앞뒤 텍스트로 감싸기 (간단 버전)
	 */
	function surroundSelection( textarea, before, after ) {
		var $ta = $( textarea );
		var start = textarea.selectionStart;
		var end = textarea.selectionEnd;

		if ( start == null || end == null ) {
			$ta.text( $ta.text() + before + after );
			return;
		}

		var text = $ta.val();
		var selected = text.slice( start, end );
		var newText = text.slice( 0, start ) + before + selected + after + text.slice( end );

		$ta.val( newText );

		var newStart = start + before.length;
		var newEnd = newStart + selected.length;
		textarea.selectionStart = newStart;
		textarea.selectionEnd = newEnd;
		$ta.trigger( 'change' );
	}

	function insertAtCursor( textarea, snippet ) {
		var $ta = $( textarea );
		var start = textarea.selectionStart;
		var end = textarea.selectionEnd;
		var text = $ta.val();

		if ( start == null || end == null ) {
			$ta.val( text + snippet );
			return;
		}

		var newText = text.slice( 0, start ) + snippet + text.slice( end );
		$ta.val( newText );
		var pos = start + snippet.length;
		textarea.selectionStart = textarea.selectionEnd = pos;
		$ta.trigger( 'change' );
	}

	function createButton( opts ) {
		var $btn = $( '<button>' )
			.attr( 'type', 'button' )
			.addClass( 'ntb-button' )
			.append(
				$( '<span>' )
					.addClass( 'ntb-icon ' + ( opts.icon || '' ) )
			)
			.append(
				$( '<span>' )
					.addClass( 'ntb-label' )
					.text( opts.label )
			)
			.on( 'click', function () {
				var textarea = document.getElementById( 'wpTextbox1' );
				if ( typeof opts.onClick === 'function' ) {
					opts.onClick( textarea || null );
				}
				if ( textarea ) {
					textarea.focus();
				}
			} );

		if ( opts.title ) {
			$btn.attr( 'title', opts.title );
		}

		return $btn;
	}

	// ---- 찾기/바꾸기 패널 만들기 ----
	function buildFindReplacePanel() {
		if ( $( '#ntb-find-panel' ).length ) {
			return $( '#ntb-find-panel' );
		}

		var $panel = $( '<div>' )
			.attr( 'id', 'ntb-find-panel' )
			.addClass( 'ntb-find-panel' );

		var $rowFind = $( '<div>' ).addClass( 'ntb-find-row' );
		var $rowReplace = $( '<div>' ).addClass( 'ntb-find-row' );
		var $rowButtons = $( '<div>' ).addClass( 'ntb-find-row ntb-find-row-buttons' );

		var $inputFind = $( '<input>' )
			.attr( {
				type: 'text',
				placeholder: '찾을 내용'
			} )
			.addClass( 'ntb-input ntb-input-find' );

		var $inputReplace = $( '<input>' )
			.attr( {
				type: 'text',
				placeholder: '바꿀 내용'
			} )
			.addClass( 'ntb-input ntb-input-replace' );

		var $status = $( '<div>' )
			.addClass( 'ntb-find-status' )
			.text( '' );

		var $btnNext = $( '<button>' )
			.attr( 'type', 'button' )
			.addClass( 'ntb-button ntb-button-small' )
			.text( '다음 찾기' );

		var $btnReplace = $( '<button>' )
			.attr( 'type', 'button' )
			.addClass( 'ntb-button ntb-button-small' )
			.text( '바꾸기' );

		var $btnReplaceAll = $( '<button>' )
			.attr( 'type', 'button' )
			.addClass( 'ntb-button ntb-button-small' )
			.text( '모두 바꾸기' );

		var $btnClose = $( '<button>' )
			.attr( 'type', 'button' )
			.addClass( 'ntb-button ntb-button-small ntb-button-quiet' )
			.text( '닫기' );

		$rowFind.append( $( '<label>' ).text( '찾기: ' ), $inputFind );
		$rowReplace.append( $( '<label>' ).text( '바꾸기: ' ), $inputReplace );
		$rowButtons.append( $btnNext, $btnReplace, $btnReplaceAll, $btnClose, $status );

		$panel.append( $rowFind, $rowReplace, $rowButtons );

		// ---- 동작 함수들 ----
		function findNext( wrap ) {
			var textarea = document.getElementById( 'wpTextbox1' );
			if ( !textarea ) {
				return;
			}
			var query = $inputFind.val();
			if ( !query ) {
				$status.text( '찾을 내용을 입력하세요.' );
				return;
			}

			var text = textarea.value;
			var startPos;

			// 새 검색어면 처음부터
			if ( findState.query !== query ) {
				findState.query = query;
				findState.lastIndex = textarea.selectionEnd || 0;
			}

			startPos = textarea.selectionEnd || findState.lastIndex;
			var idx = text.indexOf( query, startPos );

			// 못 찾으면, wrap이면 처음부터 다시
			if ( idx === -1 && wrap !== false ) {
				idx = text.indexOf( query, 0 );
			}

			if ( idx === -1 ) {
				$status.text( '더 이상 찾을 수 없습니다.' );
				return;
			}

			textarea.selectionStart = idx;
			textarea.selectionEnd = idx + query.length;
			findState.lastIndex = idx + query.length;
			textarea.focus();
			$status.text( '' );
		}

		function doReplaceOnce() {
			var textarea = document.getElementById( 'wpTextbox1' );
			if ( !textarea ) {
				return;
			}
			var query = $inputFind.val();
			var replacement = $inputReplace.val() || '';
			if ( !query ) {
				$status.text( '찾을 내용을 입력하세요.' );
				return;
			}

			var text = textarea.value;
			var selStart = textarea.selectionStart;
			var selEnd = textarea.selectionEnd;
			var current = text.slice( selStart, selEnd );

			// 현재 선택이 검색어와 다르면 먼저 찾기
			if ( current !== query ) {
				findNext( false );
				// 다시 selection 갱신
				text = textarea.value;
				selStart = textarea.selectionStart;
				selEnd = textarea.selectionEnd;
				current = text.slice( selStart, selEnd );
				if ( current !== query ) {
					$status.text( '대상이 선택되지 않았습니다.' );
					return;
				}
			}

			var newText = text.slice( 0, selStart ) + replacement + text.slice( selEnd );
			textarea.value = newText;

			var newPos = selStart + replacement.length;
			textarea.selectionStart = textarea.selectionEnd = newPos;
			findState.lastIndex = newPos;
			$( textarea ).trigger( 'change' );

			$status.text( '1개 바꿈.' );
		}

		function doReplaceAll() {
			var textarea = document.getElementById( 'wpTextbox1' );
			if ( !textarea ) {
				return;
			}
			var query = $inputFind.val();
			var replacement = $inputReplace.val() || '';
			if ( !query ) {
				$status.text( '찾을 내용을 입력하세요.' );
				return;
			}

			var text = textarea.value;
			var count = 0;
			var idx = text.indexOf( query );

			if ( idx === -1 ) {
				$status.text( '일치하는 내용이 없습니다.' );
				return;
			}

			while ( idx !== -1 ) {
				text = text.slice( 0, idx ) + replacement + text.slice( idx + query.length );
				count++;
				idx = text.indexOf( query, idx + replacement.length );
			}

			textarea.value = text;
			textarea.selectionStart = textarea.selectionEnd = 0;
			findState.lastIndex = 0;
			$( textarea ).trigger( 'change' );

			$status.text( count + '개 바꿈.' );
		}

		// 이벤트 연결
		$btnNext.on( 'click', function () {
			findNext( true );
		} );

		$inputFind.on( 'keydown', function ( e ) {
			if ( e.key === 'Enter' ) {
				e.preventDefault();
				findNext( true );
			}
		} );

		$btnReplace.on( 'click', doReplaceOnce );
		$btnReplaceAll.on( 'click', doReplaceAll );

		$btnClose.on( 'click', function () {
			$panel.hide();
		} );

		return $panel;
	}

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

	/**
	 * 선택 영역을 앞뒤 텍스트로 감싸기 (간단 버전)
	 */
	function surroundSelection( textarea, before, after ) {
		var $ta = $( textarea );
		var start = textarea.selectionStart;
		var end = textarea.selectionEnd;

		if ( start == null || end == null ) {
			// 구형 브라우저 fallback (거의 안 씀)
			$ta.text( $ta.text() + before + after );
			return;
		}

		var text = $ta.val();
		var selected = text.slice( start, end );
		var newText = text.slice( 0, start ) + before + selected + after + text.slice( end );

		$ta.val( newText );

		// 커서/선택 다시 잡기
		var newStart = start + before.length;
		var newEnd = newStart + selected.length;
		textarea.selectionStart = newStart;
		textarea.selectionEnd = newEnd;
		$ta.trigger( 'change' );
	}

	/**
	 * 현재 커서 위치에 텍스트 삽입
	 */
	function insertAtCursor( textarea, snippet ) {
		var $ta = $( textarea );
		var start = textarea.selectionStart;
		var end = textarea.selectionEnd;
		var text = $ta.val();

		if ( start == null || end == null ) {
			$ta.val( text + snippet );
			return;
		}

		var newText = text.slice( 0, start ) + snippet + text.slice( end );
		$ta.val( newText );
		var pos = start + snippet.length;
		textarea.selectionStart = textarea.selectionEnd = pos;
		$ta.trigger( 'change' );
	}

	/**
	 * 버튼 생성 헬퍼
	 */
	function createButton( opts ) {
		var $btn = $( '<button>' )
			.attr( 'type', 'button' )
			.addClass( 'ntb-button' )
			.append(
				$( '<span>' )
					.addClass( 'ntb-icon ' + ( opts.icon || '' ) )
			)
			.append(
				$( '<span>' )
					.addClass( 'ntb-label' )
					.text( opts.label )
			)
			.on( 'click', function () {
				var textarea = document.getElementById( 'wpTextbox1' );
				if ( textarea && typeof opts.onClick === 'function' ) {
					opts.onClick( textarea );
					textarea.focus();
				}
			} );

		if ( opts.title ) {
			$btn.attr( 'title', opts.title );
		}

		return $btn;
	}

	/**
	 * 새 툴바 초기화
	 */
	function initNewToolbar( $textarea ) {
		var $oldToolbar = $( '#wikiEditor-ui-toolbar' );

		// wikiEditor 없는 페이지 or 이미 생성된 경우
		if ( !$oldToolbar.length || $( '#ntb-toolbar' ).length ) {
			return;
		}

		var $toolbar = $( '<div>' )
			.attr( 'id', 'ntb-toolbar' )
			.addClass( 'ntb-toolbar' );

		// --- 그룹 1: 텍스트 스타일 ---
		var $groupText = $( '<div>' )
			.addClass( 'ntb-group ntb-group-text' )
			.append(
				$( '<span>' ).addClass( 'ntb-group-label' ).text( '텍스트' )
			);

		$groupText.append(
			createButton( {
				icon: 'ntb-icon-bold',
				label: '굵게',
				title: "'''굵게'''",
				onClick: function ( ta ) {
					surroundSelection( ta, "'''", "'''" );
				}
			} ),
			createButton( {
				icon: 'ntb-icon-italic',
				label: '기울임',
				title: "''기울임''",
				onClick: function ( ta ) {
					surroundSelection( ta, "''", "''" );
				}
			} )
		);

		// --- 그룹 2: 문단/목록 ---
		var $groupBlock = $( '<div>' )
			.addClass( 'ntb-group ntb-group-block' )
			.append(
				$( '<span>' ).addClass( 'ntb-group-label' ).text( '문단' )
			);

		$groupBlock.append(
			createButton( {
				icon: 'ntb-icon-heading',
				label: '제목',
				title: '== 문단 제목 ==',
				onClick: function ( ta ) {
					insertAtCursor( ta, '\n== 새 문단 제목 ==\n' );
				}
			} ),
			createButton( {
				icon: 'ntb-icon-ul',
				label: '글머리 목록',
				title: '* 글머리',
				onClick: function ( ta ) {
					insertAtCursor( ta, '\n* 항목\n' );
				}
			} ),
			createButton( {
				icon: 'ntb-icon-ol',
				label: '번호 목록',
				title: '# 번호 항목',
				onClick: function ( ta ) {
					insertAtCursor( ta, '\n# 항목\n' );
				}
			} )
		);

		// --- 그룹 3: 삽입 ---
		var $groupInsert = $( '<div>' )
			.addClass( 'ntb-group ntb-group-insert' )
			.append(
				$( '<span>' ).addClass( 'ntb-group-label' ).text( '삽입' )
			);

		$groupInsert.append(
			createButton( {
				icon: 'ntb-icon-link',
				label: '링크',
				title: '[[문서 링크]] 또는 [https://example.com 외부 링크]',
				onClick: function ( ta ) {
					insertAtCursor( ta, '[[링크 대상|표시 텍스트]]' );
				}
			} ),
			createButton( {
				icon: 'ntb-icon-file',
				label: '파일',
				title: '[[파일:Example.png|thumb|캡션]]',
				onClick: function ( ta ) {
					insertAtCursor( ta, '[[파일:Example.png|thumb|캡션]]' );
				}
			} ),
			createButton( {
				icon: 'ntb-icon-ref',
				label: '각주',
				title: '<ref>내용</ref>',
				onClick: function ( ta ) {
					surroundSelection( ta, '<ref>', '</ref>' );
				}
			} )
		);

		// --- 그룹 4: 기타 도구 ---
		var $groupTools = $( '<div>' )
			.addClass( 'ntb-group ntb-group-tools' )
			.append(
				$( '<span>' ).addClass( 'ntb-group-label' ).text( '도구' )
			);

		$groupTools.append(
			createButton( {
				icon: 'ntb-icon-search',
				label: '찾기/바꾸기',
				title: '문서 내용 찾기 및 바꾸기',
				onClick: function () {
					var $panel = buildFindReplacePanel();
					// 토글
					if ( $panel.is( ':visible' ) ) {
						$panel.hide();
					} else {
						$panel.show();
						// 처음 열 때는 찾기 입력에 포커스
						$panel.find( '.ntb-input-find' ).focus().select();
					}
				}
			} )
		);

		$toolbar
			.append( $groupText )
			.append( $groupBlock )
			.append( $groupInsert )
			.append( $groupTools );

		// 기존 툴바 숨기고, 새 툴바 삽입
		$oldToolbar.hide().before( $toolbar );

		// 찾기/바꾸기 패널은 툴바 바로 아래에 붙이되 처음엔 숨김
		var $panel = buildFindReplacePanel().hide();
		$toolbar.after( $panel );
	}

		$toolbar
			.append( $groupText )
			.append( $groupBlock )
			.append( $groupInsert )
			.append( $groupTools );

		// 기존 툴바 숨기고, 새 툴바 삽입
		$oldToolbar.hide().before( $toolbar );
	}
);

	// wikiEditor 준비되면 호출
	mw.hook( 'wikiEditor.toolbarReady' ).add( initNewToolbar );

}( mediaWiki, jQuery ) );