미디어위키:Gadget-newtoolbar.js: 두 판 사이의 차이
편집 요약 없음 태그: 되돌려진 기여 |
편집 요약 없음 |
||
| (같은 사용자의 중간 판 35개는 보이지 않습니다) | |||
| 1번째 줄: | 1번째 줄: | ||
/* MediaWiki:Gadget-NewToolbar.js */ | /* MediaWiki:Gadget-NewToolbar.js */ | ||
( function ( mw, $ ) { | ( function ( mw, $ ) { | ||
'use strict'; | 'use strict'; | ||
/** | /** | ||
| 19번째 줄: | 12번째 줄: | ||
if ( start == null || end == null ) { | if ( start == null || end == null ) { | ||
// 구형 브라우저 fallback (거의 안 씀) | |||
$ta.text( $ta.text() + before + after ); | $ta.text( $ta.text() + before + after ); | ||
return; | return; | ||
| 29번째 줄: | 23번째 줄: | ||
$ta.val( newText ); | $ta.val( newText ); | ||
// 커서/선택 다시 잡기 | |||
var newStart = start + before.length; | var newStart = start + before.length; | ||
var newEnd = newStart + selected.length; | var newEnd = newStart + selected.length; | ||
| 36번째 줄: | 31번째 줄: | ||
} | } | ||
/** | |||
* 현재 커서 위치에 텍스트 삽입 | |||
*/ | |||
function insertAtCursor( textarea, snippet ) { | function insertAtCursor( textarea, snippet ) { | ||
var $ta = $( textarea ); | var $ta = $( textarea ); | ||
| 53번째 줄: | 51번째 줄: | ||
$ta.trigger( 'change' ); | $ta.trigger( 'change' ); | ||
} | } | ||
/** | |||
* 간단 알림 (mw.notify 있으면 그걸 쓰고, 없으면 alert) | |||
*/ | |||
function notify( msg ) { | |||
if ( mw && mw.notify ) { | |||
mw.notify( msg ); | |||
} else { | |||
alert( msg ); | |||
} | } | ||
} | |||
/** | |||
* 찾기/바꾸기 상태 | |||
*/ | |||
var findState = { | |||
open: false, | |||
term: '', | |||
lastIndex: 0 | |||
}; | |||
return | function getMainTextarea() { | ||
return document.getElementById( 'wpTextbox1' ); | |||
} | } | ||
/ | /** | ||
* 찾기/바꾸기 패널 만들기 (없으면 생성, 있으면 그대로 반환) | |||
*/ | |||
function createFindPanel( textarea ) { | |||
var $panel = $( '#ntb-find-panel' ); | |||
if ( $panel.length ) { | |||
return $panel; | |||
} | |||
$panel = $( '<div>' ) | |||
.attr( 'id', 'ntb-find-panel' ) | |||
.addClass( 'ntb-find-panel' ); | |||
var $rowFind = $( '<div>' ).addClass( 'ntb-find-row' ); | |||
$rowFind.append( | |||
$( '<label>' ).text( '찾기' ), | |||
$( '<input>' ).attr( { type: 'text', id: 'ntb-find-text' } ) | |||
); | |||
var $rowReplace = $( '<div>' ).addClass( 'ntb-find-row' ); | |||
$rowReplace.append( | |||
$( '<label>' ).text( '바꾸기' ), | |||
$( '<input>' ).attr( { type: 'text', id: 'ntb-replace-text' } ) | |||
); | |||
var $btns = $( '<div>' ).addClass( 'ntb-find-buttons' ); | |||
var $btnNext = $( '<button>' ).attr( 'type', 'button' ).text( '다음 찾기' ); | |||
var $btnReplace = $( '<button>' ).attr( 'type', 'button' ).text( '바꾸기' ); | |||
var $btnReplaceAll = $( '<button>' ).attr( 'type', 'button' ).text( '모두 바꾸기' ); | |||
var $btnClose = $( '<button>' ).attr( 'type', 'button' ).text( '닫기' ); | |||
$btns.append( $btnNext, $btnReplace, $btnReplaceAll, $btnClose ); | |||
$panel.append( $rowFind, $rowReplace, $btns ); | |||
// 🔹 여기부터 새로 추가: Enter 키 막기 + 원하는 동작으로 바꾸기 | |||
$panel.on( 'keydown', 'input', function ( e ) { | |||
if ( e.key === 'Enter' || e.keyCode === 13 ) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
var ta = document.getElementById( 'wpTextbox1' ); | |||
if ( !ta ) { | |||
return; | |||
} | |||
if ( this.id === 'ntb-find-text' ) { | |||
// 찾기 칸에서 Enter → 다음 찾기 | |||
findNext( ta ); | |||
} else if ( this.id === 'ntb-replace-text' ) { | |||
// 바꾸기 칸에서 Enter → 현재 항목 바꾸기 | |||
replaceOne( ta ); | |||
} | |||
} | |||
} ); | |||
// 버튼 동작 정의 | |||
$btnNext.on( 'click', function () { | |||
var textarea = document.getElementById( 'wpTextbox1' ); | |||
findNext( textarea ); | |||
} ); | |||
$btnReplace.on( 'click', function () { | |||
var textarea = document.getElementById( 'wpTextbox1' ); | |||
replaceOne( textarea ); | |||
} ); | |||
$btnReplaceAll.on( 'click', function () { | |||
var textarea = document.getElementById( 'wpTextbox1' ); | |||
replaceAll( textarea ); | |||
} ); | |||
$btnClose.on( 'click', function () { | |||
closeFindPanel(); | |||
} ); | |||
return $panel; | |||
} | |||
/** | |||
* 찾기/바꾸기 패널 열기/닫기 토글 | |||
*/ | |||
function toggleFindPanel( textarea ) { | |||
var $panel = createFindPanel(); | |||
if ( findState.open ) { | |||
closeFindPanel(); | |||
} else { | |||
openFindPanel( $panel, textarea ); | |||
} | |||
} | |||
function openFindPanel( $panel, textarea ) { | |||
var $toolbar = $( '#ntb-toolbar' ); | |||
if ( !$panel.parent().length ) { | |||
$toolbar.after( $panel ); | |||
} | |||
$panel.show(); | |||
findState.open = true; | |||
findState.term = ''; | |||
findState.lastIndex = 0; | |||
// 현재 선택 텍스트가 있으면 "찾기" 칸에 채우기 | |||
$ | try { | ||
var start = textarea.selectionStart || 0; | |||
var end = textarea.selectionEnd || 0; | |||
var sel = textarea.value.slice( start, end ); | |||
if ( sel ) { | |||
$( '#ntb-find-text' ).val( sel ); | |||
} | } | ||
} catch (e) { | |||
// selectionStart를 지원하지 않는 경우는 무시 | |||
} | } | ||
$( '#ntb-find-text' ).focus().select(); | |||
} | |||
function closeFindPanel() { | |||
var $panel = $( '#ntb-find-panel' ); | |||
$panel.hide(); | |||
findState.open = false; | |||
} | |||
/** | |||
* 다음 찾기 | |||
*/ | |||
function findNext( textarea ) { | |||
var $findInput = $( '#ntb-find-text' ); | |||
if ( !$findInput.length ) { | |||
return; | |||
} | |||
var term = $findInput.val(); | |||
if ( !term ) { | |||
notify( '찾을 문자열을 입력하세요.' ); | |||
return; | |||
} | |||
var text = textarea.value; | |||
// selection 관련 값을 안전하게 얻기 | |||
textarea.selectionStart = textarea.selectionEnd | var currentStart = 0; | ||
var currentEnd = 0; | |||
try { | |||
currentStart = textarea.selectionStart || 0; | |||
currentEnd = textarea.selectionEnd || 0; | |||
} catch (e) { | |||
currentStart = currentEnd = 0; | |||
} | |||
// 검색어가 바뀌면 처음부터 시작 | |||
if ( findState.term !== term ) { | |||
findState.term = term; | |||
findState.lastIndex = currentEnd; | |||
} | } | ||
var startFrom = Math.max( currentEnd, findState.lastIndex ); | |||
var idx = text.indexOf( term, startFrom ); | |||
// 못 찾으면 처음부터 다시 | |||
if ( idx === -1 && startFrom !== 0 ) { | |||
idx = text.indexOf( term, 0 ); | |||
} | |||
if ( idx === -1 ) { | |||
notify( '더 이상 찾을 문자열이 없습니다.' ); | |||
return; | |||
} | } | ||
textarea.selectionStart = idx; | |||
textarea.selectionEnd = idx + term.length; | |||
textarea.focus(); | |||
findState.lastIndex = idx + term.length; | |||
} | } | ||
/** | /** | ||
* | * 현재 선택된 항목 한 번만 바꾸기 | ||
*/ | */ | ||
function | function replaceOne( textarea ) { | ||
var $ | var $findInput = $( '#ntb-find-text' ); | ||
var | var $replaceInput = $( '#ntb-replace-text' ); | ||
if ( !$findInput.length || !$replaceInput.length ) { | |||
return; | |||
} | |||
var term = $findInput.val(); | |||
var replacement = $replaceInput.val(); | |||
if ( !term ) { | |||
notify( '찾을 문자열을 입력하세요.' ); | |||
return; | return; | ||
} | } | ||
var | var start = 0; | ||
var end = 0; | |||
try { | |||
start = textarea.selectionStart || 0; | |||
end = textarea.selectionEnd || 0; | |||
} catch (e) { | |||
start = end = 0; | |||
} | |||
var text = textarea.value; | |||
var selected = text.slice( start, end ); | var selected = text.slice( start, end ); | ||
if ( selected && selected === term ) { | |||
// 현재 선택 영역이 검색어와 일치하면 교체 | |||
var newText = text.slice( 0, start ) + replacement + text.slice( end ); | |||
textarea.value = newText; | |||
var newEnd = start + replacement.length; | |||
textarea.selectionStart = newEnd; | |||
textarea.selectionEnd = newEnd; | |||
textarea.focus(); | |||
findState.lastIndex = newEnd; | |||
} else { | |||
// 선택이 없거나 일치하지 않으면 "다음 찾기" 먼저 | |||
findNext( textarea ); | |||
} | |||
} | } | ||
/** | /** | ||
* | * 전체 텍스트 모두 바꾸기 | ||
*/ | */ | ||
function | function replaceAll( textarea ) { | ||
var $ | var $findInput = $( '#ntb-find-text' ); | ||
var | var $replaceInput = $( '#ntb-replace-text' ); | ||
var | if ( !$findInput.length || !$replaceInput.length ) { | ||
var | return; | ||
} | |||
var term = $findInput.val(); | |||
var replacement = $replaceInput.val(); | |||
if ( !term ) { | |||
notify( '찾을 문자열을 입력하세요.' ); | |||
return; | |||
} | |||
if ( term === replacement ) { | |||
notify( '같은 문자열로 바꾸려고 합니다.' ); | |||
return; | |||
} | |||
if ( | var text = textarea.value; | ||
if ( text.indexOf( term ) === -1 ) { | |||
notify( '문서 안에 해당 문자열이 없습니다.' ); | |||
return; | return; | ||
} | } | ||
var | // 정규식 특수문자 이스케이프 | ||
var escaped = term.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); | |||
var | var re = new RegExp( escaped, 'g' ); | ||
textarea.selectionStart = textarea.selectionEnd = | var matches = text.match( re ); | ||
var count = matches ? matches.length : 0; | |||
textarea.value = text.replace( re, replacement ); | |||
textarea.selectionStart = textarea.selectionEnd = 0; | |||
textarea.focus(); | |||
findState.lastIndex = 0; | |||
notify( count + '개를 모두 바꿨습니다.' ); | |||
} | } | ||
/** | /** | ||
| 470번째 줄: | 470번째 줄: | ||
onClick: function ( ta ) { | onClick: function ( ta ) { | ||
surroundSelection( ta, '<ref>', '</ref>' ); | surroundSelection( ta, '<ref>', '</ref>' ); | ||
} | |||
} ), | |||
createButton( { | |||
icon: 'ntb-icon-redirect', | |||
label: '넘겨주기', | |||
title: '#넘겨주기 [[대상 문서]]', | |||
onClick: function ( ta ) { | |||
surroundSelection( ta, '#넘겨주기 [[', ']]' ); | |||
} | } | ||
} ) | } ) | ||
); | ); | ||
// --- 그룹 4: 기타 도구 --- | // --- 그룹 4: 기타 도구 --- | ||
var $groupTools = $( '<div>' ) | var $groupTools = $( '<div>' ) | ||
| 487번째 줄: | 493번째 줄: | ||
icon: 'ntb-icon-search', | icon: 'ntb-icon-search', | ||
label: '찾기/바꾸기', | label: '찾기/바꾸기', | ||
title: '문서 | title: '문서 안에서 텍스트 찾기/바꾸기', | ||
onClick: function () { | onClick: function ( ta ) { | ||
toggleFindPanel( ta ); | |||
} | } | ||
} ) | } ) | ||
); | |||
$groupTools.append( | |||
createButton({ | |||
icon: 'ntb-icon-preview', | |||
label: '미리보기', | |||
title: '미리 보기 패널 열기/닫기', | |||
onClick: function (ta) { | |||
togglePreviewPanel(ta); | |||
} | |||
}) | |||
); | |||
$toolbar | $toolbar | ||
| 510번째 줄: | 519번째 줄: | ||
// 기존 툴바 숨기고, 새 툴바 삽입 | // 기존 툴바 숨기고, 새 툴바 삽입 | ||
$oldToolbar.hide().before( $toolbar ); | $oldToolbar.hide().before( $toolbar ); | ||
} | |||
// wikiEditor 준비되면 호출 | |||
mw.hook( 'wikiEditor.toolbarReady' ).add( initNewToolbar ); | |||
}( mediaWiki, jQuery) ); | |||
var previewState = { | |||
open: false, | |||
}; | |||
/** | |||
* 에디터 & 미리보기 레이아웃 준비 | |||
* - #wpTextbox1를 .ntb-edit-area로 감싸고 | |||
* - 가운데에 splitter, 오른쪽에 preview panel을 넣는다. | |||
*/ | |||
function ensureEditLayout(textarea) { | |||
var $ta = $(textarea); | |||
var $ed = $ta.closest('.wikiEditor-ui-text'); | |||
var $wrapper = $ta.parent(); | |||
// preview panel 생성 | |||
var $panel = createPreviewPanel(); | |||
$ | |||
// wrapper 안에 순서대로: textarea | splitter | preview | |||
if ($panel.parent()[0] !== $wrapper[0]) { | |||
$wrapper.append($panel); | |||
} | } | ||
// 초기 flex 비율 설정 (에디터 50%, 미리보기 50%) | |||
$ta.hide(); | |||
$panel.css('width', 'calc(100% - 2rem)'); | |||
$panel.css('padding', '1rem'); | |||
$panel.css('height', '100%'); | |||
} | |||
/** | |||
* 미리보기 패널 상태 | |||
*/ | |||
var previewState = { | |||
open: false | |||
}; | |||
/** | |||
* 미리보기 패널 상태 | |||
*/ | |||
var previewState = { | |||
open: false | |||
}; | |||
/** | |||
* 미리보기 패널 생성 | |||
*/ | |||
function createPreviewPanel() { | |||
var $panel = $('#ntb-preview-panel'); | |||
if ($panel.length) return $panel; | |||
$panel = $('<div>') | |||
.attr('id', 'ntb-preview-panel') | |||
.addClass('ntb-preview-panel') | |||
.html(` | |||
<div class="ntb-preview-title">미리 보기</div> | |||
<div id="ntb-preview-content">내용 없음</div> | |||
`); | |||
return $panel; | |||
} | |||
/** | |||
* 미리보기 켜기/끄기 | |||
*/ | |||
function togglePreviewPanel(textarea) { | |||
var $ta = $(textarea); | |||
ensureEditLayout(textarea); | |||
var $panel = $('#ntb-preview-panel'); | |||
// | if (previewState.open) { | ||
$ | $('body').removeClass('ntb-preview-open'); | ||
$panel.hide(); | |||
// 에디터를 다시 100%로 돌리고 싶다면: | |||
$ta.show(); | |||
$ta.css('width', '100%'); | |||
previewState.open = false; | |||
// 입력 이벤트 unbind | |||
$ta.off('input.preview'); | |||
return; | |||
} | } | ||
// | $('body').addClass('ntb-preview-open'); | ||
$panel.show(); | |||
previewState.open = true; | |||
updatePreview(textarea); | |||
$ta.off('input.preview').on('input.preview', function () { | |||
updatePreview(textarea); | |||
}); | |||
} | |||
/** | |||
* API를 이용해 wikitext → HTML 렌더링 | |||
*/ | |||
function updatePreview(textarea) { | |||
var text = textarea.value; | |||
$('#ntb-preview-content').text('렌더링 중...'); | |||
}( | new mw.Api().post({ | ||
action: 'parse', | |||
format: 'json', | |||
contentmodel: 'wikitext', | |||
text: text | |||
}).done(function (data) { | |||
$('#ntb-preview-content').html(data.parse.text['*']); | |||
}).fail(function () { | |||
$('#ntb-preview-content').text('렌더링 실패'); | |||
}); | |||
} | |||
2026년 1월 4일 (일) 23:21 기준 최신판
/* MediaWiki:Gadget-NewToolbar.js */
( 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' );
}
/**
* 간단 알림 (mw.notify 있으면 그걸 쓰고, 없으면 alert)
*/
function notify( msg ) {
if ( mw && mw.notify ) {
mw.notify( msg );
} else {
alert( msg );
}
}
/**
* 찾기/바꾸기 상태
*/
var findState = {
open: false,
term: '',
lastIndex: 0
};
function getMainTextarea() {
return document.getElementById( 'wpTextbox1' );
}
/**
* 찾기/바꾸기 패널 만들기 (없으면 생성, 있으면 그대로 반환)
*/
function createFindPanel( textarea ) {
var $panel = $( '#ntb-find-panel' );
if ( $panel.length ) {
return $panel;
}
$panel = $( '<div>' )
.attr( 'id', 'ntb-find-panel' )
.addClass( 'ntb-find-panel' );
var $rowFind = $( '<div>' ).addClass( 'ntb-find-row' );
$rowFind.append(
$( '<label>' ).text( '찾기' ),
$( '<input>' ).attr( { type: 'text', id: 'ntb-find-text' } )
);
var $rowReplace = $( '<div>' ).addClass( 'ntb-find-row' );
$rowReplace.append(
$( '<label>' ).text( '바꾸기' ),
$( '<input>' ).attr( { type: 'text', id: 'ntb-replace-text' } )
);
var $btns = $( '<div>' ).addClass( 'ntb-find-buttons' );
var $btnNext = $( '<button>' ).attr( 'type', 'button' ).text( '다음 찾기' );
var $btnReplace = $( '<button>' ).attr( 'type', 'button' ).text( '바꾸기' );
var $btnReplaceAll = $( '<button>' ).attr( 'type', 'button' ).text( '모두 바꾸기' );
var $btnClose = $( '<button>' ).attr( 'type', 'button' ).text( '닫기' );
$btns.append( $btnNext, $btnReplace, $btnReplaceAll, $btnClose );
$panel.append( $rowFind, $rowReplace, $btns );
// 🔹 여기부터 새로 추가: Enter 키 막기 + 원하는 동작으로 바꾸기
$panel.on( 'keydown', 'input', function ( e ) {
if ( e.key === 'Enter' || e.keyCode === 13 ) {
e.preventDefault();
e.stopPropagation();
var ta = document.getElementById( 'wpTextbox1' );
if ( !ta ) {
return;
}
if ( this.id === 'ntb-find-text' ) {
// 찾기 칸에서 Enter → 다음 찾기
findNext( ta );
} else if ( this.id === 'ntb-replace-text' ) {
// 바꾸기 칸에서 Enter → 현재 항목 바꾸기
replaceOne( ta );
}
}
} );
// 버튼 동작 정의
$btnNext.on( 'click', function () {
var textarea = document.getElementById( 'wpTextbox1' );
findNext( textarea );
} );
$btnReplace.on( 'click', function () {
var textarea = document.getElementById( 'wpTextbox1' );
replaceOne( textarea );
} );
$btnReplaceAll.on( 'click', function () {
var textarea = document.getElementById( 'wpTextbox1' );
replaceAll( textarea );
} );
$btnClose.on( 'click', function () {
closeFindPanel();
} );
return $panel;
}
/**
* 찾기/바꾸기 패널 열기/닫기 토글
*/
function toggleFindPanel( textarea ) {
var $panel = createFindPanel();
if ( findState.open ) {
closeFindPanel();
} else {
openFindPanel( $panel, textarea );
}
}
function openFindPanel( $panel, textarea ) {
var $toolbar = $( '#ntb-toolbar' );
if ( !$panel.parent().length ) {
$toolbar.after( $panel );
}
$panel.show();
findState.open = true;
findState.term = '';
findState.lastIndex = 0;
// 현재 선택 텍스트가 있으면 "찾기" 칸에 채우기
try {
var start = textarea.selectionStart || 0;
var end = textarea.selectionEnd || 0;
var sel = textarea.value.slice( start, end );
if ( sel ) {
$( '#ntb-find-text' ).val( sel );
}
} catch (e) {
// selectionStart를 지원하지 않는 경우는 무시
}
$( '#ntb-find-text' ).focus().select();
}
function closeFindPanel() {
var $panel = $( '#ntb-find-panel' );
$panel.hide();
findState.open = false;
}
/**
* 다음 찾기
*/
function findNext( textarea ) {
var $findInput = $( '#ntb-find-text' );
if ( !$findInput.length ) {
return;
}
var term = $findInput.val();
if ( !term ) {
notify( '찾을 문자열을 입력하세요.' );
return;
}
var text = textarea.value;
// selection 관련 값을 안전하게 얻기
var currentStart = 0;
var currentEnd = 0;
try {
currentStart = textarea.selectionStart || 0;
currentEnd = textarea.selectionEnd || 0;
} catch (e) {
currentStart = currentEnd = 0;
}
// 검색어가 바뀌면 처음부터 시작
if ( findState.term !== term ) {
findState.term = term;
findState.lastIndex = currentEnd;
}
var startFrom = Math.max( currentEnd, findState.lastIndex );
var idx = text.indexOf( term, startFrom );
// 못 찾으면 처음부터 다시
if ( idx === -1 && startFrom !== 0 ) {
idx = text.indexOf( term, 0 );
}
if ( idx === -1 ) {
notify( '더 이상 찾을 문자열이 없습니다.' );
return;
}
textarea.selectionStart = idx;
textarea.selectionEnd = idx + term.length;
textarea.focus();
findState.lastIndex = idx + term.length;
}
/**
* 현재 선택된 항목 한 번만 바꾸기
*/
function replaceOne( textarea ) {
var $findInput = $( '#ntb-find-text' );
var $replaceInput = $( '#ntb-replace-text' );
if ( !$findInput.length || !$replaceInput.length ) {
return;
}
var term = $findInput.val();
var replacement = $replaceInput.val();
if ( !term ) {
notify( '찾을 문자열을 입력하세요.' );
return;
}
var start = 0;
var end = 0;
try {
start = textarea.selectionStart || 0;
end = textarea.selectionEnd || 0;
} catch (e) {
start = end = 0;
}
var text = textarea.value;
var selected = text.slice( start, end );
if ( selected && selected === term ) {
// 현재 선택 영역이 검색어와 일치하면 교체
var newText = text.slice( 0, start ) + replacement + text.slice( end );
textarea.value = newText;
var newEnd = start + replacement.length;
textarea.selectionStart = newEnd;
textarea.selectionEnd = newEnd;
textarea.focus();
findState.lastIndex = newEnd;
} else {
// 선택이 없거나 일치하지 않으면 "다음 찾기" 먼저
findNext( textarea );
}
}
/**
* 전체 텍스트 모두 바꾸기
*/
function replaceAll( textarea ) {
var $findInput = $( '#ntb-find-text' );
var $replaceInput = $( '#ntb-replace-text' );
if ( !$findInput.length || !$replaceInput.length ) {
return;
}
var term = $findInput.val();
var replacement = $replaceInput.val();
if ( !term ) {
notify( '찾을 문자열을 입력하세요.' );
return;
}
if ( term === replacement ) {
notify( '같은 문자열로 바꾸려고 합니다.' );
return;
}
var text = textarea.value;
if ( text.indexOf( term ) === -1 ) {
notify( '문서 안에 해당 문자열이 없습니다.' );
return;
}
// 정규식 특수문자 이스케이프
var escaped = term.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
var re = new RegExp( escaped, 'g' );
var matches = text.match( re );
var count = matches ? matches.length : 0;
textarea.value = text.replace( re, replacement );
textarea.selectionStart = textarea.selectionEnd = 0;
textarea.focus();
findState.lastIndex = 0;
notify( count + '개를 모두 바꿨습니다.' );
}
/**
* 버튼 생성 헬퍼
*/
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>' );
}
} ),
createButton( {
icon: 'ntb-icon-redirect',
label: '넘겨주기',
title: '#넘겨주기 [[대상 문서]]',
onClick: function ( ta ) {
surroundSelection( ta, '#넘겨주기 [[', ']]' );
}
} )
);
// --- 그룹 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 ( ta ) {
toggleFindPanel( ta );
}
} )
);
$groupTools.append(
createButton({
icon: 'ntb-icon-preview',
label: '미리보기',
title: '미리 보기 패널 열기/닫기',
onClick: function (ta) {
togglePreviewPanel(ta);
}
})
);
$toolbar
.append( $groupText )
.append( $groupBlock )
.append( $groupInsert )
.append( $groupTools );
// 기존 툴바 숨기고, 새 툴바 삽입
$oldToolbar.hide().before( $toolbar );
}
// wikiEditor 준비되면 호출
mw.hook( 'wikiEditor.toolbarReady' ).add( initNewToolbar );
}( mediaWiki, jQuery) );
var previewState = {
open: false,
};
/**
* 에디터 & 미리보기 레이아웃 준비
* - #wpTextbox1를 .ntb-edit-area로 감싸고
* - 가운데에 splitter, 오른쪽에 preview panel을 넣는다.
*/
function ensureEditLayout(textarea) {
var $ta = $(textarea);
var $ed = $ta.closest('.wikiEditor-ui-text');
var $wrapper = $ta.parent();
// preview panel 생성
var $panel = createPreviewPanel();
// wrapper 안에 순서대로: textarea | splitter | preview
if ($panel.parent()[0] !== $wrapper[0]) {
$wrapper.append($panel);
}
// 초기 flex 비율 설정 (에디터 50%, 미리보기 50%)
$ta.hide();
$panel.css('width', 'calc(100% - 2rem)');
$panel.css('padding', '1rem');
$panel.css('height', '100%');
}
/**
* 미리보기 패널 상태
*/
var previewState = {
open: false
};
/**
* 미리보기 패널 상태
*/
var previewState = {
open: false
};
/**
* 미리보기 패널 생성
*/
function createPreviewPanel() {
var $panel = $('#ntb-preview-panel');
if ($panel.length) return $panel;
$panel = $('<div>')
.attr('id', 'ntb-preview-panel')
.addClass('ntb-preview-panel')
.html(`
<div class="ntb-preview-title">미리 보기</div>
<div id="ntb-preview-content">내용 없음</div>
`);
return $panel;
}
/**
* 미리보기 켜기/끄기
*/
function togglePreviewPanel(textarea) {
var $ta = $(textarea);
ensureEditLayout(textarea);
var $panel = $('#ntb-preview-panel');
if (previewState.open) {
$('body').removeClass('ntb-preview-open');
$panel.hide();
// 에디터를 다시 100%로 돌리고 싶다면:
$ta.show();
$ta.css('width', '100%');
previewState.open = false;
// 입력 이벤트 unbind
$ta.off('input.preview');
return;
}
$('body').addClass('ntb-preview-open');
$panel.show();
previewState.open = true;
updatePreview(textarea);
$ta.off('input.preview').on('input.preview', function () {
updatePreview(textarea);
});
}
/**
* API를 이용해 wikitext → HTML 렌더링
*/
function updatePreview(textarea) {
var text = textarea.value;
$('#ntb-preview-content').text('렌더링 중...');
new mw.Api().post({
action: 'parse',
format: 'json',
contentmodel: 'wikitext',
text: text
}).done(function (data) {
$('#ntb-preview-content').html(data.parse.text['*']);
}).fail(function () {
$('#ntb-preview-content').text('렌더링 실패');
});
}