// (2024.12.3, 차재복, Cha Jae Bok, http://www.ktword.co.kr)
// 기능 : db로부터 해당 용어해설을 ajax에 의해 가져오고, 이를 out div에 보여주기
// 호출 : [navi_base.js] li_create()
// 용도 : 클릭 이벤트 처리용
function detailSeparateView(e) {
e.preventDefault();
// div 찾음 (out)
let out = this.parentNode.querySelector("div[class='detail_view']");
// bullet 찾음
let bullet = this.querySelector("a[class='bullet']");
// no 찾음
const no = this.dataset.no;
// bullet 수평/아래
// if (bullet.innerText == '▷') bullet.innerText = '▽';
// else if (bullet.innerText == '▽') bullet.innerText = '▷';
if (getComputedStyle(out).display == 'block') bullet.innerText = '▷';
else bullet.innerText = '▽';
// div 펼침/닫힘
if (out && out.childElementCount > 0) {
itemShowHide(out, null); // common_utils.js
return;
}
// 서버 데이터 가져와서, out에 섹션별 보여줌
detailFetchView(no,out)
// history 등록
const id_list = this.closest('li[data-itemtype="id"]').dataset.idlist;
const name = this.querySelector('.title').innerText;
historyAdd(id_list, no, name);
}
// 기능 : navi 없이, 직접 no 해설 div에 보여주기
// 호출 : navi_search.js 내 async function asynctodo (idPath, startElem, no=null)
function detailViewOnDirect(no,bullet,out){
// bullet 하향
bullet.innerText = '▽';
// div 존재하고, div 산하 자식 있고, bullet 하향이면, 복귀
if (out && out.childElementCount > 0) {
out.style.display = "block";
return;
// 그렇지 않으면, 펼침
} else {
// 서버 데이터 가져와서, out에 섹션별 보여줌
detailFetchView(no,out)
}
}
function detailFetchView(no, out) {
// ajax promise 호출 (서버로부터 데이터 가져오기)
const choice = 'ajaxFetchDetailSeparate';
let url = '/test/navigation/naviFetch.php?choice='+choice+'&no=' + no;
let method = 'get';
ajaxPromise(url, method) // common_utils.js
// 가져온 결과에 대한 데이터 가공
.then(
response => {
// (개발,디버깅)
// console.log(response.data);
if(response.err_msg) {
alert(response.err_msg);
console.log(url);
}
// out div('detail_view') 산하에, 섹션별로 보여주기
displayPerSection(out, response);
},
error => {
alert(error);
}
)
.finally(() => {
// 중복 요청 방지 해제
// const click = out.parentElement.querySelector('span.collapse');
// click.style.pointerEvents = "auto"; // 요청 완료 후 클릭 활성화
});
}
// 기능 : 화면(out)에 각 섹션(1.,2.,...)별로 div 생성 및 그 안에 서버응답 내용을 보여주기
// 호출 : [navi_base_edit.js] editDivHandle(li,response)
function displayPerSection(out, responseAll) {
// out div show
out.style.display = 'block';
const response = responseAll.separated;
const name = responseAll.name;
const fullname = responseAll.fullname;
const date = responseAll.date;
// button 생성
const length = Object.keys(response).length;
let buttonsDiv = createSectionbuttons(out, response); // navi_base_moreShow.js
// 맨끝 button 대신 본문 링크 삽입
const span = document.createElement('span');
span.style.marginLeft = '40px';
const li = out.closest('li');
// 맨끝 해당 용어 view 링크
// const title = li.querySelector('.title').innerText;
span.innerHTML = "<a href='/test/view/view.php?no="+li.dataset.no
+"' target='one_more' title='"
+ fullname +"'>"
+ name + "</a>"
+ " (" + date + ")";
buttonsDiv.appendChild(span);
// 개별 section별 div 생성
const divsDiv = document.createElement('div');
for(i=0; i<length; i++) {
// div 생성 및 스타일링
const div = document.createElement('div');
// div 식별자
div.setAttribute('class','secDiv');
div.dataset.dno = i;
// div 스타일링
div.style.marginLeft = '5px';
div.style.border = '1px solid gray';
div.style.paddingLeft = '10px';
// 첫 section div 외 모두 보이지 않기 (hide)
if(i != 0) div.style.display = 'none';
// 서버 응답을 div에 출력
div.innerHTML = "<pre style='font-size:13px; line-height:170%'>"
+ response[i]
+ "</pre>";
// div 출력 중 a 요소들에 스타일링
div.querySelectorAll('a').forEach( elem => { elem.style.textDecoration = 'none' });
// 수식 mathjax 있으면, mathjax Typeset 설정
const startDisp = response[i].indexOf('[#'),
endDisp = response[i].indexOf('#]',startDisp+1),
startInline = response[i].indexOf('{#'),
endInline = response[i].indexOf('#}',startInline+1);
if ( (startDisp > -1 && endDisp > -1) || (startInline > -1 && endInline > -1) ) {
MathJax.Hub.Queue(["Typeset",MathJax.Hub,div]); // mathjax_config.php
}
// out에 div 자식 추가
// out.appendChild(div);
divsDiv.appendChild(div);
}
// 중복 생성 방지
if (out.querySelector(':scope > div')) {
// alert('이미 산하에 div 있음!');
buttonsDiv.remove();
divsDiv.remove();
return;
} else {
out.appendChild(buttonsDiv); // 버튼들 및 링크
out.appendChild(divsDiv); // 섹션 div들
}
}
// 호출 : [navi_base.js] li_create()
function detailSourceView(span) {
// 현재 부모 li, 소스 출력할 div, bullet(▽/▷) a 취득
const li = span.closest('li');
const out = li.querySelector('.detail_view');
const bullet = li.querySelector('a.bullet');
// div 산하 자식 여부에 따라, show/hide 및 ▽/▷ 결정
if(out.childNodes.length > 0) {
if (out.style.display == "none") {
out.style.display = "block";
bullet.innerText = '▽';
} else {
out.style.display = "none";
bullet.innerText = '▷';
}
return;
} else {
out.style.display = "block";
bullet.innerText = '▽';
}
// ajax promise 호출 (서버로부터 데이터 가져오기)
// const id = e.target.closest('li').dataset.id;
const id = li.dataset.id;
const choice = 'ajaxFetchSourceView';
const url = '/test/navigation/naviFetch.php?choice='+choice+'&id=' + id;
const method = 'get';
return ajaxPromise(url, method) // common_utils.js
// 가져온 결과에 대한 데이터 가공
.then(
response => {
// (개발,디버깅)
if(response.err_msg) {
alert(response.err_msg);
console.log(url);
}
//console.log(response);
if(!response.data.code) {
alert('서버로부터 빈 소스 파일 만 도착함');
return;
}
// 상,하 div 생성
const highDiv = document.createElement('div');
highDiv.style.display = 'flex';
highDiv.style.width = '100%';
const lowDiv = document.createElement('div');
lowDiv.style.display = 'flex';
lowDiv.style.width = '100%';
out.appendChild(highDiv);
out.appendChild(lowDiv);
// 소스 배열로 가공
const rawStr = response.data.code;
const splitedSrcArr = rawStr.split(/\r\n|\n/g);
// 줄 수, 몫, 나머지 계산
const N = 30; // 30줄씩 끊어서 보여주기
const lineLength = splitedSrcArr.length // rawStr.match(/\n/g).length;
const quotient = parseInt(lineLength / N); // 몫
const remainder = lineLength % N; // 나머지
// 줄번호 배열로 가공
const splitedNumArr = Array.from( { length : lineLength }, (_, index) => index + 1);
// section별 button 생성
const objData = {};
let i;
for(i=0; i<quotient; i++)
objData[i] = (N*i + 1) + '~' + (N*i + N);
if(i==quotient && remainder>0)
objData[i] = (N*i + 1) + '~' + (N*i + (i<quotient ? N : remainder));
let buttonsDiv = createSectionbuttons(highDiv, objData); // navi_base_moreShow.js
highDiv.appendChild(buttonsDiv);
// section button 이후 파일명
const fileNameSpan = document.createElement('span');
fileNameSpan.style.marginLeft = '20px';
fileNameSpan.innerText = response.data.file;
/*
fileNameSpan.innerHTML = "<a href='/test/open_src/view_src.php?dir="+response.data.dir
+"&file="+response.data.file+"' target='_blank'>"
+response.data.file+"</a>";
*/
highDiv.appendChild(fileNameSpan);
// section button 이후 날짜
const dateSpan = document.createElement('span');
dateSpan.style.marginLeft = '15px';
dateSpan.innerText = '('+response.data.dateInTbl+')';
highDiv.appendChild(dateSpan);
if(response.data.dateDiff < 0) {
const delayDateSpan = document.createElement('span');
highDiv.appendChild(delayDateSpan);
delayDateSpan.style.color = 'red';
delayDateSpan.style.marginLeft = '10px';
if(glob_var.user_type != '종합관리자') {
// delayDateSpan.innerText = '( 수정중 )';
} else {
delayDateSpan.style.cursor = 'pointer';
delayDateSpan.innerText = ': 구 버젼 ';
delayDateSpan.addEventListener('click', () => {
const updateChoice = 'sourceFileUpdate';
const updateUrl = '/test/navigation/naviUpdate.php?choice='+updateChoice+'&id='+id;
const updateMethod = 'get';
ajaxPromise(updateUrl, updateMethod)
.then( response => {
if(response.affected) {
alert('파일 정보 최신으로 업데이트됨');
// out(detail_view) 산하 자식 노드 제거
while (out.hasChildNodes()) out.removeChild(out.firstChild);
// collapseSpan 클릭 후 처럼, detail_view에 source 내용 다시 보이기
const collapseSpan = li.querySelector('span.collapse');
detailSourceView(collapseSpan);
}
},
error => { alert(error); }
);
});
}
}
// 개별 section별 div 생성
for(let i=0; i<=quotient; i++) {
// section별 div 생성 및 스타일링
const secDiv = document.createElement('div');
// secDiv 식별자
secDiv.setAttribute('class','secDiv');
secDiv.dataset.dno = i;
// secDiv 스타일링
secDiv.style.margin = '5px';
secDiv.style.border = '1px solid gray';
// secDiv.style.padding = '0px 10px';
// (Default) 첫 section div 외 모두 보이지 않기 (hide)
if(i != 0) secDiv.style.display = 'none';
else secDiv.style.display = 'flex';
lowDiv.appendChild(secDiv);
// lowDiv에 소스 번호 div (left),내용 출력용 div (right) 생성 및 스타일링
const lNumDiv = document.createElement('div');
lNumDiv.style.width = '15x'; // '30px';
lNumDiv.style.borderRight = '1px red solid';
const rSrcDiv = document.createElement('div');
rSrcDiv.style.width = '85%'; // '650px'; //
rSrcDiv.style.overflowX = 'scroll';
[lNumDiv,rSrcDiv].map( elem => {
elem.style.display = 'block';
elem.style.float = 'left';
elem.style.boxSizing = 'border-box';
elem.style.whiteSpace = 'pre';
elem.style.padding = '15px 5px 15px 10px';
elem.style.lineHeight = '15px'; // 소스 코드이므로, 줄 간격 일정 필요
elem.style.fontFamily = 'Consolas,monospace'; // 소스 코드이므로, 이에 적합한 폰트 필요
});
secDiv.appendChild(lNumDiv);
secDiv.appendChild(rSrcDiv);
// 줄번호 및 소스 div에 출력
lNumDiv.textContent = splitedNumArr.slice(N*i,N*i+(i<quotient ? N : remainder)).join('\n');
rSrcDiv.textContent = splitedSrcArr.slice(N*i,N*i+(i<quotient ? N : remainder)).join('\n');
}
},
error => {
alert(error);
}
);
}
// 기능 : 섹션별 버튼 생성
// 호출 : [navi_base_moreShow.js] displayPerSection(), detailSourceView()
function createSectionbuttons(out, objData) {
// 외곽 div 생성
const buttonsDiv = document.createElement('div');
// button
const length = Object.keys(objData).length;
for(i=0; i<length; i++) {
const button = document.createElement('button');
// section button 식별자
button.setAttribute('class','secBtn');
button.dataset.bno = i;
// button에 스타일링
button.style.fontSize = '13px';
button.style.marginLeft = '3px';
// (Default) 초기 버튼 텍스트 강조
if(i == 0) button.style.fontWeight = 'bold';
// button 내 섹션별 제목 텍스트 삽입
let striped = stripHtml(objData[i]).trim();
let cutLen = striped.indexOf('\n')
cutLen = cutLen > 0 ? cutLen : 20;
striped = striped.substr(0, cutLen);
button.innerText = cutStr4Hangul(striped, 0, 20).trim() + ' ... ';
// button 리스너 처리기
button.addEventListener('click', (e) => {
out.querySelectorAll('button').forEach( elem => {
elem.style.fontWeight = 'normal';
});
e.target.style.fontWeight = 'bold';
let divs = out.closest('div.detail_view').querySelectorAll('div.secDiv');
for(i=0;i<divs.length;i++){
if(divs[i].dataset.dno == e.target.dataset.bno) {
divs[i].style.display = 'flex';
} else {
divs[i].style.display = 'none';
}
}
});
// out.appendChild(button);
buttonsDiv.appendChild(button);
}
return buttonsDiv;
}
// html 텍스트 내 html 태그들 만 제거
function stripHtml(html) {
const tmp = document.createElement("div");
tmp.innerHTML = html;
return tmp.innerText;
}