feat: [approvals] 기안 본문 Quill.js 편집기 토글 기능 추가
- create/edit: 본문 라벨 옆 편집기 체크박스 + Quill.js v2 WYSIWYG 에디터 - edit: 기존 HTML body 자동 감지 → 편집기 자동 활성화 - show: HTML body 안전 렌더링 (strip_tags), plain text는 기존 방식 유지 - textarea ↔ Quill 토글 시 내용 상호 이관
This commit is contained in:
@@ -43,10 +43,18 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-
|
||||
|
||||
{{-- 본문 --}}
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">본문</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
본문
|
||||
<label class="inline-flex items-center gap-1 ml-3 cursor-pointer">
|
||||
<input type="checkbox" id="useEditor" onchange="toggleEditor()"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
<span class="text-xs text-gray-500 font-normal">편집기</span>
|
||||
</label>
|
||||
</label>
|
||||
<textarea id="body" rows="12" placeholder="기안 내용을 입력하세요..."
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
style="min-height: 300px;"></textarea>
|
||||
<div id="quill-container" style="display: none; min-height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,8 +85,73 @@ class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg tran
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="https://cdn.quilljs.com/2.0.3/quill.snow.css" rel="stylesheet">
|
||||
<style>
|
||||
#quill-container .ql-editor { min-height: 260px; font-size: 0.875rem; }
|
||||
#quill-container .ql-toolbar { border-radius: 0.5rem 0.5rem 0 0; border-color: #d1d5db; }
|
||||
#quill-container .ql-container { border-radius: 0 0 0.5rem 0.5rem; border-color: #d1d5db; }
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.quilljs.com/2.0.3/quill.js"></script>
|
||||
<script>
|
||||
let quillInstance = null;
|
||||
|
||||
function toggleEditor() {
|
||||
const useEditor = document.getElementById('useEditor').checked;
|
||||
const textarea = document.getElementById('body');
|
||||
const container = document.getElementById('quill-container');
|
||||
|
||||
if (useEditor) {
|
||||
container.style.display = '';
|
||||
textarea.style.display = 'none';
|
||||
if (!quillInstance) {
|
||||
quillInstance = new Quill('#quill-container', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
[{ 'align': [] }],
|
||||
['blockquote', 'code-block'],
|
||||
['link'],
|
||||
['clean'],
|
||||
],
|
||||
},
|
||||
placeholder: '기안 내용을 입력하세요...',
|
||||
});
|
||||
}
|
||||
const text = textarea.value.trim();
|
||||
if (text) {
|
||||
if (/<[a-z][\s\S]*>/i.test(text)) {
|
||||
quillInstance.root.innerHTML = text;
|
||||
} else {
|
||||
quillInstance.setText(text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
textarea.style.display = '';
|
||||
if (quillInstance) {
|
||||
const html = quillInstance.root.innerHTML;
|
||||
textarea.value = (html === '<p><br></p>') ? '' : html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBodyContent() {
|
||||
const useEditor = document.getElementById('useEditor')?.checked;
|
||||
if (useEditor && quillInstance) {
|
||||
const html = quillInstance.root.innerHTML;
|
||||
return (html === '<p><br></p>') ? '' : html;
|
||||
}
|
||||
return document.getElementById('body').value;
|
||||
}
|
||||
|
||||
async function saveApproval(action) {
|
||||
const title = document.getElementById('title').value.trim();
|
||||
if (!title) {
|
||||
@@ -97,7 +170,7 @@ class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg tran
|
||||
const payload = {
|
||||
form_id: document.getElementById('form_id').value,
|
||||
title: title,
|
||||
body: document.getElementById('body').value,
|
||||
body: getBodyContent(),
|
||||
is_urgent: document.getElementById('is_urgent').checked,
|
||||
steps: steps,
|
||||
};
|
||||
|
||||
@@ -65,10 +65,18 @@ class="rounded border-gray-300 text-red-600 focus:ring-red-500">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">본문</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
본문
|
||||
<label class="inline-flex items-center gap-1 ml-3 cursor-pointer">
|
||||
<input type="checkbox" id="useEditor" onchange="toggleEditor()"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
<span class="text-xs text-gray-500 font-normal">편집기</span>
|
||||
</label>
|
||||
</label>
|
||||
<textarea id="body" rows="12"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
style="min-height: 300px;">{{ $approval->body }}</textarea>
|
||||
<div id="quill-container" style="display: none; min-height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,8 +118,82 @@ class="w-full bg-red-100 hover:bg-red-200 text-red-700 px-4 py-2 rounded-lg tran
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="https://cdn.quilljs.com/2.0.3/quill.snow.css" rel="stylesheet">
|
||||
<style>
|
||||
#quill-container .ql-editor { min-height: 260px; font-size: 0.875rem; }
|
||||
#quill-container .ql-toolbar { border-radius: 0.5rem 0.5rem 0 0; border-color: #d1d5db; }
|
||||
#quill-container .ql-container { border-radius: 0 0 0.5rem 0.5rem; border-color: #d1d5db; }
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.quilljs.com/2.0.3/quill.js"></script>
|
||||
<script>
|
||||
let quillInstance = null;
|
||||
|
||||
function toggleEditor() {
|
||||
const useEditor = document.getElementById('useEditor').checked;
|
||||
const textarea = document.getElementById('body');
|
||||
const container = document.getElementById('quill-container');
|
||||
|
||||
if (useEditor) {
|
||||
container.style.display = '';
|
||||
textarea.style.display = 'none';
|
||||
if (!quillInstance) {
|
||||
quillInstance = new Quill('#quill-container', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
[{ 'align': [] }],
|
||||
['blockquote', 'code-block'],
|
||||
['link'],
|
||||
['clean'],
|
||||
],
|
||||
},
|
||||
placeholder: '기안 내용을 입력하세요...',
|
||||
});
|
||||
}
|
||||
const text = textarea.value.trim();
|
||||
if (text) {
|
||||
if (/<[a-z][\s\S]*>/i.test(text)) {
|
||||
quillInstance.root.innerHTML = text;
|
||||
} else {
|
||||
quillInstance.setText(text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
textarea.style.display = '';
|
||||
if (quillInstance) {
|
||||
const html = quillInstance.root.innerHTML;
|
||||
textarea.value = (html === '<p><br></p>') ? '' : html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBodyContent() {
|
||||
const useEditor = document.getElementById('useEditor')?.checked;
|
||||
if (useEditor && quillInstance) {
|
||||
const html = quillInstance.root.innerHTML;
|
||||
return (html === '<p><br></p>') ? '' : html;
|
||||
}
|
||||
return document.getElementById('body').value;
|
||||
}
|
||||
|
||||
// 기존 HTML body 자동 감지 → 편집기 자동 활성화
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const existingBody = document.getElementById('body').value;
|
||||
if (/<[a-z][\s\S]*>/i.test(existingBody)) {
|
||||
document.getElementById('useEditor').checked = true;
|
||||
toggleEditor();
|
||||
}
|
||||
});
|
||||
|
||||
async function updateApproval(action) {
|
||||
const title = document.getElementById('title').value.trim();
|
||||
if (!title) {
|
||||
@@ -130,7 +212,7 @@ class="w-full bg-red-100 hover:bg-red-200 text-red-700 px-4 py-2 rounded-lg tran
|
||||
const payload = {
|
||||
form_id: document.getElementById('form_id').value,
|
||||
title: title,
|
||||
body: document.getElementById('body').value,
|
||||
body: getBodyContent(),
|
||||
is_urgent: document.getElementById('is_urgent').checked,
|
||||
steps: steps,
|
||||
};
|
||||
|
||||
@@ -73,7 +73,13 @@ class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition
|
||||
|
||||
<div class="border-t pt-4">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ $approval->title }}</h2>
|
||||
<div class="prose prose-sm max-w-none text-gray-700 whitespace-pre-wrap">{{ $approval->body ?? '(내용 없음)' }}</div>
|
||||
@if($approval->body && preg_match('/<[a-z][\s\S]*>/i', $approval->body))
|
||||
<div class="prose prose-sm max-w-none text-gray-700">
|
||||
{!! strip_tags($approval->body, '<p><br><strong><b><em><i><u><s><del><h1><h2><h3><h4><h5><h6><ul><ol><li><blockquote><pre><code><a><span><div><table><thead><tbody><tr><th><td>') !!}
|
||||
</div>
|
||||
@else
|
||||
<div class="prose prose-sm max-w-none text-gray-700 whitespace-pre-wrap">{{ $approval->body ?? '(내용 없음)' }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user