diff --git a/src/main/resources/static/js/common.js b/src/main/resources/static/js/common.js index f423d22..425ae80 100644 --- a/src/main/resources/static/js/common.js +++ b/src/main/resources/static/js/common.js @@ -168,3 +168,204 @@ class FormValidator { } } } + +/** + * Summernote 에디터 공통 초기화 함수 + * - 이미지 붙여넣기 시 data-uri(Base64) 방식으로 처리 + * - 이미지 리사이징 지원 + * @param {string} selector - 에디터 selector (예: '#contents') + * @param {object} customOptions - 추가 옵션 (선택사항) + */ +function initSummernote(selector, customOptions) { + customOptions = customOptions || {}; + + // 기본 설정 + var defaultOptions = { + placeholder: customOptions.placeholder || '여기에 내용을 입력하세요.', + height: customOptions.height || 300, + lang: 'ko-KR', + toolbar: customOptions.toolbar || [ + ['style', ['style']], + ['font', ['bold', 'underline', 'clear']], + ['color', ['color']], + ['para', ['ul', 'ol', 'paragraph']], + ['table', ['table']], + ['insert', ['link', 'picture']], + ['view', ['fullscreen', 'codeview', 'help']] + ], + // 이미지 팝오버 설정 - 리사이징 지원 + popover: { + image: [ + ['resize', ['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone']], + ['float', ['floatLeft', 'floatRight', 'floatNone']], + ['remove', ['removeMedia']] + ] + }, + callbacks: { + onInit: function() { + var $editable = $(selector).next('.note-editor').find('.note-editable'); + $editable.addClass('editor-content'); + $editable.on('keydown', function(e) { + if (e.keyCode === 8) { + e.stopPropagation(); + } + }); + + // 커스텀 onInit 콜백 호출 + if (customOptions.callbacks && customOptions.callbacks.onInit) { + customOptions.callbacks.onInit.call(this); + } + }, + // 이미지 업로드 처리 - data-uri(Base64) 방식 + onImageUpload: function(files) { + for (var i = 0; i < files.length; i++) { + insertImageAsDataUri(files[i], $(selector)); + } + }, + // 붙여넣기 시 이미지 처리 (MS Office 등에서 복사한 이미지 포함) + onPaste: function(e) { + var clipboardData = e.originalEvent.clipboardData; + if (!clipboardData || !clipboardData.items) return; + + var items = clipboardData.items; + var imageFile = null; + + // 1. 먼저 순수 이미지 파일이 있는지 확인 + for (var i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + imageFile = items[i].getAsFile(); + break; + } + } + + // 2. 이미지 파일이 있으면 data-uri로 변환하여 삽입 + if (imageFile) { + e.preventDefault(); + e.stopPropagation(); + insertImageAsDataUri(imageFile, $(selector)); + return; + } + + // 3. HTML 붙여넣기인 경우 (MS Office 등) - 이미지 태그 정리 + var htmlData = clipboardData.getData('text/html'); + if (htmlData && htmlData.indexOf(' maxSize) { + alert('이미지 크기는 5MB를 초과할 수 없습니다.'); + return; + } + + // 이미지 타입 확인 + if (!file.type.match(/image\/(jpeg|jpg|png|gif|bmp|webp)/i)) { + alert('지원하지 않는 이미지 형식입니다. (jpeg, png, gif, bmp, webp만 지원)'); + return; + } + + var reader = new FileReader(); + reader.onload = function(e) { + var dataUri = e.target.result; + $editor.summernote('insertImage', dataUri, function($image) { + // 이미지 스타일 설정 + $image.css({ + 'max-width': '100%', + 'height': 'auto' + }); + $image.attr('data-filename', file.name); + }); + }; + reader.onerror = function() { + alert('이미지 읽기에 실패했습니다.'); + }; + reader.readAsDataURL(file); +} + +/** + * 붙여넣기된 HTML 정리 (MS Office 등에서 복사한 내용) + * - 불필요한 Office 속성 제거 + * - 이미지 태그 정리 (alt, v:shapes 등 제거) + * @param {string} html - 원본 HTML + * @returns {string} 정리된 HTML + */ +function cleanPastedHtml(html) { + // 임시 div에서 HTML 파싱 + var $temp = $('
').html(html); + + // 이미지 태그 정리 + $temp.find('img').each(function() { + var $img = $(this); + var src = $img.attr('src'); + + // src가 없거나 file:// 프로토콜이면 제거 + if (!src || src.indexOf('file://') === 0) { + $img.remove(); + return; + } + + // 불필요한 속성 제거 (Office 관련) + var attributesToRemove = ['alt', 'v:shapes', 'o:title', 'style', 'class', 'id', 'name']; + attributesToRemove.forEach(function(attr) { + $img.removeAttr(attr); + }); + + // 깨끗한 스타일만 적용 + $img.css({ + 'max-width': '100%', + 'height': 'auto' + }); + }); + + // VML 태그 제거 (v:, o:, w: 등) + var cleanedHtml = $temp.html(); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?<\/v:[^>]*>/gi, ''); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?<\/o:[^>]*>/gi, ''); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?<\/w:[^>]*>/gi, ''); + cleanedHtml = cleanedHtml.replace(/]*>[\s\S]*?/gi, ''); + + // Office 네임스페이스 속성 제거 + cleanedHtml = cleanedHtml.replace(/\s*v:[a-z]+="[^"]*"/gi, ''); + cleanedHtml = cleanedHtml.replace(/\s*o:[a-z]+="[^"]*"/gi, ''); + + return cleanedHtml; +} diff --git a/src/main/resources/templates/views/fragment/kjbank/head.html b/src/main/resources/templates/views/fragment/kjbank/head.html index 37b82f5..2216d37 100644 --- a/src/main/resources/templates/views/fragment/kjbank/head.html +++ b/src/main/resources/templates/views/fragment/kjbank/head.html @@ -27,6 +27,27 @@ + + +