테스트 사이트 - 개발 중인 베타 버전입니다

html/css/js로 디자인된 웹 그누보드에 이식하는 방법 문의드립니다 채택완료

mapelamll 4년 전 조회 2,287

아직 그누보드 전체적인 구조를 잘 모르는점 양해부탁드립니다

 

그누보드 설정에서 테마안씀으로 하면 root 의 index.php에 먼저 진입하고

 

common에서 내부적인거 처리 --> 테마있는지 확인 --> 모바일인지확인 --> 헤더include -->메인내용 -->tail include 이런 흐름에서 

 

root/css/default.css 가 적용된다 // 까지가 제가 이해한 부분입니다

 

제목에 있는 html/css/js는 헤더 소스이구요 부트스트랩cdn 외부 plugin 외부 font를 모두 사용합니다

 

부트스트랩을 cdn으로 받아오니까 이유는 모르겠지만 404에러가나서 직접 다운받아서

 

head.sub.php파일의 !doctype선언 바로아랫부분에 Link태그로 직접다운받은 부트스트랩css를 넣었습니다

 

여기서 bootstrap과/root의 default.css가 같은 클래스네임 .container를 사용하고있는데요 default.css가 이기려면, 즉 부트스트랩보다 아래쪽에 위치시키려면 어떻게해야하나요?

doctype바로 아래에 부트스트랩을 넣어서 default.css가 부트스트랩을 이길줄알았는데 default.css가 어느 타이밍에 적용되는지 찾질못하겠습니다

 

그리고 전체적인 개발과정은 정석적인 방법인가요? fullPage기능도 그누보드로 이식시켜야하는데 무리는 없나요?

댓글을 작성하려면 로그인이 필요합니다.

답변 2개

채택된 답변
+20 포인트

스타일 시트의 경우 말씀하신바와 html 태그 내에 직접 넣을수도 있지만

저는 그누보드의 흐름을 최대한 따라가기 위해

add_javascript(), add_stylesheet() 함수를 이용합니다.

 

head.sub.php를 보시면 

add_javascript('', 0);

add_stylesheet('', 0);

와 같은 형태로 폴더의 js파일과 css를 불러오시는 것을 확인하실 수 있습니다.

 

 

