function getEditorBlocks(editor) { var body = editor.document.getBody().$; var result = []; for (var i = 0; i < body.children.length; i++) { var el = body.children[i]; // 커서 표시용 요소는 콘텐츠 블록에서 제외 if (!el.getAttribute("data-remote-cursor")) { result.push(el.outerHTML); } } return result; } function computeBlockDiff(oldBlocks, newBlocks) { var m = oldBlocks.length; var n = newBlocks.length; var i, j; var dp = []; for (i = 0; i <= m; i++) { dp[i] = []; for (j = 0; j <= n; j++) { dp[i][j] = 0; } } for (i = 1; i <= m; i++) { for (j = 1; j <= n; j++) { dp[i][j] = oldBlocks[i - 1] === newBlocks[j - 1] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]); } } var rawOps = []; i = m; j = n; while (i > 0 || j > 0) { if (i > 0 && j > 0 && oldBlocks[i - 1] === newBlocks[j - 1]) { rawOps.unshift({ op: "retain" }); i--; j--; } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) { rawOps.unshift({ op: "insert", html: newBlocks[j - 1] }); j--; } else { rawOps.unshift({ op: "delete" }); i--; } } // 인접한 delete + insert 를 replace 로 병합 var ops = []; for (var k = 0; k < rawOps.length; k++) { if (rawOps[k].op === "delete" && k + 1 < rawOps.length && rawOps[k + 1].op === "insert") { ops.push({ op: "replace", html: rawOps[k + 1].html }); k++; } else { ops.push(rawOps[k]); } } return ops; } function applyBlockDiff(editor, ops) { var editorDoc = editor.document.$; var body = editor.document.getBody().$; // 커서 요소(data-remote-cursor)는 콘텐츠 블록이 아니므로 제외 var children = Array.prototype.filter.call(body.children, function (el) { return !el.getAttribute("data-remote-cursor"); }); var pos = 0; for (var i = 0; i < ops.length; i++) { var op = ops[i]; if (op.op === "retain") { pos++; } else if (op.op === "delete") { if (children[pos] && children[pos].parentNode) { body.removeChild(children[pos]); } pos++; } else if (op.op === "insert") { var tmpIns = editorDoc.createElement("div"); tmpIns.innerHTML = op.html; var newElIns = tmpIns.firstChild; if (newElIns) { if (children[pos]) { body.insertBefore(newElIns, children[pos]); } else { body.appendChild(newElIns); } } } else if (op.op === "replace") { if (children[pos] && children[pos].parentNode) { var tmpRep = editorDoc.createElement("div"); tmpRep.innerHTML = op.html; var newElRep = tmpRep.firstChild; if (newElRep) { body.replaceChild(newElRep, children[pos]); } } pos++; } } } function getCursorPath(editor) { var selection = editor.getSelection(); if (!selection) return null; var native = selection.getNative(); if (!native || native.rangeCount === 0) return null; var range = native.getRangeAt(0); var body = editor.document.getBody().$; var node = range.startContainer; var path = []; while (node && node !== body) { var parent = node.parentNode; if (!parent) return null; path.unshift(Array.prototype.indexOf.call(parent.childNodes, node)); node = parent; } if (node !== body) return null; return { path: path, offset: range.startOffset }; } function showRemoteCursor(editor, userInfo, cursorData) { var editorDoc = editor.document.$; var body = editor.document.getBody().$; var userId = userInfo.id; removeRemoteCursor(userId); var node = body; for (var i = 0; i < cursorData.path.length; i++) { if (cursorData.path[i] >= node.childNodes.length) return; node = node.childNodes[cursorData.path[i]]; } var range = editorDoc.createRange(); try { var maxOffset = node.nodeType === 3 ? node.length : node.childNodes.length; range.setStart(node, Math.min(cursorData.offset, maxOffset)); range.collapse(true); } catch (e) { return; } var rect = range.getBoundingClientRect(); if (!rect || rect.height === 0) return; var scrollTop = editorDoc.documentElement.scrollTop || editorDoc.body.scrollTop || 0; var scrollLeft = editorDoc.documentElement.scrollLeft || editorDoc.body.scrollLeft || 0; var cursor = editorDoc.createElement("span"); cursor.setAttribute("data-remote-cursor", userId); cursor.style.cssText = "position:absolute;" + "left:" + (rect.left + scrollLeft) + "px;" + "top:" + (rect.top + scrollTop) + "px;" + "width:2px;" + "height:" + rect.height + "px;" + "background-color:" + userInfo.color + ";" + "pointer-events:none;" + "z-index:9999;"; var label = editorDoc.createElement("span"); label.style.cssText = "position:absolute;" + "top:-18px;" + "left:0;" + "background-color:" + userInfo.color + ";" + "color:#fff;" + "font-size:11px;" + "padding:1px 5px;" + "border-radius:3px 3px 3px 0;" + "white-space:nowrap;"; label.textContent = userInfo.name; cursor.appendChild(label); editorDoc.body.appendChild(cursor); window._remoteCursors = window._remoteCursors || {}; window._remoteCursors[userId] = cursor; } function removeRemoteCursor(userId) { window._remoteCursors = window._remoteCursors || {}; var el = window._remoteCursors[userId]; if (el && el.parentNode) { el.parentNode.removeChild(el); } delete window._remoteCursors[userId]; } //////////////////////////////////////////////////////////////////////////////////////// //Global Variable //////////////////////////////////////////////////////////////////////////////////////// var dbName = "drawDB"; var storeName = "armsDiagrams"; var reqStatus = {}; var reqStateMap = {}; function execDocReady() { var pluginGroups = [ [ "../reference/lightblue4/docs/lib/widgster/widgster.js", "../reference/lightblue4/docs/lib/slimScroll/jquery.slimscroll.min.js", "../reference/jquery-plugins/jstree-v.pre1.0/_lib/jquery.cookie.js", "../reference/jquery-plugins/jstree-v.pre1.0/_lib/jquery.hotkeys.js", "../reference/jquery-plugins/jstree-v.pre1.0/jquery.jstree.js", "../reference/jquery-plugins/stompjs-develop/bundles/stomp.umd.min.js", "../reference/jquery-plugins/sockjs-client-main/dist/sockjs.min.js", "../reference/jquery-plugins/datetimepicker-2.5.20/build/jquery.datetimepicker.min.css", "../reference/light-blue/lib/bootstrap-datepicker.js", "../reference/jquery-plugins/datetimepicker-2.5.20/build/jquery.datetimepicker.full.min.js" ], [ "../reference/jquery-plugins/dataTables-1.10.16/media/css/jquery.dataTables_lightblue4.css", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Responsive/css/responsive.dataTables_lightblue4.css", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Select/css/select.dataTables_lightblue4.css", "../reference/jquery-plugins/dataTables-1.10.16/media/js/jquery.dataTables.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Responsive/js/dataTables.responsive.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Select/js/dataTables.select.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/RowGroup/js/dataTables.rowsGroup.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/dataTables.buttons.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/buttons.html5.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/buttons.print.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/jszip.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/pdfmake.min.js", "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/vfs_fonts.js" ], [ "../reference/jquery-plugins/select2-4.0.2/dist/css/select2_lightblue4.css", "../reference/jquery-plugins/select2-4.0.2/dist/js/select2.min.js", "../reference/jquery-plugins/lou-multi-select-0.9.12/css/multiselect-lightblue4.css", "../reference/jquery-plugins/lou-multi-select-0.9.12/js/jquery.quicksearch.js", "../reference/jquery-plugins/lou-multi-select-0.9.12/js/jquery.multi-select.js", "../reference/jquery-plugins/multiple-select-1.5.2/dist/multiple-select-bluelight.css", "../reference/jquery-plugins/multiple-select-1.5.2/dist/multiple-select.min.js" ], [ "../reference/jquery-plugins/swiper-11.1.4/swiper-bundle.min.js", "../reference/jquery-plugins/swiper-11.1.4/swiper-bundle.min.css", "./js/common/swiperHelper.js", "./css/customSwiper.css" ], [ "./js/common/table_new.js", "./js/adms/session-manager.js", "./js/adms/wiki-list.js", "./js/adms/editor-operation.js", "./js/adms/vesion-control.js" ] ]; loadPluginGroupsParallelAndSequential(pluginGroups) .then(function () { console.log("모든 플러그인 로드 완료"); $(".widget").widgster(); setSideMenu("sidebar_large_menu_ai", "sidebar_medium_menu_ai_adms", "sidebar_small_menu_ai_adms"); //Select2 makePdServiceSelectBox(); $(".multiple-select").multipleSelect(); var waitCKEDITOR = setInterval(function () { try { if (window.CKEDITOR) { if (window.CKEDITOR.status === "loaded") { CKEDITOR.replace("add_tabmodal_editor"); clearInterval(waitCKEDITOR); } } } catch (err) { console.log("CKEDITOR 로드가 완료되지 않아서 초기화 재시도 중..."); } }, 313 /*milli*/); $("#editor").trigger("init.editor"); $("#wiki_tree").slimScroll({ height: "500px" }); $("#btn_copy_link").on("click", function () { var link = location.origin + location.pathname + "?page=adms&pdServiceId=" + $("#selected_pdService").val() + "&wikiId=" + $("#wiki_tree").jstree("get_selected").attr("id").replace("node_", "").replace("copy_", ""); if (typeof navigator.clipboard == "undefined") { var textArea = document.createElement("textarea"); textArea.value = link; textArea.style.position = "fixed"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); document.execCommand("copy"); document.body.removeChild(textArea); return; } navigator.clipboard.writeText(link); }); var lastBlocks = []; var isComposing = false; CKEDITOR.instances["editor"].on("contentDom", function (event) { var editor = event.editor; var editable = editor.editable(); lastBlocks = getEditorBlocks(editor); function sendDiffIfChanged() { var currentBlocks = getEditorBlocks(editor); var ops = computeBlockDiff(lastBlocks, currentBlocks); var hasChanges = ops.some(function (op) { return op.op !== "retain"; }); if (hasChanges) { lastBlocks = currentBlocks.slice(); sessionManager.remoteUserUpdate(JSON.stringify({ type: "diff", ops: ops })); } buildTableOfContents(editor); } // IME 조합 시작 (한글 등) - 조합 중 diff 전송 차단 editable.attachListener(editor.document, "compositionstart", function () { isComposing = true; }); // IME 조합 완료 - 완성된 내용으로 diff 전송 editable.attachListener(editor.document, "compositionend", function () { isComposing = false; sendDiffIfChanged(); }); editable.attachListener(editor.document, "keyup", function () { // IME 조합 중에는 keyup 무시 (한글 입력 방해 방지) if (isComposing) return; sendDiffIfChanged(); }); }); // selectionChange는 contentDom 외부에서 한 번만 등록 (중복 방지) var lastCursorSend = 0; CKEDITOR.instances["editor"].on("selectionChange", function (event) { var editor = event.editor; // 편집 모드일 때만 커서 전송 (읽기 전용 수신측이 커서를 보내지 않도록) if (editor.readOnly) return; var now = Date.now(); if (now - lastCursorSend < 50) return; lastCursorSend = now; var cursorPath = getCursorPath(editor); if (cursorPath) { sessionManager.remoteUserUpdate( JSON.stringify({ type: "cursor", path: cursorPath.path, offset: cursorPath.offset }) ); } }); // 목차 토글 버튼 $("#btn_table_of_contents").on("click", function () { var $tocSection = $("#table_of_contents").closest(".col-md-2"); var $btn = $(this); if ($tocSection.is(":visible")) { $btn.html(' 목차 보기'); } else { $btn.html(' 목차 숨기기'); } $tocSection.toggleClass("hidden"); $("#editor_wrapper").toggleClass("col-md-10 col-md-12"); }); var userInfo = { userId: userID, userName: userName, userColor: generateRandomHexColor() }; var sessionManager = $.sessionManager(StompJs.Client, SockJS, userInfo); sessionManager.onContentsChange = function (contents) { var data = contents.selection.message; if (typeof data === "string") { try { var parsed = JSON.parse(data); if (parsed && parsed.type === "diff") { applyBlockDiff(CKEDITOR.instances["editor"], parsed.ops); return; } if (parsed && parsed.type === "cursor") { // 자신의 커서 메시지는 무시 (서버 브로드캐스트 시 자신에게도 올 수 있음) if (String(contents.id) !== String(userInfo.userId)) { showRemoteCursor(CKEDITOR.instances["editor"], contents, parsed); } return; } } catch (e) { // JSON이 아니면 HTML로 처리 } CKEDITOR.instances["editor"].setData(data); } }; var $userList = $("#user_list"); sessionManager.onStateChange = function (participants) { var labelVariants = ["default", "primary", "success", "info", "warning", "danger"]; $userList.empty(); participants.forEach(function (participant, i) { $userList.append( $("") .addClass("ml-0 mr-xs label label-" + labelVariants[i % labelVariants.length]) .append($("").addClass("fa fa-user mr-xs")) .append(participant.name) ); }); // 퇴장한 사용자의 커서 제거 var activeIds = {}; participants.forEach(function (p) { if (p.id) activeIds[p.id] = true; }); window._remoteCursors = window._remoteCursors || {}; Object.keys(window._remoteCursors).forEach(function (userId) { if (!activeIds[userId]) { removeRemoteCursor(userId); } }); }; window.addEventListener("beforeunload", function (event) { if (!$("#btn_save_contents").hasClass("hidden")) event.preventDefault(); }); $("#btn_save_contents").on("click", function () { $(CKEDITOR.instances["editor"].document.getBody().$) .find(".panel.panel-default") .each(function () { var $self = $(this); var $panelBody = $self.find(".panel-body"); var $collapse = $self.find(".collapse"); $panelBody.empty(); $collapse.collapse("hide"); }); $.ajax({ type: "PUT", url: "/auth-user/api/arms/wiki/updateWiki.do", contentType: "application/json;charset=UTF-8", dataType: "json", data: JSON.stringify({ wikiId: getWikiId(), author: userID, contents: CKEDITOR.instances["editor"].getData() }), success: function () { $("#btn_save_contents").addClass("hidden"); $("#btn_cancel").addClass("hidden"); $("#btn_edit_contents").removeClass("hidden"); $("#btn_version").removeClass("hidden"); CKEDITOR.instances["editor"].setReadOnly(true); $("#user_list").empty(); sessionManager.closeRoom(); } }); }); $("#btn_cancel").on("click", function () { $.ajax({ type: "GET", url: "/auth-user/api/arms/wiki/" + getWikiId() + "/getWiki.do", contentType: "application/json;charset=UTF-8", dataType: "json", success: function (data) { $("#btn_save_contents").addClass("hidden"); $("#btn_cancel").addClass("hidden"); $("#btn_edit_contents").removeClass("hidden"); $("#btn_version").removeClass("hidden"); $("#user_list").empty(); CKEDITOR.instances["editor"].setData(data.contents); CKEDITOR.instances["editor"].setReadOnly(true); sessionManager.closeRoom(); } }); }); $("#btn_edit_contents").on("click", function () { $(this).addClass("hidden"); $("#btn_version").addClass("hidden"); $("#btn_save_contents").removeClass("hidden"); $("#btn_cancel").removeClass("hidden"); CKEDITOR.instances["editor"].setReadOnly(false); sessionManager.setRoom(getWikiId()).done(function () { sessionManager.openWebsocket(); }); }); var $wikiTree = $("#wiki_tree"); $("#btn_create_file").on("click", function () { var selectedWiki = $wikiTree.jstree("get_selected"); if (selectedWiki.length === 0) { selectedWiki = $("#node_2"); } $wikiTree.jstree("create", selectedWiki, "last", { attr: { rel: "default" } }); }); $("#btn_create_folder").on("click", function (obj) { var selectedWiki = $wikiTree.jstree("get_selected"); if (selectedWiki.length === 0) { selectedWiki = $("#node_2"); } $wikiTree.jstree("create", selectedWiki, "last", { attr: { rel: "folder" } }); }); $("#btn_collapse_all").on("click", function () { $wikiTree.jstree("close_all"); }); $("#mmenu .form-search").submit(function (event) { event.preventDefault(); $wikiTree.jstree("search", document.getElementById("text").value); }); resizePanel(); $("#version_table").on("click", ".btn-restore", function (clickEvent) { var $btn = $(clickEvent.target); var row = $btn.data("row"); $.ajax({ type: "GET", url: "/auth-user/api/arms/wiki/" + row.wikiId + "/" + row.version + "/getWiki.do", contentType: "application/json;charset=UTF-8", dataType: "json", success: function (data) { closePanel(); $("#btn_edit_contents").addClass("hidden"); $("#version_warning").removeClass("hidden"); $("#btn_restore_version").data("version", row.version); CKEDITOR.instances["editor"].setData(data.contents); } }); }); $("#btn_latest_version").on("click", function () { $("#btn_save_contents").addClass("hidden"); $("#btn_cancel").addClass("hidden"); var $btnVersion = $("#btn_version"); $btnVersion.addClass("hidden"); $.ajax({ type: "GET", url: "/auth-user/api/arms/wiki/" + getWikiId() + "/getWiki.do", contentType: "application/json;charset=UTF-8", dataType: "json", success: function (data) { $("#btn_edit_contents").removeClass("hidden"); $btnVersion.removeClass("hidden"); $("#version_warning").addClass("hidden"); CKEDITOR.instances["editor"].setData(data.contents); } }); }); $("#btn_restore_version").on("click", function () { var version = $(this).data("version"); $.ajax({ type: "PUT", url: "/auth-user/api/arms/wiki/changeRecent.do", contentType: "application/json;charset=UTF-8", dataType: "json", data: JSON.stringify({ wikiId: getWikiId(), version: version }), success: function () { $("#btn_version").removeClass("hidden"); $("#btn_edit_contents").removeClass("hidden"); $("#btn_cancel").addClass("hidden"); $("#btn_save_contents").addClass("hidden"); $("#version_warning").addClass("hidden"); $("#user_list").empty(); CKEDITOR.instances["editor"].setReadOnly(true); sessionManager.closeRoom(); } }); }); initPriorityCalculation(); switch_action_for_mode(); get_arms_req_state_list() .then((state_list) => { console.log(state_list); let reqStateList = []; for (let k in state_list) { let state = state_list[k]; console.log(state); //--- 테이블 보기에서 사용하는 전역변수 reqStateMap[state.c_id] = state; reqStatus[state.c_title] = state.c_id; reqStateList.push(state); } console.log(reqStateList); binding_state_list("addview_req_state", reqStateList, true); }) .catch((error) => { console.error("Error fetching data:", error); reject(error); // 에러 발생 시 프라미스를 거부 }); click_btn_for_req_save(); autoCompleteForUser(); drawio(); drawdb(); // 스크립트 실행 로직을 이곳에 추가합니다. var 라따적용_클래스이름_배열 = [".ladda_save_req"]; laddaBtnSetting(라따적용_클래스이름_배열); }) .catch(function () { console.error("플러그인 로드 중 오류 발생"); }); } function getReviewer(index, req_reviewers_id) { var reviewer = "none"; if ($("#" + req_reviewers_id).select2("data")[index] != undefined) { reviewer = $("#" + req_reviewers_id).select2("data")[0].text; } return reviewer; } function getWikiId() { var selectedJsTreeId = $("#wiki_tree").jstree("get_selected").attr("id").replace("node_", "").replace("copy_", ""); return "WIKI_" + $("#selected_pdService").val() + "_" + selectedJsTreeId; } function generateRandomHexColor() { var randomColor = Math.floor(Math.random() * 16777216); var hexColor = randomColor.toString(16); while (hexColor.length < 6) { hexColor = "0" + hexColor; } return "#" + hexColor; } function bind_VersionData_By_PdService() { $(".multiple-select option").remove(); $.ajax({ url: "/auth-user/api/arms/pdService/getVersionList?c_id=" + $("#selected_pdService").val(), type: "GET", dataType: "json", progress: true, statusCode: { 200: function (data) { ////////////////////////////////////////////////////////// for (var k in data.response) { var obj = data.response[k]; var $opt = $("", { value: obj.c_id, text: obj.c_title }); $(".multiple-select").append($opt); } if (data.length > 0) { console.log("[ reqAdd :: bind_VersionData_By_PdService ] :: result = display 재설정."); } $(".multiple-select").multipleSelect("refresh"); ////////////////////////////////////////////////////////// jSuccess("버전 조회가 완료 되었습니다."); } }, error: function (e) { jError("버전 조회 중 에러가 발생했습니다."); } }); } function makePdServiceSelectBox() { var $selectedPdservice = $("#selected_pdService"); $(".select2-results__options").slimScroll(); $.ajax({ url: "/auth-user/api/arms/pdServicePure/getPdServiceMonitor.do", type: "GET", success: function (data) { $selectedPdservice .select2({ placeholder: "Select Product(Service)", data: data.response.map(function (product) { return { id: product.c_id, text: product.c_title }; }) }) .on("select2:select", function (event) { $("#select_PdService").text(event.params.data.text); $("#btn_save_contents").addClass("hidden"); $("#btn_cancel").addClass("hidden"); $("#btn_edit_contents").addClass("hidden"); $("#btn_version").addClass("hidden"); $("#user_list").empty(); CKEDITOR.instances["editor"].setData(""); CKEDITOR.instances["editor"].setReadOnly(true); $("#wiki_tree").trigger("create.wikiList"); $("#tree_util_btn_wrapper").removeClass("hidden"); bind_VersionData_By_PdService(); }); var urlParams = new URL(location.href).searchParams; var pdServiceId = urlParams.get("pdServiceId"); if (pdServiceId !== null) { $selectedPdservice.val(parseInt(pdServiceId)).trigger("change"); $selectedPdservice.trigger({ type: "select2:select", params: { data: $selectedPdservice.select2("data") } }); } jSuccess("제품(서비스) 조회가 완료 되었습니다."); }, error: function (e) { jError("제품(서비스) 조회 중 에러가 발생했습니다."); } }); } function openPanel() { $("#slide_panel_wrapper").removeClass("hidden"); $("#version_table").trigger("init.version_list"); $("body").addClass("modal-open"); } function closePanel() { $("#slide_panel_wrapper").addClass("hidden"); var $versionTable = $("#version_table"); if ($versionTable.data("arms.table")) { $versionTable.table("empty"); } $("body").removeClass("modal-open"); } function buildTableOfContents(editor) { var $toc = $("#table_of_contents"); var $btnToc = $("#btn_table_of_contents"); var $tocSection = $toc.closest(".col-md-2"); var $editorWrapper = $("#editor_wrapper"); $toc.empty(); $btnToc.html(' 목차 숨기기'); var body = editor.document && editor.document.getBody(); if (!body) return; var headings = body.$.querySelectorAll("h1, h2, h3, h4, h5, h6"); if (headings.length === 0) { $btnToc.addClass("hidden"); $tocSection.addClass("hidden"); $editorWrapper.removeClass("col-md-10").addClass("col-md-12"); return; } $btnToc.removeClass("hidden"); $tocSection.removeClass("hidden"); $editorWrapper.removeClass("col-md-12").addClass("col-md-10"); var tocItems = Array.prototype.reduce.call( headings, function (_tocItems, heading, hIdx) { var level = hIdx === 0 ? 1 : parseInt(heading.tagName.charAt(1), 10); var text = heading.textContent.trim(); if (!text) return _tocItems; return _tocItems.concat([{ level: level, text: text }]); }, [] ); if (tocItems.length === 0) return; var minLevel = Math.min.apply( null, tocItems.map(function (item) { return item.level; }) ); $toc.append( tocItems.map(function (item) { var indent = (item.level - minLevel) * 15; return $("
") .addClass("mb-xs") .css({ "padding-left": indent + "px" }) .append($("").addClass("text-link").text(item.text)); }) ); } function resizePanel() { var isResizing = false; $(".resizer").on("mousedown", function () { isResizing = true; $("body").css("cursor", "ew-resize"); }); $(document).on("mousemove", function (e) { if (!isResizing) return; const newWidth = $(window).width() - e.clientX; if (newWidth >= 200 && newWidth <= $(window).width() * 0.9) { $(".slidePanel").css("width", newWidth + "px"); } }); $(document).on("mouseup", function () { if (isResizing) { isResizing = false; $("body").css("cursor", "default"); } }); $("#slide_panel_wrapper").on("mousedown", function (event) { var $target = $(event.target); if (!$target.closest(".slidePanel").length && !$target.closest("#btn_version").length) { closePanel(); } }); } /////////////////////////////////////////////////////////////////////////////// // 팝업에서 신규 요구사항 저장 버튼 /////////////////////////////////////////////////////////////////////////////// function click_btn_for_req_save() { $("#save_req").click(function () { var table_name = "T_ARMS_REQADD_" + $("#selected_pdService").val(); var c_type_value; if (isEmpty($("input[name=reqType]:checked").val())) { c_type_value = "default"; } else { c_type_value = $("input[name=reqType]:checked").val(); } var req_title = $("#addview_req_title").val().trim(); if (!req_title) { jError("요구사항 제목이 없습니다."); return false; } var versionset_link = $("#add_multi_version").val(); if (versionset_link.length < 1) { jError("선택된 버전이 없습니다."); return false; } var reviewers01 = getReviewer(0, "addview_req_reviewers"); var reviewers02 = getReviewer(1, "addview_req_reviewers"); var reviewers03 = getReviewer(2, "addview_req_reviewers"); var reviewers04 = getReviewer(3, "addview_req_reviewers"); var reviewers05 = getReviewer(4, "addview_req_reviewers"); let selectedReviewers = $("#addview_req_reviewers").select2("data"); if (c_type_value === "default" && selectedReviewers.length < 1) { jError("지정된 반영 리뷰어가 없습니다."); return false; } const priority_val = $("#popup_req_value_div input[name='addview_req_value']").val(); let select_req_urgency_link = $("#popup_req_urgency_div input[name='addview_req_urgency_options']:checked").val(); if (c_type_value === "default" && select_req_urgency_link === undefined) { jError("요구사항 긴급도 설정이 필요합니다."); return false; } let select_req_importance_link = $( "#popup_req_importance_div input[name='addview_req_importance_options']:checked" ).val(); if (c_type_value === "default" && select_req_importance_link === undefined) { jError("요구사항 중요도 설정이 필요합니다."); return false; } let priority_value = $("#popup_req_priority_div input[name='addview_req_priority_options']:checked").val(); let select_req_priority_link = priority_value === undefined ? "5" : priority_value; let select_req_difficulty_link = $( "#popup_req_difficulty_div input[name='addview_req_difficulty_options']:checked" ).val(); if (c_type_value === "default" && select_req_difficulty_link === undefined) { jError("요구사항 난이도 설정이 필요합니다."); return false; } let select_req_state_link = $("#popup_req_state_div input[name='addview_req_state_options']:checked").val(); if (c_type_value === "default" && select_req_state_link === undefined) { jError("요구사항 상태 설정이 필요합니다."); return false; } var start_date_value = $("#addview_req_start_date").val(); var c_req_start_date; if (start_date_value) { c_req_start_date = new Date(start_date_value); } else { jError("요구사항 시작날짜 지정이 필요합니다."); return false; } var end_date_value = $("#addview_req_end_date").val(); var c_req_end_date; if (end_date_value) { c_req_end_date = new Date(end_date_value); c_req_end_date.setHours(23); c_req_end_date.setMinutes(59); c_req_end_date.setSeconds(59); c_req_end_date.setMilliseconds(999); } else { jError("요구사항 종료날짜 지정이 필요합니다."); return false; } var data_object_param = { ref: 2, c_title: req_title, c_type: c_type_value, c_req_pdservice_link: $("#selected_pdService").val(), c_req_pdservice_versionset_link: JSON.stringify(versionset_link), c_req_start_date: c_req_start_date, c_req_end_date: c_req_end_date, c_req_writer: "[" + userName + "]" + " - " + userID, c_req_contents: CKEDITOR.instances["add_tabmodal_editor"].getData(), c_req_desc: "설명", c_req_etc: "비고" }; var selectedProductServiceId = $("#selected_pdService").val(); var drawioXML = localStorage.getItem("req-create-drawio-" + selectedProductServiceId); if (drawioXML !== null) { data_object_param.c_drawio_contents = drawioXML; } var drawioThumbnail = localStorage.getItem("req-create-drawio-image-raw-" + selectedProductServiceId); if (drawioThumbnail !== null) { data_object_param.c_drawio_image_raw = drawioThumbnail; } if (c_type_value === "default") { Object.assign(data_object_param, { // 신규 추가 :: 우선순위(자동계산값), 긴급도, 중요도 c_req_priority_value: priority_val, c_req_urgency_link: select_req_urgency_link, c_req_importance_link: select_req_importance_link, c_req_difficulty_link: select_req_difficulty_link, c_req_priority_link: select_req_priority_link, c_req_state_link: select_req_state_link, c_req_reviewer01: reviewers01, c_req_reviewer02: reviewers02, c_req_reviewer03: reviewers03, c_req_reviewer04: reviewers04, c_req_reviewer05: reviewers05, c_req_reviewer01_status: "Draft", c_req_reviewer02_status: "Draft", c_req_reviewer03_status: "Draft", c_req_reviewer04_status: "Draft", c_req_reviewer05_status: "Draft" }); } console.log("save_req :: save data ->"); console.log(data_object_param); var success_message = c_type_value === "default" ? "신규 요구사항 ( " + req_title + " )이 추가되었습니다." : " 요구사항 폴더 ( " + req_title + " )가 등록되었습니다."; var searchKey = "create-reqadd-" + $("#selected_pdService").val(); selectByIndexedDB(searchKey) .then((result) => { if (result) { data_object_param.c_drawdb_contents = JSON.stringify(result); data_object_param.c_drawdb_image_raw = result.armsThumbnail; } }) .catch((error) => { console.log("IndexedDB 조회 실패:", error); }) .finally(() => { $.ajax({ url: "/auth-user/api/arms/reqAdd/sync/" + table_name + "/addNode.do", type: "POST", data: data_object_param, success: function (res) { pendingRequestIds[res.requestId] = (c_id) => { var reqLink = new CKEDITOR.dom.element("a", CKEDITOR.instances["editor"].document); reqLink.setAttribute( "href", "/arms/detail.html?page=detail&pdService=" + data_object_param.c_req_pdservice_link + "&pdServiceVersion=" + versionset_link + "&reqAdd=" + c_id + "&jiraServer=&jiraProject=" ); reqLink.setAttribute("target", "_blank"); reqLink.$.textContent = CKEDITOR.instances["editor"].getSelection().getSelectedText(); CKEDITOR.instances["editor"].insertElement(reqLink); jSuccess(success_message); }; $("#close_req").trigger("click"); localStorage.removeItem("req-create-drawio-" + selectedProductServiceId); localStorage.removeItem("req-create-drawio-image-raw-" + selectedProductServiceId); localStorage.removeItem("req-create-drawio-time-" + selectedProductServiceId); deleteByIndexedDB(searchKey); removeDrawIOConfig(); } }); }); }); } /////////////////////////////////////////////////////////////////////////////// // 우선순위 /////////////////////////////////////////////////////////////////////////////// function initPriorityCalculation() { $("#addview_req_urgency input[type='radio']").on( "change", updatePriorityValue ); $("#addview_req_importance input[type='radio']").on( "change", updatePriorityValue ); $("#addview_req_difficulty input[type='radio']").on( "change", updatePriorityValue ); } function updatePriorityValue() { var isRegisterPopupMode = $("#requirement_modal").is(":visible"); var urgency = parseFloat($("input[name='addview_req_urgency_options']:checked").val()) || 5; var importance = parseFloat($("input[name='addview_req_importance_options']:checked").val()) || 5; var difficulty = parseFloat($("input[name='addview_req_difficulty_options']:checked").val()) || 5; if (isRegisterPopupMode) { urgency = parseFloat($("input[name='addview_req_urgency_options']:checked").val()) || 5; importance = parseFloat($("input[name='addview_req_importance_options']:checked").val()) || 5; difficulty = parseFloat($("input[name='addview_req_difficulty_options']:checked").val()) || 5; } var calculatedValue = calculatePriorityValue(urgency, importance, difficulty); if (isRegisterPopupMode) { $("#addview_req_value").val(calculatedValue); } else { $("#addview_req_value").val(calculatedValue); } } function calculatePriorityValue(urgency, importance, difficulty) { // HTML 값을 피보나치 값으로 변환 var urgencyFib = convertToFibonacci(urgency); var importanceFib = convertToFibonacci(importance); var difficultyFib = convertToFibonacci(difficulty); // 우선순위 = (긴급도 * 0.5) + (중요도 * 0.45) - (난이도 * 0.05) var priorityValue = urgencyFib * 0.5 + importanceFib * 0.45 - difficultyFib * 0.05; // 소수점 2자리까지 반올림 return Math.round(priorityValue * 100) / 100; } function convertToFibonacci(value) { var fibonacciMap = { 3: 8, // 매우 긴급/중요/어려움 -> 8 4: 5, // 긴급/중요/어려움 -> 5 5: 3, // 보통 -> 3 6: 2, // 낮음/쉬움 -> 2 7: 1 // 매우 낮음/쉬움 -> 1 }; return fibonacciMap[value] || 3; } /////////////////////////////////////////////////////////////////////////////// // drawio, drawdb 관련 /////////////////////////////////////////////////////////////////////////////// function drawio() { // 로컬 스토리지 초기화 localStorage.clear(); $("#btn_req_add_edit_drawio, #btn_modal_req_add_drawio").on("click", function () { var selectedJsTreeId = $("#wiki_tree").jstree("get_selected").attr("id").replace("node_", "").replace("copy_", ""); if (this.id === "btn_modal_req_add_drawio") { if ($("#selected_pdService").val() === "" || $("#selected_pdService").val() === undefined) { jError("제품(서비스)을 선택해 주세요."); return false; } window.open( "/reference/drawio?id=" + $("#selected_pdService").val() + "&type=create&splash=0&armsType=reqadd", "_blank" ); } else if (this.id === "btn_req_add_edit_drawio") { if (selectedJsTreeId === "" || selectedJsTreeId === undefined) { jError("요구사항을 선택해 주세요."); return false; } window.open( "/reference/drawio?id=" + selectedJsTreeId + "&type=update&splash=0&armsType=reqadd&pdServiceId=" + $("#selected_pdService").val(), "_blank" ); } else { jError("drawio was clicked but id is not matched"); return false; } }); } function drawdb() { $("#btn_req_add_edit_drawdb, #btn_modal_req_add_drawdb").on("click", function () { if (this.id === "btn_modal_req_add_drawdb") { if ($("#selected_pdService").val() == "" || $("#selected_pdService").val() == undefined) { jError("제품(서비스)을 선택해 주세요."); return false; } window.open( "/reference/drawdb?armsId=" + $("#selected_pdService").val() + "&armsMode=create&armsType=reqadd&pdServiceId=" + $("#selected_pdService").val(), "_blank" ); } else if (this.id === "btn_req_add_edit_drawdb") { if (selectedJsTreeId == "" || selectedJsTreeId == undefined) { jError("요구사항을 선택해 주세요."); return false; } window.open( "/reference/drawdb?armsId=" + selectedJsTreeId + "&armsMode=update&armsType=reqadd&pdServiceId=" + $("#selected_pdService").val(), "_blank" ); } else { jError("drawdb was clicked but id is not matched"); return false; } }); } function deleteByIndexedDB(searchKey) { var request = indexedDB.open(dbName); request.onerror = function (event) { console.error("IndexedDB 열기 실패:", event.target.error); }; request.onsuccess = function (event) { var db = event.target.result; if (!db.objectStoreNames.contains(storeName)) { reject("armsDiagrams 오브젝트 스토어가 존재하지 않습니다."); return; } var transaction = db.transaction([storeName], "readwrite"); var objectStore = transaction.objectStore(storeName); var deleteRequest = objectStore.delete(searchKey); deleteRequest.onerror = function (event) { console.error("데이터 삭제 실패:", event.target.error); }; deleteRequest.onsuccess = function (event) { console.log("데이터가 성공적으로 삭제되었습니다."); }; }; } function selectByIndexedDB(searchKey) { return new Promise((resolve, reject) => { var request = indexedDB.open(dbName); request.onerror = function (event) { console.error("IndexedDB 열기 실패:", event.target.error); reject(event.target.error); }; request.onsuccess = function (event) { var db = event.target.result; if (!db.objectStoreNames.contains(storeName)) { reject("armsDiagrams 오브젝트 스토어가 존재하지 않습니다."); return; } var transaction = db.transaction([storeName], "readonly"); var objectStore = transaction.objectStore(storeName); var getRequest = objectStore.get(searchKey); getRequest.onerror = function (event) { console.log("데이터 조회 실패:", event.target.error); reject(event.target.error); }; getRequest.onsuccess = function (event) { var result = event.target.result; if (result) { console.log("조회된 데이터:", result); resolve(result); } else { console.log("해당 키에 대한 데이터가 없습니다."); resolve(null); } }; }; }); } function setDrawioImage(localStorageKey, localStorageValue, mode) { var imageSrcArray = Array(1).fill(localStorageValue); if (mode === "create") { addImageToSwiper(imageSrcArray, "modal_req_add_drawio_swiper_container"); $("#modal_req_add_drawio_swiper").show(); } else if (mode === "update") { $("#req_add_edit_drawio_swiper").show(); addImageToSwiper(imageSrcArray, "req_add_edit_drawio_swiper_container"); $("#req_add_view_drawio_swiper").show(); addImageToSwiper(imageSrcArray, "req_add_view_drawio_swiper_container"); } } function setDrawdbImage(localStorageKey, localStorageValue, mode) { var imageSrcArray = Array(1).fill(localStorageValue); if (mode === "create") { addImageToSwiper(imageSrcArray, "modal_req_add_drawdb_swiper_container"); $("#modal_req_add_drawdb_swiper").show(); } else if (mode === "update") { $("#req_add_edit_drawdb_swiper").show(); addImageToSwiper(imageSrcArray, "req_add_edit_drawdb_swiper_container"); $("#req_add_view_drawdb_swiper").show(); addImageToSwiper(imageSrcArray, "req_add_view_drawdb_swiper_container"); } } /////////////////////////////////////////////////////////////////////////////// // 팝업에서 요구사항 등록 모드에 따라서, 정보를 보여주거나 감추는 역할 /////////////////////////////////////////////////////////////////////////////// function switch_action_for_mode() { $(".form-horizontal input[name=reqType]").on("change", function () { if ($("input[name=reqType]:checked").val() === "default") { $("#popup_version_div").show(); $("#popup_reviewer_div").show(); // 반영리뷰어 $("#popup_req_value_div").show(); // 요구사항 우선순위 $("#popup_req_urgency_div").show(); // 요구사항 긴급도 $("#popup_req_importance_div").show(); // 요구사항 중요도 $("#popup_req_difficulty_div").show(); // 요구사항 난이도 $("#popup_req_state_div").show(); // 요구사항 상태 $("#modal_req_add_drawio_div").show(); // drawio 등록 $("#modal_req_add_drawdb_div").show(); // drawdb 등록 var selectedProductServiceId = $("#selected_pdService").val(); if (localStorage.getItem("req-create-drawio-image-raw-" + selectedProductServiceId)) { setDrawioImage( "req-create-drawio-image-raw-" + selectedProductServiceId, localStorage.getItem("req-create-drawio-image-raw-" + selectedProductServiceId), "create" ); } selectByIndexedDB("create-reqadd-" + selectedProductServiceId) .then((result) => { if (result) { var armsThumbnail = result.armsThumbnail; console.log("Arms Thumbnail:", armsThumbnail); setDrawdbImage("create-reqadd-" + selectedProductServiceId, armsThumbnail, "create"); } else { console.log("No data found for the given key."); } }) .catch((error) => { console.error("Error:", error); }); } else { $("#popup_reviewer_div").hide(); // 반영리뷰어 $("#popup_req_value_div").hide(); // 요구사항 우선순위 $("#popup_req_urgency_div").hide(); // 요구사항 긴급도 $("#popup_req_importance_div").hide(); // 요구사항 중요도 $("#popup_req_difficulty_div").hide(); // 요구사항 난이도 $("#popup_req_state_div").hide(); // 요구사항 상태 $("#modal_req_add_drawio_swiper").hide(); // drawio 뷰 $("#modal_req_add_drawio_div").hide(); // drawio 등록 $("#modal_req_add_drawdb_swiper").hide(); // drawdb 뷰 $("#modal_req_add_drawdb_div").hide(); // drawdb 등록 } }); } /////////////////////////////////////////////////////////////////////////////// // --- select2 (사용자 자동완성 검색 ) 설정 --- // /////////////////////////////////////////////////////////////////////////////// function autoCompleteForUser() { $(".js-data-example-ajax").select2({ maximumSelectionLength: 5, width: "resolve", ajax: { url: function (params) { return "/auth-user/search-user/" + params.term; }, dataType: "json", delay: 250, processResults: function (data, params) { params.page = params.page || 1; return { results: data, pagination: { more: params.page * 30 < data.total_count } }; }, cache: true }, placeholder: "리뷰어 설정을 위한 계정명을 입력해 주세요", minimumInputLength: 1, templateResult: formatUser, templateSelection: formatUserSelection }); } // --- select2 (사용자 자동완성 검색 ) templateResult 설정 --- // function formatUser(jsonData) { var $container = $( "