Add Summernote editor initialization and styling for image handling
This commit is contained in:
@@ -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('<img') !== -1) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// HTML에서 이미지 src 추출 및 정리
|
||||||
|
var cleanedHtml = cleanPastedHtml(htmlData);
|
||||||
|
|
||||||
|
// 에디터에 포커스 후 HTML 삽입
|
||||||
|
$(selector).summernote('focus');
|
||||||
|
setTimeout(function() {
|
||||||
|
$(selector).summernote('pasteHTML', cleanedHtml);
|
||||||
|
}, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 커스텀 옵션 병합 (callbacks는 위에서 별도 처리)
|
||||||
|
var mergedOptions = $.extend(true, {}, defaultOptions, customOptions);
|
||||||
|
|
||||||
|
// callbacks는 기본 콜백을 유지하면서 커스텀 콜백 추가
|
||||||
|
mergedOptions.callbacks = defaultOptions.callbacks;
|
||||||
|
if (customOptions.callbacks) {
|
||||||
|
// onInit은 위에서 이미 처리됨
|
||||||
|
// 다른 커스텀 콜백 추가
|
||||||
|
for (var key in customOptions.callbacks) {
|
||||||
|
if (key !== 'onInit' && key !== 'onImageUpload' && key !== 'onPaste') {
|
||||||
|
mergedOptions.callbacks[key] = customOptions.callbacks[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(selector).summernote(mergedOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이미지를 Base64 data-uri로 변환하여 에디터에 삽입
|
||||||
|
* @param {File} file - 이미지 파일
|
||||||
|
* @param {jQuery} $editor - summernote 에디터 jQuery 객체
|
||||||
|
*/
|
||||||
|
function insertImageAsDataUri(file, $editor) {
|
||||||
|
// 파일 크기 제한 (5MB)
|
||||||
|
var maxSize = 5 * 1024 * 1024;
|
||||||
|
if (file.size > 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 = $('<div>').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(/<v:[^>]*>[\s\S]*?<\/v:[^>]*>/gi, '');
|
||||||
|
cleanedHtml = cleanedHtml.replace(/<o:[^>]*>[\s\S]*?<\/o:[^>]*>/gi, '');
|
||||||
|
cleanedHtml = cleanedHtml.replace(/<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '');
|
||||||
|
cleanedHtml = cleanedHtml.replace(/<!\[if[^>]*>[\s\S]*?<!\[endif\]>/gi, '');
|
||||||
|
|
||||||
|
// Office 네임스페이스 속성 제거
|
||||||
|
cleanedHtml = cleanedHtml.replace(/\s*v:[a-z]+="[^"]*"/gi, '');
|
||||||
|
cleanedHtml = cleanedHtml.replace(/\s*o:[a-z]+="[^"]*"/gi, '');
|
||||||
|
|
||||||
|
return cleanedHtml;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,27 @@
|
|||||||
<link rel="stylesheet" th:href="@{/plugins/jquery-ui/jquery-ui.min.css}" />
|
<link rel="stylesheet" th:href="@{/plugins/jquery-ui/jquery-ui.min.css}" />
|
||||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||||
|
|
||||||
|
<!-- Summernote 스타일 -->
|
||||||
|
<style>
|
||||||
|
.note-editable img {
|
||||||
|
max-width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/* 코드뷰 모드 가로 사이즈 고정 */
|
||||||
|
.note-editor .note-codable {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
resize: vertical !important;
|
||||||
|
white-space: pre-wrap !important;
|
||||||
|
word-wrap: break-word !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
.note-editor.note-frame {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
|
|||||||
Reference in New Issue
Block a user