first commit 2

This commit is contained in:
hmw1001
2026-06-11 18:47:38 +09:00
parent c768729ce6
commit 6f534e33a6
11095 changed files with 1595758 additions and 0 deletions
@@ -0,0 +1,324 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: list.skin.php
* '저널' 타입 전용 목록 페이지
*/
// 스킨 CSS 로드
$_skin_url = G5_THEME_URL . '/skin/board/rb.board.core.journal';
add_stylesheet('<link rel="stylesheet" href="' . $_skin_url . '/style.css?ver=' . G5_SERVER_TIME . '">', 0);
// 💡 [수정] 전역 변수 선언
global $g5, $is_admin, $board, $bo_table, $member, $list, $write_pages, $write_href, $list_href, $total_count, $page, $total_page, $sca, $sfl, $stx, $spt;
// 뷰 모드 설정
$default_view_mode = isset($board_config['list']['default_view_mode']) ? $board_config['list']['default_view_mode'] : 'card';
$view_mode = get_cookie('board_' . $bo_table . '_view_mode') ?: $default_view_mode;
// 필드 맵 정의
$field_map = array(
'summary' => 'wr_1',
'featured' => 'wr_10',
);
$ebook_link_field = 'wr_link1';
// 💡 [추가] 영문 제목을 한글 월호로 변환하는 함수
function get_journal_title_kr($subject) {
$subject = trim($subject);
// 패턴: month-year-issue-no-number (대소문자 무시)
// 예: february-2026-issue-no-385
if (preg_match('/^([a-z]+)-(\d{4})-issue-no-(\d+)/i', $subject, $matches)) {
$month_eng = strtolower($matches[1]);
$year = $matches[2];
// $no = $matches[3]; // 호수는 필요하면 사용
$months = array(
'january' => '1', 'february' => '2', 'march' => '3', 'april' => '4',
'may' => '5', 'june' => '6', 'july' => '7', 'august' => '8',
'september' => '9', 'october' => '10', 'november' => '11', 'december' => '12'
);
if (isset($months[$month_eng])) {
return $year . '년 ' . $months[$month_eng] . '월호';
}
}
return $subject; // 패턴이 맞지 않으면 원래 제목 반환
}
// 💡 [추가] wr_seo_title에서 날짜 정보를 추출하여 타임스탬프로 변환하는 함수
function get_journal_timestamp($seo_title) {
if (preg_match('/^([a-z]+)-(\d{4})-issue-no-(\d+)/i', $seo_title, $matches)) {
$month_eng = strtolower($matches[1]);
$year = $matches[2];
$months = array(
'january' => '01', 'february' => '02', 'march' => '03', 'april' => '04',
'may' => '05', 'june' => '06', 'july' => '07', 'august' => '08',
'september' => '09', 'october' => '10', 'november' => '11', 'december' => '12'
);
if (isset($months[$month_eng])) {
return strtotime($year . '-' . $months[$month_eng] . '-01');
}
}
return 0; // 날짜 정보 없으면 0
}
// 💡 [추가] 정렬 함수
function sort_journal_list($a, $b) {
$t1 = get_journal_timestamp($a['wr_seo_title']);
$t2 = get_journal_timestamp($b['wr_seo_title']);
if ($t1 == $t2) {
// 날짜가 같으면 wr_id 역순 (최신글 우선)
return $b['wr_id'] - $a['wr_id'];
}
// 날짜 내림차순 (최신 날짜 우선)
return $t2 - $t1;
}
// 💡 [핵심 수정] 상단 지정 최신글 3개 별도 조회 (페이지 상관없이 항상 최신 3개)
$featured_list = array();
$featured_ids = array(); // 중복 제거를 위한 ID 배열
// wr_10 = 'Y' 인 글을 모두 가져온 후 PHP에서 정렬하여 상위 3개만 자름
// (DB에서 정렬하기 어려우므로 전체 가져와서 처리. 개수가 아주 많지 않다고 가정)
$sql_featured = " select * from {$write_table} where wr_is_comment = 0 and {$field_map['featured']} = 'Y' ";
$result_featured = sql_query($sql_featured);
while ($row = sql_fetch_array($result_featured)) {
$row['href'] = get_pretty_url($bo_table, $row['wr_id']);
$row['datetime2'] = date('Y.m.d', strtotime($row['wr_datetime']));
// 제목 변환은 출력 직전에 함
$featured_list[] = $row;
}
// 💡 [핵심] 지정글 날짜순 정렬
usort($featured_list, 'sort_journal_list');
// 상위 3개만 추출
$featured_list = array_slice($featured_list, 0, 3);
// ID 추출 (중복 방지용)
foreach ($featured_list as $item) {
$featured_ids[] = $item['wr_id'];
// 제목 변환 적용
$item['subject'] = get_journal_title_kr($item['wr_subject']);
}
// 배열 참조 문제로 인해 다시 루프 돌며 제목 변환 적용 (위 루프에서 $item은 복사본일 수 있음)
for($i=0; $i<count($featured_list); $i++) {
$featured_list[$i]['subject'] = get_journal_title_kr($featured_list[$i]['wr_subject']);
}
// 💡 [핵심 수정] 하단 리스트 구성 (상단에 노출된 3개 글은 제외)
// $list는 현재 페이지의 게시물만 담고 있으므로, 전체 정렬을 하려면 페이징 로직을 무시하고 전체를 가져와야 함.
// 하지만 성능 이슈가 있을 수 있으므로, 현재 페이지 내에서만 정렬하거나,
// 근본적으로는 글 작성 시 날짜 필드(wr_1 등)에 값을 넣고 DB 정렬을 하는 것이 맞음.
// 여기서는 요청에 따라 $list 내에서 정렬을 시도함.
// 만약 전체 게시물을 날짜순으로 보고 싶다면, $list를 다시 쿼리해야 함.
// 하지만 페이징이 꼬일 수 있음.
// 일단 현재 페이지에 있는 $list를 날짜순으로 정렬함.
usort($list, 'sort_journal_list');
$regular_list = array();
foreach ($list as $item) {
// 상단에 이미 노출된 글이면 리스트에서 제외
// if (in_array($item['wr_id'], $featured_ids)) {
// continue;
// }
// 제목 변환 적용
$item['subject'] = get_journal_title_kr($item['wr_subject']);
$regular_list[] = $item;
}
?>
<div id="journal-board" class="board-container">
<form name="fboardlist" id="fboardlist" action="<?php echo G5_BBS_URL; ?>/board_list_update.php" onsubmit="return fboardlist_submit(this);" method="post">
<input type="hidden" name="bo_table" value="<?php echo $bo_table ?>">
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
<input type="hidden" name="stx" value="<?php echo $stx ?>">
<input type="hidden" name="spt" value="<?php echo $spt ?>">
<input type="hidden" name="sca" value="<?php echo $sca ?>">
<input type="hidden" name="page" value="<?php echo $page ?>">
<input type="hidden" name="sw" value="">
<!-- 게시판 헤더 -->
<div class="board-header">
<div class="board-total">
Total <span class="text-primary"><?php echo number_format($total_count) ?></span> / <?php echo $page ?> page
</div>
<div class="board-controls">
<div class="view-mode-switcher">
<button type="button" class="btn-view-mode <?php echo ($view_mode === 'card') ? 'active' : ''; ?>" data-mode="card" title="카드형으로 보기"><i class="fas fa-th-large"></i></button>
<button type="button" class="btn-view-mode <?php echo ($view_mode === 'list') ? 'active' : ''; ?>" data-mode="list" title="리스트형으로 보기"><i class="fas fa-bars"></i></button>
</div>
<div class="board-search">
<!-- 검색 폼은 별도로 처리하거나 JS로 처리 -->
</div>
</div>
</div>
<!-- 게시판 헤더 (검색 포함) -->
<div class="board-header-search-area" style="display:flex; justify-content:space-between; margin-bottom:20px;">
<div class="board-search">
<!-- 검색 폼 영역 -->
</div>
</div>
<!-- 💡 [핵심 수정] '지정 최신글' 출력 영역 (항상 카드형) -->
<?php if (!empty($featured_list) && $page === 1): // 첫 페이지에서만 보여줍니다. ?>
<div class="featured-list-wrapper">
<ul class="card-list">
<?php for ($i=0; $i<count($featured_list); $i++) {
$item = $featured_list[$i];
$is_featured = true; // 여기는 무조건 지정글임
$thumb = get_list_thumbnail($bo_table, $item['wr_id'], $board['bo_gallery_width'], $board['bo_gallery_height'], false, true);
$href = $item[$ebook_link_field] ? $item[$ebook_link_field] : $item['href'];
$target = $item[$ebook_link_field] ? '_blank' : '';
$summary = cut_str(strip_tags($item[$field_map['summary']] ?: $item['wr_content']), 120);
// 수정 링크 생성
$update_href = G5_BBS_URL.'/write.php?w=u&amp;bo_table='.$bo_table.'&amp;wr_id='.$item['wr_id'].'&amp;page='.$page;
?>
<li class="card-item is-featured">
<a href="<?php echo $href; ?>" target="<?php echo $target; ?>" class="card-link">
<div class="card-thumbnail">
<img src="<?php echo $thumb['src']; ?>" alt="<?php echo $thumb['alt']; ?>">
<span class="badge-featured"><i class="fa fa-star"></i> E-Book</span>
</div>
<div class="card-content">
<?php if ($is_category && $item['ca_name']): ?><span class="card-category"><?php echo $item['ca_name']; ?></span><?php endif; ?>
<h3 class="card-title"><?php echo $item['subject']; ?></h3>
<p class="card-summary"><?php echo $summary; ?></p>
<div class="card-meta"><span class="card-date"><?php echo $item['datetime2']; ?></span></div>
<!-- 💡 [수정] 관리자용 수정 버튼: 파란색 계열, 하단 배치 -->
<?php if ($is_admin): ?>
<div class="admin-btn-group" style="margin-top: 15px; text-align: right;">
<a href="<?php echo $update_href; ?>" class="btn btn-sm btn-primary" style="font-size: 13px; border-radius: 4px; background-color: #0056b3; border-color: #0056b3; color: #fff; text-decoration: none; display: inline-block;">
<i class="fa fa-pen"></i> 수정
</a>
</div>
<?php endif; ?>
</div>
</a>
</li>
<?php } ?>
</ul>
</div>
<!-- 💡 [핵심] 구분선 -->
<div class="list-separator">
<p>최신 3개를 제외하고 나머지는 아래 에서 확인 할 수있습니다.</p>
</div>
<?php endif; ?>
<!-- 💡 [핵심 수정] 일반 목록 출력 영역 (카드/리스트 전환) -->
<div class="board-list-wrapper" data-view-mode="<?php echo $view_mode; ?>">
<?php if (empty($regular_list)): ?>
<div class="empty-list">일반 게시물이 없습니다.</div>
<?php else: ?>
<!-- 카드형 목록 -->
<div class="card-list-view">
<ul class="card-list">
<?php for ($i=0; $i<count($regular_list); $i++) {
$item = $regular_list[$i];
$thumb = get_list_thumbnail($bo_table, $item['wr_id'], $board['bo_gallery_width'], $board['bo_gallery_height'], false, true);
// 💡 [수정] E-Book 링크가 있으면 해당 링크로, 없으면 일반 보기 링크로 설정
$href = $item[$ebook_link_field] ? $item[$ebook_link_field] : $item['href'];
$target = $item[$ebook_link_field] ? '_blank' : '';
$summary = cut_str(strip_tags($item[$field_map['summary']] ?: $item['wr_content']), 120);
// 수정 링크 생성
$update_href = G5_BBS_URL.'/write.php?w=u&amp;bo_table='.$bo_table.'&amp;wr_id='.$item['wr_id'].'&amp;page='.$page;
?>
<li class="card-item">
<a href="<?php echo $href; ?>" target="<?php echo $target; ?>" class="card-link">
<div class="card-thumbnail"><img src="<?php echo $thumb['src']; ?>" alt="<?php echo $thumb['alt']; ?>"></div>
<div class="card-content">
<?php if ($is_category && $item['ca_name']): ?><span class="card-category"><?php echo $item['ca_name']; ?></span><?php endif; ?>
<h3 class="card-title"><?php echo $item['subject']; ?></h3>
<p class="card-summary"><?php echo $summary; ?></p>
<div class="card-meta"><span class="card-date"><?php echo $item['datetime2']; ?></span></div>
<!-- 💡 [수정] 관리자용 수정 버튼: 파란색 계열, 하단 배치 -->
<?php if ($is_admin): ?>
<div class="admin-btn-group" style="margin-top: 15px; text-align: right;">
<a href="<?php echo $update_href; ?>" class="btn btn-sm btn-primary" style="font-size: 13px; border-radius: 4px; background-color: #0056b3; border-color: #0056b3; color: #fff; text-decoration: none; display: inline-block;">
<i class="fa fa-pen"></i> 수정
</a>
</div>
<?php endif; ?>
</div>
</a>
</li>
<?php } ?>
</ul>
</div>
<!-- 리스트형 목록 -->
<div class="list-view">
<ul class="list-group">
<li class="list-group-header">
<div class="list-cell num">번호</div><div class="list-cell title">제목</div><div class="list-cell name">글쓴이</div><div class="list-cell date">날짜</div><div class="list-cell hit">조회</div>
<?php if ($is_admin): ?><div class="list-cell admin">관리</div><?php endif; ?>
</li>
<?php for ($i=0; $i<count($regular_list); $i++) {
$item = $regular_list[$i];
// 💡 [수정] E-Book 링크가 있으면 해당 링크로, 없으면 일반 보기 링크로 설정
$href = $item[$ebook_link_field] ? $item[$ebook_link_field] : $item['href'];
$target = $item[$ebook_link_field] ? '_blank' : '';
// 수정 링크 생성
$update_href = G5_BBS_URL.'/write.php?w=u&amp;bo_table='.$bo_table.'&amp;wr_id='.$item['wr_id'].'&amp;page='.$page;
?>
<li class="list-group-item">
<div class="list-cell num"><?php echo $item['num']; ?></div>
<div class="list-cell title">
<a href="<?php echo $href; ?>" target="<?php echo $target; ?>">
<?php echo $item['subject']; ?>
<?php if ($item['icon_new']) echo ' <span class="icon-new">N</span>'; ?>
</a>
</div>
<div class="list-cell name"><?php echo $item['name']; ?></div>
<div class="list-cell date"><?php echo $item['datetime2']; ?></div>
<div class="list-cell hit"><?php echo $item['wr_hit']; ?></div>
<?php if ($is_admin): ?>
<div class="list-cell admin">
<a href="<?php echo $update_href; ?>" class="btn btn-sm btn-secondary" style=" font-size: 12px;">수정</a>
</div>
<?php endif; ?>
</li>
<?php } ?>
</ul>
</div>
<?php endif; ?>
</div>
<div class="board-footer">
<div class="btn-group">
<?php if ($is_admin && $write_href): ?><a href="<?php echo $write_href ?>" class="btn btn-primary">글쓰기</a><?php endif; ?>
</div>
</div>
</form>
</div>
<?php if ($is_admin): ?>
<script>
function fboardlist_submit(f) {
return true;
}
</script>
<?php endif; ?>
@@ -0,0 +1,152 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: list.skin.php
* '저널' 타입 전용 목록 페이지
*/
// 뷰 모드 설정
$default_view_mode = isset($board_config['list']['default_view_mode']) ? $board_config['list']['default_view_mode'] : 'card';
$view_mode = get_cookie('board_' . $bo_table . '_view_mode') ?: $default_view_mode;
// 필드 맵 정의
$field_map = array(
'summary' => 'wr_1',
'featured' => 'wr_10',
);
$ebook_link_field = 'wr_link1';
// '지정 최신글'과 '일반글'을 분리하여 재정렬
$featured_list = array();
$regular_list = array();
foreach ($list as $item) {
if ($item[$field_map['featured']] == 'Y') {
$featured_list[] = $item;
} else {
$regular_list[] = $item;
}
}
$final_list = array_merge($featured_list, $regular_list);
?>
<div class="board-container">
<!-- 게시판 헤더 -->
<div class="board-header">
<div class="board-total">
Total <span class="text-primary"><?php echo number_format($total_count) ?></span> / <?php echo $page ?> page
</div>
<div class="board-controls">
<div class="view-mode-switcher">
<button type="button" class="btn-view-mode <?php echo ($view_mode === 'card') ? 'active' : ''; ?>" data-mode="card" title="카드형으로 보기"><i class="fas fa-th-large"></i></button>
<button type="button" class="btn-view-mode <?php echo ($view_mode === 'list') ? 'active' : ''; ?>" data-mode="list" title="리스트형으로 보기"><i class="fas fa-bars"></i></button>
</div>
<div class="board-search">
<form name="fsearch" method="get">
<input type="hidden" name="bo_table" value="<?php echo $bo_table ?>"><input type="hidden" name="sca" value="<?php echo $sca ?>">
<div class="input-group">
<select name="sfl" id="sfl"><option value="wr_subject"<?php echo get_selected($sfl, "wr_subject", true); ?>>제목</option><option value="wr_content"<?php echo get_selected($sfl, "wr_content"); ?>>내용</option><option value="wr_subject||wr_content"<?php echo get_selected($sfl, "wr_subject||wr_content"); ?>>제목+내용</option><option value="mb_id,1"<?php echo get_selected($sfl, "mb_id,1"); ?>>회원ID</option><option value="wr_name,1"<?php echo get_selected($sfl, "wr_name,1"); ?>>글쓴이</option></select>
<input type="text" name="stx" value="<?php echo stripslashes($stx) ?>" required class="form-control" placeholder="검색어 입력">
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</form>
</div>
</div>
</div>
<!-- 💡 [핵심 수정] '지정 최신글' 출력 영역 (항상 카드형) -->
<?php if (!empty($featured_list) && $page === 1): // 첫 페이지에서만 보여줍니다. ?>
<div class="featured-list-wrapper">
<ul class="card-list">
<?php for ($i=0; $i<count($featured_list); $i++) {
$item = $featured_list[$i];
$is_featured = ($item[$field_map['featured']] == 'Y');
$thumb = get_list_thumbnail($bo_table, $item['wr_id'], $board['bo_gallery_width'], $board['bo_gallery_height'], false, true);
$href = $is_featured && $item[$ebook_link_field] ? $item[$ebook_link_field] : $item['href'];
$target = $is_featured && $item[$ebook_link_field] ? '_blank' : '';
$summary = cut_str(strip_tags($item[$field_map['summary']] ?: $item['wr_content']), 120);
?>
<li class="card-item <?php if($is_featured) echo 'is-featured'; ?>">
<a href="<?php echo $href; ?>" target="<?php echo $target; ?>" class="card-link">
<div class="card-thumbnail">
<img src="<?php echo $thumb['src']; ?>" alt="<?php echo $thumb['alt']; ?>">
<?php if ($is_featured): ?><span class="badge-featured"><i class="fa fa-star"></i> E-Book</span><?php endif; ?>
</div>
<div class="card-content">
<?php if ($is_category && $item['ca_name']): ?><span class="card-category"><?php echo $item['ca_name']; ?></span><?php endif; ?>
<h3 class="card-title"><?php echo $item['subject']; ?></h3>
<p class="card-summary"><?php echo $summary; ?></p>
<div class="card-meta"><span class="card-date"><?php echo $item['datetime2']; ?></span></div>
</div>
</a>
</li>
<?php } ?>
</ul>
</div>
<!-- 💡 [핵심] 구분선 -->
<div class="list-separator">
<p>최신 6개를 제외하고 나머지는 pdf를 보여주거나 다운후 확인 할 수있습니다.</p>
</div>
<?php endif; ?>
<!-- 💡 [핵심 수정] 일반 목록 출력 영역 (카드/리스트 전환) -->
<div class="board-list-wrapper" data-view-mode="<?php echo $view_mode; ?>">
<?php if (empty($regular_list)): ?>
<div class="empty-list">일반 게시물이 없습니다.</div>
<?php else: ?>
<!-- 카드형 목록 -->
<div class="card-list-view">
<ul class="card-list">
<?php for ($i=0; $i<count($regular_list); $i++) {
$item = $regular_list[$i];
$thumb = get_list_thumbnail($bo_table, $item['wr_id'], $board['bo_gallery_width'], $board['bo_gallery_height'], false, true);
$href = $item['href'];
$summary = cut_str(strip_tags($item[$field_map['summary']] ?: $item['wr_content']), 120);
?>
<li class="card-item">
<a href="<?php echo $href; ?>" class="card-link">
<div class="card-thumbnail"><img src="<?php echo $thumb['src']; ?>" alt="<?php echo $thumb['alt']; ?>"></div>
<div class="card-content">
<?php if ($is_category && $item['ca_name']): ?><span class="card-category"><?php echo $item['ca_name']; ?></span><?php endif; ?>
<h3 class="card-title"><?php echo $item['subject']; ?></h3>
<p class="card-summary"><?php echo $summary; ?></p>
<div class="card-meta"><span class="card-date"><?php echo $item['datetime2']; ?></span></div>
</div>
</a>
</li>
<?php } ?>
</ul>
</div>
<!-- 리스트형 목록 -->
<div class="list-view">
<ul class="list-group">
<li class="list-group-header">
<div class="list-cell num">번호</div><div class="list-cell title">제목</div><div class="list-cell name">글쓴이</div><div class="list-cell date">날짜</div><div class="list-cell hit">조회</div>
</li>
<?php for ($i=0; $i<count($regular_list); $i++) {
$item = $regular_list[$i];
?>
<li class="list-group-item">
<div class="list-cell num"><?php echo $item['num']; ?></div>
<div class="list-cell title">
<a href="<?php echo $item['href']; ?>">
<?php echo $item['subject']; ?>
<?php if ($item['icon_new']) echo ' <span class="icon-new">N</span>'; ?>
</a>
</div>
<div class="list-cell name"><?php echo $item['name']; ?></div>
<div class="list-cell date"><?php echo $item['datetime2']; ?></div>
<div class="list-cell hit"><?php echo $item['wr_hit']; ?></div>
</li>
<?php } ?>
</ul>
</div>
<?php endif; ?>
</div>
</div>
@@ -0,0 +1,192 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: list.skin.php
* '저널' 타입 전용 목록 페이지
*/
// 💡 [수정] 전역 변수 선언
global $g5, $is_admin, $board, $bo_table, $member, $list, $write_pages, $write_href, $list_href, $total_count, $page, $total_page, $sca, $sfl, $stx, $spt;
// 뷰 모드 설정
$default_view_mode = isset($board_config['list']['default_view_mode']) ? $board_config['list']['default_view_mode'] : 'card';
$view_mode = get_cookie('board_' . $bo_table . '_view_mode') ?: $default_view_mode;
// 필드 맵 정의
$field_map = array(
'summary' => 'wr_1',
'featured' => 'wr_10',
);
$ebook_link_field = 'wr_link1';
// '지정 최신글'과 '일반글'을 분리하여 재정렬
$featured_list = array();
$regular_list = array();
foreach ($list as $item) {
$regular_list[] = $item;
if ($item[$field_map['featured']] == 'Y') {
$featured_list[] = $item;
// } else {
}
}
$final_list = array_merge($featured_list, $regular_list);
?>
<div id="journal-board" class="board-container">
<form name="fboardlist" id="fboardlist" action="<?php echo G5_BBS_URL; ?>/board_list_update.php" onsubmit="return fboardlist_submit(this);" method="post">
<input type="hidden" name="bo_table" value="<?php echo $bo_table ?>">
<input type="hidden" name="sfl" value="<?php echo $sfl ?>">
<input type="hidden" name="stx" value="<?php echo $stx ?>">
<input type="hidden" name="spt" value="<?php echo $spt ?>">
<input type="hidden" name="sca" value="<?php echo $sca ?>">
<input type="hidden" name="page" value="<?php echo $page ?>">
<input type="hidden" name="sw" value="">
<!-- 게시판 헤더 -->
<div class="board-header">
<div class="board-total">
Total <span class="text-primary"><?php echo number_format($total_count) ?></span> / <?php echo $page ?> page
</div>
<div class="board-controls">
<div class="view-mode-switcher">
<button type="button" class="btn-view-mode <?php echo ($view_mode === 'card') ? 'active' : ''; ?>" data-mode="card" title="카드형으로 보기"><i class="fas fa-th-large"></i></button>
<button type="button" class="btn-view-mode <?php echo ($view_mode === 'list') ? 'active' : ''; ?>" data-mode="list" title="리스트형으로 보기"><i class="fas fa-bars"></i></button>
</div>
<div class="board-search">
<!-- 검색 폼은 별도로 처리하거나 JS로 처리 -->
</div>
</div>
</div>
<!-- 게시판 헤더 (검색 포함) -->
<div class="board-header-search-area" style="display:flex; justify-content:space-between; margin-bottom:20px;">
<div class="board-search">
<!-- 검색 폼 영역 -->
</div>
</div>
<!-- 💡 [핵심 수정] '지정 최신글' 출력 영역 (항상 카드형) -->
<?php if (!empty($featured_list) && $page === 1): // 첫 페이지에서만 보여줍니다. ?>
<div class="featured-list-wrapper">
<ul class="card-list">
<?php for ($i=0; $i<count($featured_list); $i++) {
$item = $featured_list[$i];
$is_featured = ($item[$field_map['featured']] == 'Y');
$thumb = get_list_thumbnail($bo_table, $item['wr_id'], $board['bo_gallery_width'], $board['bo_gallery_height'], false, true);
$href = $is_featured && $item[$ebook_link_field] ? $item[$ebook_link_field] : $item['href'];
$target = $is_featured && $item[$ebook_link_field] ? '_blank' : '';
$summary = cut_str(strip_tags($item[$field_map['summary']] ?: $item['wr_content']), 120);
// 수정 링크 생성
$update_href = G5_BBS_URL.'/write.php?w=u&amp;bo_table='.$bo_table.'&amp;wr_id='.$item['wr_id'].'&amp;page='.$page;
?>
<li class="card-item <?php if($is_featured) echo 'is-featured'; ?>">
<a href="<?php echo $href; ?>" target="<?php echo $target; ?>" class="card-link">
<div class="card-thumbnail">
<img src="<?php echo $thumb['src']; ?>" alt="<?php echo $thumb['alt']; ?>">
<?php if ($is_featured): ?><span class="badge-featured"><i class="fa fa-star"></i> E-Book</span><?php endif; ?>
</div>
<div class="card-content">
<?php if ($is_category && $item['ca_name']): ?><span class="card-category"><?php echo $item['ca_name']; ?></span><?php endif; ?>
<h3 class="card-title"><?php echo $item['subject']; ?></h3>
<p class="card-summary"><?php echo $summary; ?></p>
<div class="card-meta"><span class="card-date"><?php echo $item['datetime2']; ?></span></div>
<!-- 💡 [수정] 관리자용 수정 버튼: 파란색 계열, 하단 배치 -->
<?php if ($is_admin): ?>
<div class="admin-btn-group" style="margin-top: 15px; text-align: right;">
<a href="<?php echo $update_href; ?>" class="btn btn-sm btn-primary" style="font-size: 13px; border-radius: 4px; background-color: #0056b3; border-color: #0056b3; color: #fff; text-decoration: none; display: inline-block;">
<i class="fa fa-pen"></i> 수정
</a>
</div>
<?php endif; ?>
</div>
</a>
</li>
<?php } ?>
</ul>
</div>
<!-- 💡 [핵심] 구분선 -->
<div class="list-separator">
<p>최신 6개를 제외하고 나머지는 pdf를 보여주거나 다운후 확인 할 수있습니다.</p>
</div>
<?php endif; ?>
<!-- 💡 [핵심 수정] 일반 목록 출력 영역 (카드/리스트 전환) -->
<div class="board-list-wrapper" data-view-mode="<?php echo $view_mode; ?>">
<?php if (empty($regular_list)): ?>
<div class="empty-list">일반 게시물이 없습니다.</div>
<?php else: ?>
<!-- 카드형 목록 -->
<div class="card-list-view">
<ul class="card-list">
<?php for ($i=0; $i<count($regular_list); $i++) {
$item = $regular_list[$i];
$thumb = get_list_thumbnail($bo_table, $item['wr_id'], $board['bo_gallery_width'], $board['bo_gallery_height'], false, true);
$href = $item['href'];
$summary = cut_str(strip_tags($item[$field_map['summary']] ?: $item['wr_content']), 120);
?>
<li class="card-item">
<a href="<?php echo $href; ?>" class="card-link">
<div class="card-thumbnail"><img src="<?php echo $thumb['src']; ?>" alt="<?php echo $thumb['alt']; ?>"></div>
<div class="card-content">
<?php if ($is_category && $item['ca_name']): ?><span class="card-category"><?php echo $item['ca_name']; ?></span><?php endif; ?>
<h3 class="card-title"><?php echo $item['subject']; ?></h3>
<p class="card-summary"><?php echo $summary; ?></p>
<div class="card-meta"><span class="card-date"><?php echo $item['datetime2']; ?></span></div>
</div>
</a>
</li>
<?php } ?>
</ul>
</div>
<!-- 리스트형 목록 -->
<div class="list-view">
<ul class="list-group">
<li class="list-group-header">
<div class="list-cell num">번호</div><div class="list-cell title">제목</div><div class="list-cell name">글쓴이</div><div class="list-cell date">날짜</div><div class="list-cell hit">조회</div>
</li>
<?php for ($i=0; $i<count($regular_list); $i++) {
$item = $regular_list[$i];
?>
<li class="list-group-item">
<div class="list-cell num"><?php echo $item['num']; ?></div>
<div class="list-cell title">
<a href="<?php echo $item['href']; ?>">
<?php echo $item['subject']; ?>
<?php if ($item['icon_new']) echo ' <span class="icon-new">N</span>'; ?>
</a>
</div>
<div class="list-cell name"><?php echo $item['name']; ?></div>
<div class="list-cell date"><?php echo $item['datetime2']; ?></div>
<div class="list-cell hit"><?php echo $item['wr_hit']; ?></div>
</li>
<?php } ?>
</ul>
</div>
<?php endif; ?>
</div>
<div class="board-footer">
<div class="btn-group">
<?php if ($is_admin && $write_href): ?><a href="<?php echo $write_href ?>" class="btn btn-primary">글쓰기</a><?php endif; ?>
</div>
</div>
</form>
</div>
<?php if ($is_admin): ?>
<script>
function fboardlist_submit(f) {
return true;
}
</script>
<?php endif; ?>
@@ -0,0 +1,86 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: pdf_fade_effect.skin.php
* PDF 뷰어에 페이드 인/아웃 효과를 추가하는 확장 파일입니다.
* 이 파일을 삭제하거나 이름을 바꾸면 효과가 비활성화됩니다.
*/
?>
<!-- 1. 페이드 효과를 위한 CSS -->
<style>
#pdf-canvas {
transition: opacity 0.2s ease-in-out;
}
</style>
<!-- 2. 페이드 효과를 적용하는 JavaScript -->
<script type="module">
// 💡 [핵심] 기존 renderPage 함수를 덮어쓰기(Override)하여 페이드 효과를 추가합니다.
// 이 스크립트는 원본 view.skin.php의 스크립트보다 나중에 실행되어야 합니다.
document.addEventListener('DOMContentLoaded', function() {
// 원본 뷰어의 핵심 요소들이 존재하는지 확인
if (typeof window.pdfjsLib === 'undefined' || !document.getElementById('pdf-canvas')) {
return;
}
// 기존의 renderPage 함수를 백업합니다.
const originalRenderPage = window.renderPage;
// 페이드 효과가 적용된 새로운 renderPage 함수를 정의합니다.
window.renderPage = function(num) {
const canvas = document.getElementById('pdf-canvas');
// 이미 렌더링 중이면 대기
if (window.pageRendering) {
window.pageNumPending = num;
return;
}
// 1. 캔버스를 투명하게 만들어 사라지는 효과를 줍니다.
canvas.style.opacity = 0;
// 2. 짧은 시간(애니메이션 시간) 후에 원래의 렌더링 함수를 호출합니다.
setTimeout(() => {
// 백업해둔 원래 함수를 호출하여 페이지를 그립니다.
// originalRenderPage(num);
// -> 원본 함수 내부의 변수 스코프 문제로 직접 실행이 어려우므로, 로직을 그대로 가져옵니다.
window.pageRendering = true;
window.loadingIndicator.style.display = 'flex';
window.pdfDoc.getPage(num).then(function(page) {
const desiredWidth = window.canvasWrapper.clientWidth;
const viewport = page.getViewport({ scale: 1 });
const scale = desiredWidth / viewport.width;
const scaledViewport = page.getViewport({ scale: scale });
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
const renderContext = { canvasContext: window.ctx, viewport: scaledViewport };
const renderTask = page.render(renderContext);
renderTask.promise.then(function() {
window.pageRendering = false;
window.loadingIndicator.style.display = 'none';
// 3. 그리기가 끝나면 캔버스를 다시 나타나게 합니다.
canvas.style.opacity = 1;
if (window.pageNumPending !== null) {
window.renderPage(window.pageNumPending);
window.pageNumPending = null;
}
});
}).catch(function(error) {
console.error('Error during page rendering (fade effect):', error);
window.loadingIndicator.innerHTML = '<p style="color:red;">페이지 렌더링 중 오류가 발생했습니다 (페이드 효과).</p>';
});
window.pageNumInput.value = num;
}, 200); // 0.2초의 딜레이
}
});
</script>
@@ -0,0 +1,239 @@
@charset "UTF-8";
/**
* journal :: style.css
* 💡 [최종 수정] 뷰 전환 오류를 해결하기 위해 CSS 선택자를 원래대로 복원하고, 하이브리드 레이아웃을 올바르게 지원합니다.
*/
/* ==========================================================================
Board Common Styles
========================================================================== */
#journal-board .board-container,
#journal-board .board-write-container,
#journal-board .board-view-container {
background-color: #fff;
padding: 40px;
border-radius: 12px;
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.08);
border: none;
}
/* ==========================================================================
List View: Header
========================================================================== */
#journal-board .board-header {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
padding-bottom: 25px;
border-bottom: 1px solid #e0e0e0;
margin-bottom: 30px;
}
#journal-board .board-total { font-size: 1.2rem; color: #555; font-weight: 500; text-align: left; }
#journal-board .board-total .text-primary { color: #0056b3; font-weight: 700; }
#journal-board .board-controls { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
#journal-board .view-mode-switcher { display: flex; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }
#journal-board .btn-view-mode { background: #fff; border: none; padding: 10px 15px; cursor: pointer; color: #888; font-size: 1.2rem; line-height: 1; transition: all 0.2s ease; }
#journal-board .btn-view-mode.active { background: #0056b3; color: #fff; }
#journal-board .btn-view-mode:hover:not(.active) { background: #f0f0f0; color: #333; }
#journal-board .board-search { flex-grow: 1; }
#journal-board .board-search .input-group { display: flex; align-items: center; width: 100%; max-width: 400px; margin-left: auto; }
#journal-board .board-search select,
#journal-board .board-search input[type="text"] { border: 1px solid #ddd; padding: 0 15px; height: 42px; font-size: 1rem; background: #fff; border-radius: 8px; transition: border-color 0.2s ease; }
#journal-board .board-search select { border-radius: 8px 0 0 8px; border-right: none; }
#journal-board .board-search input[type="text"]:focus { border-color: #0056b3; outline: none; box-shadow: 0 0 0 2px rgba(0, 86, 179, 0.2); }
#journal-board .board-search .btn { border-radius: 0 8px 8px 0; height: 42px; padding: 0 20px; background: #0056b3; color: #fff; border: none; transition: background-color 0.2s ease; }
#journal-board .board-search .btn:hover { background-color: #003d82; }
/* ==========================================================================
List View: Hybrid Layout & View Switching
========================================================================== */
#journal-board .list-separator {
text-align: center;
padding: 40px 20px;
margin: 40px 0;
border-top: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
background-color: #f9f9f9;
}
#journal-board .list-separator p {
font-size: 1.1rem;
color: #666;
font-weight: 500;
}
/* 💡 [최종 수정] 뷰 전환이 올바르게 동작하도록 선택자를 수정합니다. */
#journal-board .board-list-wrapper[data-view-mode="card"] .card-list-view { display: block; }
#journal-board .board-list-wrapper[data-view-mode="list"] .list-view { display: block; }
#journal-board .board-list-wrapper .card-list-view,
#journal-board .board-list-wrapper .list-view { display: none; }
/* ==========================================================================
List View: Card & List
========================================================================== */
#journal-board .empty-list { padding: 100px 20px; text-align: center; color: #888; font-size: 1.6rem; font-weight: 600; }
#journal-board .card-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 30px; }
#journal-board .card-item { position: relative; background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 8px 25px rgba(0,0,0,0.08); transition: transform 0.3s ease, box-shadow 0.3s ease; border: none; }
#journal-board .card-item:hover { transform: translateY(-7px); box-shadow: 0 12px 35px rgba(0,0,0,0.15); }
#journal-board .card-link { display: block; text-decoration: none; color: inherit; }
#journal-board .card-thumbnail { position: relative; width: 100%; padding-top: 70%; background-color: #f0f0f0; }
#journal-board .card-thumbnail img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; }
#journal-board .badge-pdf, #journal-board .badge-link, #journal-board .badge-featured { position: absolute; top: 15px; right: 15px; padding: 8px 15px; border-radius: 20px; font-size: 0.85rem; font-weight: 700; color: #fff; text-transform: uppercase; letter-spacing: 0.5px; }
#journal-board .badge-pdf { background-color: #e74c3c; }
#journal-board .badge-link { background-color: #3498db; }
#journal-board .badge-featured { background-color: #9b59b6; }
#journal-board .card-content { padding: 25px; }
#journal-board .card-category { display: inline-block; font-size: 0.9rem; font-weight: 700; color: #0056b3; margin-bottom: 10px; }
#journal-board .card-title { font-size: 1.5rem; font-weight: 800; margin: 0 0 15px 0; line-height: 1.4; min-height: 2.8em; color: #222; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
#journal-board .card-summary { font-size: 1rem; color: #666; line-height: 1.7; height: 5.1em; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
#journal-board .card-meta { margin-top: 20px; padding-top: 15px; border-top: 1px solid #f0f0f0; font-size: 0.9rem; color: #888; }
#journal-board .card-item.is-featured { border: 2px solid #9b59b6; box-shadow: 0 8px 25px rgba(155, 89, 182, 0.2); }
#journal-board .list-group { border-top: 2px solid #333; }
#journal-board .list-group-header, #journal-board .list-group-item { display: flex; align-items: center; padding: 18px 10px; border-bottom: 1px solid #f0f0f0; }
#journal-board .list-group-header { font-weight: 700; background-color: #f9f9f9; font-size: 1.05rem; }
#journal-board .list-group-item:hover { background-color: #f5f5f5; }
#journal-board .list-cell { padding: 0 10px; text-align: center; color: #555; }
#journal-board .list-cell.num { flex: 0 0 80px; }
#journal-board .list-cell.title { flex: 1; text-align: left; }
#journal-board .list-cell.title a { color: #333; text-decoration: none; font-weight: 500; }
#journal-board .list-group-item.is-featured { background-color: #fdf8ff; }
#journal-board .badge-featured-list { color: #9b59b6; margin-right: 8px; }
/* ==========================================================================
Admin Checkbox Styles
========================================================================== */
#journal-board .chk-box input[type="checkbox"] {
-webkit-appearance: checkbox !important;
-moz-appearance: checkbox !important;
appearance: checkbox !important;
opacity: 1 !important;
visibility: visible !important;
display: inline-block !important;
position: static !important;
width: 20px !important;
height: 20px !important;
cursor: pointer !important;
}
#journal-board .list-cell.chk input[type="checkbox"] {
-webkit-appearance: checkbox !important;
-moz-appearance: checkbox !important;
appearance: checkbox !important;
opacity: 1 !important;
visibility: visible !important;
display: inline-block !important;
width: 20px !important;
height: 20px !important;
cursor: pointer !important;
}
/* ==========================================================================
View Page Styles
========================================================================== */
#journal-board .view-header { padding-bottom: 20px; border-bottom: 1px solid #e0e0e0; margin-bottom: 30px; }
#journal-board .view-header h1 { font-size: 2.5rem; font-weight: 800; margin-bottom: 15px; line-height: 1.3; }
#journal-board .view-meta { display: flex; flex-wrap: wrap; gap: 10px 20px; color: #888; font-size: 0.95rem; }
#journal-board .view-meta .meta-item i { margin-right: 5px; color: #aaa; }
#journal-board .view-content { padding: 30px 0; }
#journal-board #pdf-viewer-container { display: block; margin-bottom: 40px; border: 1px solid #e0e0e0; border-radius: 8px; }
#journal-board #pdf-viewer-header {
position: sticky;
top: 0;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
background: rgba(248, 248, 248, 0.95);
backdrop-filter: blur(5px);
border-bottom: 1px solid #e0e0e0;
}
#journal-board #pdf-viewer-header .page-indicator { font-size: 1rem; color: #333; font-weight: 500; }
#journal-board #pdf-viewer-header #page-num-input { width: 60px; text-align: center; border: 1px solid #ddd; border-radius: 5px; padding: 5px; font-size: 1rem; }
#journal-board #canvas-wrapper { overflow: auto; background: #f9f9f9; cursor: pointer; }
#journal-board #pdf-canvas { display: block; margin: 0 auto; }
#journal-board #pdf-loading { padding: 100px 20px; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 20px; font-size: 1.3rem; color: #666; font-weight: 500; }
#journal-board .spinner { width: 40px; height: 40px; border: 4px solid #e0e0e0; border-top: 4px solid #0056b3; border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
#journal-board .download-box { margin-top: 30px; padding-top: 30px; border-top: 1px solid #eee; }
#journal-board .download-box h3 { font-size: 1.3rem; font-weight: 700; margin-bottom: 15px; color: #333; }
#journal-board .download-list { list-style: none; padding: 0; margin: 0; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; }
#journal-board .download-list li { border-bottom: 1px solid #e0e0e0; }
#journal-board .download-list li:last-child { border-bottom: none; }
#journal-board .download-list a { display: flex; align-items: center; padding: 15px 20px; text-decoration: none; color: #555; transition: background-color 0.2s ease; }
#journal-board .download-list a:hover { background-color: #f9f9f9; }
#journal-board .download-list .file-icon { font-size: 1.5rem; color: #888; margin-right: 15px; width: 25px; text-align: center; }
#journal-board .download-list .fa-file-pdf-o { color: #e74c3c; }
#journal-board .download-list .fa-file-image-o { color: #3498db; }
#journal-board .download-list .fa-file-archive-o { color: #f39c12; }
#journal-board .download-list .file-name { flex-grow: 1; font-weight: 500; }
#journal-board .download-list .file-size { font-size: 0.9rem; color: #888; }
#journal-board .view-summary { font-size: 1.1rem; line-height: 1.7; color: #555; padding: 25px; background: #f8f8f8; border-radius: 8px; margin-top: 40px; }
#journal-board .view-detail-content { margin-top: 40px; }
#journal-board .view-footer { margin-top: 40px; padding-top: 30px; border-top: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
#journal-board .btn-group-left, #journal-board .btn-group-right { display: flex; gap: 10px; }
/* ==========================================================================
Common Footer & Write Form
========================================================================== */
#journal-board .board-footer { margin-top: 40px; display: flex; justify-content: space-between; align-items: center; }
#journal-board .pagination-wrapper, #journal-board .load-more-wrapper { flex-grow: 1; text-align: center; }
#journal-board .board-footer .btn-group { display: flex; gap: 10px; margin-left: auto; }
#journal-board .board-footer .btn, #journal-board .view-footer .btn { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; border-radius: 8px; font-size: 1.1rem; text-align: center; font-weight: 700; transition: all 0.2s ease; border: none; }
#journal-board .board-footer .btn:hover, #journal-board .view-footer .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
#journal-board .board-footer .btn-secondary, #journal-board .view-footer .btn-secondary { background-color: #6c757d; color: #fff; }
#journal-board .board-footer .btn-primary, #journal-board .view-footer .btn-primary { background-color: #0056b3; color: #fff; }
#journal-board .write-form-group { margin-bottom: 25px; }
#journal-board .form-divider { margin: 40px 0; border: 0; border-top: 1px solid #e0e0e0; }
#journal-board .admin-options-group { background-color: #fcfcfc; padding: 25px; border-radius: 8px; margin-top: 30px; border: 1px solid #e0e0e0; }
#journal-board .form-section-title { font-size: 1.3rem; font-weight: 700; margin: 0 0 20px 0; color: #333; }
#journal-board .form-check-label { display: flex; align-items: center; gap: 10px; font-size: 1.1rem; cursor: pointer; }
#journal-board .form-check-label input[type="checkbox"] { -webkit-appearance: checkbox; -moz-appearance: checkbox; appearance: checkbox; width: 18px; height: 18px; opacity: 1; position: static; }
#journal-board .write-form-footer { margin-top: 40px; display: flex; justify-content: flex-end; gap: 10px; }
#journal-board .write-form-footer .btn { text-decoration: none; border-radius: 8px; font-size: 1.1rem; font-weight: 700; border: none; transition: all 0.2s ease; }
#journal-board .write-form-footer .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
#journal-board .write-form-footer .btn-secondary { background-color: #6c757d; color: #fff; }
#journal-board .write-form-footer .btn-primary { background-color: #0056b3; color: #fff; }
/* 3-Column Layout - PC 전용 */
#journal-board .three-column-layout { display: flex; gap: 30px; }
#journal-board .layout-sidebar-left, #journal-board .layout-sidebar-right { flex: 0 0 240px; position: sticky; top: 100px; align-self: flex-start; }
#journal-board .layout-main-content { flex: 1; min-width: 0; }
.sub-page-container {
padding-top: 0px;
padding-bottom: 0px;
}
.layout-main-content {
padding-top: 0px;
}
/* 💡 [추가] 관리자 옵션 체크박스 스타일 */
#journal-board .admin-option-item {
background: #f9f9f9;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 15px;
}
#journal-board .admin-option-checkbox {
appearance: checkbox !important;
-webkit-appearance: checkbox !important;
width: 20px !important;
height: 20px !important;
cursor: pointer !important;
opacity: 1 !important;
visibility: visible !important;
display: inline-block !important;
position: static !important;
margin-right: 10px !important;
}
#journal-board .admin-option-item .option-text {
font-weight: bold;
color: #333;
font-size: 16px;
}
@@ -0,0 +1,174 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: view.skin.php
* '저널' 타입 전용 뷰어 - PDF 뷰어 기능만 담당
*/
// 스킨 CSS 로드
$_skin_url = G5_THEME_URL . '/skin/board/rb.board.core.journal';
add_stylesheet('<link rel="stylesheet" href="' . $_skin_url . '/style.css?ver=' . G5_SERVER_TIME . '">', 0);
if (!function_exists('get_extension')) {
function get_extension($filename) {
$filename = basename($filename);
return substr(strrchr($filename, "."), 1);
}
}
$pdfjs_url = G5_THEME_URL . '/js/pdfjs';
$pdf_file_url = '';
$pdf_download_url = ''; // 💡 [추가] 다운로드 URL을 저장할 변수
if ($view['file']['count'] > 0) { // 💡 [수정] 1이 아닌 0부터 시작하여 모든 첨부파일을 확인
for ($i = 0; $i < $view['file']['count']; $i++) { // 💡 [수정]
if (isset($view['file'][$i]) && strtolower(get_extension($view['file'][$i]['source'])) == 'pdf') {
$pdf_file_url = $view['file'][$i]['path'].'/'.urlencode($view['file'][$i]['file']);
$pdf_download_url = $view['file'][$i]['href']; // 💡 [추가] 다운로드 링크 저장
break;
}
}
}
?>
<!-- 💡 [핵심] 본문 내용은 전문 코어 파일에서 처리합니다. -->
<div class="view-detail-content">
<?php
// HTML 에디터 사용 여부에 따라 conv_content 처리
$html = 1;
if (strstr($view['wr_option'], 'html1')) {
$html = 1;
} else if (strstr($view['wr_option'], 'html2')) {
$html = 2;
}
// 💡 [핵심] g5_dynamic_img_url 함수를 적용하여 이미지 경로를 동적으로 변경합니다.
echo g5_dynamic_img_url(conv_content($view['wr_content'], $html));
?>
</div>
<?php if ($pdf_file_url): ?>
<div id="pdf-viewer-container">
<!-- <?php /*if ($is_admin && $view['wr_8'] == 'Y'): */?>
<div class="admin-option-item">
<span class="option-text">💡 이 글은 메인 화면 포트폴리오 영역에 노출되고 있습니다.</span>
</div>
--><?php /*endif; */?>
<div id="pdf-viewer-header">
<button id="prev-page" class="btn btn-secondary"><i class="fa fa-arrow-left"></i> 이전</button>
<div class="page-indicator">Page: <input type="number" id="page-num-input" value="1" min="1"> / <span id="page-count"></span></div>
<button id="next-page" class="btn btn-secondary">다음 <i class="fa fa-arrow-right"></i></button>
<?php if ($pdf_download_url): // 💡 [추가] 다운로드 버튼 ?>
<a href="<?php echo $pdf_download_url; ?>" class="btn btn-primary" download>다운로드 <i class="fa fa-download"></i></a>
<?php endif; ?>
</div>
<div id="canvas-wrapper"><canvas id="pdf-canvas"></canvas></div>
<div id="pdf-loading"><div class="spinner"></div><p>PDF 파일을 불러오는 중입니다...</p></div>
</div>
<script src="<?php echo $pdfjs_url; ?>/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc =
'<?php echo $pdfjs_url; ?>/pdf.worker.min.js';
const url = '<?php echo $pdf_file_url; ?>';
const canvas = document.getElementById('pdf-canvas');
const ctx = canvas.getContext('2d');
const loadingIndicator = document.getElementById('pdf-loading');
const pageNumInput = document.getElementById('page-num-input');
const pageCountSpan = document.getElementById('page-count');
const prevButton = document.getElementById('prev-page');
const nextButton = document.getElementById('next-page');
const canvasWrapper = document.getElementById('canvas-wrapper');
let pdfDoc = null;
let pageNum = 1;
let pageRendering = false;
let pageNumPending = null;
function hideLoadingIndicator() {
loadingIndicator.style.display = 'none';
}
function renderPage(num) {
pageRendering = true;
loadingIndicator.style.display = 'flex';
pdfDoc.getPage(num).then(function(page) {
const desiredWidth = canvasWrapper.clientWidth;
const viewport = page.getViewport({ scale: 1 });
const scale = desiredWidth / viewport.width;
const scaledViewport = page.getViewport({ scale });
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
page.render({
canvasContext: ctx,
viewport: scaledViewport
}).promise.then(function() {
pageRendering = false;
hideLoadingIndicator();
if (pageNumPending !== null) {
renderPage(pageNumPending);
pageNumPending = null;
}
});
});
pageNumInput.value = num;
}
function queueRenderPage(num) {
if (pageRendering) pageNumPending = num;
else renderPage(num);
}
function onPrevPage() {
if (pageNum <= 1) return;
pageNum--;
queueRenderPage(pageNum);
}
function onNextPage() {
if (pageNum >= pdfDoc.numPages) return;
pageNum++;
queueRenderPage(pageNum);
}
prevButton.addEventListener('click', onPrevPage);
nextButton.addEventListener('click', onNextPage);
pageNumInput.addEventListener('change', function() {
const n = parseInt(this.value, 10);
if (n > 0 && n <= pdfDoc.numPages) {
pageNum = n;
queueRenderPage(pageNum);
} else {
this.value = pageNum;
}
});
pdfjsLib.getDocument(url).promise.then(function(pdf) {
pdfDoc = pdf;
pageCountSpan.textContent = pdfDoc.numPages;
pageNumInput.max = pdfDoc.numPages;
renderPage(pageNum);
}).catch(function(err) {
console.error(err);
loadingIndicator.innerHTML =
'<p style="color:red;">PDF 로드 실패</p>';
hideLoadingIndicator();
});
</script>
<?php endif; ?>
<div class="view-footer">
<div class="btn-group-left">
<?php if ($prev_href): ?><a href="<?php echo $prev_href ?>" class="btn btn-secondary">이전글</a><?php endif; ?>
<?php if ($next_href): ?><a href="<?php echo $next_href ?>" class="btn btn-secondary">다음글</a><?php endif; ?>
</div>
<div class="btn-group-right">
<a href="<?php echo $list_href ?>" class="btn btn-primary">목록</a>
<?php if ($update_href): ?><a href="<?php echo $update_href ?>" class="btn btn-secondary">수정</a><?php endif; ?>
<?php if ($delete_href): ?><a href="<?php echo $delete_href ?>" onclick="del(this.href); return false;" class="btn btn-secondary">삭제</a><?php endif; ?>
<?php if ($write_href): ?><a href="<?php echo $write_href ?>" class="btn btn-primary">글쓰기</a><?php endif; ?>
</div>
</div>
@@ -0,0 +1,297 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: write.skin.php
* '저널' 타입 전용 글쓰기 폼
*/
// 스킨 CSS 로드
$_skin_url = G5_THEME_URL . '/skin/board/rb.board.core.journal';
add_stylesheet('<link rel="stylesheet" href="' . $_skin_url . '/style.css?ver=' . G5_SERVER_TIME . '">', 0);
$field_map = array(
'summary' => 'wr_1',
'featured' => 'wr_10',
'main_display' => 'wr_8', // 💡 [추가] 메인 화면 노출 필드 매핑
);
$ebook_link_field = 'wr_link1';
$cfg_write = isset($board_config['write']) ? $board_config['write'] : array();
// 💡 [추가] 지정 최신글 개수 확인
$featured_count = 0;
if ($is_admin) {
$sql = " select count(*) as cnt from {$write_table} where {$field_map['featured']} = 'Y' ";
// 수정 모드이고, 현재 글이 이미 지정 최신글이라면 개수에서 제외하지 않음 (이미 포함되어 있으므로)
// 하지만 로직상 '현재 체크되어 있지 않은데 체크하려는 경우'를 막아야 하므로,
// DB에 저장된 총 개수를 가져오고, JS에서 판단하는 것이 좋음.
$row = sql_fetch($sql);
$featured_count = $row['cnt'];
}
?>
<style>
/* 에디터 토글 버튼 스타일 */
.editor-toggle-buttons {
margin-bottom: 10px;
display: flex;
gap: 5px;
}
.btn-editor-toggle {
/*padding: 6px 12px;*/
font-size: 13px;
border: 1px solid #ddd;
background-color: #f8f9fa;
color: #555;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.btn-editor-toggle:hover {
background-color: #e9ecef;
}
.btn-editor-toggle.active {
background-color: #4a90e2;
color: #fff;
border-color: #4a90e2;
font-weight: bold;
}
</style>
<div class="board-write-container">
<form name="fwrite" id="fwrite" action="<?php echo $action_url ?>" onsubmit="return fwrite_submit(this);" method="post" enctype="multipart/form-data" autocomplete="off" novalidate>
<input type="hidden" name="uid" value="<?php echo get_uniqid(); ?>">
<input type="hidden" name="w" value="<?php echo $w ?>"><input type="hidden" name="bo_table" value="<?php echo $bo_table ?>"><input type="hidden" name="wr_id" value="<?php echo $wr_id ?>"><input type="hidden" name="sca" value="<?php echo $sca ?>"><input type="hidden" name="sfl" value="<?php echo $sfl ?>"><input type="hidden" name="stx" value="<?php echo $stx ?>"><input type="hidden" name="spt" value="<?php echo $spt ?>"><input type="hidden" name="sst" value="<?php echo $sst ?>"><input type="hidden" name="sod" value="<?php echo $sod ?>"><input type="hidden" name="page" value="<?php echo $page ?>">
<!-- 💡 [핵심] HTML 에디터 사용 여부를 서버에 알리기 위한 필드 -->
<input type="hidden" name="html" value="<?php echo $html ?>">
<div class="write-form-group">
<label for="wr_subject" class="form-label">제목<span class="required">*</span></label>
<input type="text" name="wr_subject" value="<?php echo $subject ?>" id="wr_subject" required class="form-control" placeholder="제목을 입력하세요">
</div>
<?php if ($is_category): ?>
<div class="write-form-group">
<label for="ca_name" class="form-label">카테고리<span class="required">*</span></label>
<select name="ca_name" id="ca_name" required class="form-control"><option value="">선택하세요</option><?php echo $category_option ?></select>
</div>
<?php endif; ?>
<?php if (!empty($cfg_write['use_summary'])): ?>
<div class="write-form-group">
<label for="<?php echo $field_map['summary']; ?>" class="form-label"><?php echo $cfg_write['summary_label'] ?: '요약'; ?></label>
<textarea name="<?php echo $field_map['summary']; ?>" id="<?php echo $field_map['summary']; ?>" class="form-control" rows="3" placeholder="<?php echo $cfg_write['summary_placeholder']; ?>"><?php echo $write[$field_map['summary']]; ?></textarea>
</div>
<?php endif; ?>
<div class="write-form-group">
<label for="wr_content" class="form-label">내용</label>
<div class="editor-toggle-buttons">
<button type="button" class="btn btn-sm btn-editor-toggle <?php echo $is_dhtml_editor ? 'active' : ''; ?>" data-editor-type="dhtml">HTML 에디터</button>
<button type="button" class="btn btn-sm btn-editor-toggle <?php echo $is_dhtml_editor ? '' : 'active'; ?>" data-editor-type="text">텍스트 에디터</button>
</div>
<!-- HTML 에디터 영역 -->
<div id="dhtml-editor-area" style="display: <?php echo $is_dhtml_editor ? 'block' : 'none'; ?>;">
<?php if ($is_dhtml_editor) { echo $editor_html; } ?>
</div>
<!-- 텍스트 에디터 영역 -->
<div id="text-editor-area" style="display: <?php echo $is_dhtml_editor ? 'none' : 'block'; ?>;">
<!-- 💡 [수정] 텍스트 에디터의 ID와 name을 명확히 구분 -->
<!-- 초기 로드 시 HTML 에디터가 활성화되어 있으면 name 속성을 제거해야 충돌 방지 -->
<textarea id="wr_content_text" class="form-control" rows="10" <?php echo $is_dhtml_editor ? '' : 'name="wr_content"'; ?>><?php echo $content ?></textarea>
</div>
</div>
<hr class="form-divider">
<?php for ($i=1; $is_file && $i<=$board['bo_upload_count']; $i++):
$file_label = isset($cfg_write['file_labels'][$i]) ? $cfg_write['file_labels'][$i] : '첨부파일 #'.$i;
$file_text = isset($cfg_write['file_texts'][$i]) ? $cfg_write['file_texts'][$i] : '';
?>
<div class="write-form-group file-upload-group">
<label for="bf_file_<?php echo $i ?>" class="form-label"><?php echo $file_label; ?><?php if($i==1) echo '<span class="required">*</span>'; ?></label>
<div class="file-input-wrapper">
<input type="file" name="bf_file[]" id="bf_file_<?php echo $i ?>" title="파일첨부 <?php echo $i ?> : 용량 <?php echo $upload_max_filesize ?> 이하만 업로드 가능" class="form-control" <?php if ($w=='' && $i==1) echo 'required'; ?>>
<?php if ($w == 'u' && $file[$i-1]['file']): ?>
<span class="file-delete-wrap"><input type="checkbox" id="bf_file_del_<?php echo $i-1 ?>" name="bf_file_del[<?php echo $i-1; ?>]" value="1"><label for="bf_file_del_<?php echo $i-1 ?>"><?php echo $file[$i-1]['source'].'('.$file[$i-1]['size'].')'; ?> 파일 삭제</label></span>
<?php endif; ?>
</div>
<?php if($file_text): ?><p class="form-text"><?php echo $file_text; ?></p><?php endif; ?>
</div>
<?php endfor; ?>
<hr class="form-divider">
<?php if ($is_admin): ?>
<div class="admin-options-group">
<h3 class="form-section-title">관리자 전용 설정</h3>
<div class="admin-option-item">
<label class="form-check-label" for="is_featured">
<input type="checkbox" name="<?php echo $field_map['featured']; ?>" value="Y" id="is_featured" class="admin-option-checkbox" <?php echo ($write[$field_map['featured']] == 'Y') ? 'checked' : ''; ?>>
<span class="option-text">이 글을 '지정 최신글'로 설정합니다. (최대 6개, 현재 <?php echo $featured_count; ?>개)</span>
</label>
<div id="ebook-link-group" style="display: <?php echo ($write[$field_map['featured']] == 'Y') ? 'block' : 'none'; ?>; margin-top: 15px;">
<label for="<?php echo $ebook_link_field; ?>" class="form-label" style="margin-top:0;">E-book 링크</label>
<input type="url" name="<?php echo $ebook_link_field; ?>" value="<?php echo $write[$ebook_link_field]; ?>" id="<?php echo $ebook_link_field; ?>" class="form-control" placeholder="https://example.com/ebook/123">
<p class="form-text">'지정 최신글'로 설정했을 경우, 클릭 시 이동할 이북 주소를 입력하세요.</p>
</div>
</div>
</div>
<?php endif; ?>
<div class="write-form-footer">
<a href="<?php echo get_pretty_url($bo_table); ?>" class="btn btn-secondary">취소</a>
<button type="submit" id="btn_submit" accesskey="s" class="btn btn-primary">작성완료</button>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const isFeaturedCheckbox = document.getElementById('is_featured');
const ebookLinkGroup = document.getElementById('ebook-link-group');
// 💡 [추가] 지정 최신글 개수 제한 로직
const featuredCount = <?php echo $featured_count; ?>;
const isAlreadyFeatured = <?php echo ($write[$field_map['featured']] == 'Y') ? 'true' : 'false'; ?>;
const maxFeatured = 12;
if (isFeaturedCheckbox) {
isFeaturedCheckbox.addEventListener('change', function() {
// 체크하려고 할 때 개수 확인
if (this.checked) {
// 이미 지정된 글이 아니면서, 현재 개수가 꽉 찼다면
if (!isAlreadyFeatured && featuredCount >= maxFeatured) {
alert('지정 최신글은 최대 ' + maxFeatured + '개까지만 설정할 수 있습니다.\n기존 글의 설정을 해제한 후 다시 시도해주세요.');
this.checked = false;
return;
}
}
if (ebookLinkGroup) {
ebookLinkGroup.style.display = this.checked ? 'block' : 'none';
}
});
}
const dhtmlEditorArea = document.getElementById('dhtml-editor-area');
const textEditorArea = document.getElementById('text-editor-area');
const editorToggleButtons = document.querySelectorAll('.btn-editor-toggle');
const wrContentTextarea = document.getElementById('wr_content_text');
const wrContentDhtmlTextarea = document.getElementById('wr_content'); // 그누보드 에디터가 생성하는 textarea
const htmlInput = document.querySelector('input[name="html"]'); // html 모드 저장용
function setEditorMode(isDhtml) {
if (isDhtml) {
// HTML 에디터 모드
dhtmlEditorArea.style.display = 'block';
textEditorArea.style.display = 'none';
// 텍스트 에디터의 내용을 HTML 에디터로 복사 (가능한 경우)
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
CKEDITOR.instances.wr_content.setData(wrContentTextarea.value);
}
// name 속성 조정: HTML 에디터가 wr_content를 사용하도록
if (wrContentDhtmlTextarea) {
wrContentDhtmlTextarea.setAttribute('name', 'wr_content');
}
wrContentTextarea.removeAttribute('name');
// html 필드 값 설정 (html1 또는 html2)
if (htmlInput) htmlInput.value = 'html1';
} else {
// 텍스트 에디터 모드
dhtmlEditorArea.style.display = 'none';
textEditorArea.style.display = 'block';
// HTML 에디터의 내용을 텍스트 에디터로 복사
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
wrContentTextarea.value = CKEDITOR.instances.wr_content.getData();
} else if (wrContentDhtmlTextarea) {
wrContentTextarea.value = wrContentDhtmlTextarea.value;
}
// name 속성 조정: 텍스트 에디터가 wr_content를 사용하도록
wrContentTextarea.setAttribute('name', 'wr_content');
if (wrContentDhtmlTextarea) {
wrContentDhtmlTextarea.removeAttribute('name');
}
// html 필드 값 초기화 (텍스트 모드)
if (htmlInput) htmlInput.value = '';
}
// 버튼 스타일 업데이트
editorToggleButtons.forEach(btn => {
if ((btn.dataset.editorType === 'dhtml' && isDhtml) || (btn.dataset.editorType === 'text' && !isDhtml)) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
}
// 초기 모드 설정
setEditorMode(<?php echo $is_dhtml_editor ? 'true' : 'false'; ?>);
editorToggleButtons.forEach(button => {
button.addEventListener('click', function() {
const type = this.dataset.editorType;
setEditorMode(type === 'dhtml');
});
});
});
function fwrite_submit(f) {
// 그누보드 에디터 내용 동기화 (필수)
<?php echo $editor_js; ?>
let content = '';
const dhtmlEditorArea = document.getElementById('dhtml-editor-area');
// 현재 활성화된 에디터 확인
if (dhtmlEditorArea && dhtmlEditorArea.style.display === 'block') {
// HTML 에디터 모드
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
content = CKEDITOR.instances.wr_content.getData();
} else if (document.getElementById('wr_content')) {
content = document.getElementById('wr_content').value;
}
} else {
// 텍스트 에디터 모드
content = document.getElementById('wr_content_text').value;
}
// 내용 유효성 검사 (태그 제거 후 확인)
let contentCheck = content.replace(/<[^>]*>/g, '').replace(/&nbsp;/g, '').trim();
// 파일 첨부 여부 확인
let hasFile = false;
const fileInputs = document.querySelectorAll('input[name="bf_file[]"]');
for (let i = 0; i < fileInputs.length; i++) {
if (fileInputs[i].value) {
hasFile = true;
break;
}
}
// 수정 모드일 때 기존 파일이 있는지 확인 (삭제 체크박스가 있으면 파일이 있는 것임)
const fileDeletes = document.querySelectorAll('input[name^="bf_file_del"]');
if (fileDeletes.length > 0) {
hasFile = true;
}
if (contentCheck === '' && !hasFile) {
alert('내용을 입력하거나 파일을 첨부해주세요.');
return false;
}
return true;
}
</script>
@@ -0,0 +1,221 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: write.skin.php
* '저널' 타입 전용 글쓰기 폼
*/
$field_map = array(
'summary' => 'wr_1',
'featured' => 'wr_10',
'main_display' => 'wr_8', // 💡 [추가] 메인 화면 노출 필드 매핑
);
$ebook_link_field = 'wr_link1';
$cfg_write = isset($board_config['write']) ? $board_config['write'] : array();
?>
<div class="board-write-container">
<form name="fwrite" id="fwrite" action="<?php echo $action_url ?>" onsubmit="return fwrite_submit(this);" method="post" enctype="multipart/form-data" autocomplete="off" novalidate>
<input type="hidden" name="uid" value="<?php echo get_uniqid(); ?>">
<input type="hidden" name="w" value="<?php echo $w ?>"><input type="hidden" name="bo_table" value="<?php echo $bo_table ?>"><input type="hidden" name="wr_id" value="<?php echo $wr_id ?>"><input type="hidden" name="sca" value="<?php echo $sca ?>"><input type="hidden" name="sfl" value="<?php echo $sfl ?>"><input type="hidden" name="stx" value="<?php echo $stx ?>"><input type="hidden" name="spt" value="<?php echo $spt ?>"><input type="hidden" name="sst" value="<?php echo $sst ?>"><input type="hidden" name="sod" value="<?php echo $sod ?>"><input type="hidden" name="page" value="<?php echo $page ?>">
<div class="write-form-group">
<label for="wr_subject" class="form-label">제목<span class="required">*</span></label>
<input type="text" name="wr_subject" value="<?php echo $subject ?>" id="wr_subject" required class="form-control" placeholder="제목을 입력하세요">
</div>
<?php if ($is_category): ?>
<div class="write-form-group">
<label for="ca_name" class="form-label">카테고리<span class="required">*</span></label>
<select name="ca_name" id="ca_name" required class="form-control"><option value="">선택하세요</option><?php echo $category_option ?></select>
</div>
<?php endif; ?>
<?php if (!empty($cfg_write['use_summary'])): ?>
<div class="write-form-group">
<label for="<?php echo $field_map['summary']; ?>" class="form-label"><?php echo $cfg_write['summary_label'] ?: '요약'; ?></label>
<textarea name="<?php echo $field_map['summary']; ?>" id="<?php echo $field_map['summary']; ?>" class="form-control" rows="3" placeholder="<?php echo $cfg_write['summary_placeholder']; ?>"><?php echo $write[$field_map['summary']]; ?></textarea>
</div>
<?php endif; ?>
<div class="write-form-group">
<label for="wr_content" class="form-label">내용<span class="required">*</span></label>
<div class="editor-toggle-buttons">
<button type="button" class="btn btn-sm btn-editor-toggle <?php echo $is_dhtml_editor ? 'active' : ''; ?>" data-editor-type="dhtml">HTML 에디터</button>
<button type="button" class="btn btn-sm btn-editor-toggle <?php echo $is_dhtml_editor ? '' : 'active'; ?>" data-editor-type="text">텍스트 에디터</button>
</div>
<div id="dhtml-editor-area" style="display: <?php echo $is_dhtml_editor ? 'block' : 'none'; ?>;">
<?php if ($is_dhtml_editor) { echo $editor_html; } ?>
</div>
<div id="text-editor-area" style="display: <?php echo $is_dhtml_editor ? 'none' : 'block'; ?>;">
<textarea id="wr_content_text" class="form-control" rows="10"><?php echo $content ?></textarea>
</div>
</div>
<hr class="form-divider">
<?php for ($i=1; $is_file && $i<=$board['bo_upload_count']; $i++):
$file_label = isset($cfg_write['file_labels'][$i]) ? $cfg_write['file_labels'][$i] : '첨부파일 #'.$i;
$file_text = isset($cfg_write['file_texts'][$i]) ? $cfg_write['file_texts'][$i] : '';
?>
<div class="write-form-group file-upload-group">
<label for="bf_file_<?php echo $i ?>" class="form-label"><?php echo $file_label; ?><?php if($i==1) echo '<span class="required">*</span>'; ?></label>
<div class="file-input-wrapper">
<input type="file" name="bf_file[]" id="bf_file_<?php echo $i ?>" title="파일첨부 <?php echo $i ?> : 용량 <?php echo $upload_max_filesize ?> 이하만 업로드 가능" class="form-control" <?php if ($w=='' && $i==1) echo 'required'; ?>>
<?php if ($w == 'u' && $file[$i-1]['file']): ?>
<span class="file-delete-wrap"><input type="checkbox" id="bf_file_del_<?php echo $i-1 ?>" name="bf_file_del[<?php echo $i-1; ?>]" value="1"><label for="bf_file_del_<?php echo $i-1 ?>"><?php echo $file[$i-1]['source'].'('.$file[$i-1]['size'].')'; ?> 파일 삭제</label></span>
<?php endif; ?>
</div>
<?php if($file_text): ?><p class="form-text"><?php echo $file_text; ?></p><?php endif; ?>
</div>
<?php endfor; ?>
<hr class="form-divider">
<?php if ($is_admin): ?>
<div class="admin-options-group">
<h3 class="form-section-title">관리자 전용 설정</h3>
<!-- <div class="admin-option-item">
<label class="form-check-label" for="is_main_display">
<input type="checkbox" name="<?php /*echo $field_map['main_display']; */?>" value="Y" id="is_main_display" class="admin-option-checkbox" <?php /*echo ($write[$field_map['main_display']] == 'Y') ? 'checked' : ''; */?>>
<span class="option-text">메인 화면 노출 (체크 시 메인 페이지 포트폴리오 영역에 노출됩니다)</span>
</label>
</div>-->
<div class="admin-option-item">
<label class="form-check-label" for="is_featured">
<input type="checkbox" name="<?php echo $field_map['featured']; ?>" value="Y" id="is_featured" class="admin-option-checkbox" <?php echo ($write[$field_map['featured']] == 'Y') ? 'checked' : ''; ?>>
<span class="option-text">이 글을 '지정 최신글'로 설정합니다. (최대 6개)</span>
</label>
<div id="ebook-link-group" style="display: <?php echo ($write[$field_map['featured']] == 'Y') ? 'block' : 'none'; ?>; margin-top: 15px;">
<label for="<?php echo $ebook_link_field; ?>" class="form-label" style="margin-top:0;">E-book 링크</label>
<input type="url" name="<?php echo $ebook_link_field; ?>" value="<?php echo $write[$ebook_link_field]; ?>" id="<?php echo $ebook_link_field; ?>" class="form-control" placeholder="https://example.com/ebook/123">
<p class="form-text">'지정 최신글'로 설정했을 경우, 클릭 시 이동할 이북 주소를 입력하세요.</p>
</div>
</div>
</div>
<?php endif; ?>
<div class="write-form-footer">
<a href="<?php echo get_pretty_url($bo_table); ?>" class="btn btn-secondary">취소</a>
<button type="submit" id="btn_submit" accesskey="s" class="btn btn-primary">작성완료</button>
</div>
</form>
</div>
<style>
/* 에디터 토글 버튼 스타일 */
.editor-toggle-buttons {
margin-bottom: 10px;
display: flex;
gap: 5px;
}
.btn-editor-toggle {
/* padding: 6px 12px; */
font-size: 13px;
border: 1px solid #ddd;
background-color: #f8f9fa;
color: #555;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.btn-editor-toggle:hover {
background-color: #e9ecef;
}
.btn-editor-toggle.active {
background-color: #4a90e2;
color: #fff;
border-color: #4a90e2;
font-weight: bold;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const isFeaturedCheckbox = document.getElementById('is_featured');
const ebookLinkGroup = document.getElementById('ebook-link-group');
if (isFeaturedCheckbox && ebookLinkGroup) {
isFeaturedCheckbox.addEventListener('change', function() {
ebookLinkGroup.style.display = this.checked ? 'block' : 'none';
});
}
const dhtmlEditorArea = document.getElementById('dhtml-editor-area');
const textEditorArea = document.getElementById('text-editor-area');
const editorToggleButtons = document.querySelectorAll('.btn-editor-toggle');
const wrContentTextarea = document.getElementById('wr_content_text');
const wrContentDhtmlTextarea = document.getElementById('wr_content');
function setEditorMode(isDhtml) {
if (isDhtml) {
dhtmlEditorArea.style.display = 'block';
textEditorArea.style.display = 'none';
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
CKEDITOR.instances.wr_content.setData(wrContentTextarea.value);
}
if (wrContentDhtmlTextarea) {
wrContentDhtmlTextarea.setAttribute('name', 'wr_content');
wrContentDhtmlTextarea.setAttribute('required', 'required');
}
wrContentTextarea.removeAttribute('name');
wrContentTextarea.removeAttribute('required');
} else {
dhtmlEditorArea.style.display = 'none';
textEditorArea.style.display = 'block';
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
wrContentTextarea.value = CKEDITOR.instances.wr_content.getData();
}
wrContentTextarea.setAttribute('name', 'wr_content');
wrContentTextarea.setAttribute('required', 'required');
if (wrContentDhtmlTextarea) {
wrContentDhtmlTextarea.removeAttribute('name');
wrContentDhtmlTextarea.removeAttribute('required');
}
}
editorToggleButtons.forEach(btn => {
if ((btn.dataset.editorType === 'dhtml' && isDhtml) || (btn.dataset.editorType === 'text' && !isDhtml)) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
}
setEditorMode(<?php echo $is_dhtml_editor ? 'true' : 'false'; ?>);
editorToggleButtons.forEach(button => {
button.addEventListener('click', function() {
const type = this.dataset.editorType;
setEditorMode(type === 'dhtml');
});
});
});
function fwrite_submit(f) {
<?php echo $editor_js; ?>
let finalContentValue = '';
const dhtmlEditorArea = document.getElementById('dhtml-editor-area');
if (dhtmlEditorArea.style.display === 'block') {
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
finalContentValue = CKEDITOR.instances.wr_content.getData();
} else if (document.getElementById('wr_content')) {
finalContentValue = document.getElementById('wr_content').value;
}
} else {
finalContentValue = document.getElementById('wr_content_text').value;
}
if (finalContentValue.trim() === '') {
alert('내용을 입력해주세요.');
if (dhtmlEditorArea.style.display === 'block' && typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
CKEDITOR.instances.wr_content.focus();
} else {
document.getElementById('wr_content_text').focus();
}
return false;
}
return true;
}
</script>
@@ -0,0 +1,264 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* rb.board.core.journal :: write.skin.php
* '저널' 타입 전용 글쓰기 폼
*/
$field_map = array(
'summary' => 'wr_1',
'featured' => 'wr_10',
'main_display' => 'wr_8', // 💡 [추가] 메인 화면 노출 필드 매핑
);
$ebook_link_field = 'wr_link1';
$cfg_write = isset($board_config['write']) ? $board_config['write'] : array();
?>
<style>
/* 에디터 토글 버튼 스타일 */
.editor-toggle-buttons {
margin-bottom: 10px;
display: flex;
gap: 5px;
}
.btn-editor-toggle {
/*padding: 6px 12px;*/
font-size: 13px;
border: 1px solid #ddd;
background-color: #f8f9fa;
color: #555;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.btn-editor-toggle:hover {
background-color: #e9ecef;
}
.btn-editor-toggle.active {
background-color: #4a90e2;
color: #fff;
border-color: #4a90e2;
font-weight: bold;
}
</style>
<div class="board-write-container">
<form name="fwrite" id="fwrite" action="<?php echo $action_url ?>" onsubmit="return fwrite_submit(this);" method="post" enctype="multipart/form-data" autocomplete="off" novalidate>
<input type="hidden" name="uid" value="<?php echo get_uniqid(); ?>">
<input type="hidden" name="w" value="<?php echo $w ?>"><input type="hidden" name="bo_table" value="<?php echo $bo_table ?>"><input type="hidden" name="wr_id" value="<?php echo $wr_id ?>"><input type="hidden" name="sca" value="<?php echo $sca ?>"><input type="hidden" name="sfl" value="<?php echo $sfl ?>"><input type="hidden" name="stx" value="<?php echo $stx ?>"><input type="hidden" name="spt" value="<?php echo $spt ?>"><input type="hidden" name="sst" value="<?php echo $sst ?>"><input type="hidden" name="sod" value="<?php echo $sod ?>"><input type="hidden" name="page" value="<?php echo $page ?>">
<!-- 💡 [핵심] HTML 에디터 사용 여부를 서버에 알리기 위한 필드 -->
<input type="hidden" name="html" value="<?php echo $html ?>">
<div class="write-form-group">
<label for="wr_subject" class="form-label">제목<span class="required">*</span></label>
<input type="text" name="wr_subject" value="<?php echo $subject ?>" id="wr_subject" required class="form-control" placeholder="제목을 입력하세요">
</div>
<?php if ($is_category): ?>
<div class="write-form-group">
<label for="ca_name" class="form-label">카테고리<span class="required">*</span></label>
<select name="ca_name" id="ca_name" required class="form-control"><option value="">선택하세요</option><?php echo $category_option ?></select>
</div>
<?php endif; ?>
<?php if (!empty($cfg_write['use_summary'])): ?>
<div class="write-form-group">
<label for="<?php echo $field_map['summary']; ?>" class="form-label"><?php echo $cfg_write['summary_label'] ?: '요약'; ?></label>
<textarea name="<?php echo $field_map['summary']; ?>" id="<?php echo $field_map['summary']; ?>" class="form-control" rows="3" placeholder="<?php echo $cfg_write['summary_placeholder']; ?>"><?php echo $write[$field_map['summary']]; ?></textarea>
</div>
<?php endif; ?>
<div class="write-form-group">
<label for="wr_content" class="form-label">내용</label>
<div class="editor-toggle-buttons">
<button type="button" class="btn btn-sm btn-editor-toggle <?php echo $is_dhtml_editor ? 'active' : ''; ?>" data-editor-type="dhtml">HTML 에디터</button>
<button type="button" class="btn btn-sm btn-editor-toggle <?php echo $is_dhtml_editor ? '' : 'active'; ?>" data-editor-type="text">텍스트 에디터</button>
</div>
<!-- HTML 에디터 영역 -->
<div id="dhtml-editor-area" style="display: <?php echo $is_dhtml_editor ? 'block' : 'none'; ?>;">
<?php if ($is_dhtml_editor) { echo $editor_html; } ?>
</div>
<!-- 텍스트 에디터 영역 -->
<div id="text-editor-area" style="display: <?php echo $is_dhtml_editor ? 'none' : 'block'; ?>;">
<!-- 💡 [수정] 텍스트 에디터의 ID와 name을 명확히 구분 -->
<!-- 초기 로드 시 HTML 에디터가 활성화되어 있으면 name 속성을 제거해야 충돌 방지 -->
<textarea id="wr_content_text" class="form-control" rows="10" <?php echo $is_dhtml_editor ? '' : 'name="wr_content"'; ?>><?php echo $content ?></textarea>
</div>
</div>
<hr class="form-divider">
<?php for ($i=1; $is_file && $i<=$board['bo_upload_count']; $i++):
$file_label = isset($cfg_write['file_labels'][$i]) ? $cfg_write['file_labels'][$i] : '첨부파일 #'.$i;
$file_text = isset($cfg_write['file_texts'][$i]) ? $cfg_write['file_texts'][$i] : '';
?>
<div class="write-form-group file-upload-group">
<label for="bf_file_<?php echo $i ?>" class="form-label"><?php echo $file_label; ?><?php if($i==1) echo '<span class="required">*</span>'; ?></label>
<div class="file-input-wrapper">
<input type="file" name="bf_file[]" id="bf_file_<?php echo $i ?>" title="파일첨부 <?php echo $i ?> : 용량 <?php echo $upload_max_filesize ?> 이하만 업로드 가능" class="form-control" <?php if ($w=='' && $i==1) echo 'required'; ?>>
<?php if ($w == 'u' && $file[$i-1]['file']): ?>
<span class="file-delete-wrap"><input type="checkbox" id="bf_file_del_<?php echo $i-1 ?>" name="bf_file_del[<?php echo $i-1; ?>]" value="1"><label for="bf_file_del_<?php echo $i-1 ?>"><?php echo $file[$i-1]['source'].'('.$file[$i-1]['size'].')'; ?> 파일 삭제</label></span>
<?php endif; ?>
</div>
<?php if($file_text): ?><p class="form-text"><?php echo $file_text; ?></p><?php endif; ?>
</div>
<?php endfor; ?>
<hr class="form-divider">
<?php if ($is_admin): ?>
<div class="admin-options-group">
<h3 class="form-section-title">관리자 전용 설정</h3>
<div class="admin-option-item">
<label class="form-check-label" for="is_featured">
<input type="checkbox" name="<?php echo $field_map['featured']; ?>" value="Y" id="is_featured" class="admin-option-checkbox" <?php echo ($write[$field_map['featured']] == 'Y') ? 'checked' : ''; ?>>
<span class="option-text">이 글을 '지정 최신글'로 설정합니다. (최대 6개)</span>
</label>
<div id="ebook-link-group" style="display: <?php echo ($write[$field_map['featured']] == 'Y') ? 'block' : 'none'; ?>; margin-top: 15px;">
<label for="<?php echo $ebook_link_field; ?>" class="form-label" style="margin-top:0;">E-book 링크</label>
<input type="url" name="<?php echo $ebook_link_field; ?>" value="<?php echo $write[$ebook_link_field]; ?>" id="<?php echo $ebook_link_field; ?>" class="form-control" placeholder="https://example.com/ebook/123">
<p class="form-text">'지정 최신글'로 설정했을 경우, 클릭 시 이동할 이북 주소를 입력하세요.</p>
</div>
</div>
</div>
<?php endif; ?>
<div class="write-form-footer">
<a href="<?php echo get_pretty_url($bo_table); ?>" class="btn btn-secondary">취소</a>
<button type="submit" id="btn_submit" accesskey="s" class="btn btn-primary">작성완료</button>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const isFeaturedCheckbox = document.getElementById('is_featured');
const ebookLinkGroup = document.getElementById('ebook-link-group');
if (isFeaturedCheckbox && ebookLinkGroup) {
isFeaturedCheckbox.addEventListener('change', function() {
ebookLinkGroup.style.display = this.checked ? 'block' : 'none';
});
}
const dhtmlEditorArea = document.getElementById('dhtml-editor-area');
const textEditorArea = document.getElementById('text-editor-area');
const editorToggleButtons = document.querySelectorAll('.btn-editor-toggle');
const wrContentTextarea = document.getElementById('wr_content_text');
const wrContentDhtmlTextarea = document.getElementById('wr_content'); // 그누보드 에디터가 생성하는 textarea
const htmlInput = document.querySelector('input[name="html"]'); // html 모드 저장용
function setEditorMode(isDhtml) {
if (isDhtml) {
// HTML 에디터 모드
dhtmlEditorArea.style.display = 'block';
textEditorArea.style.display = 'none';
// 텍스트 에디터의 내용을 HTML 에디터로 복사 (가능한 경우)
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
CKEDITOR.instances.wr_content.setData(wrContentTextarea.value);
}
// name 속성 조정: HTML 에디터가 wr_content를 사용하도록
if (wrContentDhtmlTextarea) {
wrContentDhtmlTextarea.setAttribute('name', 'wr_content');
}
wrContentTextarea.removeAttribute('name');
// html 필드 값 설정 (html1 또는 html2)
if (htmlInput) htmlInput.value = 'html1';
} else {
// 텍스트 에디터 모드
dhtmlEditorArea.style.display = 'none';
textEditorArea.style.display = 'block';
// HTML 에디터의 내용을 텍스트 에디터로 복사
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
wrContentTextarea.value = CKEDITOR.instances.wr_content.getData();
} else if (wrContentDhtmlTextarea) {
wrContentTextarea.value = wrContentDhtmlTextarea.value;
}
// name 속성 조정: 텍스트 에디터가 wr_content를 사용하도록
wrContentTextarea.setAttribute('name', 'wr_content');
if (wrContentDhtmlTextarea) {
wrContentDhtmlTextarea.removeAttribute('name');
}
// html 필드 값 초기화 (텍스트 모드)
if (htmlInput) htmlInput.value = '';
}
// 버튼 스타일 업데이트
editorToggleButtons.forEach(btn => {
if ((btn.dataset.editorType === 'dhtml' && isDhtml) || (btn.dataset.editorType === 'text' && !isDhtml)) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
}
// 초기 모드 설정
setEditorMode(<?php echo $is_dhtml_editor ? 'true' : 'false'; ?>);
editorToggleButtons.forEach(button => {
button.addEventListener('click', function() {
const type = this.dataset.editorType;
setEditorMode(type === 'dhtml');
});
});
});
function fwrite_submit(f) {
// 그누보드 에디터 내용 동기화 (필수)
<?php echo $editor_js; ?>
let content = '';
const dhtmlEditorArea = document.getElementById('dhtml-editor-area');
// 현재 활성화된 에디터 확인
if (dhtmlEditorArea && dhtmlEditorArea.style.display === 'block') {
// HTML 에디터 모드
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.wr_content) {
content = CKEDITOR.instances.wr_content.getData();
} else if (document.getElementById('wr_content')) {
content = document.getElementById('wr_content').value;
}
} else {
// 텍스트 에디터 모드
content = document.getElementById('wr_content_text').value;
}
// 내용 유효성 검사 (태그 제거 후 확인)
let contentCheck = content.replace(/<[^>]*>/g, '').replace(/&nbsp;/g, '').trim();
// 파일 첨부 여부 확인
let hasFile = false;
const fileInputs = document.querySelectorAll('input[name="bf_file[]"]');
for (let i = 0; i < fileInputs.length; i++) {
if (fileInputs[i].value) {
hasFile = true;
break;
}
}
// 수정 모드일 때 기존 파일이 있는지 확인 (삭제 체크박스가 있으면 파일이 있는 것임)
const fileDeletes = document.querySelectorAll('input[name^="bf_file_del"]');
if (fileDeletes.length > 0) {
hasFile = true;
}
if (contentCheck === '' && !hasFile) {
alert('내용을 입력하거나 파일을 첨부해주세요.');
return false;
}
return true;
}
</script>