default.css는 head.sub.php 에서 태그 밑에</p> <p> </p> <p><?php echo run_replace('head_css_url', G5_THEME_CSS_URL.'/'.(G5_IS_MOBILE ? 'mobile' : <link rel="stylesheet" href="<?php echo run_replace('head_css_url', G5_THEME_CSS_URL.'/'.(G5_IS_MOBILE ? 'mobile' : 'default').'.css?ver='.G5_CSS_VER, G5_THEME_URL); ?>"></p> <p> </p> <p>와 같이 선언되어있습니다.</p> <p> </p> <p>이 코드를 옮기기 보다는 default.css는 공백 파일로 놔두고 그 부근에</p> <p>add_stylesheet('<link rel="stylesheet" href="'.G5_THEME_CSS_URL.'/default_gnu5.css">', 0);</p> <p>의 형태로 넣어놓는게 낫지 않을까 싶습니다.</p> <p> </p> </div> <!-- 좋아요/싫어요 및 액션 버튼 --> <div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700"> <div class="flex items-center space-x-4"> <div class="text-sm text-gray-500 dark:text-gray-400"> 로그인 후 평가할 수 있습니다 </div> </div> </div> </div> <!-- 댓글 섹션 (답변 카드 안에 포함) --> <div class="mx-6 mb-6"> <div class="relative"> <!-- 연결선 표시 --> <div class="absolute -top-3 left-8 w-px h-6 bg-gradient-to-b from-gray-300 to-transparent dark:from-gray-600"></div> <div class="px-6 py-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700"> <!-- 댓글 목록 --> <!-- 디버깅: 답변 386789의 전체 댓글 구조 --> <!-- 댓글 수: 1, 대댓글 그룹 수: 0 --> <div class="mb-4"> <div class="flex items-center gap-2 mb-4"> <svg class="w-4 h-4 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-4 4z"></path> </svg> <h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300">답변에 대한 댓글 1개</h4> </div> <div class="space-y-3"> <div id="c_386833" class="relative "> <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-200"> <div class="p-4"> <div class="flex items-start justify-between mb-3"> <div class="flex items-center gap-3"> <div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white text-sm font-medium"> m </div> <div> <div class="font-medium text-sm text-gray-900 dark:text-white"> mapelamll </div> <div class="text-xs text-gray-500 dark:text-gray-400" title="2020-11-25 (수) 17:33:40"> 4년 전 </div> </div> </div> </div> <div class="text-sm text-gray-700 dark:text-gray-300 comment-content leading-relaxed"> 감사합니다 default추가하는거 확인했습니다 코드로 조합되어있어서 default.css로 찾으면 안나왔던거였네요.. </div> <!-- 댓글 액션 버튼 --> <div class="flex items-center gap-2 mt-3 pt-2 border-t border-gray-100 dark:border-gray-700"> </div> <!-- 댓글 수정 폼 (숨김) --> <div class="comment-edit-form hidden mt-3"> <textarea class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm resize-none" rows="3" placeholder="댓글을 수정하세요...">감사합니다 default추가하는거 확인했습니다 코드로 조합되어있어서 default.css로 찾으면 안나왔던거였네요..</textarea> <div class="flex justify-end gap-2 mt-2"> <button type="button" class="comment-save-btn px-3 py-1.5 bg-qa-btn-primary-bg-light hover:bg-qa-btn-primary-hover-light dark:bg-qa-btn-primary-bg-dark dark:hover:bg-qa-btn-primary-hover-dark text-white rounded-lg text-sm font-medium transition-colors" data-comment-id="386833">저장</button> <button type="button" class="comment-cancel-btn px-3 py-1.5 bg-gray-500 hover:bg-gray-600 text-white rounded-lg text-sm font-medium transition-colors">취소</button> </div> </div> <!-- 대댓글 작성 폼 (숨김) --> <div class="reply-form hidden mt-3 bg-gray-50 dark:bg-gray-700 rounded-lg p-3"> <div class="flex items-center gap-2 mb-2"> <svg class="w-3 h-3 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path> </svg> <span class="text-xs text-gray-600 dark:text-gray-300 reply-target"></span> </div> <form class="reply-form-element" data-parent-id="386789" data-comment-id="386833"> <input type="hidden" name="_token" value="OIGDuOL0oNQr2pof3LFlMcNV1bNUQtfVlIR01TCU" autocomplete="off"> <textarea name="wr_content" placeholder="답글을 작성하세요..." rows="2" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-600 dark:text-white text-sm resize-none" required></textarea> <div class="flex justify-end gap-2 mt-2"> <button type="button" class="reply-cancel-btn px-3 py-1.5 bg-gray-500 hover:bg-gray-600 text-white rounded text-sm font-medium transition-colors">취소</button> <button type="submit" class="px-3 py-1.5 bg-qa-btn-primary-bg-light hover:bg-qa-btn-primary-hover-light dark:bg-qa-btn-primary-bg-dark dark:hover:bg-qa-btn-primary-hover-dark text-white rounded text-sm font-medium transition-colors">답글 등록</button> </div> </form> </div> </div> </div> </div> <!-- 대댓글 표시 --> <!-- 디버깅: 댓글 1, 답변ID 386789, 대댓글 있음: 아니오 --> </div> </div> <!-- 댓글 작성 폼 --> <div class="text-center py-3"> <p class="text-sm text-gray-500 dark:text-gray-400"> 댓글을 작성하려면 <a href="/login" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">로그인</a>이 필요합니다. </p> </div> </div> </div> </div> </div> <div id="answer_386786" class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-all duration-300 overflow-hidden border border-gray-200 dark:border-gray-700"> <div class="p-6"> <!-- 답변 작성자 정보 --> <div class="flex items-center justify-between mb-5"> <div class="flex items-center space-x-4"> <div class="relative inline-block" style="width: 48px; height: 48px;"> <!-- SVG 아바타 사용 (기본값) --> <svg width="48" height="48" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" class="rounded-full"> <!-- 배경 원 (그라데이션) --> <defs> <linearGradient id="grad-avatar-69172408f08660.79654241" x1="0%" y1="0%" x2="100%" y2="100%"> <stop offset="0%" style="stop-color:hsl(204, 84%, 53%);stop-opacity:1" /> <stop offset="100%" style="stop-color:hsl(234, 84%, 48%);stop-opacity:1" /> </linearGradient> </defs> <circle cx="50" cy="50" r="50" fill="url(#grad-avatar-69172408f08660.79654241)" /> <!-- 닉네임 첫 글자 --> <text x="50" y="50" font-size="45" fill="white" text-anchor="middle" dy=".35em" font-weight="bold" font-family="sans-serif"> 볼 </text> </svg> </div> <div> <div class="flex items-center space-x-2"> <a href="https://new-sir.gnuboard.net/profile/6723564b-ad76-4222-a9dc-add8782e25f8" class="font-semibold text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-colors"> 볼피드 </a> </div> <div class="text-sm text-gray-500 dark:text-gray-400">4년 전</div> </div> </div> <!-- 답변 액션 버튼들 --> <div class="flex items-center space-x-2"> </div> </div> <!-- 답변 내용 --> <div class="prose prose-lg max-w-none dark:prose-invert text-gray-800 dark:text-gray-200 leading-relaxed mb-6 qa-content"> <p>개발은 개발자의 스타일대로 편한대로 이뤄지기 때문에 정석이라고 할만한 방법이 없습니다 특히 제작사쪽에서 가이드라인이나 권장하는 방법이 없다면 더더욱 그렇습니다</p> <p>저같은 경우 부트스트랩, 테일윈드 등 외부 프레임워크를 사용하는 경우 브라우저간의 레이아웃 틀어짐으로 인한 태그값 초기화를 위한 초기화 부분을 제외하곤 다 제거 하고 작업합니다</p> <p>css간 충돌은 본인이 제작했거나 이미 많이 다뤄봐서 익숙한 부분이 아니라면 해당 부분 찾아가고 분석하는데 시간이 오래 걸리기 때문입니다.</p> </div> <!-- 좋아요/싫어요 및 액션 버튼 --> <div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700"> <div class="flex items-center space-x-4"> <div class="text-sm text-gray-500 dark:text-gray-400"> 로그인 후 평가할 수 있습니다 </div> </div> </div> </div> <!-- 댓글 섹션 (답변 카드 안에 포함) --> <div class="mx-6 mb-6"> <div class="relative"> <!-- 연결선 표시 --> <div class="absolute -top-3 left-8 w-px h-6 bg-gradient-to-b from-gray-300 to-transparent dark:from-gray-600"></div> <div class="px-6 py-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700"> <!-- 댓글 목록 --> <!-- 디버깅: 답변 386786의 전체 댓글 구조 --> <!-- 댓글 수: 1, 대댓글 그룹 수: 0 --> <div class="mb-4"> <div class="flex items-center gap-2 mb-4"> <svg class="w-4 h-4 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-4 4z"></path> </svg> <h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300">답변에 대한 댓글 1개</h4> </div> <div class="space-y-3"> <div id="c_386834" class="relative "> <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-200"> <div class="p-4"> <div class="flex items-start justify-between mb-3"> <div class="flex items-center gap-3"> <div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white text-sm font-medium"> m </div> <div> <div class="font-medium text-sm text-gray-900 dark:text-white"> mapelamll </div> <div class="text-xs text-gray-500 dark:text-gray-400" title="2020-11-25 (수) 17:33:55"> 4년 전 </div> </div> </div> </div> <div class="text-sm text-gray-700 dark:text-gray-300 comment-content leading-relaxed"> 감사합니다 </div> <!-- 댓글 액션 버튼 --> <div class="flex items-center gap-2 mt-3 pt-2 border-t border-gray-100 dark:border-gray-700"> </div> <!-- 댓글 수정 폼 (숨김) --> <div class="comment-edit-form hidden mt-3"> <textarea class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm resize-none" rows="3" placeholder="댓글을 수정하세요...">감사합니다</textarea> <div class="flex justify-end gap-2 mt-2"> <button type="button" class="comment-save-btn px-3 py-1.5 bg-qa-btn-primary-bg-light hover:bg-qa-btn-primary-hover-light dark:bg-qa-btn-primary-bg-dark dark:hover:bg-qa-btn-primary-hover-dark text-white rounded-lg text-sm font-medium transition-colors" data-comment-id="386834">저장</button> <button type="button" class="comment-cancel-btn px-3 py-1.5 bg-gray-500 hover:bg-gray-600 text-white rounded-lg text-sm font-medium transition-colors">취소</button> </div> </div> <!-- 대댓글 작성 폼 (숨김) --> <div class="reply-form hidden mt-3 bg-gray-50 dark:bg-gray-700 rounded-lg p-3"> <div class="flex items-center gap-2 mb-2"> <svg class="w-3 h-3 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path> </svg> <span class="text-xs text-gray-600 dark:text-gray-300 reply-target"></span> </div> <form class="reply-form-element" data-parent-id="386786" data-comment-id="386834"> <input type="hidden" name="_token" value="OIGDuOL0oNQr2pof3LFlMcNV1bNUQtfVlIR01TCU" autocomplete="off"> <textarea name="wr_content" placeholder="답글을 작성하세요..." rows="2" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-600 dark:text-white text-sm resize-none" required></textarea> <div class="flex justify-end gap-2 mt-2"> <button type="button" class="reply-cancel-btn px-3 py-1.5 bg-gray-500 hover:bg-gray-600 text-white rounded text-sm font-medium transition-colors">취소</button> <button type="submit" class="px-3 py-1.5 bg-qa-btn-primary-bg-light hover:bg-qa-btn-primary-hover-light dark:bg-qa-btn-primary-bg-dark dark:hover:bg-qa-btn-primary-hover-dark text-white rounded text-sm font-medium transition-colors">답글 등록</button> </div> </form> </div> </div> </div> </div> <!-- 대댓글 표시 --> <!-- 디버깅: 댓글 1, 답변ID 386786, 대댓글 있음: 아니오 --> </div> </div> <!-- 댓글 작성 폼 --> <div class="text-center py-3"> <p class="text-sm text-gray-500 dark:text-gray-400"> 댓글을 작성하려면 <a href="/login" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">로그인</a>이 필요합니다. </p> </div> </div> </div> </div> </div> </div> <!-- 답변 페이징 --> </div> <!-- Error Messages --> <!-- Answer Form --> <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 text-center"> <p class="text-gray-600 dark:text-gray-400 mb-4">답변을 작성하려면 로그인이 필요합니다.</p> <a href="https://new-sir.gnuboard.net/login?redirect=https%3A%2F%2Fnew-sir.gnuboard.net%2Fquestions%2F386784" class="inline-block px-6 py-2 bg-qa-btn-primary-bg-light dark:bg-qa-btn-primary-bg-dark hover:bg-qa-btn-primary-hover-light dark:hover:bg-qa-btn-primary-hover-dark text-qa-btn-primary-text-light dark:text-qa-btn-primary-text-dark rounded-lg transition-colors"> 로그인 </a> </div> </div> <!-- Sidebar Component --> <!-- Sidebar --> <aside class="hidden lg:block w-80 flex-shrink-0 self-start"> <!-- 지금 질문하기 버튼 --> <div class="mb-4"> <a href="https://new-sir.gnuboard.net/questions/create" class="flex items-center justify-center gap-2 w-full px-4 py-3 bg-gradient-to-r from-qa-gradient-from-light to-qa-gradient-to-light dark:from-qa-gradient-from-dark dark:to-qa-primary-dark hover:from-qa-accent-light hover:to-qa-primary-light dark:hover:from-qa-primary-dark dark:hover:to-qa-gradient-to-dark text-qa-btn-primary-text-light dark:text-qa-btn-primary-text-dark font-medium rounded-lg shadow-md transition-all duration-200"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path> </svg> 지금 질문하기 </a> </div> <!-- 질문하는방법 --> <div class="bg-qa-card-bg-light dark:bg-qa-card-bg-dark rounded-lg shadow-sm p-6 mb-4"> <h3 class="text-lg font-bold text-qa-heading-light dark:text-qa-heading-dark mb-4"> <a href="https://new-sir.gnuboard.net/questions/howto" class="hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark transition-colors"> 질문하는방법 </a> </h3> <div class="space-y-3 text-sm"> <div class="flex justify-between items-center"> <a href="https://new-sir.gnuboard.net/questions" class="text-qa-link-light dark:text-qa-link-dark hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark">모든질문</a> <span class="font-semibold text-qa-heading-light dark:text-qa-heading-dark">129,285</span> </div> <div class="flex justify-between items-center"> <a href="https://new-sir.gnuboard.net/questions?unanswered=1" class="text-qa-link-light dark:text-qa-link-dark hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark">답변없는질문</a> <span class="font-semibold text-qa-heading-light dark:text-qa-heading-dark">8</span> </div> <div class="flex justify-between items-center"> <a href="https://new-sir.gnuboard.net/questions?unadopted=1" class="text-qa-link-light dark:text-qa-link-dark hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark">미채택된질문</a> <span class="font-semibold text-qa-heading-light dark:text-qa-heading-dark">18,376</span> </div> <div class="flex justify-between items-center"> <a href="https://new-sir.gnuboard.net/questions?adopted=1" class="text-qa-link-light dark:text-qa-link-dark hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark">채택된질문</a> <span class="font-semibold text-qa-heading-light dark:text-qa-heading-dark">101,681</span> </div> </div> <h4 class="text-sm font-semibold text-qa-heading-light dark:text-qa-heading-dark mt-6 mb-3">모든답변</h4> <div class="space-y-3 text-sm"> <div class="flex items-center gap-2"> <span class="inline-flex items-center justify-center w-6 h-6 bg-qa-icon-badge-bg-light dark:bg-qa-icon-badge-bg-dark text-xs font-medium text-qa-icon-badge-text-light dark:text-qa-icon-badge-text-dark rounded">5</span> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C5" class="text-qa-link-light dark:text-qa-link-dark hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark">그누보드5</a> <span class="ml-auto font-semibold text-qa-heading-light dark:text-qa-heading-dark">62,905</span> </div> <div class="flex justify-between items-center pl-8"> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%98%81%EC%B9%B4%ED%8A%B85" class="text-qa-link-light dark:text-qa-link-dark hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark">영카트5</a> <span class="font-semibold text-qa-heading-light dark:text-qa-heading-dark">17,535</span> </div> </div> <h4 class="text-sm font-semibold text-qa-heading-light dark:text-qa-heading-dark mt-6 mb-3"> <a href="https://new-sir.gnuboard.net/questions/tags" class="hover:text-qa-link-hover-light dark:hover:text-qa-link-hover-dark transition-colors"> 태그전체보기 </a> </h4> <div class="space-y-3 text-sm"> </div> </div> <!-- 관심태그 --> <div class="bg-qa-card-bg-light dark:bg-qa-card-bg-dark rounded-lg shadow-sm p-6 mb-4"> <h3 class="text-lg font-bold text-qa-heading-light dark:text-qa-heading-dark mb-4">관심태그</h3> <div class="flex flex-wrap gap-2"> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%98%81%EC%B9%B4%ED%8A%B85" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #영카트5 </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C4" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누보드4 </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%98%81%EC%B9%B4%ED%8A%B84" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #영카트4 </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%A0%95%EA%B7%9C%EC%8B%9D" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #정규식 </a> <a href="https://new-sir.gnuboard.net/questions?tag=%ED%85%8C%EB%A7%88" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #테마 </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C5" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누보드5 </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EB%84%A4%EC%9D%B4%EB%B2%84%ED%8E%98%EC%9D%B4" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #네이버페이 </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EC%BB%A4%EB%A8%B8%EC%8A%A4" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누커머스 </a> <a href="https://new-sir.gnuboard.net/questions?tag=php" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #php </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C6" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs font-medium rounded-full transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누보드6 </a> </div> </div> <!-- 인기태그 --> <div class="bg-qa-card-bg-light dark:bg-qa-card-bg-dark rounded-lg shadow-sm p-6"> <h3 class="text-lg font-bold text-qa-heading-light dark:text-qa-heading-dark mb-4">인기태그</h3> <div class="flex flex-wrap gap-2"> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C5" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누보드5 <span class="text-[10px] opacity-75">(62,905)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%98%81%EC%B9%B4%ED%8A%B85" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #영카트5 <span class="text-[10px] opacity-75">(17,535)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=php" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #php <span class="text-[10px] opacity-75">(15,830)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C4" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누보드4 <span class="text-[10px] opacity-75">(10,649)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B2%8C%EC%8B%9C%ED%8C%90" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #게시판 <span class="text-[10px] opacity-75">(3,273)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=javascript" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #javascript <span class="text-[10px] opacity-75">(2,678)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누보드 <span class="text-[10px] opacity-75">(2,463)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=css" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #css <span class="text-[10px] opacity-75">(2,129)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=mysql" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #mysql <span class="text-[10px] opacity-75">(1,556)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%98%81%EC%B9%B4%ED%8A%B8" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #영카트 <span class="text-[10px] opacity-75">(1,295)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EB%AA%A8%EB%B0%94%EC%9D%BC" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #모바일 <span class="text-[10px] opacity-75">(1,293)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EA%B7%B8%EB%88%84%EC%BB%A4%EB%A8%B8%EC%8A%A4" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-interest-bg-light dark:bg-qa-tag-interest-bg-dark text-qa-tag-interest-text-light dark:text-qa-tag-interest-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #그누커머스 <span class="text-[10px] opacity-75">(1,178)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=html" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #html <span class="text-[10px] opacity-75">(1,133)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=jquery" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #jquery <span class="text-[10px] opacity-75">(1,027)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%B5%9C%EC%8B%A0%EA%B8%80" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #최신글 <span class="text-[10px] opacity-75">(970)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #자바스크립트 <span class="text-[10px] opacity-75">(953)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=DB" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #DB <span class="text-[10px] opacity-75">(850)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EB%A1%9C%EA%B7%B8%EC%9D%B8" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #로그인 <span class="text-[10px] opacity-75">(821)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%9D%B4%EB%AF%B8%EC%A7%80" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #이미지 <span class="text-[10px] opacity-75">(785)</span> </a> <a href="https://new-sir.gnuboard.net/questions?tag=%EC%97%AC%EB%B6%84%ED%95%84%EB%93%9C" class="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors bg-qa-tag-bg-light dark:bg-qa-tag-bg-dark text-qa-tag-text-light dark:text-qa-tag-text-dark hover:bg-qa-tag-hover-light dark:hover:bg-qa-tag-hover-dark " title="태그 추가"> #여분필드 <span class="text-[10px] opacity-75">(784)</span> </a> </div> </div> </aside> </div> </div> <!-- Share Modal --> <div id="share-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden"> <div class="flex items-center justify-center min-h-screen p-4"> <div class="bg-white dark:bg-gray-800 rounded-lg max-w-md w-full p-6"> <div class="flex items-center justify-between mb-4"> <h3 class="text-lg font-medium text-gray-900 dark:text-white">공유하기</h3> <button onclick="closeShareModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> </div> <div class="flex items-center gap-4"> <a href="#" id="facebook-share" target="_blank" class="flex items-center justify-center w-12 h-12 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"> <i class="fa fa-facebook"></i> </a> <a href="#" id="twitter-share" target="_blank" class="flex items-center justify-center w-12 h-12 bg-blue-400 text-white rounded-lg hover:bg-blue-500 transition-colors"> <i class="fa fa-twitter"></i> </a> <button onclick="copyToClipboard()" class="flex items-center justify-center w-12 h-12 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"> <i class="fa fa-link"></i> </button> </div> </div> </div> </div> <!-- 신고 모달 다이얼로그 --> <div id="sir_react_singod" style="display: none;"> <p>이 게시물을 신고 하시겠습니까?<br>신고사유를 선택해주세요.</p> <ul class="space-y-2"> <li> <input type="radio" name="rdo_reason" id="rdo_reason_ad" value="광고"> <label for="rdo_reason_ad" class="ml-2">광고성 게시물</label> </li> <li> <input type="radio" name="rdo_reason" id="rdo_reason_ob" value="음란"> <label for="rdo_reason_ob" class="ml-2">음란성 게시물</label> </li> <li> <input type="radio" name="rdo_reason" id="rdo_reason_ha" value="혐오"> <label for="rdo_reason_ha" class="ml-2">상대방 비방 및 혐오</label> </li> <li> <input type="radio" name="rdo_reason" id="rdo_reason_etc" value="기타"> <label for="rdo_reason_etc" class="ml-2">기타</label> <div class="mt-2"> <label for="sg_reason" class="block text-sm text-gray-600 dark:text-gray-400">기타 신고사유</label> <input type="text" name="sg_reason" id="sg_reason" class="mt-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="10글자 이상 입력해주세요"> </div> </li> </ul> </div> <script> document.addEventListener('DOMContentLoaded', function() { // 좋아요 기능 const voteGoodBtn = document.getElementById('vote-good-btn'); if (voteGoodBtn) { voteGoodBtn.addEventListener('click', function() { const wrId = this.dataset.wrId; const countSpan = this.querySelector('.vote-good-count'); const textSpan = this.querySelector('.vote-good-text'); const isVoted = textSpan.textContent === '좋아요됨'; if (this.disabled) return; this.disabled = true; this.classList.add('opacity-50'); fetch(`/questions/${wrId}/vote`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ type: 'good' }) }) .then(response => response.json()) .then(data => { if (data.success) { countSpan.textContent = data.good_count; if (data.user_vote === null) { // 취소된 경우 textSpan.textContent = '좋아요'; this.classList.remove('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-300'); this.classList.add('bg-green-100', 'dark:bg-green-900', 'text-green-700', 'dark:text-green-300', 'hover:bg-green-200', 'dark:hover:bg-green-800'); } else { // 투표된 경우 textSpan.textContent = '좋아요됨'; this.classList.remove('bg-green-100', 'dark:bg-green-900', 'text-green-700', 'dark:text-green-300', 'hover:bg-green-200', 'dark:hover:bg-green-800'); this.classList.add('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-300'); } console.log('Success message:', data.message); showMessage(data.message, 'success'); } else { console.log('Error message:', data.error); showMessage(data.error || '좋아요에 실패했습니다.', 'error'); } this.disabled = false; this.classList.remove('opacity-50'); }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); this.disabled = false; this.classList.remove('opacity-50'); }); }); } // 싫어요 기능 const voteNogoodBtn = document.getElementById('vote-nogood-btn'); if (voteNogoodBtn) { voteNogoodBtn.addEventListener('click', function() { const wrId = this.dataset.wrId; const countSpan = this.querySelector('.vote-nogood-count'); const textSpan = this.querySelector('.vote-nogood-text'); const isVoted = textSpan.textContent === '싫어요됨'; if (this.disabled) return; this.disabled = true; this.classList.add('opacity-50'); fetch(`/questions/${wrId}/vote`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ type: 'nogood' }) }) .then(response => response.json()) .then(data => { if (data.success) { countSpan.textContent = data.nogood_count; if (data.user_vote === null) { // 취소된 경우 textSpan.textContent = '싫어요'; this.classList.remove('bg-orange-100', 'dark:bg-orange-900', 'text-orange-700', 'dark:text-orange-300'); this.classList.add('bg-red-100', 'dark:bg-red-900', 'text-red-700', 'dark:text-red-300', 'hover:bg-red-200', 'dark:hover:bg-red-800'); } else { // 투표된 경우 textSpan.textContent = '싫어요됨'; this.classList.remove('bg-red-100', 'dark:bg-red-900', 'text-red-700', 'dark:text-red-300', 'hover:bg-red-200', 'dark:hover:bg-red-800'); this.classList.add('bg-orange-100', 'dark:bg-orange-900', 'text-orange-700', 'dark:text-orange-300'); } console.log('Success message:', data.message); showMessage(data.message, 'success'); } else { console.log('Error message:', data.error); showMessage(data.error || '싫어요에 실패했습니다.', 'error'); } this.disabled = false; this.classList.remove('opacity-50'); }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); this.disabled = false; this.classList.remove('opacity-50'); }); }); } // 스크랩 기능 const scrapBtn = document.getElementById('scrap-btn'); if (scrapBtn) { scrapBtn.addEventListener('click', function() { const wrId = this.dataset.wrId; const countSpan = this.querySelector('.scrap-count'); this.disabled = true; this.classList.add('opacity-50'); fetch(`/questions/${wrId}/scrap`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.success) { countSpan.textContent = `(${data.count})`; const textSpan = this.querySelector('.scrap-text'); const iconSpan = this.querySelector('i'); // 스크랩된 상태로 변경 textSpan.textContent = '스크랩됨'; iconSpan.className = 'fa fa-bookmark mr-1'; this.classList.remove('bg-gray-100', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300', 'hover:bg-gray-200', 'dark:hover:bg-gray-600'); this.classList.add('bg-yellow-100', 'dark:bg-yellow-900', 'text-yellow-700', 'dark:text-yellow-300'); // 스크랩 후 버튼 비활성화 this.disabled = true; showMessage(data.message, 'success'); } else { showMessage(data.error || '스크랩에 실패했습니다.', 'error'); } this.disabled = false; this.classList.remove('opacity-50'); }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); this.disabled = false; this.classList.remove('opacity-50'); }); }); } // 신고 기능 document.querySelectorAll('button[id^=qa_singo-]').forEach(button => { button.addEventListener('click', function() { const idParts = this.id.split('-'); const bo_table = idParts[1]; const wr_id = idParts[2]; const radios_reason = ["#rdo_reason_ad", "#rdo_reason_ob", "#rdo_reason_ha", "#rdo_reason_etc"]; // 모달 다이얼로그 표시 const modal = document.getElementById('sir_react_singod'); modal.style.display = 'block'; modal.style.position = 'fixed'; modal.style.top = '50%'; modal.style.left = '50%'; modal.style.transform = 'translate(-50%, -50%)'; modal.style.backgroundColor = 'white'; modal.style.padding = '20px'; modal.style.borderRadius = '8px'; modal.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)'; modal.style.zIndex = '1000'; modal.style.minWidth = '400px'; // 배경 오버레이 const overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; overlay.style.zIndex = '999'; document.body.appendChild(overlay); // 신고하기 버튼 const reportBtn = document.createElement('button'); reportBtn.textContent = '신고하기'; reportBtn.className = 'px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 mr-2'; reportBtn.addEventListener('click', function() { let r_checked = false; let reason = ''; radios_reason.forEach(radioId => { if (document.querySelector(radioId).checked) { r_checked = true; if (radioId === '#rdo_reason_etc') { const customReason = document.getElementById('sg_reason').value.trim(); if (customReason.length < 10) { alert('기타 선택시 신고사유를 10글자 이상 입력하십시오.'); return; } reason = customReason; } else { reason = document.querySelector(radioId).value; } } }); if (!r_checked) { alert('신고사유를 선택하세요.'); return; } // 신고 요청 fetch(`/questions/${wr_id}/singo`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ bo_table: bo_table, reason: reason, q_mode: 'singo' }) }) .then(response => response.json()) .then(data => { if (data.error) { alert(data.error); return; } // 모달 닫기 modal.style.display = 'none'; document.body.removeChild(overlay); // 신고 카운트 업데이트 const countElement = document.querySelector(`#qa_singo-${bo_table}-${wr_id} .singo-count`); if (countElement) { countElement.textContent = data.count; } // 버튼 비활성화 button.disabled = true; button.classList.add('opacity-50'); alert(data.message); }) .catch(error => { console.error('Error:', error); alert('신고 처리 중 오류가 발생했습니다.'); }); }); // 취소 버튼 const cancelBtn = document.createElement('button'); cancelBtn.textContent = '취소'; cancelBtn.className = 'px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600'; cancelBtn.addEventListener('click', function() { modal.style.display = 'none'; document.body.removeChild(overlay); }); // 기존 버튼들 제거 const existingButtons = modal.querySelectorAll('button'); existingButtons.forEach(btn => btn.remove()); // 버튼들 추가 const buttonContainer = document.createElement('div'); buttonContainer.className = 'mt-4 text-right'; buttonContainer.appendChild(reportBtn); buttonContainer.appendChild(cancelBtn); modal.appendChild(buttonContainer); // 오버레이 클릭 시 닫기 overlay.addEventListener('click', function() { modal.style.display = 'none'; document.body.removeChild(overlay); }); }); }); // 공유 기능 const shareBtn = document.getElementById('share-btn'); if (shareBtn) { shareBtn.addEventListener('click', function() { const shareModal = document.getElementById('share-modal'); const currentUrl = window.location.href; const title = document.title; // SNS 공유 링크 설정 const facebookUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(currentUrl)}`; const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(currentUrl)}&text=${encodeURIComponent(title)}`; document.getElementById('facebook-share').href = facebookUrl; document.getElementById('twitter-share').href = twitterUrl; shareModal.classList.remove('hidden'); }); } // 메시지 표시 함수 }); // 공유 모달 닫기 function closeShareModal() { document.getElementById('share-modal').classList.add('hidden'); } // 클립보드에 복사 function copyToClipboard() { navigator.clipboard.writeText(window.location.href).then(function() { showMessage('링크가 클립보드에 복사되었습니다.', 'success'); closeShareModal(); }); } // Image preview function function previewImage(imageUrl, filename) { // Create modal overlay const overlay = document.createElement('div'); overlay.className = 'fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4'; overlay.onclick = function() { document.body.removeChild(overlay); }; // Create modal content const modal = document.createElement('div'); modal.className = 'bg-white dark:bg-gray-800 rounded-lg max-w-4xl max-h-full overflow-auto'; modal.onclick = function(e) { e.stopPropagation(); }; modal.innerHTML = ` <div class="p-4 border-b border-gray-200 dark:border-gray-700"> <div class="flex items-center justify-between"> <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">${filename}</h3> <button onclick="document.body.removeChild(this.closest('.fixed'))" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> </div> </div> <div class="p-4"> <img src="${imageUrl}" alt="${filename}" class="max-w-full h-auto rounded-lg shadow-lg"> </div> `; overlay.appendChild(modal); document.body.appendChild(overlay); } // 메시지 표시 함수 function showMessage(message, type = 'info') { console.log('showMessage called:', message, type); // 기존 메시지 제거 const existingMessage = document.querySelector('.message-toast'); if (existingMessage) { existingMessage.remove(); } // 새 메시지 생성 const messageDiv = document.createElement('div'); messageDiv.className = 'message-toast fixed top-4 right-4 z-50 px-6 py-3 rounded-lg shadow-lg transition-all duration-300'; if (type === 'success') { messageDiv.className += ' bg-green-500 text-white'; } else if (type === 'error') { messageDiv.className += ' bg-red-500 text-white'; } else { messageDiv.className += ' bg-blue-500 text-white'; } messageDiv.textContent = message; document.body.appendChild(messageDiv); console.log('Message displayed:', messageDiv); // 3초 후 자동 제거 setTimeout(() => { if (messageDiv.parentNode) { messageDiv.style.opacity = '0'; messageDiv.style.transform = 'translateX(100%)'; setTimeout(() => { if (messageDiv.parentNode) { messageDiv.parentNode.removeChild(messageDiv); } }, 300); } }, 3000); } // 답변 작성 폼 처리 document.addEventListener('DOMContentLoaded', function() { // 답변 작성 폼 const answerForm = document.getElementById('answer-form-element'); if (answerForm) { answerForm.addEventListener('submit', function(e) { e.preventDefault(); // 기본 폼 제출 방지 // 중복 제출 방지 const submitBtn = this.querySelector('button[type="submit"]'); if (submitBtn.disabled) { return; } // 버튼 비활성화 submitBtn.disabled = true; submitBtn.textContent = '등록 중...'; // CKEditor 내용 가져오기 const content = CKEDITOR.instances.wr_content ? CKEDITOR.instances.wr_content.getData() : ''; // FormData 생성 const formData = new FormData(); formData.append('_token', document.querySelector('input[name="_token"]').value); formData.append('wr_qa_id', document.querySelector('input[name="wr_qa_id"]').value); formData.append('wr_content', content); // AJAX 요청 fetch('https://new-sir.gnuboard.net/questions/answer', { method: 'POST', body: formData, headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } }) .then(response => response.json()) .then(data => { if (data.success) { // 성공 메시지 표시 showMessage('답변이 등록되었습니다.', 'success'); // 페이지 새로고침하여 새 답변 표시 window.location.reload(); } else { // 에러 메시지 표시 showMessage(data.message || '답변 등록 중 오류가 발생했습니다.', 'error'); // 버튼 복원 submitBtn.disabled = false; submitBtn.textContent = '답변 등록'; } }) .catch(error => { console.error('Error:', error); showMessage('답변 등록 중 오류가 발생했습니다.', 'error'); // 버튼 복원 submitBtn.disabled = false; submitBtn.textContent = '답변 등록'; }); }); } // URL 프래그먼트 처리 function handleFragment() { const hash = window.location.hash; if (hash === '#new-answer') { // URL에서 프래그먼트 제거 (히스토리 정리) history.replaceState(null, null, window.location.pathname + window.location.search); setTimeout(() => { // 답변 목록에서 가장 최근 답변 찾기 const answers = document.querySelectorAll('.answer-item'); if (answers.length > 0) { const lastAnswer = answers[answers.length - 1]; lastAnswer.scrollIntoView({ behavior: 'smooth', block: 'start' }); // 새 답변에 하이라이트 효과 추가 lastAnswer.style.backgroundColor = '#fef3c7'; lastAnswer.style.transition = 'background-color 0.3s ease'; setTimeout(() => { lastAnswer.style.backgroundColor = ''; }, 3000); } }, 500); } } // 페이지 로드 시 프래그먼트 처리 handleFragment(); // 해시 변경 이벤트 리스너 (브라우저 뒤로가기/앞으로가기 대응) window.addEventListener('hashchange', handleFragment); }); // 답변 좋아요/싫어요 기능 document.addEventListener('DOMContentLoaded', function() { // 답변 좋아요 버튼 document.querySelectorAll('.vote-good-btn').forEach(button => { if (button.id !== 'vote-good-btn') { // 질문 좋아요 버튼 제외 button.addEventListener('click', function() { const wrId = this.dataset.wrId; const countSpan = this.querySelector('.vote-good-count'); const textSpan = this.querySelector('.vote-good-text'); const isVoted = textSpan.textContent === '좋아요됨'; if (this.disabled) return; this.disabled = true; this.classList.add('opacity-50'); fetch(`/questions/${wrId}/vote`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ type: 'good' }) }) .then(response => response.json()) .then(data => { if (data.success) { countSpan.textContent = data.good_count; if (data.user_vote === null) { // 취소된 경우 textSpan.textContent = '좋아요'; this.classList.remove('bg-blue-100', 'dark:bg-blue-700', 'text-blue-700', 'dark:text-blue-300'); this.classList.add('bg-gray-100', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300', 'hover:bg-gray-200', 'dark:hover:bg-gray-600'); } else { // 투표된 경우 textSpan.textContent = '좋아요됨'; this.classList.remove('bg-gray-100', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300', 'hover:bg-gray-200', 'dark:hover:bg-gray-600'); this.classList.add('bg-blue-100', 'dark:bg-blue-700', 'text-blue-700', 'dark:text-blue-300'); } showMessage(data.message, 'success'); } else { showMessage(data.error || '좋아요에 실패했습니다.', 'error'); } this.disabled = false; this.classList.remove('opacity-50'); }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); this.disabled = false; this.classList.remove('opacity-50'); }); }); } }); // 답변 싫어요 버튼 document.querySelectorAll('.vote-nogood-btn').forEach(button => { if (button.id !== 'vote-nogood-btn') { // 질문 싫어요 버튼 제외 button.addEventListener('click', function() { const wrId = this.dataset.wrId; const countSpan = this.querySelector('.vote-nogood-count'); const textSpan = this.querySelector('.vote-nogood-text'); const isVoted = textSpan.textContent === '싫어요됨'; if (this.disabled) return; this.disabled = true; this.classList.add('opacity-50'); fetch(`/questions/${wrId}/vote`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ type: 'nogood' }) }) .then(response => response.json()) .then(data => { if (data.error === '') { countSpan.textContent = data.count; if (data.cancle_vote === 'cancle') { // 취소된 경우 textSpan.textContent = '싫어요'; this.classList.remove('bg-orange-100', 'dark:bg-orange-700', 'text-orange-700', 'dark:text-orange-300'); this.classList.add('bg-gray-100', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300', 'hover:bg-gray-200', 'dark:hover:bg-gray-600'); } else { // 투표된 경우 textSpan.textContent = '싫어요됨'; this.classList.remove('bg-gray-100', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300', 'hover:bg-gray-200', 'dark:hover:bg-gray-600'); this.classList.add('bg-orange-100', 'dark:bg-orange-700', 'text-orange-700', 'dark:text-orange-300'); } showMessage(data.message, 'success'); } else { showMessage(data.error || '싫어요에 실패했습니다.', 'error'); } this.disabled = false; this.classList.remove('opacity-50'); }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); this.disabled = false; this.classList.remove('opacity-50'); }); }); } }); // 채택 버튼 document.querySelectorAll('.adopt-btn').forEach(button => { button.addEventListener('click', function() { const questionId = this.dataset.questionId; const answerId = this.dataset.answerId; if (!confirm('이 답변을 채택하시겠습니까?\n\n채택된 답변은 변경할 수 없습니다.')) { return; } fetch(`/questions/${questionId}/adopt/${answerId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } }) .then(response => response.json()) .then(data => { if (data.success) { showMessage(data.message, 'success'); // 페이지 새로고침하여 채택 상태 반영 setTimeout(() => { location.reload(); }, 1500); } else { showMessage(data.error || '채택에 실패했습니다.', 'error'); } }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); }); }); }); // 채택 취소 버튼 document.querySelectorAll('.unadopt-btn').forEach(button => { button.addEventListener('click', function() { const questionId = this.dataset.questionId; if (!confirm('채택을 취소하시겠습니까?\n\n채택된 답변 작성자에게 지급된 포인트가 회수됩니다.')) { return; } fetch(`/questions/${questionId}/unadopt`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } }) .then(response => response.json()) .then(data => { if (data.success) { showMessage(data.message, 'success'); // 페이지 새로고침하여 채택 취소 상태 반영 setTimeout(() => { location.reload(); }, 1500); } else { showMessage(data.error || '채택 취소에 실패했습니다.', 'error'); } }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); }); }); }); // 댓글 작성 document.querySelectorAll('.comment-form-element').forEach(form => { form.addEventListener('submit', function(e) { e.preventDefault(); // 중복 제출 방지 const submitBtn = this.querySelector('button[type="submit"]'); if (submitBtn.disabled) { return; } const parentId = this.dataset.parentId; const content = this.querySelector('textarea[name="wr_content"]').value.trim(); if (!content) { showMessage('댓글 내용을 입력해주세요.', 'error'); return; } // 버튼 비활성화 submitBtn.disabled = true; submitBtn.textContent = '등록 중...'; const formData = new FormData(); formData.append('wr_qa_id', parentId); formData.append('wr_content', content); formData.append('_token', document.querySelector('meta[name="csrf-token"]').getAttribute('content')); fetch('/questions/comment', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { showMessage(data.message, 'success'); // 페이지 새로고침하여 댓글 반영 setTimeout(() => { location.reload(); }, 1000); } else { showMessage(data.error || '댓글 등록에 실패했습니다.', 'error'); // 실패 시 버튼 다시 활성화 submitBtn.disabled = false; submitBtn.textContent = '댓글 등록'; } }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); // 오류 시 버튼 다시 활성화 submitBtn.disabled = false; submitBtn.textContent = '댓글 등록'; }); }); }); // 댓글 수정 document.querySelectorAll('.edit-comment-btn').forEach(button => { button.addEventListener('click', function() { const commentContainer = this.closest('.relative'); const commentContent = commentContainer.querySelector('.comment-content'); const editForm = commentContainer.querySelector('.comment-edit-form'); const textarea = editForm.querySelector('textarea'); // 편집 모드로 전환 commentContent.classList.add('hidden'); editForm.classList.remove('hidden'); textarea.focus(); }); }); // 댓글 저장 document.querySelectorAll('.comment-save-btn').forEach(button => { button.addEventListener('click', function() { const commentId = this.dataset.commentId; const commentContainer = this.closest('.relative'); const textarea = commentContainer.querySelector('textarea'); const newContent = textarea.value.trim(); if (!newContent) { showMessage('댓글 내용을 입력해주세요.', 'error'); return; } fetch(`/questions/comment/${commentId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, body: JSON.stringify({ wr_content: newContent }) }) .then(response => response.json()) .then(data => { if (data.success) { showMessage(data.message, 'success'); // 페이지 새로고침하여 수정된 댓글 반영 setTimeout(() => { location.reload(); }, 1000); } else { showMessage(data.error || '댓글 수정에 실패했습니다.', 'error'); } }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); }); }); }); // 댓글 수정 취소 document.querySelectorAll('.comment-cancel-btn').forEach(button => { button.addEventListener('click', function() { const commentContainer = this.closest('.relative'); const commentContent = commentContainer.querySelector('.comment-content'); const editForm = commentContainer.querySelector('.comment-edit-form'); const textarea = editForm.querySelector('textarea'); // 원래 내용으로 복원 textarea.value = commentContent.textContent.trim(); // 편집 모드 해제 editForm.classList.add('hidden'); commentContent.classList.remove('hidden'); }); }); // 댓글 삭제 document.querySelectorAll('.delete-comment-btn').forEach(button => { button.addEventListener('click', function() { const commentId = this.dataset.commentId; if (!confirm('정말로 이 댓글을 삭제하시겠습니까?')) { return; } fetch(`/questions/comment/${commentId}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } }) .then(response => response.json()) .then(data => { if (data.success) { showMessage(data.message, 'success'); // 페이지 새로고침하여 삭제된 댓글 반영 setTimeout(() => { location.reload(); }, 1000); } else { showMessage(data.error || '댓글 삭제에 실패했습니다.', 'error'); } }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); }); }); }); // 대댓글 답글 버튼 클릭 document.querySelectorAll('.reply-comment-btn').forEach(button => { button.addEventListener('click', function() { const commentId = this.getAttribute('data-comment-id'); const commentAuthor = this.getAttribute('data-comment-author'); const replyForm = this.closest('.relative').querySelector('.reply-form'); const replyTarget = replyForm.querySelector('.reply-target'); // 다른 대댓글 폼들 숨기기 document.querySelectorAll('.reply-form').forEach(form => { if (form !== replyForm) { form.classList.add('hidden'); } }); // 현재 대댓글 폼 토글 replyForm.classList.toggle('hidden'); // 답글 대상 표시 replyTarget.textContent = `${commentAuthor}님에게 답글`; // 폼이 보이면 textarea에 포커스 if (!replyForm.classList.contains('hidden')) { const textarea = replyForm.querySelector('textarea'); textarea.focus(); } }); }); // 대댓글 취소 버튼 document.querySelectorAll('.reply-cancel-btn').forEach(button => { button.addEventListener('click', function() { const replyForm = this.closest('.reply-form'); const textarea = replyForm.querySelector('textarea'); // 내용 초기화 textarea.value = ''; // 폼 숨기기 replyForm.classList.add('hidden'); }); }); // 대댓글 등록 document.querySelectorAll('.reply-form-element').forEach(form => { form.addEventListener('submit', function(e) { e.preventDefault(); // 중복 제출 방지 const submitBtn = this.querySelector('button[type="submit"]'); if (submitBtn.disabled) { return; } const formData = new FormData(this); const parentId = this.getAttribute('data-parent-id'); const commentId = this.getAttribute('data-comment-id'); const textarea = this.querySelector('textarea[name="wr_content"]'); const content = textarea.value.trim(); if (!content) { showMessage('답글 내용을 입력해주세요.', 'error'); return; } // 버튼 비활성화 submitBtn.disabled = true; submitBtn.textContent = '등록 중...'; // 답글 대상 정보 추가 formData.append('wr_comment_reply', commentId); formData.append('wr_qa_id', parentId); formData.append('parent_id', parentId); fetch(`/questions/comment`, { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(data => { if (data.success) { showMessage('답글이 등록되었습니다.', 'success'); textarea.value = ''; this.closest('.reply-form').classList.add('hidden'); // 페이지 새로고침으로 새 답글 표시 setTimeout(() => { location.reload(); }, 1000); } else { showMessage(data.error || '답글 등록 중 오류가 발생했습니다.', 'error'); // 실패 시 버튼 다시 활성화 submitBtn.disabled = false; submitBtn.textContent = '답글 등록'; } }) .catch(error => { console.error('Error:', error); showMessage('네트워크 오류가 발생했습니다.', 'error'); // 오류 시 버튼 다시 활성화 submitBtn.disabled = false; submitBtn.textContent = '답글 등록'; }); }); }); }); // 프래그먼트 식별자 스크롤 처리 document.addEventListener('DOMContentLoaded', function() { // URL에 프래그먼트가 있는지 확인 if (window.location.hash) { const hash = window.location.hash.substring(1); // # 제거 const targetElement = document.getElementById(hash); if (targetElement) { // 약간의 지연 후 스크롤 (페이지 로딩 완료 후) setTimeout(() => { const headerHeight = 120; // 헤더 높이 + 여백 const elementPosition = targetElement.offsetTop; const offsetPosition = elementPosition - headerHeight; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); }, 100); } } }); // 답변 정렬 기능 document.addEventListener('DOMContentLoaded', function() { const sortSelect = document.getElementById('qa_sort_select'); if (sortSelect) { sortSelect.addEventListener('change', function() { const sortValue = this.value; const currentUrl = new URL(window.location); currentUrl.searchParams.set('vsst', sortValue); currentUrl.searchParams.set('vpage', '1'); // 정렬 변경 시 첫 페이지로 currentUrl.hash = '#qa_answer'; window.location.href = currentUrl.toString(); }); } }); // 답변 삭제 기능 document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.delete-answer-btn').forEach(button => { button.addEventListener('click', function() { const answerId = this.getAttribute('data-answer-id'); if (confirm('정말로 이 답변을 삭제하시겠습니까?')) { fetch(`/questions/${answerId}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } }) .then(response => response.json()) .then(data => { if (data.success) { // 답변 요소 제거 const answerElement = document.getElementById(`answer_${answerId}`); if (answerElement) { answerElement.remove(); } // 답변 수 업데이트 (전체 답변 수에서 1 감소) const answerCountElement = document.querySelector('h2'); if (answerCountElement) { const currentCount = parseInt(answerCountElement.textContent.match(/\d+/)[0]); answerCountElement.textContent = `답변 ${currentCount - 1}개`; } // 페이지 새로고침 (전체 답변 수와 페이징을 정확히 반영하기 위해) setTimeout(() => { window.location.reload(); }, 1000); showMessage('답변이 삭제되었습니다.', 'success'); } else { showMessage(data.message || '답변 삭제 중 오류가 발생했습니다.', 'error'); } }) .catch(error => { console.error('Error:', error); showMessage('답변 삭제 중 오류가 발생했습니다.', 'error'); }); } }); }); }); // 미채택 완료 function closeWithoutAdopt(questionId) { if (!confirm('답변을 채택하지 않고 질문을 완료하시겠습니까?\n\n이 작업은 취소할 수 없습니다.')) { return; } fetch(`/questions/${questionId}/close-without-adopt`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.success) { showMessage('질문이 미채택 완료되었습니다.', 'success'); setTimeout(() => { window.location.reload(); }, 1500); } else { showMessage(data.error || '처리에 실패했습니다.', 'error'); } }) .catch(error => { console.error('Error:', error); showMessage('처리 중 오류가 발생했습니다.', 'error'); }); } // 재등록 function reregisterQuestion(questionId) { if (!confirm('이 질문을 재등록하시겠습니까?\n\n재등록하면 새로운 답변을 받을 수 있습니다.')) { return; } fetch(`/questions/${questionId}/reregister`, { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json', 'Accept': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.success) { showMessage('질문이 재등록되었습니다.', 'success'); if (data.redirect) { setTimeout(() => { window.location.href = data.redirect; }, 1500); } else { setTimeout(() => { window.location.reload(); }, 1500); } } else { showMessage(data.error || '처리에 실패했습니다.', 'error'); } }) .catch(error => { console.error('Error:', error); showMessage('처리 중 오류가 발생했습니다.', 'error'); }); } // Prism.js용 코드 복사 기능 function copyCodePrism(button) { // 버튼의 부모 요소에서 코드 블록 찾기 const wrapper = button.closest('.code-block-wrapper'); const codeBlock = wrapper.querySelector('code'); const codeText = codeBlock.textContent; // 클립보드에 복사 navigator.clipboard.writeText(codeText).then(() => { // 버튼 텍스트 임시 변경 const originalHTML = button.innerHTML; const originalStyle = { background: button.style.background, borderColor: button.style.borderColor, color: button.style.color }; button.innerHTML = '<svg style="width: 14px; height: 14px; display: inline-block; vertical-align: middle; margin-right: 4px;" fill="currentColor" viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></svg> Copied!'; button.style.background = 'rgba(72, 187, 120, 0.7)'; button.style.borderColor = 'rgba(72, 187, 120, 0.9)'; button.style.color = '#fff'; // 2초 후 원래 상태로 복구 setTimeout(() => { button.innerHTML = originalHTML; button.style.background = originalStyle.background; button.style.borderColor = originalStyle.borderColor; button.style.color = originalStyle.color; }, 2000); }).catch(err => { console.error('Failed to copy code:', err); // Fallback: 전통적인 복사 방법 사용 const textArea = document.createElement('textarea'); textArea.value = codeText; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); // 성공 표시 const originalHTML = button.innerHTML; button.innerHTML = '<span style="margin-right: 4px;">✓</span> Copied!'; setTimeout(() => { button.innerHTML = originalHTML; }, 2000); } catch (err) { alert('코드 복사에 실패했습니다.'); } document.body.removeChild(textArea); }); } </script> </main> <!-- Footer --> <footer class="bg-brand-bg-light dark:bg-brand-bg-dark"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> <div class="flex flex-col sm:flex-row justify-between items-center gap-4"> <!-- Footer Text (Left/Center) --> <div class="text-center sm:text-left text-sm text-brand-neutral-light dark:text-brand-neutral-dark"> <p class="hidden md:block">(주)에스아이알소프트 | 사업자등록번호: 217-81-36347 | 대표: 홍석명</p> <p class="hidden md:block mt-1">서울특별시 강남구 테헤란로 322 한신인터밸리24 서관 1402호</p> <p class="mt-3 font-semibold">© 2025 SIRSOFT. All rights reserved.</p> </div> </div> </div> </footer> <!-- Floating Action Buttons --> <script> function toggleDarkMode() { const html = document.documentElement; const isDark = html.classList.contains('dark'); if (isDark) { html.classList.remove('dark'); localStorage.setItem('darkMode', 'false'); } else { html.classList.add('dark'); localStorage.setItem('darkMode', 'true'); } } function toggleProfileMenu() { const dropdown = document.getElementById('profileDropdown'); dropdown.classList.toggle('hidden'); } // Close profile dropdown when clicking outside document.addEventListener('click', function(event) { const dropdown = document.getElementById('profileDropdown'); const button = event.target.closest('button[onclick="toggleProfileMenu()"]'); if (!button && !dropdown?.contains(event.target)) { dropdown?.classList.add('hidden'); } }); function toggleMobileMenu() { const mobileMenu = document.getElementById('mobileMenu'); const hamburgerIcon = document.getElementById('hamburgerIcon'); const closeIcon = document.getElementById('closeIcon'); // 요소가 존재하는지 확인 if (!mobileMenu || !hamburgerIcon || !closeIcon) { console.error('Mobile menu elements not found:', { mobileMenu: !!mobileMenu, hamburgerIcon: !!hamburgerIcon, closeIcon: !!closeIcon }); return; } if (mobileMenu.classList.contains('hidden')) { // Open menu mobileMenu.classList.remove('hidden'); mobileMenu.style.opacity = '0'; mobileMenu.style.transform = 'translateY(-10px)'; setTimeout(() => { mobileMenu.style.transition = 'opacity 200ms ease-out, transform 200ms ease-out'; mobileMenu.style.opacity = '1'; mobileMenu.style.transform = 'translateY(0)'; }, 10); // Switch icons hamburgerIcon.classList.add('hidden'); closeIcon.classList.remove('hidden'); } else { // Close menu mobileMenu.style.opacity = '0'; mobileMenu.style.transform = 'translateY(-10px)'; setTimeout(() => { mobileMenu.classList.add('hidden'); mobileMenu.style.transition = ''; }, 200); // Switch icons hamburgerIcon.classList.remove('hidden'); closeIcon.classList.add('hidden'); } } function closeMobileMenu() { const mobileMenu = document.getElementById('mobileMenu'); const hamburgerIcon = document.getElementById('hamburgerIcon'); const closeIcon = document.getElementById('closeIcon'); mobileMenu?.classList.add('hidden'); hamburgerIcon?.classList.remove('hidden'); closeIcon?.classList.add('hidden'); // 모든 서브메뉴 닫기 document.querySelectorAll('.submenu').forEach(submenu => { submenu.style.maxHeight = '0'; }); document.querySelectorAll('.mobile-menu-item button svg').forEach(arrow => { arrow.classList.remove('rotate-180'); }); } function toggleMobileSubmenu(button) { // 부모 컨테이너에서 submenu 찾기 (버튼의 부모의 다음 형제) const parent = button.closest('.mobile-menu-item'); const submenu = parent.querySelector('.submenu'); const arrow = button.querySelector('svg'); if (!submenu) return; // 현재 상태 확인 const isOpen = submenu.style.maxHeight && submenu.style.maxHeight !== '0px'; if (isOpen) { // 닫기: max-height를 0으로 submenu.style.maxHeight = '0'; arrow.classList.remove('rotate-180'); } else { // 먼저 다른 열린 서브메뉴들을 모두 닫기 (한 번에 하나만 열리도록) document.querySelectorAll('.submenu').forEach(otherSubmenu => { if (otherSubmenu !== submenu) { otherSubmenu.style.maxHeight = '0'; const otherArrow = otherSubmenu.closest('.mobile-menu-item')?.querySelector('button svg'); if (otherArrow) { otherArrow.classList.remove('rotate-180'); } } }); // 열기: 실제 높이로 max-height 설정 const innerDiv = submenu.querySelector('div'); submenu.style.maxHeight = innerDiv.scrollHeight + 'px'; arrow.classList.add('rotate-180'); } } // Close mobile menu when clicking outside document.addEventListener('click', function(event) { const mobileMenu = document.getElementById('mobileMenu'); const menuButton = event.target.closest('button[onclick="toggleMobileMenu()"]'); const menuLink = event.target.closest('#mobileMenu'); if (!menuButton && !menuLink && !mobileMenu?.classList.contains('hidden')) { closeMobileMenu(); } }); </script> <!-- Alpine.js --> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> <!-- Floating Action Buttons --> <!-- Attendance Check Float Button --> <!-- Prism.js JavaScript --> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script> <!-- Core languages --> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-clike.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script> <!-- PHP requires markup-templating --> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup-templating.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-php.min.js"></script> <!-- Other languages --> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-sql.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script> <!-- Plugins --> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script> <script> // Prism.js 초기화 및 하이라이팅 새로고침 document.addEventListener('DOMContentLoaded', function() { // Prism.js 하이라이팅 적용 if (typeof Prism !== 'undefined') { Prism.highlightAll(); } }); </script> </body> </html>