$(function() { // 댓글입력 창 높이 자동조정 $.each($('textarea[data-autoresize]'), function () { $(this).on('keyup input', function () { Comment.resizeTextarea(this); }).removeAttr('data-autoresize'); }); }); const Comment = { callback: "", listID: "commentList", writeID: "commentWrite", method: {write: "store", edit: "update", reply: "reply"}, mode: "", code: "", bid: 0, pid: 0, cid: 0, uid: 0, total: 0, page: 0, perPage: 0, minLength: 0, maxLength: 0, listForm: null, writeForm: null, saveHtml: null, saveBefore: null, useEditor: false, queryString: null, tinymce: null, // 댓글 기본설정 init: function(config) { if(!config.hasOwnProperty("code") && !config.hasOwnProperty("bid") && !config.hasOwnProperty("pid")) { return false; } this.code = config.code; this.bid = config.bid; this.pid = config.pid; this.callback = (BASE_URL + "/board/" + String(this.code) + "/" + String(this.pid) + "/comment"); this.listForm = document.getElementById(this.listID); this.writeForm = document.getElementById(this.writeID); // 댓글 입력창 let form = this.writeForm.getElementsByTagName("form")[0]; this.minLength = Number(form.elements["min_length"].value); this.maxLength = Number(form.elements["max_length"].value); this.saveHtml = this.writeForm.innerHTML; // 댓글 입력 창 저장 }, // 초기화 reset: function(form) { this.mode = "write"; this.cid = 0; this.saveBefore = null; if (typeof form !== "undefined") { if(form.elements["mode"].value !== "write") { form.style.display = "none"; } form.elements["mode"].value = "write"; form.elements["bid"].value = ""; form.elements["pid"].value = ""; form.elements["cid"].value = ""; form.elements["uid"].value = ""; form.elements["length"].value = "0"; form.elements["content"].value = ""; if (form.elements.hasOwnProperty("username")) { form.elements["username"].value = ""; } if (form.elements.hasOwnProperty("password")) { form.elements["password"].value = ""; } if (form.elements.hasOwnProperty("is_secret")) { form.elements["is_secret"].checked = false; } let length = form.getElementsByClassName("comment-length"); if (length.length > 0) { length[0].innerText = "0"; } form.elements["btn_comment_submit"].disabled = false; form.elements["btn_comment_cancel"].disabled = false; this.resizeTextarea(form.elements["content"]); } if (this.useEditor) { tinyMCE.activeEditor.setContent(""); } }, // 댓글 목록 새로고침 refresh: function() { this.list(this.page); }, // textarea 높이 조절 resizeTextarea: function(e) { $(e).css("height", "auto").css("height", e.scrollHeight + (e.offsetHeight - e.clientHeight)); }, // 댓글 영역으로 스크롤 이동 scrollReplyNow: function(cid) { if (typeof cid != "undefined" && cid) { let offset = $("#comment_" + cid).offset(); if (typeof offset != "undefined") { window.document.body.scrollTop = offset.top; document.getElementById("comment_" + cid).scrollIntoView(); } } }, // 댓글 영역 강조 focusCommentBox: function(cid) { if (typeof cid != "undefined" && cid) { $("#comment_" + cid).effect("highlight", { 'color': '#4f4f4f' }, 1500); } }, // 금지 단어 확인 filterSpamKeyword: function (content) { let ret = ""; $.ajax({ headers: {"X-CSRF-TOKEN": CSRF}, url: (BASE_URL + "/api/filterSpamKeyword"), type: "post", dataType: "json", async: false, cache: false, data: {subject: "", content: content}, success: function (res) { ret = res.content; }, error: function (xhr, status, err) { procErrorEvent(xhr, status, err); } }); return ret; }, // 글자수 확인 checkByte: function(e) { let form = e.form; if (typeof form !== "undefined") { let hiddenInputLength = form.elements["length"]; let spanInputLength = form.getElementsByClassName("comment-length")[0]; let textarea = form.elements["content"]; let length = textarea.value.length; let tinymce = form.getElementsByTagName("iframe"); if (this.useEditor) { let editor = tinymce[0].contentDocument.body; length = editor.innerText.trim().length; if (this.maxLength > 0 && length > this.maxLength) { editor.innerHTML = editor.innerHTML.substr(0, this.maxLength); length = editor.innerHTML.length; } } else { if (this.maxLength > 0 && length > this.maxLength) { textarea.value = textarea.value.substr(0, this.maxLength); length = textarea.value.length; } } // 글자 수 저장 hiddenInputLength.value = length; // 글자 수 출력 if (typeof spanInputLength !== "undefined") { spanInputLength.innerText = length; } } }, // 목록 list: function(page, cid, message, sort) { if(!this.code && !this.bid && !this.pid) { return false; } if (!page || page < 1 || page === Number.POSITIVE_INFINITY) { page = 1; } let listURL = (this.callback + "?page=" + page); if (sort) { listURL += "&sort=" + sort; } $('#' + this.listID).load(listURL, function (response, status, xhr) { // HTML 형식으로 뎃글목록 출력 if(status !== "success") { return; } let total = this.children.total.value; let page = this.children.page.value; let perPage = this.children.per_page.value; document.getElementById("txtCommentRows").innerText = total; document.getElementById("txtPostCommentCnt").innerText = total; if (message) { // 알림이 있을 경우 alert alert(message); } Comment.total = total; Comment.page = page; Comment.perPage = perPage; Comment.focusCommentBox(cid); Comment.scrollReplyNow(cid); }); }, // 댓글 등록/수정 submit: function(e) { e.blur(); e.disabled = true; $(e.form).validate({ onkeyup: false, onclick: false, onfocusout: false, rules: { username: {required: "#username:enabled", minlength: 2, maxlength: 10}, password: {required: "#password:enabled", minlength: 3, maxlength: 10}, mode: {required: true, contains: ["write", "edit", "reply"]}, content: { required: true, normalizer: function (value) { if(Comment.useEditor) { return tinyMCE.activeEditor.getContent({format: "text"}).trim(); }else{ return value.trim(); } }, minlength: Comment.minLength, maxlength: function() { return (Comment.maxLength > 0 ? Comment.maxLength : 500) } }, is_secret: {number: true} }, messages: { username: {required: "이름을 입력하세요.", minlength: "이름은 두 글자 이상 입력하세요.", maxlength: "이름은 10자까지 입력 가능합니다."}, password: {required: "비밀번호를 입력하세요.", minlength: "비밀번호는 3자 이상 입력하세요.", maxlength: "비밀번호는 10자까지 입력 가능합니다."}, mode: {required: "댓글 처리중 오류가 발생하였습니다.", contains: "잘못된 요청입니다."}, content: {required: "댓글을 입력해주세요."}, is_secret: {number: true} }, showErrors: function (errorMap, errorList) { if (this.numberOfInvalids()) { setTimeout(function() { alert(errorList[0].message); errorList[0].element.focus(); if(Comment.useEditor) { tinyMCE.activeEditor.getBody().scrollIntoView(); }else{ window.scrollTo({top: errorList[0].element.offsetTop}); } }, 100); e.disabled = false; } }, submitHandler: function (form) { let formData = new FormData(form); let spamKeyword = Comment.filterSpamKeyword(formData.get("content")); // 금지 단어 검사 if (spamKeyword) { alert("내용에 금지어 ('" + spamKeyword + "')가 포함 되어있습니다."); form.elements["content"].focus(); return false; } let page; let mode = formData.get("mode"); if(mode === "write") { formData.append("bid", Comment.bid); formData.append("pid", Comment.pid); page = Math.ceil((Number(Comment.total) + 1) / Comment.perPage); }else{ page = Comment.page; } if(mode === "edit") { formData.append("_method", "PUT"); } // 버튼 비활성화 form.elements["btn_comment_submit"].disabled = true; form.elements["btn_comment_cancel"].disabled = true; $.ajax({ headers: {'X-CSRF-TOKEN': CSRF}, url: (Comment.callback + "/" + Comment.method[mode]), type: "post", cache: false, async: true, contentType: false, processData: false, data: formData, dataType: "json", beforeSend: function () { if (formData.get("uid")) { // 권한 확인 if (!loginCheck()) { return xhr.abort(); } } showLoading(); }, success: function (res) { if (res.success) { Comment.list(page, res.cid); }else{ alert(res.message); } }, error: function (xhr, status, err) { procErrorEvent(xhr, status, err); }, complete: function() { Comment.reset(form); hideLoading(); } }); } }); $(e.form).submit(); }, // 수정, 답글 write: function(e, mode) { e.blur(); if(typeof e === "undefined") { return false; } if(typeof mode === "undefined" || mode == null || !mode) { return false; } if(!mode in this.method) { return false; } let formData = new FormData(e.form); // 댓글 영역 let mediaFormID = ("#fCommentWrite_" + formData.get("cid")); // 현재 답글 번호 let $saveBefore = $(this.saveBefore); // 이전 댓글 창 let $target = $(mediaFormID); // 현재 댓글 창 if(this.cid === formData.get("cid") && this.saveBefore === mediaFormID && this.mode === mode) { $saveBefore.toggle(); }else{ $saveBefore.html(""); $target.show(); let writeForm = $target.html(this.saveHtml).get(0).firstElementChild; // 답글 if(mode === "reply") { if(writeForm.elements.hasOwnProperty("username")) { writeForm.elements["username"].value = ""; } if(writeForm.elements.hasOwnProperty("is_secret")) { writeForm.elements["is_secret"].checked = false; } writeForm.elements["content"].value = ""; } // 수정 if(mode === "edit") { if(writeForm.elements.hasOwnProperty("username")) { writeForm.elements["username"].value = formData.get("username"); } if(writeForm.elements.hasOwnProperty("is_secret")) { writeForm.elements["is_secret"].checked = Boolean(formData.get("is_secret").checked); } writeForm.elements["content"].value = formData.get("content"); // 입력창 높이 조절 this.resizeTextarea(writeForm.elements["content"]); } writeForm.elements["mode"].value = mode; writeForm.elements["bid"].value = formData.get("bid"); writeForm.elements["pid"].value = formData.get("pid"); writeForm.elements["cid"].value = formData.get("cid"); writeForm.elements["uid"].value = formData.get("uid"); // textarea ID 할당 if(writeForm) { writeForm.elements["content"].setAttribute("id", formData.get("content") + "_" + formData.get("cid")); // 1:1 문의 게시판은 에디터를 사용 if(this.useEditor) { tinyMCE.EditorManager.execCommand('mceAddEditor', true, writeForm.elements["content"]); } // 글자 수 확인 this.checkByte(formData.get("content")); } } this.mode = mode; this.cid = formData.get("cid"); this.saveBefore = mediaFormID; // 현재 영역 }, // 댓글 삭제 delete: function(e) { e.blur(); let formData = new FormData(e.form); $.ajax({ url: (this.callback + "/delete"), type: "delete", cache: false, async: false, data: {bid: formData.get("bid"), pid: formData.get("pid"), cid: formData.get("cid"), _method: "DELETE"}, dataType: "json", beforeSend: function(xhr) { if (!formData.get("uid")) { // 권한 확인 const password = prompt("댓글 비밀번호 확인이 필요합니다.\n비밀번호를 입력해주세요."); if (!password) { return false; } this.url += ("?passwd=" + encodeURIComponent(password.trim())); } else { if (!loginCheck()) { return xhr.abort(); } } if (!confirm("댓글을 삭제하시겠습니까?")) { return false; } }, success: function (res) { if (res.success) { alert("댓글이 삭제되었습니다."); Comment.refresh(); }else{ alert(res.message || "처리 중 오류가 발생하였습니다. 관리자에게 문의하십시오."); } Comment.reset(); }, error: function (xhr, status, err) { procErrorEvent(xhr, status, err); } }); return false; }, // 댓글 신고 blame: function(e) { e.blur(); if(!loginCheck()) { return false; } // 신고 접수 유효성 적용 $("#fCommentBlame").validate({ onkeyup: false, onclick: false, onfocusout: false, rules: { type: {required: true, contains: ['1', '2', '3', '4', '5', '6', '7', '8', '9']}, reason: { required: true, normalizer: function (value) { return $.trim(value); }, maxlength: 1000 } }, messages: { type: {required: "신고 유형을 선택해주세요.", contains: "잘못된 요청입니다."}, reason: {required: "신고 내용을 입력해주세요.", maxlength: "신고 내용은 {0}자까지 입력 가능합니다."} }, showErrors: function (errorMap, errorList) { if (this.numberOfInvalids()) { alert(errorList[0].message); errorList[0].element.focus(); } }, submitHandler: function (form) { if (confirm("신고를 접수하시겠습니까?")) { let formData = new FormData(form); formData.append("bid", e.form.elements["bid"].value); formData.append("pid", e.form.elements["pid"].value); formData.append("cid", e.form.elements["cid"].value); $.ajax({ url: (Comment.callback + "/blame"), type: "post", cache: false, contentType: false, processData: false, data: formData, dataType: "json", beforeSend: function (xhr) { if (!loginCheck()) { xhr.abort(); } }, success: function (res) { if (res.success) { alert("감사합니다. 신고가 접수되었습니다. 이용 규칙을 위반한 것으로 확인되면 댓글이 삭제됩니다."); } else { alert(res.message || "처리 중 오류가 발생하였습니다. 관리자에게 문의하십시오."); } }, error: function (xhr, status, err) { procErrorEvent(xhr, status, err); }, complete : function() { $("#commentBlameModal").modal('hide'); } }); } } }); }, // 추천 like: function (e) { let formData = new FormData(e.form); formData.append("type", e.value); $.ajax({ url: (this.callback + "/like"), type: "post", cache: false, contentType: false, processData: false, data: formData, dataType: "json", beforeSend: function (xhr) { if (!loginCheck()) { xhr.abort(); } }, success: function (res) { if (res.success) { Comment.toggleLike(formData.get("cid"), 1, !Number(e.dataset.active)); } else { alert(res.message || "처리 중 오류가 발생하였습니다. 관리자에게 문의하십시오."); } }, error: function (xhr, status, err) { procErrorEvent(xhr, status, err); } }); }, // 비추천 dislike: function (e) { let formData = new FormData(e.form); formData.append("type", e.value); $.ajax({ url: (this.callback + "/dislike"), type: "post", cache: false, contentType: false, processData: false, data: formData, dataType: "json", beforeSend: function (xhr) { if (!loginCheck()) { xhr.abort(); } }, success: function (res) { if (res.success) { Comment.toggleLike(formData.get("cid"), 2, !Number(e.dataset.active)); } else { alert(res.message || "처리 중 오류가 발생하였습니다. 관리자에게 문의하십시오."); } }, error: function (xhr, status, err) { procErrorEvent(xhr, status, err); } }); }, // 댓글 입력 취소 cancel: function (e) { if (e.form.mode.value === "write") { e.form.elements["mode"].value = "write"; e.form.elements["cid"].value = ""; e.form.elements["length"].value = "0"; e.form.elements["content"].value = ""; if (typeof e.form.elements["is_secret"] !== "undefined") { e.form.elements["is_secret"].checked = false; } let length = e.form.getElementsByClassName("comment-length"); if (length.length > 0) { length[0].innerText = "0"; } } else { e.form.parentNode.style.display = "none"; e.form.parentNode.innerHTML = ""; } this.reset(e.form); }, /* * 댓글 에디터 옵션 변경 * 1:1 게시판에서 댓글을 작성하면 실행 */ setInitEditor: function () { let tinyMCEExtendSetting = { min_height: 210, max_height: 310, file_browser_url: (BASE_URL + "/board/" + this.code + "/uploader"), init_instance_callback: function (editor) { editor.on('SetContent', function (e) { let iframeRows = e.target.contentDocument.body.getElementsByTagName("iframe"); if (iframeRows > 0) { $(e.content).filter("iframe").attr("src", ""); // iframe src 주소 } }); editor.on('keyup', function () { Comment.checkByte(editor.targetElm); }); editor.on('keypress', function () { Comment.checkByte(editor.targetElm); // document.getElementById(e.target.dataset.id).value = e.currentTarget.textContent; }); } }; this.tinymce = tinymce.init(Object.assign(tinyMCE.activeEditor.settings, tinyMCEExtendSetting)); this.useEditor = true; }, // 좋아요/싫어요 처리 toggleLike: function (cid, type, active) { let btnCommentLike = document.getElementById("comment_" + cid).getElementsByClassName("btn-comment-like")[0]; let btnCommentDisLike = document.getElementById("comment_" + cid).getElementsByClassName("btn-comment-dislike")[0]; if (type === 1) { this.setLike(cid, active); if (Number(btnCommentDisLike.dataset.active)) { this.setDislike(cid, false); } } else if (type === 2) { this.setDislike(cid, active); if (Number(btnCommentLike.dataset.active)) { this.setLike(cid, false); } } }, // 좋아요 갱신 setLike: function (cid, active) { let button = document.getElementById("comment_" + cid).getElementsByClassName("btn-comment-like")[0]; let rows = Number(button.dataset.rows); if (active) { rows = (rows + 1); button.innerHTML = ' ' + String(rows) + ''; button.dataset.active = "1"; } else { rows = (rows > 0 ? rows - 1 : rows); button.innerHTML = ' ' + String(rows) + ''; button.dataset.active = "0"; } button.dataset.rows = rows; }, // 싫어요 갱신 setDislike: function (cid, active) { let button = document.getElementById("comment_" + cid).getElementsByClassName("btn-comment-dislike")[0]; let rows = Number(button.dataset.rows); if (active) { rows = (rows + 1); button.innerHTML = ' ' + String(rows) + ''; button.dataset.active = "1"; } else { rows = (rows > 0 ? rows - 1 : rows); button.innerHTML = ' ' + String(rows) + ''; button.dataset.active = "0"; } button.dataset.rows = rows; } }; // 댓글 입력하면 창 크기 조절 $(document).on("keyup input", "textarea[data-autoresize]", function() { Comment.resizeTextarea(this); }); // 댓글 글자수 확인 $(document).on('keydown keyup', "textarea.comment-content", function (e) { Comment.checkByte(this); }); // 댓글 등록 $(document).on("click", "button.btn-comment-submit", function () { Comment.submit(this); }); // 댓글 답글 $(document).on("click", "button.btn-comment-reply", function() { Comment.write(this, "reply"); }); // 댓글 수정 $(document).on("click", "button.btn-comment-edit", function () { Comment.write(this, "edit"); }); // 댓글 취소 $(document).on("click", "button.btn-comment-cancel", function () { Comment.cancel(this); }); // 댓글 삭제 $(document).on("click", "button.btn-comment-delete", function () { Comment.delete(this); }); // 댓글 신고 $(document).on("click", "button.btn-comment-blame", function() { Comment.blame(this); }); // 댓글 추천하기 $(document).on("click", "button.btn-comment-like", function () { Comment.like(this); }); // 댓글 비추천하기 $(document).on("click", "button.btn-comment-dislike", function () { Comment.dislike(this); }); // 댓글 페이징 처리 $(document).on("click", "#commentList > nav > ul > li > a", function () { let page = Number(params('page', this.href)); return Comment.list(page); }); // 댓글 정렬 $(document).on("change", "select#commentSort", function () { Comment.list(null, null, null, this.value); }); // 댓글 신고 모달 열리면 초기화 $(document).on("show.bs.modal", "#commentBlameModal", function(e) { let form = e.currentTarget.getElementsByTagName("form")[0]; form.elements["type"].selectedIndex = 0; form.elements["reason"].value = ""; if(!IS_USER) { return false; } });