최근 바뀜
도구
업로드
도움말
길라잡이
위키 문법
특수 문서
문의·신고
디스코드
IP 사용자
216.73.216.63
로그인
미디어위키:Gadget-newtoolbar.js 문서 원본 보기
←
미디어위키:Gadget-newtoolbar.js
편집
토론
역사
새로고침
주시
역링크
정보
문서 편집 권한이 없습니다. 다음 이유를 확인해주세요:
요청한 명령은 다음 권한을 가진 사용자에게 제한됩니다:
사용자
.
이 문서는 이 위키의 소프트웨어 인터페이스에 쓰이는 문서로, 부정 행위를 막기 위해 보호되어 있습니다. 모든 위키에 대한 번역을 추가하거나 바꾸려면 미디어위키 지역화 프로젝트인
translatewiki.net
에 참여하시기 바랍니다.
모든 방문자에게 영향을 미칠 수 있기 때문에 이 자바스크립트 문서의 편집 권한이 없습니다.
문서의 원본을 보거나 복사할 수 있습니다.
/* 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 }; /** * 찾기/바꾸기 패널 만들기 (없으면 생성, 있으면 그대로 반환) */ 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 ); // 버튼 동작 정의 $btnNext.on( 'click', function () { findNext( textarea ); } ); $btnReplace.on( 'click', function () { replaceOne( textarea ); } ); $btnReplaceAll.on( 'click', function () { replaceAll( textarea ); } ); $btnClose.on( 'click', function () { closeFindPanel(); } ); return $panel; } /** * 찾기/바꾸기 패널 열기/닫기 토글 */ function toggleFindPanel( textarea ) { var $panel = createFindPanel( textarea ); 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; // 현재 선택 텍스트가 있으면 "찾기" 칸에 채우기 var sel = textarea.value.slice( textarea.selectionStart, textarea.selectionEnd ); if ( sel ) { $( '#ntb-find-text' ).val( sel ); } $( '#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; var startFrom; // 검색어가 바뀌면 처음부터 if ( findState.term !== term ) { findState.term = term; findState.lastIndex = textarea.selectionEnd || 0; } startFrom = textarea.selectionEnd || 0; 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 = textarea.selectionStart; var end = textarea.selectionEnd; 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 = start; 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; } // 간단하게 split/join 사용 var count = ( text.match( new RegExp( term.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ), 'g' ) ) || [] ).length; textarea.value = text.split( term ).join( replacement ); textarea.selectionStart = textarea.selectionEnd = 0; textarea.focus(); 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>' ); } } ) ); // --- 그룹 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 }; /** * 미리보기 패널 상태 */ 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 $textbox = $('#wpTextbox1'); var $panel = createPreviewPanel(); var $wrapper = $('#ntb-editor-wrapper'); // 처음 한 번: wrapper를 만들고 textarea를 그 안으로 옮김 if (!$wrapper.length) { $wrapper = $('<div>') .attr('id', 'ntb-editor-wrapper') .addClass('ntb-editor-wrapper'); // 기존 위치 유지: textarea 앞에 wrapper를 넣고, 그 안으로 textarea 이동 $textbox.before($wrapper); $wrapper.append($textbox); } // 패널이 아직 wrapper 안에 없다면 붙이기 if (!$panel.parent().length) { $wrapper.append($panel); } if (previewState.open) { $panel.hide(); $wrapper.removeClass('ntb-preview-open'); previewState.open = false; // textarea 폭 다시 100%로 } else { $panel.show(); $wrapper.addClass('ntb-preview-open'); previewState.open = true; updatePreview(textarea); // 입력될 때마다 미리보기 갱신 $(textarea).off('.preview').on('input.preview', function () { updatePreview(textarea); }); } } 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('렌더링 실패'); }); } /** * 미리보기 패널 생성 */ 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 $panel = createPreviewPanel(); var $textbox = $('#wpTextbox1'); if (previewState.open) { $('body').removeClass('ntb-preview-open'); $panel.hide(); previewState.open = false; return; } // DOM에 삽입(텍스트박스 바로 뒤) if (!$panel.parent().length) { $textbox.after($panel); } $('body').addClass('ntb-preview-open'); $panel.show(); previewState.open = true; // 최초 업데이트 updatePreview(textarea); // 입력될 때마다 업데이트 $(textarea).off('.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('렌더링 실패'); }); } var previewState = { open: false, heightSync: { enabled: false, syncing: false, ro: null } }; function startHeightSync( textarea, panel ) { // 이미 켜져 있으면 패스 if ( previewState.heightSync.enabled ) { return; } previewState.heightSync.enabled = true; if ( !window.ResizeObserver ) { // 구형 브라우저면 그냥 무시 (필수는 아님) return; } var ta = textarea; var pv = panel; var ro = new ResizeObserver( function () { if ( previewState.heightSync.syncing ) { return; } previewState.heightSync.syncing = true; // 현재 실제 높이 var taH = ta.offsetHeight; var pvH = pv.offsetHeight; // 둘 중 큰 높이로 통일 var target = Math.max( taH, pvH ); ta.style.height = target + 'px'; pv.style.height = target + 'px'; previewState.heightSync.syncing = false; } ); previewState.heightSync.ro = ro; ro.observe( ta ); ro.observe( pv ); } function stopHeightSync() { if ( previewState.heightSync.ro ) { previewState.heightSync.ro.disconnect(); } previewState.heightSync.enabled = false; }
미디어위키:Gadget-newtoolbar.js
문서로 돌아갑니다.