// 외부 jQuery 및 플러그인 로드 함수
function loadFunctionCSS(hrefList){
hrefList.forEach(function (href) {
var cssLink = $(" ").attr({
type: "text/css",
rel: "stylesheet",
href: href
});
$("head").append(cssLink);
});
}
function execDocReady() {
var pluginGroups = [
["../reference/light-blue/lib/vendor/jquery.ui.widget.js", "../reference/lightblue4/docs/lib/widgster/widgster.js"],
["../reference/lightblue4/docs/lib/bootstrap-select/dist/js/bootstrap-select.min.js"]
];
loadPluginGroupsParallelAndSequential(pluginGroups)
.then(function () {
loadFunctionCSS([
"/cover/css/function/layout.css",
"/cover/css/experience/experience-common.css",
"/cover/css/experience/experience-detail.css"
]);
$(".widget").widgster();
$("#sidebar").hide();
$(".wrap").css("margin-left", 0);
$("#footer").load("/cover/html/template/landing-footer.html");
})
.catch(function (error) {
console.error("플러그인 로드 중 오류 발생");
console.error(error);
});
}
// Tiny Confirm Modal
function ensureTinyModalLoaded() {
return new Promise((resolve) => {
const ready = () => resolve(true);
if (document.getElementById('tiny-modal')) return ready();
if (!document.getElementById('modal-root')) {
const div = document.createElement('div');
div.id = 'modal-root';
document.body.appendChild(div);
}
$('#modal-root').load('/cover/html/experience/components/confirm-modal.html', () => ready());
});
}
async function tinyConfirm({
title='확인',
message='진행하시겠습니까?',
okText='확인',
cancelText='취소',
okType='primary' // 'primary', 'danger', 'success'
} = {}) {
await ensureTinyModalLoaded();
const modal = document.getElementById('tiny-modal');
const titleEl = document.getElementById('tiny-modal-title');
const msgEl = document.getElementById('tiny-modal-message');
const okBtn = document.getElementById('tiny-modal-ok');
const cancelBtn = document.getElementById('tiny-modal-cancel');
const closeBtn = document.getElementById('tiny-modal-close');
const backdrop = modal.querySelector('.tiny-modal-backdrop');
titleEl.textContent = title;
msgEl.innerHTML = message;
okBtn.textContent = okText;
cancelBtn.textContent = cancelText;
// 기존 클래스 제거 후 새 클래스 추가
okBtn.className = '';
okBtn.classList.add('modal-btn-ok', `modal-btn-${okType}`);
modal.classList.add('is-open');
document.body.style.overflow = 'hidden';
return new Promise((resolve) => {
const close = (result) => {
modal.classList.remove('is-open');
document.body.style.overflow = '';
cleanup();
resolve(result);
};
const onOk = () => close(true);
const onCancel = () => close(false);
const onEsc = (e) => { if (e.key === 'Escape') close(false); };
const onBackdrop = (e) => { if (e.target === backdrop) close(false); };
function cleanup(){
okBtn.removeEventListener('click', onOk);
cancelBtn.removeEventListener('click', onCancel);
closeBtn.removeEventListener('click', onCancel);
document.removeEventListener('keydown', onEsc);
backdrop.removeEventListener('click', onBackdrop);
}
okBtn.addEventListener('click', onOk);
cancelBtn.addEventListener('click', onCancel);
closeBtn.addEventListener('click', onCancel);
document.addEventListener('keydown', onEsc);
backdrop.addEventListener('click', onBackdrop);
});
}
/* ============================================================
* 디테일 페이지: 백엔드에서 단건 조회해 렌더
* ============================================================ */
(function () {
'use strict';
// 전역 변수
var isAdmin = false;
// --- 유틸리티 및 헬퍼 함수 ---
function getParam(name) {
return new URL(location.href).searchParams.get(name);
}
function escapeHTML(s) {
return String(s || "").replace(/[&<>"']/g, m => ({
"&": "&", "<": "<", ">": ">", "\"": """, "'": "'"
}[m]));
}
function buildPocHref() {
const url = new URL(location.href);
if (url.searchParams.get('page')) {
url.searchParams.set('page', 'poc');
url.searchParams.delete('id');
return url.toString();
}
return 'poc.html';
}
function redirectToExperienceList() {
const url = new URL(location.href);
if (url.searchParams.get('page')) {
url.searchParams.set('page', 'experience');
url.searchParams.delete('id');
window.location.href = url.toString();
} else {
window.location.href = 'experience.html';
}
}
/* ---------- 권한 체크 ---------- */
function experienceDetailAuthCheck() {
return $.ajax({
url: "/auth-user/me",
type: "GET",
timeout: 7313,
global: false,
statusCode: {
200: function (json) {
console.log("[ experienceDetail :: authCheck ] userName = " + json.preferred_username);
console.log("[ experienceDetail :: authCheck ] roles = " + json.realm_access.roles);
const hasAdminPermission = json.realm_access.roles.includes("ROLE_ADMIN");
if (hasAdminPermission) {
isAdmin = true;
showAdminButtons();
} else {
isAdmin = false;
hideAdminButtons();
}
},
401: function () {
// 비로그인 사용자
console.log("비로그인 사용자 - 수정/삭제 버튼 숨김");
isAdmin = false;
hideAdminButtons();
}
}
});
}
function showAdminButtons() {
$("#btn-modify-experience").show();
$("#btn-delete-experience").show();
console.log("관리자 권한 확인 - 수정/삭제 버튼 표시");
}
function hideAdminButtons() {
$("#btn-modify-experience").hide();
$("#btn-delete-experience").hide();
console.log("수정/삭제 버튼 숨김");
}
// --- HTML 템플릿 함수 ---
function shareBlockHtml() {
return `
`;
}
function heroBlockHtml(src) {
const safeSrc = src || '/cover/img/logo.png';
return `
`;
}
// 이미지 비율에 따라 object-fit 조정
function adjustDetailImageObjectFit() {
$('.detail-hero-image').each(function() {
const img = this;
if (img.complete && img.naturalWidth > 0) {
applyDetailObjectFit(img);
} else {
$(img).on('load', function() {
applyDetailObjectFit(this);
});
}
});
}
function applyDetailObjectFit(img) {
const ratio = img.naturalWidth / img.naturalHeight;
// 16:10 비율 = 1.6, 허용 범위 1.4 ~ 1.8 (16:9 ≈ 1.78 포함)
if (ratio >= 1.4 && ratio <= 1.8) {
// 가로로 긴 이미지: cover 사용
img.style.objectFit = 'cover';
img.style.maxHeight = 'none';
} else {
// 세로로 긴 이미지나 특이한 비율: contain
img.style.objectFit = 'contain';
img.style.maxHeight = '730px';
}
}
// --- 소셜 미디어 공유 함수 ---
async function shareToSocial(platform, title, url, description, thumbnailUrl) {
const encodedUrl = encodeURIComponent(url);
const encodedTitle = encodeURIComponent(title);
const encodedDesc = encodeURIComponent(description || '');
let shareUrl = '';
switch(platform) {
case 'facebook':
// Facebook Share Dialog
shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`;
break;
case 'twitter':
// Twitter/X Share
const twitterText = encodeURIComponent(`${title}`);
shareUrl = `https://twitter.com/intent/tweet?text=${twitterText}&url=${encodedUrl}`;
break;
case 'linkedin':
// LinkedIn Share (제목, 설명, 출처 포함)
const source = encodeURIComponent('313devgrp');
shareUrl = `https://www.linkedin.com/shareArticle?mini=true&url=${encodedUrl}&title=${encodedTitle}&summary=${encodedDesc}&source=${source}`;
break;
case 'link':
// 링크 복사
navigator.clipboard.writeText(url).then(() => {
jSuccess('링크가 복사되었습니다!');
}).catch(() => {
jError('링크 복사에 실패했습니다.');
});
return;
default:
return;
}
if (shareUrl) {
window.open(shareUrl, '_blank');
}
}
// --- API 호출 함수 ---
function fetchExperienceById(id) {
return $.ajax({
url: `/auth-anon/api/cover/experience/getExperience/${encodeURIComponent(id)}`,
type: 'GET'
}).then(function(res){
if (res && res.success && res.response) {
return res.response;
}
});
}
function removeExperienceById(id) {
return $.ajax({
url: `/auth-anon/api/cover/experience/removeExperience/${encodeURIComponent(id)}`,
type: 'DELETE'
});
}
// --- Open Graph 태그 업데이트 함수 ---
function updateOpenGraphTags(title, description, imageUrl, url) {
// 기존 OG 태그 제거
$('meta[property^="og:"]').remove();
$('meta[name^="twitter:"]').remove();
// 이미지 URL 절대 경로로 변환
let absoluteImageUrl = imageUrl;
if (imageUrl && !imageUrl.startsWith('http')) {
absoluteImageUrl = window.location.origin + imageUrl;
}
// 새 OG 태그 추가
const ogTags = [
{ property: 'og:type', content: 'article' },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
{ property: 'og:image', content: absoluteImageUrl },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ property: 'og:url', content: url },
{ property: 'og:site_name', content: '313devgrp' },
{ property: 'og:locale', content: 'ko_KR' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: title },
{ name: 'twitter:description', content: description },
{ name: 'twitter:image', content: absoluteImageUrl },
{ name: 'author', content: '313devgrp' }
];
ogTags.forEach(tag => {
const meta = $(' ');
if (tag.property) meta.attr('property', tag.property);
if (tag.name) meta.attr('name', tag.name);
meta.attr('content', tag.content);
$('head').append(meta);
});
}
// --- 렌더링 함수 ---
function renderDetail($root, entity) {
const id = entity.c_id;
const title = entity.c_clientcase_title;
const subtitle = entity.c_clientcase_subtitle || '';
const category = entity.c_clientcase_category || 'client_case';
const rawDateObject = entity.c_clientcase_created;
let date = '';
// 생성일자
if (rawDateObject && typeof rawDateObject === 'object' && rawDateObject.year) {
const year = rawDateObject.year;
const month = String(rawDateObject.monthValue).padStart(2, '0');
const day = String(rawDateObject.dayOfMonth).padStart(2, '0');
date = `${year}-${month}-${day}`;
}
const thumb = entity.c_clientcase_thumbnail_image;
const contents = entity.c_clientcase_contents || '';
// 설명 텍스트 추출 (HTML 태그 제거)
const tempDiv = $('').html(contents);
const description = tempDiv.text().substring(0, 200).trim() + '...';
// Open Graph 태그 업데이트
updateOpenGraphTags(
title,
description,
thumb || '/cover/img/logo.png',
window.location.href
);
const header = `
${escapeHTML(title || "")}
${subtitle ? `
${escapeHTML(subtitle)}
` : ''}
${shareBlockHtml()}
`;
const body = `
${heroBlockHtml(thumb)}
`;
const footer = `
글이 마음에 드셨나요?
공유하기
${shareBlockHtml()}
`;
$root
.data('currentItem', {
id,
title,
description,
thumbnail: thumb
})
.html(header + body + footer);
// 이미지 로드 후 비율에 따라 object-fit 조정
adjustDetailImageObjectFit();
}
// --- 자동 마운트 ---
$(function () {
console.log('experienceDetail] Init');
// DOM이 완전히 로드될 때까지 대기
function waitForElement() {
const $host = $('#experience-detail');
if (!$host.length) {
console.log("experienceDetail not found ==> retrying");
setTimeout(waitForElement, 100); // 100ms 후 재시도
return;
}
const id = parseInt(getParam('id'), 10);
if (!id) {
$host.html('
');
return;
}
hideAdminButtons();
// 권한 체크 먼저 실행
experienceDetailAuthCheck().always(function() {
// 권한 체크 완료 후 데이터 로드
fetchExperienceById(id)
.then(entity => {
if (!entity || !entity.c_id) {
$host.html('
');
return;
}
renderDetail($host, entity);
})
.catch(err => {
console.error(err);
$host.html('
');
});
});
}
// 초기 실행
waitForElement();
// 이벤트 리스너: 공유 버튼
$(document).on('click', '.share-btn', function(e) {
e.preventDefault();
const platform = $(this).data('platform');
const $host = $('#experience-detail');
const item = $host.data('currentItem');
if (!item) {
jError('공유할 콘텐츠 정보를 찾을 수 없습니다.');
return;
}
const currentUrl = window.location.href;
const title = item.title || 'A-RMS 경험 공유';
const description = item.description || 'A-RMS의 프로젝트 경험을 확인해보세요.';
const thumbnailUrl = item.thumbnail;
shareToSocial(platform, title, currentUrl, description, thumbnailUrl);
});
// 이벤트 리스너: 목록으로 버튼
$(document).on('click', '#btn-back-to-list', function () {
redirectToExperienceList();
});
// 이벤트 리스너: 수정 버튼
$(document).on('click', '#btn-modify-experience', function () {
if (!isAdmin) {
jError('권한이 없습니다.');
return;
}
const $host = $('#experience-detail');
const item = $host.data('currentItem');
if (!item) {
jError('콘텐츠 정보를 찾을 수 없습니다.');
return;
}
sessionStorage.setItem('experienceEditDraft', JSON.stringify({
id: item.id,
title: item.title || ''
}));
const url = new URL(location.href);
url.searchParams.set('page', 'experienceEditor');
url.searchParams.set('id', String(item.id));
location.href = url.toString();
});
// 이벤트 리스너: 삭제 버튼
$(document).on('click', '#btn-delete-experience', async function () {
if (!isAdmin) {
jError('권한이 없습니다.');
return;
}
const $btn = $(this);
const $host = $('#experience-detail');
const item = $host.data('currentItem');
if (!item) {
jError('콘텐츠 정보를 찾을 수 없습니다.');
return;
}
const ok = await tinyConfirm({
title: '삭제 확인',
message: '정말 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',
okText: '삭제',
cancelText: '취소',
okType: 'danger'
});
if (!ok) return;
$btn.prop('disabled', true);
try {
const res = await removeExperienceById(item.id);
if (res?.success) {
jSuccess('삭제되었습니다.');
redirectToExperienceList();
} else {
jError('삭제는 되었지만 응답 포맷을 확인하세요.');
}
} catch (err) {
console.error(err);
jError('삭제 중 오류가 발생했습니다.');
} finally {
$btn.prop('disabled', false);
}
});
// 이벤트 리스너: 카테고리 클릭
$(document).on('click', '.meta-cat', function(e) {
e.preventDefault();
redirectToExperienceList();
});
//이벤트 리스너 : poc이동하기 버튼
$(document).on('click', '#btn-apply', function(e) {
e.preventDefault();
window.location.href = buildPocHref();
});
});
})();