...
This commit is contained in:
@@ -100,4 +100,7 @@ logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
|
||||
server.tomcat.connection-timeout=60s
|
||||
# For reactive applications (like yours), also set this timeout
|
||||
spring.webflux.response-timeout=60s
|
||||
api.base-url=ss
|
||||
api.base-url=ss
|
||||
build.config.run=local
|
||||
jwt.secret=your-very-long-and-super-secret-key-for-jwt-that-is-at-least-64-characters-long
|
||||
jwt.expiration=86400000
|
||||
@@ -100,4 +100,7 @@ logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
|
||||
server.tomcat.connection-timeout=60s
|
||||
# For reactive applications (like yours), also set this timeout
|
||||
spring.webflux.response-timeout=60s
|
||||
api.base-url=ss
|
||||
api.base-url=ss
|
||||
build.config.run=prd
|
||||
jwt.secret=your-very-long-and-super-secret-key-for-jwt-that-is-at-least-64-characters-long
|
||||
jwt.expiration=86400000
|
||||
@@ -100,4 +100,8 @@ logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
|
||||
server.tomcat.connection-timeout=60s
|
||||
# For reactive applications (like yours), also set this timeout
|
||||
spring.webflux.response-timeout=60s
|
||||
api.base-url=ss
|
||||
api.base-url=ss
|
||||
|
||||
build.config.run=local
|
||||
jwt.secret=your-very-long-and-super-secret-key-for-jwt-that-is-at-least-64-characters-long
|
||||
jwt.expiration=86400000
|
||||
@@ -233,6 +233,8 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
closePopup();
|
||||
});
|
||||
}
|
||||
|
||||
checkUnreadMessages(); // 함수 호출 추가
|
||||
});
|
||||
/* --- (DOMContentLoaded 끝) --- */
|
||||
|
||||
@@ -815,6 +817,7 @@ function gotoHome() { document.location.replace(`${getMainPath()}/home.bs`); }
|
||||
function gotoWrite() { document.location.replace(`${getMainPath()}/blog/edit`); }
|
||||
function gotoModify() { document.location.replace(`${getMainPath()}/blog/posts`); }
|
||||
function gotoWhere() { document.location.replace(`${getMainPath()}/bums/where.bs`); }
|
||||
function gotoBUMSpace() { document.location.replace(`${getMainPath()}/bums/face.bs`); }
|
||||
function gotoJoin() { document.location.replace(`${getMainPath()}/user/join.bs`); }
|
||||
// [추가] 네모로직 업로드 페이지로 이동하는 함수
|
||||
function gotoPuzzleUpload() { document.location.replace(`${getMainPath()}/puzzle/upload.bs`); }
|
||||
@@ -1551,4 +1554,227 @@ async function showConfirm(title, text) {
|
||||
cancelButtonText: '취소'
|
||||
});
|
||||
return result.isConfirmed;
|
||||
}
|
||||
function sendTlg(form, type,keyword) {
|
||||
console.log(form)
|
||||
let data = {
|
||||
'name': form.querySelector("#name").value,
|
||||
'email': form.querySelector("#email").value,
|
||||
'message': form.querySelector("#message").value,
|
||||
}
|
||||
if (data.name != null && data.email != null && data.message != null && data.message.length > 0) {
|
||||
if(confirm(JSON.stringify(data) + "\n해당 내용으로\n메시지 보내쉴?")) {
|
||||
post(getMainPath()+"/tlg/repotToMe.bjx",type,JSON.stringify(data),keyword, function (resultData) {
|
||||
showAlert("서버에 전달됨.")
|
||||
})
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
async function checkUnreadMessages() {
|
||||
const isLoggedIn = !!document.querySelector('a[href="javascript:logout()"]');
|
||||
if (!isLoggedIn) return; // 비로그인 상태면 실행 중단
|
||||
|
||||
try {
|
||||
const response = await fetch('/messages/unread-count');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.count > 0) {
|
||||
const icon = document.getElementById('message-icon');
|
||||
if (icon) {
|
||||
icon.style.display = 'inline-block'; // 아이콘 표시
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check for unread messages:', error);
|
||||
}
|
||||
}
|
||||
function handleBookmarkVote(buttonElement, voteType) {
|
||||
const controls = buttonElement.closest('.vote-controls');
|
||||
const bookmarkId = controls.dataset.bookmarkId;
|
||||
controls.querySelectorAll('button').forEach(btn => btn.disabled = true); // 중복 클릭 방지
|
||||
|
||||
// [수정] 북마크용 API 엔드포인트 사용
|
||||
const url = `${getMainPath()}/bookmarks/${bookmarkId}/${voteType === 'like' ? 'like' : 'unlike'}`;
|
||||
|
||||
// CSRF 토큰 준비
|
||||
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
|
||||
const headers = { 'X-CSRF-TOKEN': csrfToken };
|
||||
|
||||
fetch(url, { method: 'POST', headers: headers })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
controls.querySelector('.like-count').innerText = data.voteCount;
|
||||
controls.querySelector('.unlike-count').innerText = data.unlikeCount;
|
||||
})
|
||||
.catch(error => console.error('Error handling bookmark vote:', error))
|
||||
.finally(() => {
|
||||
controls.querySelectorAll('button').forEach(btn => btn.disabled = false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 북마크의 댓글 섹션을 열거나 닫습니다.
|
||||
*/
|
||||
function toggleCommentSection(bookmarkId) {
|
||||
const section = document.getElementById(`comment-section-${bookmarkId}`);
|
||||
if (section.style.display === 'none') {
|
||||
section.style.display = 'block';
|
||||
fetchBookmarkComments(bookmarkId); // 처음 열 때 댓글 로드
|
||||
} else {
|
||||
section.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 북마크의 댓글 목록을 불러옵니다.
|
||||
*/
|
||||
async function fetchBookmarkComments(bookmarkId) {
|
||||
const listContainer = document.getElementById(`comments-list-${bookmarkId}`);
|
||||
listContainer.innerHTML = '댓글 로딩 중...';
|
||||
|
||||
const response = await fetch(`${getMainPath()}/bookmarks/${bookmarkId}/comments`);
|
||||
const data = await response.json();
|
||||
|
||||
listContainer.innerHTML = '';
|
||||
if (data.resultCode === 0 && data.comments.length > 0) {
|
||||
data.comments.forEach(comment => {
|
||||
// 기존 블로그 댓글 HTML 생성 함수 재사용
|
||||
listContainer.innerHTML += createCommentHTML(comment);
|
||||
});
|
||||
} else {
|
||||
listContainer.innerHTML = '아직 댓글이 없습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 북마크에 댓글을 등록합니다.
|
||||
*/
|
||||
function submitBookmarkComment(bookmarkId) {
|
||||
const input = document.getElementById(`comment-input-${bookmarkId}`);
|
||||
const content = input.value.trim();
|
||||
if (!content) {
|
||||
showAlert('알림', '댓글 내용을 입력하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 블로그 댓글과 동일한 DTO 및 암호화 방식 사용
|
||||
const commentData = { content: content, parentId: null };
|
||||
const uploadUrl = `${getMainPath()}/bookmarks/${bookmarkId}/comments`;
|
||||
|
||||
// 기존 `post` 유틸리티 함수를 재사용하여 서버에 전송
|
||||
post(uploadUrl, serverData.enc, JSON.stringify(commentData), serverData.keyword, (resultData) => {
|
||||
const response = JSON.parse(resultData);
|
||||
if (response.resultCode === 0) {
|
||||
input.value = '';
|
||||
fetchBookmarkComments(bookmarkId); // 댓글 목록 새로고침
|
||||
} else {
|
||||
showAlert('오류', '댓글 등록에 실패했습니다: ' + response.resultMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 북마크 클릭 시 사용자에게 선택지를 보여주는 함수
|
||||
* @param {HTMLElement} element - 클릭된 <a> 요소
|
||||
*/
|
||||
async function showBookmarkOptions(element) {
|
||||
const url = element.dataset.url;
|
||||
const title = element.dataset.title;
|
||||
|
||||
const result = await Swal.fire({
|
||||
title: '어떻게 보시겠어요?',
|
||||
text: title,
|
||||
icon: 'question',
|
||||
showDenyButton: true,
|
||||
confirmButtonText: '새 탭에서 열기',
|
||||
denyButtonText: '여기서 보기 (Iframe)',
|
||||
confirmButtonColor: '#3085d6',
|
||||
denyButtonColor: '#555',
|
||||
});
|
||||
|
||||
if (result.isConfirmed) {
|
||||
// '새 탭에서 열기' 선택 시
|
||||
window.open(url, '_blank');
|
||||
} else if (result.isDenied) {
|
||||
// '여기서 보기 (Iframe)' 선택 시
|
||||
openBookmarkInIframe(url, title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iframe 로드 실패 시 일관된 처리를 위한 헬퍼 함수
|
||||
* @param {string} title - 북마크 제목
|
||||
* @param {string} url - 북마크 URL
|
||||
*/
|
||||
function handleIframeLoadFailure(title, url) {
|
||||
closePopup(); // 팝업 닫기
|
||||
if (confirm(`'${title}' 페이지를 내부에서 여는 데 실패했습니다.\n\n새 탭에서 여시겠습니까?`)) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 지정된 URL을 Iframe 팝업으로 여는 함수 (try-catch 로직 적용)
|
||||
* @param {string} url - 표시할 URL
|
||||
* @param {string} title - 표시할 제목
|
||||
*/
|
||||
function openBookmarkInIframe(url, title) {
|
||||
const popup = document.getElementById('iframe-viewer-popup');
|
||||
const titleElement = document.getElementById('iframe-viewer-title');
|
||||
const iframe = document.getElementById('bookmark-iframe');
|
||||
const overlay = document.querySelector('.dim_layer');
|
||||
const newTabLink = document.getElementById('iframe-open-new-tab-link');
|
||||
|
||||
if (!popup || !titleElement || !iframe || !overlay || !newTabLink) {
|
||||
console.error('Iframe viewer elements not found!');
|
||||
return;
|
||||
}
|
||||
|
||||
// iframe의 로딩을 시작하기 전에 src를 초기화하여 이전 상태를 지웁니다.
|
||||
iframe.src = 'about:blank';
|
||||
|
||||
// iframe의 onload 이벤트 핸들러
|
||||
iframe.onload = () => {
|
||||
console.log("iframe onload 이벤트 발생. 내부 문서 접근을 시도합니다...");
|
||||
|
||||
try {
|
||||
// 동일 출처 정책(Same-Origin Policy)을 위반하는 접근 시도
|
||||
// 이 코드가 오류를 발생시키면, 다른 출처의 문서가 로드된 것 (성공 또는 오류 페이지)
|
||||
const dummyAccess = iframe.contentWindow.location.href;
|
||||
|
||||
// 만약 위 코드에서 오류가 발생하지 않았다면, iframe이 동일 출처이거나 비어있다는 의미.
|
||||
// 외부 사이트 로드는 실패한 것으로 간주합니다.
|
||||
console.warn("iframe 접근이 차단되지 않았습니다. 로드 실패로 간주합니다.");
|
||||
handleIframeLoadFailure(title, url);
|
||||
|
||||
} catch (e) {
|
||||
|
||||
// SecurityError가 발생! 다른 출처의 문서가 성공적으로 로드되었다고 간주합니다.
|
||||
// (이것이 실제 콘텐츠일 수도, 브라우저의 오류 페이지일 수도 있습니다)
|
||||
console.log("iframe 접근이 보안 정책에 의해 차단되었습니다. 일단 성공으로 간주합니다.", e);
|
||||
// 팝업을 그대로 유지
|
||||
}
|
||||
};
|
||||
|
||||
// 네트워크 오류 등으로 iframe 로드 자체가 실패했을 때를 위한 핸들러
|
||||
iframe.onerror = () => {
|
||||
console.error("iframe onerror 이벤트 발생. 로드 실패로 처리합니다.");
|
||||
handleIframeLoadFailure(title, url);
|
||||
};
|
||||
|
||||
// 제목과 새 탭 링크 설정
|
||||
titleElement.textContent = title;
|
||||
newTabLink.href = url;
|
||||
|
||||
// 실제 URL로 로딩 시작
|
||||
iframe.src = url;
|
||||
|
||||
// 팝업과 오버레이 표시
|
||||
overlay.style.display = 'block';
|
||||
popup.style.display = 'block';
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/default_layout}">
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="content" id="content">
|
||||
<section class="wrapper style2">
|
||||
<div class="container">
|
||||
<header class="major">
|
||||
<h2 th:text="${srcPost.title}">소개글 제목</h2>
|
||||
<p>
|
||||
최종 수정일: <span th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(srcPost.modifyTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm')}"></span>
|
||||
</p>
|
||||
</header>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wrapper style1">
|
||||
<div class="container">
|
||||
<article>
|
||||
<div id="editor"></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
<script>
|
||||
// DOM 로드 완료 후 Quill 에디터를 읽기 전용(false)으로 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initEditor(false);
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
</html>
|
||||
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/default_layout}">
|
||||
<head>
|
||||
<title>Bookmarks</title>
|
||||
</head>
|
||||
<th:block layout:fragment="content">
|
||||
<section class="wrapper style2">
|
||||
<div class="container">
|
||||
<header class="major">
|
||||
<h2>Bookmarks</h2>
|
||||
<p>다른 사용자들이 저장한 유용한 페이지들을 둘러보세요.</p>
|
||||
</header>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wrapper style1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12" th:if="${bookmarksPage.empty}">
|
||||
<p style="text-align: center;">아직 저장된 페이지가 없습니다.</p>
|
||||
</div>
|
||||
<div class="col-4 col-6-medium col-12-small" th:each="bookmark : ${bookmarksPage.content}">
|
||||
<section class="box feature">
|
||||
<a href="javascript:void(0);"
|
||||
th:data-url="${bookmark.url}"
|
||||
th:data-title="${bookmark.title}"
|
||||
onclick="showBookmarkOptions(this)" class="image featured">
|
||||
<img th:src="${bookmark.thumbnailUrl ?: '/images/pic01.jpg'}" alt="Thumbnail" />
|
||||
</a>
|
||||
<div class="inner">
|
||||
<header>
|
||||
<h3 th:text="${bookmark.title}">북마크 제목</h3>
|
||||
<p th:if="${bookmark.userComment}" th:text="${bookmark.userComment}" style="font-style: italic; color: #007bff;"></p>
|
||||
</header>
|
||||
<p th:text="${#strings.abbreviate(bookmark.description, 100)}"></p>
|
||||
|
||||
<div class="bookmark-controls" style="margin-top: 1em; padding-top: 1em; border-top: 1px solid #eee;">
|
||||
<div class="vote-controls" th:data-bookmark-id="${bookmark.id}" style="text-align: center; margin-bottom: 1em;">
|
||||
<button class="button small alt" th:onclick="handleBookmarkVote(this, 'like')">
|
||||
👍 (<span class="like-count" th:text="${bookmark.voteCount}">0</span>)
|
||||
</button>
|
||||
<button class="button small alt" th:onclick="handleBookmarkVote(this, 'unlike')" style="margin-left: 0.5em;">
|
||||
👎 (<span class="unlike-count" th:text="${bookmark.unlikeCount}">0</span>)
|
||||
</button>
|
||||
</div>
|
||||
<a href="javascript:void(0);" th:onclick="toggleCommentSection('[[${bookmark.id}]]')" class="button small fit">댓글 보기</a>
|
||||
<div th:id="'comment-section-' + ${bookmark.id}" class="comment-section" style="display: none; margin-top: 1em;">
|
||||
<div th:id="'comments-list-' + ${bookmark.id}" class="comments-list"></div>
|
||||
<textarea th:id="'comment-input-' + ${bookmark.id}" placeholder="댓글을 입력하세요..." style="margin-top: 1em;"></textarea>
|
||||
<button class="button small" th:onclick="submitBookmarkComment('[[${bookmark.id}]]')">등록</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer style="font-size: 0.8em; color: #888; text-align: right; margin-top: 1em;">
|
||||
by <span th:text="${bookmark.userId}"></span>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav th:if="${bookmarksPage.totalPages > 1}" style="text-align: center; margin-top: 2.5em;">
|
||||
<ul class="pagination">
|
||||
<li th:classappend="${bookmarksPage.first} ? 'disabled'">
|
||||
<a th:href="@{/bookmarks(page=${bookmarksPage.number - 1})}" class="button alt small">Prev</a>
|
||||
</li>
|
||||
<li th:each="pageNum : ${#numbers.sequence(0, bookmarksPage.totalPages - 1)}">
|
||||
<a th:href="@{/bookmarks(page=${pageNum})}"
|
||||
th:text="${pageNum + 1}"
|
||||
th:class="${pageNum == bookmarksPage.number} ? 'button small' : 'button alt small'"></a>
|
||||
</li>
|
||||
<li th:classappend="${bookmarksPage.last} ? 'disabled'">
|
||||
<a th:href="@{/bookmarks(page=${bookmarksPage.number + 1})}" class="button alt small">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
</html>
|
||||
@@ -7,11 +7,7 @@
|
||||
>
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.js"></script>
|
||||
<script>document.addEventListener('DOMContentLoaded', function() {initEditor(true)});</script>
|
||||
|
||||
</th:block>
|
||||
|
||||
<body>
|
||||
@@ -110,6 +106,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.js"></script>
|
||||
<script>document.addEventListener('DOMContentLoaded', function() {initEditor(true)});</script>
|
||||
</th:block>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +1,10 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.w3.org/1999/xhtml" layout:decorate="~{layout/default_layout}">
|
||||
<head>
|
||||
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<th:block layout:fragment="content" id="content">
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/default_layout}">
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
<style>
|
||||
.message-list { list-style: none; padding-left: 0; }
|
||||
.message-item { border: 1px solid #ddd; border-radius: 5px; margin-bottom: 1em; }
|
||||
.message-header { padding: 10px 15px; background: #f7f7f7; cursor: pointer; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; }
|
||||
.message-item.unread .message-header { font-weight: bold; background: #fffbe5; border-left: 3px solid #FFA500; }
|
||||
.message-content { padding: 15px; display: none; border-top: 1px solid #ddd; }
|
||||
.message-content.active { display: block; }
|
||||
.reply-form { margin-top: 1em; }
|
||||
.message-title { flex-grow: 1; }
|
||||
</style>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="content">
|
||||
<section class="wrapper style1">
|
||||
<div class="container">
|
||||
<header class="major">
|
||||
<h2 th:text="${pageTitle}"></h2>
|
||||
</header>
|
||||
|
||||
<div class="box">
|
||||
<ul class="message-list">
|
||||
<li th:if="${#lists.isEmpty(messages)}">받은 쪽지가 없습니다.</li>
|
||||
<li th:each="msg : ${messages}" class="message-item" th:classappend="${!msg.isRead} ? 'unread'" th:data-message-id="${msg.id}">
|
||||
<div class="message-header" onclick="toggleMessage(this)">
|
||||
<div class="message-title">
|
||||
<span th:text="${msg.title}"></span>
|
||||
<small style="margin-left: 1em; color: #777;" th:text="'보낸 사람: ' + ${msg.senderId}"></small>
|
||||
</div>
|
||||
<small th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(msg.timestamp).atZone(T(java.time.ZoneId).systemDefault()), 'yyyy-MM-dd HH:mm')}"></small>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<p style="white-space: pre-wrap;" th:text="${msg.content}"></p>
|
||||
<hr/>
|
||||
<div class="reply-form">
|
||||
<h4>답장 보내기</h4>
|
||||
<form onsubmit="sendMessage(event, this)">
|
||||
<input type="hidden" name="receiverId" th:value="${msg.senderId}" />
|
||||
<div class="row gtr-50">
|
||||
<div class="col-12">
|
||||
<input type="text" name="title" placeholder="제목" th:value="'RE: ' + ${msg.title}" required />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<textarea name="content" placeholder="내용" rows="4" required></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="button primary">답장 전송</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script th:inline="javascript">
|
||||
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
|
||||
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content');
|
||||
|
||||
async function toggleMessage(headerElement) {
|
||||
const messageItem = headerElement.closest('.message-item');
|
||||
const content = messageItem.querySelector('.message-content');
|
||||
const messageId = messageItem.dataset.messageId;
|
||||
|
||||
const isOpening = !content.classList.contains('active');
|
||||
|
||||
// 모든 열린 쪽지 닫기
|
||||
document.querySelectorAll('.message-content.active').forEach(c => {
|
||||
if(c !== content) c.classList.remove('active');
|
||||
});
|
||||
content.classList.toggle('active');
|
||||
|
||||
if (isOpening && messageItem.classList.contains('unread')) {
|
||||
const response = await fetch(`/messages/${messageId}/read`, {
|
||||
method: 'POST',
|
||||
headers: { [csrfHeader]: csrfToken }
|
||||
});
|
||||
if (response.ok) {
|
||||
messageItem.classList.remove('unread');
|
||||
// 헤더의 아이콘도 업데이트 할 수 있지만, 페이지 새로고침 전까지는 유지됩니다.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage(event, form) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
const data = {
|
||||
receiverId: formData.get('receiverId'),
|
||||
title: formData.get('title'),
|
||||
content: formData.get('content')
|
||||
};
|
||||
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = '전송 중...';
|
||||
|
||||
const response = await fetch('/messages/send', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', [csrfHeader]: csrfToken },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if(response.ok) {
|
||||
alert('답장을 성공적으로 보냈습니다.');
|
||||
form.querySelector('textarea').value = ''; // 내용만 초기화
|
||||
toggleMessage(form.closest('.message-item').querySelector('.message-header')); // 답장 후 창 닫기
|
||||
} else {
|
||||
alert('답장 보내기에 실패했습니다.');
|
||||
}
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = '답장 전송';
|
||||
}
|
||||
</script>
|
||||
</th:block>
|
||||
</html>
|
||||
@@ -15,6 +15,49 @@
|
||||
.user-list li, .post-list li { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee; }
|
||||
.user-list li:last-child, .post-list li:last-child { border-bottom: none; }
|
||||
.button.small { margin-left: 0.5em; }
|
||||
.custom-radio {
|
||||
display: none; /* 기본 라디오 버튼 숨기기 */
|
||||
}
|
||||
|
||||
.custom-label {
|
||||
position: relative;
|
||||
padding-left: 25px; /* 라벨 왼쪽에 가짜 버튼을 위한 공간 확보 */
|
||||
cursor: pointer;
|
||||
line-height: 20px;
|
||||
display: inline-block;
|
||||
color: #555; /* 라벨 텍스트 색상 */
|
||||
}
|
||||
|
||||
/* 가짜 라디오 버튼 (원) 만들기 */
|
||||
.custom-label::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 50%; /* 원 모양 */
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 선택되었을 때 가짜 라디오 버튼 스타일 변경 */
|
||||
.custom-radio:checked + .custom-label::before {
|
||||
border-color: #FFA500; /* 테두리 색상 변경 (사이트의 포인트 색상) */
|
||||
background: #FFA500; /* 배경 색상 채우기 */
|
||||
}
|
||||
|
||||
/* 선택되었을 때 원 안에 작은 점 추가 */
|
||||
.custom-radio:checked + .custom-label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
|
||||
@@ -31,10 +74,15 @@
|
||||
<div class="tab-link" onclick="openTab(event, 'myPosts')">내가 쓴 글</div>
|
||||
<div class="tab-link" onclick="openTab(event, 'myComments')">내가 쓴 댓글</div>
|
||||
<div class="tab-link" onclick="openTab(event, 'myRanks')">내 게임 랭킹</div>
|
||||
<div class="tab-link" onclick="openTab(event, 'myMessages')">쪽지함</div>
|
||||
<div class="tab-link" onclick="openTab(event, 'myBookmarks')">저장한 페이지</div>
|
||||
|
||||
<th:block sec:authorize="hasRole('ADMIN')">
|
||||
<div class="tab-link" onclick="openTab(event, 'userManagement')">회원 관리</div>
|
||||
<div class="tab-link" onclick="openTab(event, 'postManagement')">게시물 관리</div>
|
||||
<div class="tab-link" onclick="openTab(event, 'bannerManagement')">배너 관리</div>
|
||||
<div class="tab-link" onclick="openTab(event, 'aboutManagement')">사이트 소개 관리</div>
|
||||
|
||||
</th:block>
|
||||
</div>
|
||||
|
||||
@@ -78,7 +126,57 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="myBookmarks" class="tab-content">
|
||||
<div class="box">
|
||||
<h4>새 페이지 저장하기</h4>
|
||||
<div id="bookmark-form">
|
||||
<input type="url" id="bookmark-url-input" placeholder="저장할 페이지 URL을 입력하세요" style="margin-bottom: 1em;">
|
||||
<div id="og-preview" style="display:none; border: 1px solid #ddd; padding: 1em; margin-bottom: 1em; border-radius: 5px;">
|
||||
<img id="og-image" src="" style="max-width: 150px; float: left; margin-right: 1em;">
|
||||
<h5 id="og-title"></h5>
|
||||
<p id="og-description" style="font-size: 0.9em; color: #555;"></p>
|
||||
</div>
|
||||
<textarea id="bookmark-comment-input" placeholder="이 페이지에 대한 나의 생각 (선택)" rows="3"></textarea>
|
||||
<div id="visibility-selector" style="margin-top: 1em; display: flex; align-items: center; flex-wrap: wrap;">
|
||||
<strong style="margin-right: 1.5em;">공개 범위:</strong>
|
||||
|
||||
<div style="display: flex; align-items: center; margin-right: 1.5em;">
|
||||
<input type="radio" name="visibility" id="visibility-private" value="PRIVATE" class="custom-radio" checked>
|
||||
<label for="visibility-private" class="custom-label">비공개</label>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; margin-right: 1.5em;">
|
||||
<input type="radio" name="visibility" id="visibility-members" value="MEMBERS" class="custom-radio">
|
||||
<label for="visibility-members" class="custom-label">회원 공개</label>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<input type="radio" name="visibility" id="visibility-public" value="PUBLIC" class="custom-radio">
|
||||
<label for="visibility-public" class="custom-label">전체 공개</label>
|
||||
</div>
|
||||
</div>
|
||||
<button id="save-bookmark-btn" class="button primary" style="margin-top: 1em;">저장하기</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box" style="margin-top: 2em;">
|
||||
<h4>저장된 목록</h4>
|
||||
<div id="bookmarks-list" class="row">
|
||||
<div class="col-4 col-12-medium">
|
||||
<section class="box feature">
|
||||
<a href="#" class="image featured"><img src="/images/pic01.jpg" alt="" /></a>
|
||||
<div class="inner">
|
||||
<header>
|
||||
<h2>카드 제목</h2>
|
||||
<p>사용자 코멘트가 여기에 들어갑니다.</p>
|
||||
</header>
|
||||
<p style="font-size: 0.8em; color: #888;">원본 페이지 설명...</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="myRanks" class="tab-content">
|
||||
<div class="box">
|
||||
<ul class="post-list">
|
||||
@@ -104,7 +202,27 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="myMessages" class="tab-content">
|
||||
<div class="box">
|
||||
<ul class="post-list">
|
||||
<li th:if="${#lists.isEmpty(myMessages)}">주고받은 쪽지가 없습니다.</li>
|
||||
<li th:each="msg : ${myMessages}">
|
||||
<div style="display: flex; align-items: center; gap: 1em;">
|
||||
<span th:if="${msg.senderId == user.user_id}" class="tag-item" style="background: #e0f7fa;">보냄</span>
|
||||
<span th:if="${msg.receiverId == user.user_id}" class="tag-item" style="background: #fffbe5;">받음</span>
|
||||
|
||||
<div style="min-width: 150px;">
|
||||
<strong th:if="${msg.senderId == user.user_id}" th:text="'To: ' + ${msg.receiverId}"></strong>
|
||||
<strong th:if="${msg.receiverId == user.user_id}" th:text="'From: ' + ${msg.senderId}"></strong>
|
||||
</div>
|
||||
|
||||
<a th:href="@{/messages}" th:text="${msg.title}">쪽지 제목</a>
|
||||
</div>
|
||||
<span th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(msg.timestamp).atZone(T(java.time.ZoneId).systemDefault()), 'yyyy-MM-dd HH:mm')}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="userManagement" class="tab-content" sec:authorize="hasRole('ADMIN')">
|
||||
<div class="box">
|
||||
<h4>권한 요청</h4>
|
||||
@@ -154,7 +272,8 @@
|
||||
<ul class="post-list">
|
||||
<li th:each="image : ${allImages}" th:id="'image-row-' + ${image.id}">
|
||||
<div style="display: flex; align-items: center; gap: 1em;">
|
||||
<img th:src="@{'/api/images/' + ${image.fileName}}" alt="Image Thumbnail" style="width: 100px; height: 60px; object-fit: cover; border-radius: 4px;"/>
|
||||
<!-- <img th:src="${post.thumb != null and not #strings.isEmpty(post.thumb)} ? ${apiBaseUrl + post.thumb} : @{/images/pic01.jpg}" alt="Post Thumbnail" />-->
|
||||
<img th:src="${apiBaseUrl + '/api/images/' + image.fileName + '?type=thumbnail'}" alt="Image Thumbnail" style="width: 100px; height: 60px; object-fit: cover; border-radius: 4px;"/>
|
||||
<div>
|
||||
<strong th:text="${image.fileName}"></strong><br>
|
||||
<span th:if="${image.isBannerCandidate}" style="color: #2a9d8f; font-weight: bold;">(배너로 사용 중)</span>
|
||||
@@ -170,6 +289,27 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="aboutManagement" class="tab-content" sec:authorize="hasRole('ADMIN')">
|
||||
<div class="box">
|
||||
<h4>사이트 소개글 관리</h4>
|
||||
<p>
|
||||
'사이트 소개' 페이지에 표시될 내용입니다. 글을 수정하면 이전 버전은 히스토리로 여기에 남게 됩니다.
|
||||
</p>
|
||||
<th:block th:with="latestAbout=${!#lists.isEmpty(aboutPostHistory) ? aboutPostHistory[0] : null}">
|
||||
<a th:if="${latestAbout != null}" th:href="@{/blog/edit/{postId}(postId=${latestAbout.id})}" class="button primary">최신 소개글 수정</a>
|
||||
<a th:if="${latestAbout == null}" th:href="@{/blog/edit(type='ABOUT_SITE')}" class="button">새 소개글 작성</a>
|
||||
</th:block>
|
||||
|
||||
<h5 style="margin-top: 2em;">수정 히스토리</h5>
|
||||
<ul class="post-list">
|
||||
<li th:each="post : ${aboutPostHistory}">
|
||||
<a th:href="@{'/blog/viewer/' + ${post.id}}" th:text="${post.title}">수정된 버전 제목</a>
|
||||
<span th:text="|수정일: ${#dates.format(post.modifyTime, 'yyyy-MM-dd HH:mm')}|"></span>
|
||||
</li>
|
||||
<li th:if="${#lists.isEmpty(aboutPostHistory)}">작성된 소개글이 없습니다.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
@@ -315,6 +455,83 @@
|
||||
alert('작업 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const urlInput = document.getElementById('bookmark-url-input');
|
||||
const preview = document.getElementById('og-preview');
|
||||
|
||||
let ogData = {}; // OG 파싱 결과를 저장할 변수
|
||||
|
||||
// URL 입력 필드에서 포커스가 벗어났을 때(onblur) OG 정보 파싱 API 호출
|
||||
urlInput.addEventListener('blur', async function() {
|
||||
const url = this.value.trim();
|
||||
if (!url) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/og/parse?url=${encodeURIComponent(url)}`);
|
||||
if (!response.ok) throw new Error('파싱 실패');
|
||||
|
||||
ogData = await response.json();
|
||||
|
||||
// 미리보기 UI 업데이트
|
||||
document.getElementById('og-title').textContent = ogData.title || '제목 없음';
|
||||
document.getElementById('og-description').textContent = ogData.description || '';
|
||||
const ogImage = document.getElementById('og-image');
|
||||
if (ogData.thumbnailUrl) {
|
||||
ogImage.src = ogData.thumbnailUrl;
|
||||
ogImage.style.display = 'block';
|
||||
} else {
|
||||
ogImage.style.display = 'none';
|
||||
}
|
||||
preview.style.display = 'block';
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
preview.style.display = 'none';
|
||||
alert('페이지 정보를 가져오는 데 실패했습니다. URL을 확인해주세요.');
|
||||
}
|
||||
});
|
||||
|
||||
// 저장 버튼 클릭 이벤트
|
||||
document.getElementById('save-bookmark-btn').addEventListener('click', async function() {
|
||||
const comment = document.getElementById('bookmark-comment-input').value.trim();
|
||||
|
||||
// [수정] 선택된 공개 범위(visibility) 값을 읽어오는 코드 추가
|
||||
const visibility = document.querySelector('input[name="visibility"]:checked').value;
|
||||
|
||||
const bookmarkData = {
|
||||
url: urlInput.value.trim(),
|
||||
// title, description 등은 ogData에서 가져오는 것은 그대로 유지
|
||||
title: ogData.title,
|
||||
description: ogData.description,
|
||||
thumbnailUrl: ogData.thumbnailUrl,
|
||||
userComment: comment,
|
||||
// [수정] bookmarkData 객체에 visibility 프로퍼티 추가
|
||||
visibility: visibility
|
||||
};
|
||||
|
||||
if (!bookmarkData.url) {
|
||||
alert('URL을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 서버에 북마크 저장 요청 (이하 코드는 동일)
|
||||
const response = await fetch('/user/bookmarks/save', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[csrfHeader]: csrfToken
|
||||
},
|
||||
body: JSON.stringify(bookmarkData)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('페이지가 저장되었습니다.');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('저장에 실패했습니다.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
</html>
|
||||
@@ -5,14 +5,7 @@
|
||||
layout:decorate="~{layout/default_layout}">
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.js"></script>
|
||||
<script>document.addEventListener('DOMContentLoaded', function() {
|
||||
initEditor(false)
|
||||
fetchComments(serverData.id);
|
||||
});</script>
|
||||
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="content" id="content">
|
||||
@@ -108,5 +101,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill-table-better@1/dist/quill-table-better.js"></script>
|
||||
<script>document.addEventListener('DOMContentLoaded', function() {
|
||||
initEditor(false)
|
||||
fetchComments(serverData.id);
|
||||
});</script>
|
||||
</th:block>
|
||||
</html>
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">>
|
||||
<th:block th:fragment="footer">
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
@@ -9,60 +10,66 @@
|
||||
/*]]>*/
|
||||
|
||||
</script>
|
||||
<div id="footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<section class="col-3 col-6-narrower col-12-mobilep">
|
||||
<h3 id="ranking-title">Rank of Views</h3>
|
||||
<ul class="rank_of_view" >
|
||||
</ul>
|
||||
</section>
|
||||
<section class="col-3 col-6-narrower col-12-mobilep">
|
||||
<h3>Recent of Posts</h3>
|
||||
<ul class="recent_posts">
|
||||
<div id="footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<section class="col-3 col-6-narrower col-12-mobilep">
|
||||
<h3 id="ranking-title">Rank of Views</h3>
|
||||
<ul class="rank_of_view" >
|
||||
</ul>
|
||||
</section>
|
||||
<section class="col-3 col-6-narrower col-12-mobilep">
|
||||
<h3>Recent of Posts</h3>
|
||||
<ul class="recent_posts">
|
||||
|
||||
</ul>
|
||||
</section>
|
||||
<section class="col-6 col-12-narrower">
|
||||
<h3>SEND TO ME(TELEGRAM BOT)</h3>
|
||||
<div id="tlg_form" >
|
||||
<div class="row gtr-50">
|
||||
<div class="col-6 col-12-mobilep">
|
||||
</ul>
|
||||
</section>
|
||||
<section class="col-6 col-12-narrower">
|
||||
<h3>SEND TO ME(TELEGRAM BOT)</h3>
|
||||
<div id="tlg_form" >
|
||||
<div class="row gtr-50">
|
||||
<div class="col-6 col-12-mobilep">
|
||||
<div sec:authorize="isAuthenticated()">
|
||||
<input type="text" name="name" id="name" placeholder="Name" th:value="${#authentication.principal.username}" readonly />
|
||||
</div>
|
||||
<div sec:authorize="isAnonymous()">
|
||||
<input type="text" name="name" id="name" placeholder="Name" />
|
||||
</div>
|
||||
<div class="col-6 col-12-mobilep">
|
||||
<input type="email" name="email" id="email" placeholder="Email" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<textarea name="message" id="message" placeholder="Message" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<ul class="actions">
|
||||
<li><input type="submit" class="button alt" value="Send Message" onclick="callSendTlg()" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-12-mobilep">
|
||||
<input type="email" name="email" id="email" placeholder="Email" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<textarea name="message" id="message" placeholder="Message" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<ul class="actions">
|
||||
<li><input type="submit" class="button alt" value="Send Message" onclick="callSendTlg()" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Icons -->
|
||||
<ul class="icons">
|
||||
<li><a href="#" class="icon brands fa-twitter"><span class="label">Twitter</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-facebook-f"><span class="label">Facebook</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-github"><span class="label">GitHub</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-linkedin-in"><span class="label">LinkedIn</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-google-plus-g"><span class="label">Google+</span></a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Copyright -->
|
||||
<div class="copyright">
|
||||
<ul class="menu">
|
||||
<li>©lunaticbum All rights reserved</li><li>Origin Design from:<a href="http://html5up.net">HTML5 UP</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Icons -->
|
||||
<ul class="icons">
|
||||
<li><a href="#" class="icon brands fa-twitter"><span class="label">Twitter</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-facebook-f"><span class="label">Facebook</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-github"><span class="label">GitHub</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-linkedin-in"><span class="label">LinkedIn</span></a></li>
|
||||
<li><a href="#" class="icon brands fa-google-plus-g"><span class="label">Google+</span></a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Copyright -->
|
||||
<div class="copyright">
|
||||
<ul class="menu">
|
||||
<li>©lunaticbum All rights reserved</li><li>Origin Design from:<a href="http://html5up.net">HTML5 UP</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
|
||||
@@ -15,19 +15,21 @@
|
||||
<ul>
|
||||
<li id="menu_home" ><a th:href="@{/}">Home</a></li>
|
||||
<li id="menu_posts"><a href="blog/posts">Posts</a></li>
|
||||
<li id="menu_nonogram"><a href="puzzle/play">Nonogram</a></li>
|
||||
<li id="menu_2048"><a href="puzzle/2048">2048</a></li>
|
||||
<li id="menu_sudoku"><a href="puzzle/sudoku">sudoku</a></li>
|
||||
<li id="menu_spider"><a href="puzzle/spider">spider</a></li>
|
||||
<!-- <li id="menu_sec"><a href="left-sidebar">Left Sidebar</a></li>-->
|
||||
<!-- <li id="menu_thr"><a href="right-sidebar">Right Sidebar</a></li>-->
|
||||
<!-- <li id="menu_four"><a href="two-sidebar">Two Sidebar</a></li>-->
|
||||
<li id="menu_bookmarks"><a href="/bookmarks">Bookmarks</a></li>
|
||||
<li id="menu_drop">
|
||||
<a href="#">Game</a>
|
||||
<ul>
|
||||
<li id="menu_nonogram"><a href="puzzle/play">Nonogram</a></li>
|
||||
<li id="menu_2048"><a href="puzzle/2048">2048</a></li>
|
||||
<li id="menu_sudoku"><a href="puzzle/sudoku">sudoku</a></li>
|
||||
<li id="menu_spider"><a href="puzzle/spider">spider</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="menu_drop">
|
||||
<a href="#">About</a>
|
||||
<ul>
|
||||
<li><a href="javascript:gotoWhere()">bums's where</a></li>
|
||||
<li><a href="#">Magna phasellus</a></li>
|
||||
<li><a href="#">Etiam sed tempus</a></li>
|
||||
<li><a href="javascript:gotoBUMSpace()">BUM'sPase</a></li>
|
||||
<li>
|
||||
<a href="#">Submenu</a>
|
||||
<ul>
|
||||
@@ -46,9 +48,12 @@
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<th:block sec:authorize="isAuthenticated()">
|
||||
<li><a th:href="@{/user/info}">내 정보</a></li>
|
||||
</th:block>
|
||||
<li sec:authorize="isAuthenticated()">
|
||||
<a href="/user/info" th:text="${#authentication.principal.username}">사용자ID</a>
|
||||
<a href="/messages" id="message-icon" style="display: none; color: #FFA500; margin-left: 5px;" title="새 쪽지">
|
||||
쪽지함<i class="icon solid fa-envelope"></i>
|
||||
</a>
|
||||
</li>
|
||||
<th:block sec:authorize="!isAuthenticated()">
|
||||
<li id="menu_login">
|
||||
<a class="open-login-popup" to="#loginPopup">Login</a>
|
||||
|
||||
@@ -73,7 +73,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="iframe-viewer-popup" class="pop_layer" style="width: 90%; height: 90%; max-width: 1400px;">
|
||||
<div class="pop_container" style="height: 100%; display: flex; flex-direction: column;">
|
||||
<div class="pop_header" style="display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; border-bottom: 1px solid #eee; background: #f8f8f8;">
|
||||
<h4 id="iframe-viewer-title" style="margin: 0; font-size: 1em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"></h4>
|
||||
<a href="#" class="btn_layerClose" style="font-size: 1.5em;" onclick="closePopup()">×</a>
|
||||
</div>
|
||||
<div class="pop_conts" style="flex-grow: 1; padding: 0;">
|
||||
<iframe id="bookmark-iframe" src="" style="width: 100%; height: 100%; border: none;">
|
||||
이 브라우저는 iframe을 지원하지 않습니다.
|
||||
</iframe>
|
||||
</div>
|
||||
<div class="pop_footer" style="padding: 10px 20px; border-top: 1px solid #eee; background: #f8f8f8; text-align: center; font-size: 0.9em;">
|
||||
콘텐츠가 표시되지 않나요?
|
||||
<a id="iframe-open-new-tab-link" href="#" target="_blank" class="button small alt" style="margin-left: 1em; vertical-align: middle;" onclick="closePopup()">새 탭에서 열기</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="unified-game-success-modal" class="pop_layer">
|
||||
<div class="pop_container"> <div class="pop_conts"> <h2 id="ugsm-title">🎉 성공! 🎉</h2>
|
||||
<p id="ugsm-message">여기에 성공 메시지가 표시됩니다.</p>
|
||||
|
||||
Reference in New Issue
Block a user